@ai-ecoverse/slicc-agent 3.36.1 → 3.37.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/README.md +57 -3
- package/dist/client.js +3 -1
- package/dist/client.js.map +2 -2
- package/dist/control.js +3 -1
- package/dist/control.js.map +2 -2
- package/dist/host.js +14 -3
- package/dist/host.js.map +2 -2
- package/dist/kernel/groups.js +6 -0
- package/dist/kernel/groups.js.map +2 -2
- package/dist/main.js +7 -1
- package/dist/main.js.map +2 -2
- package/dist/mcp/index.js +1 -0
- package/dist/mcp/index.js.map +2 -2
- package/dist/mcp/runtime.js +1 -0
- package/dist/mcp/runtime.js.map +2 -2
- package/dist/memory/format.js +8 -0
- package/dist/memory/format.js.map +2 -2
- package/dist/monitor/index.js +449 -0
- package/dist/monitor/index.js.map +7 -0
- package/dist/monitor/redact.js +25 -0
- package/dist/monitor/redact.js.map +7 -0
- package/dist/readonly.js +1 -1
- package/dist/readonly.js.map +2 -2
- package/dist/scoops/index.js +27 -8
- package/dist/scoops/index.js.map +2 -2
- package/dist/services.js.map +2 -2
- package/dist/spectrum/index.js +12 -1
- package/dist/spectrum/index.js.map +3 -3
- package/dist/spectrum/monitor-port.js +621 -0
- package/dist/spectrum/monitor-port.js.map +7 -0
- package/dist/spectrum/tray-port.js +0 -2
- package/dist/spectrum/tray-port.js.map +2 -2
- package/dist/types/client.d.ts +2 -1
- package/dist/types/control.d.ts +2 -1
- package/dist/types/host.d.ts +2 -0
- package/dist/types/kernel/groups.d.ts +1 -0
- package/dist/types/mcp/runtime.d.ts +1 -0
- package/dist/types/monitor/index.d.ts +67 -0
- package/dist/types/monitor/redact.d.ts +5 -0
- package/dist/types/scoops/index.d.ts +5 -1
- package/dist/types/services.d.ts +71 -0
- package/dist/types/spectrum/index.d.ts +7 -0
- package/dist/types/spectrum/monitor-port.d.ts +70 -0
- package/package.json +3 -3
|
@@ -0,0 +1,621 @@
|
|
|
1
|
+
import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
|
|
2
|
+
import { Emitter } from "./emitter.js";
|
|
3
|
+
const RENEW_MS = 1e4;
|
|
4
|
+
const ROWS_MAX = 200;
|
|
5
|
+
const HISTORY = 24;
|
|
6
|
+
const BUDGET_WARN = 80;
|
|
7
|
+
const BUDGET_ERROR = 95;
|
|
8
|
+
const RETRY_WARN = 3;
|
|
9
|
+
const RETRY_ERROR = 6;
|
|
10
|
+
const RETRY_SLOW_MS = 12e4;
|
|
11
|
+
const FAILED_MS = 6e5;
|
|
12
|
+
const ABORT_MS = 3e4;
|
|
13
|
+
const ABORT_ROW_MS = 1e4;
|
|
14
|
+
const GROUP_MAX = 32;
|
|
15
|
+
const TOTAL_MAX = 128;
|
|
16
|
+
const ZOMBIE_MS = 6e4;
|
|
17
|
+
const STALE_MS = 18e5;
|
|
18
|
+
const CONTEXT_WARN = 0.8;
|
|
19
|
+
const DEPTH_MAX = 6;
|
|
20
|
+
const MINUTE = 6e4;
|
|
21
|
+
const HOUR = 36e5;
|
|
22
|
+
const KINDS = {
|
|
23
|
+
"pi.generation": "generating",
|
|
24
|
+
"pi.compaction": "compacting",
|
|
25
|
+
"slicc.cron": "cron",
|
|
26
|
+
"slicc.scoop-wait": "agent wait",
|
|
27
|
+
"slicc.scoop-reporter": "report",
|
|
28
|
+
"slicc.memory-extract": "memory extraction"
|
|
29
|
+
};
|
|
30
|
+
function age(ms) {
|
|
31
|
+
const seconds = Math.max(0, Math.round(ms / 1e3));
|
|
32
|
+
if (seconds < 60) return `${seconds}s`;
|
|
33
|
+
if (seconds < 3600) return `${Math.floor(seconds / 60)}m`;
|
|
34
|
+
return `${Math.floor(seconds / 3600)}h`;
|
|
35
|
+
}
|
|
36
|
+
function money(value) {
|
|
37
|
+
return `$${value.toFixed(2)}`;
|
|
38
|
+
}
|
|
39
|
+
function count(value) {
|
|
40
|
+
if (value < 1e3) return String(value);
|
|
41
|
+
if (value < 1e6) return `${(value / 1e3).toFixed(1)}k`;
|
|
42
|
+
return `${(value / 1e6).toFixed(1)}M`;
|
|
43
|
+
}
|
|
44
|
+
function bytes(value) {
|
|
45
|
+
if (value < 1024) return `${value} B`;
|
|
46
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
47
|
+
let size = value / 1024;
|
|
48
|
+
let unit = 0;
|
|
49
|
+
while (size >= 1024 && unit < units.length - 1) {
|
|
50
|
+
size /= 1024;
|
|
51
|
+
unit++;
|
|
52
|
+
}
|
|
53
|
+
return `${size.toFixed(size < 10 ? 1 : 0)} ${units[unit]}`;
|
|
54
|
+
}
|
|
55
|
+
function memoryVital(processes) {
|
|
56
|
+
const measured = processes.filter((row) => typeof row.rss === "number");
|
|
57
|
+
if (!measured.length) return [];
|
|
58
|
+
const total = measured.reduce((sum, row) => sum + row.rss, 0);
|
|
59
|
+
const agents = measured.filter((row) => row.agentId).reduce((sum, row) => sum + row.rss, 0);
|
|
60
|
+
return [
|
|
61
|
+
{
|
|
62
|
+
id: "memory",
|
|
63
|
+
label: "Process memory",
|
|
64
|
+
value: bytes(total),
|
|
65
|
+
unit: `${bytes(agents)} by agents`
|
|
66
|
+
}
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
function taskLabel(task) {
|
|
70
|
+
if (task.kind === "pi.tool") return task.detail ? `tool ${task.detail}` : "tool";
|
|
71
|
+
if (task.kind === "slicc.scoop-anchor") return task.detail ? `scoop ${task.detail}` : "scoop";
|
|
72
|
+
return KINDS[task.kind] ?? task.kind;
|
|
73
|
+
}
|
|
74
|
+
function coneOf(agents, id) {
|
|
75
|
+
const visited = /* @__PURE__ */ new Set();
|
|
76
|
+
let current = id === null ? void 0 : agents.get(id);
|
|
77
|
+
while (current?.parentId && !visited.has(current.id)) {
|
|
78
|
+
visited.add(current.id);
|
|
79
|
+
const parent = agents.get(current.parentId);
|
|
80
|
+
if (!parent) break;
|
|
81
|
+
current = parent;
|
|
82
|
+
}
|
|
83
|
+
return current ? current.id : null;
|
|
84
|
+
}
|
|
85
|
+
function ordered(feed) {
|
|
86
|
+
const byId = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
87
|
+
const out = [];
|
|
88
|
+
for (const cone of feed.agents.filter((agent) => agent.kind === "cone")) {
|
|
89
|
+
out.push(cone);
|
|
90
|
+
for (const agent of feed.agents)
|
|
91
|
+
if (agent.kind === "scoop" && coneOf(byId, agent.id) === cone.id) out.push(agent);
|
|
92
|
+
}
|
|
93
|
+
for (const agent of feed.agents) if (!out.includes(agent)) out.push(agent);
|
|
94
|
+
return out;
|
|
95
|
+
}
|
|
96
|
+
function taskTree(feed) {
|
|
97
|
+
const agents = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
98
|
+
const order = ordered(feed).map((agent) => agent.id);
|
|
99
|
+
const rank = (task) => {
|
|
100
|
+
const at = task.agentId === null ? -1 : order.indexOf(task.agentId);
|
|
101
|
+
return at < 0 ? order.length : at;
|
|
102
|
+
};
|
|
103
|
+
const groupOf = (task) => coneOf(agents, task.agentId) ?? "";
|
|
104
|
+
const byId = new Map(feed.tasks.map((task) => [task.id, task]));
|
|
105
|
+
const children = /* @__PURE__ */ new Map();
|
|
106
|
+
const roots = [];
|
|
107
|
+
for (const task of feed.tasks) {
|
|
108
|
+
const owner = task.owner === null ? void 0 : byId.get(task.owner);
|
|
109
|
+
if (owner && groupOf(owner) === groupOf(task)) {
|
|
110
|
+
const list = children.get(owner.id) ?? [];
|
|
111
|
+
list.push(task);
|
|
112
|
+
children.set(owner.id, list);
|
|
113
|
+
} else roots.push(task);
|
|
114
|
+
}
|
|
115
|
+
const sort = (list) => list.sort((a, b) => rank(a) - rank(b) || Number(a.id) - Number(b.id));
|
|
116
|
+
const out = [];
|
|
117
|
+
const visit = (task, depth) => {
|
|
118
|
+
out.push({ task, depth });
|
|
119
|
+
for (const child of sort(children.get(task.id) ?? [])) visit(child, depth + 1);
|
|
120
|
+
};
|
|
121
|
+
for (const root of sort(roots)) visit(root, 0);
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
function capped(rows) {
|
|
125
|
+
if (rows.length <= ROWS_MAX) return rows;
|
|
126
|
+
const shown = rows.slice(0, ROWS_MAX - 1);
|
|
127
|
+
return [
|
|
128
|
+
...shown,
|
|
129
|
+
{ id: "more", name: `+${rows.length - shown.length} more`, meta: "", status: "idle" }
|
|
130
|
+
];
|
|
131
|
+
}
|
|
132
|
+
function contextRatio(agent, models) {
|
|
133
|
+
const window = models.find((model) => model.id === agent.model)?.contextWindow;
|
|
134
|
+
if (!agent.context || !window) return null;
|
|
135
|
+
return Math.min(1, agent.context / window);
|
|
136
|
+
}
|
|
137
|
+
function rate(history, pick) {
|
|
138
|
+
const first = history[0];
|
|
139
|
+
const last = history.at(-1);
|
|
140
|
+
if (!first || !last || last.at - first.at < MINUTE) return null;
|
|
141
|
+
return (pick(last) - pick(first)) / (last.at - first.at) * HOUR;
|
|
142
|
+
}
|
|
143
|
+
function budgetVital(budget) {
|
|
144
|
+
const resets = new Date(budget.resets);
|
|
145
|
+
return {
|
|
146
|
+
id: "budget",
|
|
147
|
+
label: `Budget (${budget.window})`,
|
|
148
|
+
value: `${budget.percent}%`,
|
|
149
|
+
...Number.isNaN(resets.getTime()) ? {} : { unit: `resets ${resets.toISOString().slice(0, 16).replace("T", " ")} UTC` },
|
|
150
|
+
ratio: Math.min(1, Math.max(0, budget.percent / 100))
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function vitals(input, feed) {
|
|
154
|
+
const out = [];
|
|
155
|
+
if (feed.usage) {
|
|
156
|
+
const free = feed.usage.free;
|
|
157
|
+
const pick = (sample) => free ? sample.tokens : sample.cost;
|
|
158
|
+
const perHour = rate(input.history, pick);
|
|
159
|
+
const series = input.history.map(pick);
|
|
160
|
+
out.push({
|
|
161
|
+
id: "spend",
|
|
162
|
+
label: free ? "Tokens" : "Spend",
|
|
163
|
+
value: free ? count(feed.usage.tokens) : money(feed.usage.cost),
|
|
164
|
+
unit: free ? "tokens" : `${count(feed.usage.tokens)} tokens`,
|
|
165
|
+
...perHour === null ? {} : { delta: `${free ? count(perHour) : money(perHour)}/h` },
|
|
166
|
+
...series.length > 1 ? { series } : {}
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
const active = feed.agents.filter((agent) => agent.running).length;
|
|
170
|
+
const actives = input.history.map((sample) => sample.active);
|
|
171
|
+
out.push({
|
|
172
|
+
id: "agents",
|
|
173
|
+
label: "Active agents",
|
|
174
|
+
value: String(active),
|
|
175
|
+
unit: `of ${feed.agents.length}`,
|
|
176
|
+
...actives.length > 1 ? { series: actives } : {}
|
|
177
|
+
});
|
|
178
|
+
const tools = feed.tasks.filter(
|
|
179
|
+
(task) => task.kind === "pi.tool" && task.status === "running"
|
|
180
|
+
).length;
|
|
181
|
+
out.push({
|
|
182
|
+
id: "tasks",
|
|
183
|
+
label: "Running",
|
|
184
|
+
value: String(tools),
|
|
185
|
+
unit: `${tools === 1 ? "tool" : "tools"} \xB7 ${feed.tasks.length} ${feed.tasks.length === 1 ? "task" : "tasks"}`
|
|
186
|
+
});
|
|
187
|
+
out.push(
|
|
188
|
+
feed.processes ? {
|
|
189
|
+
id: "processes",
|
|
190
|
+
label: "Processes",
|
|
191
|
+
value: String(feed.processes.length),
|
|
192
|
+
unit: `${feed.processes.filter((row) => row.agentId).length} by agents`
|
|
193
|
+
} : { id: "processes", label: "Processes", value: "\u2014", unit: "kernel not reachable" }
|
|
194
|
+
);
|
|
195
|
+
out.push(...memoryVital(feed.processes ?? []));
|
|
196
|
+
const ratios = feed.agents.map((agent) => contextRatio(agent, input.models)).filter((ratio) => ratio !== null);
|
|
197
|
+
if (ratios.length) {
|
|
198
|
+
const fullest = Math.max(...ratios);
|
|
199
|
+
out.push({
|
|
200
|
+
id: "context",
|
|
201
|
+
label: "Fullest context",
|
|
202
|
+
value: `${Math.round(fullest * 100)}%`,
|
|
203
|
+
ratio: fullest
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
function stale(process, agents, now) {
|
|
209
|
+
if (!process.agentId || process.state === "Z" || !process.started) return false;
|
|
210
|
+
if (agents.get(process.agentId)?.tool === "bash") return false;
|
|
211
|
+
return now - process.started > STALE_MS;
|
|
212
|
+
}
|
|
213
|
+
function groups(processes) {
|
|
214
|
+
const out = /* @__PURE__ */ new Map();
|
|
215
|
+
for (const process of processes) {
|
|
216
|
+
if (!process.agentId) continue;
|
|
217
|
+
const list = out.get(process.pgid) ?? [];
|
|
218
|
+
list.push(process);
|
|
219
|
+
out.set(process.pgid, list);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
function resetLine(budget) {
|
|
224
|
+
const resets = new Date(budget.resets);
|
|
225
|
+
return Number.isNaN(resets.getTime()) ? `The ${budget.window} budget is nearly used up.` : `Resets ${resets.toISOString().slice(0, 16).replace("T", " ")} UTC.`;
|
|
226
|
+
}
|
|
227
|
+
function budgetAlerts(budget) {
|
|
228
|
+
if (!budget || budget.percent < BUDGET_WARN) return [];
|
|
229
|
+
return [
|
|
230
|
+
{
|
|
231
|
+
id: "budget",
|
|
232
|
+
title: `Budget ${budget.percent}% used`,
|
|
233
|
+
detail: resetLine(budget),
|
|
234
|
+
severity: budget.percent >= BUDGET_ERROR ? "error" : "warn"
|
|
235
|
+
}
|
|
236
|
+
];
|
|
237
|
+
}
|
|
238
|
+
function agentAlerts(input, feed) {
|
|
239
|
+
const out = [];
|
|
240
|
+
for (const agent of feed.agents) {
|
|
241
|
+
const retry = agent.retry;
|
|
242
|
+
if (retry && (retry.attempt >= RETRY_WARN || input.now - retry.since > RETRY_SLOW_MS))
|
|
243
|
+
out.push({
|
|
244
|
+
id: `retry-${agent.id}`,
|
|
245
|
+
title: `${agent.name} is retrying (attempt ${retry.attempt})`,
|
|
246
|
+
detail: retry.error,
|
|
247
|
+
severity: retry.attempt >= RETRY_ERROR ? "error" : "warn"
|
|
248
|
+
});
|
|
249
|
+
if (agent.failed && !agent.running)
|
|
250
|
+
out.push({
|
|
251
|
+
id: `turn-${agent.id}`,
|
|
252
|
+
title: `${agent.name}'s last turn failed`,
|
|
253
|
+
detail: agent.failed,
|
|
254
|
+
severity: "error"
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
function taskAlerts(input, feed, nameOf) {
|
|
260
|
+
const out = [];
|
|
261
|
+
for (const problem of feed.problems)
|
|
262
|
+
if (problem.kind === "task" && input.now - problem.at < FAILED_MS)
|
|
263
|
+
out.push({
|
|
264
|
+
id: `failed-${problem.id}`,
|
|
265
|
+
title: `A task of ${nameOf(problem.agentId)} failed`,
|
|
266
|
+
detail: problem.message,
|
|
267
|
+
severity: "error"
|
|
268
|
+
});
|
|
269
|
+
for (const task of feed.tasks)
|
|
270
|
+
if (task.aborting !== null && input.now - task.aborting > ABORT_MS)
|
|
271
|
+
out.push({
|
|
272
|
+
id: `abort-${task.id}`,
|
|
273
|
+
title: `Stop is still waiting for ${taskLabel(task)}`,
|
|
274
|
+
detail: `${nameOf(task.agentId)} asked it to stop ${age(input.now - task.aborting)} ago.`,
|
|
275
|
+
severity: "warn"
|
|
276
|
+
});
|
|
277
|
+
return out;
|
|
278
|
+
}
|
|
279
|
+
function crowdAlerts(processes, nameOf) {
|
|
280
|
+
const out = [];
|
|
281
|
+
for (const [pgid, members] of groups(processes))
|
|
282
|
+
if (members.length > GROUP_MAX)
|
|
283
|
+
out.push({
|
|
284
|
+
id: `runaway-${pgid}`,
|
|
285
|
+
title: `${nameOf(members[0].agentId)} has ${members.length} processes`,
|
|
286
|
+
detail: `Kill them with kill -- -${pgid}.`,
|
|
287
|
+
severity: members.length > GROUP_MAX * 2 ? "error" : "warn"
|
|
288
|
+
});
|
|
289
|
+
if (processes.length > TOTAL_MAX)
|
|
290
|
+
out.push({
|
|
291
|
+
id: "runaway-total",
|
|
292
|
+
title: `The kernel has ${processes.length} processes`,
|
|
293
|
+
detail: "Something may be starting processes in a loop.",
|
|
294
|
+
severity: processes.length > TOTAL_MAX * 2 ? "error" : "warn"
|
|
295
|
+
});
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function processAlerts(input, feed, nameOf) {
|
|
299
|
+
const processes = feed.processes ?? [];
|
|
300
|
+
const agents = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
301
|
+
const out = crowdAlerts(processes, nameOf);
|
|
302
|
+
for (const process of processes) {
|
|
303
|
+
if (process.zombie !== null && input.now - process.zombie > ZOMBIE_MS)
|
|
304
|
+
out.push({
|
|
305
|
+
id: `zombie-${process.pid}`,
|
|
306
|
+
title: `Process ${process.pid} is a zombie`,
|
|
307
|
+
detail: `Its parent ${process.ppid} hasn't reaped it.`,
|
|
308
|
+
severity: "warn"
|
|
309
|
+
});
|
|
310
|
+
if (stale(process, agents, input.now))
|
|
311
|
+
out.push({
|
|
312
|
+
id: `stale-${process.pid}`,
|
|
313
|
+
title: `${process.command || `Process ${process.pid}`} has run for ${age(input.now - process.started)}`,
|
|
314
|
+
detail: `${nameOf(process.agentId)} started it in the background. Kill it with kill ${process.pid}.`,
|
|
315
|
+
severity: input.now - process.started > STALE_MS * 2 ? "error" : "warn"
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
function contextAlerts(input, feed) {
|
|
321
|
+
const out = [];
|
|
322
|
+
for (const agent of feed.agents) {
|
|
323
|
+
const ratio = contextRatio(agent, input.models);
|
|
324
|
+
if (ratio !== null && ratio >= CONTEXT_WARN)
|
|
325
|
+
out.push({
|
|
326
|
+
id: `context-${agent.id}`,
|
|
327
|
+
title: `${agent.name} is ${Math.round(ratio * 100)}% full`,
|
|
328
|
+
detail: "Compact it soon to keep replies fast.",
|
|
329
|
+
severity: "warn"
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
if (feed.paused)
|
|
333
|
+
out.push({
|
|
334
|
+
id: "scheduler",
|
|
335
|
+
title: "The agent isn't scheduling work",
|
|
336
|
+
detail: "Its scheduler is paused.",
|
|
337
|
+
severity: "warn"
|
|
338
|
+
});
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
function alerts(input, feed) {
|
|
342
|
+
const agents = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
343
|
+
const nameOf = (id) => id ? agents.get(id)?.name ?? id : "the session";
|
|
344
|
+
return [
|
|
345
|
+
...budgetAlerts(input.budget),
|
|
346
|
+
...agentAlerts(input, feed),
|
|
347
|
+
...taskAlerts(input, feed, nameOf),
|
|
348
|
+
...processAlerts(input, feed, nameOf),
|
|
349
|
+
...contextAlerts(input, feed)
|
|
350
|
+
];
|
|
351
|
+
}
|
|
352
|
+
function agentRow(agent, depth, models) {
|
|
353
|
+
const ratio = contextRatio(agent, models);
|
|
354
|
+
const word = agent.retry ? "retrying" : agent.running ? "running" : agent.failed ? "failed" : "idle";
|
|
355
|
+
const status = agent.retry ? "warn" : agent.running ? "active" : agent.failed ? "error" : "idle";
|
|
356
|
+
const meta = [word, agent.model, ratio === null ? null : `${Math.round(ratio * 100)}% context`].filter(Boolean).join(" \xB7 ");
|
|
357
|
+
const badges = [
|
|
358
|
+
...agent.queued ? [`${agent.queued} queued`] : [],
|
|
359
|
+
...agent.tool ? [agent.tool] : [],
|
|
360
|
+
...agent.retry ? [`retry ${agent.retry.attempt}`] : []
|
|
361
|
+
];
|
|
362
|
+
return {
|
|
363
|
+
id: agent.id,
|
|
364
|
+
name: agent.name,
|
|
365
|
+
meta,
|
|
366
|
+
status,
|
|
367
|
+
depth,
|
|
368
|
+
...badges.length ? { badges } : {}
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
function taskRow(task, depth, nameOf, now) {
|
|
372
|
+
const aborting = task.aborting !== null && now - task.aborting > ABORT_ROW_MS;
|
|
373
|
+
const status = aborting ? "warn" : task.status === "running" || task.status === "completing" ? "active" : "idle";
|
|
374
|
+
const badges = [
|
|
375
|
+
...task.background ? ["background"] : [],
|
|
376
|
+
...task.aborting !== null ? ["aborting"] : [],
|
|
377
|
+
...task.waits ? [`waits on ${task.waits}`] : []
|
|
378
|
+
];
|
|
379
|
+
return {
|
|
380
|
+
id: task.id,
|
|
381
|
+
name: taskLabel(task),
|
|
382
|
+
depth: Math.min(depth, DEPTH_MAX),
|
|
383
|
+
meta: [nameOf(task.agentId), task.phase, age(now - task.since)].filter(Boolean).join(" \xB7 "),
|
|
384
|
+
status,
|
|
385
|
+
...badges.length ? { badges } : {}
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
function processRows(input, feed) {
|
|
389
|
+
const agents = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
390
|
+
const crowded = /* @__PURE__ */ new Set();
|
|
391
|
+
for (const [pgid, members] of groups(feed.processes ?? []))
|
|
392
|
+
if (members.length > GROUP_MAX) crowded.add(pgid);
|
|
393
|
+
return (feed.processes ?? []).map((process) => {
|
|
394
|
+
const runaway = stale(process, agents, input.now) || crowded.has(process.pgid);
|
|
395
|
+
const badges = [
|
|
396
|
+
...process.agentId ? [agents.get(process.agentId)?.name ?? process.agentId] : [],
|
|
397
|
+
...process.tty ? ["terminal"] : []
|
|
398
|
+
];
|
|
399
|
+
return {
|
|
400
|
+
id: String(process.pid),
|
|
401
|
+
name: process.command || `[${process.pid}]`,
|
|
402
|
+
meta: [
|
|
403
|
+
`pid ${process.pid}`,
|
|
404
|
+
process.user,
|
|
405
|
+
typeof process.rss === "number" ? bytes(process.rss) : null,
|
|
406
|
+
process.started ? age(input.now - process.started) : null
|
|
407
|
+
].filter(Boolean).join(" \xB7 "),
|
|
408
|
+
status: runaway ? "error" : process.state === "Z" ? "warn" : "idle",
|
|
409
|
+
...badges.length ? { badges } : {}
|
|
410
|
+
};
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const PROBLEM_KINDS = { task: "task failed", retry: "retry", turn: "turn failed" };
|
|
414
|
+
function depthOf(agents, agent) {
|
|
415
|
+
const visited = /* @__PURE__ */ new Set();
|
|
416
|
+
let depth = 0;
|
|
417
|
+
let current = agent;
|
|
418
|
+
while (current.parentId && !visited.has(current.id)) {
|
|
419
|
+
visited.add(current.id);
|
|
420
|
+
const parent = agents.get(current.parentId);
|
|
421
|
+
if (!parent) break;
|
|
422
|
+
depth++;
|
|
423
|
+
current = parent;
|
|
424
|
+
}
|
|
425
|
+
return depth;
|
|
426
|
+
}
|
|
427
|
+
function sections(input, feed) {
|
|
428
|
+
const agents = new Map(feed.agents.map((agent) => [agent.id, agent]));
|
|
429
|
+
const nameOf = (id) => id ? agents.get(id)?.name ?? id : "session";
|
|
430
|
+
return [
|
|
431
|
+
{
|
|
432
|
+
id: "agents",
|
|
433
|
+
label: "Cones and scoops",
|
|
434
|
+
rows: ordered(feed).map(
|
|
435
|
+
(agent) => agentRow(agent, Math.min(depthOf(agents, agent), DEPTH_MAX), input.models)
|
|
436
|
+
)
|
|
437
|
+
},
|
|
438
|
+
{
|
|
439
|
+
id: "tasks",
|
|
440
|
+
label: "Tasks",
|
|
441
|
+
rows: capped(
|
|
442
|
+
taskTree(feed).map(({ task, depth }) => taskRow(task, depth, nameOf, input.now))
|
|
443
|
+
)
|
|
444
|
+
},
|
|
445
|
+
{ id: "processes", label: "Processes", rows: capped(processRows(input, feed)) },
|
|
446
|
+
{
|
|
447
|
+
id: "problems",
|
|
448
|
+
label: "Recent problems",
|
|
449
|
+
rows: feed.problems.map((problem) => ({
|
|
450
|
+
id: problem.id,
|
|
451
|
+
name: `${nameOf(problem.agentId)} \xB7 ${PROBLEM_KINDS[problem.kind]}`,
|
|
452
|
+
meta: problem.message,
|
|
453
|
+
status: problem.kind === "retry" ? "warn" : "error"
|
|
454
|
+
}))
|
|
455
|
+
}
|
|
456
|
+
];
|
|
457
|
+
}
|
|
458
|
+
function pageSections(input) {
|
|
459
|
+
return [
|
|
460
|
+
{
|
|
461
|
+
id: "changes",
|
|
462
|
+
label: "Pending changes",
|
|
463
|
+
rows: input.changes.map((change) => ({
|
|
464
|
+
id: change.path,
|
|
465
|
+
name: change.path,
|
|
466
|
+
meta: change.status,
|
|
467
|
+
status: "warn"
|
|
468
|
+
}))
|
|
469
|
+
},
|
|
470
|
+
{
|
|
471
|
+
id: "sprinkles",
|
|
472
|
+
label: "Sprinkles",
|
|
473
|
+
rows: input.sprinkles.map((sprinkle) => ({
|
|
474
|
+
name: sprinkle.title,
|
|
475
|
+
meta: sprinkle.name,
|
|
476
|
+
status: "idle"
|
|
477
|
+
}))
|
|
478
|
+
},
|
|
479
|
+
...input.extra
|
|
480
|
+
];
|
|
481
|
+
}
|
|
482
|
+
function monitorSnapshot(input) {
|
|
483
|
+
const budget = input.budget ? [budgetVital(input.budget)] : [];
|
|
484
|
+
const feed = input.feed;
|
|
485
|
+
if (!feed?.at)
|
|
486
|
+
return {
|
|
487
|
+
vitals: budget,
|
|
488
|
+
alerts: [],
|
|
489
|
+
sections: pageSections(input),
|
|
490
|
+
updatedAt: input.now
|
|
491
|
+
};
|
|
492
|
+
return {
|
|
493
|
+
vitals: [...budget, ...vitals(input, feed)],
|
|
494
|
+
alerts: alerts(input, feed),
|
|
495
|
+
sections: [...sections(input, feed), ...pageSections(input)],
|
|
496
|
+
updatedAt: feed.at
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
const globalIntervals = {
|
|
500
|
+
every: (run, ms) => setInterval(run, ms),
|
|
501
|
+
stop: (handle) => clearInterval(handle)
|
|
502
|
+
};
|
|
503
|
+
class MonitorAdapter {
|
|
504
|
+
#connection;
|
|
505
|
+
#emitter = new Emitter();
|
|
506
|
+
#extra;
|
|
507
|
+
#renewMs;
|
|
508
|
+
#intervals;
|
|
509
|
+
#now;
|
|
510
|
+
#listeners = 0;
|
|
511
|
+
#open;
|
|
512
|
+
#history = [];
|
|
513
|
+
constructor(connection, options = {}) {
|
|
514
|
+
this.#connection = connection;
|
|
515
|
+
this.#extra = options.extra ?? (() => []);
|
|
516
|
+
this.#renewMs = options.renewMs ?? RENEW_MS;
|
|
517
|
+
this.#intervals = options.intervals ?? globalIntervals;
|
|
518
|
+
this.#now = options.now ?? Date.now;
|
|
519
|
+
connection.monitor?.subscribe(() => this.#update());
|
|
520
|
+
connection.settings.state.subscribe(() => this.#update());
|
|
521
|
+
connection.changes?.subscribe(() => this.#update());
|
|
522
|
+
connection.sprinkles?.subscribe(() => this.#update());
|
|
523
|
+
}
|
|
524
|
+
on(type, listener) {
|
|
525
|
+
const off = this.#emitter.on(type, listener);
|
|
526
|
+
this.#listeners++;
|
|
527
|
+
if (this.#listeners === 1) this.#start();
|
|
528
|
+
let done = false;
|
|
529
|
+
return () => {
|
|
530
|
+
if (done) return;
|
|
531
|
+
done = true;
|
|
532
|
+
off();
|
|
533
|
+
this.#listeners--;
|
|
534
|
+
if (!this.#listeners) this.#stop();
|
|
535
|
+
};
|
|
536
|
+
}
|
|
537
|
+
snapshot() {
|
|
538
|
+
const settings = this.#connection.settings.state.value;
|
|
539
|
+
return monitorSnapshot({
|
|
540
|
+
feed: this.#connection.monitor?.value,
|
|
541
|
+
budget: settings?.budget,
|
|
542
|
+
models: settings?.models ?? [],
|
|
543
|
+
changes: this.#connection.changes?.value?.changes ?? [],
|
|
544
|
+
sprinkles: this.#connection.sprinkles?.value ?? [],
|
|
545
|
+
extra: this.#extra(),
|
|
546
|
+
history: this.#history,
|
|
547
|
+
now: this.#now()
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
resync() {
|
|
551
|
+
const open = this.#open;
|
|
552
|
+
const control = this.#connection.control;
|
|
553
|
+
if (open)
|
|
554
|
+
void open.lease.then((lease) => lease ? control.monitorOpen(lease, true, BACKGROUND_CONTEXT) : void 0).catch(() => void 0);
|
|
555
|
+
this.#emitter.emit("snapshot", this.snapshot());
|
|
556
|
+
}
|
|
557
|
+
#start() {
|
|
558
|
+
const control = this.#connection.control;
|
|
559
|
+
this.#history = [];
|
|
560
|
+
void control.changesOpen(BACKGROUND_CONTEXT).catch(() => void 0);
|
|
561
|
+
const lease = control.monitorOpen(null, true, BACKGROUND_CONTEXT).then(
|
|
562
|
+
(opened) => opened.lease || null,
|
|
563
|
+
() => null
|
|
564
|
+
);
|
|
565
|
+
const renew = () => void lease.then((held) => held ? control.monitorOpen(held, false, BACKGROUND_CONTEXT) : void 0).catch(() => void 0);
|
|
566
|
+
this.#open = { lease, timer: this.#intervals.every(renew, this.#renewMs) };
|
|
567
|
+
}
|
|
568
|
+
#stop() {
|
|
569
|
+
const open = this.#open;
|
|
570
|
+
if (!open) return;
|
|
571
|
+
this.#open = void 0;
|
|
572
|
+
this.#intervals.stop(open.timer);
|
|
573
|
+
const control = this.#connection.control;
|
|
574
|
+
void open.lease.then((held) => held ? control.monitorClose(held, BACKGROUND_CONTEXT) : void 0).catch(() => void 0);
|
|
575
|
+
}
|
|
576
|
+
#sample() {
|
|
577
|
+
const feed = this.#connection.monitor?.value;
|
|
578
|
+
if (!feed?.open || !feed.usage || this.#history.at(-1)?.at === feed.at) return;
|
|
579
|
+
this.#history.push({
|
|
580
|
+
at: feed.at,
|
|
581
|
+
cost: feed.usage.cost,
|
|
582
|
+
tokens: feed.usage.tokens,
|
|
583
|
+
active: feed.agents.filter((agent) => agent.running).length
|
|
584
|
+
});
|
|
585
|
+
if (this.#history.length > HISTORY) this.#history.shift();
|
|
586
|
+
}
|
|
587
|
+
#update() {
|
|
588
|
+
if (!this.#listeners) return;
|
|
589
|
+
this.#sample();
|
|
590
|
+
this.#emitter.emit("snapshot", this.snapshot());
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
export {
|
|
594
|
+
ABORT_MS,
|
|
595
|
+
ABORT_ROW_MS,
|
|
596
|
+
BUDGET_ERROR,
|
|
597
|
+
BUDGET_WARN,
|
|
598
|
+
CONTEXT_WARN,
|
|
599
|
+
DEPTH_MAX,
|
|
600
|
+
FAILED_MS,
|
|
601
|
+
GROUP_MAX,
|
|
602
|
+
HISTORY,
|
|
603
|
+
MonitorAdapter,
|
|
604
|
+
RENEW_MS,
|
|
605
|
+
RETRY_ERROR,
|
|
606
|
+
RETRY_SLOW_MS,
|
|
607
|
+
RETRY_WARN,
|
|
608
|
+
ROWS_MAX,
|
|
609
|
+
STALE_MS,
|
|
610
|
+
TOTAL_MAX,
|
|
611
|
+
ZOMBIE_MS,
|
|
612
|
+
age,
|
|
613
|
+
bytes,
|
|
614
|
+
count,
|
|
615
|
+
globalIntervals,
|
|
616
|
+
money,
|
|
617
|
+
monitorSnapshot,
|
|
618
|
+
taskLabel,
|
|
619
|
+
taskTree
|
|
620
|
+
};
|
|
621
|
+
//# sourceMappingURL=monitor-port.js.map
|