@bastani/atomic 0.9.44-alpha.1 → 0.9.44-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/builtin/intercom/CHANGELOG.md +17 -0
- package/dist/builtin/intercom/broker/client-message-validation.ts +6 -0
- package/dist/builtin/intercom/broker/client.ts +4 -0
- package/dist/builtin/intercom/broker/framing.ts +18 -1
- package/dist/builtin/intercom/broker/send-signature.ts +4 -1
- package/dist/builtin/intercom/index.bundle.mjs +101 -18
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/intercom/skills/intercom/SKILL.md +1 -0
- package/dist/builtin/intercom/types.ts +8 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/index.bundle.mjs +2510 -1308
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +13 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +363 -37
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +363 -37
- package/dist/builtin/workflows/src/index.js +363 -37
- package/dist/builtin/workflows/src/local-worker-entry.mjs +363 -37
- package/dist/core/agent-session-custom-message-commit.d.ts.map +1 -1
- package/dist/core/agent-session-custom-message-commit.js +59 -0
- package/dist/core/agent-session-custom-message-commit.js.map +1 -1
- package/dist/core/agent-session-methods.d.ts +1 -0
- 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-prompt.d.ts +4 -0
- package/dist/core/agent-session-prompt.d.ts.map +1 -1
- package/dist/core/agent-session-prompt.js +10 -2
- package/dist/core/agent-session-prompt.js.map +1 -1
- package/dist/core/agent-session.d.ts +2 -0
- package/dist/core/agent-session.d.ts.map +1 -1
- package/dist/core/agent-session.js +9 -0
- package/dist/core/agent-session.js.map +1 -1
- package/dist/core/builtin-packages.d.ts.map +1 -1
- package/dist/core/builtin-packages.js +13 -13
- package/dist/core/builtin-packages.js.map +1 -1
- package/docs/intercom/operations.md +1 -1
- package/docs/intercom.md +11 -1
- package/docs/subagents.md +9 -1
- package/examples/extensions/gondolin/package-lock.json +3 -3
- package/npm-shrinkwrap.json +67 -51
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.44-alpha.3] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
Chatty workflow swarms stay fast and their journals stay small, and Atomic picks up a round of dependency security fixes.
|
|
8
|
+
|
|
9
|
+
- **Pattern sends at fan-out scale:** stages that message each other through `workflow:<rootRunId>/...` patterns no longer saturate the workflow host or time out their sends, so every send in a 150-child fan-out is delivered ([#3931](https://github.com/bastani-inc/atomic/issues/3931), [#3932](https://github.com/bastani-inc/atomic/pull/3932))
|
|
10
|
+
- **Journals that grow with the work, not the message count:** journal events record only what changed in a run's pending messages instead of the whole list ([#3929](https://github.com/bastani-inc/atomic/issues/3929), [#3930](https://github.com/bastani-inc/atomic/pull/3930))
|
|
11
|
+
- **Security updates:** `undici` 8.12.3, `zod` 4.6.7, `fast-uri` 3.1.9, `body-parser` 2.4.0 and `tokio` 1.54.1 close the published advisories against them ([#3935](https://github.com/bastani-inc/atomic/pull/3935))
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- Updated `undici`, which Atomic uses for HTTP requests, to 8.12.3. A server can no longer crash Atomic by sending more than five `Content-Encoding` values ([GHSA-xjj6-f33q-929c](https://github.com/advisories/GHSA-xjj6-f33q-929c)), Server-Sent Events streams can no longer grow parser memory without bound ([GHSA-4qv8-2p9j-2685](https://github.com/advisories/GHSA-4qv8-2p9j-2685)), and Subresource Integrity checks no longer skip uppercase algorithm names ([GHSA-f72v-6w45-q472](https://github.com/advisories/GHSA-f72v-6w45-q472)).
|
|
16
|
+
- Updated the MCP SDK dependencies in the published lockfile to `zod` 4.6.7 ([GHSA-mrhx-c7xc-m3w5](https://github.com/advisories/GHSA-mrhx-c7xc-m3w5)), `fast-uri` 3.1.9 ([GHSA-w74x-v6f9-j3xg](https://github.com/advisories/GHSA-w74x-v6f9-j3xg), [GHSA-wjr9-j795-jxcr](https://github.com/advisories/GHSA-wjr9-j795-jxcr), [GHSA-xhq7-xmqj-769h](https://github.com/advisories/GHSA-xhq7-xmqj-769h), [GHSA-333x-w4r6-6crp](https://github.com/advisories/GHSA-333x-w4r6-6crp)), and `body-parser` 2.4.0 ([GHSA-27r3-jxmh-p828](https://github.com/advisories/GHSA-27r3-jxmh-p828)).
|
|
17
|
+
- Updated the native module's `tokio` crate to 1.54.1, replacing the yanked 1.54.0 release and its `Interval` race ([GHSA-5wjr-9356-4wc8](https://github.com/advisories/GHSA-5wjr-9356-4wc8)).
|
|
18
|
+
- Updated the gondolin example extension's lockfile to `undici` 6.29.1, resolving [GHSA-xjj6-f33q-929c](https://github.com/advisories/GHSA-xjj6-f33q-929c) and [GHSA-4qv8-2p9j-2685](https://github.com/advisories/GHSA-4qv8-2p9j-2685) in its `@earendil-works/gondolin` dependency.
|
|
19
|
+
|
|
20
|
+
## [0.9.44-alpha.2] - 2026-10-24
|
|
21
|
+
|
|
22
|
+
Workflow swarms now message each other without cancelling each other's work, and stages, replies and session exits behave reliably under fan-out.
|
|
23
|
+
|
|
24
|
+
- **Messages no longer cancel a peer's work:** Intercom `send` and `ask` now default to `"steer"`, so a message reaches a busy subagent or workflow stage after its current tool calls finish; pass `delivery: "followUp"` to wait until it stops, or `"interrupt"` for the old cancelling behavior ([#3921](https://github.com/bastani-inc/atomic/issues/3921), [#3924](https://github.com/bastani-inc/atomic/pull/3924))
|
|
25
|
+
- **Replies reach the right sender:** an implicit Intercom `reply` answers only messages the agent has actually seen, and every ask delivered in the same turn can still be answered ([#3921](https://github.com/bastani-inc/atomic/issues/3921), [#3927](https://github.com/bastani-inc/atomic/pull/3927))
|
|
26
|
+
- **Workflow stages start reliably:** a message that reaches a stage before its own prompt becomes context for the first turn instead of failing the stage with `Agent is already processing` ([#3919](https://github.com/bastani-inc/atomic/issues/3919), [#3923](https://github.com/bastani-inc/atomic/pull/3923))
|
|
27
|
+
- **Smaller journals under broadcast:** sticky broadcasts to many stages record only what changed, so the workflow journal grows linearly instead of quadratically ([#3920](https://github.com/bastani-inc/atomic/issues/3920), [#3922](https://github.com/bastani-inc/atomic/pull/3922))
|
|
28
|
+
- **Clean exits and binary startup:** closing a session while background subagents finish no longer fails with `Cannot dispose a session with a queued protected reconciliation`, and the standalone binary starts the Intercom broker for pooled stages even when a build tree exists on the host ([#3925](https://github.com/bastani-inc/atomic/issues/3925), [#3926](https://github.com/bastani-inc/atomic/pull/3926), [#3917](https://github.com/bastani-inc/atomic/issues/3917), [#3918](https://github.com/bastani-inc/atomic/pull/3918))
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- The standalone binary resolves its builtin packages only from the `builtin/` directory beside the executable, so the Intercom broker starts for pooled workflow stages even when the build tree still exists on the host ([#3917](https://github.com/bastani-inc/atomic/issues/3917))
|
|
33
|
+
- Fixed workflow stages failing with `Agent is already processing. Specify streamingBehavior ('steer' or 'followUp') to queue the message.` when an Intercom message, such as a sibling's send or a sticky `workflow:<rootRunId>/**` broadcast, reached the stage after it went live but before the workflow sent the stage's own prompt. The message started a turn of its own and the stage prompt was rejected. Messages that arrive before the stage prompt now become context for the stage's first turn, and the stage prompt always starts that turn ([#3919](https://github.com/bastani-inc/atomic/issues/3919))
|
|
34
|
+
- Background subagent completions that arrive while the parent session is finishing a turn now always get their reply turn, so exiting or disposing a session saved to a file no longer fails with `Cannot dispose a session with a queued protected reconciliation` ([#3925](https://github.com/bastani-inc/atomic/issues/3925))
|
|
35
|
+
|
|
5
36
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
6
37
|
|
|
7
38
|
Large agent swarms and local workflow fan-outs now run without freezing the TUI or losing their Intercom routes, and background completion notices survive a restart.
|
|
@@ -4,6 +4,23 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.44-alpha.3] - 2026-10-25
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- Intercom `send` and `ask` to a workflow-stage target (`workflow:<rootRunId>/...`, including patterns) no longer fetch the whole session list before sending. The broker resolves those targets itself, so the extra request only added load, and when many stages sent at once some sends failed with `List sessions timeout` ([#3931](https://github.com/bastani-inc/atomic/issues/3931))
|
|
12
|
+
- Receiving a large Intercom message (for example a big workflow roster) no longer copies its data again for every chunk that arrives, which made large frames cost quadratic time ([#3931](https://github.com/bastani-inc/atomic/issues/3931))
|
|
13
|
+
|
|
14
|
+
## [0.9.44-alpha.2] - 2026-10-24
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Intercom `send` and `ask` take a `delivery` option, and the default is now `"steer"`: a message to a working subagent or live workflow stage arrives after the recipient's current tool calls finish instead of cancelling its work, so peers in a swarm no longer abort each other's long-running tool calls. `"followUp"` waits until the recipient finishes and stops. `"interrupt"` keeps the previous behavior of cancelling the recipient's current model call or tool call, and is now opt-in ([#3921](https://github.com/bastani-inc/atomic/issues/3921))
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- An implicit Intercom `reply` no longer answers a message the agent has not seen yet. Before, a queued message (for example a `"followUp"` ask that arrived while a tool ran) could become the reply target as soon as any new turn started, including the continuation after that tool call, so the reply reached the wrong sender or thread. A message now becomes the reply target only when it enters the agent's context, including when Intercom is loaded through its lazy entry point, and every ask that enters the context in the same turn (for example several `"followUp"` asks delivered together) can still be answered with an implicit `reply` after the first one is answered ([#3921](https://github.com/bastani-inc/atomic/issues/3921), follow-up to [#3924](https://github.com/bastani-inc/atomic/pull/3924))
|
|
23
|
+
|
|
7
24
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
8
25
|
|
|
9
26
|
### Changed
|
|
@@ -22,6 +22,12 @@ export function isMessage(value: unknown): value is Message {
|
|
|
22
22
|
if (typeof message.id !== "string" || typeof message.timestamp !== "number") return false;
|
|
23
23
|
if (message.replyTo !== undefined && typeof message.replyTo !== "string") return false;
|
|
24
24
|
if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean") return false;
|
|
25
|
+
if (
|
|
26
|
+
message.delivery !== undefined
|
|
27
|
+
&& message.delivery !== "steer"
|
|
28
|
+
&& message.delivery !== "followUp"
|
|
29
|
+
&& message.delivery !== "interrupt"
|
|
30
|
+
) return false;
|
|
25
31
|
if (message.replyError !== undefined && typeof message.replyError !== "string") return false;
|
|
26
32
|
if (message.source !== undefined && !isMessageSource(message.source)) return false;
|
|
27
33
|
if (typeof message.content !== "object" || message.content === null) return false;
|
|
@@ -18,6 +18,7 @@ import type {
|
|
|
18
18
|
WorkflowPossibleStageAnnouncement,
|
|
19
19
|
WorkflowRunParentAnnouncement,
|
|
20
20
|
PendingStageRouteRefusalCode,
|
|
21
|
+
IntercomDeliveryMode,
|
|
21
22
|
} from "../types.js";
|
|
22
23
|
import { buildSendSignature, PendingSendRegistry, PendingSendTimeoutError } from "./pending-send-registry.js";
|
|
23
24
|
import { readSubagentMessageSource } from "../source-ownership.js";
|
|
@@ -43,6 +44,8 @@ export interface SendOptions {
|
|
|
43
44
|
attachments?: Attachment[];
|
|
44
45
|
replyTo?: string;
|
|
45
46
|
expectsReply?: boolean;
|
|
47
|
+
/** How a busy recipient receives the message; absent means `steer`. */
|
|
48
|
+
delivery?: IntercomDeliveryMode;
|
|
46
49
|
replyError?: string;
|
|
47
50
|
messageId?: string;
|
|
48
51
|
/** Internal caller-issued target identity; transport still uses the `to` argument. */
|
|
@@ -1410,6 +1413,7 @@ export class IntercomClient extends EventEmitter {
|
|
|
1410
1413
|
timestamp: Date.now(),
|
|
1411
1414
|
replyTo: options.replyTo,
|
|
1412
1415
|
expectsReply: options.expectsReply,
|
|
1416
|
+
...(options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }),
|
|
1413
1417
|
replyError: options.replyError,
|
|
1414
1418
|
source: this._messageSource,
|
|
1415
1419
|
content: { text: options.text, attachments: options.attachments },
|
|
@@ -27,14 +27,31 @@ export function createMessageReader(
|
|
|
27
27
|
onError: (error: Error) => void,
|
|
28
28
|
) {
|
|
29
29
|
let buffer = Buffer.alloc(0);
|
|
30
|
+
// Chunks of a frame still arriving: joined once the frame is complete, not on every
|
|
31
|
+
// chunk, so a large frame costs linear rather than quadratic copying.
|
|
32
|
+
let pending: Buffer[] = [];
|
|
33
|
+
let pendingLength = 0;
|
|
34
|
+
let awaitedLength = 0;
|
|
30
35
|
|
|
31
36
|
return (data: Buffer) => {
|
|
32
|
-
|
|
37
|
+
if (pendingLength + data.length < awaitedLength) {
|
|
38
|
+
pending.push(data);
|
|
39
|
+
pendingLength += data.length;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
buffer = pending.length === 0 ? Buffer.concat([buffer, data]) : Buffer.concat([...pending, data]);
|
|
43
|
+
pending = [];
|
|
44
|
+
pendingLength = 0;
|
|
45
|
+
awaitedLength = 0;
|
|
33
46
|
|
|
34
47
|
while (buffer.length >= 4) {
|
|
35
48
|
const length = buffer.readUInt32BE(0);
|
|
36
49
|
|
|
37
50
|
if (buffer.length < 4 + length) {
|
|
51
|
+
pending = [buffer];
|
|
52
|
+
pendingLength = buffer.length;
|
|
53
|
+
awaitedLength = 4 + length;
|
|
54
|
+
buffer = Buffer.alloc(0);
|
|
38
55
|
break;
|
|
39
56
|
}
|
|
40
57
|
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import type { Attachment, Message } from "../types.js";
|
|
1
|
+
import type { Attachment, IntercomDeliveryMode, Message } from "../types.js";
|
|
2
2
|
|
|
3
3
|
export interface LogicalSendOptions {
|
|
4
4
|
text: string;
|
|
5
5
|
attachments?: Attachment[];
|
|
6
6
|
replyTo?: string;
|
|
7
7
|
expectsReply?: boolean;
|
|
8
|
+
delivery?: IntercomDeliveryMode;
|
|
8
9
|
replyError?: string;
|
|
9
10
|
}
|
|
10
11
|
|
|
@@ -28,6 +29,7 @@ export function buildSendSignature(to: string, options: LogicalSendOptions): str
|
|
|
28
29
|
replyTo: options.replyTo ?? null,
|
|
29
30
|
expectsReply: options.expectsReply ?? false,
|
|
30
31
|
replyError: options.replyError ?? null,
|
|
32
|
+
...(options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }),
|
|
31
33
|
});
|
|
32
34
|
}
|
|
33
35
|
|
|
@@ -37,6 +39,7 @@ export function buildMessageSendSignature(to: string, message: Message, senderId
|
|
|
37
39
|
attachments: message.content.attachments,
|
|
38
40
|
replyTo: message.replyTo,
|
|
39
41
|
expectsReply: message.expectsReply,
|
|
42
|
+
delivery: message.delivery,
|
|
40
43
|
replyError: message.replyError,
|
|
41
44
|
});
|
|
42
45
|
if (senderId === undefined && message.source === undefined) return logicalSignature;
|
|
@@ -364,6 +364,8 @@ function isMessage(value) {
|
|
|
364
364
|
return false;
|
|
365
365
|
if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean")
|
|
366
366
|
return false;
|
|
367
|
+
if (message.delivery !== undefined && message.delivery !== "steer" && message.delivery !== "followUp" && message.delivery !== "interrupt")
|
|
368
|
+
return false;
|
|
367
369
|
if (message.replyError !== undefined && typeof message.replyError !== "string")
|
|
368
370
|
return false;
|
|
369
371
|
if (message.source !== undefined && !isMessageSource(message.source))
|
|
@@ -563,11 +565,26 @@ function writeMessage(socket, msg, callback) {
|
|
|
563
565
|
}
|
|
564
566
|
function createMessageReader(onMessage, onError) {
|
|
565
567
|
let buffer = Buffer.alloc(0);
|
|
568
|
+
let pending = [];
|
|
569
|
+
let pendingLength = 0;
|
|
570
|
+
let awaitedLength = 0;
|
|
566
571
|
return (data) => {
|
|
567
|
-
|
|
572
|
+
if (pendingLength + data.length < awaitedLength) {
|
|
573
|
+
pending.push(data);
|
|
574
|
+
pendingLength += data.length;
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
buffer = pending.length === 0 ? Buffer.concat([buffer, data]) : Buffer.concat([...pending, data]);
|
|
578
|
+
pending = [];
|
|
579
|
+
pendingLength = 0;
|
|
580
|
+
awaitedLength = 0;
|
|
568
581
|
while (buffer.length >= 4) {
|
|
569
582
|
const length = buffer.readUInt32BE(0);
|
|
570
583
|
if (buffer.length < 4 + length) {
|
|
584
|
+
pending = [buffer];
|
|
585
|
+
pendingLength = buffer.length;
|
|
586
|
+
awaitedLength = 4 + length;
|
|
587
|
+
buffer = Buffer.alloc(0);
|
|
571
588
|
break;
|
|
572
589
|
}
|
|
573
590
|
const payload = buffer.subarray(4, 4 + length);
|
|
@@ -689,7 +706,8 @@ function buildSendSignature(to, options) {
|
|
|
689
706
|
attachments: canonicalizeAttachmentsForSendSignature(options.attachments) ?? [],
|
|
690
707
|
replyTo: options.replyTo ?? null,
|
|
691
708
|
expectsReply: options.expectsReply ?? false,
|
|
692
|
-
replyError: options.replyError ?? null
|
|
709
|
+
replyError: options.replyError ?? null,
|
|
710
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }
|
|
693
711
|
});
|
|
694
712
|
}
|
|
695
713
|
|
|
@@ -1990,6 +2008,7 @@ var init_client = __esm(() => {
|
|
|
1990
2008
|
timestamp: Date.now(),
|
|
1991
2009
|
replyTo: options.replyTo,
|
|
1992
2010
|
expectsReply: options.expectsReply,
|
|
2011
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery },
|
|
1993
2012
|
replyError: options.replyError,
|
|
1994
2013
|
source: this._messageSource,
|
|
1995
2014
|
content: { text: options.text, attachments: options.attachments }
|
|
@@ -2790,6 +2809,7 @@ class ReplyTracker {
|
|
|
2790
2809
|
pendingAsks = new Map;
|
|
2791
2810
|
pendingTurnContexts = [];
|
|
2792
2811
|
currentTurnContext = null;
|
|
2812
|
+
turnContextAdmitted = false;
|
|
2793
2813
|
constructor(askTimeoutMs = ASK_REPLY_TIMEOUT_MS) {
|
|
2794
2814
|
this.askTimeoutMs = askTimeoutMs;
|
|
2795
2815
|
}
|
|
@@ -2815,15 +2835,27 @@ class ReplyTracker {
|
|
|
2815
2835
|
}
|
|
2816
2836
|
beginTurn(now = Date.now()) {
|
|
2817
2837
|
this.pruneExpired(now);
|
|
2818
|
-
this.
|
|
2838
|
+
this.turnContextAdmitted = false;
|
|
2839
|
+
}
|
|
2840
|
+
admitTurnMessage(messageId) {
|
|
2841
|
+
if (this.currentTurnContext?.message.id === messageId) {
|
|
2842
|
+
this.turnContextAdmitted = true;
|
|
2843
|
+
return;
|
|
2844
|
+
}
|
|
2845
|
+
const index = this.pendingTurnContexts.findIndex((context) => context.message.id === messageId);
|
|
2846
|
+
if (index < 0)
|
|
2847
|
+
return;
|
|
2848
|
+
const [admitted] = this.pendingTurnContexts.splice(index, 1);
|
|
2849
|
+
if (this.turnContextAdmitted)
|
|
2850
|
+
return;
|
|
2851
|
+
this.currentTurnContext = admitted;
|
|
2852
|
+
this.turnContextAdmitted = true;
|
|
2819
2853
|
}
|
|
2820
2854
|
endTurn() {
|
|
2821
2855
|
this.currentTurnContext = null;
|
|
2822
2856
|
}
|
|
2823
2857
|
restoreTurnContext() {
|
|
2824
|
-
|
|
2825
|
-
this.pendingTurnContexts.unshift(this.currentTurnContext);
|
|
2826
|
-
this.currentTurnContext = null;
|
|
2858
|
+
this.turnContextAdmitted = false;
|
|
2827
2859
|
}
|
|
2828
2860
|
reset() {
|
|
2829
2861
|
this.pendingAsks.clear();
|
|
@@ -2872,10 +2904,11 @@ class ReplyTracker {
|
|
|
2872
2904
|
}
|
|
2873
2905
|
throw new Error(`No pending ask from "${options.to}"`);
|
|
2874
2906
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2907
|
+
const visible = pending.filter((context) => !this.pendingTurnContexts.some((queued) => queued.message.id === context.message.id));
|
|
2908
|
+
if (visible.length === 1) {
|
|
2909
|
+
return visible[0];
|
|
2877
2910
|
}
|
|
2878
|
-
if (
|
|
2911
|
+
if (visible.length === 0) {
|
|
2879
2912
|
throw new Error("No active intercom context to reply to");
|
|
2880
2913
|
}
|
|
2881
2914
|
throw new Error("Multiple pending asks — specify `to`");
|
|
@@ -4055,6 +4088,7 @@ function registerIntercomTool(pi, deps) {
|
|
|
4055
4088
|
const retryIdentities = new RetryIdentityReservations({ maxEntries: deps.retryIdentityMaxEntries });
|
|
4056
4089
|
const { childOrchestratorMetadata, ensureConnected, syncPresenceIdentity, beginReplyWait } = deps;
|
|
4057
4090
|
const resolveTarget = deps.resolveSessionTarget ?? resolveSessionTargetId;
|
|
4091
|
+
const resolveRecipient = async (client, target) => parseWorkflowStageTarget(target) === undefined ? await resolveTarget(client, target) ?? target : target;
|
|
4058
4092
|
const getMetadata = typeof childOrchestratorMetadata === "function" ? childOrchestratorMetadata : () => childOrchestratorMetadata ?? null;
|
|
4059
4093
|
const activeReplyTracker = () => typeof deps.replyTracker === "function" ? deps.replyTracker() : deps.replyTracker;
|
|
4060
4094
|
pi.registerTool({
|
|
@@ -4078,6 +4112,10 @@ A valid target outside the known set queues speculatively with a \`notInKnownSet
|
|
|
4078
4112
|
undeliverable at terminal only if never delivered. Use \`ask\` only for a live, reply-capable target.
|
|
4079
4113
|
Stage paths win over same-named owned subgroups for send/ask; \`list\` with \`group\` continues to
|
|
4080
4114
|
select the group.
|
|
4115
|
+
Choose \`delivery\` per send/ask by urgency. The default "steer" arrives after the recipient's current
|
|
4116
|
+
tool calls finish and never cancels its work. Use "interrupt" when it must stop now (an urgent correction
|
|
4117
|
+
or a stop order); it cancels the current model call or tool call. Use "followUp" when the message can wait
|
|
4118
|
+
until the recipient finishes (low priority, "after you're done"). An idle recipient starts a turn.
|
|
4081
4119
|
For send/ask/reply, Intercom retries recoverable disconnects internally up to three times with the same operation identity. Each new tool call is a new operation. If recovery ends with an unknown delivery outcome, do not repeat it automatically.
|
|
4082
4120
|
|
|
4083
4121
|
Usage:
|
|
@@ -4089,6 +4127,7 @@ Usage:
|
|
|
4089
4127
|
intercom({ action: "leave" }) → Return to your resolved home group
|
|
4090
4128
|
intercom({ action: "send", to: "session-name", message: "..." }) → Send message (shared group only)
|
|
4091
4129
|
intercom({ action: "ask", to: "session-name", message: "..." }) → Ask and wait for reply
|
|
4130
|
+
intercom({ action: "send", to: "session-name", message: "...", delivery: "interrupt" }) → Cancel the recipient's current work
|
|
4092
4131
|
intercom({ action: "reply", message: "..." }) → Reply to the active or exact pending ask
|
|
4093
4132
|
intercom({ action: "pending" }) → List unresolved inbound asks
|
|
4094
4133
|
intercom({ action: "status" }) → Show connection status and all your groups
|
|
@@ -4113,6 +4152,9 @@ one shared membership; contact_supervisor remains the only cross-group path.`,
|
|
|
4113
4152
|
content: Type2.String(),
|
|
4114
4153
|
language: Type2.Optional(Type2.String())
|
|
4115
4154
|
}))),
|
|
4155
|
+
delivery: Type2.Optional(Type2.Union([Type2.Literal("steer"), Type2.Literal("followUp"), Type2.Literal("interrupt")], {
|
|
4156
|
+
description: "For 'send'/'ask'; choose per message by urgency. 'steer' (default): arrives after the recipient's current tool calls, never cancelling work. 'interrupt': the recipient must stop now (urgent correction, stop order); cancels its current model call or tool call. 'followUp': can wait until the recipient finishes (low priority, after you're done)."
|
|
4157
|
+
})),
|
|
4116
4158
|
replyTo: Type2.Optional(Type2.String({
|
|
4117
4159
|
description: "Exact pending-ask message ID or unique 8-hex UUID prefix; disambiguates concurrent asks, including asks from one sender"
|
|
4118
4160
|
})),
|
|
@@ -4130,6 +4172,7 @@ one shared membership; contact_supervisor remains the only cross-group path.`,
|
|
|
4130
4172
|
}
|
|
4131
4173
|
const toolSessionId = ctx.sessionManager.getSessionId();
|
|
4132
4174
|
const { action, to, message, replyTo, group } = params;
|
|
4175
|
+
const deliveryOption = params.delivery === undefined || params.delivery === "steer" ? {} : { delivery: params.delivery };
|
|
4133
4176
|
const attachments = params.attachments?.map((attachment) => ({ ...attachment }));
|
|
4134
4177
|
const executeAttempt = async (retryToken, _signal) => {
|
|
4135
4178
|
if (retryToken !== undefined) {
|
|
@@ -4413,7 +4456,7 @@ ${message}${attachmentText}`);
|
|
|
4413
4456
|
details: { error: true, delivered: false, reason: goneChild }
|
|
4414
4457
|
};
|
|
4415
4458
|
}
|
|
4416
|
-
const sendTo = await
|
|
4459
|
+
const sendTo = await resolveRecipient(connectedClient, to);
|
|
4417
4460
|
if (_signal?.aborted) {
|
|
4418
4461
|
const retained = retainInconclusiveRetry(retryIdentities, retryIdentity);
|
|
4419
4462
|
retryIdentity = undefined;
|
|
@@ -4440,7 +4483,8 @@ ${message}${attachmentText}`);
|
|
|
4440
4483
|
logicalTarget: to,
|
|
4441
4484
|
text: message,
|
|
4442
4485
|
attachments,
|
|
4443
|
-
replyTo
|
|
4486
|
+
replyTo,
|
|
4487
|
+
...deliveryOption
|
|
4444
4488
|
});
|
|
4445
4489
|
if (result.queued === true) {
|
|
4446
4490
|
retryIdentities.release(retryIdentity);
|
|
@@ -4586,8 +4630,7 @@ ${message}${attachmentText}`);
|
|
|
4586
4630
|
details: { error: true, delivered: false, reason: goneChild }
|
|
4587
4631
|
};
|
|
4588
4632
|
}
|
|
4589
|
-
const
|
|
4590
|
-
const sendTo = listedTarget ?? to;
|
|
4633
|
+
const sendTo = await resolveRecipient(connectedClient, to);
|
|
4591
4634
|
if (_signal?.aborted) {
|
|
4592
4635
|
const retained = retryIdentity === undefined ? undefined : retainInconclusiveRetry(retryIdentities, retryIdentity);
|
|
4593
4636
|
retryIdentity = undefined;
|
|
@@ -4672,6 +4715,7 @@ ${message}${attachmentText}`);
|
|
|
4672
4715
|
attachments,
|
|
4673
4716
|
replyTo,
|
|
4674
4717
|
expectsReply: true,
|
|
4718
|
+
...deliveryOption,
|
|
4675
4719
|
onReplyTarget: wait.bindSender
|
|
4676
4720
|
});
|
|
4677
4721
|
if (sendResult.queued === true) {
|
|
@@ -5561,6 +5605,13 @@ function registerIntercomLifecycle(pi, deps) {
|
|
|
5561
5605
|
deps.syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
5562
5606
|
activeReplyTracker().beginTurn();
|
|
5563
5607
|
});
|
|
5608
|
+
pi.on("message_start", (event, ctx) => {
|
|
5609
|
+
if (!deps.getLiveContext(ctx))
|
|
5610
|
+
return;
|
|
5611
|
+
const id = admittedIntercomMessageId(event.message, ctx);
|
|
5612
|
+
if (id !== undefined)
|
|
5613
|
+
activeReplyTracker().admitTurnMessage(id);
|
|
5614
|
+
});
|
|
5564
5615
|
pi.on("model_select", (event, ctx) => {
|
|
5565
5616
|
if (!deps.getLiveContext(ctx))
|
|
5566
5617
|
return;
|
|
@@ -5575,6 +5626,25 @@ function registerIntercomLifecycle(pi, deps) {
|
|
|
5575
5626
|
}
|
|
5576
5627
|
});
|
|
5577
5628
|
}
|
|
5629
|
+
function intercomMessageId(customType, details) {
|
|
5630
|
+
if (customType !== "intercom_message" || details === null || typeof details !== "object")
|
|
5631
|
+
return;
|
|
5632
|
+
const id = details.message?.id;
|
|
5633
|
+
return typeof id === "string" ? id : undefined;
|
|
5634
|
+
}
|
|
5635
|
+
function admittedIntercomMessageId(message, ctx) {
|
|
5636
|
+
const custom = message;
|
|
5637
|
+
if (custom?.role !== "custom")
|
|
5638
|
+
return;
|
|
5639
|
+
if (custom.customType !== PROTECTED_RECONCILIATION_CUSTOM_TYPE)
|
|
5640
|
+
return intercomMessageId(custom.customType, custom.details);
|
|
5641
|
+
const intentEntryId = custom.details?.protectedReconciliationOf;
|
|
5642
|
+
if (typeof intentEntryId !== "string")
|
|
5643
|
+
return;
|
|
5644
|
+
const intent = ctx.sessionManager.getEntries().find((entry) => entry.id === intentEntryId);
|
|
5645
|
+
return intent?.type === "custom_message" ? intercomMessageId(intent.customType, intent.details) : undefined;
|
|
5646
|
+
}
|
|
5647
|
+
var PROTECTED_RECONCILIATION_CUSTOM_TYPE = "atomic:protected-streaming-reconciliation";
|
|
5578
5648
|
var init_lifecycle = __esm(() => {
|
|
5579
5649
|
init_diagnostics();
|
|
5580
5650
|
init_intercom_utils();
|
|
@@ -6654,8 +6724,9 @@ function createIncomingMessageSender(input) {
|
|
|
6654
6724
|
if (!input.canDeliver(generation)) {
|
|
6655
6725
|
return Promise.reject(new Error("Intercom session retired before inbound delivery"));
|
|
6656
6726
|
}
|
|
6657
|
-
const
|
|
6658
|
-
|
|
6727
|
+
const startsTurn = requested === "trigger" || requested === "triggerFollowUp" || requested === "interrupt";
|
|
6728
|
+
const delivery = startsTurn && !mayStartTurn(entry) ? "prelude" : requested;
|
|
6729
|
+
if ((delivery === "trigger" || delivery === "triggerFollowUp" || delivery === "interrupt") && trackReplyContext && maySupplyReplyContext(entry)) {
|
|
6659
6730
|
input.queueTurnContext(turnContext ?? { from: entry.from, message: entry.message, receivedAt: Date.now() });
|
|
6660
6731
|
}
|
|
6661
6732
|
const baseOptions = {
|
|
@@ -6663,7 +6734,7 @@ function createIncomingMessageSender(input) {
|
|
|
6663
6734
|
persistWhenStreaming: true,
|
|
6664
6735
|
...stageAdmissionBarrier ? { stageAdmissionBarrier } : {}
|
|
6665
6736
|
};
|
|
6666
|
-
const options = delivery === "interrupt" ? { ...baseOptions, triggerTurn: true, deliverAs: "interrupt" } : delivery === "trigger" ? { ...baseOptions, triggerTurn: true } : delivery === "followUp" ? { ...baseOptions, deliverAs: "followUp" } : baseOptions;
|
|
6737
|
+
const options = delivery === "interrupt" ? { ...baseOptions, triggerTurn: true, deliverAs: "interrupt" } : delivery === "trigger" ? { ...baseOptions, triggerTurn: true } : delivery === "triggerFollowUp" ? { ...baseOptions, triggerTurn: true, deliverAs: "followUp" } : delivery === "followUp" ? { ...baseOptions, deliverAs: "followUp" } : baseOptions;
|
|
6667
6738
|
return Promise.resolve(input.pi.sendMessage(buildIncomingCustomMessage(entry), options));
|
|
6668
6739
|
};
|
|
6669
6740
|
}
|
|
@@ -7902,8 +7973,9 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
7902
7973
|
});
|
|
7903
7974
|
const activeDelivery = admitActiveSessionInbound(liveContext, (admissionBarrier) => {
|
|
7904
7975
|
replyTracker.queueTurnContext(replyContext);
|
|
7976
|
+
const delivery = message.delivery === "interrupt" ? "interrupt" : message.delivery === "followUp" ? "triggerFollowUp" : "trigger";
|
|
7905
7977
|
return retryStableDelivery({
|
|
7906
|
-
deliver: () => sendIncomingMessage(entry,
|
|
7978
|
+
deliver: () => sendIncomingMessage(entry, delivery, messageGeneration, false, undefined, admissionBarrier),
|
|
7907
7979
|
isCurrent: () => Boolean(getLiveContext(liveContext, messageGeneration)) && liveContext.subagentPolicy?.executionEnded?.aborted !== true && liveContext.subagentPolicy?.messageAdmission?.isOpen() !== false
|
|
7908
7980
|
});
|
|
7909
7981
|
}, () => foregroundDetachHandoff.claim(from, message, messageGeneration, () => Boolean(getLiveContext(liveContext, messageGeneration))), release);
|
|
@@ -8959,7 +9031,8 @@ function createForwardedHandlerMap() {
|
|
|
8959
9031
|
agent_end: [],
|
|
8960
9032
|
tool_execution_start: [],
|
|
8961
9033
|
tool_execution_end: [],
|
|
8962
|
-
model_select: []
|
|
9034
|
+
model_select: [],
|
|
9035
|
+
message_start: []
|
|
8963
9036
|
};
|
|
8964
9037
|
}
|
|
8965
9038
|
function addHandler(captured, event, handler) {
|
|
@@ -8993,6 +9066,9 @@ function captureForwardedHandler(captured, event, handler) {
|
|
|
8993
9066
|
return;
|
|
8994
9067
|
case "model_select":
|
|
8995
9068
|
addHandler(captured, event, handler);
|
|
9069
|
+
return;
|
|
9070
|
+
case "message_start":
|
|
9071
|
+
addHandler(captured, event, handler);
|
|
8996
9072
|
}
|
|
8997
9073
|
}
|
|
8998
9074
|
function addEventHandler(captured, event, handler) {
|
|
@@ -9501,6 +9577,13 @@ function intercom(pi, options = {}) {
|
|
|
9501
9577
|
if (heavy)
|
|
9502
9578
|
await lifecycleForward.enqueue(() => dispatchHandlers(heavy, "agent_end", event, ctx));
|
|
9503
9579
|
});
|
|
9580
|
+
pi.on("message_start", async (event, ctx) => {
|
|
9581
|
+
if (activeLease.retired)
|
|
9582
|
+
return;
|
|
9583
|
+
const heavy = loadedHeavy?.heavy;
|
|
9584
|
+
if (heavy)
|
|
9585
|
+
await lifecycleForward.enqueue(() => dispatchHandlers(heavy, "message_start", event, ctx));
|
|
9586
|
+
});
|
|
9504
9587
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
9505
9588
|
if (activeLease.retired)
|
|
9506
9589
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.44-alpha.
|
|
3
|
+
"version": "0.9.44-alpha.3",
|
|
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": [
|
|
@@ -276,6 +276,7 @@ How a swarm works:
|
|
|
276
276
|
- Decide on evidence, not deference. A peer's approval, rejection, or claim is input to inspect, not a verdict to copy, and each agent still returns its own result.
|
|
277
277
|
- `contact_supervisor` is for supervisor decisions; explicit user approval gates and authorization for destructive or external actions still apply.
|
|
278
278
|
- Inside a workflow, `send` to a known pending sibling queues until it starts, and `ask` to a completed sibling that retains a valid conversation reopens it for a post-mortem turn. Named stage subgroups isolate their members from sibling subgroups.
|
|
279
|
+
- Choose `delivery` per `send`/`ask` by urgency. The default `"steer"` arrives after the recipient's current tool calls finish and never cancels its work. Use `"interrupt"` when it must stop now (an urgent correction or a stop order), and `"followUp"` when the message can wait until it finishes (low priority, "after you're done").
|
|
279
280
|
- Peer asks and one blocking supervisor request may wait concurrently in the same child; mutual asks work when both sides process inbound work.
|
|
280
281
|
- Scale with the task. For very complex work or requested heavy parallelism, hundreds or thousands of agents self-organize into teams (named groups, per-stage subagent sets, child workflows) and coordinate peer-to-peer like an organization. Groups have no member limit; team members reach their lead with `contact_supervisor`, and leads talk to each other in their shared group.
|
|
281
282
|
|
|
@@ -115,11 +115,19 @@ export interface BrokerStatusReport {
|
|
|
115
115
|
multiNode: BrokerMultiNodeStatus;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
+
export type IntercomDeliveryMode = "steer" | "followUp" | "interrupt";
|
|
119
|
+
|
|
118
120
|
export interface Message {
|
|
119
121
|
id: string;
|
|
120
122
|
timestamp: number;
|
|
121
123
|
replyTo?: string;
|
|
122
124
|
expectsReply?: boolean;
|
|
125
|
+
/**
|
|
126
|
+
* How a busy recipient receives this message. `steer` (default when absent) queues it until the current turn's tool
|
|
127
|
+
* calls finish, never cancelling in-flight work. `followUp` waits until the recipient's agent finishes and stops.
|
|
128
|
+
* `interrupt` cancels the current model call or cancellable tool call and handles the message next.
|
|
129
|
+
*/
|
|
130
|
+
delivery?: IntercomDeliveryMode;
|
|
123
131
|
/** Correlated delivery refusal: settles a waiting ask, otherwise surfaces as send feedback. */
|
|
124
132
|
replyError?: string;
|
|
125
133
|
source?: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.44-alpha.
|
|
3
|
+
"version": "0.9.44-alpha.3",
|
|
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": [
|