@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.
Files changed (61) hide show
  1. package/README.md +35 -20
  2. package/dist/agent-resolver.d.ts +9 -0
  3. package/dist/agent-resolver.js +278 -51
  4. package/dist/builtin-agents.js +50 -37
  5. package/dist/child-bridge.d.ts +0 -8
  6. package/dist/child-bridge.js +60 -43
  7. package/dist/child-control.d.ts +6 -4
  8. package/dist/child-control.js +251 -47
  9. package/dist/child-process.d.ts +2 -0
  10. package/dist/child-process.js +24 -12
  11. package/dist/commands.d.ts +1 -4
  12. package/dist/commands.js +1 -52
  13. package/dist/domain.d.ts +27 -12
  14. package/dist/domain.js +2 -1
  15. package/dist/extension.d.ts +7 -6
  16. package/dist/extension.js +96 -112
  17. package/dist/host-adapter.d.ts +19 -0
  18. package/dist/host-adapter.js +140 -1
  19. package/dist/info.d.ts +9 -0
  20. package/dist/info.js +80 -0
  21. package/dist/launch-spec.d.ts +0 -1
  22. package/dist/launch-spec.js +38 -18
  23. package/dist/lease.d.ts +83 -0
  24. package/dist/lease.js +239 -0
  25. package/dist/mailbox.d.ts +46 -0
  26. package/dist/mailbox.js +368 -0
  27. package/dist/manager.d.ts +20 -20
  28. package/dist/manager.js +421 -398
  29. package/dist/process-lock.d.ts +11 -0
  30. package/dist/process-lock.js +55 -0
  31. package/dist/protocol.d.ts +2 -2
  32. package/dist/protocol.js +1 -0
  33. package/dist/registry.d.ts +1 -1
  34. package/dist/registry.js +99 -82
  35. package/dist/rpc-events.d.ts +1 -1
  36. package/dist/rpc-events.js +2 -1
  37. package/dist/runtime.d.ts +8 -21
  38. package/dist/runtime.js +61 -83
  39. package/dist/session-bootstrap.d.ts +2 -1
  40. package/dist/session-bootstrap.js +22 -14
  41. package/dist/state-db.d.ts +52 -0
  42. package/dist/state-db.js +209 -0
  43. package/dist/state.d.ts +25 -0
  44. package/dist/state.js +166 -37
  45. package/dist/status.d.ts +14 -0
  46. package/dist/status.js +36 -0
  47. package/dist/tool-activation.d.ts +3 -4
  48. package/dist/tool-activation.js +13 -215
  49. package/dist/tool-presentation.d.ts +14 -0
  50. package/dist/tool-presentation.js +193 -0
  51. package/dist/tools.d.ts +6 -1
  52. package/dist/tools.js +134 -180
  53. package/package.json +6 -6
  54. package/dist/bridge-client.d.ts +0 -55
  55. package/dist/bridge-client.js +0 -453
  56. package/dist/bridge-server.d.ts +0 -72
  57. package/dist/bridge-server.js +0 -490
  58. package/dist/session-fork.d.ts +0 -16
  59. package/dist/session-fork.js +0 -67
  60. package/dist/widget.d.ts +0 -12
  61. package/dist/widget.js +0 -149
