@fitchmultz/pi-subagents 0.50.0 → 0.51.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 (169) hide show
  1. package/CHANGELOG.md +51 -2
  2. package/README.md +36 -5
  3. package/dist/agent/contracts.js +6 -24
  4. package/dist/agent/host.js +3 -16
  5. package/dist/agent/index.js +207 -173
  6. package/dist/agent/journal-tail.js +115 -156
  7. package/dist/agent/launch.js +20 -8
  8. package/dist/agent/legacy.js +65 -23
  9. package/dist/agent/notifications.js +138 -47
  10. package/dist/agent/observation-state.js +246 -0
  11. package/dist/agent/observation-view.js +258 -0
  12. package/dist/agent/observation.js +346 -0
  13. package/dist/agent/observe-card.js +243 -0
  14. package/dist/agent/observe-chrome.js +85 -0
  15. package/dist/agent/observe-client.js +285 -0
  16. package/dist/agent/observe-layout.js +65 -0
  17. package/dist/agent/observe-reading.js +84 -0
  18. package/dist/agent/observe-screen.js +400 -96
  19. package/dist/agent/observe-transcript.js +382 -0
  20. package/dist/agent/observe.js +250 -43
  21. package/dist/agent/process.js +198 -25
  22. package/dist/agent/receipts.js +92 -20
  23. package/dist/agent/runner.js +125 -20
  24. package/dist/agent/shell.js +2 -1
  25. package/dist/agent/storage.js +29 -12
  26. package/dist/agent/tool-schemas.js +20 -0
  27. package/dist/agent/typebox.js +10 -5
  28. package/dist/agent/usage.js +0 -115
  29. package/dist/agent/view.js +46 -16
  30. package/dist/agent/wake.js +99 -0
  31. package/dist/build-info.js +1 -1
  32. package/dist/intercom-client.d.ts +26 -0
  33. package/dist/intercom-client.js +45 -0
  34. package/dist/pi-intercom/agent-dir.d.ts +1 -0
  35. package/dist/pi-intercom/bridge-session.js +2 -1
  36. package/dist/pi-intercom/broker/broker.js +27 -21
  37. package/dist/pi-intercom/broker/client-last.d.ts +13 -0
  38. package/dist/pi-intercom/broker/client-last.js +17 -5
  39. package/dist/pi-intercom/broker/client.d.ts +60 -0
  40. package/dist/pi-intercom/broker/client.js +47 -16
  41. package/dist/pi-intercom/broker/connection.d.ts +29 -0
  42. package/dist/pi-intercom/broker/framing.d.ts +17 -0
  43. package/dist/pi-intercom/broker/framing.js +10 -7
  44. package/dist/pi-intercom/broker/guard.js +23 -0
  45. package/dist/pi-intercom/broker/last.d.ts +26 -0
  46. package/dist/pi-intercom/broker/last.js +57 -32
  47. package/dist/pi-intercom/broker/paths.d.ts +13 -0
  48. package/dist/pi-intercom/broker/paths.js +35 -12
  49. package/dist/pi-intercom/broker/pid.js +5 -1
  50. package/dist/pi-intercom/broker/server-connection.js +19 -0
  51. package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
  52. package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
  53. package/dist/pi-intercom/broker/targets.d.ts +9 -0
  54. package/dist/pi-intercom/connection.js +10 -2
  55. package/dist/pi-intercom/identity.js +6 -5
  56. package/dist/pi-intercom/inbound-delivery.js +16 -60
  57. package/dist/pi-intercom/inbound-journal.js +83 -6
  58. package/dist/pi-intercom/inbound-record.js +18 -1
  59. package/dist/pi-intercom/inbound.js +12 -38
  60. package/dist/pi-intercom/index.js +3 -0
  61. package/dist/pi-intercom/last-message.js +4 -16
  62. package/dist/pi-intercom/lifecycle.js +88 -3
  63. package/dist/pi-intercom/outbound.js +5 -15
  64. package/dist/pi-intercom/reply-tracker.js +7 -6
  65. package/dist/pi-intercom/reply-wait.js +2 -2
  66. package/dist/pi-intercom/session-targets.d.ts +24 -0
  67. package/dist/pi-intercom/tool-registration.js +4 -1
  68. package/dist/pi-intercom/tool-results.js +5 -7
  69. package/dist/pi-intercom/tool-schemas.js +12 -26
  70. package/dist/pi-intercom/types.d.ts +212 -0
  71. package/dist/pi-intercom/types.js +54 -3
  72. package/dist/pi-intercom/validation.d.ts +12 -0
  73. package/dist/pi-intercom/validation.js +3 -0
  74. package/dist/shared/bounded-json.js +151 -0
  75. package/dist/shared/compact-cards.js +66 -0
  76. package/dist/shared/journal-frames.js +3 -6
  77. package/dist/shared/json-projection.js +267 -129
  78. package/dist/shared/native-journal.js +123 -56
  79. package/dist/shared/post-exit-stdio-guard.js +120 -7
  80. package/dist/shared/tui-action.js +29 -0
  81. package/dist/shared/unknown.d.ts +9 -0
  82. package/docs/code-quality.md +2 -0
  83. package/docs/development.md +40 -1
  84. package/docs/intercom-bridge.md +9 -4
  85. package/docs/intercom.md +226 -6
  86. package/docs/performance.md +87 -11
  87. package/docs/reference.md +157 -20
  88. package/docs/retired-history-maintenance.md +252 -0
  89. package/package.json +28 -15
  90. package/scripts/archive-retired-history.mjs +464 -0
  91. package/scripts/build.mjs +48 -28
  92. package/scripts/measure-size.mjs +1 -1
  93. package/scripts/native-package-smoke.mjs +153 -12
  94. package/scripts/package-smoke.mjs +80 -11
  95. package/scripts/prepare.mjs +2 -2
  96. package/scripts/real-pi-live.mjs +377 -0
  97. package/scripts/real-pi-processes.mjs +6 -1
  98. package/scripts/real-pi-smoke.mjs +6 -282
  99. package/scripts/run-tests.mjs +4 -2
  100. package/scripts/typescript.mjs +35 -0
  101. package/skills/pi-intercom/SKILL.md +2 -1
  102. package/skills/pi-subagents/SKILL.md +23 -5
  103. package/src/agent/contracts.ts +8 -34
  104. package/src/agent/host.ts +19 -20
  105. package/src/agent/index.ts +224 -190
  106. package/src/agent/journal-tail.ts +141 -153
  107. package/src/agent/launch.ts +33 -9
  108. package/src/agent/legacy.ts +85 -31
  109. package/src/agent/notifications.ts +192 -67
  110. package/src/agent/observation-state.ts +277 -0
  111. package/src/agent/observation-view.ts +267 -0
  112. package/src/agent/observation.ts +405 -0
  113. package/src/agent/observe-card.ts +304 -0
  114. package/src/agent/observe-chrome.ts +170 -0
  115. package/src/agent/observe-client.ts +318 -0
  116. package/src/agent/observe-layout.ts +79 -0
  117. package/src/agent/observe-reading.ts +97 -0
  118. package/src/agent/observe-screen.ts +443 -129
  119. package/src/agent/observe-transcript.ts +434 -0
  120. package/src/agent/observe.ts +304 -71
  121. package/src/agent/process.ts +240 -34
  122. package/src/agent/receipts.ts +114 -24
  123. package/src/agent/runner.ts +125 -27
  124. package/src/agent/shell.ts +2 -1
  125. package/src/agent/storage.ts +27 -11
  126. package/src/agent/tool-schemas.ts +33 -0
  127. package/src/agent/typebox.ts +12 -7
  128. package/src/agent/usage.ts +0 -130
  129. package/src/agent/view.ts +70 -20
  130. package/src/agent/wake.ts +107 -0
  131. package/src/intercom-client.ts +108 -0
  132. package/src/pi-intercom/bridge-session.ts +2 -2
  133. package/src/pi-intercom/broker/broker.ts +27 -23
  134. package/src/pi-intercom/broker/client-last.ts +25 -12
  135. package/src/pi-intercom/broker/client.ts +55 -33
  136. package/src/pi-intercom/broker/framing.ts +11 -8
  137. package/src/pi-intercom/broker/guard.ts +33 -0
  138. package/src/pi-intercom/broker/last.ts +86 -33
  139. package/src/pi-intercom/broker/paths.ts +39 -12
  140. package/src/pi-intercom/broker/pid.ts +6 -1
  141. package/src/pi-intercom/broker/server-connection.ts +25 -0
  142. package/src/pi-intercom/connection.ts +21 -10
  143. package/src/pi-intercom/identity.ts +8 -5
  144. package/src/pi-intercom/inbound-delivery.ts +20 -66
  145. package/src/pi-intercom/inbound-journal.ts +103 -7
  146. package/src/pi-intercom/inbound-record.ts +21 -4
  147. package/src/pi-intercom/inbound.ts +18 -49
  148. package/src/pi-intercom/index.ts +3 -0
  149. package/src/pi-intercom/last-message.ts +10 -19
  150. package/src/pi-intercom/lifecycle.ts +117 -14
  151. package/src/pi-intercom/outbound.ts +7 -16
  152. package/src/pi-intercom/reply-tracker.ts +8 -6
  153. package/src/pi-intercom/reply-wait.ts +3 -2
  154. package/src/pi-intercom/runtime-types.ts +0 -1
  155. package/src/pi-intercom/tool-registration.ts +4 -1
  156. package/src/pi-intercom/tool-results.ts +10 -9
  157. package/src/pi-intercom/tool-schemas.ts +12 -44
  158. package/src/pi-intercom/types.ts +115 -4
  159. package/src/pi-intercom/validation.ts +4 -0
  160. package/src/shared/bounded-json.ts +162 -0
  161. package/src/shared/compact-cards.ts +118 -0
  162. package/src/shared/journal-frames.ts +3 -19
  163. package/src/shared/json-projection.ts +267 -147
  164. package/src/shared/native-journal.ts +157 -75
  165. package/src/shared/post-exit-stdio-guard.ts +165 -7
  166. package/src/shared/tui-action.ts +39 -0
  167. package/tsconfig.client.json +8 -0
  168. package/tsconfig.json +1 -0
  169. package/src/shared/stream-json.d.ts +0 -10
