@bastani/atomic 0.9.15 → 0.9.16-alpha.2

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 (151) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +10 -0
  3. package/dist/builtin/intercom/README.md +8 -8
  4. package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
  5. package/dist/builtin/intercom/index-heavy.ts +1 -0
  6. package/dist/builtin/intercom/index.ts +21 -15
  7. package/dist/builtin/intercom/intercom-tool.ts +66 -10
  8. package/dist/builtin/intercom/package.json +1 -1
  9. package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
  10. package/dist/builtin/mcp/package.json +1 -1
  11. package/dist/builtin/subagents/CHANGELOG.md +26 -0
  12. package/dist/builtin/subagents/README.md +37 -114
  13. package/dist/builtin/subagents/agents/worker.md +2 -2
  14. package/dist/builtin/subagents/package.json +1 -5
  15. package/dist/builtin/subagents/skills/subagent/SKILL.md +26 -103
  16. package/dist/builtin/subagents/src/extension/index.ts +5 -30
  17. package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
  18. package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
  19. package/dist/builtin/subagents/src/extension/tool-description.ts +1 -4
  20. package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
  21. package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -54
  22. package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
  23. package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
  24. package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
  25. package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
  26. package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
  27. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
  28. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
  29. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
  30. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
  31. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
  32. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
  33. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
  34. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
  35. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
  36. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
  37. package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
  38. package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -257
  39. package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
  40. package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
  41. package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
  42. package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
  43. package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
  44. package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
  45. package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
  46. package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
  47. package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
  48. package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
  49. package/dist/builtin/subagents/src/slash/slash-commands.ts +4 -468
  50. package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
  51. package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
  52. package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
  53. package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
  54. package/dist/builtin/web-access/package.json +1 -1
  55. package/dist/builtin/workflows/CHANGELOG.md +16 -0
  56. package/dist/builtin/workflows/README.md +1 -1
  57. package/dist/builtin/workflows/package.json +1 -1
  58. package/dist/builtin/workflows/skills/bro/LICENSE.txt +21 -0
  59. package/dist/builtin/workflows/skills/bro/SKILL.md +14 -0
  60. package/dist/builtin/workflows/skills/how/LICENSE.txt +21 -0
  61. package/dist/builtin/workflows/skills/how/SKILL.md +162 -0
  62. package/dist/builtin/workflows/skills/how/references/critic-prompt.md +59 -0
  63. package/dist/builtin/workflows/skills/how/references/critique-rubric.md +58 -0
  64. package/dist/builtin/workflows/skills/how/references/explainer-prompt.md +55 -0
  65. package/dist/builtin/workflows/skills/how/references/explorer-prompt.md +52 -0
  66. package/dist/builtin/workflows/skills/teach/LICENSE.txt +21 -0
  67. package/dist/builtin/workflows/skills/teach/SKILL.md +28 -0
  68. package/dist/builtin/workflows/skills/unslop/LICENSE.txt +21 -0
  69. package/dist/builtin/workflows/skills/unslop/SKILL.md +87 -0
  70. package/dist/builtin/workflows/skills/why/LICENSE.txt +21 -0
  71. package/dist/builtin/workflows/skills/why/SKILL.md +267 -0
  72. package/dist/builtin/workflows/skills/why/references/epistemics.md +144 -0
  73. package/dist/builtin/workflows/skills/why/references/investigator-prompt.md +103 -0
  74. package/dist/builtin/workflows/skills/why/references/source-playbook.md +17 -0
  75. package/dist/builtin/workflows/skills/why/references/sources/code-archaeology.md +88 -0
  76. package/dist/builtin/workflows/skills/why/references/sources/databricks.md +70 -0
  77. package/dist/builtin/workflows/skills/why/references/sources/datadog.md +99 -0
  78. package/dist/builtin/workflows/skills/why/references/sources/incident-postmortem.md +15 -0
  79. package/dist/builtin/workflows/skills/why/references/sources/linear.md +48 -0
  80. package/dist/builtin/workflows/skills/why/references/sources/notion.md +55 -0
  81. package/dist/builtin/workflows/skills/why/references/sources/sentry.md +100 -0
  82. package/dist/builtin/workflows/skills/why/references/sources/slack.md +54 -0
  83. package/dist/builtin/workflows/skills/why/references/synthesizer-prompt.md +135 -0
  84. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
  85. package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
  86. package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
  87. package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
  88. package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
  89. package/dist/builtin/workflows/src/extension/companions.ts +1 -1
  90. package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
  91. package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
  92. package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
  93. package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
  94. package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
  95. package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
  96. package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
  97. package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
  98. package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
  99. package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
  100. package/dist/builtin/workflows/src/tui/widget.ts +25 -6
  101. package/dist/core/agent-session-auto-compaction.js +10 -10
  102. package/dist/core/agent-session-auto-compaction.js.map +1 -1
  103. package/dist/core/agent-session-events.d.ts.map +1 -1
  104. package/dist/core/agent-session-events.js +11 -13
  105. package/dist/core/agent-session-events.js.map +1 -1
  106. package/dist/core/agent-session-methods.d.ts +1 -1
  107. package/dist/core/agent-session-methods.d.ts.map +1 -1
  108. package/dist/core/agent-session-methods.js.map +1 -1
  109. package/dist/core/agent-session-models.d.ts.map +1 -1
  110. package/dist/core/agent-session-models.js +5 -7
  111. package/dist/core/agent-session-models.js.map +1 -1
  112. package/dist/core/agent-session-prompt.js +4 -4
  113. package/dist/core/agent-session-prompt.js.map +1 -1
  114. package/dist/core/agent-session-retry.d.ts +4 -4
  115. package/dist/core/agent-session-retry.d.ts.map +1 -1
  116. package/dist/core/agent-session-retry.js +8 -34
  117. package/dist/core/agent-session-retry.js.map +1 -1
  118. package/dist/core/agent-session-tool-hooks.js +4 -4
  119. package/dist/core/agent-session-tool-hooks.js.map +1 -1
  120. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  121. package/dist/core/atomic-guide-command.js +3 -10
  122. package/dist/core/atomic-guide-command.js.map +1 -1
  123. package/dist/core/event-bus.d.ts.map +1 -1
  124. package/dist/core/event-bus.js +19 -3
  125. package/dist/core/event-bus.js.map +1 -1
  126. package/dist/core/slash-commands.d.ts.map +1 -1
  127. package/dist/core/slash-commands.js +0 -3
  128. package/dist/core/slash-commands.js.map +1 -1
  129. package/dist/core/system-prompt.d.ts.map +1 -1
  130. package/dist/core/system-prompt.js +0 -12
  131. package/dist/core/system-prompt.js.map +1 -1
  132. package/dist/modes/interactive-engine/remote-command-catalog.d.ts +1 -1
  133. package/dist/modes/interactive-engine/remote-command-catalog.js +1 -1
  134. package/dist/modes/interactive-engine/remote-command-catalog.js.map +1 -1
  135. package/docs/intercom.md +29 -27
  136. package/docs/settings.md +2 -2
  137. package/docs/subagents.md +18 -15
  138. package/docs/usage.md +0 -9
  139. package/docs/workflows.md +27 -10
  140. package/npm-shrinkwrap.json +32 -32
  141. package/package.json +3 -3
  142. package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +0 -27
  143. package/dist/builtin/subagents/prompts/parallel-cleanup.md +0 -61
  144. package/dist/builtin/subagents/prompts/parallel-context-build.md +0 -43
  145. package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +0 -47
  146. package/dist/builtin/subagents/prompts/parallel-research.md +0 -34
  147. package/dist/builtin/subagents/prompts/parallel-review.md +0 -47
  148. package/dist/builtin/subagents/prompts/review-loop.md +0 -39
  149. package/dist/builtin/subagents/src/extension/doctor.ts +0 -188
  150. package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
  151. /package/dist/builtin/subagents/src/runs/foreground/{subagent-executor-resume.ts → subagent-executor-cwd.ts} +0 -0
