@hheei/pi-subagents 0.7.0 → 0.8.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.
Files changed (59) hide show
  1. package/LICENSES/Apache-2.0.txt +202 -0
  2. package/NOTICE +10 -0
  3. package/README.md +13 -13
  4. package/dist/child-control.d.ts +0 -8
  5. package/dist/child-control.js +40 -8
  6. package/dist/commands.js +1 -1
  7. package/dist/controller-ipc.d.ts +38 -0
  8. package/dist/controller-ipc.js +292 -0
  9. package/dist/controller-main.d.ts +1 -0
  10. package/dist/controller-main.js +26 -0
  11. package/dist/controller-role.d.ts +40 -0
  12. package/dist/controller-role.js +37 -0
  13. package/dist/controller-socket.d.ts +6 -0
  14. package/dist/controller-socket.js +43 -0
  15. package/dist/controller.d.ts +25 -0
  16. package/dist/controller.js +217 -0
  17. package/dist/delegation.d.ts +73 -0
  18. package/dist/delegation.js +71 -0
  19. package/dist/domain.d.ts +20 -18
  20. package/dist/domain.js +6 -1
  21. package/dist/extension.js +43 -26
  22. package/dist/host-adapter.js +1 -1
  23. package/dist/launch-spec.d.ts +1 -3
  24. package/dist/launch-spec.js +38 -15
  25. package/dist/lease.d.ts +0 -1
  26. package/dist/lease.js +8 -11
  27. package/dist/mailbox.js +110 -34
  28. package/dist/manager.d.ts +52 -6
  29. package/dist/manager.js +568 -32
  30. package/dist/mcp/app.html +948 -0
  31. package/dist/mcp/delegate.d.ts +30 -0
  32. package/dist/mcp/delegate.js +44 -0
  33. package/dist/mcp/views.d.ts +5 -0
  34. package/dist/mcp/views.js +12 -0
  35. package/dist/mcp-server.d.ts +6 -0
  36. package/dist/mcp-server.js +287 -0
  37. package/dist/mcp-stdio.d.ts +2 -0
  38. package/dist/mcp-stdio.js +111 -0
  39. package/dist/process-lock.d.ts +1 -1
  40. package/dist/process-lock.js +4 -2
  41. package/dist/protocol.d.ts +5 -5
  42. package/dist/protocol.js +3 -4
  43. package/dist/receipts.d.ts +80 -0
  44. package/dist/receipts.js +326 -0
  45. package/dist/runtime.d.ts +5 -26
  46. package/dist/runtime.js +15 -57
  47. package/dist/session-bootstrap.js +2 -6
  48. package/dist/state-db.d.ts +11 -4
  49. package/dist/state-db.js +102 -24
  50. package/dist/state.js +12 -3
  51. package/dist/tool-activation.d.ts +2 -2
  52. package/dist/tool-activation.js +5 -5
  53. package/dist/tool-presentation.d.ts +2 -6
  54. package/dist/tool-presentation.js +75 -122
  55. package/dist/tools.d.ts +0 -3
  56. package/dist/tools.js +104 -132
  57. package/package.json +12 -5
  58. package/dist/json-lines.d.ts +0 -14
  59. package/dist/json-lines.js +0 -95