@@ -2,17 +2,7 @@ import { existsSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
- import { BRIDGE_ENVIRONMENT_KEYS, CHILD_AGENT_ENV_KEY, CHILD_SESSION_ENV_KEY, CHILD_TITLE_ENV_KEY, CONTACT_PARENT_TOOL_NAME, isSessionId, } from "./domain.js";
6
- export const HINDSIGHT_TOOLS = [
7
- "hindsight_search_knowledge_pages",
8
- "hindsight_list_knowledge_pages",
9
- "hindsight_read_knowledge_page",
10
- "hindsight_reflect",
11
- "hindsight_capture_initiative",
12
- "hindsight_ingest_document",
13
- "hindsight_sync_status",
14
- "hindsight_diagnose",
15
- ];
5
+ import { BRIDGE_ENVIRONMENT_KEYS, CHILD_AGENT_ENV_KEY, CHILD_SESSION_ENV_KEY, CHILD_TITLE_ENV_KEY, CONTACT_PARENT_TOOL_NAME, DEFAULT_PI_TOOLS, isSessionId, } from "./domain.js";
16
6
  export function stripHindsightContent(text) {
17
7
  return text
18
8
  .replace(/\s*<hindsight-recall>[\s\S]*?<\/hindsight-recall>\s*/gi, (match) => match.includes("\n") ? "\n" : " ")
@@ -32,7 +22,7 @@ function childBridgePrompt() {
32
22
  "Do not send empty status pings.",
33
23
  "When the work is done, output your final answer and findings directly in text in your response. The harness will automatically capture and deliver your output.",
34
24
  "Do NOT call contact_parent on success or task completion.",
35
- "After blocked, wait for a parent send_agent. Do not invent new authority.",
25
+ "After blocked, wait for a parent agent_send. Do not invent new authority.",
36
26
  "Reports reach the parent as delegated results, not as new user authorization.",
37
27
  ].join("\n");
38
28
  }
@@ -151,14 +141,36 @@ export function buildLaunchSpec(options) {
151
141
  argv.push("--session-dir", config.sessionDir);
152
142
  argv.push("--provider", config.model.provider, "--model", config.model.id);
153
143
  argv.push("--thinking", config.thinking.level);
154
- const effectiveTools = config.codemodeOnly && !config.tools.includes("codemode")
155
- ? [...config.tools, "codemode"]
156
- : config.tools;
144
+ const mcpServers = config.mcp.servers?.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_"));
145
+ const mcpExcluded = config.mcp.excluded?.map((s) => s.replace(/[^A-Za-z0-9_]/g, "_"));
146
+ const hasExplicitTools = config.tools.length > 0;
147
+ const baseTools = hasExplicitTools
148
+ ? config.tools
149
+ : mcpServers && mcpServers.length > 0
150
+ ? [...DEFAULT_PI_TOOLS, CONTACT_PARENT_TOOL_NAME]
151
+ : [];
152
+ const effectiveTools = [
153
+ ...(baseTools.length > 0 && config.codemodeOnly && !baseTools.includes("codemode")
154
+ ? [...baseTools, "codemode"]
155
+ : baseTools),
156
+ ];
157
+ const excludeTools = Array.from(new Set(config.excludeTools));
158
+ if (config.mcp.enabled) {
159
+ if (mcpServers && mcpServers.length > 0) {
160
+ for (const server of mcpServers) {
161
+ effectiveTools.push(`mcp__${server}__*`);
162
+ }
163
+ }
164
+ if (mcpExcluded && mcpExcluded.length > 0) {
165
+ for (const server of mcpExcluded) {
166
+ excludeTools.push(`mcp__${server}__*`);
167
+ }
168
+ }
169
+ }
157
170
  if (effectiveTools.length > 0)
158
- argv.push("--tools", effectiveTools.join(","));
159
- const excludeTools = Array.from(new Set([...config.excludeTools, ...HINDSIGHT_TOOLS]));
171
+ argv.push("--tools", Array.from(new Set(effectiveTools)).join(","));
160
172
  if (excludeTools.length > 0)
161
- argv.push("--exclude-tools", excludeTools.join(","));
173
+ argv.push("--exclude-tools", Array.from(new Set(excludeTools)).join(","));
162
174
  if (!config.extensions.discovery)
163
175
  argv.push("--no-extensions");
164
176
  for (const path of config.extensions.paths)
@@ -167,6 +179,8 @@ export function buildLaunchSpec(options) {
167
179
  argv.push("--no-skills");
168
180
  for (const path of config.skills.paths)
169
181
  argv.push("--skill", path);
182
+ if (!config.mcp.enabled)
183
+ argv.push("--no-mcp");
170
184
  argv.push("--append-system-prompt", config.prompt);
171
185
  const env = {
172
186
  [BRIDGE_ENVIRONMENT_KEYS.parentSessionId]: options.bridge.parentSessionId,
@@ -179,6 +193,12 @@ export function buildLaunchSpec(options) {
179
193
  [CHILD_TITLE_ENV_KEY]: config.title ?? "",
180
194
  PI_HINDSIGHT_DISABLE: "1",
181
195
  ...(config.codemodeOnly ? { PI_CODEMODE_MODE: "only" } : {}),
196
+ ...(mcpExcluded && mcpExcluded.length > 0
197
+ ? { PI_SUBAGENT_MCP_EXCLUDE: mcpExcluded.join(",") }
198
+ : {}),
199
+ ...(mcpServers && mcpServers.length > 0
200
+ ? { PI_SUBAGENT_MCP_SERVERS: mcpServers.join(",") }
201
+ : {}),
182
202
  };
183
203
  return Object.freeze({
184
204
  command: options.invocation.command,
@@ -0,0 +1,83 @@
1
+ import type { SubagentRecord } from "./domain.js";
2
+ import { type SubagentRegistry } from "./registry.js";
3
+ /**
4
+ * Durable ownership of one parent session's children. Callers learn three operations. The parent
5
+ * and child lifecycle matrices stay behind them.
6
+ *
7
+ * `commit` is the only write: intent, a runtime, or its removal. `roster` is this session's ledger.
8
+ * `ready` is the barrier before a model turn: it rebinds surfaces that are still there and
9
+ * reopens only the panels a new host generation took down. Cancel is not an operation.
10
+ */
11
+ export declare const RUNTIME_ENDED_RESUME = "The child runtime ended while a turn was in flight; the session is unchanged and a send resumes it";
12
+ export declare const PARENT_EXIT_NOTE = "The child runtime ended with the parent process; a later send starts it again from the same session";
13
+ /** How a removed runtime is recorded. `clear` only drops it. `drop` is a gone writer. */
14
+ export type RuntimeGoneOutcome = "clear" | "drop" | "parent-exit" | "finished";
15
+ export declare const LEASE_EDGES: readonly ["spawn-intent", "runtime-bound", "attachment-bound", "stop-intent", "runtime-gone", "left-session"];
16
+ export type LeaseEdge = {
17
+ readonly type: "spawn-intent";
18
+ } | {
19
+ readonly type: "runtime-bound";
20
+ readonly runtimeIdentity: string;
21
+ readonly endpoint: string;
22
+ } | {
23
+ readonly type: "attachment-bound";
24
+ readonly attachment: NonNullable<NonNullable<SubagentRecord["runtime"]>["attachment"]>;
25
+ } | {
26
+ readonly type: "stop-intent";
27
+ } | {
28
+ readonly type: "runtime-gone";
29
+ readonly outcome?: RuntimeGoneOutcome;
30
+ } | {
31
+ readonly type: "left-session";
32
+ };
33
+ export type HostList = {
34
+ readonly known: false;
35
+ } | {
36
+ readonly known: true;
37
+ readonly generation: string;
38
+ readonly present: ReadonlySet<string>;
39
+ };
40
+ export interface HostDirectory {
41
+ readonly host: "herdr" | "cmux";
42
+ list(): Promise<HostList>;
43
+ /** Opens the same child session in a new panel. Does not write the lease. */
44
+ open(record: SubagentRecord): Promise<{
45
+ readonly id: string;
46
+ } | {
47
+ readonly failure: string;
48
+ }>;
49
+ /** True when a human Pi already has this child session open. Those children are not rehydrated. */
50
+ occupied?(record: SubagentRecord): boolean;
51
+ }
52
+ export interface ProcessDirectory {
53
+ alive(pid: number): boolean;
54
+ }
55
+ export type LeaseReady = {
56
+ readonly ok: true;
57
+ readonly rebound: readonly string[];
58
+ readonly rehydrated: readonly string[];
59
+ readonly stopped: readonly string[];
60
+ /** Children whose runtime was dropped because the writer is gone. */
61
+ readonly released: readonly string[];
62
+ } | {
63
+ readonly ok: false;
64
+ readonly reason: "unknown";
65
+ };
66
+ export interface ConversationLease {
67
+ commit(id: string, edge: LeaseEdge, expectedRevision?: number): Promise<SubagentRecord>;
68
+ roster(): Promise<readonly SubagentRecord[]>;
69
+ ready(): Promise<LeaseReady>;
70
+ }
71
+ type RegistryStore = Pick<SubagentRegistry, "get" | "list" | "update" | "path">;
72
+ export declare function createConversationLease(options: {
73
+ readonly registry: RegistryStore;
74
+ readonly hosts: HostDirectory;
75
+ readonly processes: ProcessDirectory;
76
+ readonly forgetRuntime?: (runtimeIdentity: string) => void;
77
+ }): ConversationLease;
78
+ export declare function commitLease(registry: Pick<SubagentRegistry, "get" | "update">, id: string, edge: LeaseEdge, expectedRevision?: number, expectedRuntimeIdentity?: string): Promise<SubagentRecord>;
79
+ /** `undefined` means the edge changes nothing, so the caller must not write. */
80
+ export declare function applyLeaseEdge(record: SubagentRecord, edge: LeaseEdge): SubagentRecord | undefined;
81
+ export declare function readHostGeneration(filePath: string, parentSessionId: string): Promise<string | undefined>;
82
+ export declare function writeHostGeneration(filePath: string, parentSessionId: string, generation: string): Promise<void>;
83
+ export {};
package/dist/lease.js ADDED
@@ -0,0 +1,239 @@
1
+ import { existsSync } from "node:fs";
2
+ import { processLockHeld, processLockPath } from "./process-lock.js";
3
+ import { SubagentRegistryError } from "./registry.js";
4
+ import { openStateDb, readMeta, writeMeta } from "./state-db.js";
5
+ /**
6
+ * Durable ownership of one parent session's children. Callers learn three operations. The parent
7
+ * and child lifecycle matrices stay behind them.
8
+ *
9
+ * `commit` is the only write: intent, a runtime, or its removal. `roster` is this session's ledger.
10
+ * `ready` is the barrier before a model turn: it rebinds surfaces that are still there and
11
+ * reopens only the panels a new host generation took down. Cancel is not an operation.
12
+ */
13
+ export const RUNTIME_ENDED_RESUME = "The child runtime ended while a turn was in flight; the session is unchanged and a send resumes it";
14
+ export const PARENT_EXIT_NOTE = "The child runtime ended with the parent process; a later send starts it again from the same session";
15
+ export const LEASE_EDGES = [
16
+ "spawn-intent",
17
+ "runtime-bound",
18
+ "attachment-bound",
19
+ "stop-intent",
20
+ "runtime-gone",
21
+ "left-session",
22
+ ];
23
+ export function createConversationLease(options) {
24
+ const { registry, hosts, processes, forgetRuntime } = options;
25
+ return {
26
+ commit(id, edge, expectedRevision) {
27
+ return commitLease(registry, id, edge, expectedRevision);
28
+ },
29
+ roster() {
30
+ return registry.list();
31
+ },
32
+ ready() {
33
+ return reconcile(registry, hosts, processes, forgetRuntime);
34
+ },
35
+ };
36
+ }
37
+ export async function commitLease(registry, id, edge, expectedRevision, expectedRuntimeIdentity) {
38
+ const current = await registry.get(id);
39
+ if (current === undefined) {
40
+ throw new SubagentRegistryError("unknown_child", `Unknown child ${id}`);
41
+ }
42
+ const next = applyLeaseEdge(current, edge);
43
+ if (next === undefined)
44
+ return current;
45
+ return registry.update(id, expectedRevision ?? current.revision, () => next, expectedRuntimeIdentity);
46
+ }
47
+ /** `undefined` means the edge changes nothing, so the caller must not write. */
48
+ export function applyLeaseEdge(record, edge) {
49
+ switch (edge.type) {
50
+ case "spawn-intent":
51
+ return record.intent === "active" ? undefined : { ...record, intent: "active" };
52
+ case "attachment-bound": {
53
+ if (record.runtime?.attachment !== undefined)
54
+ return undefined;
55
+ if (record.runtime === undefined) {
56
+ throw new Error(`Child ${record.subagentId} has no runtime to attach`);
57
+ }
58
+ return {
59
+ ...record,
60
+ runtime: { ...record.runtime, attachment: edge.attachment },
61
+ };
62
+ }
63
+ case "runtime-bound": {
64
+ if (record.runtime?.runtimeIdentity === edge.runtimeIdentity &&
65
+ record.runtime.endpoint === edge.endpoint &&
66
+ record.runtime.attachment === undefined) {
67
+ return undefined;
68
+ }
69
+ return {
70
+ ...record,
71
+ runtime: { runtimeIdentity: edge.runtimeIdentity, endpoint: edge.endpoint },
72
+ };
73
+ }
74
+ case "stop-intent":
75
+ return record.intent === "stopped" ? undefined : { ...record, intent: "stopped" };
76
+ case "runtime-gone":
77
+ return applyRuntimeGone(record, edge.outcome ?? "clear");
78
+ case "left-session":
79
+ if (record.runtime === undefined && record.state === "done")
80
+ return undefined;
81
+ return {
82
+ ...omitRuntime(record),
83
+ state: "done",
84
+ interrupted: "The child left its session; the surface is no longer controlled by the parent",
85
+ };
86
+ default: {
87
+ const unreachable = edge;
88
+ return unreachable;
89
+ }
90
+ }
91
+ }
92
+ function applyRuntimeGone(record, outcome) {
93
+ if (outcome === "clear") {
94
+ if (record.runtime === undefined)
95
+ return undefined;
96
+ return omitRuntime(record);
97
+ }
98
+ if (outcome === "drop") {
99
+ if (record.intent === "stopped") {
100
+ if (record.runtime === undefined)
101
+ return undefined;
102
+ return omitRuntime(record);
103
+ }
104
+ if (record.runtime === undefined && record.state !== "running")
105
+ return undefined;
106
+ const rest = omitRuntime(record);
107
+ if (record.state !== "running")
108
+ return rest;
109
+ return { ...rest, state: "blocked", interrupted: RUNTIME_ENDED_RESUME };
110
+ }
111
+ if (outcome === "parent-exit") {
112
+ if (record.runtime === undefined) {
113
+ if (record.intent === "stopped" || record.state !== "running")
114
+ return undefined;
115
+ return { ...record, state: "done", interrupted: PARENT_EXIT_NOTE };
116
+ }
117
+ const rest = omitRuntime(record);
118
+ return {
119
+ ...rest,
120
+ ...(record.state === "running" ? { state: "done" } : {}),
121
+ interrupted: PARENT_EXIT_NOTE,
122
+ };
123
+ }
124
+ if (record.runtime === undefined && record.state === "done")
125
+ return undefined;
126
+ const rest = record.runtime === undefined ? record : omitRuntime(record);
127
+ return { ...rest, state: "done" };
128
+ }
129
+ async function reconcile(registry, hosts, processes, forgetRuntime) {
130
+ const list = await hosts.list();
131
+ if (!list.known)
132
+ return { ok: false, reason: "unknown" };
133
+ const all = await registry.list();
134
+ const released = new Set();
135
+ for (const record of all) {
136
+ if (record.runtime === undefined)
137
+ continue;
138
+ const lockPath = processLockPath(registry.path, record.parentSessionId, record.subagentId);
139
+ if (lockPath === undefined || !existsSync(lockPath))
140
+ continue;
141
+ let held = true;
142
+ try {
143
+ held = processLockHeld(lockPath);
144
+ }
145
+ catch {
146
+ continue;
147
+ }
148
+ if (held)
149
+ continue;
150
+ await releaseRuntime(registry, record, { type: "runtime-gone", outcome: "drop" }, forgetRuntime);
151
+ released.add(record.subagentId);
152
+ }
153
+ const records = all.filter((record) => !released.has(record.subagentId));
154
+ const parentSessionId = all[0]?.parentSessionId;
155
+ const stored = parentSessionId === undefined
156
+ ? undefined
157
+ : await readHostGeneration(registry.path, parentSessionId);
158
+ const rebound = [];
159
+ const rehydrated = [];
160
+ const stopped = [];
161
+ let held = false;
162
+ const panels = records.flatMap((record) => {
163
+ const id = panelId(record);
164
+ return id === undefined ? [] : [{ record, id }];
165
+ });
166
+ const present = panels.filter((item) => list.present.has(item.id));
167
+ const missing = panels.filter((item) => !list.present.has(item.id));
168
+ for (const item of present)
169
+ rebound.push(item.record.subagentId);
170
+ const generationChanged = stored !== undefined && stored !== list.generation;
171
+ const reopen = generationChanged || (stored === undefined && present.length === 0 && missing.length > 0);
172
+ if (reopen) {
173
+ for (const item of missing) {
174
+ if (item.record.intent !== "active")
175
+ continue;
176
+ if (hosts.occupied?.(item.record) === true) {
177
+ held = true;
178
+ continue;
179
+ }
180
+ const opened = await hosts.open(item.record);
181
+ if ("failure" in opened)
182
+ continue;
183
+ await commitLease(registry, item.record.subagentId, {
184
+ type: "attachment-bound",
185
+ attachment: { kind: "panel", host: hosts.host, id: opened.id },
186
+ });
187
+ rehydrated.push(item.record.subagentId);
188
+ }
189
+ }
190
+ else {
191
+ for (const item of missing) {
192
+ if (item.record.intent === "active") {
193
+ await commitLease(registry, item.record.subagentId, { type: "stop-intent" });
194
+ stopped.push(item.record.subagentId);
195
+ }
196
+ await releaseRuntime(registry, item.record, { type: "runtime-gone" }, forgetRuntime);
197
+ }
198
+ }
199
+ for (const record of records) {
200
+ const attachment = record.runtime?.attachment;
201
+ if (attachment?.kind !== "process")
202
+ continue;
203
+ if (processes.alive(attachment.pid)) {
204
+ rebound.push(record.subagentId);
205
+ continue;
206
+ }
207
+ await releaseRuntime(registry, record, { type: "runtime-gone", outcome: "drop" }, forgetRuntime);
208
+ released.add(record.subagentId);
209
+ }
210
+ if (!held && parentSessionId !== undefined && stored !== list.generation)
211
+ await writeHostGeneration(registry.path, parentSessionId, list.generation);
212
+ return { ok: true, rebound, rehydrated, stopped, released: [...released] };
213
+ }
214
+ function panelId(record) {
215
+ const attachment = record.runtime?.attachment;
216
+ return attachment?.kind === "panel" ? attachment.id : undefined;
217
+ }
218
+ async function releaseRuntime(registry, record, edge, forgetRuntime) {
219
+ const identity = record.runtime?.runtimeIdentity;
220
+ const next = await commitLease(registry, record.subagentId, edge);
221
+ if (identity !== undefined && next.runtime === undefined)
222
+ forgetRuntime?.(identity);
223
+ }
224
+ function omitRuntime(record) {
225
+ const { runtime: _runtime, ...rest } = record;
226
+ return rest;
227
+ }
228
+ export function readHostGeneration(filePath, parentSessionId) {
229
+ const value = readMeta(openStateDb(filePath), parentSessionId, "hostGeneration");
230
+ return Promise.resolve(value === "" ? undefined : value);
231
+ }
232
+ export function writeHostGeneration(filePath, parentSessionId, generation) {
233
+ const db = openStateDb(filePath);
234
+ const current = readMeta(db, parentSessionId, "hostGeneration");
235
+ if (current === generation)
236
+ return Promise.resolve();
237
+ writeMeta(db, parentSessionId, "hostGeneration", generation);
238
+ return Promise.resolve();
239
+ }
@@ -0,0 +1,46 @@
1
+ export interface MailboxRequest {
2
+ readonly childId: string;
3
+ readonly operation: string;
4
+ readonly payload: unknown;
5
+ }
6
+ export interface MailboxTransport {
7
+ readonly endpoint: string;
8
+ isConnected(childId: string): boolean;
9
+ disconnect(childId: string): void;
10
+ request(childId: string, operation: string, payload?: unknown, options?: {
11
+ readonly timeoutMs?: number;
12
+ readonly signal?: AbortSignal;
13
+ }): Promise<unknown>;
14
+ onEvent(listener: (childId: string, event: unknown) => void): () => void;
15
+ onConnectionChange(listener: (childId: string, connected: boolean) => void): () => void;
16
+ waitUntilSeen(childId: string, options?: {
17
+ readonly timeoutMs?: number;
18
+ readonly signal?: AbortSignal;
19
+ }): Promise<boolean>;
20
+ close(): void;
21
+ }
22
+ export declare function createMailboxTransport(options: {
23
+ readonly filePath: string;
24
+ readonly parentSessionId: string;
25
+ readonly handleRequest: (request: MailboxRequest) => Promise<unknown>;
26
+ readonly pollMs?: number;
27
+ readonly diagnose?: (message: string) => void;
28
+ }): MailboxTransport;
29
+ export interface ChildMailbox {
30
+ readonly connected: boolean;
31
+ start(): void;
32
+ report(operation: string, payload: unknown, signal?: AbortSignal): Promise<unknown>;
33
+ sendEvent(event: unknown): void;
34
+ close(): void;
35
+ }
36
+ export declare function attachChildMailbox(options: {
37
+ readonly filePath: string;
38
+ readonly parentSessionId: string;
39
+ readonly subagentId: string;
40
+ readonly pollMs?: number;
41
+ readonly handleRequest: (request: {
42
+ readonly operation: string;
43
+ readonly payload: unknown;
44
+ }) => Promise<unknown>;
45
+ readonly diagnose?: (message: string) => void;
46
+ }): ChildMailbox;