pi-subagents 0.13.1 → 0.13.3

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 CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.13.3] - 2026-04-13
6
+
7
+ ### Added
8
+ - Added `intercomBridge.instructionFile` so subagent intercom guidance can be overridden from a Markdown template with `{orchestratorTarget}` interpolation.
9
+
10
+ ### Fixed
11
+ - Intercom-enabled delegated runs now detach only after the child actually starts the `intercom` tool, preserving clean sync behavior until coordination is needed.
12
+ - Graceful intercom coordination no longer leaves detached child runs vulnerable to later parent abort listeners, and reply confirmation follow-ups avoid unnecessary orchestrator aborts.
13
+ - Child process spawn failures now preserve the original error message instead of collapsing to a generic failure.
14
+
15
+ ## [0.13.2] - 2026-04-13
16
+
17
+ ### Changed
18
+ - `intercomBridge` now defaults to `always` so intercom coordination instructions are injected for both `fresh` and `fork` delegated runs when `pi-intercom` is available.
19
+
5
20
  ## [0.13.1] - 2026-04-13
6
21
 
7
22
  ### Added
package/README.md CHANGED
@@ -14,12 +14,6 @@ https://github.com/user-attachments/assets/702554ec-faaf-4635-80aa-fb5d6e292fd1
14
14
  pi install npm:pi-subagents
15
15
  ```
16
16
 
17
- To remove:
18
-
19
- ```bash
20
- npx pi-subagents --remove
21
- ```
22
-
23
17
  If you use [pi-prompt-template-model](https://github.com/nicobailon/pi-prompt-template-model), you can wrap subagent delegation in a slash command:
24
18
 
25
19
  ```markdown
@@ -331,7 +325,7 @@ Chains can be created from the Agents Manager template picker ("Blank Chain"), o
331
325
  | Parallel | Yes | `{ tasks: [{agent, task}...] }` - via TUI toggle or converted to chain for async |
332
326
 
333
327
  Execution context defaults to `context: "fresh"`, which starts each child run from a clean session. Set `context: "fork"` to start each child from a real branched session created from the parent's current leaf.
334
- When `intercomBridge` is enabled (default: `fork-only`) and `pi-intercom` is installed/enabled, forked children get runtime instructions for contacting the orchestrator session via `intercom({ action: "ask"|"send", ... })`.
328
+ When `intercomBridge` is enabled (default: `always`) and `pi-intercom` is installed/enabled, delegated children get runtime instructions for contacting the orchestrator session via `intercom({ action: "ask"|"send", ... })`.
335
329
 
336
330
  > **Note:** Intercom bridging requires the [pi-intercom](https://github.com/nicobailon/pi-intercom) extension. Install it with `pi install npm:pi-intercom`.
337
331
 
@@ -787,19 +781,34 @@ Controls whether subagents receive runtime intercom coordination instructions (a
787
781
 
788
782
  ```json
789
783
  {
790
- "intercomBridge": "fork-only"
784
+ "intercomBridge": {
785
+ "mode": "always",
786
+ "instructionFile": "./intercom-bridge.md"
787
+ }
791
788
  }
792
789
  ```
793
790
 
794
- Values:
795
- - `"fork-only"` (default): inject intercom bridge only when `context: "fork"`
796
- - `"always"`: inject bridge in both `fresh` and `fork`
797
- - `"off"`: disable bridge entirely
791
+ Fields:
792
+ - `"mode"` (default: `"always"`): inject bridge in both `fresh` and `fork`, only in `fork`, or disable it entirely
793
+ - `"instructionFile"` (optional): path to a Markdown template that replaces the default injected subagent intercom instructions. Supports `{orchestratorTarget}` placeholder interpolation. Relative paths resolve from `~/.pi/agent/extensions/subagent/`.
794
+
795
+ Example `instructionFile`:
796
+
797
+ ```md
798
+ Intercom orchestration channel:
799
+
800
+ Use `intercom` only to coordinate with the orchestrator session `{orchestratorTarget}`.
801
+
802
+ - Need a decision or you're blocked: `intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })`
803
+ - Need to report progress or completion: `intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })`
804
+
805
+ If intercom is unavailable in this run, continue the task normally.
806
+ ```
798
807
 
