agent-yes 1.122.2 → 1.123.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/default.config.yaml +19 -0
- package/dist/{SUPPORTED_CLIS-BTu2brih.js → SUPPORTED_CLIS-B4O2cFlt.js} +2 -2
- package/dist/SUPPORTED_CLIS-DHkqGoNv.js +8 -0
- package/dist/{agent-yes.config-z-IPzH5U.js → agent-yes.config-D6ycMApr.js} +2 -65
- package/dist/cli.js +6 -6
- package/dist/configShared-C5QaNPnz.js +71 -0
- package/dist/{globalPidIndex-gZuTvTBs.js → globalPidIndex-C7r2m6s7.js} +19 -20
- package/dist/index.js +4 -4
- package/dist/pidStore-C4c2O15q.js +5 -0
- package/dist/{pidStore-B5vBu8Px.js → pidStore-CGKIhaJO.js} +5 -4
- package/dist/reaper-BLVA780B.js +3 -0
- package/dist/{reaper-Dj8R7ltI.js → reaper-BkjPN7mw.js} +24 -2
- package/dist/{remotes-CpGcTr7A.js → remotes-BRCDVnR7.js} +1 -1
- package/dist/{remotes-D2fqaRU8.js → remotes-D8GvSbhf.js} +1 -1
- package/dist/{schedule-DgRrdA_n.js → schedule-DULdIkU9.js} +7 -7
- package/dist/{serve-tn7ZetZs.js → serve-r_2v9EKc.js} +202 -58
- package/dist/{setup-dZhgpNse.js → setup-DHa6fX8M.js} +3 -3
- package/dist/{share-CksllWW-.js → share-YuM6-Q6A.js} +78 -4
- package/dist/{subcommands-D9BWZilr.js → subcommands-B13Kto-u.js} +647 -32
- package/dist/subcommands-Tv6AwUkD.js +7 -0
- package/dist/{tray-DjCIyakK.js → tray-BVnJLThD.js} +1 -1
- package/dist/{ts-CIf0uaR7.js → ts-DgukRoEI.js} +10 -7
- package/dist/{versionChecker-DjxKi4qe.js → versionChecker-BqOr1YqC.js} +2 -2
- package/dist/{workspaceConfig-XP2NEWmV.js → workspaceConfig-BJO4fzEn.js} +1 -1
- package/lab/ui/console-logic.js +222 -10
- package/lab/ui/icon.svg +5 -0
- package/lab/ui/index.html +689 -14
- package/lab/ui/landing.html +276 -0
- package/lab/ui/manifest.webmanifest +14 -0
- package/lab/ui/sw.js +56 -0
- package/package.json +5 -1
- package/ts/agentTree.spec.ts +92 -0
- package/ts/agentTree.ts +149 -0
- package/ts/configShared.ts +4 -0
- package/ts/globalPidIndex.ts +28 -20
- package/ts/idleWaiter.spec.ts +7 -1
- package/ts/index.ts +9 -0
- package/ts/lsWatch.spec.ts +61 -0
- package/ts/lsWatch.ts +94 -0
- package/ts/needsInput.spec.ts +55 -0
- package/ts/needsInput.ts +68 -0
- package/ts/pidStore.ts +3 -0
- package/ts/reaper.spec.ts +26 -2
- package/ts/reaper.ts +25 -0
- package/ts/resultEnvelope.spec.ts +43 -0
- package/ts/resultEnvelope.ts +88 -0
- package/ts/serve.ts +276 -41
- package/ts/share.ts +156 -3
- package/ts/subcommands.ts +0 -0
- package/ts/todoParse.spec.ts +68 -0
- package/ts/todoParse.ts +88 -0
- package/ts/utils.spec.ts +4 -1
- package/dist/SUPPORTED_CLIS-DcOKE9Nz.js +0 -8
- package/dist/pidStore-7y1cTcAE.js +0 -5
- package/dist/reaper-HqcUms2d.js +0 -3
- package/dist/subcommands-D8sHibKu.js +0 -6
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import "./logger-B9h0djqx.js";
|
|
2
|
+
import "./globalPidIndex-C7r2m6s7.js";
|
|
3
|
+
import "./configShared-C5QaNPnz.js";
|
|
4
|
+
import "./remotes-D8GvSbhf.js";
|
|
5
|
+
import { _ as stopTipForCli, a as extractNeedsInput, c as isPidAlive, d as matchKeyword, f as readNotes, g as snapshotStatus, h as runSubcommand, i as cursorAbs, l as isSubcommand, m as resolveOne, n as cmdHelp, o as extractTaskCounts, p as renderRawLog, r as controlCodeFromName, s as finalizedLines, t as GRACEFUL_EXIT_COMMANDS, u as listRecords, v as writeToIpc } from "./subcommands-B13Kto-u.js";
|
|
6
|
+
|
|
7
|
+
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-BqOr1YqC.js";
|
|
3
3
|
import { t as agentYesHome } from "./agentYesHome-BvaUOzCV.js";
|
|
4
4
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
|
|
5
|
-
import { t as PidStore } from "./pidStore-
|
|
6
|
-
import { i as readGlobalPids } from "./globalPidIndex-
|
|
7
|
-
import { n as
|
|
5
|
+
import { t as PidStore } from "./pidStore-CGKIhaJO.js";
|
|
6
|
+
import { i as readGlobalPids } from "./globalPidIndex-C7r2m6s7.js";
|
|
7
|
+
import { n as register, r as sweep } from "./reaper-BkjPN7mw.js";
|
|
8
8
|
import { arch, platform } from "process";
|
|
9
9
|
import { execSync } from "child_process";
|
|
10
10
|
import { closeSync, constants, createReadStream, existsSync, mkdirSync, openSync } from "fs";
|
|
@@ -1060,7 +1060,7 @@ async function notifyWebhook(status, details, cwd = process.cwd()) {
|
|
|
1060
1060
|
|
|
1061
1061
|
//#endregion
|
|
1062
1062
|
//#region ts/index.ts
|
|
1063
|
-
const config = await import("./agent-yes.config-
|
|
1063
|
+
const config = await import("./agent-yes.config-D6ycMApr.js").then((mod) => mod.default || mod);
|
|
1064
1064
|
const CLIS_CONFIG = config.clis;
|
|
1065
1065
|
/**
|
|
1066
1066
|
* Main function to run agent-cli with automatic yes/no responses
|
|
@@ -1212,6 +1212,8 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1212
1212
|
} else logger.warn(`Unknown promptArg format: ${cliConf.promptArg}`);
|
|
1213
1213
|
sweep().catch(() => {});
|
|
1214
1214
|
const ptyEnv = { ...env ?? process.env };
|
|
1215
|
+
const inheritedAyPid = Number(ptyEnv.AGENT_YES_PID);
|
|
1216
|
+
const parentPid = Number.isInteger(inheritedAyPid) && inheritedAyPid > 0 ? inheritedAyPid : void 0;
|
|
1215
1217
|
ptyEnv.AGENT_YES_PID = String(process.pid);
|
|
1216
1218
|
const ptyOptions = {
|
|
1217
1219
|
name: "xterm-color",
|
|
@@ -1241,7 +1243,8 @@ async function agentYes({ cli, cliArgs = [], prompt, robust = true, cwd, env, ex
|
|
|
1241
1243
|
args: cliArgs,
|
|
1242
1244
|
prompt,
|
|
1243
1245
|
cwd: workingDir,
|
|
1244
|
-
wrapperPid: process.pid
|
|
1246
|
+
wrapperPid: process.pid,
|
|
1247
|
+
parentPid
|
|
1245
1248
|
});
|
|
1246
1249
|
} catch (error) {
|
|
1247
1250
|
logger.warn(`[pidStore] Failed to register process ${shell.pid}:`, error);
|
|
@@ -1784,4 +1787,4 @@ function sleep(ms) {
|
|
|
1784
1787
|
|
|
1785
1788
|
//#endregion
|
|
1786
1789
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1787
|
-
//# sourceMappingURL=ts-
|
|
1790
|
+
//# sourceMappingURL=ts-DgukRoEI.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "agent-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.123.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -215,4 +215,4 @@ async function displayVersion() {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
218
|
-
//# sourceMappingURL=versionChecker-
|
|
218
|
+
//# sourceMappingURL=versionChecker-BqOr1YqC.js.map
|
package/lab/ui/console-logic.js
CHANGED
|
@@ -14,9 +14,17 @@
|
|
|
14
14
|
export const cliLabel = (e) => (e.cli && e.cli !== "claude" ? e.cli : "");
|
|
15
15
|
|
|
16
16
|
// Parse owner/repo/branch from a cwd like .../ws/<owner>/<repo>/tree/<branch>.
|
|
17
|
+
// A cwd inside a git submodule keeps trailing path after the worktree branch
|
|
18
|
+
// (e.g. .../tree/share/lib/bot, where lib/bot is a submodule). The owner/repo/
|
|
19
|
+
// branch still describe the superproject worktree — git itself resolves a
|
|
20
|
+
// submodule cwd's identity to the superproject — so we surface the submodule's
|
|
21
|
+
// leaf dir as `sub` to keep nested repos distinguishable. `sub` is "" when the
|
|
22
|
+
// cwd is the worktree root.
|
|
17
23
|
export function repoBranch(e) {
|
|
18
|
-
const m = /\/([^/]+)\/([^/]+)\/tree\/([^/]+)
|
|
19
|
-
|
|
24
|
+
const m = /\/([^/]+)\/([^/]+)\/tree\/([^/]+)(\/.*)?$/.exec(e.cwd || "");
|
|
25
|
+
if (!m) return null;
|
|
26
|
+
const sub = (m[4] || "").split("/").filter(Boolean).pop() || "";
|
|
27
|
+
return { owner: m[1], repo: m[2], branch: m[3], sub };
|
|
20
28
|
}
|
|
21
29
|
|
|
22
30
|
// Identity string for the left panel. cap=true → repo/branch each clipped to
|
|
@@ -25,7 +33,8 @@ export function ident(e, cap) {
|
|
|
25
33
|
const rb = repoBranch(e);
|
|
26
34
|
if (!rb) return "";
|
|
27
35
|
const c = (s) => (cap && s.length > 3 ? s.slice(0, 3) : s);
|
|
28
|
-
|
|
36
|
+
const sub = rb.sub ? `→${rb.sub}` : "";
|
|
37
|
+
return `${c(rb.repo)}/${c(rb.branch)}${sub}`;
|
|
29
38
|
}
|
|
30
39
|
|
|
31
40
|
// ---- device-aware identity (multi-room) -----------------------------------
|
|
@@ -46,11 +55,18 @@ export function deviceParts(host) {
|
|
|
46
55
|
// The five identity fields, in display order, for one agent.
|
|
47
56
|
export function identFields(e) {
|
|
48
57
|
const d = deviceParts(e._host);
|
|
49
|
-
const rb = repoBranch(e) || { owner: "", repo: "", branch: "" };
|
|
50
|
-
return {
|
|
58
|
+
const rb = repoBranch(e) || { owner: "", repo: "", branch: "", sub: "" };
|
|
59
|
+
return {
|
|
60
|
+
user: d.user,
|
|
61
|
+
host: d.host,
|
|
62
|
+
owner: rb.owner,
|
|
63
|
+
repo: rb.repo,
|
|
64
|
+
branch: rb.branch,
|
|
65
|
+
sub: rb.sub,
|
|
66
|
+
};
|
|
51
67
|
}
|
|
52
68
|
|
|
53
|
-
const IDENT_ORDER = ["user", "host", "owner", "repo", "branch"];
|
|
69
|
+
const IDENT_ORDER = ["user", "host", "owner", "repo", "branch", "sub"];
|
|
54
70
|
|
|
55
71
|
// Precompute, over the whole shown list: which fields are uniform (identical for
|
|
56
72
|
// every agent — so they can be omitted) and whether any device info exists at
|
|
@@ -69,11 +85,22 @@ export function identContext(entries) {
|
|
|
69
85
|
// machine-parseable: e.g. all on one device → "@:age/mai", a mixed-device list →
|
|
70
86
|
// "sno@tak:age/mai". A purely local list (no devices anywhere) falls back to the
|
|
71
87
|
// legacy "own/rep/bra" with no device prefix.
|
|
72
|
-
|
|
88
|
+
//
|
|
89
|
+
// `parent` is this row's tree parent entry (a subagent's superagent), when it
|
|
90
|
+
// has one. A field that matches the parent's is ALSO blanked: the nesting
|
|
91
|
+
// already conveys it, so a subagent in the same worktree as its parent shows
|
|
92
|
+
// only what differs — often just the submodule leaf (e.g. "//→bot"), or nothing
|
|
93
|
+
// at all (hidden by hasIdent) when it's the very same checkout.
|
|
94
|
+
export function compactIdent(e, ctx, cap = 3, parent = null) {
|
|
73
95
|
const m = identFields(e);
|
|
96
|
+
const p = parent ? identFields(parent) : null;
|
|
74
97
|
const clip = (s) => (cap && s.length > cap ? s.slice(0, cap) : s);
|
|
75
|
-
const
|
|
76
|
-
const
|
|
98
|
+
const blank = (f) => ctx.uniform[f] || (p != null && p[f] === m[f]);
|
|
99
|
+
const v = (f) => (blank(f) ? "" : clip(m[f]));
|
|
100
|
+
// Submodule leaf is shown in full (the finest-grain distinguisher) and joined
|
|
101
|
+
// with → rather than / so it reads as "inside that worktree".
|
|
102
|
+
const sub = blank("sub") ? "" : m.sub;
|
|
103
|
+
const path = `${v("owner")}/${v("repo")}/${v("branch")}${sub ? `→${sub}` : ""}`;
|
|
77
104
|
return ctx.anyDevice ? `${v("user")}@${v("host")}:${path}` : path;
|
|
78
105
|
}
|
|
79
106
|
|
|
@@ -81,7 +108,8 @@ export function compactIdent(e, ctx, cap = 3) {
|
|
|
81
108
|
// prefix only when this agent actually has device info.
|
|
82
109
|
export function fullIdent(e) {
|
|
83
110
|
const m = identFields(e);
|
|
84
|
-
const
|
|
111
|
+
const sub = m.sub ? `→${m.sub}` : "";
|
|
112
|
+
const path = `${m.owner}/${m.repo}/${m.branch}${sub}`;
|
|
85
113
|
return m.user || m.host ? `${m.user}@${m.host}:${path}` : path;
|
|
86
114
|
}
|
|
87
115
|
|
|
@@ -108,6 +136,7 @@ export function tagsFor(e) {
|
|
|
108
136
|
const rb = repoBranch(e);
|
|
109
137
|
if (rb) {
|
|
110
138
|
t.push(["repo", `${rb.owner}/${rb.repo}`], ["wt", rb.branch]);
|
|
139
|
+
if (rb.sub) t.push(["sub", rb.sub]); // submodule leaf, when cwd is nested
|
|
111
140
|
}
|
|
112
141
|
const cli = cliLabel(e);
|
|
113
142
|
if (cli) t.push(["cli", cli]);
|
|
@@ -129,6 +158,15 @@ export function gitLabel(e) {
|
|
|
129
158
|
return parts.join(" ");
|
|
130
159
|
}
|
|
131
160
|
|
|
161
|
+
// Task-progress badge ("2/5") from the agent's parsed todo block (e.tasks =
|
|
162
|
+
// { done, total }, computed live in /api/ls). Empty string when no todo block was
|
|
163
|
+
// confidently detected — the badge is omitted entirely, never shown as "0/0".
|
|
164
|
+
export function taskLabel(e) {
|
|
165
|
+
const t = e.tasks;
|
|
166
|
+
if (!t || typeof t.total !== "number" || t.total <= 0) return "";
|
|
167
|
+
return `${t.done}/${t.total}`;
|
|
168
|
+
}
|
|
169
|
+
|
|
132
170
|
// Human age of an agent ("12s" / "5m" / "3h"). `now` is injectable so tests
|
|
133
171
|
// don't depend on the wall clock; the browser calls age(e) and gets Date.now().
|
|
134
172
|
export function age(e, now = Date.now()) {
|
|
@@ -175,6 +213,180 @@ export function matches(e, toks) {
|
|
|
175
213
|
});
|
|
176
214
|
}
|
|
177
215
|
|
|
216
|
+
// Box-drawing rail prefix for a node given the "is-last-child?" flags of each
|
|
217
|
+
// ancestor (root→node). depth 0 → "". Shared by the agent forest and the layered
|
|
218
|
+
// room/peer tree so all rails line up: "│ ", " " for ancestors, "├ "/"└ " here.
|
|
219
|
+
function railPrefix(ancestorsLast) {
|
|
220
|
+
const depth = ancestorsLast.length;
|
|
221
|
+
let s = "";
|
|
222
|
+
for (let i = 0; i < depth - 1; i++) s += ancestorsLast[i] ? " " : "│ ";
|
|
223
|
+
if (depth > 0) s += ancestorsLast[depth - 1] ? "└ " : "├ ";
|
|
224
|
+
return s;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Stable ordered grouping: [[key, items[]], ...] in first-seen key order.
|
|
228
|
+
function groupBy(arr, keyFn) {
|
|
229
|
+
const m = new Map();
|
|
230
|
+
for (const e of arr) {
|
|
231
|
+
const k = keyFn(e);
|
|
232
|
+
if (!m.has(k)) m.set(k, []);
|
|
233
|
+
m.get(k).push(e);
|
|
234
|
+
}
|
|
235
|
+
return [...m.entries()];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Build the agent>subagent forest for ONE host's entries (pids are only unique
|
|
239
|
+
// per machine, so the caller must pre-scope by host). Primary link is the explicit
|
|
240
|
+
// spawn relationship (parent_pid === wrapper_pid). As a FALLBACK, an agent with no
|
|
241
|
+
// such parent whose cwd sits INSIDE another agent's cwd of the SAME worktree
|
|
242
|
+
// (owner/repo/branch) snaps under it — so a submodule/subdir agent nests under its
|
|
243
|
+
// superproject agent even without a parent_pid link. Returns root nodes
|
|
244
|
+
// { entry, children }; sibling/root order = input order. Cycles can't drop nodes:
|
|
245
|
+
// anything not reached from a root is appended as its own root.
|
|
246
|
+
function agentForestNodes(list) {
|
|
247
|
+
const byWrapper = new Map();
|
|
248
|
+
for (const e of list) if (e.wrapper_pid != null) byWrapper.set(e.wrapper_pid, e);
|
|
249
|
+
const nodeOf = new Map(list.map((e) => [e, { entry: e, children: [] }]));
|
|
250
|
+
|
|
251
|
+
const parentOf = new Map();
|
|
252
|
+
// 1) explicit spawn link.
|
|
253
|
+
for (const e of list) {
|
|
254
|
+
const p = e.parent_pid != null ? byWrapper.get(e.parent_pid) : null;
|
|
255
|
+
if (p && p !== e) parentOf.set(e, p);
|
|
256
|
+
}
|
|
257
|
+
// 2) cwd-containment fallback for the still-parentless. The closest ancestor
|
|
258
|
+
// (longest containing cwd) wins; same-worktree guard keeps an unrelated
|
|
259
|
+
// shared prefix (e.g. /Users/x/ws) from grouping strangers together.
|
|
260
|
+
for (const e of list) {
|
|
261
|
+
if (parentOf.has(e) || !e.cwd) continue;
|
|
262
|
+
const rb = repoBranch(e);
|
|
263
|
+
if (!rb) continue;
|
|
264
|
+
let best = null;
|
|
265
|
+
for (const c of list) {
|
|
266
|
+
if (c === e || !c.cwd || !e.cwd.startsWith(c.cwd + "/")) continue;
|
|
267
|
+
const crb = repoBranch(c);
|
|
268
|
+
if (!crb || crb.owner !== rb.owner || crb.repo !== rb.repo || crb.branch !== rb.branch)
|
|
269
|
+
continue;
|
|
270
|
+
if (!best || c.cwd.length > best.cwd.length) best = c;
|
|
271
|
+
}
|
|
272
|
+
if (best) parentOf.set(e, best);
|
|
273
|
+
}
|
|
274
|
+
// Attach, refusing any edge that would close a cycle (a pre-existing parent_pid
|
|
275
|
+
// cycle, or a pid/cwd mix) — such nodes stay roots.
|
|
276
|
+
const wouldCycle = (parent, child) => {
|
|
277
|
+
for (let cur = parent, i = 0; cur && i < list.length + 1; cur = parentOf.get(cur), i++)
|
|
278
|
+
if (cur === child) return true;
|
|
279
|
+
return false;
|
|
280
|
+
};
|
|
281
|
+
const roots = [];
|
|
282
|
+
for (const e of list) {
|
|
283
|
+
const p = parentOf.get(e);
|
|
284
|
+
if (p && p !== e && !wouldCycle(p, e)) nodeOf.get(p).children.push(nodeOf.get(e));
|
|
285
|
+
else roots.push(nodeOf.get(e));
|
|
286
|
+
}
|
|
287
|
+
// Cycle safety: collect nodes reachable from roots; append the rest as roots.
|
|
288
|
+
const seen = new Set();
|
|
289
|
+
const mark = (n) => {
|
|
290
|
+
if (seen.has(n)) return;
|
|
291
|
+
seen.add(n);
|
|
292
|
+
n.children.forEach(mark);
|
|
293
|
+
};
|
|
294
|
+
roots.forEach(mark);
|
|
295
|
+
for (const e of list) if (!seen.has(nodeOf.get(e))) roots.push(nodeOf.get(e));
|
|
296
|
+
return roots;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Order entries as agent>subagent forests so a nested `ay` (one agent spawning
|
|
300
|
+
// another) renders indented under its parent. SCOPED PER HOST. Returns a NEW
|
|
301
|
+
// array in depth-first order; each entry is shallow-copied with `_branch` (a
|
|
302
|
+
// box-drawing tree prefix like "│ └ ") and `_depth`. A fleet with no nesting
|
|
303
|
+
// renders exactly as before (every row a root, empty `_branch`).
|
|
304
|
+
export function forestOrder(entries) {
|
|
305
|
+
const out = [];
|
|
306
|
+
for (const [, list] of groupBy(entries, (e) => e._host || "")) {
|
|
307
|
+
const seen = new Set();
|
|
308
|
+
const walk = (node, ancestorsLast) => {
|
|
309
|
+
if (seen.has(node)) return; // break a pathological parent_pid cycle
|
|
310
|
+
seen.add(node);
|
|
311
|
+
out.push(
|
|
312
|
+
Object.assign({}, node.entry, {
|
|
313
|
+
_branch: railPrefix(ancestorsLast),
|
|
314
|
+
_depth: ancestorsLast.length,
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
317
|
+
node.children.forEach((c, i) =>
|
|
318
|
+
walk(c, ancestorsLast.concat(i === node.children.length - 1)),
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
for (const r of agentForestNodes(list)) walk(r, []);
|
|
322
|
+
}
|
|
323
|
+
return out;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Build the full console hierarchy — signalling-server > rooms > peers(hosts) >
|
|
327
|
+
// agents > subagents — and flatten it into ordered render rows, VSCode-explorer
|
|
328
|
+
// style: a container layer (room/peer) with a single node in its scope is HIDDEN
|
|
329
|
+
// (its children float up a level); a layer with ≥2 siblings becomes a tree with
|
|
330
|
+
// ├ └ │ rails. Agents always get their own row, with their subagent forest nested
|
|
331
|
+
// beneath. The server layer is implicit (always one) so it never shows.
|
|
332
|
+
//
|
|
333
|
+
// Returns [{ kind:'room'|'peer'|'agent', label, entry, branch, depth }] in
|
|
334
|
+
// display order. Headers (room/peer) are non-selectable; only 'agent' rows are.
|
|
335
|
+
// A purely local fleet (one room, one unlabelled host) yields only agent rows —
|
|
336
|
+
// identical to forestOrder — so the common case stays a plain agent tree.
|
|
337
|
+
export function layeredRows(entries) {
|
|
338
|
+
const multiRoom = new Set(entries.map((e) => e._room || "")).size > 1;
|
|
339
|
+
|
|
340
|
+
// Container nodes carry a `kind`/`label` for headers; agent nodes carry an
|
|
341
|
+
// `entry`. A hidden layer simply isn't created — its agents/peers attach to the
|
|
342
|
+
// parent — which is what makes single-node layers vanish.
|
|
343
|
+
const roomNodes = [];
|
|
344
|
+
for (const [rid, re] of groupBy(entries, (e) => e._room || "")) {
|
|
345
|
+
const peerGroups = groupBy(re, (e) => e._host || "");
|
|
346
|
+
const multiPeer = peerGroups.length > 1;
|
|
347
|
+
const underRoom = [];
|
|
348
|
+
for (const [host, pe] of peerGroups) {
|
|
349
|
+
const agentNodes = agentForestNodes(pe).map(toAgentNode);
|
|
350
|
+
if (multiPeer && host)
|
|
351
|
+
underRoom.push({ kind: "peer", label: host, room: rid, host, children: agentNodes });
|
|
352
|
+
else underRoom.push(...agentNodes); // single/unlabelled peer hidden
|
|
353
|
+
}
|
|
354
|
+
if (multiRoom && rid) roomNodes.push({ kind: "room", label: rid, children: underRoom });
|
|
355
|
+
else roomNodes.push(...underRoom); // single room hidden
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const rows = [];
|
|
359
|
+
const seen = new Set();
|
|
360
|
+
// parentAgent carries the nearest ancestor agent's entry down the walk so a
|
|
361
|
+
// subagent row knows its superagent — used to omit identity fields the tree
|
|
362
|
+
// nesting already conveys (see compactIdent). Room/peer headers are skipped.
|
|
363
|
+
const walk = (node, ancestorsLast, parentAgent) => {
|
|
364
|
+
if (seen.has(node)) return; // break a pathological parent_pid cycle
|
|
365
|
+
seen.add(node);
|
|
366
|
+
rows.push({
|
|
367
|
+
kind: node.kind,
|
|
368
|
+
label: node.label,
|
|
369
|
+
entry: node.entry,
|
|
370
|
+
room: node.room, // peer headers: (room,host) keys the connection-type cache
|
|
371
|
+
host: node.host,
|
|
372
|
+
parentEntry: node.kind === "agent" ? parentAgent : null,
|
|
373
|
+
branch: railPrefix(ancestorsLast),
|
|
374
|
+
depth: ancestorsLast.length,
|
|
375
|
+
});
|
|
376
|
+
const nextParent = node.kind === "agent" ? node.entry : parentAgent;
|
|
377
|
+
(node.children || []).forEach((c, i) =>
|
|
378
|
+
walk(c, ancestorsLast.concat(i === node.children.length - 1), nextParent),
|
|
379
|
+
);
|
|
380
|
+
};
|
|
381
|
+
for (const r of roomNodes) walk(r, [], null);
|
|
382
|
+
return rows;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Wrap an agent forest node { entry, children } into a layered-tree node.
|
|
386
|
+
function toAgentNode(n) {
|
|
387
|
+
return { kind: "agent", entry: n.entry, children: n.children.map(toAgentNode) };
|
|
388
|
+
}
|
|
389
|
+
|
|
178
390
|
// Next selection index when stepping the list by `dir` (+1 down / -1 up).
|
|
179
391
|
// No current selection (i<0) lands on the first (down) or last (up) row;
|
|
180
392
|
// otherwise clamps at the ends. Returns -1 for an empty list.
|
package/lab/ui/icon.svg
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="agent-yes">
|
|
2
|
+
<rect width="512" height="512" rx="104" fill="#0d1117" />
|
|
3
|
+
<path d="M132 268 l78 80 l170-186" fill="none" stroke="#3fb950" stroke-width="56"
|
|
4
|
+
stroke-linecap="round" stroke-linejoin="round" />
|
|
5
|
+
</svg>
|