@bastani/atomic 0.9.15 → 0.9.16-alpha.1
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 +7 -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 +13 -0
- package/dist/builtin/subagents/README.md +23 -12
- package/dist/builtin/subagents/agents/worker.md +2 -2
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/subagents/skills/subagent/SKILL.md +10 -17
- 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 -2
- package/dist/builtin/subagents/src/extension/tool-rendering.ts +53 -0
- package/dist/builtin/subagents/src/intercom/intercom-bridge.ts +1 -1
- 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 -220
- 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/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 +10 -0
- package/dist/builtin/workflows/README.md +1 -1
- package/dist/builtin/workflows/package.json +1 -1
- 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/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/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/docs/intercom.md +28 -26
- package/docs/subagents.md +15 -11
- package/docs/workflows.md +27 -10
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
- 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
|
@@ -161,7 +161,7 @@ Workflow invocations receive a stable, non-`default` Intercom group automaticall
|
|
|
161
161
|
|
|
162
162
|
## Where running subagents show up
|
|
163
163
|
|
|
164
|
-
Foreground runs stream progress in the conversation while they run. Parallel calls keep their grouped task shape in progress and results, and status/control actions can inspect or interrupt
|
|
164
|
+
Foreground runs stream progress in the conversation while they run. Parallel calls keep their grouped task shape in progress and results, and status/control actions can inspect or interrupt live foreground children.
|
|
165
165
|
|
|
166
166
|
You can ask naturally:
|
|
167
167
|
|
|
@@ -193,7 +193,7 @@ Use the optional prompt shortcuts below when you want the pattern to be repeatab
|
|
|
193
193
|
|
|
194
194
|
Packaged `worker` defaults to forked context when a launch omits `context`; every other builtin runs fresh. Pass `context: "fresh"` when you intentionally want a fresh `worker` run.
|
|
195
195
|
|
|
196
|
-
Child-safety boundaries are enforced at runtime by typed admission policy. In-process child sessions load bundled extensions through normal discovery. The `subagent` tool may therefore be registered when the child's active tool selection permits it, including the default no-allowlist case; an explicit allowlist may omit it. Tool presence does not grant fanout: fanout is authorized only when the resolved builtin `tools` list includes `subagent`. Typed admission policy lets a non-fanout child use only `list`, `get`, `status`, and `doctor`; delegation
|
|
196
|
+
Child-safety boundaries are enforced at runtime by typed admission policy. In-process child sessions load bundled extensions through normal discovery. The `subagent` tool may therefore be registered when the child's active tool selection permits it, including the default no-allowlist case; an explicit allowlist may omit it. Tool presence does not grant fanout: fanout is authorized only when the resolved builtin `tools` list includes `subagent`. Typed admission policy lets a non-fanout child use only `list`, `get`, `status`, and `doctor`; delegation and `interrupt` receive the fanout refusal. A management-restricted child is also refused `create`, `update`, and `delete`. The bundled `pi-subagents` skill remains parent-only and is stripped from child prompts, including fanout-authorized children. No admitted child may delegate or control another child: launches and `interrupt` are refused for every child regardless of its fanout authorization. Children receive boundary instructions that they are not the parent orchestrator and must complete their assigned task directly. Forked child context filtering also removes parent-only subagent artifacts (including old hidden orchestration-instruction messages, slash/status/control messages, and prior parent `subagent` tool-call/tool-result history) while preserving ordinary prose and unrelated tool calls/results.
|
|
197
197
|
|
|
198
198
|
## Optional shortcuts
|
|
199
199
|
|
|
@@ -233,12 +233,15 @@ Ask codebase-analyzer to review this plan. If it sees a decision I need to make,
|
|
|
233
233
|
|
|
234
234
|
The child can use one dedicated coordination tool:
|
|
235
235
|
|
|
236
|
-
- `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for blocking
|
|
236
|
+
- `contact_supervisor`: the child contacts the parent/supervisor session that delegated the task. Use `reason: "need_decision"` for a blocking decision, `reason: "interview_request"` for structured questions, and `reason: "progress_update"` for a short non-blocking update when a discovery changes the plan. Do not ask for clarification when the only conflict is review-only/no-edit versus progress-writing or artifact-writing instructions; no-edit wins.
|
|
237
237
|
|
|
238
|
-
Child-side routine completion handoffs are still not expected. With the
|
|
239
|
-
When the companion is enabled and available, the bridge gives eligible children deterministic Intercom identities and coordination tools without connecting them automatically. Parent and child connections remain tool-driven: if a child may need live coordination, the parent model should invoke `intercom({ action: "status" })` before launch, and the child connects when it invokes `contact_supervisor` or `intercom`. Foreground launches and management-only actions do not force Intercom loading or broker startup.
|
|
238
|
+
Child-side routine completion handoffs are still not expected. With the Intercom bridge active, a blocking decision or interview from the exact foreground child ends at the source before broker send or reply-waiter admission. The parent `subagent` call returns the verbatim question, ordered attachments with duplicates preserved, agent identity, terminal run ID, and a dynamic `[TASK_CONTEXT]` handoff. `intercom.ask` does the same only when its resolved target is the launching parent. The parent answers by launching a fresh child with a new run identity and the supervisor answer in its task.
|
|
240
239
|
|
|
241
|
-
For
|
|
240
|
+
For parallel runs, the claim interrupts every active sibling and prevents queued work from starting or requesting authorization. The sibling set, sessions, and worktrees are not retained for continuation. Any follow-up starts fresh SINGLE or PARALLEL children explicitly. `intercom.send`, progress updates, and asks to siblings or other peers retain the exact-child probe/commit detach and ordinary Intercom delivery paths.
|
|
241
|
+
|
|
242
|
+
With the Intercom bridge active, the parent may load and connect its Intercom runtime before initial child execution to issue the exact child's broker capability. The child connection remains tool-driven. A claimed `contact_supervisor` decision or interview still yields before child send or reply-waiter admission; `intercom.ask` connects the child to resolve both targets.
|
|
243
|
+
|
|
244
|
+
Parent-side Atomic still sends grouped completion results through Intercom: one grouped message per foreground parent `subagent` run and one per detached child completion. Intercom-confirmed delivery returns a compact receipt with artifact/session paths; without that confirmation, the normal full output is preserved. Grouped messages include child Intercom targets and full child summaries.
|
|
242
245
|
|
|
243
246
|
If a child appears stalled, needs-attention notices can show up in the parent session with useful next actions, such as checking `subagent({ action: "status" })`, interrupting the run, or nudging the child.
|
|
244
247
|
|
|
@@ -537,6 +540,16 @@ These are the parameters the LLM passes when it calls the `subagent` tool. Most
|
|
|
537
540
|
], worktree: true }
|
|
538
541
|
```
|
|
539
542
|
|
|
543
|
+
### Sibling execution calls
|
|
544
|
+
|
|
545
|
+
If one assistant response emits several sibling execution-mode `subagent` calls, Atomic collects the synchronous burst before any child starts and runs it through one indexed parallel set. Each original tool call receives only its own children in source order, and its live result, progress, control, and artifact updates are projected to that same route without sibling data. The TUI redraws the shared run as one aggregate parallel widget rather than retaining one widget per original call. A solitary call keeps its original mode, sequential awaited calls stay independent, and management actions bypass collection. A call that arrives after a child has started still gets the existing in-progress rejection. Prefer one explicit `{ tasks: [...] }` call when you intend parallel work; burst collection handles model-emitted sibling calls.
|
|
546
|
+
|
|
547
|
+
In a collected burst, a call's top-level `agent` task comes first, followed by its `tasks` array. Duplicates and array order are preserved, and `count` expands in place. The task cap applies after all calls are flattened and counts are expanded, with the same hard maximum of 50. Each call-level `cwd` selects that call's agent-discovery scope and child base directory. A task-level `cwd` stays relative to that base and affects child execution, not agent discovery. This per-origin rule applies only to collected sibling calls; an ordinary explicit `{ tasks: [...] }` call keeps one discovery scope from its top-level `cwd`. Per-call and per-task `group` values also remain attached to their children, and a task-level group wins.
|
|
548
|
+
|
|
549
|
+
For a collected `worktree: true` burst, every call-level `cwd` must resolve to the same path. That common path becomes the shared worktree root; differing origins reject the burst before launch, and any task-level `cwd` must still resolve to that root. Each projected caller result keeps shared worktree diff text and terminal control guidance while its child results and standard child-output sections remain route-local.
|
|
550
|
+
|
|
551
|
+
One parallel run also needs one value for each run-wide option. Sibling calls must agree on `concurrency`, `worktree`, `context`, `share`, `control`, `sessionDir`, `maxOutput`, `artifacts`, `includeProgress`, and `agentScope`. If any value differs, Atomic rejects the full burst before launch and names the field instead of mixing settings.
|
|
552
|
+
|
|
540
553
|
### Management actions
|
|
541
554
|
|
|
542
555
|
Agent definitions are not loaded into context by default. Management actions let the LLM discover, inspect, create, update, and delete agents at runtime.
|
|
@@ -580,7 +593,7 @@ Agent definitions are not loaded into context by default. Management actions let
|
|
|
580
593
|
|-------|------|---------|-------------|
|
|
581
594
|
| `agent` | string | - | Agent name for single mode, or target for management actions. |
|
|
582
595
|
| `task` | string | - | Task string for single mode. |
|
|
583
|
-
| `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`,
|
|
596
|
+
| `action` | string | - | `list`, `get`, `create`, `update`, `delete`, `status`, `interrupt`, or `doctor`. |
|
|
584
597
|
| `config` | object/string | - | Agent config for create/update. |
|
|
585
598
|
| `output` | `string \| false` | agent default | Override single-agent output file. |
|
|
586
599
|
| `outputMode` | `"inline" \| "file-only"` | `inline` | Return saved output inline or as a concise saved-file reference. `file-only` requires an `output` path. |
|
|
@@ -611,12 +624,10 @@ Status and control actions:
|
|
|
611
624
|
subagent({ action: "status" })
|
|
612
625
|
subagent({ action: "status", id: "<run-id>" })
|
|
613
626
|
subagent({ action: "interrupt", id: "<run-id>" })
|
|
614
|
-
subagent({ action: "resume", id: "<run-id>", message: "follow-up question" })
|
|
615
|
-
subagent({ action: "resume", id: "<run-id>", index: 1, message: "follow-up for child 2" })
|
|
616
627
|
subagent({ action: "doctor" })
|
|
617
628
|
```
|
|
618
629
|
|
|
619
|
-
|
|
630
|
+
Completed, interrupted, and parent-question children are terminal for continuation. A prior run ID cannot revive a child or parallel sibling set. Start a fresh subagent call with an explicit context handoff for follow-up work.
|
|
620
631
|
|
|
621
632
|
## Worktree isolation
|
|
622
633
|
|
|
@@ -640,7 +651,7 @@ Requirements:
|
|
|
640
651
|
- the main repository's Husky or populated `.git/hooks` directory is shared through `core.hooksPath`
|
|
641
652
|
- gitignored files matched by `.worktreeinclude` are copied into the worktree
|
|
642
653
|
|
|
643
|
-
After a worktree parallel step
|
|
654
|
+
After a worktree parallel step reaches any terminal result, per-agent diff stats are appended to the output and full patch files are written to artifacts. A parent-directed ask terminally ends the active set, captures its staged and unstaged changes in the same handoff result, and then cleans up every worktree and `worktree-*` branch after a brief Git lock-release wait. The same cleanup runs after post-creation setup failures.
|
|
644
655
|
|
|
645
656
|
## Configuration
|
|
646
657
|
|
|
@@ -772,7 +783,7 @@ This is disabled by default. Session data may contain source code, paths, enviro
|
|
|
772
783
|
|
|
773
784
|
## Delegation boundary
|
|
774
785
|
|
|
775
|
-
Delegation is exactly one level deep, and nothing configures it. A top-level session — main chat or a workflow stage — may call `subagent`. A session that was itself admitted as a subagent child may not: every launch
|
|
786
|
+
Delegation is exactly one level deep, and nothing configures it. A top-level session — main chat or a workflow stage — may call `subagent`. A session that was itself admitted as a subagent child may not: every launch and `interrupt` it attempts is refused with guidance to complete its assigned task directly. The observing actions `list`, `get`, `status`, and `doctor` stay available to a child.
|
|
776
787
|
|
|
777
788
|
There is no configuration option, agent frontmatter field, or tool parameter for the delegation level. The rule is enforced twice: the subagent executor refuses a child before any run starts, and the Rust `SubagentControl` admission door refuses a child deeper than the single permitted level. Admitted depth is typed admission state and is not inherited through an environment variable.
|
|
778
789
|
|
|
@@ -20,9 +20,9 @@ Treat an approved handoff or execution plan as the contract. Inspect inherited c
|
|
|
20
20
|
|
|
21
21
|
## Decision and escalation contract
|
|
22
22
|
|
|
23
|
-
Do not silently make a new product, architecture, or scope decision. When implementation reveals an unapproved decision required to continue safely,
|
|
23
|
+
Do not silently make a new product, architecture, or scope decision. When implementation reveals an unapproved decision required to continue safely, use the live coordination route supplied at runtime. Use `contact_supervisor` with `reason: "need_decision"`; a claimed request ends this child and gives the supervisor a fresh-subagent handoff, so do not wait for a reply in this run. Use `reason: "progress_update"` only for a concise, non-blocking update when helpful or explicitly requested. Fall back to `intercom` only when `contact_supervisor` is unavailable.
|
|
24
24
|
|
|
25
|
-
Do not end with a question requiring the supervisor to choose before work can continue. Do not send routine completion handoffs; return the normal task result when coordination is unnecessary. If you sent a
|
|
25
|
+
Do not end with a question requiring the supervisor to choose before work can continue. Do not send routine completion handoffs; return the normal task result when coordination is unnecessary. If you sent a progress update through `contact_supervisor`, keep it short and still provide the full structured result.
|
|
26
26
|
|
|
27
27
|
## Work and validation
|
|
28
28
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.16-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with parallel execution. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -251,24 +251,17 @@ Avoid duplicate output paths in parallel tasks. Concurrent children should not w
|
|
|
251
251
|
Concurrent writers conflict. `code-simplifier` and `debugger` change files. Do not run two writers in parallel against the same worktree unless you isolate them with `worktree: true`.
|
|
252
252
|
|
|
253
253
|
|
|
254
|
-
### Foreground execution and
|
|
254
|
+
### Foreground execution and fresh follow-ups
|
|
255
255
|
|
|
256
256
|
All subagent execution runs in the foreground and returns its result to the parent call. Parallel tasks may still run concurrently within one foreground invocation, and forked context still creates branched child sessions.
|
|
257
257
|
|
|
258
|
-
|
|
258
|
+
Completed, interrupted, and parent-question children are terminal for continuation. Do not address a prior child or sibling set by run ID. Start follow-up work with the normal launch form and an explicit handoff:
|
|
259
259
|
|
|
260
260
|
```typescript
|
|
261
|
-
subagent({
|
|
262
|
-
subagent({ action: "resume", id: "run-id", index: 1, message: "Continue reviewer 2." })
|
|
261
|
+
subagent({ agent: "worker", task: "[TASK_CONTEXT] Continue with this supervisor answer: ..." })
|
|
263
262
|
```
|
|
264
263
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
- If a child is still running and reachable, `resume` sends the follow-up through its intercom route when available.
|
|
268
|
-
- Completed foreground single and parallel runs can be revived by `index` while their run metadata remains in extension state.
|
|
269
|
-
- A revived child starts a new in-process attempt from its persisted session file.
|
|
270
|
-
- Multi-child runs require `index` unless only one child is selectable.
|
|
271
|
-
- If the chosen child has no persisted `.jsonl` session file, resume fails and reports that directly.
|
|
264
|
+
A parent-ask handoff supplies the original question, ordered attachments, previous agent identity, and dynamic task context. The fresh launch receives a new run identity.
|
|
272
265
|
|
|
273
266
|
Use diagnostics when setup or child startup looks wrong:
|
|
274
267
|
|
|
@@ -280,7 +273,7 @@ Humans can use `/subagents-doctor` for the same read-only report. It checks runt
|
|
|
280
273
|
|
|
281
274
|
### Subagent control
|
|
282
275
|
|
|
283
|
-
Subagent control is the runtime visibility and intervention layer for delegated runs.
|
|
276
|
+
Subagent control is the runtime visibility and intervention layer for delegated runs. Lifecycle status distinguishes queued and running children from terminal completed, failed, or interrupted results. Activity reporting is factual: it tracks the last observed activity time and the current tool when known. It does not pretend to know that a child is truly stuck.
|
|
284
277
|
|
|
285
278
|
Default behavior is intentionally conservative. When no activity has been observed past the configured threshold, the run emits a `needs_attention` control event. Foreground runs push this as a `subagent:control-event` event, and notification-worthy control events are inserted into the visible transcript so both the user and the parent agent can see them, with a proactive hint plus concrete `nudge`, `status`, and `interrupt` options. Visible notifications fire once per child run and attention state.
|
|
286
279
|
|
|
@@ -296,7 +289,7 @@ Pass `id` when targeting a specific controllable run:
|
|
|
296
289
|
subagent({ action: "interrupt", id: "abc123" })
|
|
297
290
|
```
|
|
298
291
|
|
|
299
|
-
A soft interrupt cancels the current child turn and
|
|
292
|
+
A soft interrupt cancels the current child turn and terminally records the child as interrupted. It does not mean the delegated task succeeded. Decide the next explicit action: launch a fresh child with the relevant task context, replace the task, ask the user, or stop the workflow.
|
|
300
293
|
|
|
301
294
|
Per-run control thresholds can be overridden when a task legitimately runs without observable output for longer than usual:
|
|
302
295
|
|
|
@@ -339,7 +332,7 @@ subagent({
|
|
|
339
332
|
|
|
340
333
|
Atomic subagents work without intercom. When Atomic's bundled intercom companion or upstream `pi-intercom` is installed and enabled, the bridge can give eligible child agents a private coordination tool back to the parent session without connecting either session automatically. If a child may need live coordination, invoke `intercom({ action: "status" })` in the parent before launching it; the child connects when it first invokes `contact_supervisor` or `intercom`.
|
|
341
334
|
|
|
342
|
-
The builtin `debugger` and `worker` agents declare `intercom` and `contact_supervisor`. With an active bridge route, they can send progress or
|
|
335
|
+
The builtin `debugger` and `worker` agents declare `intercom` and `contact_supervisor`. With an active bridge route, they can send progress or terminally hand a parent-directed question back to the supervisor. Other builtin specialists finish their pass and return without live coordination; use a custom agent with bridge tools when another role needs that ability.
|
|
343
336
|
|
|
344
337
|
Custom agents that do have the bridge tool can ask the parent for a decision:
|
|
345
338
|
|
|
@@ -461,8 +454,8 @@ If a prompt-template extension is installed, additional user prompt templates ca
|
|
|
461
454
|
|
|
462
455
|
- **Forking requires a persisted parent session.** If the current session does not have a persisted session file, forked runs fail.
|
|
463
456
|
- **Forked runs inherit parent history.** They are branched threads, not fresh filtered contexts. Use fresh context for adversarial review unless the user explicitly asks for forked context.
|
|
464
|
-
- **Delegation is one level deep and not configurable.** A subagent cannot call `subagent`: every launch
|
|
465
|
-
- **Attention signals are not lifecycle state.** `needs_attention` means no activity has been observed past the configured threshold. `
|
|
457
|
+
- **Delegation is one level deep and not configurable.** A subagent cannot call `subagent`: every launch and `interrupt` from inside a child is refused. Only `list`, `get`, `status`, and `doctor` stay available to a child.
|
|
458
|
+
- **Attention signals are not lifecycle state.** `needs_attention` means no activity has been observed past the configured threshold. `interrupted` means the child turn ended before completion; it is terminal for continuation and is not the same as `failed`.
|
|
466
459
|
- **Builtin coordination varies by agent.** `debugger` and `worker` declare `intercom` and `contact_supervisor`; the other builtin specialists do not. For agents without bridge tools, decide the task up front or use a custom agent when mid-run coordination is required.
|
|
467
460
|
- **Intercom asks are blocking.** A session can only maintain one pending outbound ask wait state at a time.
|
|
468
461
|
- **Keep conversational authority clear.** Advisory specialists should not silently become second decision-makers.
|
|
@@ -550,7 +543,7 @@ For complex or risky changes, increase review and validation fanout when user in
|
|
|
550
543
|
|
|
551
544
|
For very large work, split into serial milestones instead of launching a swarm of writers. Each milestone gets one writer, a validation contract, fresh-context review, a fix pass, and parent approval before the next milestone starts. Use parallel subagents inside a milestone for read-only context, research, and review only.
|
|
552
545
|
|
|
553
|
-
Keep orchestration authority in the parent session. Child subagents cannot launch more subagents or run their own orchestration loops: delegation is one level deep and nothing configures it. This skill is parent-only and is stripped from every child prompt. A child may still have the `subagent` extension tool registered, because bundled extensions load through normal discovery; registration is not authority. Typed admission policy lets a child use only `list`, `get`, `status`, and `doctor`, and refuses delegation
|
|
546
|
+
Keep orchestration authority in the parent session. Child subagents cannot launch more subagents or run their own orchestration loops: delegation is one level deep and nothing configures it. This skill is parent-only and is stripped from every child prompt. A child may still have the `subagent` extension tool registered, because bundled extensions load through normal discovery; registration is not authority. Typed admission policy lets a child use only `list`, `get`, `status`, and `doctor`, and refuses delegation and `interrupt`. Spawned children also do not receive parent-only status/control/slash messages or prior parent `subagent` tool-call/tool-result artifacts, and child context filtering strips old hidden orchestration-instruction messages when they appear in inherited history. Every child also receives a boundary instruction that says the parent owns orchestration, that the `subagent` tool refuses every launch and `interrupt` from inside a subagent, and that writer children must call real edit/write tools instead of printing pseudo tool calls. Pass children concrete role-specific work instead.
|
|
554
547
|
|
|
555
548
|
1. Clarify only when needed. Use existing context first; gather missing code or research context selectively, then ask only unresolved questions that materially affect scope, completion criteria, constraints, or non-goals.
|
|
556
549
|
2. Define the validation contract. State completion expectations before implementation: expected behavior, checks to run, user flows to exercise, and evidence required in the writer handoff. For UI, CLI, integration, or workflow changes, include at least one validator angle that uses the product the way a user would rather than only reading code.
|
|
@@ -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,6 @@ 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
|
|
17
|
+
• { action: "interrupt", id?: "..." } - soft-interrupt the current child turn; the child is terminal for continuation, so follow-up work starts a fresh launch
|
|
19
18
|
DIAGNOSTICS:
|
|
20
19
|
• { action: "doctor" } - read-only report for runtime paths, discovery, sessions, and intercom`;
|
|
@@ -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>" })
|
|
@@ -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 =
|