@@ -38,7 +38,6 @@ import {
38
38
  } from "../slash/slash-live-state.js";
39
39
  import {
40
40
  advanceResultPulseFrame,
41
- renderLiveSubagentResult,
42
41
  renderSubagentResult,
43
42
  type SubagentResultRenderState,
44
43
  stopResultAnimations,
@@ -48,6 +47,7 @@ import { parseSubagentNotifyContent } from "./notification-content.js";
48
47
  import { DEFAULT_PROMPT_GUIDANCE } from "./prompt-guidance.js";
49
48
  import { SubagentParams } from "./schemas.js";
50
49
  import { SUBAGENT_TOOL_DESCRIPTION } from "./tool-description.js";
50
+ import { renderSubagentToolCall, renderSubagentToolResult } from "./tool-rendering.js";
51
51
 
52
52
  export {
53
53
  PROMPT_TEMPLATE_SUBAGENT_CANCEL_EVENT,
@@ -157,7 +157,7 @@ export function renderSubagentNotification(
157
157
  const icon =
158
158
  details.status === "completed"
159
159
  ? theme.fg("success", "āœ“")
160
- : details.status === "paused"
160
+ : details.status === "interrupted"
161
161
  ? theme.fg("warning", "ā– ")
162
162
  : theme.fg("error", "āœ—");
163
163
  const parts: string[] = [];
@@ -218,7 +218,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
218
218
  baseCwd: "",
219
219
  currentSessionId: null,
220
220
  subagentInProgress: false,
221
- foregroundRuns: new Map(),
222
221
  foregroundControls: new Map(),
223
222
  lastForegroundControlId: null,
224
223
  pendingForegroundControlNotices: new Map(),
@@ -326,14 +325,6 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
326
325
  promptTemplateBridge.cancelAll();
327
326
  promptTemplateBridge.dispose();
328
327
  });
329
- function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
330
- if (!tasks || tasks.length === 0) return 0;
331
- return tasks.reduce((total, task) => {
332
- const count =
333
- typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;
334
- return total + count;
335
- }, 0);
336
- }
337
328
  const tool: ToolDefinition<typeof SubagentParams, Details, SubagentToolRenderState> = {
338
329
  name: "subagent",
339
330
  label: "Subagent",
@@ -344,27 +335,11 @@ export default function registerSubagentExtension(pi: ExtensionAPI): void {
344
335
  const executionSignal = signal ?? ctx.signal ?? new AbortController().signal;
345
336
  return executeSubagentCollapsed(id, params as SubagentParamsLike, executionSignal, onUpdate, ctx);
346
337
  },
347
- renderCall(args, theme) {
348
- if (args.action) {
349
- const target = args.agent || "";
350
- return new Text(
351
- `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
352
- 0,
353
- 0,
354
- );
355
- }
356
- const isParallel = (args.tasks?.length ?? 0) > 0;
357
- const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
358
- return new Text(
359
- isParallel
360
- ? `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`
361
- : `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}`,
362
- 0,
363
- 0,
364
- );
338
+ renderCall(args, theme, context) {
339
+ return renderSubagentToolCall(args as SubagentParamsLike, theme, context);
365
340
  },
366
341
  renderResult(result, options, theme, context) {
367
- return renderLiveSubagentResult(result, options, theme, context);
342
+ return renderSubagentToolResult(result, options, theme, context);
368
343
  },
369
344
  };
370
345
  pi.registerTool(tool);
@@ -1,6 +1,6 @@
1
1
  import type { SubagentNotifyDetails } from "../runs/foreground/notify.js";
2
2
 
3
- const HEADER_STATUSES = ["completed", "failed", "paused"] as const;
3
+ const HEADER_STATUSES = ["completed", "failed", "interrupted"] as const;
4
4
 
5
5
  function isHeaderLineTerminator(character: string): boolean {
6
6
  return character === "\r" || character === "\u2028" || character === "\u2029";
@@ -124,23 +124,11 @@ export const SubagentParams = Type.Object(
124
124
  description: "Management/control action. Omit for execution mode.",
125
125
  }),
126
126
  ),
127
- id: Type.Optional(
128
- Type.String({
129
- description: "Run id or prefix for action='status', action='interrupt', or action='resume'.",
130
- }),
131
- ),
127
+ id: Type.Optional(Type.String({ description: "Run id or prefix for action='status' or action='interrupt'." })),
132
128
  runId: Type.Optional(
133
129
  Type.String({
134
130
  description:
135
- "Target run ID for action='interrupt' or action='resume'. Defaults to the most recently active controllable run for interrupt. Prefer id for new calls.",
136
- }),
137
- ),
138
- index: Type.Optional(
139
- Type.Integer({ minimum: 0, description: "Zero-based child index for actions that target a specific child." }),
140
- ),
141
- message: Type.Optional(
142
- Type.String({
143
- description: "Follow-up message for action='resume'. Use index to choose a child from multi-child runs.",
131
+ "Target run ID for action='interrupt'. Defaults to the most recently active controllable run. Prefer id for new calls.",
144
132
  }),
145
133
  ),
146
134
  config: Type.Optional(
@@ -14,7 +14,4 @@ MANAGEMENT (use action field, omit agent/task/tasks):
14
14
  • { action: "delete", agent: "code-analysis.custom-agent" }
15
15
  CONTROL:
16
16
  • { action: "status", id: "..." } - inspect an in-process run by id or prefix
17
- • { action: "interrupt", id?: "..." } - soft-interrupt the current child turn and leave the run paused
18
- • { action: "resume", id: "...", message: "...", index?: 0 } - follow up with a live child or revive a retained foreground child
19
- DIAGNOSTICS:
20
- • { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`;
17
+ • { action: "interrupt", id?: "..." } - soft-interrupt the current child turn; the child is terminal for continuation, so follow-up work starts a fresh launch`;
@@ -0,0 +1,53 @@
1
+ import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
+ import { type Component, Container, Text } from "@earendil-works/pi-tui";
3
+ import { getBurstDisplay } from "../runs/foreground/subagent-executor-burst-display.js";
4
+ import type { SubagentParamsLike } from "../runs/foreground/subagent-executor-types.js";
5
+ import type { Details } from "../shared/types.js";
6
+ import { renderLiveSubagentResult } from "../tui/render.js";
7
+
8
+ type Theme = Parameters<typeof renderLiveSubagentResult>[2];
9
+ type RenderContext = Parameters<typeof renderLiveSubagentResult>[3] & { toolCallId: string };
10
+
11
+ function effectiveParallelTaskCount(tasks: Array<{ count?: unknown }> | undefined): number {
12
+ if (!tasks || tasks.length === 0) return 0;
13
+ return tasks.reduce((total, task) => {
14
+ const count = typeof task.count === "number" && Number.isInteger(task.count) && task.count >= 1 ? task.count : 1;
15
+ return total + count;
16
+ }, 0);
17
+ }
18
+
19
+ export function renderSubagentToolCall(args: SubagentParamsLike, theme: Theme, context: RenderContext): Component {
20
+ const burst = getBurstDisplay(context.toolCallId);
21
+ if (burst && !burst.owner) return new Container();
22
+ if (args.action) {
23
+ const target = args.agent || "";
24
+ return new Text(
25
+ `${theme.fg("toolTitle", theme.bold("subagent "))}${args.action}${target ? ` ${theme.fg("accent", target)}` : ""}`,
26
+ 0,
27
+ 0,
28
+ );
29
+ }
30
+ if (burst) {
31
+ return new Text(`${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${burst.taskCount})`, 0, 0);
32
+ }
33
+ const isParallel = (args.tasks?.length ?? 0) > 0;
34
+ const parallelCount = effectiveParallelTaskCount(args.tasks as Array<{ count?: unknown }> | undefined);
35
+ return new Text(
36
+ isParallel
37
+ ? `${theme.fg("toolTitle", theme.bold("subagent "))}parallel (${parallelCount})`
38
+ : `${theme.fg("toolTitle", theme.bold("subagent "))}${theme.fg("accent", args.agent || "?")}`,
39
+ 0,
40
+ 0,
41
+ );
42
+ }
43
+
44
+ export function renderSubagentToolResult(
45
+ result: AgentToolResult<Details>,
46
+ options: { expanded: boolean; isPartial: boolean },
47
+ theme: Theme,
48
+ context: RenderContext,
49
+ ): Component {
50
+ const burst = getBurstDisplay(context.toolCallId);
51
+ if (burst && !burst.owner) return new Container();
52
+ return renderLiveSubagentResult(burst?.result ?? result, options, theme, context);
53
+ }
@@ -37,7 +37,7 @@ const DEFAULT_INTERCOM_BRIDGE_TEMPLATE = `The inherited thread is reference-only
37
37
 
38
38
  Use contact_supervisor first. It resolves the supervisor session "{orchestratorTarget}" and run metadata automatically.
39
39
  - Need a decision, blocked, approval, or product/API/scope ambiguity: contact_supervisor({ reason: "need_decision", message: "<question>" })
40
- - After contact_supervisor with reason "need_decision", stay alive and continue only after the reply arrives. Do not finish your final response with a choose-one question.
40
+ - A claimed decision or interview ends this child. Do not wait for or expect a reply in this run; the supervisor receives a dynamic [TASK_CONTEXT] handoff and may launch a fresh subagent with a new run identity and the answer.
41
41
  - Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions. Review-only/no-edit wins; leave files unchanged and mention the conflict in your final result only if it matters.
42
42
  - Meaningful progress or unexpected discoveries that change the plan: contact_supervisor({ reason: "progress_update", message: "UPDATE: <summary>" })
43
43
  - Generic intercom is lower-level plumbing/fallback only: intercom({ action: "ask", to: "{orchestratorTarget}", message: "<question>" })
@@ -52,19 +52,6 @@ export interface IntercomBridgeState {
52
52
  instruction: string;
53
53
  }
54
54
 
55
- export interface IntercomBridgeDiagnostic {
56
- active: boolean;
57
- mode: IntercomBridgeMode;
58
- wantsIntercom: boolean;
59
- piIntercomAvailable: boolean;
60
- extensionDir: string;
61
- configPath?: string;
62
- orchestratorTarget?: string;
63
- reason?: string;
64
- intercomConfigEnabled?: boolean;
65
- intercomConfigError?: string;
66
- }
67
-
68
55
  interface ResolveIntercomBridgeInput {
69
56
  config: ExtensionConfig["intercomBridge"];
70
57
  context: "fresh" | "fork" | undefined;
@@ -317,46 +304,6 @@ function buildIntercomBridgeInstruction(orchestratorTarget: string, template: st
317
304
  ${instruction}`;
318
305
  }
319
306
 
320
- export function diagnoseIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeDiagnostic {
321
- const config = resolveIntercomBridgeConfig(input.config);
322
- const mode = config.mode;
323
- const agentDir = path.resolve(input.agentDir ?? defaultAgentDir());
324
- const extensionDir = resolveIntercomExtensionDir(input, agentDir);
325
- const orchestratorTarget = input.orchestratorTarget?.trim();
326
- const configPath = path.resolve(input.configPath ?? defaultIntercomConfigPath(agentDir));
327
- const wantsIntercom = mode !== "off" && !(mode === "fork-only" && input.context !== "fork");
328
- const piIntercomAvailable = fs.existsSync(extensionDir);
329
- let configStatus: ReturnType<typeof intercomConfigStatus> | undefined;
330
- let reason: string | undefined;
331
- if (mode === "off") reason = "bridge mode is off";
332
- else if (mode === "fork-only" && input.context !== "fork")
333
- reason = "bridge mode is fork-only and context is not fork";
334
- else if (!orchestratorTarget) reason = "orchestrator target is not available";
335
- else if (!piIntercomAvailable) reason = "pi-intercom extension was not found";
336
- else {
337
- configStatus = intercomConfigStatus(configPath);
338
- if (!configStatus.enabled) reason = "intercom config is disabled";
339
- }
340
- let intercomConfigError: string | undefined;
341
- if (configStatus?.error) {
342
- const error = configStatus.error;
343
- intercomConfigError = error instanceof Error ? `${error.name}: ${error.message}` : String(error);
344
- }
345
-
346
- return {
347
- active: reason === undefined,
348
- mode,
349
- wantsIntercom,
350
- piIntercomAvailable,
351
- extensionDir,
352
- configPath,
353
- ...(orchestratorTarget ? { orchestratorTarget } : {}),
354
- ...(reason ? { reason } : {}),
355
- ...(configStatus ? { intercomConfigEnabled: configStatus.enabled } : {}),
356
- ...(intercomConfigError ? { intercomConfigError } : {}),
357
- };
358
- }
359
-
360
307
  export function resolveIntercomBridge(input: ResolveIntercomBridgeInput): IntercomBridgeState {
361
308
  const config = resolveIntercomBridgeConfig(input.config);
362
309
  const mode = config.mode;
@@ -21,7 +21,7 @@ export function resolveSubagentResultStatus(input: {
21
21
  detached?: boolean;
22
22
  }): SubagentResultStatus {
23
23
  if (input.detached || input.status === "continued") return "detached";
24
- if (input.interrupted || input.status === "interrupted" || input.state === "paused") return "paused";
24
+ if (input.interrupted || input.status === "interrupted" || input.state === "interrupted") return "interrupted";
25
25
  if (input.status === "ok" || input.success === true || input.state === "complete") return "completed";
26
26
  if (input.status === "error" || input.status === "skipped" || input.success === false || input.state === "failed")
27
27
  return "failed";
@@ -32,7 +32,7 @@ function countStatuses(children: SubagentResultIntercomChild[]): Record<Subagent
32
32
  const counts: Record<SubagentResultStatus, number> = {
33
33
  completed: 0,
34
34
  failed: 0,
35
- paused: 0,
35
+ interrupted: 0,
36
36
  detached: 0,
37
37
  };
38
38
  for (const child of children) {
@@ -45,7 +45,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
45
45
  const parts = [
46
46
  counts.completed ? `${counts.completed} completed` : undefined,
47
47
  counts.failed ? `${counts.failed} failed` : undefined,
48
- counts.paused ? `${counts.paused} paused` : undefined,
48
+ counts.interrupted ? `${counts.interrupted} interrupted` : undefined,
49
49
  counts.detached ? `${counts.detached} detached` : undefined,
50
50
  ].filter((part): part is string => Boolean(part));
51
51
  return parts.length ? parts.join(", ") : "0 results";
@@ -54,7 +54,7 @@ function formatStatusCounts(counts: Record<SubagentResultStatus, number>): strin
54
54
  function resolveGroupedStatus(children: SubagentResultIntercomChild[]): SubagentResultStatus {
55
55
  const counts = countStatuses(children);
56
56
  if (counts.failed > 0) return "failed";
57
- if (counts.paused > 0) return "paused";
57
+ if (counts.interrupted > 0) return "interrupted";
58
58
  if (counts.completed > 0) return "completed";
59
59
  if (counts.detached > 0) return "detached";
60
60
  return "failed";
@@ -0,0 +1,78 @@
1
+ import {
2
+ PARENT_ASK_HANDOFF_REQUEST_EVENT,
3
+ type ParentAskHandoffRequest,
4
+ type RunSyncOptions,
5
+ } from "../../shared/types.js";
6
+
7
+ interface ExecutionParentAskState {
8
+ readonly agent: string;
9
+ readonly isUnavailable: () => boolean;
10
+ }
11
+
12
+ const PROCESS_PARENT_ASK_CLAIMS = Symbol.for("atomic/subagents/parent-ask-claims@1");
13
+
14
+ type ProcessParentAskClaimHandler = (payload: unknown) => void;
15
+
16
+ interface ProcessParentAskClaimRegistry {
17
+ handlers: Set<ProcessParentAskClaimHandler>;
18
+ }
19
+
20
+ function processClaimSlots(): typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined> {
21
+ return globalThis as typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined>;
22
+ }
23
+
24
+ function registerProcessClaimHandler(handler: ProcessParentAskClaimHandler): () => void {
25
+ const slots = processClaimSlots();
26
+ const registry = slots[PROCESS_PARENT_ASK_CLAIMS] ?? { handlers: new Set<ProcessParentAskClaimHandler>() };
27
+ slots[PROCESS_PARENT_ASK_CLAIMS] = registry;
28
+ registry.handlers.add(handler);
29
+ return () => {
30
+ registry.handlers.delete(handler);
31
+ if (registry.handlers.size === 0 && slots[PROCESS_PARENT_ASK_CLAIMS] === registry) {
32
+ delete slots[PROCESS_PARENT_ASK_CLAIMS];
33
+ }
34
+ };
35
+ }
36
+
37
+ function isParentAskHandoffRequest(payload: unknown): payload is ParentAskHandoffRequest {
38
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return false;
39
+ const request = payload as Partial<ParentAskHandoffRequest>;
40
+ return (
41
+ typeof request.runId === "string" &&
42
+ typeof request.index === "number" &&
43
+ typeof request.agent === "string" &&
44
+ typeof request.childIntercomTarget === "string" &&
45
+ typeof request.orchestratorTarget === "string" &&
46
+ (request.kind === "decision" || request.kind === "interview" || request.kind === "intercom") &&
47
+ typeof request.question === "string" &&
48
+ typeof request.claimed === "boolean"
49
+ );
50
+ }
51
+
52
+ /** Claim a blocking ask only for the exact live child attempt that issued it. */
53
+ export function registerExecutionParentAskHandoff(options: RunSyncOptions, state: ExecutionParentAskState): () => void {
54
+ if (!options.onParentAskHandoff) return () => {};
55
+ const handleRequest = (payload: unknown): void => {
56
+ if (state.isUnavailable() || !isParentAskHandoffRequest(payload)) return;
57
+ if (
58
+ payload.claimed ||
59
+ payload.runId !== options.runId ||
60
+ payload.index !== (options.index ?? 0) ||
61
+ payload.agent !== state.agent ||
62
+ payload.childIntercomTarget !== options.intercomSessionName ||
63
+ payload.orchestratorTarget !== options.orchestratorIntercomTarget
64
+ )
65
+ return;
66
+ payload.claimed = true;
67
+ options.onParentAskHandoff?.(payload);
68
+ };
69
+ const eventCleanup = options.intercomEvents?.on(PARENT_ASK_HANDOFF_REQUEST_EVENT, handleRequest) ?? (() => {});
70
+ const processCleanup = registerProcessClaimHandler(handleRequest);
71
+ let cleaned = false;
72
+ return () => {
73
+ if (cleaned) return;
74
+ cleaned = true;
75
+ eventCleanup();
76
+ processCleanup();
77
+ };
78
+ }
@@ -23,6 +23,7 @@ import type { AttemptOutcome, ChildSpec, ParentContext } from "../inprocess/runn
23
23
  import { filterSpawnableModelCandidates } from "../shared/model-candidate-filter.js";
24
24
  import { buildModelCandidates } from "../shared/model-fallback.js";
25
25
  import { registerExecutionIntercomDetach } from "./execution-intercom-detach.js";
26
+ import { registerExecutionParentAskHandoff } from "./execution-parent-ask-handoff.js";
26
27
 
27
28
  function emptyUsage(): Usage {
28
29
  return {
@@ -349,11 +350,6 @@ export async function runSingleInProcess(
349
350
  lastActivityAt: Date.now(),
350
351
  });
351
352
  }
352
- control.registerAttempt(options.runId, running, {
353
- model: resolvedCandidate?.model,
354
- modelId: candidate,
355
- thinkingLevel: spec.thinkingLevel,
356
- });
357
353
  let detached = false;
358
354
  let resolveContinuation!: () => void;
359
355
  const continuation = new Promise<void>((resolve) => {
@@ -369,10 +365,15 @@ export async function runSingleInProcess(
369
365
  resolveContinuation();
370
366
  },
371
367
  });
368
+ const parentAskCleanup = registerExecutionParentAskHandoff(options, {
369
+ agent: agent.name,
370
+ isUnavailable: () => running.status !== "running" || detached,
371
+ });
372
372
  const terminal = running.promise.then((value) => ({ kind: "terminal" as const, value }));
373
373
  const winner = await Promise.race([terminal, continuation.then(() => ({ kind: "continued" as const }))]);
374
374
  if (winner.kind === "continued") {
375
375
  detachCleanup();
376
+ parentAskCleanup();
376
377
  void running.promise.then(async (continuedOutcome) => {
377
378
  const recovered = resultFromOutcome(agent, task, continuedOutcome, startedAt, artifactPaths, {
378
379
  cwd,
@@ -441,6 +442,7 @@ export async function runSingleInProcess(
441
442
  return continuedResult;
442
443
  }
443
444
  detachCleanup();
445
+ parentAskCleanup();
444
446
  const outcome = winner.value;
445
447
  const result = resultFromOutcome(agent, task, outcome, startedAt, artifactPaths, {
446
448
  cwd,
@@ -21,7 +21,7 @@ interface SubagentStepResult {
21
21
 
22
22
  export interface SubagentNotifyDetails {
23
23
  agent: string;
24
- status: "completed" | "failed" | "paused";
24
+ status: "completed" | "failed" | "interrupted";
25
25
  taskInfo?: string;
26
26
  resultPreview: string;
27
27
  durationMs?: number;
@@ -211,14 +211,13 @@ export default function registerSubagentNotify(pi: ExtensionAPI): () => void {
211
211
  inFlight.set(key, ownership.promise);
212
212
  const agent = result.agent ?? "unknown";
213
213
  const summary = typeof result.summary === "string" ? result.summary : "";
214
- const paused =
215
- result.status === "interrupted" || result.state === "paused" || summary.startsWith("Paused after interrupt.");
216
- const status = paused
217
- ? "paused"
214
+ const interrupted = result.status === "interrupted" || result.state === "interrupted";
215
+ const status = interrupted
216
+ ? "interrupted"
218
217
  : result.status === "ok"
219
218
  ? "completed"
220
219
  : result.status === "continued"
221
- ? "paused"
220
+ ? "interrupted"
222
221
  : "failed";
223
222
 
224
223
  const taskInfo =
@@ -0,0 +1,48 @@
1
+ import type { ForegroundParentAskHandoff, ParentAskHandoffRequest } from "../../shared/types.js";
2
+
3
+ function formatAttachments(request: ParentAskHandoffRequest): string {
4
+ let text = "";
5
+ for (const attachment of request.attachments ?? []) {
6
+ if (attachment.language) {
7
+ text += `\n\n---\nšŸ“Ž ${attachment.name}\n~~~${attachment.language}\n${attachment.content}\n~~~`;
8
+ } else {
9
+ text += `\n\n---\nšŸ“Ž ${attachment.name}\n${attachment.content}`;
10
+ }
11
+ }
12
+ return text;
13
+ }
14
+
15
+ function formatQuestion(request: ParentAskHandoffRequest): string {
16
+ const parts: string[] = [];
17
+ if (request.question.length > 0) parts.push(request.question);
18
+ if (request.interview) parts.push(JSON.stringify(request.interview, null, 2));
19
+ return parts.join("\n\n") + formatAttachments(request);
20
+ }
21
+
22
+ function freshTask(request: ParentAskHandoffRequest): string {
23
+ return [
24
+ "[TASK_CONTEXT]",
25
+ "Original delegated task and objective:",
26
+ request.taskContext ?? "(Original task context unavailable.)",
27
+ "",
28
+ `Previous child identity: ${request.agent} (child ${request.index + 1})`,
29
+ "Previous child question:",
30
+ formatQuestion(request),
31
+ "",
32
+ "Continue with this supervisor answer: <SUPERVISOR_ANSWER>",
33
+ ].join("\n");
34
+ }
35
+
36
+ export function formatParentAskHandoffOutput(handoff: ForegroundParentAskHandoff): string {
37
+ const request = handoff.request;
38
+ const call = JSON.stringify({ agent: request.agent, task: freshTask(request) }, null, 2);
39
+ return [
40
+ `Subagent yielded for parent input (${request.agent}, child ${request.index + 1}).`,
41
+ `Previous run (terminal): ${request.runId}`,
42
+ "Question:",
43
+ formatQuestion(request),
44
+ "",
45
+ "Start a fresh subagent with a new run identity, replacing <SUPERVISOR_ANSWER> with your answer:",
46
+ `subagent(${call})`,
47
+ ].join("\n");
48
+ }
@@ -0,0 +1,46 @@
1
+ import type { SubagentToolResult } from "../../shared/types.js";
2
+
3
+ interface BurstDisplay {
4
+ ownerId: string;
5
+ callIds: string[];
6
+ taskCount: number;
7
+ result?: SubagentToolResult;
8
+ }
9
+
10
+ const MAX_RETAINED_BURST_DISPLAYS = 100;
11
+ const displays: BurstDisplay[] = [];
12
+ const displayByCallId = new Map<string, BurstDisplay>();
13
+
14
+ export interface BurstDisplaySnapshot {
15
+ owner: boolean;
16
+ taskCount: number;
17
+ result?: SubagentToolResult;
18
+ }
19
+
20
+ export function registerBurstDisplay(callIds: string[], taskCount: number): BurstDisplay {
21
+ const display: BurstDisplay = { ownerId: callIds[0]!, callIds, taskCount };
22
+ displays.push(display);
23
+ for (const callId of callIds) displayByCallId.set(callId, display);
24
+ while (displays.length > MAX_RETAINED_BURST_DISPLAYS) {
25
+ const expired = displays.shift();
26
+ if (!expired) break;
27
+ for (const callId of expired.callIds) {
28
+ if (displayByCallId.get(callId) === expired) displayByCallId.delete(callId);
29
+ }
30
+ }
31
+ return display;
32
+ }
33
+
34
+ export function updateBurstDisplay(display: BurstDisplay, result: SubagentToolResult): void {
35
+ display.result = result;
36
+ }
37
+
38
+ export function getBurstDisplay(callId: string): BurstDisplaySnapshot | undefined {
39
+ const display = displayByCallId.get(callId);
40
+ if (!display) return undefined;
41
+ return {
42
+ owner: display.ownerId === callId,
43
+ taskCount: display.taskCount,
44
+ ...(display.result ? { result: display.result } : {}),
45
+ };
46
+ }