@fitchmultz/pi-subagents 0.50.0 → 0.52.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/CHANGELOG.md +63 -2
- package/README.md +36 -5
- package/dist/agent/child-runtime.js +80 -0
- package/dist/agent/contracts.js +15 -24
- package/dist/agent/host.js +3 -16
- package/dist/agent/index.js +212 -192
- package/dist/agent/journal-tail.js +115 -156
- package/dist/agent/launch.js +20 -8
- package/dist/agent/legacy.js +65 -23
- package/dist/agent/notifications.js +138 -47
- package/dist/agent/observation-state.js +246 -0
- package/dist/agent/observation-view.js +258 -0
- package/dist/agent/observation.js +350 -0
- package/dist/agent/observe-card.js +243 -0
- package/dist/agent/observe-chrome.js +85 -0
- package/dist/agent/observe-client.js +285 -0
- package/dist/agent/observe-layout.js +65 -0
- package/dist/agent/observe-reading.js +84 -0
- package/dist/agent/observe-screen.js +400 -96
- package/dist/agent/observe-transcript.js +382 -0
- package/dist/agent/observe.js +250 -43
- package/dist/agent/parking.js +253 -0
- package/dist/agent/process.js +228 -28
- package/dist/agent/receipts.js +92 -20
- package/dist/agent/runner.js +229 -126
- package/dist/agent/shell.js +2 -1
- package/dist/agent/storage.js +47 -12
- package/dist/agent/tool-schemas.js +20 -0
- package/dist/agent/typebox.js +10 -5
- package/dist/agent/usage.js +0 -115
- package/dist/agent/view.js +46 -16
- package/dist/agent/wake.js +99 -0
- package/dist/build-info.js +1 -1
- package/dist/intercom-client.d.ts +26 -0
- package/dist/intercom-client.js +45 -0
- package/dist/pi-intercom/agent-dir.d.ts +1 -0
- package/dist/pi-intercom/bridge-session.js +2 -1
- package/dist/pi-intercom/broker/broker.js +62 -43
- package/dist/pi-intercom/broker/client-last.d.ts +24 -0
- package/dist/pi-intercom/broker/client-last.js +42 -5
- package/dist/pi-intercom/broker/client-park.d.ts +9 -0
- package/dist/pi-intercom/broker/client-park.js +21 -0
- package/dist/pi-intercom/broker/client.d.ts +59 -0
- package/dist/pi-intercom/broker/client.js +55 -29
- package/dist/pi-intercom/broker/connection.d.ts +29 -0
- package/dist/pi-intercom/broker/framing.d.ts +17 -0
- package/dist/pi-intercom/broker/framing.js +10 -7
- package/dist/pi-intercom/broker/guard.js +23 -0
- package/dist/pi-intercom/broker/last.d.ts +30 -0
- package/dist/pi-intercom/broker/last.js +87 -40
- package/dist/pi-intercom/broker/parked.js +107 -0
- package/dist/pi-intercom/broker/paths.d.ts +13 -0
- package/dist/pi-intercom/broker/paths.js +35 -12
- package/dist/pi-intercom/broker/pid.js +5 -1
- package/dist/pi-intercom/broker/server-connection.js +20 -1
- package/dist/pi-intercom/broker/snapshot-reader.d.ts +9 -0
- package/dist/pi-intercom/broker/snapshots.d.ts +29 -0
- package/dist/pi-intercom/broker/targets.d.ts +9 -0
- package/dist/pi-intercom/connection.js +71 -20
- package/dist/pi-intercom/identity.js +6 -5
- package/dist/pi-intercom/inbound-delivery.js +16 -60
- package/dist/pi-intercom/inbound-journal.js +83 -6
- package/dist/pi-intercom/inbound-record.js +18 -1
- package/dist/pi-intercom/inbound.js +22 -37
- package/dist/pi-intercom/index.js +41 -0
- package/dist/pi-intercom/last-message.js +4 -16
- package/dist/pi-intercom/lifecycle.js +88 -3
- package/dist/pi-intercom/outbound.js +5 -15
- package/dist/pi-intercom/reply-tracker.js +7 -6
- package/dist/pi-intercom/reply-wait.js +2 -2
- package/dist/pi-intercom/session-targets.d.ts +24 -0
- package/dist/pi-intercom/tool-registration.js +4 -1
- package/dist/pi-intercom/tool-results.js +5 -7
- package/dist/pi-intercom/tool-schemas.js +12 -26
- package/dist/pi-intercom/types.d.ts +212 -0
- package/dist/pi-intercom/types.js +54 -3
- package/dist/pi-intercom/validation.d.ts +12 -0
- package/dist/pi-intercom/validation.js +3 -0
- package/dist/shared/bounded-json.js +151 -0
- package/dist/shared/compact-cards.js +66 -0
- package/dist/shared/journal-frames.js +3 -6
- package/dist/shared/json-projection.js +267 -129
- package/dist/shared/native-journal.js +123 -56
- package/dist/shared/parking.js +12 -0
- package/dist/shared/post-exit-stdio-guard.js +120 -7
- package/dist/shared/tui-action.js +29 -0
- package/dist/shared/unknown.d.ts +9 -0
- package/docs/code-quality.md +5 -3
- package/docs/development.md +42 -3
- package/docs/intercom-bridge.md +9 -4
- package/docs/intercom.md +228 -7
- package/docs/performance.md +118 -14
- package/docs/reference.md +168 -20
- package/docs/retired-history-maintenance.md +252 -0
- package/package.json +28 -15
- package/scripts/archive-retired-history.mjs +464 -0
- package/scripts/build.mjs +48 -28
- package/scripts/measure-size.mjs +1 -1
- package/scripts/native-package-smoke.mjs +153 -12
- package/scripts/package-smoke.mjs +80 -11
- package/scripts/prepare.mjs +2 -2
- package/scripts/real-pi-live.mjs +377 -0
- package/scripts/real-pi-processes.mjs +6 -1
- package/scripts/real-pi-smoke.mjs +6 -282
- package/scripts/run-tests.mjs +4 -2
- package/scripts/typescript.mjs +35 -0
- package/skills/pi-intercom/SKILL.md +2 -1
- package/skills/pi-subagents/SKILL.md +23 -5
- package/src/agent/child-runtime.ts +105 -0
- package/src/agent/contracts.ts +18 -34
- package/src/agent/host.ts +39 -20
- package/src/agent/index.ts +234 -207
- package/src/agent/journal-tail.ts +141 -153
- package/src/agent/launch.ts +33 -9
- package/src/agent/legacy.ts +85 -31
- package/src/agent/notifications.ts +192 -67
- package/src/agent/observation-state.ts +277 -0
- package/src/agent/observation-view.ts +267 -0
- package/src/agent/observation.ts +409 -0
- package/src/agent/observe-card.ts +304 -0
- package/src/agent/observe-chrome.ts +170 -0
- package/src/agent/observe-client.ts +318 -0
- package/src/agent/observe-layout.ts +79 -0
- package/src/agent/observe-reading.ts +97 -0
- package/src/agent/observe-screen.ts +443 -129
- package/src/agent/observe-transcript.ts +434 -0
- package/src/agent/observe.ts +304 -71
- package/src/agent/parking.ts +341 -0
- package/src/agent/process.ts +280 -37
- package/src/agent/receipts.ts +114 -24
- package/src/agent/runner.ts +227 -149
- package/src/agent/shell.ts +2 -1
- package/src/agent/storage.ts +44 -11
- package/src/agent/tool-schemas.ts +33 -0
- package/src/agent/typebox.ts +12 -7
- package/src/agent/usage.ts +0 -130
- package/src/agent/view.ts +70 -20
- package/src/agent/wake.ts +107 -0
- package/src/intercom-client.ts +108 -0
- package/src/pi-intercom/bridge-session.ts +2 -2
- package/src/pi-intercom/broker/broker.ts +64 -53
- package/src/pi-intercom/broker/client-last.ts +65 -12
- package/src/pi-intercom/broker/client-park.ts +23 -0
- package/src/pi-intercom/broker/client.ts +63 -45
- package/src/pi-intercom/broker/framing.ts +11 -8
- package/src/pi-intercom/broker/guard.ts +33 -0
- package/src/pi-intercom/broker/last.ts +131 -43
- package/src/pi-intercom/broker/parked.ts +130 -0
- package/src/pi-intercom/broker/paths.ts +39 -12
- package/src/pi-intercom/broker/pid.ts +6 -1
- package/src/pi-intercom/broker/server-connection.ts +27 -2
- package/src/pi-intercom/connection.ts +103 -39
- package/src/pi-intercom/identity.ts +8 -5
- package/src/pi-intercom/inbound-delivery.ts +20 -66
- package/src/pi-intercom/inbound-journal.ts +103 -7
- package/src/pi-intercom/inbound-record.ts +21 -4
- package/src/pi-intercom/inbound.ts +32 -49
- package/src/pi-intercom/index.ts +44 -0
- package/src/pi-intercom/last-message.ts +11 -20
- package/src/pi-intercom/lifecycle.ts +117 -14
- package/src/pi-intercom/outbound.ts +7 -16
- package/src/pi-intercom/reply-tracker.ts +8 -6
- package/src/pi-intercom/reply-wait.ts +3 -2
- package/src/pi-intercom/runtime-types.ts +0 -1
- package/src/pi-intercom/tool-registration.ts +4 -1
- package/src/pi-intercom/tool-results.ts +10 -9
- package/src/pi-intercom/tool-schemas.ts +12 -44
- package/src/pi-intercom/types.ts +115 -4
- package/src/pi-intercom/validation.ts +4 -0
- package/src/shared/bounded-json.ts +162 -0
- package/src/shared/compact-cards.ts +118 -0
- package/src/shared/journal-frames.ts +3 -19
- package/src/shared/json-projection.ts +267 -147
- package/src/shared/native-journal.ts +157 -75
- package/src/shared/parking.ts +22 -0
- package/src/shared/post-exit-stdio-guard.ts +165 -7
- package/src/shared/tui-action.ts +39 -0
- package/tsconfig.client.json +8 -0
- package/tsconfig.json +1 -0
- package/src/shared/stream-json.d.ts +0 -10
package/src/agent/observe.ts
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
1
|
import * as path from "node:path";
|
|
2
|
-
import
|
|
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,
|
|
9
|
-
import {
|
|
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
|
|
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.
|
|
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
|
-
|
|
66
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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:
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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()]
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
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
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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>(
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
dispose
|
|
219
|
-
|
|
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",
|