@@ -1,25 +1,42 @@
1
1
  import * as path from "node:path";
2
- import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
2
+ import * as fs from "node:fs";
3
+ import {
4
+ SettingsManager,
5
+ type ExtensionAPI,
6
+ type ExtensionContext,
7
+ } from "@earendil-works/pi-coding-agent";
8
+ import { Container, TruncatedText } from "@earendil-works/pi-tui";
9
+ import { actionText } from "../shared/tui-action.ts";
3
10
  import { getPiAgentDir } from "../pi-intercom/agent-dir.ts";
4
11
  import { isChild, errorText } from "./host.ts";
5
12
  import { launchAt, list, ownerDirectory, runDirectory, read } from "./storage.ts";
6
13
  import { successorSchema, type Launch } from "./contracts.ts";
7
14
  import { readCanonicalResult } from "./usage.ts";
8
- import { actor, alive } from "./process.ts";
9
- import { JournalTail, plainText } from "./journal-tail.ts";
15
+ import { actor, observeAlive } from "./process.ts";
16
+ import { plainText } from "./journal-tail.ts";
17
+ import { ObservationClient, type Watch } from "./observe-client.ts";
10
18
  import { adoptedJournal } from "./saved.ts";
11
- import type { ObserverRow, Watch } from "./observe-screen.ts";
19
+ import type { ObserverRow } from "./observe-chrome.ts";
12
20
 