@@ -0,0 +1,948 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width,initial-scale=1">
6
+ <meta http-equiv="Content-Security-Policy" content="default-src 'none'; style-src 'unsafe-inline'; script-src 'unsafe-inline'; img-src data:; connect-src 'none'">
7
+ <title>{{TITLE}}</title>
8
+ <style>
9
+ /* Compact shell adapted from AgentDock mcpapps/app.html
10
+ https://github.com/uvwt/agentdock-protocol/blob/a0ffc5114507a498013dc8a5bbd572114e23bf5d/mcpapps/app.html
11
+ Copyright AgentDock contributors. Licensed Apache-2.0. */
12
+ :root {
13
+ color-scheme: light dark;
14
+ font: 13px/1.45 var(--font-sans, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif);
15
+ --ad-text-primary: var(--color-text-primary, #111827);
16
+ --ad-text-secondary: var(--color-text-secondary, #4b5563);
17
+ --ad-text-muted: var(--color-text-muted, #9ca3af);
18
+ --ad-surface: var(--color-background-primary, #ffffff);
19
+ --ad-surface-hover: var(--color-background-secondary, #f9fafb);
20
+ --ad-surface-muted: var(--color-background-tertiary, #f3f4f6);
21
+ --ad-border: var(--color-border-subtle, #e5e7eb);
22
+ --ad-border-strong: var(--color-border, #d1d5db);
23
+ --ad-success: #16a34a;
24
+ --ad-danger: #dc2626;
25
+ --ad-accent: #2563eb;
26
+ }
27
+ :root[data-theme="dark"] {
28
+ color-scheme: dark;
29
+ --ad-text-primary: var(--color-text-primary, #f9fafb);
30
+ --ad-text-secondary: var(--color-text-secondary, #9ca3af);
31
+ --ad-text-muted: var(--color-text-muted, #6b7280);
32
+ --ad-surface: var(--color-background-primary, #111827);
33
+ --ad-surface-hover: var(--color-background-secondary, #1f2937);
34
+ --ad-surface-muted: var(--color-background-tertiary, #1e2533);
35
+ --ad-border: var(--color-border-subtle, #374151);
36
+ --ad-border-strong: var(--color-border, #4b5563);
37
+ --ad-success: #22c55e;
38
+ --ad-danger: #ef4444;
39
+ --ad-accent: #3b82f6;
40
+ }
41
+ @media (prefers-color-scheme: dark) {
42
+ :root:not([data-theme="light"]) {
43
+ color-scheme: dark;
44
+ --ad-text-primary: var(--color-text-primary, #f9fafb);
45
+ --ad-text-secondary: var(--color-text-secondary, #9ca3af);
46
+ --ad-text-muted: var(--color-text-muted, #6b7280);
47
+ --ad-surface: var(--color-background-primary, #111827);
48
+ --ad-surface-hover: var(--color-background-secondary, #1f2937);
49
+ --ad-surface-muted: var(--color-background-tertiary, #1e2533);
50
+ --ad-border: var(--color-border-subtle, #374151);
51
+ --ad-border-strong: var(--color-border, #4b5563);
52
+ --ad-success: #22c55e;
53
+ --ad-danger: #ef4444;
54
+ --ad-accent: #3b82f6;
55
+ }
56
+ }
57
+ * { box-sizing: border-box; }
58
+ body {
59
+ margin: 0;
60
+ padding: 8px 10px;
61
+ background: transparent;
62
+ color: var(--ad-text-primary);
63
+ }
64
+ #root {
65
+ background: var(--ad-surface);
66
+ border: 1px solid var(--ad-border-strong);
67
+ border-radius: 12px;
68
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05), 0 2px 8px rgba(0, 0, 0, 0.02);
69
+ overflow: hidden;
70
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
71
+ }
72
+ .compact-toggle {
73
+ width: 100%;
74
+ min-height: 64px;
75
+ border: 0;
76
+ padding: 12px 14px;
77
+ background: transparent;
78
+ color: inherit;
79
+ display: grid;
80
+ grid-template-columns: minmax(0, 1fr) auto;
81
+ gap: 6px 12px;
82
+ align-items: center;
83
+ text-align: left;
84
+ font: inherit;
85
+ cursor: pointer;
86
+ transition: background 0.12s ease;
87
+ }
88
+ .compact-toggle:hover {
89
+ background: var(--ad-surface-hover);
90
+ }
91
+ .compact-toggle.single-line { min-height: 44px; }
92
+ .compact-primary {
93
+ display: flex;
94
+ min-width: 0;
95
+ gap: 8px;
96
+ align-items: center;
97
+ }
98
+ .compact-action {
99
+ font-size: 10px;
100
+ font-weight: 700;
101
+ letter-spacing: 0.05em;
102
+ text-transform: uppercase;
103
+ padding: 2px 6px;
104
+ border-radius: 5px;
105
+ background: var(--ad-surface-muted);
106
+ color: var(--ad-text-secondary);
107
+ border: 1px solid var(--ad-border);
108
+ }
109
+ .compact-title {
110
+ min-width: 0;
111
+ overflow: hidden;
112
+ text-overflow: ellipsis;
113
+ white-space: nowrap;
114
+ font-weight: 600;
115
+ font-size: 13.5px;
116
+ }
117
+ .compact-end {
118
+ display: flex;
119
+ align-items: center;
120
+ gap: 8px;
121
+ white-space: nowrap;
122
+ }
123
+ .chevron {
124
+ width: 7px;
125
+ height: 7px;
126
+ border-right: 1.5px solid var(--ad-text-secondary);
127
+ border-bottom: 1.5px solid var(--ad-text-secondary);
128
+ transform: rotate(45deg);
129
+ transition: transform 0.15s ease;
130
+ }
131
+ .expanded .chevron { transform: rotate(225deg); }
132
+ .compact-row { grid-column: 1 / -1; min-width: 0; }
133
+ .compact-summary {
134
+ color: var(--ad-text-secondary);
135
+ font-size: 12px;
136
+ line-height: 1.4;
137
+ overflow: hidden;
138
+ display: -webkit-box;
139
+ -webkit-line-clamp: 2;
140
+ -webkit-box-orient: vertical;
141
+ }
142
+ .compact-meta-row {
143
+ color: var(--ad-text-muted);
144
+ font-size: 11px;
145
+ white-space: nowrap;
146
+ overflow: hidden;
147
+ text-overflow: ellipsis;
148
+ }
149
+ .detail-panel {
150
+ border-top: 1px solid var(--ad-border);
151
+ padding: 12px 14px 14px;
152
+ background: var(--ad-surface);
153
+ }
154
+ .detail-panel[hidden] { display: none; }
155
+ .meta {
156
+ display: flex;
157
+ flex-wrap: wrap;
158
+ gap: 8px;
159
+ margin: 4px 0 8px;
160
+ font-size: 11px;
161
+ color: var(--ad-text-secondary);
162
+ }
163
+ #error:not(:empty) {
164
+ color: var(--ad-danger);
165
+ padding: 4px 8px;
166
+ border-radius: 4px;
167
+ background: rgba(220, 38, 38, 0.08);
168
+ }
169
+ pre, .pane-box {
170
+ margin: 8px 0 10px;
171
+ min-height: 65px;
172
+ max-height: 380px;
173
+ overflow: auto;
174
+ border: 1px solid var(--ad-border);
175
+ border-radius: 8px;
176
+ background: var(--ad-surface-muted);
177
+ padding: 10px 12px;
178
+ font: 12px/1.55 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
179
+ white-space: pre-wrap;
180
+ word-break: break-word;
181
+ color: var(--ad-text-primary);
182
+ }
183
+ .tabs, .composer, .create, .filter {
184
+ display: flex;
185
+ gap: 8px;
186
+ align-items: center;
187
+ }
188
+ .tabs {
189
+ display: inline-flex;
190
+ background: var(--ad-surface-muted);
191
+ padding: 3px;
192
+ border-radius: 8px;
193
+ border: 1px solid var(--ad-border);
194
+ gap: 2px;
195
+ margin: 10px 0 8px;
196
+ }
197
+ .composer, .create, .filter { margin-top: 8px; }
198
+ button, input, select {
199
+ font: inherit;
200
+ color: inherit;
201
+ background: var(--ad-surface);
202
+ border: 1px solid var(--ad-border-strong);
203
+ border-radius: 6px;
204
+ padding: 5px 10px;
205
+ font-size: 12px;
206
+ }
207
+ input:focus, select:focus {
208
+ outline: none;
209
+ border-color: var(--ad-text-primary);
210
+ box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.05);
211
+ }
212
+ button {
213
+ cursor: pointer;
214
+ transition: all 0.12s ease;
215
+ font-weight: 500;
216
+ }
217
+ button:hover:not(:disabled) {
218
+ background: var(--ad-surface-hover);
219
+ border-color: var(--ad-border-strong);
220
+ }
221
+ button:disabled {
222
+ opacity: 0.5;
223
+ cursor: not-allowed;
224
+ }
225
+ .tabs button {
226
+ border: 0;
227
+ border-radius: 6px;
228
+ padding: 4px 11px;
229
+ font-size: 11.5px;
230
+ font-weight: 500;
231
+ color: var(--ad-text-secondary);
232
+ background: transparent;
233
+ }
234
+ .tabs button:hover {
235
+ color: var(--ad-text-primary);
236
+ }
237
+ .tabs button[aria-selected="true"] {
238
+ background: var(--ad-surface);
239
+ color: var(--ad-text-primary);
240
+ font-weight: 600;
241
+ box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
242
+ }
243
+ #send, #start {
244
+ background: var(--ad-text-primary);
245
+ color: var(--ad-surface);
246
+ border-color: var(--ad-text-primary);
247
+ font-weight: 600;
248
+ }
249
+ #send:hover:not(:disabled), #start:hover:not(:disabled) {
250
+ opacity: 0.88;
251
+ }
252
+ #stop {
253
+ color: var(--ad-danger);
254
+ border-color: var(--ad-border-strong);
255
+ }
256
+ #stop:hover:not(:disabled) {
257
+ background: rgba(220, 38, 38, 0.08);
258
+ border-color: var(--ad-danger);
259
+ }
260
+ .list {
261
+ max-height: 180px;
262
+ overflow-y: auto;
263
+ margin: 8px 0;
264
+ display: flex;
265
+ flex-direction: column;
266
+ gap: 3px;
267
+ padding: 1px;
268
+ }
269
+ .list button {
270
+ display: flex;
271
+ width: 100%;
272
+ align-items: center;
273
+ justify-content: space-between;
274
+ text-align: left;
275
+ border: 1px solid var(--ad-border);
276
+ border-radius: 6px;
277
+ background: var(--ad-surface);
278
+ padding: 6px 10px;
279
+ font-size: 12px;
280
+ }
281
+ .list button:hover {
282
+ background: var(--ad-surface-hover);
283
+ border-color: var(--ad-border-strong);
284
+ }
285
+ .list button[aria-current="true"] {
286
+ background: var(--ad-surface-muted);
287
+ border-color: var(--ad-border-strong);
288
+ font-weight: 600;
289
+ }
290
+ @media (max-width: 520px) {
291
+ body { padding: 4px; }
292
+ .compact-toggle { padding: 9px 10px; }
293
+ .compact-meta-row { white-space: normal; }
294
+ .composer, .create, .filter { flex-wrap: wrap; }
295
+ }
296
+ </style>
297
+ </head>
298
+ <body>
299
+ <main id="root"></main>
300
+ <script>
301
+
302
+ const view = "{{VIEW}}";
303
+ const root = document.querySelector("#root");
304
+ const TERMINAL = new Set(["completed", "failed", "uncertain"]);
305
+ const pending = new Map();
306
+ let rpcID = 0;
307
+ let generation = 0;
308
+ let timer = 0;
309
+ let busy = false;
310
+ let queued = false;
311
+ let childId = "";
312
+ let revision = 0;
313
+ let terminal = false;
314
+ let status = "";
315
+ let outputExpanded = false;
316
+ let outputText = "";
317
+ let outputLoaded = false;
318
+ let transcriptCursor = 0;
319
+ let stopArmed = false;
320
+ let lastReportedHeight = 0;
321
+ let closed = false;
322
+ let transcriptRequest = null;
323
+ let userSelectedTab = false;
324
+ let lastChildren = [];
325
+ let lastStats = { total: 0, running: 0, blocked: 0, completed: 0, failed: 0 };
326
+ let historyRows = [];
327
+ let historyLoaded = false;
328
+
329
+ const el = (tag, className, text) => {
330
+ const node = document.createElement(tag);
331
+ if (className) node.className = className;
332
+ if (text !== undefined) node.textContent = String(text);
333
+ return node;
334
+ };
335
+
336
+ const toggle = el("button", "compact-toggle");
337
+ toggle.type = "button";
338
+ toggle.setAttribute("aria-expanded", "false");
339
+ const actionNode = el("strong", "compact-action", view === "dashboard" ? "LIST" : "TASK");
340
+ const titleNode = el("span", "compact-title", view === "dashboard" ? "Subagents" : "Subagent");
341
+ titleNode.id = "title";
342
+ const primary = el("span", "compact-primary");
343
+ primary.append(actionNode, titleNode);
344
+ const stateNode = el("span", "", "");
345
+ const end = el("span", "compact-end");
346
+ end.append(stateNode, el("span", "chevron"));
347
+ const taskNode = el("span", "compact-row compact-summary", "");
348
+ taskNode.id = "task";
349
+ const metricNode = el("span", "compact-row compact-meta-row", "");
350
+ toggle.append(primary, end, taskNode, metricNode);
351
+
352
+ const panel = el("div", "detail-panel");
353
+ panel.hidden = true;
354
+ const filter = el("input");
355
+ filter.id = "filter";
356
+ filter.setAttribute("aria-label", "Filter subagents");
357
+ filter.placeholder = "Filter";
358
+ const list = el("div", "list");
359
+ list.id = "list";
360
+ const agent = el("select");
361
+ agent.id = "agent";
362
+ agent.setAttribute("aria-label", "Agent");
363
+ const newTask = el("input");
364
+ newTask.id = "new-task";
365
+ newTask.setAttribute("aria-label", "New task");
366
+ newTask.placeholder = "New task";
367
+ const start = el("button", "", "Start");
368
+ start.type = "button";
369
+ start.disabled = view === "dashboard";
370
+ const create = el("form", "create");
371
+ create.append(agent, newTask, start);
372
+ const filterRow = el("div", "filter");
373
+ filterRow.append(filter);
374
+ const tabs = el("div", "tabs");
375
+ const panes = {};
376
+ for (const name of ["Activity", "Output", "Diagnostics", "Transcript"]) {
377
+ const button = el("button", "", name);
378
+ button.type = "button";
379
+ button.setAttribute("aria-selected", name === "Activity" ? "true" : "false");
380
+ const pane = el(name === "Activity" || name === "Diagnostics" ? "div" : "pre", name === "Activity" || name === "Diagnostics" ? "pane-box" : "");
381
+ pane.id = name.toLowerCase();
382
+ pane.hidden = name !== "Activity";
383
+ panes[name] = pane;
384
+ button.addEventListener("click", () => {
385
+ userSelectedTab = true;
386
+ selectTab(name);
387
+ });
388
+ tabs.append(button);
389
+ }
390
+ const history = el("div", "list");
391
+ history.id = "history";
392
+ history.hidden = true;
393
+ function selectTab(name) {
394
+ for (const other of tabs.querySelectorAll("button")) other.setAttribute("aria-selected", other.textContent === name ? "true" : "false");
395
+ for (const [key, node] of Object.entries(panes)) node.hidden = key !== name;
396
+ if (name === "Transcript" && !panes.Transcript.textContent) void loadTranscript(false);
397
+ if (name === "Output" && !outputLoaded) kick(0);
398
+ if (name === "Diagnostics" && !historyLoaded) void loadHistory();
399
+ scheduleReportSize();
400
+ }
401
+ const moreOutput = el("button", "", "Show all");
402
+ moreOutput.type = "button";
403
+ moreOutput.hidden = true;
404
+ moreOutput.addEventListener("click", () => { outputExpanded = true; paintOutput(); scheduleReportSize(); });
405
+ const moreTranscript = el("button", "", "More");
406
+ moreTranscript.type = "button";
407
+ moreTranscript.hidden = true;
408
+ moreTranscript.addEventListener("click", () => { void loadTranscript(true); });
409
+ const errorNode = el("div", "meta");
410
+ errorNode.id = "error";
411
+ const hintNode = el("div", "meta");
412
+ hintNode.id = "hint";
413
+ const composer = el("form", "composer");
414
+ const draft = el("input");
415
+ draft.id = "continue";
416
+ draft.setAttribute("aria-label", "Continue this agent");
417
+ draft.placeholder = "Continue this agent...";
418
+ const send = el("button", "", "Send");
419
+ send.id = "send";
420
+ send.type = "button";
421
+ const stop = el("button", "", "Stop");
422
+ stop.type = "button";
423
+ const copy = el("button", "", "Copy");
424
+ copy.type = "button";
425
+ composer.append(draft, send, stop, copy);
426
+ panel.append(filterRow, list, create, tabs, panes.Activity, panes.Output, moreOutput, panes.Diagnostics, history, panes.Transcript, moreTranscript, hintNode, errorNode, composer);
427
+ root.append(toggle, panel);
428
+ if (view !== "dashboard") { filterRow.hidden = true; list.hidden = true; create.hidden = true; }
429
+
430
+ toggle.addEventListener("click", () => {
431
+ const expanded = toggle.getAttribute("aria-expanded") === "true";
432
+ toggle.setAttribute("aria-expanded", String(!expanded));
433
+ toggle.classList.toggle("expanded", !expanded);
434
+ panel.hidden = expanded;
435
+ scheduleReportSize();
436
+ });
437
+ filter.addEventListener("input", () => paintList(lastChildren, lastStats));
438
+ create.addEventListener("submit", (event) => { event.preventDefault(); void createChild(); });
439
+ start.addEventListener("click", () => { void createChild(); });
440
+ send.addEventListener("click", (event) => { event.preventDefault(); void steer(); });
441
+ stop.addEventListener("click", () => { void halt(); });
442
+ copy.addEventListener("click", () => { void copyText(); });
443
+
444
+ function paintOutput() {
445
+ if (!outputLoaded) {
446
+ panes.Output.textContent = childId ? "Output loads when this tab is shown." : "No agent selected.";
447
+ moreOutput.hidden = true;
448
+ return;
449
+ }
450
+ const text = outputText || "";
451
+ panes.Output.textContent = outputExpanded || text.length <= 600 ? text : text.slice(0, 600) + "…";
452
+ moreOutput.hidden = text.length <= 600;
453
+ }
454
+
455
+ function paintList(children, stats) {
456
+ lastChildren = Array.isArray(children) ? children : lastChildren;
457
+ if (stats && typeof stats === "object") lastStats = stats;
458
+ const query = filter.value.trim().toLowerCase();
459
+ const shown = lastChildren.filter((child) => {
460
+ const blob = [child.agent, child.id, child.runtimeState, child.status, child.summary].join(" ").toLowerCase();
461
+ return query === "" || blob.includes(query);
462
+ });
463
+ const top = list.scrollTop;
464
+ list.replaceChildren();
465
+ for (const child of shown) {
466
+ const titleText = child.id + (child.agent ? " (" + child.agent + ")" : "") + " · " + (child.runtimeState || "") + (child.status ? " / " + child.status : "");
467
+ const button = el("button", "", titleText);
468
+ button.type = "button";
469
+ button.setAttribute("aria-current", child.id === childId ? "true" : "false");
470
+ button.addEventListener("click", () => focusChild(child));
471
+ list.append(button);
472
+ }
473
+ if (!shown.length) list.append(el("div", "meta", lastChildren.length ? "No match" : "No subagents"));
474
+ list.scrollTop = top;
475
+ if (view === "dashboard") {
476
+ titleNode.textContent = childId ? "Subagents · " + childId : "Subagents";
477
+ stateNode.textContent = String(lastStats.total || 0);
478
+ metricNode.textContent = "running " + (lastStats.running || 0) + " · blocked " + (lastStats.blocked || 0) + " · completed " + (lastStats.completed || 0) + " · failed " + (lastStats.failed || 0);
479
+ }
480
+ }
481
+
482
+ function paintHistory() {
483
+ const rows = historyRows.filter((row) => row.id === childId);
484
+ history.replaceChildren();
485
+ history.hidden = rows.length < 2;
486
+ for (const row of rows) {
487
+ const label = "rev " + row.revision + " · " + row.status + (row.hasOutput ? " · output" : "");
488
+ const button = el("button", "", label);
489
+ button.type = "button";
490
+ button.setAttribute("aria-current", row.revision === revision ? "true" : "false");
491
+ button.addEventListener("click", () => {
492
+ generation += 1;
493
+ revision = row.revision;
494
+ terminal = TERMINAL.has(row.status);
495
+ outputText = "";
496
+ outputLoaded = false;
497
+ paintOutput();
498
+ kick(0);
499
+ });
500
+ history.append(button);
501
+ }
502
+ }
503
+
504
+ function focusChild(child) {
505
+ if (!child || child.id === childId) return;
506
+ generation += 1;
507
+ childId = child.id;
508
+ draft.value = "";
509
+ userSelectedTab = false;
510
+ revision = typeof child.revision === "number" ? child.revision : 0;
511
+ terminal = TERMINAL.has(child.status || "");
512
+ outputText = "";
513
+ outputLoaded = false;
514
+ outputExpanded = false;
515
+ paintOutput();
516
+ transcriptCursor = 0;
517
+ transcriptRequest = null;
518
+ historyRows = [];
519
+ historyLoaded = false;
520
+ moreTranscript.disabled = false;
521
+ panes.Transcript.textContent = "";
522
+ moreTranscript.hidden = true;
523
+ stopArmed = false;
524
+ stop.textContent = "Stop";
525
+ paintList(lastChildren, lastStats);
526
+ applyView({
527
+ id: child.id,
528
+ revision: child.revision,
529
+ status: child.status,
530
+ agent: child.agent,
531
+ hasOutput: child.hasOutput,
532
+ progress: { runtimeState: child.runtimeState, summary: child.summary }
533
+ });
534
+ if (terminal) selectTab("Output");
535
+ else if (!panes.Transcript.hidden) void loadTranscript(false);
536
+ if (!panel.hidden) scheduleReportSize();
537
+ kick(0);
538
+ }
539
+
540
+ function applyView(data) {
541
+ if (!data || typeof data !== "object") return;
542
+ if (typeof data.id === "string" && data.id !== "" && view === "task") childId = data.id;
543
+ if (typeof data.revision === "number" && data.revision > 0) revision = data.revision;
544
+ if (typeof data.agent === "string" && data.agent !== "" && view === "task") titleNode.textContent = data.agent;
545
+ if (typeof data.task === "string" && data.task !== "") taskNode.textContent = data.task;
546
+ const progress = data.progress && typeof data.progress === "object" ? data.progress : {};
547
+ const runtime = typeof progress.runtimeState === "string" ? progress.runtimeState : "";
548
+ const delegationStatus = typeof data.status === "string" ? data.status : "";
549
+ status = delegationStatus || runtime;
550
+ terminal = TERMINAL.has(delegationStatus);
551
+ const ended = [delegationStatus, runtime && runtime !== delegationStatus ? runtime : ""].filter(Boolean).join(" · ");
552
+ stateNode.textContent = ended + elapsed(data.createdAt);
553
+ if (typeof data.resultText === "string") {
554
+ outputText = data.resultText;
555
+ outputLoaded = true;
556
+ paintOutput();
557
+ }
558
+ if (!userSelectedTab && terminal) selectTab("Output");
559
+ const activeTool = typeof progress.activeTool === "string" ? progress.activeTool : "";
560
+ const summary = typeof progress.summary === "string" ? progress.summary : "";
561
+ if (activeTool) panes.Activity.textContent = "● " + activeTool + (summary ? "\n" + summary : "");
562
+ else if (summary) panes.Activity.textContent = summary;
563
+ else if (terminal) panes.Activity.textContent = "Agent completed. View Output for the final result.";
564
+ else if (runtime === "running") panes.Activity.textContent = "Agent is running...";
565
+ else panes.Activity.textContent = "No active activity.";
566
+ const reason = data.error && typeof data.error.reason === "string" ? data.error.reason : "";
567
+ if (view !== "dashboard") metricNode.textContent = usageText(data.usage);
568
+ panes.Diagnostics.textContent = ["agent " + (childId || "—"), "revision " + (revision || "latest"), reason].filter(Boolean).join("\n");
569
+ hintNode.textContent = hint(status, reason);
570
+ }
571
+
572
+ function hint(value, reason) {
573
+ if (value === "blocked") return "Answer below to continue this agent.";
574
+ if (value === "failed" || value === "uncertain" || value === "error") return reason || "Send a steer, or stop and inspect the transcript.";
575
+ if (String(reason).includes("model_unavailable")) return "The controller has no usable model.";
576
+ if (value === "disconnected") return "Reconnecting…";
577
+ return "";
578
+ }
579
+
580
+ function elapsed(at) {
581
+ const start = typeof at === "number" ? at : Date.parse(at || "");
582
+ if (!Number.isFinite(start)) return "";
583
+ const seconds = Math.max(0, Math.floor((Date.now() - start) / 1000));
584
+ return " · " + String(Math.floor(seconds / 60)).padStart(2, "0") + ":" + String(seconds % 60).padStart(2, "0");
585
+ }
586
+
587
+ function usageText(usage) {
588
+ if (!usage || typeof usage !== "object") return metricNode.textContent;
589
+ const turns = usage.turns ? usage.turns + " turns" : "";
590
+ const tokens = (usage.inputTokens || 0) + (usage.outputTokens || 0);
591
+ return [turns, tokens ? Math.round(tokens / 10) / 100 + "k tokens" : ""].filter(Boolean).join(" · ");
592
+ }
593
+
594
+ function structured(result) {
595
+ return result && result.structuredContent && typeof result.structuredContent === "object" ? result.structuredContent : {};
596
+ }
597
+
598
+ function textOf(result) {
599
+ const block = result && Array.isArray(result.content) ? result.content.find((item) => item.type === "text") : undefined;
600
+ return block ? block.text : "";
601
+ }
602
+
603
+ async function callTool(name, args) {
604
+ const result = await rpcRequest("tools/call", { name, arguments: args }, 8000);
605
+ const data = structured(result);
606
+ const carriesData = data.status || data.id || Array.isArray(data.children) || Array.isArray(data.agents) || Array.isArray(data.entries) || Array.isArray(data.receipts);
607
+ if (result && result.isError && !carriesData) throw new Error(data.reason || data.error || textOf(result) || "Tool call failed");
608
+ return data;
609
+ }
610
+
611
+ async function pull(gen) {
612
+ if (view === "dashboard") {
613
+ const listed = await callTool("list", {});
614
+ if (gen !== generation) return 0;
615
+ paintList(listed.children || [], listed);
616
+ }
617
+ if (childId) {
618
+ const includeOutput = !panes.Output.hidden && !outputLoaded;
619
+ const request = revision > 0 ? { id: childId, revision, includeOutput } : { id: childId, includeOutput };
620
+ const viewData = await callTool("get", request);
621
+ if (gen !== generation) return 0;
622
+ applyView(viewData);
623
+ }
624
+ if (view === "task" && terminal) return 0;
625
+ return status === "blocked" ? 10000 : 2000;
626
+ }
627
+
628
+ function kick(delay) {
629
+ if (closed || document.hidden) return;
630
+ if (busy) { queued = true; return; }
631
+ if (timer) window.clearTimeout(timer);
632
+ const gen = generation;
633
+ timer = window.setTimeout(() => { void run(gen); }, delay);
634
+ }
635
+
636
+ async function run(gen) {
637
+ timer = 0;
638
+ if (closed || gen !== generation || document.hidden) return;
639
+ busy = true;
640
+ let next = 2000;
641
+ try { next = await pull(gen); }
642
+ catch (error) {
643
+ if (gen === generation) {
644
+ status = "disconnected";
645
+ errorNode.textContent = error instanceof Error ? error.message : String(error);
646
+ hintNode.textContent = hint(status, errorNode.textContent);
647
+ next = 5000;
648
+ }
649
+ }
650
+ finally { busy = false; }
651
+ if (closed) return;
652
+ if (queued) { queued = false; kick(0); return; }
653
+ if (gen !== generation) return;
654
+ if (next > 0 && !document.hidden) kick(next);
655
+ }
656
+
657
+ async function steer() {
658
+ const task = draft.value.trim();
659
+ if (closed || !childId || task === "" || send.disabled) return;
660
+ const selected = childId;
661
+ const gen = generation;
662
+ send.disabled = true;
663
+ try {
664
+ const accepted = await callTool("delegate", { id: selected, task, wait: false });
665
+ if (closed || gen !== generation || childId !== selected) return;
666
+ const nextRevision = typeof accepted.revision === "number" ? accepted.revision : 0;
667
+ if (nextRevision > 0 && nextRevision !== revision) {
668
+ generation += 1;
669
+ revision = nextRevision;
670
+ terminal = false;
671
+ outputText = "";
672
+ outputLoaded = false;
673
+ paintOutput();
674
+ }
675
+ applyView(accepted);
676
+ draft.value = "";
677
+ errorNode.textContent = "";
678
+ } catch (error) {
679
+ if (!closed && childId === selected && gen === generation) errorNode.textContent = error instanceof Error ? error.message : String(error);
680
+ } finally {
681
+ send.disabled = false;
682
+ if (!closed && !terminal) kick(0);
683
+ }
684
+ }
685
+
686
+ async function halt() {
687
+ if (closed || !childId || stop.disabled) return;
688
+ if (!stopArmed) {
689
+ stopArmed = true;
690
+ stop.textContent = "Confirm stop";
691
+ window.setTimeout(() => {
692
+ if (stopArmed) { stopArmed = false; stop.textContent = "Stop"; }
693
+ }, 4000);
694
+ return;
695
+ }
696
+ stopArmed = false;
697
+ stop.textContent = "Stop";
698
+ stop.disabled = true;
699
+ const selected = childId;
700
+ const gen = generation;
701
+ try {
702
+ const stopped = await callTool("stop", { id: selected });
703
+ if (closed || gen !== generation || childId !== selected) return;
704
+ applyView(stopped);
705
+ kick(0);
706
+ } catch (error) {
707
+ if (!closed) errorNode.textContent = error instanceof Error ? error.message : String(error);
708
+ } finally {
709
+ stop.disabled = false;
710
+ }
711
+ }
712
+
713
+ async function createChild() {
714
+ const task = newTask.value.trim();
715
+ if (closed || task === "" || start.disabled) return;
716
+ const gen = generation;
717
+ start.disabled = true;
718
+ try {
719
+ const created = await callTool("delegate", {
720
+ agent: agent.value || undefined,
721
+ task,
722
+ wait: false
723
+ });
724
+ newTask.value = "";
725
+ if (closed) return;
726
+ if (typeof created.id === "string" && gen === generation) {
727
+ childId = created.id;
728
+ revision = typeof created.revision === "number" ? created.revision : 0;
729
+ terminal = false;
730
+ outputText = "";
731
+ outputLoaded = false;
732
+ paintOutput();
733
+ generation += 1;
734
+ applyView(created);
735
+ }
736
+ kick(0);
737
+ } catch (error) {
738
+ if (!closed) errorNode.textContent = error instanceof Error ? error.message : String(error);
739
+ } finally {
740
+ start.disabled = false;
741
+ }
742
+ }
743
+
744
+ async function loadHistory() {
745
+ historyLoaded = true;
746
+ try {
747
+ const page = await callTool("receipts", { limit: 20 });
748
+ historyRows = Array.isArray(page.receipts) ? page.receipts : [];
749
+ paintHistory();
750
+ scheduleReportSize();
751
+ } catch (error) {
752
+ historyLoaded = false;
753
+ if (!closed) errorNode.textContent = error instanceof Error ? error.message : String(error);
754
+ }
755
+ }
756
+
757
+ async function loadCatalog() {
758
+ const catalog = await callTool("catalog", {});
759
+ const agents = Array.isArray(catalog.agents) ? catalog.agents : [];
760
+ const selected = agent.value;
761
+ agent.replaceChildren();
762
+ for (const entry of agents) {
763
+ if (entry && entry.name) {
764
+ const opt = document.createElement("option");
765
+ opt.value = entry.name;
766
+ opt.textContent = entry.name;
767
+ if (entry.enabled === false) opt.disabled = true;
768
+ agent.append(opt);
769
+ }
770
+ }
771
+ if (selected && Array.from(agent.options).some((item) => item.value === selected)) agent.value = selected;
772
+ start.disabled = !agent.options.length;
773
+ }
774
+
775
+ async function loadTranscript(next) {
776
+ if (!childId || (next && !transcriptCursor) || transcriptRequest) return;
777
+ const cursor = next ? transcriptCursor : 0;
778
+ const request = { cursor, id: childId };
779
+ transcriptRequest = request;
780
+ moreTranscript.disabled = true;
781
+ try {
782
+ const data = await callTool("transcript", { id: childId, cursor, limit: 20 });
783
+ if (closed || transcriptRequest !== request) return;
784
+ const entries = Array.isArray(data.entries) ? data.entries : [];
785
+ const formatted = entries.map((entry) => JSON.stringify(entry)).join("\n");
786
+ if (!cursor) panes.Transcript.textContent = formatted;
787
+ else if (formatted) panes.Transcript.textContent += "\n" + formatted;
788
+ transcriptCursor = typeof data.nextCursor === "number" ? data.nextCursor : 0;
789
+ moreTranscript.hidden = !transcriptCursor;
790
+ scheduleReportSize();
791
+ } catch (error) {
792
+ if (!closed && transcriptRequest === request) errorNode.textContent = error instanceof Error ? error.message : String(error);
793
+ } finally {
794
+ if (transcriptRequest === request) { transcriptRequest = null; moreTranscript.disabled = false; }
795
+ }
796
+ }
797
+
798
+ async function copyText() {
799
+ const text = [childId && "agent " + childId, revision && "revision " + revision, outputText].filter(Boolean).join("\n");
800
+ if (navigator.clipboard && navigator.clipboard.writeText) await navigator.clipboard.writeText(text);
801
+ else panes.Diagnostics.textContent = text;
802
+ }
803
+
804
+ function applyHost(context) {
805
+ if (!context || typeof context !== "object") return;
806
+ if (context.theme === "light" || context.theme === "dark") document.documentElement.dataset.theme = context.theme;
807
+ else if (Object.prototype.hasOwnProperty.call(context, "theme")) delete document.documentElement.dataset.theme;
808
+ const variables = context.styles && context.styles.variables;
809
+ if (!variables || typeof variables !== "object") return;
810
+ for (const [name, value] of Object.entries(variables)) {
811
+ if (name.startsWith("--") && typeof value === "string") document.documentElement.style.setProperty(name, value);
812
+ }
813
+ }
814
+
815
+ function seed(data) {
816
+ if (closed || !data || typeof data !== "object") return;
817
+ if (Array.isArray(data.children)) paintList(data.children, data);
818
+ const nextChild = typeof data.id === "string" ? data.id : "";
819
+ const nextRevision = typeof data.revision === "number" ? data.revision : 0;
820
+ if (nextChild && nextChild !== childId) {
821
+ childId = nextChild;
822
+ revision = nextRevision;
823
+ terminal = false;
824
+ outputText = "";
825
+ outputLoaded = false;
826
+ generation += 1;
827
+ } else if (nextRevision > 0 && nextRevision !== revision) {
828
+ revision = nextRevision;
829
+ terminal = false;
830
+ outputText = "";
831
+ outputLoaded = false;
832
+ generation += 1;
833
+ }
834
+ applyView(data);
835
+ if (!(view === "task" && terminal)) kick(700);
836
+ }
837
+
838
+ const rpcNotify = (method, params) => window.parent.postMessage({ jsonrpc: "2.0", method, params: params || {} }, "*");
839
+ function rpcRequest(method, params, timeoutMS) {
840
+ const id = ++rpcID;
841
+ return new Promise((resolve, reject) => {
842
+ const timerID = window.setTimeout(() => {
843
+ if (!pending.delete(id)) return;
844
+ reject(new Error("RPC request timed out"));
845
+ }, timeoutMS || 8000);
846
+ pending.set(id, {
847
+ resolve: (value) => { window.clearTimeout(timerID); resolve(value); },
848
+ reject: (error) => { window.clearTimeout(timerID); reject(error); }
849
+ });
850
+ window.parent.postMessage({ jsonrpc: "2.0", id, method, params: params || {} }, "*");
851
+ });
852
+ }
853
+
854
+ let rafId = 0;
855
+ function reportSize() {
856
+ if (closed) return;
857
+ const rootEl = document.querySelector("#root");
858
+ const height = Math.ceil(rootEl ? rootEl.getBoundingClientRect().height + 16 : document.body.scrollHeight);
859
+ if (!height || Math.abs(height - lastReportedHeight) < 2) return;
860
+ lastReportedHeight = height;
861
+ rpcNotify("ui/notifications/size-changed", { height });
862
+ }
863
+
864
+ function scheduleReportSize() {
865
+ if (closed) return;
866
+ if (typeof requestAnimationFrame !== "undefined") {
867
+ if (rafId) return;
868
+ rafId = requestAnimationFrame(() => {
869
+ rafId = 0;
870
+ reportSize();
871
+ });
872
+ } else {
873
+ reportSize();
874
+ }
875
+ }
876
+
877
+ function teardown() {
878
+ if (closed) return;
879
+ closed = true;
880
+ if (timer) window.clearTimeout(timer);
881
+ for (const [id, waiter] of pending.entries()) {
882
+ pending.delete(id);
883
+ waiter.reject(new Error("View closed"));
884
+ }
885
+ }
886
+
887
+ window.addEventListener("message", (event) => {
888
+ if (event.source !== window.parent) return;
889
+ const message = event.data;
890
+ if (!message || message.jsonrpc !== "2.0") return;
891
+ if (message.id !== undefined && !message.method) {
892
+ const waiter = pending.get(message.id);
893
+ if (!waiter) return;
894
+ pending.delete(message.id);
895
+ if (message.error) waiter.reject(new Error(message.error.message || "RPC failed"));
896
+ else waiter.resolve(message.result);
897
+ return;
898
+ }
899
+ if (closed) return;
900
+ if (message.method === "ui/notifications/tool-input") {
901
+ const input = message.params && message.params.arguments;
902
+ if (view === "task" && input && typeof input.task === "string") {
903
+ taskNode.textContent = input.task;
904
+ stateNode.textContent = "starting";
905
+ }
906
+ return;
907
+ }
908
+ if (message.method === "ui/notifications/host-context-changed") {
909
+ applyHost(message.params || {});
910
+ return;
911
+ }
912
+ if (message.method === "ui/notifications/tool-result") {
913
+ const data = message.params && message.params.structuredContent;
914
+ seed(data);
915
+ if (view === "dashboard") void loadCatalog().catch((error) => { errorNode.textContent = error instanceof Error ? error.message : String(error); });
916
+ }
917
+ if (message.method === "ui/resource-teardown") teardown();
918
+ });
919
+
920
+ document.addEventListener("visibilitychange", () => {
921
+ if (document.hidden) {
922
+ if (timer) window.clearTimeout(timer);
923
+ timer = 0;
924
+ return;
925
+ }
926
+ kick(0);
927
+ });
928
+
929
+ if (typeof ResizeObserver !== "undefined") new ResizeObserver(scheduleReportSize).observe(document.body);
930
+ async function initializeBridge() {
931
+ try {
932
+ const initialized = await rpcRequest("ui/initialize", {
933
+ protocolVersion: "2026-01-26",
934
+ appInfo: { name: "pi-agents-" + view, version: "0.7.0" },
935
+ appCapabilities: { availableDisplayModes: ["inline"] }
936
+ }, 8000);
937
+ if (closed) return;
938
+ if (initialized && initialized.hostContext) applyHost(initialized.hostContext);
939
+ rpcNotify("ui/notifications/initialized", {});
940
+ scheduleReportSize();
941
+ } catch (_) {
942
+ if (!closed) errorNode.textContent = "Unable to initialize MCP App.";
943
+ }
944
+ }
945
+ initializeBridge();
946
+ </script>
947
+ </body>
948
+ </html>