@hheei/pi-subagents 0.2.4 → 0.4.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 +35 -20
- package/dist/agent-resolver.d.ts +9 -0
- package/dist/agent-resolver.js +278 -51
- package/dist/builtin-agents.js +50 -37
- package/dist/child-bridge.d.ts +0 -8
- package/dist/child-bridge.js +60 -43
- package/dist/child-control.d.ts +6 -4
- package/dist/child-control.js +251 -47
- package/dist/child-process.d.ts +2 -0
- package/dist/child-process.js +24 -12
- package/dist/commands.d.ts +1 -4
- package/dist/commands.js +1 -52
- package/dist/domain.d.ts +27 -12
- package/dist/domain.js +2 -1
- package/dist/extension.d.ts +7 -6
- package/dist/extension.js +96 -112
- package/dist/host-adapter.d.ts +19 -0
- package/dist/host-adapter.js +140 -1
- package/dist/info.d.ts +9 -0
- package/dist/info.js +80 -0
- package/dist/launch-spec.d.ts +0 -1
- package/dist/launch-spec.js +38 -18
- package/dist/lease.d.ts +83 -0
- package/dist/lease.js +239 -0
- package/dist/mailbox.d.ts +46 -0
- package/dist/mailbox.js +368 -0
- package/dist/manager.d.ts +20 -20
- package/dist/manager.js +421 -398
- package/dist/process-lock.d.ts +11 -0
- package/dist/process-lock.js +55 -0
- package/dist/protocol.d.ts +2 -2
- package/dist/protocol.js +1 -0
- package/dist/registry.d.ts +1 -1
- package/dist/registry.js +99 -82
- package/dist/rpc-events.d.ts +1 -1
- package/dist/rpc-events.js +2 -1
- package/dist/runtime.d.ts +8 -21
- package/dist/runtime.js +61 -83
- package/dist/session-bootstrap.d.ts +2 -1
- package/dist/session-bootstrap.js +22 -14
- package/dist/state-db.d.ts +52 -0
- package/dist/state-db.js +209 -0
- package/dist/state.d.ts +25 -0
- package/dist/state.js +166 -37
- package/dist/status.d.ts +14 -0
- package/dist/status.js +36 -0
- package/dist/tool-activation.d.ts +3 -4
- package/dist/tool-activation.js +13 -215
- package/dist/tool-presentation.d.ts +14 -0
- package/dist/tool-presentation.js +193 -0
- package/dist/tools.d.ts +6 -1
- package/dist/tools.js +134 -180
- package/package.json +6 -6
- package/dist/bridge-client.d.ts +0 -55
- package/dist/bridge-client.js +0 -453
- package/dist/bridge-server.d.ts +0 -72
- package/dist/bridge-server.js +0 -490
- package/dist/session-fork.d.ts +0 -16
- package/dist/session-fork.js +0 -67
- package/dist/widget.d.ts +0 -12
- package/dist/widget.js +0 -149
package/dist/mailbox.js
ADDED
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { errorMessage } from "@hheei/pi-ext-core";
|
|
5
|
+
import { holdProcessLock, processLockHeld, processLockPath, } from "./process-lock.js";
|
|
6
|
+
import { BridgeError } from "./protocol.js";
|
|
7
|
+
import { claimCommand, claimParentCommands, deleteCommand, enqueueCommand, finishCommand, hasPendingCommand, openStateDb, readCommand, requeueAbandonedParentCommands, withWrite, } from "./state-db.js";
|
|
8
|
+
const DEFAULT_POLL_MS = 50;
|
|
9
|
+
const DEFAULT_REQUEST_TIMEOUT_MS = 30_000;
|
|
10
|
+
export function createMailboxTransport(options) {
|
|
11
|
+
const db = openStateDb(options.filePath);
|
|
12
|
+
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
13
|
+
const seen = new Set();
|
|
14
|
+
const eventListeners = new Set();
|
|
15
|
+
const connectionListeners = new Set();
|
|
16
|
+
let closed = false;
|
|
17
|
+
let polling = false;
|
|
18
|
+
const emitConnection = (childId, connected) => {
|
|
19
|
+
for (const listener of [...connectionListeners]) {
|
|
20
|
+
try {
|
|
21
|
+
listener(childId, connected);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
options.diagnose?.(`connection listener failed: ${errorMessage(error)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
};
|
|
28
|
+
const lockHeld = (childId) => {
|
|
29
|
+
const lockPath = processLockPath(options.filePath, options.parentSessionId, childId);
|
|
30
|
+
if (lockPath === undefined)
|
|
31
|
+
return false;
|
|
32
|
+
try {
|
|
33
|
+
return processLockHeld(lockPath);
|
|
34
|
+
}
|
|
35
|
+
catch (error) {
|
|
36
|
+
options.diagnose?.(`could not probe the process lock for ${childId}: ${errorMessage(error)}`);
|
|
37
|
+
return undefined;
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const noticeLocks = () => {
|
|
41
|
+
if (closed)
|
|
42
|
+
return;
|
|
43
|
+
const directory = join(dirname(options.filePath), "locks", options.parentSessionId);
|
|
44
|
+
let names = [];
|
|
45
|
+
try {
|
|
46
|
+
names = readdirSync(directory);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
const missing = typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
50
|
+
if (!missing) {
|
|
51
|
+
options.diagnose?.(`could not read process locks: ${errorMessage(error)}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
const heldNow = new Set();
|
|
56
|
+
for (const name of names) {
|
|
57
|
+
if (!name.endsWith(".lock"))
|
|
58
|
+
continue;
|
|
59
|
+
const childId = name.slice(0, -".lock".length);
|
|
60
|
+
const held = lockHeld(childId);
|
|
61
|
+
if (held === true || (held === undefined && seen.has(childId)))
|
|
62
|
+
heldNow.add(childId);
|
|
63
|
+
}
|
|
64
|
+
for (const childId of heldNow) {
|
|
65
|
+
if (seen.has(childId))
|
|
66
|
+
continue;
|
|
67
|
+
seen.add(childId);
|
|
68
|
+
emitConnection(childId, true);
|
|
69
|
+
}
|
|
70
|
+
for (const childId of [...seen]) {
|
|
71
|
+
if (heldNow.has(childId))
|
|
72
|
+
continue;
|
|
73
|
+
seen.delete(childId);
|
|
74
|
+
emitConnection(childId, false);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
const takeParentCommands = async () => {
|
|
78
|
+
if (closed || polling)
|
|
79
|
+
return;
|
|
80
|
+
polling = true;
|
|
81
|
+
try {
|
|
82
|
+
noticeLocks();
|
|
83
|
+
// Empty is the common tick. A read sees it; claiming would take a write lock and fsync.
|
|
84
|
+
if (!hasPendingCommand(db, options.parentSessionId, "to_parent"))
|
|
85
|
+
return;
|
|
86
|
+
const claimed = withWrite(db, () => claimParentCommands(db, options.parentSessionId));
|
|
87
|
+
const eventIds = [];
|
|
88
|
+
const dropEvents = () => {
|
|
89
|
+
if (eventIds.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
const ids = eventIds.splice(0);
|
|
92
|
+
withWrite(db, () => {
|
|
93
|
+
for (const id of ids)
|
|
94
|
+
deleteCommand(db, id);
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
for (const command of claimed) {
|
|
98
|
+
if (!seen.has(command.subagentId) && lockHeld(command.subagentId) === true) {
|
|
99
|
+
seen.add(command.subagentId);
|
|
100
|
+
emitConnection(command.subagentId, true);
|
|
101
|
+
}
|
|
102
|
+
if (command.operation === "event") {
|
|
103
|
+
let event = command.payload;
|
|
104
|
+
try {
|
|
105
|
+
event = JSON.parse(command.payload);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
event = command.payload;
|
|
109
|
+
}
|
|
110
|
+
for (const listener of [...eventListeners]) {
|
|
111
|
+
try {
|
|
112
|
+
listener(command.subagentId, event);
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
options.diagnose?.(`event listener failed: ${errorMessage(error)}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
eventIds.push(command.id);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
dropEvents();
|
|
122
|
+
try {
|
|
123
|
+
const payload = JSON.parse(command.payload);
|
|
124
|
+
const result = await options.handleRequest({
|
|
125
|
+
childId: command.subagentId,
|
|
126
|
+
operation: command.operation,
|
|
127
|
+
payload,
|
|
128
|
+
});
|
|
129
|
+
finishCommand(db, command.id, { status: "done", result: JSON.stringify(result ?? null) });
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
const bridge = error instanceof BridgeError ? error : undefined;
|
|
133
|
+
finishCommand(db, command.id, {
|
|
134
|
+
status: "failed",
|
|
135
|
+
errorCode: bridge?.code ?? "request_failed",
|
|
136
|
+
error: bridge?.message ?? errorMessage(error),
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
dropEvents();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
polling = false;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
withWrite(db, () => {
|
|
147
|
+
requeueAbandonedParentCommands(db, options.parentSessionId);
|
|
148
|
+
});
|
|
149
|
+
const timer = setInterval(() => {
|
|
150
|
+
void takeParentCommands();
|
|
151
|
+
}, pollMs);
|
|
152
|
+
timer.unref?.();
|
|
153
|
+
return {
|
|
154
|
+
endpoint: options.filePath,
|
|
155
|
+
isConnected(childId) {
|
|
156
|
+
return lockHeld(childId) !== false;
|
|
157
|
+
},
|
|
158
|
+
disconnect(childId) {
|
|
159
|
+
if (lockHeld(childId) !== false)
|
|
160
|
+
return;
|
|
161
|
+
if (seen.delete(childId))
|
|
162
|
+
emitConnection(childId, false);
|
|
163
|
+
},
|
|
164
|
+
async request(childId, operation, payload, requestOptions) {
|
|
165
|
+
const id = randomUUID();
|
|
166
|
+
enqueueCommand(db, {
|
|
167
|
+
id,
|
|
168
|
+
parentSessionId: options.parentSessionId,
|
|
169
|
+
subagentId: childId,
|
|
170
|
+
direction: "to_child",
|
|
171
|
+
operation,
|
|
172
|
+
payload: JSON.stringify(payload ?? null),
|
|
173
|
+
createdAt: Date.now(),
|
|
174
|
+
});
|
|
175
|
+
return waitForCommand(db, id, requestOptions?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS, pollMs, requestOptions?.signal);
|
|
176
|
+
},
|
|
177
|
+
onEvent(listener) {
|
|
178
|
+
eventListeners.add(listener);
|
|
179
|
+
return () => {
|
|
180
|
+
eventListeners.delete(listener);
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
onConnectionChange(listener) {
|
|
184
|
+
connectionListeners.add(listener);
|
|
185
|
+
return () => {
|
|
186
|
+
connectionListeners.delete(listener);
|
|
187
|
+
};
|
|
188
|
+
},
|
|
189
|
+
async waitUntilSeen(childId, waitOptions) {
|
|
190
|
+
const timeoutMs = waitOptions?.timeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
|
|
191
|
+
const started = Date.now();
|
|
192
|
+
while (Date.now() - started < timeoutMs) {
|
|
193
|
+
if (waitOptions?.signal?.aborted)
|
|
194
|
+
return false;
|
|
195
|
+
if (lockHeld(childId) === true) {
|
|
196
|
+
if (!seen.has(childId)) {
|
|
197
|
+
seen.add(childId);
|
|
198
|
+
emitConnection(childId, true);
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
await delay(pollMs);
|
|
203
|
+
}
|
|
204
|
+
return false;
|
|
205
|
+
},
|
|
206
|
+
close() {
|
|
207
|
+
closed = true;
|
|
208
|
+
clearInterval(timer);
|
|
209
|
+
},
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
export function attachChildMailbox(options) {
|
|
213
|
+
let db;
|
|
214
|
+
let processLock;
|
|
215
|
+
const pollMs = options.pollMs ?? DEFAULT_POLL_MS;
|
|
216
|
+
let closed = false;
|
|
217
|
+
let started = false;
|
|
218
|
+
let working = false;
|
|
219
|
+
let timer;
|
|
220
|
+
const database = () => {
|
|
221
|
+
if (db === undefined)
|
|
222
|
+
throw new BridgeError("not_connected", "the state database is not open");
|
|
223
|
+
return db;
|
|
224
|
+
};
|
|
225
|
+
const tick = async () => {
|
|
226
|
+
if (closed || working || db === undefined)
|
|
227
|
+
return;
|
|
228
|
+
working = true;
|
|
229
|
+
const current = db;
|
|
230
|
+
try {
|
|
231
|
+
if (!hasPendingCommand(current, options.parentSessionId, "to_child", options.subagentId)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
const command = withWrite(current, () => claimCommand(current, options.parentSessionId, options.subagentId, "to_child"));
|
|
235
|
+
if (command === undefined)
|
|
236
|
+
return;
|
|
237
|
+
await answer(current, command, options.handleRequest, options.diagnose);
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
working = false;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
get connected() {
|
|
245
|
+
return started && !closed;
|
|
246
|
+
},
|
|
247
|
+
start() {
|
|
248
|
+
if (started || closed)
|
|
249
|
+
return;
|
|
250
|
+
try {
|
|
251
|
+
db = openStateDb(options.filePath);
|
|
252
|
+
const lockPath = processLockPath(options.filePath, options.parentSessionId, options.subagentId);
|
|
253
|
+
if (lockPath === undefined)
|
|
254
|
+
throw new Error("invalid process lock id");
|
|
255
|
+
processLock = holdProcessLock(lockPath);
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
processLock?.release();
|
|
259
|
+
processLock = undefined;
|
|
260
|
+
db = undefined;
|
|
261
|
+
options.diagnose?.(`could not hold the child process lock: ${errorMessage(error)}`);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
started = true;
|
|
265
|
+
timer = setInterval(() => {
|
|
266
|
+
void tick();
|
|
267
|
+
}, pollMs);
|
|
268
|
+
timer.unref?.();
|
|
269
|
+
void tick();
|
|
270
|
+
},
|
|
271
|
+
report(operation, payload, signal) {
|
|
272
|
+
const current = database();
|
|
273
|
+
const id = randomUUID();
|
|
274
|
+
enqueueCommand(current, {
|
|
275
|
+
id,
|
|
276
|
+
parentSessionId: options.parentSessionId,
|
|
277
|
+
subagentId: options.subagentId,
|
|
278
|
+
direction: "to_parent",
|
|
279
|
+
operation,
|
|
280
|
+
payload: JSON.stringify(payload ?? null),
|
|
281
|
+
createdAt: Date.now(),
|
|
282
|
+
});
|
|
283
|
+
return waitForCommand(current, id, DEFAULT_REQUEST_TIMEOUT_MS, pollMs, signal);
|
|
284
|
+
},
|
|
285
|
+
sendEvent(event) {
|
|
286
|
+
if (db === undefined)
|
|
287
|
+
return;
|
|
288
|
+
const current = db;
|
|
289
|
+
enqueueCommand(current, {
|
|
290
|
+
id: randomUUID(),
|
|
291
|
+
parentSessionId: options.parentSessionId,
|
|
292
|
+
subagentId: options.subagentId,
|
|
293
|
+
direction: "to_parent",
|
|
294
|
+
operation: "event",
|
|
295
|
+
payload: JSON.stringify(event ?? null),
|
|
296
|
+
createdAt: Date.now(),
|
|
297
|
+
});
|
|
298
|
+
},
|
|
299
|
+
close() {
|
|
300
|
+
closed = true;
|
|
301
|
+
started = false;
|
|
302
|
+
if (timer !== undefined)
|
|
303
|
+
clearInterval(timer);
|
|
304
|
+
processLock?.release();
|
|
305
|
+
processLock = undefined;
|
|
306
|
+
},
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
async function answer(db, command, handleRequest, diagnose) {
|
|
310
|
+
try {
|
|
311
|
+
const payload = JSON.parse(command.payload);
|
|
312
|
+
const result = await handleRequest({ operation: command.operation, payload });
|
|
313
|
+
finishCommand(db, command.id, { status: "done", result: JSON.stringify(result ?? null) });
|
|
314
|
+
}
|
|
315
|
+
catch (error) {
|
|
316
|
+
const bridge = error instanceof BridgeError ? error : undefined;
|
|
317
|
+
diagnose?.(`child command ${command.operation} failed: ${errorMessage(error)}`);
|
|
318
|
+
finishCommand(db, command.id, {
|
|
319
|
+
status: "failed",
|
|
320
|
+
errorCode: bridge?.code ?? "request_failed",
|
|
321
|
+
error: bridge?.message ?? errorMessage(error),
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
function waitForCommand(db, id, timeoutMs, pollMs, signal) {
|
|
326
|
+
const started = Date.now();
|
|
327
|
+
return new Promise((resolve, reject) => {
|
|
328
|
+
const timer = setInterval(() => {
|
|
329
|
+
if (signal?.aborted) {
|
|
330
|
+
clearInterval(timer);
|
|
331
|
+
withWrite(db, () => {
|
|
332
|
+
deleteCommand(db, id);
|
|
333
|
+
});
|
|
334
|
+
reject(new BridgeError("aborted", "The mailbox request was cancelled"));
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
const command = readCommand(db, id);
|
|
338
|
+
if (command === undefined)
|
|
339
|
+
return;
|
|
340
|
+
if (command.status === "done" ||
|
|
341
|
+
command.status === "failed" ||
|
|
342
|
+
Date.now() - started >= timeoutMs) {
|
|
343
|
+
clearInterval(timer);
|
|
344
|
+
withWrite(db, () => {
|
|
345
|
+
deleteCommand(db, command.id);
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (command.status === "done") {
|
|
349
|
+
resolve(command.result === null ? null : JSON.parse(command.result));
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
if (command.status === "failed") {
|
|
353
|
+
reject(new BridgeError(command.errorCode ?? "request_failed", command.error ?? "The command failed"));
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
if (Date.now() - started >= timeoutMs) {
|
|
357
|
+
reject(new BridgeError("timeout", `No answer for ${id} after ${timeoutMs}ms`));
|
|
358
|
+
}
|
|
359
|
+
}, pollMs);
|
|
360
|
+
timer.unref?.();
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
function delay(ms) {
|
|
364
|
+
return new Promise((resolve) => {
|
|
365
|
+
const timer = setTimeout(resolve, ms);
|
|
366
|
+
timer.unref?.();
|
|
367
|
+
});
|
|
368
|
+
}
|
package/dist/manager.d.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { type
|
|
2
|
-
import type {
|
|
3
|
-
import type { EffectiveLaunchConfig, OperationError, Presentation, PublicSubagent, SendMode, SpawnSubagentInput, SubagentRecord, SubagentState } from "./domain.js";
|
|
1
|
+
import { type ChildRuntime } from "./child-process.js";
|
|
2
|
+
import type { EffectiveLaunchConfig, OperationError, Presentation, PublicSubagent, RuntimeAttachment, SendMode, SpawnSubagentInput, SubagentRecord, SubagentState } from "./domain.js";
|
|
4
3
|
import type { HostAttachment } from "./host-adapter.js";
|
|
4
|
+
import type { HostDirectory, LeaseReady } from "./lease.js";
|
|
5
5
|
import { type BridgeOperation } from "./protocol.js";
|
|
6
6
|
import type { SubagentRegistry } from "./registry.js";
|
|
7
7
|
import type { LaunchOutcome, RuntimeTokenStore } from "./runtime.js";
|
|
8
8
|
/**
|
|
9
9
|
* How long an idle child keeps its runtime. A background child holds a whole Pi process and its
|
|
10
|
-
* session lock, so an idle one is reclaimed after this window and a later `
|
|
10
|
+
* session lock, so an idle one is reclaimed after this window and a later `agent_send` starts it
|
|
11
11
|
* again from the same session.
|
|
12
12
|
*/
|
|
13
13
|
export declare const SUBAGENT_IDLE_TIMEOUT_MS = 15000;
|
|
@@ -44,7 +44,7 @@ export interface ChildBridgeTransport {
|
|
|
44
44
|
}
|
|
45
45
|
export interface ManagerDependencies {
|
|
46
46
|
readonly parentSessionId: string;
|
|
47
|
-
readonly registry: Pick<SubagentRegistry, "get" | "list" | "update">;
|
|
47
|
+
readonly registry: Pick<SubagentRegistry, "get" | "list" | "update" | "path">;
|
|
48
48
|
readonly resolve: (input: SpawnSubagentInput) => Promise<EffectiveLaunchConfig>;
|
|
49
49
|
readonly bootstrap: (input: {
|
|
50
50
|
readonly parentSessionId: string;
|
|
@@ -63,6 +63,13 @@ export interface ManagerDependencies {
|
|
|
63
63
|
readonly presentation?: PresentationDeps;
|
|
64
64
|
/** Waits for an already-running runtime to reconnect; false when it does not. */
|
|
65
65
|
readonly connect: (record: SubagentRecord) => Promise<boolean>;
|
|
66
|
+
/**
|
|
67
|
+
* Host listing used to restore this session's panels before the next turn. Absent in tests that
|
|
68
|
+
* still exercise the bridge-only recover path.
|
|
69
|
+
*/
|
|
70
|
+
readonly directory?: HostDirectory & {
|
|
71
|
+
rebind?(attachment: RuntimeAttachment): HostAttachment | undefined;
|
|
72
|
+
};
|
|
66
73
|
readonly deadlineMs?: number;
|
|
67
74
|
readonly settledCloseTimeoutMs?: number;
|
|
68
75
|
readonly idleTimeoutMs?: number;
|
|
@@ -82,6 +89,7 @@ export interface PresentationDeps {
|
|
|
82
89
|
}
|
|
83
90
|
export interface SpawnResult {
|
|
84
91
|
readonly child: PublicSubagent;
|
|
92
|
+
readonly path: string;
|
|
85
93
|
/** Set when the child did not run where it would have by default. */
|
|
86
94
|
readonly note?: string;
|
|
87
95
|
}
|
|
@@ -99,14 +107,8 @@ export interface RecoveryResult {
|
|
|
99
107
|
export declare function extractLastAssistantText(entries: readonly unknown[]): string | undefined;
|
|
100
108
|
export declare class SubagentManager {
|
|
101
109
|
#private;
|
|
102
|
-
bindTaskRegistry(registry: TaskRegistry): () => void;
|
|
103
110
|
constructor(deps: ManagerDependencies);
|
|
104
|
-
/**
|
|
105
|
-
* Observes the raw bridge events of every child. A Task producer needs the child's
|
|
106
|
-
* `task_result` and settle events without also owning the bridge connection.
|
|
107
|
-
*/
|
|
108
|
-
onChildEvent(listener: (childId: string, event: unknown) => void): () => void;
|
|
109
|
-
/** Presentation-only: widget and similar projections re-read list() after this. */
|
|
111
|
+
/** Presentation-only: status and similar projections re-read list() after this. */
|
|
110
112
|
onChange(listener: () => void): () => void;
|
|
111
113
|
dispose(): void;
|
|
112
114
|
/**
|
|
@@ -115,7 +117,7 @@ export declare class SubagentManager {
|
|
|
115
117
|
* not when extensions are reloaded.
|
|
116
118
|
*/
|
|
117
119
|
closeLocalConnections(): void;
|
|
118
|
-
/** Reads a child's session entries;
|
|
120
|
+
/** Reads a child's session entries; used by transcript inspection fallbacks. */
|
|
119
121
|
getEntries(id: string): Promise<readonly unknown[] | undefined>;
|
|
120
122
|
/**
|
|
121
123
|
* Adopts the children that are still running. Nothing is launched here: a child that survived a
|
|
@@ -127,19 +129,17 @@ export declare class SubagentManager {
|
|
|
127
129
|
send(id: string, rawMessage: string, mode?: SendMode, signal?: AbortSignal): Promise<OperationError | PublicSubagent>;
|
|
128
130
|
get(id: string): Promise<PublicSubagent | OperationError>;
|
|
129
131
|
list(): Promise<readonly PublicSubagent[]>;
|
|
130
|
-
/** Child startup and runs count until Pi settles, including post-agent_end continuation. */
|
|
131
|
-
get activeCount(): number;
|
|
132
132
|
stop(id: string): Promise<OperationError | PublicSubagent>;
|
|
133
133
|
contactParent(id: string, reason: string, message: string, knownRecord?: SubagentRecord): Promise<OperationError | {
|
|
134
134
|
readonly delivered: true;
|
|
135
135
|
}>;
|
|
136
|
-
/**
|
|
137
|
-
* Handles a request a child sent to its parent. The two child-initiated operations are the
|
|
138
|
-
* child's report channel and its Task result, and both are answered here so the child learns
|
|
139
|
-
* whether the parent accepted them.
|
|
140
|
-
*/
|
|
136
|
+
/** Answers a request a child sent to its parent, so the child learns whether the parent accepted it. */
|
|
141
137
|
handleChildRequest(childId: string, operation: string, payload: unknown): Promise<unknown>;
|
|
142
138
|
/** Applies a bridge event to this child's record and notifies the raw child-event listeners. */
|
|
143
139
|
handleEvent(childId: string, event: unknown): void;
|
|
144
140
|
handleConnectionChange(childId: string, connected: boolean): void;
|
|
141
|
+
holdAttachment(id: string, attachment: HostAttachment): void;
|
|
142
|
+
/** Starts restoration. `before_agent_start` waits on `ready` so the turn sees the same ledger. */
|
|
143
|
+
reconcile(): Promise<LeaseReady>;
|
|
144
|
+
ready(): Promise<LeaseReady>;
|
|
145
145
|
}
|