naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
181 lines • 6.75 kB
JavaScript
import { keyBy, pushToArrayMap } from "@naisys/common";
import table from "text-table";
import { usersCmd } from "../command/commandDefs.js";
function buildHierarchy(users) {
const nodeMap = new Map();
const roots = [];
for (const user of users) {
nodeMap.set(user.userId, { ...user, children: [] });
}
for (const user of users) {
const node = nodeMap.get(user.userId);
if (user.leadUserId && nodeMap.has(user.leadUserId)) {
nodeMap.get(user.leadUserId).children.push(node);
}
else {
roots.push(node);
}
}
return roots;
}
function flattenHierarchy(nodes, hiddenCounts, depth = 0) {
const result = [];
const sortedNodes = [...nodes].sort((a, b) => a.username.localeCompare(b.username));
for (const node of sortedNodes) {
result.push({ type: "user", node, depth });
result.push(...flattenHierarchy(node.children, hiddenCounts, depth + 1));
const hidden = hiddenCounts.get(node.userId) ?? 0;
if (hidden > 0) {
result.push({ type: "hidden", count: hidden, depth: depth + 1 });
}
}
return result;
}
/**
* Collect the set of user IDs relevant to the current agent:
* - All root nodes (no lead)
* - Superiors chain (walking up leadUserId)
* - Peers (direct subordinates of each superior, i.e. siblings)
* - All subordinates (recursively walking down)
*/
function getRelevantUserIds(allUsers, currentUserId) {
const relevant = new Set();
const byId = keyBy(allUsers, (u) => u.userId);
const childrenOf = new Map();
for (const u of allUsers) {
if (u.leadUserId != null) {
pushToArrayMap(childrenOf, u.leadUserId, u.userId);
}
}
// Root nodes
for (const u of allUsers) {
if (!u.leadUserId) {
relevant.add(u.userId);
}
}
// Walk up the chain (superiors) and include peers at each level
let cursor = currentUserId;
while (true) {
relevant.add(cursor);
const user = byId.get(cursor);
if (!user?.leadUserId)
break;
// Add peers (all direct subordinates of our superior)
for (const peerId of childrenOf.get(user.leadUserId) ?? []) {
relevant.add(peerId);
}
cursor = user.leadUserId;
}
// Walk down: all subordinates recursively
const addDescendants = (parentId) => {
for (const childId of childrenOf.get(parentId) ?? []) {
relevant.add(childId);
addDescendants(childId);
}
};
addDescendants(currentUserId);
return relevant;
}
export function createUserDisplayService(userService, inputMode, localUserId) {
function handleCommand(cmdArgs) {
const targetUsername = cmdArgs.trim();
let perspectiveUserId = localUserId;
if (targetUsername) {
const targetUser = userService.getUserByName(targetUsername);
if (!targetUser) {
return `Error: user '${targetUsername}' not found`;
}
perspectiveUserId = targetUser.userId;
}
// Filter ephemerals to those owned by this perspective so foreign
// ephemerals are not counted in the hidden-children rollup either.
const allUsers = userService.getVisibleUsers(perspectiveUserId);
if (allUsers.length === 0) {
return "No users found.";
}
const userItems = allUsers.map((u) => ({
userId: u.userId,
username: u.username,
title: u.config.title,
leadUserId: u.leadUserId,
}));
const relevantIds = getRelevantUserIds(userItems, perspectiveUserId);
const filteredItems = userItems.filter((u) => relevantIds.has(u.userId));
// Build full children map for hidden-count computation
const fullChildrenOf = new Map();
for (const u of userItems) {
if (u.leadUserId != null) {
pushToArrayMap(fullChildrenOf, u.leadUserId, u.userId);
}
}
// Count all descendants of a user in the full tree
const countAllDescendants = (userId) => {
let count = 0;
for (const childId of fullChildrenOf.get(userId) ?? []) {
count += 1 + countAllDescendants(childId);
}
return count;
};
// For each visible node, count hidden users in its direct hidden branches
// (visible children handle their own hidden subtrees)
const hiddenCounts = new Map();
for (const u of filteredItems) {
let count = 0;
for (const childId of fullChildrenOf.get(u.userId) ?? []) {
if (!relevantIds.has(childId)) {
count += 1 + countAllDescendants(childId);
}
}
if (count > 0) {
hiddenCounts.set(u.userId, count);
}
}
const hierarchy = buildHierarchy(filteredItems);
const flattened = flattenHierarchy(hierarchy, hiddenCounts);
// Build userId → username lookup for lead display
const userById = keyBy(userItems, (u) => u.userId);
const isDebug = inputMode.isDebug();
const headers = ["Username", "Title", "Lead", "Status"];
if (isDebug) {
headers.push("*Host");
}
const rows = flattened.map((entry) => {
if (entry.type === "hidden") {
const indent = " ".repeat(entry.depth);
const row = [`${indent}(+${entry.count} not shown)`, "", "", ""];
if (isDebug)
row.push("");
return row;
}
const { node, depth } = entry;
const indent = " ".repeat(depth);
const displayName = `${indent}${node.username}`;
const leadUsername = node.leadUserId
? userById.get(node.leadUserId)?.username || "(unknown)"
: "(none)";
const row = [
displayName,
node.title,
leadUsername,
userService.getUserStatus(node.userId),
];
if (isDebug) {
row.push(userService.getUserHostDisplayNames(node.userId).join(", ") || "");
}
return row;
});
let output = table([headers, ...rows], { hsep: " | " });
if (isDebug) {
output += "\n* Only visible in debug mode";
}
return output;
}
const registrableCommand = {
command: usersCmd,
handleCommand,
};
return {
...registrableCommand,
};
}
//# sourceMappingURL=userDisplayService.js.map