13
21
  const STATUS_KEY = "agent-observer";
22
+ type Roots = Map<string, ReturnType<typeof actor>>;
23
+ type Commit = (live: ReadonlyMap<string, boolean>) => void;
14
24
 
15
25
  class Observer {
16
26
  private ctx?: ExtensionContext;
17
27
  private owner = "";
18
28
  private base = "";
19
29
  private timer?: NodeJS.Timeout;
30
+ private scanning?: Promise<void>;
31
+ private rescan = false;
20
32
  private close?: () => void;
21
33
  private refresh?: () => void;
22
34
  private readonly rows = new Map<string, ObserverRow>();
35
+ private readonly peers = new Map<string, { token: string; client: ObservationClient }>();
36
+ private watcher?: fs.FSWatcher;
37
+ private widgetVisible = false;
38
+ private widgetSignature = "";
39
+ private widgetRender?: () => void;
23
40
  private readonly launches = new Map<string, Launch>();
24
41
  private readonly finished = new Set<string>();
25
42
  private pending: string[] = [];
@@ -31,10 +48,22 @@ class Observer {
31
48
  }
32
49
  private dispose(): void {
33
50
  clearInterval(this.timer);
51
+ this.timer = undefined;
52
+ this.scanning = undefined;
53
+ this.rescan = false;
54
+ this.watcher?.close();
55
+ this.watcher = undefined;
56
+ for (const peer of this.peers.values()) {
57
+ peer.client.close();
58
+ }
59
+ this.peers.clear();
60
+ this.widgetVisible = false;
61
+ this.widgetSignature = "";
62
+ this.widgetRender = undefined;
34
63
  this.close?.();
35
64
  this.close = undefined;
36
65
  this.refresh = undefined;
37
- this.ctx?.ui.setStatus(STATUS_KEY, undefined);
66
+ this.ctx?.ui.setWidget(STATUS_KEY, undefined);
38
67
  this.ctx = undefined;
39
68
  this.rows.clear();
40
69
  this.launches.clear();
@@ -53,58 +82,76 @@ class Observer {
53
82
  this.ctx = ctx;
54
83
  this.owner = ctx.sessionManager.getSessionId();
55
84
  this.base = ownerDirectory(path.resolve(getPiAgentDir()), this.owner);
85
+ fs.mkdirSync(this.base, { recursive: true, mode: 0o700 });
86
+ try {
87
+ const watcher = fs.watch(this.base, { recursive: true }, (_event, file) => {
88
+ if (
89
+ this.watcher === watcher &&
90
+ this.timer === undefined &&
91
+ (file === null || /(?:launch|result|successor|process)\.json$/.test(file))
92
+ ) {
93
+ this.scan();
94
+ }
95
+ });
96
+ this.watcher = watcher;
97
+ watcher.on("error", () => {
98
+ if (this.watcher !== watcher) {
99
+ return;
100
+ }
101
+ watcher.close();
102
+ this.watcher = undefined;
103
+ this.scan();
104
+ });
105
+ } catch {
106
+ /* Sparse native execution events still wake inventory without an idle poll. */
107
+ }
56
108
  this.scan();
57
- this.timer = setInterval(() => this.scan(), 1000);
58
- this.timer.unref();
59
109
  }
60
110
  private launch(id: string): Launch {
61
111
  const launch = this.launches.get(id) ?? launchAt(this.base, id, this.owner);
62
112
  this.launches.set(id, launch);
63
113
  return launch;
64
114
  }
65
- private live(launch: Launch, roots: Map<string, boolean>): boolean {
66
- const cached = roots.get(launch.rootId);
67
- if (cached !== undefined) {
68
- return cached;
69
- }
70
- const identity = actor(this.base, launch);
71
- const live = identity !== undefined && alive(identity);
72
- roots.set(launch.rootId, live);
73
- return live;
74
- }
75
- private load(id: string, roots: Map<string, boolean>): void {
115
+ /** Reads durable metadata now; unfinished rows commit after their root's batched liveness. */
116
+ private load(id: string, roots: Roots): Commit | undefined {
76
117
  if (this.finished.has(id)) {
77
- return;
118
+ return undefined;
78
119
  }
79
120
  const launch = this.launch(id);
80
121
  const result = readCanonicalResult(this.base, launch);
81
122
  const finished = result !== undefined || this.hasSuccessor(id);
82
- const live = !finished && this.live(launch, roots);
83
123
  if (finished) {
84
124
  this.finished.add(id);
125
+ } else if (!roots.has(launch.rootId)) {
126
+ roots.set(launch.rootId, actor(this.base, launch));
85
127
  }
86
- this.rows.set(id, {
87
- id,
88
- name: plainText(launch.name ?? id, 120),
89
- status: result?.status ?? (live ? "running" : "interrupted"),
90
- live,
91
- createdAt: launch.createdAt,
92
- });
128
+ return (live) => {
129
+ const running = !finished && live.get(launch.rootId) === true;
130
+ this.rows.set(id, {
131
+ id,
132
+ name: plainText(launch.name ?? id, 120),
133
+ status: result?.status ?? (running ? "running" : "interrupted"),
134
+ live: running,
135
+ createdAt: launch.createdAt,
136
+ activity: this.peers.get(id)?.client.activity,
137
+ });
138
+ };
93
139
  }
94
140
  private hasSuccessor(id: string): boolean {
95
141
  return (
96
142
  read(path.join(runDirectory(this.base, id), "successor.json"), successorSchema) !== undefined
97
143
  );
98
144
  }
99
- private loadVisible(id: string, roots: Map<string, boolean>): void {
145
+ private loadVisible(id: string, roots: Roots): Commit | undefined {
100
146
  try {
101
- this.load(id, roots);
147
+ return this.load(id, roots);
102
148
  } catch (error) {
103
149
  this.rows.set(id, { id, name: id, status: "unavailable", live: false, createdAt: 0 });
104
150
  this.error = plainText(errorText(error));
151
+ return undefined;
105
152
  }
106
153
  }
107
- private scan(): void {
154
+ private loadBatch(roots: Roots): Commit[] {
108
155
  try {
109
156
  const ids = list(this.base);
110
157
  const present = new Set(ids);
@@ -123,13 +170,176 @@ class Observer {
123
170
  this.pending = ids.filter((id) => !this.finished.has(id));
124
171
  }
125
172
  this.error = "";
126
- const roots = new Map<string, boolean>();
127
- this.pending.splice(0, 50).forEach((id) => this.loadVisible(id, roots));
173
+ return this.pending.splice(0, 50).flatMap((id) => this.loadVisible(id, roots) ?? []);
128
174
  } catch (error) {
129
175
  this.error = plainText(errorText(error));
176
+ return [];
177
+ }
178
+ }
179
+ /** One scan at a time; triggers during a scan coalesce into one follow-up. */
180
+ private scan(): void {
181
+ if (this.scanning !== undefined) {
182
+ this.rescan = true;
183
+ return;
184
+ }
185
+ const scanning = this.update()
186
+ .catch((error: unknown) => {
187
+ this.error = plainText(errorText(error));
188
+ })
189
+ .finally(() => {
190
+ if (this.scanning !== scanning) {
191
+ return;
192
+ }
193
+ this.scanning = undefined;
194
+ if (this.rescan) {
195
+ this.rescan = false;
196
+ this.scan();
197
+ }
198
+ });
199
+ this.scanning = scanning;
200
+ }
201
+ private async update(): Promise<void> {
202
+ const ctx = this.ctx;
203
+ const roots: Roots = new Map();
204
+ const commits = this.loadBatch(roots);
205
+ const claims = [...roots].flatMap(([root, identity]) => (identity ? [{ root, identity }] : []));
206
+ const verdicts = await observeAlive(claims.map(({ identity }) => identity));
207
+ if (this.ctx !== ctx) {
208
+ return;
130
209
  }
131
- this.ctx?.ui.setStatus(STATUS_KEY, this.status());
210
+ const live = new Map(claims.map(({ root }, index) => [root, verdicts[index]]));
211
+ for (const commit of commits) {
212
+ commit(live);
213
+ }
214
+ this.activity();
215
+ this.widget();
132
216
  this.refresh?.();
217
+ const periodic = this.pending.length > 0 || [...this.rows.values()].some((row) => row.live);
218
+ if (periodic && !this.timer) {
219
+ this.timer = setInterval(() => this.scan(), 1000);
220
+ this.timer.unref();
221
+ } else if (!periodic) {
222
+ clearInterval(this.timer);
223
+ this.timer = undefined;
224
+ }
225
+ }
226
+ private activity(): void {
227
+ const visible = this.strip()
228
+ .filter((entry) => entry.live)
229
+ .slice(0, 4);
230
+ const ids = new Set(visible.map((entry) => entry.id));
231
+ for (const [id, peer] of this.peers) {
232
+ if (!ids.has(id) || peer.client.closed) {
233
+ peer.client.close();
234
+ this.peers.delete(id);
235
+ }
236
+ }
237
+ for (const row of visible) {
238
+ if (this.peers.has(row.id)) {
239
+ continue;
240
+ }
241
+ try {
242
+ const launch = this.launch(row.id);
243
+ const identity = actor(this.base, launch);
244
+ if (!identity) {
245
+ continue;
246
+ }
247
+ const client = new ObservationClient(
248
+ { token: identity.token, rootId: launch.rootId, sessionId: launch.sessionId },
249
+ "activity",
250
+ () => {
251
+ if (this.peers.get(row.id)?.client !== client) {
252
+ return;
253
+ }
254
+ const current = this.rows.get(row.id);
255
+ if (current) {
256
+ current.activity = client.connected ? client.activity : "Reconnecting";
257
+ }
258
+ this.widget();
259
+ },
260
+ );
261
+ this.peers.set(row.id, { token: identity.token, client });
262
+ } catch {
263
+ // Older or not-yet-ready actors still have a known run status, but no live phase.
264
+ }
265
+ }
266
+ }
267
+ private widget(): void {
268
+ const ctx = this.ctx;
269
+ if (!ctx) {
270
+ return;
271
+ }
272
+ const rows = this.strip();
273
+ if (rows.length === 0 && this.rows.size === this.total && this.error.length === 0) {
274
+ if (this.widgetVisible) {
275
+ ctx.ui.setWidget(STATUS_KEY, undefined);
276
+ }
277
+ this.widgetVisible = false;
278
+ this.widgetSignature = "";
279
+ return;
280
+ }
281
+ const signature = JSON.stringify([this.status(), rows.slice(0, 4), rows.length]);
282
+ if (signature === this.widgetSignature) {
283
+ return;
284
+ }
285
+ this.widgetSignature = signature;
286
+ if (this.widgetVisible) {
287
+ this.widgetRender?.();
288
+ return;
289
+ }
290
+ this.widgetVisible = true;
291
+ ctx.ui.setWidget(
292
+ STATUS_KEY,
293
+ (tui, theme) => {
294
+ this.widgetRender = () => {
295
+ tui.requestRender();
296
+ };
297
+ const content = new Container();
298
+ const open = (id: string) => {
299
+ if (ctx !== this.ctx) {
300
+ return;
301
+ }
302
+ void this.show(id, ctx).catch((error: unknown) => {
303
+ ctx.ui.notify(errorText(error), "error");
304
+ });
305
+ };
306
+ return {
307
+ render: (width) => {
308
+ const strip = this.strip();
309
+ const visible = strip.slice(0, 4);
310
+ content.clear();
311
+ content.addChild(actionText(theme.fg("accent", this.status()), () => open("")));
312
+ for (const row of visible) {
313
+ const glyph = row.live ? "●" : "○";
314
+ const color = row.live ? "accent" : "warning";
315
+ content.addChild(
316
+ actionText(
317
+ `${theme.fg(color, glyph)} ${theme.bold(row.name)} · ${row.activity ?? row.status}`,
318
+ () => open(row.id),
319
+ ),
320
+ );
321
+ }
322
+ if (strip.length > visible.length) {
323
+ content.addChild(
324
+ actionText(
325
+ theme.fg("dim", `+${strip.length - visible.length} more · /agents`),
326
+ () => open(""),
327
+ ),
328
+ );
329
+ }
330
+ if (this.error.length > 0) {
331
+ content.addChild(new TruncatedText(theme.fg("warning", this.error), 0, 0));
332
+ }
333
+ return content.render(width);
334
+ },
335
+ handleMouse: (event) => content.handleMouse(event),
336
+ invalidate: () => {
337
+ // Render reads current rows, expansion and theme on every frame.
338
+ },
339
+ };
340
+ },
341
+ { placement: "aboveEditor" },
342
+ );
133
343
  }
134
344
  private status(): string {
135
345
  const active = [...this.rows.values()].filter((row) => row.live).length;
@@ -138,14 +348,20 @@ class Observer {
138
348
  return `Agents: ${active}${checking ? "+" : ""} active${checking ? " (checking…)" : ""} · ${this.total} runs${error} · /agents`;
139
349
  }
140
350
  private inventory(): ObserverRow[] {
141
- return [...this.rows.values()].sort((a, b) => {
142
- const order = Number(b.live) - Number(a.live);
143
- if (order !== 0) {
144
- return order;
145
- }
146
- const created = b.createdAt - a.createdAt;
147
- return created !== 0 ? created : a.id.localeCompare(b.id);
148
- });
351
+ return [...this.rows.values()]
352
+ .filter((row) => row.status !== "completed")
353
+ .sort((a, b) => {
354
+ const order = Number(b.live) - Number(a.live);
355
+ if (order !== 0) {
356
+ return order;
357
+ }
358
+ const created = b.createdAt - a.createdAt;
359
+ return created !== 0 ? created : a.id.localeCompare(b.id);
360
+ });
361
+ }
362
+ /** The always-visible strip shows active work and unreadable runs; /agents keeps history. */
363
+ private strip(): ObserverRow[] {
364
+ return this.inventory().filter((row) => row.live || row.status === "unavailable");
149
365
  }
150
366
  private watch(id: string): Watch {
151
367
  const matches = list(this.base).filter((candidate) => candidate.startsWith(id));
@@ -176,11 +392,14 @@ class Observer {
176
392
  ) {
177
393
  throw new Error("Child journal path is outside its saved conversation.");
178
394
  }
395
+ const identity = actor(this.base, launch);
179
396
  return {
180
- tail: new JournalTail(launch.sessionFile, launch.sessionId),
181
- file: launch.sessionFile,
182
- id: launch.id,
183
- inherited: launch.context === "fork",
397
+ launch,
398
+ identity: identity && {
399
+ token: identity.token,
400
+ rootId: launch.rootId,
401
+ sessionId: launch.sessionId,
402
+ },
184
403
  };
185
404
  }
186
405
  private async show(args: string, ctx: ExtensionContext): Promise<void> {
@@ -194,38 +413,52 @@ class Observer {
194
413
  }
195
414
  this.close?.();
196
415
  this.scan();
197
- await ctx.ui.custom<boolean>((tui, theme, keys, done) => {
198
- const screen = new ObserverScreen({
199
- data: {
200
- inventory: () => this.inventory(),
201
- row: (id) => this.rows.get(id),
202
- status: () => this.status(),
203
- error: () => this.error,
204
- watch: (id) => this.watch(id),
205
- },
206
- tui,
207
- theme,
208
- keys,
209
- done: () => done(true),
210
- selected: args.trim(),
211
- });
212
- this.close = () => done(true);
213
- this.refresh = () => screen.update();
214
- return {
215
- render: (width) => screen.render(width),
216
- handleInput: (data) => screen.handleInput(data),
217
- invalidate: () => screen.invalidate(),
218
- dispose: () => {
219
- screen.dispose();
416
+ await ctx.ui.custom<boolean>(
417
+ (tui, theme, keys, done) => {
418
+ const screen = new ObserverScreen({
419
+ data: {
420
+ inventory: () => this.inventory(),
421
+ checking: () => this.rows.size < this.total,
422
+ row: (id) => this.rows.get(id),
423
+ status: () => this.status(),
424
+ error: () => this.error,
425
+ watch: (id) => this.watch(id),
426
+ },
427
+ tui,
428
+ theme,
429
+ keys,
430
+ done: () => done(true),
431
+ selected: args.trim(),
432
+ hideThinking: SettingsManager.create(ctx.cwd, getPiAgentDir()).getHideThinkingBlock(),
433
+ });
434
+ this.close = () => done(true);
435
+ this.refresh = () => screen.update();
436
+ const dispose = screen.dispose.bind(screen);
437
+ screen.dispose = () => {
438
+ dispose();
220
439
  this.close = undefined;
221
440
  this.refresh = undefined;
222
- },
223
- };
224
- });
441
+ };
442
+ return screen;
443
+ },
444
+ {
445
+ overlay: true,
446
+ overlayOptions: { width: "100%", maxHeight: "100%", anchor: "top-left", margin: 0 },
447
+ },
448
+ );
225
449
  }
226
450
  register(): void {
227
451
  this.pi.on("session_start", (_event, ctx) => this.attach(ctx));
228
452
  this.pi.on("session_shutdown", () => this.dispose());
453
+ this.pi.on("tool_execution_end", (event) => {
454
+ if (
455
+ this.ctx &&
456
+ this.watcher === undefined &&
457
+ (event.toolName === "delegate" || event.toolName === "agent_runs")
458
+ ) {
459
+ this.scan();
460
+ }
461
+ });
229
462
  this.pi.registerCommand("agents", {
230
463
  description:
231
464
  "List this parent's children; /agents <run-id or unique 8+ prefix> watches native activity read-only",