@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.
- package/CHANGELOG.md +19 -0
- package/dist/builtin/intercom/CHANGELOG.md +10 -0
- package/dist/builtin/intercom/README.md +8 -8
- package/dist/builtin/intercom/contact-supervisor-tool.ts +68 -32
- package/dist/builtin/intercom/index-heavy.ts +1 -0
- package/dist/builtin/intercom/index.ts +21 -15
- package/dist/builtin/intercom/intercom-tool.ts +66 -10
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/parent-ask-handoff.ts +72 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +26 -0
- package/dist/builtin/subagents/README.md +37 -114
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -5
- package/dist/builtin/subagents/skills/subagent/SKILL.md +26 -103
- package/dist/builtin/subagents/src/extension/index.ts +5 -30
- package/dist/builtin/subagents/src/extension/notification-content.ts +1 -1
- package/dist/builtin/subagents/src/extension/schemas.ts +2 -14
- package/dist/builtin/subagents/src/extension/tool-description.ts +1 -4
- package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -54
- package/dist/builtin/subagents/src/intercom/result-intercom.ts +4 -4
- package/dist/builtin/subagents/src/runs/foreground/execution-parent-ask-handoff.ts +78 -0
- package/dist/builtin/subagents/src/runs/foreground/inprocess-run-sync.ts +7 -5
- package/dist/builtin/subagents/src/runs/foreground/notify.ts +5 -6
- package/dist/builtin/subagents/src/runs/foreground/parent-ask-output.ts +48 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst-display.ts +46 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-burst.ts +403 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-context.ts +48 -9
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-input.ts +3 -1
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-live-update.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel-task.ts +164 -93
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parallel.ts +36 -29
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-parent-ask-projection.ts +11 -0
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-single.ts +42 -17
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-status.ts +3 -121
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor-types.ts +7 -2
- package/dist/builtin/subagents/src/runs/foreground/subagent-executor.ts +30 -257
- package/dist/builtin/subagents/src/runs/inprocess/control-status.ts +0 -34
- package/dist/builtin/subagents/src/runs/inprocess/index.ts +0 -12
- package/dist/builtin/subagents/src/runs/inprocess/prompt-behavior.ts +1 -1
- package/dist/builtin/subagents/src/runs/inprocess/runner.ts +2 -141
- package/dist/builtin/subagents/src/runs/shared/parallel-utils.ts +8 -0
- package/dist/builtin/subagents/src/shared/status-format.ts +3 -3
- package/dist/builtin/subagents/src/shared/types-config.ts +41 -0
- package/dist/builtin/subagents/src/shared/types-foreground-state.ts +7 -15
- package/dist/builtin/subagents/src/shared/types-results.ts +3 -1
- package/dist/builtin/subagents/src/shared/types-runtime.ts +1 -11
- package/dist/builtin/subagents/src/slash/slash-commands.ts +4 -468
- package/dist/builtin/subagents/src/tui/render-progress.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result-compact.ts +2 -2
- package/dist/builtin/subagents/src/tui/render-result.ts +38 -14
- package/dist/builtin/subagents/src/tui/render-status-progress.ts +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +16 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/skills/bro/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/bro/SKILL.md +14 -0
- package/dist/builtin/workflows/skills/how/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/how/SKILL.md +162 -0
- package/dist/builtin/workflows/skills/how/references/critic-prompt.md +59 -0
- package/dist/builtin/workflows/skills/how/references/critique-rubric.md +58 -0
- package/dist/builtin/workflows/skills/how/references/explainer-prompt.md +55 -0
- package/dist/builtin/workflows/skills/how/references/explorer-prompt.md +52 -0
- package/dist/builtin/workflows/skills/teach/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/teach/SKILL.md +28 -0
- package/dist/builtin/workflows/skills/unslop/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/unslop/SKILL.md +87 -0
- package/dist/builtin/workflows/skills/why/LICENSE.txt +21 -0
- package/dist/builtin/workflows/skills/why/SKILL.md +267 -0
- package/dist/builtin/workflows/skills/why/references/epistemics.md +144 -0
- package/dist/builtin/workflows/skills/why/references/investigator-prompt.md +103 -0
- package/dist/builtin/workflows/skills/why/references/source-playbook.md +17 -0
- package/dist/builtin/workflows/skills/why/references/sources/code-archaeology.md +88 -0
- package/dist/builtin/workflows/skills/why/references/sources/databricks.md +70 -0
- package/dist/builtin/workflows/skills/why/references/sources/datadog.md +99 -0
- package/dist/builtin/workflows/skills/why/references/sources/incident-postmortem.md +15 -0
- package/dist/builtin/workflows/skills/why/references/sources/linear.md +48 -0
- package/dist/builtin/workflows/skills/why/references/sources/notion.md +55 -0
- package/dist/builtin/workflows/skills/why/references/sources/sentry.md +100 -0
- package/dist/builtin/workflows/skills/why/references/sources/slack.md +54 -0
- package/dist/builtin/workflows/skills/why/references/synthesizer-prompt.md +135 -0
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres-root.ts +548 -39
- package/dist/builtin/workflows/src/durable/dbos-embedded-postgres.ts +529 -65
- package/dist/builtin/workflows/src/durable/dbos-lifecycle.ts +30 -8
- package/dist/builtin/workflows/src/durable/dbos-local-postgres.ts +36 -3
- package/dist/builtin/workflows/src/durable/local-command.ts +68 -10
- package/dist/builtin/workflows/src/extension/companions.ts +1 -1
- package/dist/builtin/workflows/src/extension/dispatcher.ts +7 -1
- package/dist/builtin/workflows/src/extension/extension-factory.ts +2 -2
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +1094 -298
- package/dist/builtin/workflows/src/extension/public-types.ts +2 -2
- package/dist/builtin/workflows/src/extension/render-result.ts +16 -2
- package/dist/builtin/workflows/src/extension/runtime.ts +8 -1
- package/dist/builtin/workflows/src/extension/workflow-request-abort.ts +15 -0
- package/dist/builtin/workflows/src/extension/workflow-tool-content.ts +4 -3
- package/dist/builtin/workflows/src/extension/workflow-tool-registration.ts +107 -13
- package/dist/builtin/workflows/src/extension/workflow-tool.ts +22 -12
- package/dist/builtin/workflows/src/tui/widget.ts +25 -6
- package/dist/core/agent-session-auto-compaction.js +10 -10
- package/dist/core/agent-session-auto-compaction.js.map +1 -1
- package/dist/core/agent-session-events.d.ts.map +1 -1
- package/dist/core/agent-session-events.js +11 -13
- package/dist/core/agent-session-events.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -1
- package/dist/core/agent-session-methods.d.ts.map +1 -1
- package/dist/core/agent-session-methods.js.map +1 -1
- package/dist/core/agent-session-models.d.ts.map +1 -1
- package/dist/core/agent-session-models.js +5 -7
- package/dist/core/agent-session-models.js.map +1 -1
- package/dist/core/agent-session-prompt.js +4 -4
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session-retry.d.ts +4 -4
- package/dist/core/agent-session-retry.d.ts.map +1 -1
- package/dist/core/agent-session-retry.js +8 -34
- package/dist/core/agent-session-retry.js.map +1 -1
- package/dist/core/agent-session-tool-hooks.js +4 -4
- package/dist/core/agent-session-tool-hooks.js.map +1 -1
- package/dist/core/atomic-guide-command.d.ts.map +1 -1
- package/dist/core/atomic-guide-command.js +3 -10
- package/dist/core/atomic-guide-command.js.map +1 -1
- package/dist/core/event-bus.d.ts.map +1 -1
- package/dist/core/event-bus.js +19 -3
- package/dist/core/event-bus.js.map +1 -1
- package/dist/core/slash-commands.d.ts.map +1 -1
- package/dist/core/slash-commands.js +0 -3
- package/dist/core/slash-commands.js.map +1 -1
- package/dist/core/system-prompt.d.ts.map +1 -1
- package/dist/core/system-prompt.js +0 -12
- package/dist/core/system-prompt.js.map +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.d.ts +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.js +1 -1
- package/dist/modes/interactive-engine/remote-command-catalog.js.map +1 -1
- package/docs/intercom.md +29 -27
- package/docs/settings.md +2 -2
- package/docs/subagents.md +18 -15
- package/docs/usage.md +0 -9
- package/docs/workflows.md +27 -10
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- package/dist/builtin/subagents/prompts/gather-context-and-clarify.md +0 -27
- package/dist/builtin/subagents/prompts/parallel-cleanup.md +0 -61
- package/dist/builtin/subagents/prompts/parallel-context-build.md +0 -43
- package/dist/builtin/subagents/prompts/parallel-handoff-plan.md +0 -47
- package/dist/builtin/subagents/prompts/parallel-research.md +0 -34
- package/dist/builtin/subagents/prompts/parallel-review.md +0 -47
- package/dist/builtin/subagents/prompts/review-loop.md +0 -39
- package/dist/builtin/subagents/src/extension/doctor.ts +0 -188
- package/dist/builtin/subagents/src/runs/inprocess/attempt-handles.ts +0 -104
- /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 === "
|
|
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
|
-
|
|
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
|
|
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", "
|
|
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'
|
|
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
|
|
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
|
-
-
|
|
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 === "
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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" | "
|
|
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
|
|
215
|
-
|
|
216
|
-
|
|
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
|
-
? "
|
|
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
|
+
}
|