@bastani/atomic 0.9.15-alpha.1 → 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 +29 -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/CHANGELOG.md +8 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +32 -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 +26 -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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.16-alpha.1] - 2026-08-23
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Foreground child decisions, structured interviews, and `intercom.ask` calls resolved to their launching parent now pause the retained child and return the question plus ordered attachments through the parent `subagent` call instead of deadlocking behind Intercom reply delivery. Real typed foreground children receive exact broker authorization for `contact_supervisor`, including successful non-blocking progress delivery. Bare run-ID resume preserves each paused child's session, cwd, Intercom group, execution settings, canonical index, worktree, and dirty changes while rebuilding control and detach callbacks; a sibling completed at the ask boundary stays terminal without blocking paused siblings. Queued work remains unlaunched and unauthorized, worktree diff capture and cleanup wait for terminal resume, and completed children remain non-resumable ([#2589](https://github.com/bastani-inc/atomic/issues/2589)).
|
|
10
|
+
- Coalesced same-turn sibling `subagent` execution calls into one indexed parallel run instead of rejecting every call after the first. Live result, progress, control, and artifact updates and final results stay route-local without sibling data, while the TUI redraws the shared run as one aggregate parallel widget. Solitary, sequential, management, and true-overlap behavior remains unchanged ([#2588](https://github.com/bastani-inc/atomic/issues/2588)).
|
|
11
|
+
|
|
12
|
+
## [0.9.15] - 2026-08-21
|
|
13
|
+
|
|
14
|
+
Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.
|
|
15
|
+
|
|
16
|
+
### Breaking Changes
|
|
17
|
+
|
|
18
|
+
- Subagent delegation is now fixed at one level. `WorkflowStageOrchestrationContext.constraints` no longer has the required nesting-depth field and is now `{ disableWorkflowTool: true }`; `SubagentChildPolicy` no longer has the optional inherited delegation limit and keeps `depth` alone. SDK callers that construct stage orchestration contexts must drop the removed field.
|
|
19
|
+
|
|
20
|
+
### Changed
|
|
21
|
+
|
|
22
|
+
- Vendored `@earendil-works/pi-ai` into the Atomic monorepo as `@bastani/pi-ai` and switched first-party imports, loader aliases, shrinkwrap, and tagged release publishing to the workspace package. Legacy extensions that import `@earendil-works/pi-ai` still resolve through the loader.
|
|
23
|
+
- `@bastani/pi-ai` now refreshes its models.dev catalog at package build instead of shipping the frozen v0.84.2 snapshot.
|
|
24
|
+
- Moved `COPILOT_GITHUB_TOKEN` env-token host routing into `@bastani/pi-ai/providers/github-copilot-env`, preserving `models.json` endpoint overrides ([#2522](https://github.com/bastani-inc/atomic/issues/2522)).
|
|
25
|
+
- SQLite resource selectors now use `node:sqlite` only. The `bun:sqlite` fallback was removed as Atomic's Bun floor moved to 1.4.0; Node ≥ 22.13 and Bun ≥ 1.4.0 provide `node:sqlite` without a flag.
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
|
|
29
|
+
- Fixed fine-grained PATs supplied through `COPILOT_GITHUB_TOKEN` failing with `Personal Access Tokens are not supported for this endpoint`. Raw tokens now send `Copilot-Integration-Id: copilot-developer-cli`, exchanged OAuth tokens retain their behavior, and explicit provider/model header overrides keep precedence ([#2522](https://github.com/bastani-inc/atomic/issues/2522)).
|
|
30
|
+
- Fixed workflow stages hanging after GitHub Copilot stream decompression failures. Provider stream stalls now settle as transient transport errors and can retry or fail over; the new `streamDeadlineMs` setting bounds idle gaps and supports duration strings or disabling the deadline ([#2553](https://github.com/bastani-inc/atomic/issues/2553)).
|
|
31
|
+
- Extension widgets now remount once after the interactive host clears or disposes them, preserve in-place updates, and discard stale isolated-engine mounts before reopening ([#2556](https://github.com/bastani-inc/atomic/issues/2556)).
|
|
32
|
+
- Ported unreleased upstream pi-ai fixes for Copilot throttling, provider usage and reasoning metadata, Bedrock headers and reasoning, Anthropic fallback pricing, Azure tool choice, model catalogs, xAI Responses routing, stream request options, User-Agent headers, and OpenAI reasoning replay. Cerebras now defaults to `gpt-oss-120b`, and the unused OpenTelemetry dependency was removed.
|
|
33
|
+
|
|
5
34
|
## [0.9.15-alpha.1] - 2026-08-21
|
|
6
35
|
|
|
7
36
|
### Breaking Changes
|
|
@@ -4,6 +4,16 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.16-alpha.1] - 2026-08-23
|
|
8
|
+
|
|
9
|
+
### Breaking Changes
|
|
10
|
+
|
|
11
|
+
- Parent-targeted blocking asks no longer pause a child for `subagent` resume. They end the child and return a fresh-subagent `[TASK_CONTEXT]` handoff; migrate supervisors to launch a new child with the answer ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
|
|
15
|
+
- `contact_supervisor` decisions/interviews and child-to-parent `intercom.ask` now preserve the original question, ordered duplicate attachments, and agent identity in the fresh-start handoff before broker send or reply-waiter admission ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
|
|
16
|
+
|
|
7
17
|
## [0.9.13] - 2026-08-13
|
|
8
18
|
|
|
9
19
|
Cumulative release of the `0.9.13-alpha.1` – `0.9.13-alpha.3` prereleases. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease sections below.
|
|
@@ -134,7 +134,7 @@ When a blocking `intercom.ask` targets a workflow stage that has already complet
|
|
|
134
134
|
|
|
135
135
|
When a blocking ask reaches a sibling workflow stage during an active model/tool turn, the target reserves it synchronously in the open stage generation before any asynchronous foreground-owner detach handshake. Queue insertion waits inside that reservation, and stage finalization drains it before publishing the terminal snapshot. This prevents a structured-output or other terminal tool call from overtaking a mid-turn ask. If destination-side admission genuinely cannot complete, the asker receives an exact-thread actionable error instead of consuming the full 10-minute timeout.
|
|
136
136
|
|
|
137
|
-
For delegated children, queued messages and terminal lifecycle notices are ordered per child. Intercom claims the terminal child
|
|
137
|
+
For delegated children, queued messages and terminal lifecycle notices are ordered per child. Intercom claims the terminal child's pre-terminal ordinary entries in FIFO order and atomically admits that prelude together with the interrupted, completed, or failed notice. A process-local companion bridge covers lazily loaded extensions whose event buses are distinct, while exact terminal-identity deduplication prevents double admission even when the successful terminal dispatch has no queued prelude. Failed dispatches remain retryable, and each terminal child identity is admitted only once. Other children's entries remain independently queued, messages are not discarded, terminal admission does not wait for a separate model turn, and correlated ask replies still bypass unrelated queued sends.
|
|
138
138
|
|
|
139
139
|
## Workflow: Planner-Worker Coordination
|
|
140
140
|
|
|
@@ -244,21 +244,21 @@ This workflow uses Atomic's in-process subagent admission. When the runtime admi
|
|
|
244
244
|
|
|
245
245
|
`contact_supervisor` is registered from the typed admission record. The record binds the supervisor target, canonical child identity, child index, session name, and broker-issued capability to the child session; these values are not inherited from environment variables. If the parent does not grant supervisor coordination, the session falls back to the regular `intercom` tool.
|
|
246
246
|
|
|
247
|
-
|
|
247
|
+
Parent-targeted decisions, interviews, and `intercom.ask` make the current child terminal for continuation. The parent receives the original question, ordered attachments, agent identity, and a dynamic `[TASK_CONTEXT]` handoff for a fresh child with a new run identity. Ordinary Intercom detach for sends, progress updates, and non-parent asks remains separate.
|
|
248
248
|
|
|
249
249
|
### Three Reasons
|
|
250
250
|
|
|
251
251
|
| Reason | Behavior | Use When |
|
|
252
252
|
|--------|----------|----------|
|
|
253
|
-
| `need_decision` |
|
|
254
|
-
| `interview_request` |
|
|
253
|
+
| `need_decision` | In a claimed foreground run, ends the child and returns a fresh-child handoff; otherwise uses the normal ask fallback | The subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision |
|
|
254
|
+
| `interview_request` | In a claimed foreground run, ends the child and returns structured questions in a fresh-child handoff | The subagent needs multiple machine-readable answers from the supervisor in one exchange |
|
|
255
255
|
| `progress_update` | Fire-and-forget update to the supervisor | Meaningful progress or unexpected discoveries that change the plan |
|
|
256
256
|
|
|
257
|
-
Do not use `contact_supervisor` for routine completion handoffs. Return the final subagent result normally
|
|
257
|
+
Do not use `contact_supervisor` for routine completion handoffs. Return the final subagent result normally.
|
|
258
258
|
|
|
259
259
|
Cross-group delivery uses a dedicated broker protocol. Ordinary raw `send` frames always remain group-isolated and are rejected if they include a forged `channel: "supervisor"` marker. A child can cross groups only after its broker-issued capability has bound its registered socket to the exact supervisor. The broker adds the `supervisor` channel marker to validated inbound traffic so parent relays can distinguish it. Replies cross back only when `replyTo` matches a recorded supervisor message in the exact reverse direction; fabricated thread IDs do not bypass isolation.
|
|
260
260
|
|
|
261
|
-
During a foreground subagent run,
|
|
261
|
+
During a foreground subagent run, parent-targeted decisions, interviews, and asks are claimed before broker delivery or reply-waiter admission. The current child ends and its parent tool call receives the fresh-start handoff. Parallel claims interrupt active siblings, prevent queued tasks from launching, and retain no sibling set for later bare-run-ID continuation. Sends, progress updates, and asks to other peers retain the exact-child probe/commit detach path and ordinary Intercom delivery behavior.
|
|
262
262
|
|
|
263
263
|
### Example: Blocked Subagent Asks for Guidance
|
|
264
264
|
|
|
@@ -267,7 +267,7 @@ contact_supervisor({
|
|
|
267
267
|
reason: "need_decision",
|
|
268
268
|
message: "The auth service returns 403 instead of 401 for expired tokens. Should I treat 403 as a re-auth trigger or a hard failure?"
|
|
269
269
|
})
|
|
270
|
-
// →
|
|
270
|
+
// → Parent receives a [TASK_CONTEXT] handoff and launches a fresh child with the answer.
|
|
271
271
|
```
|
|
272
272
|
|
|
273
273
|
### Example: Structured Supervisor Interview
|
|
@@ -284,7 +284,7 @@ contact_supervisor({
|
|
|
284
284
|
]
|
|
285
285
|
}
|
|
286
286
|
})
|
|
287
|
-
// →
|
|
287
|
+
// → Parent includes the structured supervisor answer in a fresh child's task.
|
|
288
288
|
```
|
|
289
289
|
|
|
290
290
|
### Example: Progress Update
|
|
@@ -4,6 +4,7 @@ import { Type } from "typebox";
|
|
|
4
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
5
|
import type { IntercomClient } from "./broker/client.js";
|
|
6
6
|
import type { ReplyWait, ReplyWaitAdmission } from "./reply-waiter.ts";
|
|
7
|
+
import { requestParentAskHandoff } from "./parent-ask-handoff.js";
|
|
7
8
|
import { renderContactSupervisorResult } from "./result-renderers.js";
|
|
8
9
|
import {
|
|
9
10
|
type ChildOrchestratorMetadata,
|
|
@@ -42,18 +43,18 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
|
|
|
42
43
|
pi.registerTool({
|
|
43
44
|
name: "contact_supervisor",
|
|
44
45
|
label: "Contact Supervisor",
|
|
45
|
-
description: "Subagent-only tool for contacting the supervisor agent that delegated this task.
|
|
46
|
-
promptSnippet: "Subagent-only:
|
|
46
|
+
description: "Subagent-only tool for contacting the supervisor agent that delegated this task. In a live foreground child, need_decision and interview_request end the child and return a fresh-subagent handoff to the supervisor; fallback Intercom delivery waits for a reply when no foreground owner claims the request. progress_update is fire-and-forget. Do not use for routine completion handoffs.",
|
|
47
|
+
promptSnippet: "Subagent-only: yield decisions or structured interviews to the supervisor for a fresh-child follow-up, or send meaningful plan-changing progress updates.",
|
|
47
48
|
promptGuidelines: [
|
|
48
|
-
"Use contact_supervisor with reason='need_decision' when a subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision
|
|
49
|
-
"Use contact_supervisor with reason='interview_request' when the child needs multiple structured answers
|
|
49
|
+
"Use contact_supervisor with reason='need_decision' when a subagent is blocked, uncertain, needs approval, or faces a product/API/scope decision. A claimed foreground request ends this child; do not wait for a reply.",
|
|
50
|
+
"Use contact_supervisor with reason='interview_request' when the child needs multiple structured answers. A claimed foreground request ends this child; the supervisor starts a fresh child with the answers.",
|
|
50
51
|
"Use contact_supervisor with reason='progress_update' only for meaningful progress or unexpected discoveries that change the plan.",
|
|
51
52
|
"Do not use contact_supervisor for routine completion handoffs; return the final subagent result normally.",
|
|
52
53
|
],
|
|
53
54
|
parameters: Type.Object({
|
|
54
55
|
reason: Type.String({
|
|
55
56
|
enum: ["need_decision", "progress_update", "interview_request"],
|
|
56
|
-
description: "Contact reason: 'need_decision'
|
|
57
|
+
description: "Contact reason: 'need_decision' and 'interview_request' yield a live foreground child for a fresh follow-up; 'progress_update' sends a non-blocking update",
|
|
57
58
|
}),
|
|
58
59
|
message: Type.Optional(Type.String({
|
|
59
60
|
description: "Decision request, optional interview note, or meaningful progress update for the supervisor",
|
|
@@ -79,7 +80,7 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
|
|
|
79
80
|
details: { error: true },
|
|
80
81
|
};
|
|
81
82
|
}
|
|
82
|
-
if (
|
|
83
|
+
if (reason === "progress_update" && typeof params.message !== "string") {
|
|
83
84
|
return {
|
|
84
85
|
content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
|
|
85
86
|
isError: true,
|
|
@@ -97,36 +98,71 @@ export function registerContactSupervisorTool(pi: ExtensionAPI, deps: ContactSup
|
|
|
97
98
|
};
|
|
98
99
|
}
|
|
99
100
|
const supervisorInterview = interviewValidation?.ok === true ? interviewValidation.interview : undefined;
|
|
101
|
+
const metadata = getMetadata();
|
|
102
|
+
if (reason === "need_decision" && typeof params.message !== "string" && !metadata) {
|
|
103
|
+
return {
|
|
104
|
+
content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
|
|
105
|
+
isError: true,
|
|
106
|
+
details: { error: true },
|
|
107
|
+
};
|
|
108
|
+
}
|
|
100
109
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
if (!metadata) {
|
|
111
|
+
return {
|
|
112
|
+
content: [{ type: "text", text: "Supervisor contact is unavailable for this session" }],
|
|
113
|
+
isError: true,
|
|
114
|
+
details: { error: true },
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
if (signal?.aborted) {
|
|
118
|
+
return {
|
|
119
|
+
content: [{ type: "text", text: "Cancelled" }],
|
|
120
|
+
isError: true,
|
|
121
|
+
details: { error: true },
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (
|
|
125
|
+
reason !== "progress_update" &&
|
|
126
|
+
requestParentAskHandoff(pi.events, metadata, {
|
|
127
|
+
kind: reason === "interview_request" ? "interview" : "decision",
|
|
128
|
+
question: typeof params.message === "string" ? params.message : "",
|
|
129
|
+
...(supervisorInterview ? { interview: supervisorInterview } : {}),
|
|
130
|
+
})
|
|
131
|
+
) {
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
|
|
134
|
+
isError: false,
|
|
135
|
+
details: { yielded: true },
|
|
136
|
+
};
|
|
137
|
+
}
|
|
111
138
|
|
|
112
|
-
|
|
139
|
+
if (reason === "need_decision" && typeof params.message !== "string") {
|
|
140
|
+
return {
|
|
141
|
+
content: [{ type: "text", text: `Missing 'message' parameter for reason '${reason}'.` }],
|
|
142
|
+
isError: true,
|
|
143
|
+
details: { error: true },
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
let connectedClient: IntercomClient;
|
|
147
|
+
try {
|
|
148
|
+
connectedClient = await ensureConnected("tool");
|
|
149
|
+
} catch (error) {
|
|
150
|
+
return {
|
|
151
|
+
content: [{ type: "text", text: `Intercom not connected: ${getErrorMessage(error)}` }],
|
|
152
|
+
isError: true,
|
|
153
|
+
details: { error: true },
|
|
154
|
+
};
|
|
155
|
+
}
|
|
113
156
|
|
|
114
|
-
|
|
115
|
-
return {
|
|
116
|
-
content: [{ type: "text", text: "Cancelled" }],
|
|
117
|
-
isError: true,
|
|
118
|
-
details: { error: true },
|
|
119
|
-
};
|
|
120
|
-
}
|
|
157
|
+
syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
121
158
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
159
|
+
if (signal?.aborted) {
|
|
160
|
+
return {
|
|
161
|
+
content: [{ type: "text", text: "Cancelled" }],
|
|
162
|
+
isError: true,
|
|
163
|
+
details: { error: true },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
130
166
|
let sendTo: string;
|
|
131
167
|
if (connectedClient.supervisorSessionId || metadata.supervisor) {
|
|
132
168
|
sendTo = connectedClient.supervisorSessionId ?? metadata.supervisor!.supervisorSessionId;
|
|
@@ -518,6 +518,7 @@ export default function piIntercomExtension(pi: ExtensionAPI, testOverrides: Int
|
|
|
518
518
|
hasReplyWaiter: () => replyWaiters.has(),
|
|
519
519
|
});
|
|
520
520
|
registerIntercomTool(pi, {
|
|
521
|
+
childOrchestratorMetadata: currentChildOrchestratorMetadata,
|
|
521
522
|
ensureConnected,
|
|
522
523
|
syncPresenceIdentity,
|
|
523
524
|
beginReplyWait: (from, replyTo, signal) => replyWaiters.begin(from, replyTo, signal),
|
|
@@ -230,22 +230,28 @@ export default function intercom(pi: ExtensionAPI, options: LightweightIntercomO
|
|
|
230
230
|
return promise;
|
|
231
231
|
}
|
|
232
232
|
let typedContactSupervisorRegistered = hasSubagentIntercomEnv();
|
|
233
|
+
const activateTypedContactSupervisor = (): void => {
|
|
234
|
+
const activeTools = pi.getActiveTools();
|
|
235
|
+
if (!activeTools.includes("contact_supervisor")) pi.setActiveTools([...activeTools, "contact_supervisor"]);
|
|
236
|
+
};
|
|
233
237
|
const registerTypedContactSupervisor = (): void => {
|
|
234
|
-
if (typedContactSupervisorRegistered)
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
238
|
+
if (!typedContactSupervisorRegistered) {
|
|
239
|
+
typedContactSupervisorRegistered = true;
|
|
240
|
+
pi.registerTool({
|
|
241
|
+
name: "contact_supervisor",
|
|
242
|
+
label: "Contact Supervisor",
|
|
243
|
+
description: "Subagent-only tool for contacting the supervisor agent that delegated this task.",
|
|
244
|
+
promptSnippet: "Subagent-only: contact the supervisor for decisions, interviews, or meaningful updates.",
|
|
245
|
+
parameters: Type.Object({
|
|
246
|
+
reason: Type.String({ enum: ["need_decision", "progress_update", "interview_request"] }),
|
|
247
|
+
message: Type.Optional(Type.String()),
|
|
248
|
+
interview: Type.Optional(Type.Unknown()),
|
|
249
|
+
}),
|
|
250
|
+
execute: (...args) => executeHeavyTool(loadHeavy, "contact_supervisor", args),
|
|
251
|
+
renderResult: (...args) => renderHeavyToolResult(loadedHeavy?.heavy ?? null, "contact_supervisor", args),
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
activateTypedContactSupervisor();
|
|
249
255
|
};
|
|
250
256
|
pi.on("session_start", async (event, ctx) => {
|
|
251
257
|
const typedIdentity = ctx.subagentPolicy?.intercom;
|
|
@@ -3,9 +3,11 @@ import { randomUUID } from "crypto";
|
|
|
3
3
|
import { Type } from "typebox";
|
|
4
4
|
import { Text } from "@earendil-works/pi-tui";
|
|
5
5
|
import type { IntercomClient } from "./broker/client.js";
|
|
6
|
+
import { requestParentAskHandoff } from "./parent-ask-handoff.js";
|
|
6
7
|
import type { ReplyWait, ReplyWaitAdmission } from "./reply-waiter.ts";
|
|
7
8
|
import { renderIntercomResult } from "./result-renderers.js";
|
|
8
9
|
import {
|
|
10
|
+
type ChildOrchestratorMetadata,
|
|
9
11
|
formatAttachments,
|
|
10
12
|
formatSessionListRow,
|
|
11
13
|
getErrorMessage,
|
|
@@ -17,6 +19,7 @@ import { resolveSessionTargetId } from "./session-target.js";
|
|
|
17
19
|
import { normalizeGroup, validateRuntimeGroup } from "./group.js";
|
|
18
20
|
|
|
19
21
|
interface IntercomToolDeps {
|
|
22
|
+
childOrchestratorMetadata?: ChildOrchestratorMetadata | null | (() => ChildOrchestratorMetadata | null);
|
|
20
23
|
ensureConnected(reason: "tool"): Promise<IntercomClient>;
|
|
21
24
|
syncPresenceIdentity(sessionId: string): void;
|
|
22
25
|
resolveSessionTarget?(activeClient: IntercomClient, nameOrId: string): Promise<string | null>;
|
|
@@ -36,8 +39,11 @@ interface IntercomToolDeps {
|
|
|
36
39
|
}
|
|
37
40
|
|
|
38
41
|
export function registerIntercomTool(pi: ExtensionAPI, deps: IntercomToolDeps): void {
|
|
39
|
-
const { ensureConnected, syncPresenceIdentity, beginReplyWait, hasReplyWaiter } = deps;
|
|
42
|
+
const { childOrchestratorMetadata, ensureConnected, syncPresenceIdentity, beginReplyWait, hasReplyWaiter } = deps;
|
|
40
43
|
const resolveTarget = deps.resolveSessionTarget ?? resolveSessionTargetId;
|
|
44
|
+
const getMetadata = typeof childOrchestratorMetadata === "function"
|
|
45
|
+
? childOrchestratorMetadata
|
|
46
|
+
: () => childOrchestratorMetadata ?? null;
|
|
41
47
|
const activeReplyTracker = (): ReplyTracker =>
|
|
42
48
|
typeof deps.replyTracker === "function" ? deps.replyTracker() : deps.replyTracker;
|
|
43
49
|
pi.registerTool({
|
|
@@ -306,7 +312,7 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
|
|
|
306
312
|
}
|
|
307
313
|
|
|
308
314
|
case "ask": {
|
|
309
|
-
if (!to
|
|
315
|
+
if (!to) {
|
|
310
316
|
return {
|
|
311
317
|
content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }],
|
|
312
318
|
isError: true,
|
|
@@ -314,14 +320,6 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
|
|
|
314
320
|
};
|
|
315
321
|
}
|
|
316
322
|
|
|
317
|
-
if (hasReplyWaiter()) {
|
|
318
|
-
return {
|
|
319
|
-
content: [{ type: "text", text: "Already waiting for a reply" }],
|
|
320
|
-
isError: true,
|
|
321
|
-
details: { error: true },
|
|
322
|
-
};
|
|
323
|
-
}
|
|
324
|
-
|
|
325
323
|
if (_signal?.aborted) {
|
|
326
324
|
return {
|
|
327
325
|
content: [{ type: "text", text: "Cancelled" }],
|
|
@@ -332,6 +330,36 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
|
|
|
332
330
|
let wait: ReplyWait | null = null;
|
|
333
331
|
|
|
334
332
|
try {
|
|
333
|
+
const metadata = getMetadata();
|
|
334
|
+
const currentSupervisorId = connectedClient.supervisorSessionId ?? undefined;
|
|
335
|
+
const metadataSupervisorId = metadata?.supervisor?.supervisorSessionId;
|
|
336
|
+
const directParentTarget = Boolean(
|
|
337
|
+
metadata &&
|
|
338
|
+
(to === metadata.orchestratorTarget ||
|
|
339
|
+
to === currentSupervisorId ||
|
|
340
|
+
to === metadataSupervisorId),
|
|
341
|
+
);
|
|
342
|
+
const claimParentAsk = (resolvedTargetId: string): boolean =>
|
|
343
|
+
Boolean(
|
|
344
|
+
metadata &&
|
|
345
|
+
requestParentAskHandoff(pi.events, metadata, {
|
|
346
|
+
kind: "intercom",
|
|
347
|
+
question: typeof message === "string" ? message : "",
|
|
348
|
+
attachments,
|
|
349
|
+
resolvedTargetId,
|
|
350
|
+
}),
|
|
351
|
+
);
|
|
352
|
+
if (
|
|
353
|
+
directParentTarget &&
|
|
354
|
+
claimParentAsk(currentSupervisorId ?? metadataSupervisorId ?? to)
|
|
355
|
+
) {
|
|
356
|
+
return {
|
|
357
|
+
content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
|
|
358
|
+
isError: false,
|
|
359
|
+
details: { yielded: true },
|
|
360
|
+
};
|
|
361
|
+
}
|
|
362
|
+
|
|
335
363
|
const sendTo = await resolveTarget(connectedClient, to) ?? to;
|
|
336
364
|
if (_signal?.aborted) {
|
|
337
365
|
return {
|
|
@@ -347,6 +375,34 @@ does not grant cross-group access: contact_supervisor is the only cross-group pa
|
|
|
347
375
|
details: { error: true },
|
|
348
376
|
};
|
|
349
377
|
}
|
|
378
|
+
if (metadata && !directParentTarget) {
|
|
379
|
+
const authoritativeParent = [currentSupervisorId, metadataSupervisorId].find(
|
|
380
|
+
(candidate) => candidate === sendTo,
|
|
381
|
+
);
|
|
382
|
+
const resolvedParent =
|
|
383
|
+
authoritativeParent ?? await resolveTarget(connectedClient, metadata.orchestratorTarget);
|
|
384
|
+
if (resolvedParent !== null && resolvedParent === sendTo && claimParentAsk(sendTo)) {
|
|
385
|
+
return {
|
|
386
|
+
content: [{ type: "text", text: "Parent ask claimed; this child is ending for a fresh subagent start." }],
|
|
387
|
+
isError: false,
|
|
388
|
+
details: { yielded: true },
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (!message) {
|
|
393
|
+
return {
|
|
394
|
+
content: [{ type: "text", text: "Missing 'to' or 'message' parameter" }],
|
|
395
|
+
isError: true,
|
|
396
|
+
details: { error: true },
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (hasReplyWaiter()) {
|
|
400
|
+
return {
|
|
401
|
+
content: [{ type: "text", text: "Already waiting for a reply" }],
|
|
402
|
+
isError: true,
|
|
403
|
+
details: { error: true },
|
|
404
|
+
};
|
|
405
|
+
}
|
|
350
406
|
const questionId = randomUUID();
|
|
351
407
|
const admission = beginReplyWait(sendTo, questionId, _signal);
|
|
352
408
|
if (!admission.ok) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.16-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isStaleExtensionContextError, type ExtensionAPI } from "@bastani/atomic";
|
|
2
|
+
import type {
|
|
3
|
+
ChildOrchestratorMetadata,
|
|
4
|
+
SupervisorInterviewRequest,
|
|
5
|
+
} from "./intercom-utils.js";
|
|
6
|
+
import type { Attachment } from "./types.js";
|
|
7
|
+
|
|
8
|
+
export const PARENT_ASK_HANDOFF_REQUEST_EVENT = "subagent:parent-ask-handoff-request";
|
|
9
|
+
|
|
10
|
+
const PROCESS_PARENT_ASK_CLAIMS = Symbol.for("atomic/subagents/parent-ask-claims@1");
|
|
11
|
+
|
|
12
|
+
type ProcessParentAskClaimHandler = (payload: unknown) => void;
|
|
13
|
+
|
|
14
|
+
interface ProcessParentAskClaimRegistry {
|
|
15
|
+
handlers: Set<ProcessParentAskClaimHandler>;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function processClaimRegistry(): ProcessParentAskClaimRegistry | undefined {
|
|
19
|
+
const slots = globalThis as typeof globalThis & Record<symbol, ProcessParentAskClaimRegistry | undefined>;
|
|
20
|
+
return slots[PROCESS_PARENT_ASK_CLAIMS];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export type ParentAskKind = "decision" | "interview" | "intercom";
|
|
24
|
+
|
|
25
|
+
export interface ParentAskHandoffRequest {
|
|
26
|
+
runId: string;
|
|
27
|
+
index: number;
|
|
28
|
+
agent: string;
|
|
29
|
+
childIntercomTarget: string;
|
|
30
|
+
orchestratorTarget: string;
|
|
31
|
+
kind: ParentAskKind;
|
|
32
|
+
question: string;
|
|
33
|
+
attachments?: Attachment[];
|
|
34
|
+
interview?: SupervisorInterviewRequest;
|
|
35
|
+
resolvedTargetId?: string;
|
|
36
|
+
taskContext?: string;
|
|
37
|
+
claimed: boolean;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function requestParentAskHandoff(
|
|
41
|
+
events: ExtensionAPI["events"] | undefined,
|
|
42
|
+
metadata: ChildOrchestratorMetadata,
|
|
43
|
+
input: Pick<ParentAskHandoffRequest, "kind" | "question" | "attachments" | "interview" | "resolvedTargetId">,
|
|
44
|
+
): boolean {
|
|
45
|
+
const index = Number(metadata.index);
|
|
46
|
+
if (!events || !Number.isInteger(index) || index < 0 || !metadata.sessionName) return false;
|
|
47
|
+
const request: ParentAskHandoffRequest = {
|
|
48
|
+
runId: metadata.runId,
|
|
49
|
+
index,
|
|
50
|
+
agent: metadata.agent,
|
|
51
|
+
childIntercomTarget: metadata.sessionName,
|
|
52
|
+
orchestratorTarget: metadata.orchestratorTarget,
|
|
53
|
+
kind: input.kind,
|
|
54
|
+
question: input.question,
|
|
55
|
+
...(input.attachments ? { attachments: input.attachments } : {}),
|
|
56
|
+
...(input.interview ? { interview: input.interview } : {}),
|
|
57
|
+
...(input.resolvedTargetId ? { resolvedTargetId: input.resolvedTargetId } : {}),
|
|
58
|
+
claimed: false,
|
|
59
|
+
};
|
|
60
|
+
try {
|
|
61
|
+
events.emit(PARENT_ASK_HANDOFF_REQUEST_EVENT, request);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
if (!isStaleExtensionContextError(error)) throw error;
|
|
64
|
+
}
|
|
65
|
+
if (!request.claimed) {
|
|
66
|
+
for (const handler of [...(processClaimRegistry()?.handlers ?? [])]) {
|
|
67
|
+
handler(request);
|
|
68
|
+
if (request.claimed) break;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return request.claimed;
|
|
72
|
+
}
|
|
@@ -7,6 +7,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.15] - 2026-08-21
|
|
11
|
+
|
|
12
|
+
Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
|
|
16
|
+
- Switched the optional peer from `@earendil-works/pi-ai` to `@bastani/pi-ai`.
|
|
17
|
+
|
|
10
18
|
## [0.9.15-alpha.1] - 2026-08-21
|
|
11
19
|
|
|
12
20
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.16-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -2,6 +2,38 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.16-alpha.1] - 2026-08-23
|
|
6
|
+
|
|
7
|
+
### Breaking Changes
|
|
8
|
+
|
|
9
|
+
- Removed the public `subagent({ action: "resume" })` action and its `index`/`message` inputs. Completed, interrupted, and parent-question children are terminal; migrate follow-ups to a fresh `subagent({ agent, task })` launch with explicit `[TASK_CONTEXT]` and expect a new run identity ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Parent-targeted `contact_supervisor` and Intercom asks now end the child and return the verbatim question, ordered attachments, agent identity, and a dynamic fresh-child handoff. Parallel asks no longer retain sibling sets for bare-run-ID continuation ([#2604](https://github.com/bastani-inc/atomic/issues/2604)).
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
- Coalesced same-turn sibling `subagent` execution calls into one indexed parallel run instead of rejecting every call after the first. Live result, progress, control, and artifact updates and final results stay route-local without sibling data, while the TUI redraws the shared run as one aggregate parallel widget. Solitary, sequential, management, and true-overlap behavior remains unchanged ([#2588](https://github.com/bastani-inc/atomic/issues/2588)).
|
|
17
|
+
|
|
18
|
+
## [0.9.15] - 2026-08-21
|
|
19
|
+
|
|
20
|
+
Cumulative release of the `0.9.15-alpha.1` prerelease. The summary below covers the user-visible outcome of that work; the per-change detail remains in the prerelease section below.
|
|
21
|
+
|
|
22
|
+
### Breaking Changes
|
|
23
|
+
|
|
24
|
+
- Subagent delegation is now fixed at one level. A top-level chat or workflow stage may launch subagents, but a subagent child may not launch, resume, or interrupt another child. Read-only management actions remain available, and the executor plus Rust admission boundary both enforce the rule.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- Removed the extension config and agent frontmatter options for delegation depth. Stale keys remain parseable and are ignored; agent-management rewrites strip the old frontmatter field.
|
|
29
|
+
- Removed inherited per-child delegation limits and the unreachable multi-level nested-run transport, registry, recursive status, and result payload fields. Direct-child status and results remain unchanged, and startup still cleans route directories left by older versions.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Switched the optional peer from `@earendil-works/pi-ai` to `@bastani/pi-ai`.
|
|
34
|
+
- Updated the fanout-child boundary prompt to reflect that child delegation is unavailable.
|
|
35
|
+
- Replaced built-in `gpt-5.6-luna:max` primary and fallback pins with `gpt-5.6-sol`, using `:medium` for worker and analysis agents and `:low` for locator and pattern agents.
|
|
36
|
+
|
|
5
37
|
## [0.9.15-alpha.1] - 2026-08-21
|
|
6
38
|
|
|
7
39
|
### Breaking Changes
|