799
808
  Bridge activation also requires all of the following:
800
809
  - [pi-intercom](https://github.com/nicobailon/pi-intercom) is installed (`pi install npm:pi-intercom`)
801
810
  - `~/.pi/agent/intercom/config.json` is not set to `"enabled": false`
802
- - the current session has a name to target as orchestrator
811
+ - the current session has a target name (existing `/name`, or auto-assigned `session-<id>` when unnamed)
803
812
  - if agent `extensions` is an explicit allowlist, it must include `pi-intercom`
804
813
 
805
814
  ### `worktreeSetupHook`
package/execution.ts CHANGED
@@ -19,6 +19,8 @@ import {
19
19
  type SingleResult,
20
20
  type Usage,
21
21
  DEFAULT_MAX_OUTPUT,
22
+ INTERCOM_DETACH_REQUEST_EVENT,
23
+ INTERCOM_DETACH_RESPONSE_EVENT,
22
24
  truncateOutput,
23
25
  getSubagentDepthEnv,
24
26
  } from "./types.ts";
@@ -123,7 +125,6 @@ async function runSingleAttempt(
123
125
  const startTime = Date.now();
124
126
  const spawnEnv = { ...process.env, ...sharedEnv, ...getSubagentDepthEnv(options.maxSubagentDepth) };
125
127
 
126
- let closeJsonlWriter: (() => Promise<void>) | undefined;
127
128
  const exitCode = await new Promise<number>((resolve) => {
128
129
  const spawnSpec = getPiSpawnCommand(args);
129
130
  const proc = spawn(spawnSpec.command, spawnSpec.args, {
@@ -132,9 +133,46 @@ async function runSingleAttempt(
132
133
  stdio: ["ignore", "pipe", "pipe"],
133
134
  });
134
135
  const jsonlWriter = createJsonlWriter(shared.jsonlPath, proc.stdout);
135
- closeJsonlWriter = () => jsonlWriter.close();
136
136
  let buf = "";
137
137
  let processClosed = false;
138
+ let settled = false;
139
+ let detached = false;
140
+ let intercomStarted = false;
141
+ let removeAbortListener: (() => void) | undefined;
142
+
143
+ const detachForIntercom = () => {
144
+ detached = true;
145
+ processClosed = true;
146
+ result.detached = true;
147
+ result.detachedReason = "intercom coordination";
148
+ progress.status = "detached";
149
+ progress.durationMs = Date.now() - startTime;
150
+ result.progressSummary = {
151
+ toolCount: progress.toolCount,
152
+ tokens: progress.tokens,
153
+ durationMs: progress.durationMs,
154
+ };
155
+ finish(-2);
156
+ };
157
+
158
+ const unsubscribeIntercomDetach = options.intercomEvents?.on(INTERCOM_DETACH_REQUEST_EVENT, (payload) => {
159
+ if (!options.allowIntercomDetach || detached || processClosed) return;
160
+ if (!payload || typeof payload !== "object") return;
161
+ const requestId = (payload as { requestId?: unknown }).requestId;
162
+ if (typeof requestId !== "string" || requestId.length === 0) return;
163
+ const accepted = intercomStarted;
164
+ options.intercomEvents?.emit(INTERCOM_DETACH_RESPONSE_EVENT, { requestId, accepted });
165
+ if (!accepted) return;
166
+ detachForIntercom();
167
+ });
168
+
169
+ const finish = (code: number) => {
170
+ if (settled) return;
171
+ settled = true;
172
+ unsubscribeIntercomDetach?.();
173
+ removeAbortListener?.();
174
+ resolve(code);
175
+ };
138
176
 
139
177
  const fireUpdate = () => {
140
178
  if (!options.onUpdate || processClosed) return;
@@ -154,6 +192,9 @@ async function runSingleAttempt(
154
192
  progress.durationMs = now - startTime;
155
193
 
156
194
  if (evt.type === "tool_execution_start") {
195
+ if (options.allowIntercomDetach && evt.toolName === "intercom") {
196
+ intercomStarted = true;
197
+ }
157
198
  progress.toolCount++;
158
199
  progress.currentTool = evt.toolName;
159
200
  progress.currentToolArgs = extractToolArgsPreview((evt.args || {}) as Record<string, unknown>);
@@ -215,35 +256,55 @@ async function runSingleAttempt(
215
256
  stderrBuf += d.toString();
216
257
  });
217
258
  proc.on("close", (code) => {
259
+ void jsonlWriter.close().catch(() => {
260
+ // JSONL artifact flush is best effort.
261
+ });
262
+ cleanupTempDir(tempDir);
263
+ if (detached) {
264
+ finish(-2);
265
+ return;
266
+ }
218
267
  processClosed = true;
219
268
  if (buf.trim()) processLine(buf);
220
269
  if (code !== 0 && stderrBuf.trim() && !result.error) {
221
270
  result.error = stderrBuf.trim();
222
271
  }
223
- resolve(code ?? 0);
272
+ finish(code ?? 0);
273
+ });
274
+ proc.on("error", (error) => {
275
+ void jsonlWriter.close().catch(() => {
276
+ // JSONL artifact flush is best effort.
277
+ });
278
+ cleanupTempDir(tempDir);
279
+ if (!result.error) {
280
+ result.error = error instanceof Error ? error.message : String(error);
281
+ }
282
+ finish(1);
224
283
  });
225
- proc.on("error", () => resolve(1));
226
284
 
227
285
  if (options.signal) {
228
286
  const kill = () => {
287
+ if (processClosed || detached) return;
288
+ if (options.allowIntercomDetach && intercomStarted && !detached) {
289
+ detachForIntercom();
290
+ return;
291
+ }
229
292
  proc.kill("SIGTERM");
230
293
  setTimeout(() => !proc.killed && proc.kill("SIGKILL"), 3000);
231
294
  };
232
295
  if (options.signal.aborted) kill();
233
- else options.signal.addEventListener("abort", kill, { once: true });
296
+ else {
297
+ options.signal.addEventListener("abort", kill, { once: true });
298
+ removeAbortListener = () => options.signal?.removeEventListener("abort", kill);
299
+ }
234
300
  }
235
301
  });
236
-
237
- if (closeJsonlWriter) {
238
- try {
239
- await closeJsonlWriter();
240
- } catch {
241
- // JSONL artifact flush is best effort.
242
- }
243
- }
244
-
245
- cleanupTempDir(tempDir);
246
302
  result.exitCode = exitCode;
303
+ if (result.detached) {
304
+ result.exitCode = 0;
305
+ result.finalOutput = "Detached for intercom coordination.";
306
+ return result;
307
+ }
247
308
 
248
309
  if (exitCode === 0 && !result.error) {
249
310
  const errInfo = detectSubagentError(result.messages);
package/index.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * Toggle: async parameter (default: false, configurable via config.json)
10
10
  *
11
11
  * Config file: ~/.pi/agent/extensions/subagent/config.json
12
- * { "asyncByDefault": true, "maxSubagentDepth": 1, "intercomBridge": "fork-only", "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
12
+ * { "asyncByDefault": true, "maxSubagentDepth": 1, "intercomBridge": { "mode": "always", "instructionFile": "./intercom-bridge.md" }, "worktreeSetupHook": "./scripts/setup-worktree.mjs" }
13
13
  */
14
14
 
15
15
  import * as fs from "node:fs";
@@ -2,32 +2,50 @@ import * as fs from "node:fs";
2
2
  import * as os from "node:os";
3
3
  import * as path from "node:path";
4
4
  import type { AgentConfig } from "./agents.ts";
5
- import type { ExtensionConfig } from "./types.ts";
5
+ import type { ExtensionConfig, IntercomBridgeConfig, IntercomBridgeMode } from "./types.ts";
6
6
 
7
7
  const DEFAULT_INTERCOM_EXTENSION_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "pi-intercom");
8
8
  const DEFAULT_INTERCOM_CONFIG_PATH = path.join(os.homedir(), ".pi", "agent", "intercom", "config.json");
9
+ const DEFAULT_SUBAGENT_CONFIG_DIR = path.join(os.homedir(), ".pi", "agent", "extensions", "subagent");
9
10
  const INTERCOM_BRIDGE_MARKER = "Intercom orchestration channel:";
10
-
11
- export type IntercomBridgeMode = NonNullable<ExtensionConfig["intercomBridge"]>;
11
+ const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `Use intercom only for coordination with the orchestrator session:
12
+ - Need a decision or blocked: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
13
+ - Completion/update: intercom({ action: "send", to: "{orchestratorTarget}", message: "DONE: <summary>" })
14
+ If intercom is unavailable in this run, continue the task normally.`;
12
15
 
13
16
  export interface IntercomBridgeState {
14
17
  active: boolean;
15
18
  mode: IntercomBridgeMode;
16
19
  orchestratorTarget?: string;
17
20
  extensionDir: string;
21
+ instruction: string;
18
22
  }
19
23
 
20
24
  interface ResolveIntercomBridgeInput {
21
- mode: unknown;
25
+ config: ExtensionConfig["intercomBridge"];
22
26
  context: "fresh" | "fork" | undefined;
23
27
  orchestratorTarget?: string;
24
28
  extensionDir?: string;
25
29
  configPath?: string;
30
+ settingsDir?: string;
26
31
  }
27
32
 
28
33
  export function resolveIntercomBridgeMode(value: unknown): IntercomBridgeMode {
29
34
  if (value === "off" || value === "always" || value === "fork-only") return value;
30
- return "fork-only";
35
+ return "always";
36
+ }
37
+
38
+ function resolveIntercomBridgeConfig(value: ExtensionConfig["intercomBridge"]): Required<IntercomBridgeConfig> {
39
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
40
+ return {
41
+ mode: "always",
42
+ instructionFile: "",
43
+ };
44
+ }
45
+ return {
46
+ mode: resolveIntercomBridgeMode(value.mode),
47
+ instructionFile: typeof value.instructionFile === "string" ? value.instructionFile : "",
48
+ };
31
49
  }
32
50
 
33
51
  function intercomEnabled(configPath: string): boolean {
@@ -43,51 +61,84 @@ function intercomEnabled(configPath: string): boolean {
43
61
 
44
62
  function extensionSandboxAllowsIntercom(extensions: string[] | undefined, extensionDir: string): boolean {
45
63
  if (extensions === undefined) return true;
46
- const lowerExtensionDir = extensionDir.toLowerCase();
47
- return extensions.some((entry) => {
48
- const normalized = entry.toLowerCase();
49
- if (normalized.includes(lowerExtensionDir)) return true;
50
- return /(^|[\\/])pi-intercom([\\/]|$)/i.test(normalized);
51
- });
64
+
65
+ const intercomDir = path.resolve(extensionDir).replaceAll("\\", "/").toLowerCase();
66
+ for (const entry of extensions) {
67
+ const normalized = entry.trim().replaceAll("\\", "/").toLowerCase();
68
+ if (normalized === "pi-intercom") return true;
69
+ if (normalized === intercomDir) return true;
70
+ if (normalized.startsWith(`${intercomDir}/`)) return true;
71
+ if (normalized.endsWith("/pi-intercom")) return true;
72
+ if (normalized.includes("/pi-intercom/")) return true;
73
+ }
74
+ return false;
52
75
  }
53
76
 
54
- function buildIntercomBridgeInstruction(orchestratorTarget: string): string {
55
- const escapedTarget = JSON.stringify(orchestratorTarget);
77
+ function expandTilde(filePath: string): string {
78
+ return filePath.startsWith("~/") ? path.join(os.homedir(), filePath.slice(2)) : filePath;
79
+ }
80
+
81
+ function resolveInstructionTemplate(instructionFile: string, settingsDir: string): string {
82
+ if (!instructionFile) return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
83
+ const expandedPath = expandTilde(instructionFile);
84
+ const resolvedPath = path.isAbsolute(expandedPath)
85
+ ? expandedPath
86
+ : path.resolve(settingsDir, expandedPath);
87
+ try {
88
+ return fs.readFileSync(resolvedPath, "utf-8");
89
+ } catch (error) {
90
+ console.warn(`Failed to read intercom bridge instructionFile at '${resolvedPath}'. Using default instructions.`, error);
91
+ return DEFAULT_INTERCOM_BRIDGE_TEMPLATE;
92
+ }
93
+ }
94
+
95
+ function buildIntercomBridgeInstruction(orchestratorTarget: string, template: string): string {
96
+ const instruction = template.replaceAll("{orchestratorTarget}", orchestratorTarget).trim();
97
+ if (instruction.startsWith(INTERCOM_BRIDGE_MARKER)) return instruction;
56
98
  return `${INTERCOM_BRIDGE_MARKER}
57
- Use intercom only for coordination with the orchestrator session:
58
- - Need a decision or blocked: intercom({ action: "ask", to: ${escapedTarget}, message: "<question>" })
59
- - Completion/update: intercom({ action: "send", to: ${escapedTarget}, message: "DONE: <summary>" })
60
- If intercom is unavailable in this run, continue the task normally.`;
99
+ ${instruction}`;
61
100
  }
62
101
 
63
102
  export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeState {
64
- const mode = resolveIntercomBridgeMode(input.mode);
103
+ const config = resolveIntercomBridgeConfig(input.config);
104
+ const mode = config.mode;
65
105
  const extensionDir = path.resolve(input.extensionDir ?? DEFAULT_INTERCOM_EXTENSION_DIR);
66
106
  const orchestratorTarget = input.orchestratorTarget?.trim();
107
+ const settingsDir = path.resolve(input.settingsDir ?? DEFAULT_SUBAGENT_CONFIG_DIR);
108
+ const defaultInstruction = buildIntercomBridgeInstruction(
109
+ orchestratorTarget || "{orchestratorTarget}",
110
+ DEFAULT_INTERCOM_BRIDGE_TEMPLATE,
111
+ );
67
112
 
68
113
  if (mode === "off") {
69
- return { active: false, mode, extensionDir };
114
+ return { active: false, mode, extensionDir, instruction: defaultInstruction };
70
115
  }
71
116
  if (mode === "fork-only" && input.context !== "fork") {
72
- return { active: false, mode, extensionDir };
117
+ return { active: false, mode, extensionDir, instruction: defaultInstruction };
73
118
  }
74
119
  if (!orchestratorTarget) {
75
- return { active: false, mode, extensionDir };
120
+ return { active: false, mode, extensionDir, instruction: defaultInstruction };
76
121
  }
77
122
  if (!fs.existsSync(extensionDir)) {
78
- return { active: false, mode, extensionDir };
123
+ return { active: false, mode, extensionDir, instruction: defaultInstruction };
79
124
  }
80
125
 
81
126
  const configPath = path.resolve(input.configPath ?? DEFAULT_INTERCOM_CONFIG_PATH);
82
127
  if (!intercomEnabled(configPath)) {
83
- return { active: false, mode, extensionDir };
128
+ return { active: false, mode, extensionDir, instruction: defaultInstruction };
84
129
  }
85
130
 
131
+ const instruction = buildIntercomBridgeInstruction(
132
+ orchestratorTarget,
133
+ resolveInstructionTemplate(config.instructionFile, settingsDir),
134
+ );
135
+
86
136
  return {
87
137
  active: true,
88
138
  mode,
89
139
  orchestratorTarget,
90
140
  extensionDir,
141
+ instruction,
91
142
  };
92
143
  }
93
144
 
@@ -98,7 +149,7 @@ export function applyIntercomBridgeToAgent(agent: AgentConfig, bridge: IntercomB
98
149
  const tools = agent.tools && !agent.tools.includes("intercom")
99
150
  ? [...agent.tools, "intercom"]
100
151
  : agent.tools;
101
- const instruction = buildIntercomBridgeInstruction(bridge.orchestratorTarget);
152
+ const instruction = bridge.instruction;
102
153
  const trimmedPrompt = agent.systemPrompt?.trim() || "";
103
154
  const systemPrompt = trimmedPrompt.includes(INTERCOM_BRIDGE_MARKER)
104
155
  ? trimmedPrompt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-subagents",
3
- "version": "0.13.1",
3
+ "version": "0.13.3",
4
4
  "description": "Pi extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification",
5
5
  "author": "Nico Bailon",
6
6
  "license": "MIT",
package/render.ts CHANGED
@@ -195,9 +195,11 @@ export function renderSubagentResult(
195
195
  const isRunning = r.progress?.status === "running";
196
196
  const icon = isRunning
197
197
  ? theme.fg("warning", "...")
198
- : r.exitCode === 0
199
- ? theme.fg("success", "ok")
200
- : theme.fg("error", "X");
198
+ : r.detached
199
+ ? theme.fg("warning", "")
200
+ : r.exitCode === 0
201
+ ? theme.fg("success", "ok")
202
+ : theme.fg("error", "X");
201
203
  const contextBadge = d.context === "fork" ? theme.fg("warning", " [fork]") : "";
202
204
  const output = r.truncation?.text || getSingleResultOutput(r);
203
205
 
@@ -995,6 +995,8 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
995
995
  const r = await runSync(ctx.cwd, agents, params.agent!, task, {
996
996
  cwd: params.cwd,
997
997
  signal,
998
+ allowIntercomDetach: agentConfig.systemPrompt?.includes("Intercom orchestration channel:") === true,
999
+ intercomEvents: deps.pi.events,
998
1000
  runId,
999
1001
  sessionDir: sessionDirForIndex(0),
1000
1002
  sessionFile: sessionFileForIndex(0),
@@ -1024,6 +1026,19 @@ async function runSinglePath(data: ExecutionContextData, deps: ExecutorDeps): Pr
1024
1026
  saveError: r.outputSaveError,
1025
1027
  });
1026
1028
 
1029
+ if (r.detached) {
1030
+ return {
1031
+ content: [{ type: "text", text: `Detached for intercom coordination: ${params.agent}` }],
1032
+ details: {
1033
+ mode: "single",
1034
+ results: [r],
1035
+ progress: params.includeProgress ? allProgress : undefined,
1036
+ artifacts: allArtifactPaths.length ? { dir: artifactsDir, files: allArtifactPaths } : undefined,
1037
+ truncation: r.truncation,
1038
+ },
1039
+ };
1040
+ }
1041
+
1027
1042
  if (r.exitCode !== 0)
1028
1043
  return {
1029
1044
  content: [{ type: "text", text: r.error || "Failed" }],
@@ -1102,12 +1117,15 @@ export function createSubagentExecutor(deps: ExecutorDeps): {
1102
1117
  const parentSessionFile = ctx.sessionManager.getSessionFile() ?? null;
1103
1118
  deps.state.currentSessionId = parentSessionFile ?? `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1104
1119
  const discoveredAgents = deps.discoverAgents(ctx.cwd, scope).agents;
1105
- const piWithSessionName = deps.pi as ExtensionAPI & { getSessionName?: () => string | undefined };
1106
- const orchestratorTarget = piWithSessionName.getSessionName?.();
1120
+ let sessionName = deps.pi.getSessionName()?.trim();
1121
+ if (!sessionName) {
1122
+ sessionName = `session-${ctx.sessionManager.getSessionId().slice(0, 8)}`;
1123
+ deps.pi.setSessionName(sessionName);
1124
+ }
1107
1125
  const intercomBridge = resolveIntercomBridge({
1108
- mode: deps.config.intercomBridge,
1126
+ config: deps.config.intercomBridge,
1109
1127
  context: normalizedParams.context,
1110
- orchestratorTarget,
1128
+ orchestratorTarget: sessionName,
1111
1129
  });
1112
1130
  const agents = intercomBridge.active
1113
1131
  ? discoveredAgents.map((agent) => applyIntercomBridgeToAgent(agent, intercomBridge))
package/types.ts CHANGED
@@ -58,7 +58,7 @@ export interface ResolvedSkill {
58
58
  export interface AgentProgress {
59
59
  index: number;
60
60
  agent: string;
61
- status: "pending" | "running" | "completed" | "failed";
61
+ status: "pending" | "running" | "completed" | "failed" | "detached";
62
62
  task: string;
63
63
  skills?: string[];
64
64
  currentTool?: string;
@@ -94,6 +94,8 @@ export interface SingleResult {
94
94
  agent: string;
95
95
  task: string;
96
96
  exitCode: number;
97
+ detached?: boolean;
98
+ detachedReason?: string;
97
99
  messages: Message[];
98
100
  usage: Usage;
99
101
  model?: string;
@@ -237,6 +239,14 @@ export interface ErrorInfo {
237
239
  details?: string;
238
240
  }
239
241
 
242
+ export interface IntercomEventBus {
243
+ on(channel: string, handler: (data: unknown) => void): () => void;
244
+ emit(channel: string, data: unknown): void;
245
+ }
246
+
247
+ export const INTERCOM_DETACH_REQUEST_EVENT = "pi-intercom:detach-request";
248
+ export const INTERCOM_DETACH_RESPONSE_EVENT = "pi-intercom:detach-response";
249
+
240
250
  // ============================================================================
241
251
  // Execution Options
242
252
  // ============================================================================
@@ -244,6 +254,8 @@ export interface ErrorInfo {
244
254
  export interface RunSyncOptions {
245
255
  cwd?: string;
246
256
  signal?: AbortSignal;
257
+ allowIntercomDetach?: boolean;
258
+ intercomEvents?: IntercomEventBus;
247
259
  onUpdate?: (r: import("@mariozechner/pi-agent-core").AgentToolResult<Details>) => void;
248
260
  maxOutput?: MaxOutputConfig;
249
261
  artifactsDir?: string;
@@ -263,13 +275,20 @@ export interface RunSyncOptions {
263
275
  skills?: string[];
264
276
  }
265
277
 
278
+ export type IntercomBridgeMode = "off" | "fork-only" | "always";
279
+
280
+ export interface IntercomBridgeConfig {
281
+ mode?: IntercomBridgeMode;
282
+ instructionFile?: string;
283
+ }
284
+
266
285
  export interface ExtensionConfig {
267
286
  asyncByDefault?: boolean;
268
287
  defaultSessionDir?: string;
269
288
  maxSubagentDepth?: number;
270
289
  worktreeSetupHook?: string;
271
290
  worktreeSetupHookTimeoutMs?: number;
272
- intercomBridge?: "off" | "fork-only" | "always";
291
+ intercomBridge?: IntercomBridgeConfig;
273
292
  }
274
293
 
275
294
  // ============================================================================