@bastani/atomic 0.9.44-alpha.1 → 0.9.44-alpha.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +16 -0
- package/dist/builtin/intercom/CHANGELOG.md +10 -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/send-signature.ts +4 -1
- package/dist/builtin/intercom/index.bundle.mjs +82 -14
- 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/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +6 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +94 -5
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +94 -5
- package/dist/builtin/workflows/src/index.js +94 -5
- package/dist/builtin/workflows/src/local-worker-entry.mjs +94 -5
- 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/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,22 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.44-alpha.2] - 2026-10-24
|
|
6
|
+
|
|
7
|
+
Workflow swarms now message each other without cancelling each other's work, and stages, replies and session exits behave reliably under fan-out.
|
|
8
|
+
|
|
9
|
+
- **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))
|
|
10
|
+
- **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))
|
|
11
|
+
- **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))
|
|
12
|
+
- **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))
|
|
13
|
+
- **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))
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- 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))
|
|
18
|
+
- 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))
|
|
19
|
+
- 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))
|
|
20
|
+
|
|
5
21
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
6
22
|
|
|
7
23
|
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,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.44-alpha.2] - 2026-10-24
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- 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))
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- 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))
|
|
16
|
+
|
|
7
17
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
8
18
|
|
|
9
19
|
### 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 },
|
|
@@ -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))
|
|
@@ -689,7 +691,8 @@ function buildSendSignature(to, options) {
|
|
|
689
691
|
attachments: canonicalizeAttachmentsForSendSignature(options.attachments) ?? [],
|
|
690
692
|
replyTo: options.replyTo ?? null,
|
|
691
693
|
expectsReply: options.expectsReply ?? false,
|
|
692
|
-
replyError: options.replyError ?? null
|
|
694
|
+
replyError: options.replyError ?? null,
|
|
695
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }
|
|
693
696
|
});
|
|
694
697
|
}
|
|
695
698
|
|
|
@@ -1990,6 +1993,7 @@ var init_client = __esm(() => {
|
|
|
1990
1993
|
timestamp: Date.now(),
|
|
1991
1994
|
replyTo: options.replyTo,
|
|
1992
1995
|
expectsReply: options.expectsReply,
|
|
1996
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery },
|
|
1993
1997
|
replyError: options.replyError,
|
|
1994
1998
|
source: this._messageSource,
|
|
1995
1999
|
content: { text: options.text, attachments: options.attachments }
|
|
@@ -2790,6 +2794,7 @@ class ReplyTracker {
|
|
|
2790
2794
|
pendingAsks = new Map;
|
|
2791
2795
|
pendingTurnContexts = [];
|
|
2792
2796
|
currentTurnContext = null;
|
|
2797
|
+
turnContextAdmitted = false;
|
|
2793
2798
|
constructor(askTimeoutMs = ASK_REPLY_TIMEOUT_MS) {
|
|
2794
2799
|
this.askTimeoutMs = askTimeoutMs;
|
|
2795
2800
|
}
|
|
@@ -2815,15 +2820,27 @@ class ReplyTracker {
|
|
|
2815
2820
|
}
|
|
2816
2821
|
beginTurn(now = Date.now()) {
|
|
2817
2822
|
this.pruneExpired(now);
|
|
2818
|
-
this.
|
|
2823
|
+
this.turnContextAdmitted = false;
|
|
2824
|
+
}
|
|
2825
|
+
admitTurnMessage(messageId) {
|
|
2826
|
+
if (this.currentTurnContext?.message.id === messageId) {
|
|
2827
|
+
this.turnContextAdmitted = true;
|
|
2828
|
+
return;
|
|
2829
|
+
}
|
|
2830
|
+
const index = this.pendingTurnContexts.findIndex((context) => context.message.id === messageId);
|
|
2831
|
+
if (index < 0)
|
|
2832
|
+
return;
|
|
2833
|
+
const [admitted] = this.pendingTurnContexts.splice(index, 1);
|
|
2834
|
+
if (this.turnContextAdmitted)
|
|
2835
|
+
return;
|
|
2836
|
+
this.currentTurnContext = admitted;
|
|
2837
|
+
this.turnContextAdmitted = true;
|
|
2819
2838
|
}
|
|
2820
2839
|
endTurn() {
|
|
2821
2840
|
this.currentTurnContext = null;
|
|
2822
2841
|
}
|
|
2823
2842
|
restoreTurnContext() {
|
|
2824
|
-
|
|
2825
|
-
this.pendingTurnContexts.unshift(this.currentTurnContext);
|
|
2826
|
-
this.currentTurnContext = null;
|
|
2843
|
+
this.turnContextAdmitted = false;
|
|
2827
2844
|
}
|
|
2828
2845
|
reset() {
|
|
2829
2846
|
this.pendingAsks.clear();
|
|
@@ -2872,10 +2889,11 @@ class ReplyTracker {
|
|
|
2872
2889
|
}
|
|
2873
2890
|
throw new Error(`No pending ask from "${options.to}"`);
|
|
2874
2891
|
}
|
|
2875
|
-
|
|
2876
|
-
|
|
2892
|
+
const visible = pending.filter((context) => !this.pendingTurnContexts.some((queued) => queued.message.id === context.message.id));
|
|
2893
|
+
if (visible.length === 1) {
|
|
2894
|
+
return visible[0];
|
|
2877
2895
|
}
|
|
2878
|
-
if (
|
|
2896
|
+
if (visible.length === 0) {
|
|
2879
2897
|
throw new Error("No active intercom context to reply to");
|
|
2880
2898
|
}
|
|
2881
2899
|
throw new Error("Multiple pending asks — specify `to`");
|
|
@@ -4078,6 +4096,10 @@ A valid target outside the known set queues speculatively with a \`notInKnownSet
|
|
|
4078
4096
|
undeliverable at terminal only if never delivered. Use \`ask\` only for a live, reply-capable target.
|
|
4079
4097
|
Stage paths win over same-named owned subgroups for send/ask; \`list\` with \`group\` continues to
|
|
4080
4098
|
select the group.
|
|
4099
|
+
Choose \`delivery\` per send/ask by urgency. The default "steer" arrives after the recipient's current
|
|
4100
|
+
tool calls finish and never cancels its work. Use "interrupt" when it must stop now (an urgent correction
|
|
4101
|
+
or a stop order); it cancels the current model call or tool call. Use "followUp" when the message can wait
|
|
4102
|
+
until the recipient finishes (low priority, "after you're done"). An idle recipient starts a turn.
|
|
4081
4103
|
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
4104
|
|
|
4083
4105
|
Usage:
|
|
@@ -4089,6 +4111,7 @@ Usage:
|
|
|
4089
4111
|
intercom({ action: "leave" }) → Return to your resolved home group
|
|
4090
4112
|
intercom({ action: "send", to: "session-name", message: "..." }) → Send message (shared group only)
|
|
4091
4113
|
intercom({ action: "ask", to: "session-name", message: "..." }) → Ask and wait for reply
|
|
4114
|
+
intercom({ action: "send", to: "session-name", message: "...", delivery: "interrupt" }) → Cancel the recipient's current work
|
|
4092
4115
|
intercom({ action: "reply", message: "..." }) → Reply to the active or exact pending ask
|
|
4093
4116
|
intercom({ action: "pending" }) → List unresolved inbound asks
|
|
4094
4117
|
intercom({ action: "status" }) → Show connection status and all your groups
|
|
@@ -4113,6 +4136,9 @@ one shared membership; contact_supervisor remains the only cross-group path.`,
|
|
|
4113
4136
|
content: Type2.String(),
|
|
4114
4137
|
language: Type2.Optional(Type2.String())
|
|
4115
4138
|
}))),
|
|
4139
|
+
delivery: Type2.Optional(Type2.Union([Type2.Literal("steer"), Type2.Literal("followUp"), Type2.Literal("interrupt")], {
|
|
4140
|
+
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)."
|
|
4141
|
+
})),
|
|
4116
4142
|
replyTo: Type2.Optional(Type2.String({
|
|
4117
4143
|
description: "Exact pending-ask message ID or unique 8-hex UUID prefix; disambiguates concurrent asks, including asks from one sender"
|
|
4118
4144
|
})),
|
|
@@ -4130,6 +4156,7 @@ one shared membership; contact_supervisor remains the only cross-group path.`,
|
|
|
4130
4156
|
}
|
|
4131
4157
|
const toolSessionId = ctx.sessionManager.getSessionId();
|
|
4132
4158
|
const { action, to, message, replyTo, group } = params;
|
|
4159
|
+
const deliveryOption = params.delivery === undefined || params.delivery === "steer" ? {} : { delivery: params.delivery };
|
|
4133
4160
|
const attachments = params.attachments?.map((attachment) => ({ ...attachment }));
|
|
4134
4161
|
const executeAttempt = async (retryToken, _signal) => {
|
|
4135
4162
|
if (retryToken !== undefined) {
|
|
@@ -4440,7 +4467,8 @@ ${message}${attachmentText}`);
|
|
|
4440
4467
|
logicalTarget: to,
|
|
4441
4468
|
text: message,
|
|
4442
4469
|
attachments,
|
|
4443
|
-
replyTo
|
|
4470
|
+
replyTo,
|
|
4471
|
+
...deliveryOption
|
|
4444
4472
|
});
|
|
4445
4473
|
if (result.queued === true) {
|
|
4446
4474
|
retryIdentities.release(retryIdentity);
|
|
@@ -4672,6 +4700,7 @@ ${message}${attachmentText}`);
|
|
|
4672
4700
|
attachments,
|
|
4673
4701
|
replyTo,
|
|
4674
4702
|
expectsReply: true,
|
|
4703
|
+
...deliveryOption,
|
|
4675
4704
|
onReplyTarget: wait.bindSender
|
|
4676
4705
|
});
|
|
4677
4706
|
if (sendResult.queued === true) {
|
|
@@ -5561,6 +5590,13 @@ function registerIntercomLifecycle(pi, deps) {
|
|
|
5561
5590
|
deps.syncPresenceIdentity(ctx.sessionManager.getSessionId());
|
|
5562
5591
|
activeReplyTracker().beginTurn();
|
|
5563
5592
|
});
|
|
5593
|
+
pi.on("message_start", (event, ctx) => {
|
|
5594
|
+
if (!deps.getLiveContext(ctx))
|
|
5595
|
+
return;
|
|
5596
|
+
const id = admittedIntercomMessageId(event.message, ctx);
|
|
5597
|
+
if (id !== undefined)
|
|
5598
|
+
activeReplyTracker().admitTurnMessage(id);
|
|
5599
|
+
});
|
|
5564
5600
|
pi.on("model_select", (event, ctx) => {
|
|
5565
5601
|
if (!deps.getLiveContext(ctx))
|
|
5566
5602
|
return;
|
|
@@ -5575,6 +5611,25 @@ function registerIntercomLifecycle(pi, deps) {
|
|
|
5575
5611
|
}
|
|
5576
5612
|
});
|
|
5577
5613
|
}
|
|
5614
|
+
function intercomMessageId(customType, details) {
|
|
5615
|
+
if (customType !== "intercom_message" || details === null || typeof details !== "object")
|
|
5616
|
+
return;
|
|
5617
|
+
const id = details.message?.id;
|
|
5618
|
+
return typeof id === "string" ? id : undefined;
|
|
5619
|
+
}
|
|
5620
|
+
function admittedIntercomMessageId(message, ctx) {
|
|
5621
|
+
const custom = message;
|
|
5622
|
+
if (custom?.role !== "custom")
|
|
5623
|
+
return;
|
|
5624
|
+
if (custom.customType !== PROTECTED_RECONCILIATION_CUSTOM_TYPE)
|
|
5625
|
+
return intercomMessageId(custom.customType, custom.details);
|
|
5626
|
+
const intentEntryId = custom.details?.protectedReconciliationOf;
|
|
5627
|
+
if (typeof intentEntryId !== "string")
|
|
5628
|
+
return;
|
|
5629
|
+
const intent = ctx.sessionManager.getEntries().find((entry) => entry.id === intentEntryId);
|
|
5630
|
+
return intent?.type === "custom_message" ? intercomMessageId(intent.customType, intent.details) : undefined;
|
|
5631
|
+
}
|
|
5632
|
+
var PROTECTED_RECONCILIATION_CUSTOM_TYPE = "atomic:protected-streaming-reconciliation";
|
|
5578
5633
|
var init_lifecycle = __esm(() => {
|
|
5579
5634
|
init_diagnostics();
|
|
5580
5635
|
init_intercom_utils();
|
|
@@ -6654,8 +6709,9 @@ function createIncomingMessageSender(input) {
|
|
|
6654
6709
|
if (!input.canDeliver(generation)) {
|
|
6655
6710
|
return Promise.reject(new Error("Intercom session retired before inbound delivery"));
|
|
6656
6711
|
}
|
|
6657
|
-
const
|
|
6658
|
-
|
|
6712
|
+
const startsTurn = requested === "trigger" || requested === "triggerFollowUp" || requested === "interrupt";
|
|
6713
|
+
const delivery = startsTurn && !mayStartTurn(entry) ? "prelude" : requested;
|
|
6714
|
+
if ((delivery === "trigger" || delivery === "triggerFollowUp" || delivery === "interrupt") && trackReplyContext && maySupplyReplyContext(entry)) {
|
|
6659
6715
|
input.queueTurnContext(turnContext ?? { from: entry.from, message: entry.message, receivedAt: Date.now() });
|
|
6660
6716
|
}
|
|
6661
6717
|
const baseOptions = {
|
|
@@ -6663,7 +6719,7 @@ function createIncomingMessageSender(input) {
|
|
|
6663
6719
|
persistWhenStreaming: true,
|
|
6664
6720
|
...stageAdmissionBarrier ? { stageAdmissionBarrier } : {}
|
|
6665
6721
|
};
|
|
6666
|
-
const options = delivery === "interrupt" ? { ...baseOptions, triggerTurn: true, deliverAs: "interrupt" } : delivery === "trigger" ? { ...baseOptions, triggerTurn: true } : delivery === "followUp" ? { ...baseOptions, deliverAs: "followUp" } : baseOptions;
|
|
6722
|
+
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
6723
|
return Promise.resolve(input.pi.sendMessage(buildIncomingCustomMessage(entry), options));
|
|
6668
6724
|
};
|
|
6669
6725
|
}
|
|
@@ -7902,8 +7958,9 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
7902
7958
|
});
|
|
7903
7959
|
const activeDelivery = admitActiveSessionInbound(liveContext, (admissionBarrier) => {
|
|
7904
7960
|
replyTracker.queueTurnContext(replyContext);
|
|
7961
|
+
const delivery = message.delivery === "interrupt" ? "interrupt" : message.delivery === "followUp" ? "triggerFollowUp" : "trigger";
|
|
7905
7962
|
return retryStableDelivery({
|
|
7906
|
-
deliver: () => sendIncomingMessage(entry,
|
|
7963
|
+
deliver: () => sendIncomingMessage(entry, delivery, messageGeneration, false, undefined, admissionBarrier),
|
|
7907
7964
|
isCurrent: () => Boolean(getLiveContext(liveContext, messageGeneration)) && liveContext.subagentPolicy?.executionEnded?.aborted !== true && liveContext.subagentPolicy?.messageAdmission?.isOpen() !== false
|
|
7908
7965
|
});
|
|
7909
7966
|
}, () => foregroundDetachHandoff.claim(from, message, messageGeneration, () => Boolean(getLiveContext(liveContext, messageGeneration))), release);
|
|
@@ -8959,7 +9016,8 @@ function createForwardedHandlerMap() {
|
|
|
8959
9016
|
agent_end: [],
|
|
8960
9017
|
tool_execution_start: [],
|
|
8961
9018
|
tool_execution_end: [],
|
|
8962
|
-
model_select: []
|
|
9019
|
+
model_select: [],
|
|
9020
|
+
message_start: []
|
|
8963
9021
|
};
|
|
8964
9022
|
}
|
|
8965
9023
|
function addHandler(captured, event, handler) {
|
|
@@ -8993,6 +9051,9 @@ function captureForwardedHandler(captured, event, handler) {
|
|
|
8993
9051
|
return;
|
|
8994
9052
|
case "model_select":
|
|
8995
9053
|
addHandler(captured, event, handler);
|
|
9054
|
+
return;
|
|
9055
|
+
case "message_start":
|
|
9056
|
+
addHandler(captured, event, handler);
|
|
8996
9057
|
}
|
|
8997
9058
|
}
|
|
8998
9059
|
function addEventHandler(captured, event, handler) {
|
|
@@ -9501,6 +9562,13 @@ function intercom(pi, options = {}) {
|
|
|
9501
9562
|
if (heavy)
|
|
9502
9563
|
await lifecycleForward.enqueue(() => dispatchHandlers(heavy, "agent_end", event, ctx));
|
|
9503
9564
|
});
|
|
9565
|
+
pi.on("message_start", async (event, ctx) => {
|
|
9566
|
+
if (activeLease.retired)
|
|
9567
|
+
return;
|
|
9568
|
+
const heavy = loadedHeavy?.heavy;
|
|
9569
|
+
if (heavy)
|
|
9570
|
+
await lifecycleForward.enqueue(() => dispatchHandlers(heavy, "message_start", event, ctx));
|
|
9571
|
+
});
|
|
9504
9572
|
pi.on("tool_execution_start", async (event, ctx) => {
|
|
9505
9573
|
if (activeLease.retired)
|
|
9506
9574
|
return;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.44-alpha.
|
|
3
|
+
"version": "0.9.44-alpha.2",
|
|
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.2",
|
|
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": [
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.44-alpha.
|
|
3
|
+
"version": "0.9.44-alpha.2",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for web search, URL fetching, GitHub repo cloning, PDF/video extraction. Fork of: https://github.com/nicobailon/pi-web-access",
|
|
6
6
|
"contributors": [
|
|
@@ -6,6 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
6
6
|
|
|
7
7
|
## [Unreleased]
|
|
8
8
|
|
|
9
|
+
## [0.9.44-alpha.2] - 2026-10-24
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- A sticky Intercom broadcast, such as `workflow:<rootRunId>/**`, that reaches many workflow stages no longer makes the workflow journal grow quadratically. Recording one more delivery used to rewrite every stage's deliveries in the run's queued-message event; each event now stores only the deliveries that changed since the run's previous event. In a 160-worker run this shrinks those events from about 160 MB to about 9 MB. Journals written by earlier versions read and resume unchanged ([#3920](https://github.com/bastani-inc/atomic/issues/3920))
|
|
14
|
+
|
|
9
15
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
10
16
|
|
|
11
17
|
### Changed
|
|
@@ -6808,6 +6808,13 @@ function removeProcessExitCleanup(cleanup) {
|
|
|
6808
6808
|
if (processExitCleanups.delete(cleanup) && processExitCleanups.size === 0)
|
|
6809
6809
|
process.off("exit", runProcessExitCleanups);
|
|
6810
6810
|
}
|
|
6811
|
+
function snapshotNeedsHistory(tail) {
|
|
6812
|
+
const event = tail.snapshot.event;
|
|
6813
|
+
if (event.type !== RUN_EVENT_TYPES.messageQueued)
|
|
6814
|
+
return false;
|
|
6815
|
+
const payload = event.payload;
|
|
6816
|
+
return hasPendingStageDeliveryDelta(payload.pendingStageMessages);
|
|
6817
|
+
}
|
|
6811
6818
|
function* invoke(action) {
|
|
6812
6819
|
try {
|
|
6813
6820
|
const result = action();
|
|
@@ -6923,8 +6930,11 @@ class JournalDurableBackend {
|
|
|
6923
6930
|
this.projection.applyWorkflowSnapshot(event.payload.handle);
|
|
6924
6931
|
this.prompts.set(entry.runId, new PromptReservationState(event.payload.handle.pendingPrompts, event.payload.prompts));
|
|
6925
6932
|
} else {
|
|
6926
|
-
const handle = event.payload;
|
|
6927
6933
|
const previous = this.projection.getWorkflow(entry.runId);
|
|
6934
|
+
const handle = event.type === RUN_EVENT_TYPES.messageQueued && hasPendingStageDeliveryDelta(event.payload.pendingStageMessages) ? {
|
|
6935
|
+
...event.payload,
|
|
6936
|
+
pendingStageMessages: decodePendingStageDeliveries(previous?.pendingStageMessages ?? [], event.payload.pendingStageMessages ?? [])
|
|
6937
|
+
} : event.payload;
|
|
6928
6938
|
this.projection.applyWorkflowSnapshot(handle);
|
|
6929
6939
|
if (event.type === RUN_EVENT_TYPES.runStarted || previous?.pendingPrompts !== handle.pendingPrompts)
|
|
6930
6940
|
this.prompts.delete(entry.runId);
|
|
@@ -7220,11 +7230,23 @@ class JournalDurableBackend {
|
|
|
7220
7230
|
for (const message of messages)
|
|
7221
7231
|
if (message.runId !== logicalRunId)
|
|
7222
7232
|
throw new JournalMessageScopeError(logicalRunId, message.runId);
|
|
7223
|
-
|
|
7233
|
+
const next = {
|
|
7224
7234
|
...handle,
|
|
7225
7235
|
pendingStageMessages: replacePendingStageMessagesForRun(handle.pendingStageMessages ?? [], logicalRunId, messages),
|
|
7226
7236
|
updatedAt: Math.max(Date.now(), handle.updatedAt + 1)
|
|
7227
|
-
}
|
|
7237
|
+
};
|
|
7238
|
+
const encoded = encodePendingStageDeliveries(handle.pendingStageMessages ?? [], next.pendingStageMessages ?? []);
|
|
7239
|
+
const base = this.sequences.get(runId);
|
|
7240
|
+
if (encoded !== undefined && base !== undefined) {
|
|
7241
|
+
try {
|
|
7242
|
+
yield* this.append(runId, { type: RUN_EVENT_TYPES.messageQueued, payload: { ...next, pendingStageMessages: encoded } }, { expectSeq: base });
|
|
7243
|
+
return true;
|
|
7244
|
+
} catch (error) {
|
|
7245
|
+
if (!(error instanceof JournalCasConflictError))
|
|
7246
|
+
throw error;
|
|
7247
|
+
}
|
|
7248
|
+
}
|
|
7249
|
+
yield* this.snapshot(next, RUN_EVENT_TYPES.messageQueued);
|
|
7228
7250
|
return true;
|
|
7229
7251
|
}
|
|
7230
7252
|
async archivePendingStageMessage(runId, message) {
|
|
@@ -7318,7 +7340,7 @@ class JournalDurableBackend {
|
|
|
7318
7340
|
if (summarized !== undefined && summarized.seq === version.seq && summarized.at === version.at)
|
|
7319
7341
|
return;
|
|
7320
7342
|
const tail = yield* invoke(() => journal.snapshotTail(runId));
|
|
7321
|
-
if (tail !== undefined)
|
|
7343
|
+
if (tail !== undefined && !snapshotNeedsHistory(tail))
|
|
7322
7344
|
return this.summarize(runId, tail);
|
|
7323
7345
|
if (summarized === undefined && version.seq === 0)
|
|
7324
7346
|
return;
|
|
@@ -7544,6 +7566,69 @@ class JournalDurableBackend {
|
|
|
7544
7566
|
}
|
|
7545
7567
|
}
|
|
7546
7568
|
}
|
|
7569
|
+
function identity(message) {
|
|
7570
|
+
return JSON.stringify([message.runId, message.id]);
|
|
7571
|
+
}
|
|
7572
|
+
function uniqueByIdentity(messages) {
|
|
7573
|
+
const result = new Map;
|
|
7574
|
+
const repeated = new Set;
|
|
7575
|
+
for (const message of messages) {
|
|
7576
|
+
const key = identity(message);
|
|
7577
|
+
if (result.has(key))
|
|
7578
|
+
repeated.add(key);
|
|
7579
|
+
result.set(key, message);
|
|
7580
|
+
}
|
|
7581
|
+
for (const key of repeated)
|
|
7582
|
+
result.delete(key);
|
|
7583
|
+
return result;
|
|
7584
|
+
}
|
|
7585
|
+
function isDeltaEncoded(message) {
|
|
7586
|
+
return message.deliveryDelta !== undefined;
|
|
7587
|
+
}
|
|
7588
|
+
function encodePendingStageDeliveries(previous, next) {
|
|
7589
|
+
const bases = uniqueByIdentity(previous);
|
|
7590
|
+
const nextIdentities = uniqueByIdentity(next);
|
|
7591
|
+
let encodedAny = false;
|
|
7592
|
+
const encoded = next.map((message) => {
|
|
7593
|
+
const base = bases.get(identity(message))?.deliveries;
|
|
7594
|
+
const deliveries = message.deliveries;
|
|
7595
|
+
if (base === undefined || deliveries === undefined || !nextIdentities.has(identity(message)))
|
|
7596
|
+
return message;
|
|
7597
|
+
const drop = [];
|
|
7598
|
+
let kept = 0;
|
|
7599
|
+
base.forEach((delivery, index) => {
|
|
7600
|
+
if (kept < deliveries.length && JSON.stringify(delivery) === JSON.stringify(deliveries[kept]))
|
|
7601
|
+
kept++;
|
|
7602
|
+
else
|
|
7603
|
+
drop.push(index);
|
|
7604
|
+
});
|
|
7605
|
+
const add = deliveries.slice(kept);
|
|
7606
|
+
if (drop.length + add.length >= deliveries.length)
|
|
7607
|
+
return message;
|
|
7608
|
+
encodedAny = true;
|
|
7609
|
+
const { deliveries: _full, ...rest } = message;
|
|
7610
|
+
return { ...rest, deliveryDelta: drop.length === 0 ? { add } : { drop, add } };
|
|
7611
|
+
});
|
|
7612
|
+
return encodedAny ? encoded : undefined;
|
|
7613
|
+
}
|
|
7614
|
+
function decodePendingStageDeliveries(previous, messages) {
|
|
7615
|
+
if (!messages.some(isDeltaEncoded))
|
|
7616
|
+
return messages;
|
|
7617
|
+
const bases = uniqueByIdentity(previous);
|
|
7618
|
+
return messages.map((message) => {
|
|
7619
|
+
if (!isDeltaEncoded(message))
|
|
7620
|
+
return message;
|
|
7621
|
+
const base = bases.get(identity(message))?.deliveries;
|
|
7622
|
+
if (base === undefined)
|
|
7623
|
+
throw new Error(`atomic-workflows: journal delivery change for ${identity(message)} has no base entry`);
|
|
7624
|
+
const { deliveryDelta, ...rest } = message;
|
|
7625
|
+
const dropped = new Set(deliveryDelta.drop ?? []);
|
|
7626
|
+
return { ...rest, deliveries: [...base.filter((_, index) => !dropped.has(index)), ...deliveryDelta.add] };
|
|
7627
|
+
});
|
|
7628
|
+
}
|
|
7629
|
+
function hasPendingStageDeliveryDelta(messages) {
|
|
7630
|
+
return messages?.some(isDeltaEncoded) === true;
|
|
7631
|
+
}
|
|
7547
7632
|
var PROCESS_HOLDER_ID, processExitCleanups, JournalOwnershipError, JournalBackendClosedError, JournalOperationAbortedError, JournalExecutionRetiredError, JournalMessageScopeError;
|
|
7548
7633
|
var init_journal_backend = __esm(() => {
|
|
7549
7634
|
init_workflow_instance_ownership_error();
|
|
@@ -61647,7 +61732,8 @@ function buildSendSignature(to, options) {
|
|
|
61647
61732
|
attachments: canonicalizeAttachmentsForSendSignature(options.attachments) ?? [],
|
|
61648
61733
|
replyTo: options.replyTo ?? null,
|
|
61649
61734
|
expectsReply: options.expectsReply ?? false,
|
|
61650
|
-
replyError: options.replyError ?? null
|
|
61735
|
+
replyError: options.replyError ?? null,
|
|
61736
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery }
|
|
61651
61737
|
});
|
|
61652
61738
|
}
|
|
61653
61739
|
|
|
@@ -61833,6 +61919,8 @@ function isMessage(value2) {
|
|
|
61833
61919
|
return false;
|
|
61834
61920
|
if (message.expectsReply !== undefined && typeof message.expectsReply !== "boolean")
|
|
61835
61921
|
return false;
|
|
61922
|
+
if (message.delivery !== undefined && message.delivery !== "steer" && message.delivery !== "followUp" && message.delivery !== "interrupt")
|
|
61923
|
+
return false;
|
|
61836
61924
|
if (message.replyError !== undefined && typeof message.replyError !== "string")
|
|
61837
61925
|
return false;
|
|
61838
61926
|
if (message.source !== undefined && !isMessageSource(message.source))
|
|
@@ -63042,6 +63130,7 @@ var init_client = __esm(() => {
|
|
|
63042
63130
|
timestamp: Date.now(),
|
|
63043
63131
|
replyTo: options.replyTo,
|
|
63044
63132
|
expectsReply: options.expectsReply,
|
|
63133
|
+
...options.delivery === undefined || options.delivery === "steer" ? {} : { delivery: options.delivery },
|
|
63045
63134
|
replyError: options.replyError,
|
|
63046
63135
|
source: this._messageSource,
|
|
63047
63136
|
content: { text: options.text, attachments: options.attachments }
|