@bastani/atomic 0.9.44-alpha.6 → 0.9.44
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 +55 -0
- package/dist/builtin/intercom/CHANGELOG.md +18 -0
- package/dist/builtin/intercom/index.bundle.mjs +32 -4
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +7 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +9 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +31 -0
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/extension/index.bundle.mjs +141 -38
- package/dist/builtin/workflows/src/extension/local-worker-entry.mjs +139 -30
- package/dist/builtin/workflows/src/index.js +139 -30
- package/dist/builtin/workflows/src/local-worker-entry.mjs +139 -30
- package/npm-shrinkwrap.json +32 -32
- package/package.json +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,61 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.44] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
Large agent swarms and pooled workflow fan-outs now run for hours without freezing the TUI, dropping messages or slowing down, and recover cleanly from a crash.
|
|
8
|
+
|
|
9
|
+
- **A TUI that stays responsive under heavy fan-out:** local worker pools run in their own coordinator process, journals no longer grow on every heartbeat, and the root's main thread stays idle while hundreds of pooled children run ([#3905](https://github.com/bastani-inc/atomic/pull/3905))
|
|
10
|
+
- **Intercom that keeps working at scale:** pooled stages wait for their run's route instead of failing with `Live workflow-stage route has no registered workflow owner`, runs share one broker connection, a refused route no longer drops the others, and route announcements are batched and bounded ([#3907](https://github.com/bastani-inc/atomic/pull/3907), [#3915](https://github.com/bastani-inc/atomic/pull/3915), [#3939](https://github.com/bastani-inc/atomic/pull/3939), [#3951](https://github.com/bastani-inc/atomic/pull/3951))
|
|
11
|
+
- **Messages that arrive once, on time and without interrupting:** Intercom `send` and `ask` default to `"steer"` (with `followUp` and `interrupt` available), replies reach the right sender, a message that reaches a stage before its prompt becomes context for its first turn, and queued messages survive pauses, worker replacement and reconnects ([#3921](https://github.com/bastani-inc/atomic/issues/3921), [#3923](https://github.com/bastani-inc/atomic/pull/3923), [#3924](https://github.com/bastani-inc/atomic/pull/3924), [#3927](https://github.com/bastani-inc/atomic/pull/3927), [#3958](https://github.com/bastani-inc/atomic/pull/3958), [#3959](https://github.com/bastani-inc/atomic/pull/3959))
|
|
12
|
+
- **Journals and costs that stay flat over a long run:** journal writes are group-committed and batched, each delivery receipt is written once, pending-message changes are stored as deltas, and pool commits, delivery transitions and receipt bookkeeping cost what changed rather than the run's history ([#3922](https://github.com/bastani-inc/atomic/pull/3922), [#3930](https://github.com/bastani-inc/atomic/pull/3930), [#3937](https://github.com/bastani-inc/atomic/pull/3937), [#3940](https://github.com/bastani-inc/atomic/pull/3940), [#3942](https://github.com/bastani-inc/atomic/pull/3942)–[#3948](https://github.com/bastani-inc/atomic/pull/3948), [#3952](https://github.com/bastani-inc/atomic/pull/3952))
|
|
13
|
+
- **Fast, reliable recovery:** `/workflow status` and resume load large journals in seconds, resume no longer rebuilds every child's history, and resumed pooled children no longer fail with checkpoint identity conflicts or spurious `Storage unavailable` errors; a stage never waits forever to start ([#3950](https://github.com/bastani-inc/atomic/pull/3950), [#3954](https://github.com/bastani-inc/atomic/pull/3954), [#3955](https://github.com/bastani-inc/atomic/pull/3955), [#3958](https://github.com/bastani-inc/atomic/pull/3958))
|
|
14
|
+
- **Peer-to-peer swarms by default:** subagents and workflow stages talk to each other directly, subagent `parallel.*` limits and workflow `defaultConcurrency` default to 1000, and workflow `maxDepth` now defaults to 3; set them lower to throttle ([#3912](https://github.com/bastani-inc/atomic/pull/3912))
|
|
15
|
+
- **Completion notices you can rely on:** background subagent and shell completions reach the parent after a restart, after a paused stage resumes, and when the parent is idle with a session saved to a file ([#3909](https://github.com/bastani-inc/atomic/pull/3909), [#3926](https://github.com/bastani-inc/atomic/pull/3926), [#3953](https://github.com/bastani-inc/atomic/pull/3953))
|
|
16
|
+
- **Upstream sync, tools and security:** streamed tool-call arguments are parsed once per delta, Cua Driver 0.38.0 approves its own macOS permission prompts (`ATOMIC_CUA_AUTO_APPROVE=0` to keep asking), herdr 0.9.5 and agent-browser 0.39.2 are bundled, and `undici`, `zod`, `fast-uri`, `body-parser` and `tokio` close their published advisories ([#3910](https://github.com/bastani-inc/atomic/pull/3910), [#3911](https://github.com/bastani-inc/atomic/pull/3911), [#3935](https://github.com/bastani-inc/atomic/pull/3935))
|
|
17
|
+
|
|
18
|
+
**Breaking change:** `appendGrammarToolInputJsonDelta()` in `@bastani/pi-ai` now takes only the new input delta and no longer closes the input; call `closeGrammarToolInputJson()` when the input is complete ([#3910](https://github.com/bastani-inc/atomic/pull/3910))
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- An Intercom message sent to one specific workflow stage while that stage is paused, blocked or awaiting input and has not started yet is now queued for that stage and reaches it when it starts. Previously the send was accepted as a message for a possible future stage (`notInKnownSet`) and matched only the stage's old id, so it was lost when the stage started under a new id, as it does when a pooled worker is replaced. Messages sent to the whole run (`workflow:<rootRunId>/**`) were not affected ([#3869](https://github.com/bastani-inc/atomic/issues/3869))
|
|
23
|
+
- A workflow stage no longer waits forever before its first turn when an Intercom message to a pattern target such as `workflow:<run>/child-a/**` is queued just after the stage's queued messages were delivered at startup. The stage now starts once that startup delivery has finished, and later messages reach it as ordinary live messages. Before, the stage waited for a second delivery that never ran, so its prompt was never sent and its pooled work stayed leased with its lease still renewed. In a 640-child pooled load test this left 7 children (about 1%) stuck for 20–45 minutes.
|
|
24
|
+
- A workflow stage never waits more than 60 seconds without delivery progress for its queued Intercom messages before it starts. If a startup delivery stops making progress, the stage logs one warning that names the undelivered messages and whether the delivery is still running. It then starts, and the messages stay queued: they arrive live or on the stage's next delivery.
|
|
25
|
+
- A sticky Intercom message that could not reach a workflow stage while the stage was disconnected from the broker is now delivered when the stage reconnects, exactly as it would have arrived live: an `interrupt` still cancels the stage's running operation, and an ask becomes the stage's reply target. Messages the stage already has are not delivered again.
|
|
26
|
+
- Under heavy write load a local SQLite store (`node-sqlite`, including the workflow journal) commits transactions in groups: transactions requested while another is committing share one `BEGIN IMMEDIATE … COMMIT`, each in its own savepoint and in request order. A transaction that fails rolls back only its own writes, and every caller still settles only after the shared commit. In a 480-child pooled workflow the pool coordinator was committing about 60 transcript transactions a second, each taking the write lock and syncing on its own, which starved the workflow host's writes until Intercom sends timed out and worker leases expired.
|
|
27
|
+
- A background task completion that arrives just as a paused workflow stage is resumed now starts the stage's next turn. Pausing a stage cancels its background shells, and their completion notices are written to the session before the stage acts on them. If the stage was resumed during that write, the notice was queued after the resume had already released the stage's held messages, so the stage sat idle with the notice unanswered until something else started a turn.
|
|
28
|
+
- A local SQLite store (`node-sqlite`, including the workflow journal) now waits for another process's write lock instead of failing after the 5 second busy timeout. When the lock is busy, it retries the start of the write transaction for up to 30 seconds without blocking the event loop. Single statements outside a transaction still use the configured busy timeout. Other failures, and a lock still held at that point, fail at once. `Storage unavailable` errors now include the SQLite reason, such as `database is locked (SQLite code 5)`, so the reason survives when local workers pass an error to another process
|
|
29
|
+
- 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)).
|
|
30
|
+
- 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)).
|
|
31
|
+
- 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)).
|
|
32
|
+
- 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.
|
|
33
|
+
- 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))
|
|
34
|
+
- 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))
|
|
35
|
+
- 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))
|
|
36
|
+
- Fixed background subagent and shell completion notices never reaching the parent conversation after the session was restarted or resumed in a new process: the new process numbers its tasks from the start again, so a completion matched an earlier one already acknowledged in the session history and was silently dropped. Each completion now carries an identity unique to its process generation, and every settled task still delivers exactly one notice ([#3908](https://github.com/bastani-inc/atomic/issues/3908))
|
|
37
|
+
- Atomic now starts when a package configured in `settings.json` fails to install (for example when npm cannot reach the registry), instead of crashing with an uncaught error. It skips that package, warns with the `atomic remove` command that drops it (`--local` for project packages), and tries again on the next start or `/reload`. `--help`, `--list-models` and `atomic config` show the warning too ([pi #11088](https://github.com/earendil-works/pi/issues/11088))
|
|
38
|
+
- `pi.exec()` no longer corrupts multi-byte UTF-8 characters split across output chunks, which came back as `�` replacement characters ([pi #11312](https://github.com/earendil-works/pi/issues/11312))
|
|
39
|
+
- Automatic `fd` and `rg` downloads no longer fetch x86-64 binaries on ARM32 and other architectures without a prebuilt release. Atomic now warns and recommends installing the tool with the system package manager ([pi #11209](https://github.com/earendil-works/pi/issues/11209))
|
|
40
|
+
- Streamed tool calls no longer re-run their call renderer on every argument delta; the interactive mode renders the latest arguments once per frame, so a large streamed `write` no longer re-highlights its whole content per delta ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
|
|
41
|
+
|
|
42
|
+
### Added
|
|
43
|
+
|
|
44
|
+
- `/model` and `/scoped-models` now show a model's name next to its ID when the name adds words the ID lacks, such as a renamed `models.json` override ([pi #5916](https://github.com/earendil-works/pi/issues/5916), [pi #11347](https://github.com/earendil-works/pi/pull/11347))
|
|
45
|
+
|
|
46
|
+
## [0.9.44-alpha.7] - 2026-10-25
|
|
47
|
+
|
|
48
|
+
Workflow stages always get their queued Intercom messages and never wait forever to start, even around pauses, worker replacement and reconnects.
|
|
49
|
+
|
|
50
|
+
- **No stuck stages:** a stage no longer waits forever before its first turn for a pattern message it receives live, and never waits more than 60 seconds without delivery progress; any message still queued is delivered afterwards ([#3958](https://github.com/bastani-inc/atomic/pull/3958))
|
|
51
|
+
- **No lost messages:** a direct send to a paused, blocked or not-yet-started stage is queued for that stage and survives a pooled worker replacement, and messages missed while a stage was disconnected arrive after it reconnects, with their delivery mode and reply target intact ([#3958](https://github.com/bastani-inc/atomic/pull/3958), [#3959](https://github.com/bastani-inc/atomic/pull/3959))
|
|
52
|
+
|
|
53
|
+
### Fixed
|
|
54
|
+
|
|
55
|
+
- An Intercom message sent to one specific workflow stage while that stage is paused, blocked or awaiting input and has not started yet is now queued for that stage and reaches it when it starts. Previously the send was accepted as a message for a possible future stage (`notInKnownSet`) and matched only the stage's old id, so it was lost when the stage started under a new id, as it does when a pooled worker is replaced. Messages sent to the whole run (`workflow:<rootRunId>/**`) were not affected ([#3869](https://github.com/bastani-inc/atomic/issues/3869))
|
|
56
|
+
- A workflow stage no longer waits forever before its first turn when an Intercom message to a pattern target such as `workflow:<run>/child-a/**` is queued just after the stage's queued messages were delivered at startup. The stage now starts once that startup delivery has finished, and later messages reach it as ordinary live messages. Before, the stage waited for a second delivery that never ran, so its prompt was never sent and its pooled work stayed leased with its lease still renewed. In a 640-child pooled load test this left 7 children (about 1%) stuck for 20–45 minutes.
|
|
57
|
+
- A workflow stage never waits more than 60 seconds without delivery progress for its queued Intercom messages before it starts. If a startup delivery stops making progress, the stage logs one warning that names the undelivered messages and whether the delivery is still running. It then starts, and the messages stay queued: they arrive live or on the stage's next delivery.
|
|
58
|
+
- A sticky Intercom message that could not reach a workflow stage while the stage was disconnected from the broker is now delivered when the stage reconnects, exactly as it would have arrived live: an `interrupt` still cancels the stage's running operation, and an ask becomes the stage's reply target. Messages the stage already has are not delivered again.
|
|
59
|
+
|
|
5
60
|
## [0.9.44-alpha.6] - 2026-10-25
|
|
6
61
|
|
|
7
62
|
Large workflow fan-outs stay fast for the whole run and recover cleanly from a crash, even with hundreds of pooled child workflows messaging each other.
|
|
@@ -4,6 +4,24 @@ 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] - 2026-10-25
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- `intercom list` lists at most 5 sticky delivery states per workflow row: states that still need action first, then the most recent deliveries. A final line reports how many were omitted and the total in each state, and the row details carry the same totals as `deliveryStateCounts` and `omittedDeliveryStates`. In a 640-child workflow, each list result had stored about 3 MB of delivery states in the session transcript. A test with 8,000 queued messages in 163 rows shrinks the list result from about 4.6 MB to about 0.2 MB. The broker applies the same bound to rows announced by older workflow hosts.
|
|
12
|
+
- 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))
|
|
13
|
+
- The `intercom` skill now presents unlimited peer-to-peer (swarm) coordination as the default, with a broadcast example and guidance for agents to discover and talk to peers proactively. It also covers scaling a swarm to hundreds or thousands of agents organized into teams.
|
|
14
|
+
|
|
15
|
+
### Fixed
|
|
16
|
+
|
|
17
|
+
- A pooled workflow stage whose Intercom extension delivers queued messages at startup no longer fails with `Extension startup failed: …intercom…: Storage unavailable: node-sqlite at <journal>` while other processes are writing heavily to the workflow journal. The journal write now waits for the lock, and any remaining failure states its SQLite cause
|
|
18
|
+
- 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))
|
|
19
|
+
- 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))
|
|
20
|
+
- 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))
|
|
21
|
+
- Workflow stages that start while their run's Intercom route is still registering, or is registering again after its connection dropped, no longer fail every Intercom call with `Live workflow-stage route has no registered workflow owner`. The stage waits for the route to come back before it uses Intercom, instead of giving up after a few quick retries ([#3904](https://github.com/bastani-inc/atomic/issues/3904))
|
|
22
|
+
- Workflow route owners now register the routes of every run in a workflow invocation over one broker connection instead of opening one connection per run. The connection is per workflow invocation rather than per host because the broker grants route ownership per invocation group. All child runs of one root share that connection, whatever the fan-out size, so a large fan-out of child workflows no longer builds a connection backlog at the broker, and a run whose route moves to a new capability keeps the connection the other runs use. A run whose route the broker refuses moves to a connection of its own, so it cannot take the other runs' routes down with it. Only refused runs get such a connection, so the number of extra connections is bounded by the refused runs, not by the number of runs ([#3904](https://github.com/bastani-inc/atomic/issues/3904))
|
|
23
|
+
- When the Intercom broker refuses one workflow run's route, for example a route that another session owns, it now refuses that route on its own and keeps the shared route connection open, so the routes of the other runs in the same workflow invocation are no longer dropped for a reconnect. Authorization is unchanged: the same registrations are refused for the same reasons. The route owner retries only the refused run, on its own backoff. With an older broker, which still ends the connection, the refused run still moves to a connection of its own. Clients that do not ask for this, including older ones, keep the previous behavior ([#3906](https://github.com/bastani-inc/atomic/issues/3906))
|
|
24
|
+
|
|
7
25
|
## [0.9.44-alpha.6] - 2026-10-25
|
|
8
26
|
|
|
9
27
|
### Changed
|
|
@@ -7923,11 +7923,12 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
7923
7923
|
scheduleInboundFlush();
|
|
7924
7924
|
return plan.notices ?? [];
|
|
7925
7925
|
}
|
|
7926
|
-
|
|
7926
|
+
let redrainPendingStage = null;
|
|
7927
|
+
function handleIncomingMessage(ctx, from, message, channel, receivedBeforeStageStart = false, recoveredAfterStageStart = false) {
|
|
7927
7928
|
const messageGeneration = runtimeGeneration;
|
|
7928
7929
|
const liveContext = getLiveContext(ctx, messageGeneration);
|
|
7929
7930
|
if (!liveContext) {
|
|
7930
|
-
if (receivedBeforeStageStart)
|
|
7931
|
+
if (receivedBeforeStageStart || recoveredAfterStageStart)
|
|
7931
7932
|
throw new Error("Intercom session retired before pending-stage delivery; message remains queued");
|
|
7932
7933
|
return;
|
|
7933
7934
|
}
|
|
@@ -7988,6 +7989,13 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
7988
7989
|
});
|
|
7989
7990
|
}, () => foregroundDetachHandoff.claim(from, message, messageGeneration, () => Boolean(getLiveContext(liveContext, messageGeneration))), release);
|
|
7990
7991
|
if (activeDelivery !== false) {
|
|
7992
|
+
if (recoveredAfterStageStart) {
|
|
7993
|
+
return activeDelivery.then(commit, (error) => {
|
|
7994
|
+
replyTracker.forgetIncomingMessage(replyContext);
|
|
7995
|
+
inboundDeliveries.release(reservation, toError3(error));
|
|
7996
|
+
throw error;
|
|
7997
|
+
});
|
|
7998
|
+
}
|
|
7991
7999
|
activeDelivery.then(commit, release);
|
|
7992
8000
|
return;
|
|
7993
8001
|
}
|
|
@@ -8553,6 +8561,8 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
8553
8561
|
if (nextClient.sessionId && nextClient.sessionId !== preferredSessionId) {
|
|
8554
8562
|
await recordIntercomSessionIdentity(sessionIdAtStart, nextClient.sessionId);
|
|
8555
8563
|
}
|
|
8564
|
+
if (reason !== "startup")
|
|
8565
|
+
redrainPendingStage?.();
|
|
8556
8566
|
return nextClient;
|
|
8557
8567
|
} catch (error) {
|
|
8558
8568
|
if (client === nextClient) {
|
|
@@ -8788,18 +8798,36 @@ function piIntercomExtension(pi, testOverrides = {}) {
|
|
|
8788
8798
|
await ensureConnected("startup");
|
|
8789
8799
|
return;
|
|
8790
8800
|
}
|
|
8791
|
-
const
|
|
8801
|
+
const generation = runtimeGeneration;
|
|
8802
|
+
const deliverPending = async (started = false) => {
|
|
8792
8803
|
const receivedMessageIds = ctx.sessionManager.getBranch().flatMap((entry) => {
|
|
8793
8804
|
if (entry.type !== "custom_message" || entry.customType !== "intercom_message")
|
|
8794
8805
|
return [];
|
|
8795
8806
|
const id = entry.details?.message?.id;
|
|
8796
8807
|
return typeof id === "string" ? [id] : [];
|
|
8797
8808
|
});
|
|
8798
|
-
await pendingStageDelivery.deliverPending((from, message) => handleIncomingMessage(ctx, from, message, undefined, true), { sessionId: ctx.sessionManager.getSessionId(), receivedMessageIds });
|
|
8809
|
+
await pendingStageDelivery.deliverPending((from, message) => started ? handleIncomingMessage(ctx, from, message, undefined, false, true) : handleIncomingMessage(ctx, from, message, undefined, true), { sessionId: ctx.sessionManager.getSessionId(), receivedMessageIds });
|
|
8799
8810
|
};
|
|
8811
|
+
let startupDrained = false;
|
|
8812
|
+
let redrainRequested = false;
|
|
8813
|
+
const redrain = () => {
|
|
8814
|
+
if (!getLiveContext(ctx, generation))
|
|
8815
|
+
return;
|
|
8816
|
+
if (!startupDrained) {
|
|
8817
|
+
redrainRequested = true;
|
|
8818
|
+
return;
|
|
8819
|
+
}
|
|
8820
|
+
deliverPending(true).catch(() => {});
|
|
8821
|
+
};
|
|
8822
|
+
redrainPendingStage = redrain;
|
|
8800
8823
|
await deliverPending();
|
|
8801
8824
|
await ensureConnected("startup");
|
|
8802
8825
|
await deliverPending();
|
|
8826
|
+
startupDrained = true;
|
|
8827
|
+
if (redrainRequested) {
|
|
8828
|
+
redrainRequested = false;
|
|
8829
|
+
redrain();
|
|
8830
|
+
}
|
|
8803
8831
|
});
|
|
8804
8832
|
pi.registerMessageRenderer("intercom_message", (message, _options, theme) => {
|
|
8805
8833
|
const details = message.details;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.44
|
|
3
|
+
"version": "0.9.44",
|
|
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": [
|
|
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.44] - 2026-10-25
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- MCP sign-in now accepts only `http:` and `https:` authorization endpoints and other OAuth metadata URLs. Before, a malicious MCP server could supply a `file:` or custom protocol authorization endpoint (for example `search-ms:`), which the platform URL handler would launch on sign-in ([pi 9ffd27cf](https://github.com/earendil-works/pi/commit/9ffd27cfaaeed5c1ecf9369f393c3222975729b4))
|
|
15
|
+
- The excerpt of an HTTP error body in Streamable HTTP errors no longer keeps the whole response body alive ([pi #9265](https://github.com/earendil-works/pi/issues/9265))
|
|
16
|
+
|
|
10
17
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
11
18
|
|
|
12
19
|
### Fixed
|
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.44] - 2026-10-25
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Security policy change:** on macOS, the agent now approves Cua Driver's own Accessibility and Screen Recording permission prompts itself by default, without asking, by clicking them through the host's VNC or Screen Sharing server when credentials for it are available (for example `CUA_VNC_SERVER` and `CUA_VNC_PASSWORD`). It reports each approval and when it happened, never approves any other permission or another app's dialog, and types a password only when you supplied one. Set `ATOMIC_CUA_AUTO_APPROVE=0` to turn this off and have the agent ask you to grant the permissions, as before.
|
|
10
|
+
- Updated the bundled Cua Driver skill to upstream `cua-driver-rs-v0.38.0`. Refusals, `unverifiable` actions and `suspected_noop` results now come with a `hint` and a safe `next_call` to try, and three single actions in a row suggest batching with `run_steps`. Steps inside `run_steps` and `run_script` skip most of the cursor glide and window watch on macOS. The macOS guide adds installing and updating the driver and granting its permissions with an MDM profile on managed Macs. It also covers `invoke_menu` refusing unless the requested window is key, background `<select>` menus in Chrome and Electron via `set_value`, Electron drags, and handing a URL to a specific Windows browser with `additional_arguments`.
|
|
11
|
+
- Updated the bundled Herdr skill to upstream `v0.9.5`. It can now control agents and panes on a saved SSH machine with `herdr --machine <label-or-id>`, and it explains that an agent suspended with Ctrl+Z keeps its identity, that Antigravity and Grok can look idle while background work continues, that `pane run` only acknowledges admission (and rejects over-long lines on macOS), and that CLI closes skip the TUI's last-pane confirmation. The explicit-request trigger and the `HERDR_ENV=1` requirement are unchanged.
|
|
12
|
+
- Subagents now default to peer-to-peer (swarm) coordination with no limits on communication between agents: children discover siblings with `intercom list` and broadcast, debate, claim, hand off, and ask each other directly instead of relaying through the supervisor. Explicit user approval and authorization gates still apply. Swarms scale with the task: for very complex work or requested heavy parallelism, hundreds or thousands of agents self-organize into teams, and the subagents guide documents the settings that bound swarm size. The `parallel.maxTasks`, `parallel.concurrency` and `parallel.runningCapacity` defaults rise from 50, 3 and 4 to 1000, so one parent can run up to 1000 children at once without configuration; set them lower in `~/.atomic/agent/extensions/subagent/config.json` to throttle, or higher for larger swarms.
|
|
13
|
+
|
|
5
14
|
## [0.9.44-alpha.1] - 2026-10-24
|
|
6
15
|
|
|
7
16
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.44
|
|
3
|
+
"version": "0.9.44",
|
|
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
|
|
3
|
+
"version": "0.9.44",
|
|
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,37 @@ 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] - 2026-10-25
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- After a pooled root was killed and resumed, some child workflows no longer fail with `Extension startup failed: …intercom…: Storage unavailable: custom at custom journal`. While a resumed root and its workers were busy, a worker's lease could run out a moment before the pool extended it. When the root then recorded that a queued Intercom message had reached the worker's stage, it refused the write as if the attempt had been replaced and reported the refusal as unavailable storage, so the stage failed at startup. In a 640-child run this failed 21 children. The pool already keeps a live worker's child when its lease runs out (#3888), and the root now does the same when recording deliveries. Only a reassigned attempt or a new root epoch is refused, with `Stale work lease` or the session reason, and the replaced attempt's child is replayed. A failure of a custom journal now also names the cause's error class and code, though not its message, which may contain credentials.
|
|
14
|
+
- A long-running workflow fan-out whose stages keep sending Intercom messages to stage patterns (for example `workflow:<rootRunId>/child-*/**`) no longer slows down with every message it has sent. Pattern messages stay queued until the root run ends, and each delivery receipt made the workflow host re-check every queued message and every recorded delivery against every run. In a 480-child load test this saturated the host, Intercom sends timed out after 10 s, and about half of them were never delivered. A delivery now re-checks only the messages it changed and the messages delivered to stages that have since ended: with 3,000 queued messages, recording one delivery takes about 1.2 ms instead of 6.7 ms. Delivery results are unchanged.
|
|
15
|
+
- An Intercom send to a workflow stage no longer times out when a wave of stages starts at the same time. Every pending-message change for a run used to wait for the previous one's journal write, so a send that arrived while about 160 starting stages recorded their deliveries waited behind about 160 writes. Changes that arrive while a write is in progress are now applied together and saved in one write. In a test with a 40 ms write, a send issued in the middle of a 160-stage burst returned after 2 writes (about 90 ms) instead of 81 (about 3.3 s).
|
|
16
|
+
- Large workflow fan-outs no longer saturate the local worker pool's coordinator. Every pool commit, including each batch of session transcript entries, was checked for JSON-serializability with a recursive schema whose `$ref` evaluation cost far more than the write itself, so with 160 workers the coordinator spent about 90% of its time validating and stages stopped starting. Plain JSON values now pass a direct structural check; anything unusual still goes through the schema, with the same errors.
|
|
17
|
+
- Intercom sends to stage patterns no longer time out while a large workflow fan-out records delivery receipts. The workflow host wrote each receipt in its own journal transaction, one after another, and a pattern message waited behind every receipt queued before it. When another process also held the journal's write lock, that wait exceeded the 10-second send acknowledgement, and in a 480-child load test delivery fell to 0–2%. Receipts and settlements queued back to back for a run are now written together in one transaction, in the same order and with the same durability. In a test that queues a pattern message halfway through 2,000 receipts from 160 callers while a second process holds the write lock, the message persisted in under 30 ms instead of more than 4 seconds.
|
|
18
|
+
- Resuming a large workflow with many completed child workflows no longer takes minutes before any work restarts. Resume rebuilt every nested run from the whole checkpoint journal once for each cached child, and each rebuild itself rescanned the journal for every child boundary. Resume now rebuilds the nested runs once, reuses them while replay records only its own bookkeeping, and rebuilds each in a single pass. Resuming a pooled root with 640 completed children went from 156 s to 1.3 s (64 children: 271 ms to 37 ms), and resumed runs reach the same state as before.
|
|
19
|
+
- The local worker pool coordinator no longer slows down as a long pooled run grows. Each checkpoint a worker committed was validated against every checkpoint the root had ever recorded, and each commit re-added every usage report, so after about 15 minutes of a 640-child run the coordinator sat near full CPU and Intercom sends began to fail. Validation now reads only the checkpoints recorded since the previous commit, and the root's usage total is kept up to date as usage is written. Validation results and totals are unchanged. With 5,000 recorded checkpoints, a commit takes about 1.2 ms instead of 16 ms, and reading the usage total for 40,000 usage rows takes 0.02 ms instead of 8 ms.
|
|
20
|
+
- Late in a long workflow fan-out, Intercom sends no longer time out while the host announces workflow routes or records delivery receipts. Each root route announcement recomputed every queued pattern message's delivery state on every matching stage, and every recorded receipt re-read the delivery count of every queued message. Both grow for the whole run (about 7,700 queued messages after 21 minutes in a 640-child load test), so one announcement could block the host for seconds. Announcements and receipts now recompute only what changed. With 8,000 queued messages, an announcement after one delivery makes 2 receipt checks instead of 16,000, and recording a receipt reads 1 delivery count instead of 8,000. Delivery counts are unchanged.
|
|
21
|
+
- `/workflow status` from a new session, and resuming a workflow, no longer stall on a journal that holds many delivery receipts. Loading the journal re-read the delivery count of every queued message for every receipt. After a 640-child run with about 800,000 receipts, status had not rendered after 3 minutes. In a test with 200,000 receipts over 8,000 queued messages, opening and loading the run now takes about 3.4 s instead of 168 s.
|
|
22
|
+
- Each delivery of a pattern (sticky) Intercom message to a workflow stage is now written to the run's journal once. When a delivered stage ended, the workflow host archived that delivery's receipt together with every earlier receipt still kept for the message. Those earlier receipts stayed kept, so they were written again each time a later stage ended. In pooled load-test journals about 93% of delivery receipts were exact copies, with one receipt written up to 423 times, and each copy took a slot in the run's journal writes. Receipts that are already archived are no longer written again. Delivery counts are unchanged: the copies never raised the recorded count.
|
|
23
|
+
- A pooled child workflow resumed more than once no longer fails with `Pool checkpoint identity conflict`. Replaying a completed stage records a small replay-metadata checkpoint under an id derived from the stage, stamped with the time of that replay. The first resume recorded it. When a later resume, or a replacement worker, replayed the stage again, the pool coordinator compared the new record byte for byte with the stored one, saw a different time and failed the child. In a 640-child load test, 31 children failed this way after one SIGKILL and resume. A re-recorded checkpoint that differs only in its record time now keeps the first write. A checkpoint with any other difference is still rejected.
|
|
24
|
+
- A large workflow fan-out no longer pins the workflow host's main thread rebuilding Intercom routes. Every store change re-announced the route of every run (480 children in a load test), so under a stream of messages and delivery receipts the host spent most of its time on announcements and Intercom sends began to time out. Store changes now publish routes in batches about 25 ms apart, spaced further when needed so announcing takes at most about a fifth of the host's time, and a stage that waits for its route still gets the latest one first.
|
|
25
|
+
- A pending-message write that loses a race with another writer to the same run (for example a stage's delivery receipt) no longer stores the run's whole list of pending stage messages in the journal. It is rebuilt as a change on top of the other writer's entry and retried, and only falls back to the full list after repeated conflicts. In a 160-worker fan-out, 2% of queued events had fallen back and written 93% of their bytes, up to 1.5 MB each and growing with the list ([#3929](https://github.com/bastani-inc/atomic/issues/3929))
|
|
26
|
+
- A workflow whose stages send many Intercom messages to stage patterns (for example `workflow:<rootRunId>/child-*/**`) no longer saturates the workflow host. The host matched every queued pattern message against every stage on each change; it now indexes the patterns and each stage's paths once. In a 150-child fan-out test, delivered sends went from 566 of 900 to 900 of 900 and host event-loop lag p99 from about 1 s to under 200 ms ([#3931](https://github.com/bastani-inc/atomic/issues/3931))
|
|
27
|
+
- Workflow journal events no longer store a run's whole list of pending stage messages each time. Pattern sends stay queued until the root run ends, so every queued message rewrote the growing list and the journal grew quadratically with the number of sends. Each event now records only what changed; older journals still read as before ([#3929](https://github.com/bastani-inc/atomic/issues/3929))
|
|
28
|
+
- 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))
|
|
29
|
+
- Running many local workers at once no longer freezes the interactive TUI. A local worker pool now runs its workers under a separate pool coordinator process, which handles worker messages, heartbeats, pool journal writes and the Intercom routes of pooled children. The root process only receives periodic progress summaries. If the coordinator process dies, its workers exit with it and `ctx.workflow(child)` throws a retryable `WorkflowInfrastructureError` with code `worker_lost` and the message `Local worker pool coordinator exited (…)` ([#3903](https://github.com/bastani-inc/atomic/issues/3903))
|
|
30
|
+
- Local worker heartbeats and session appends no longer grow the workflow journal on every renewal or entry. Lease renewals update the child's work entry in place instead of adding a row, and session appends no longer repeat the stage checkpoint and the full request with each entry. The first time a pool opens an existing journal, it removes the stored renewal rows and keeps only a digest of each recorded pool request; older runs stay readable and resumable ([#3903](https://github.com/bastani-inc/atomic/issues/3903))
|
|
31
|
+
- `/workflow status` no longer reports `durable checkpoint topology is malformed or incomplete` for a running or crashed root, for example one that was killed, whose child workflow had failed or been skipped while the root kept going. Resuming such a root also replays its completed children from their checkpoints again instead of failing ([#3903](https://github.com/bastani-inc/atomic/issues/3903))
|
|
32
|
+
- Stages of child workflows that run on local workers no longer fail every Intercom call (`join`, `list`, `status`, `send`) with `Live workflow-stage route has no registered workflow owner` when a root fans out many children at once. A pooled stage now starts only after the broker holds its run's Intercom route, and a stage whose route is registering again after a dropped connection waits for it instead of failing. All pooled runs of one root invocation share one route connection, one per invocation rather than per host because the broker grants route ownership per invocation group. A run whose route the broker refuses moves to a connection of its own, so it no longer takes the other runs' routes down with it; only refused runs get one, so the number of extra connections is bounded by the refused runs, not by the number of runs. Pausing, resuming or stopping the root while a stage waits for its route still takes effect at once ([#3904](https://github.com/bastani-inc/atomic/issues/3904))
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- Route announcements carry at most 5 sticky delivery states per possible-stage row, with per-state totals and the number omitted. Each state leaves out its target, which the row already names. Workflow status and run inspection list at most 5 delivery states per run in the same way, with the same omitted-states line. Previously each announcement, `intercom list` result and status result carried every delivery state. With 8,000 queued messages across 163 rows, an announcement shrinks from about 4.6 MB to about 0.19 MB.
|
|
37
|
+
- Workflow stages now default to peer-to-peer (swarm) exchange with no limits on communication between agents: stages coordinate with each other directly instead of relaying through the supervisor, and each stage still returns its own structured result. Workflow guidance now scales swarms to hundreds or thousands of agents for very complex work or requested heavy parallelism, organized into named subgroups, per-stage subagent sets and child workflows. The `defaultConcurrency` setting, which bounds how many workflow stages and `ctx.parallel(...)` tasks run at once when no per-call `concurrency` is given, now defaults to 1000 instead of 3; set it lower to limit concurrency.
|
|
38
|
+
- The `maxDepth` setting, which limits how many workflow levels `ctx.workflow(...)` can nest, now defaults to 3 instead of 4. Set it higher in the workflow config to allow deeper nesting.
|
|
39
|
+
|
|
9
40
|
## [0.9.44-alpha.6] - 2026-10-25
|
|
10
41
|
|
|
11
42
|
### Fixed
|
|
@@ -2072,6 +2072,9 @@ var init_workflow_stage_target = __esm(() => {
|
|
|
2072
2072
|
});
|
|
2073
2073
|
|
|
2074
2074
|
// dist/builtin/workflows/src/shared/pending-stage-status.ts
|
|
2075
|
+
function isAddressableStageStatus(status) {
|
|
2076
|
+
return ADDRESSABLE_STAGE_STATUSES.has(status);
|
|
2077
|
+
}
|
|
2075
2078
|
function workflowBoundarySegments(runs, runId) {
|
|
2076
2079
|
return createWorkflowBoundarySegmentsResolver(runs)(runId);
|
|
2077
2080
|
}
|
|
@@ -2220,9 +2223,17 @@ function pendingWorkflowStageStatuses(run, resolveOwningRunStatus, resolveBounda
|
|
|
2220
2223
|
return pending === undefined ? [] : [pending];
|
|
2221
2224
|
});
|
|
2222
2225
|
}
|
|
2226
|
+
var ADDRESSABLE_STAGE_STATUSES;
|
|
2223
2227
|
var init_pending_stage_status = __esm(() => {
|
|
2224
2228
|
init_store_internal();
|
|
2225
2229
|
init_workflow_stage_target();
|
|
2230
|
+
ADDRESSABLE_STAGE_STATUSES = new Set([
|
|
2231
|
+
"pending",
|
|
2232
|
+
"running",
|
|
2233
|
+
"awaiting_input",
|
|
2234
|
+
"paused",
|
|
2235
|
+
"blocked"
|
|
2236
|
+
]);
|
|
2226
2237
|
});
|
|
2227
2238
|
|
|
2228
2239
|
// dist/builtin/workflows/src/shared/timing.ts
|
|
@@ -62531,7 +62542,7 @@ function registerPendingStageIntercomBridge(pi, activeStore, ownsRun = () => tru
|
|
|
62531
62542
|
stageName: stage.name,
|
|
62532
62543
|
target: stageRouteTarget(runs, rootRunId, run.id, stage.id, resolveBoundarySegments),
|
|
62533
62544
|
lifecycle: stage.sessionId === undefined && stage.sessionFile === undefined ? "pending" : "running",
|
|
62534
|
-
routeEligible: stage.pendingStageDeliveryAvailable === true && (stage.status
|
|
62545
|
+
routeEligible: stage.pendingStageDeliveryAvailable === true && isAddressableStageStatus(stage.status),
|
|
62535
62546
|
group: stage.intercomGroup ?? workflowInvocationIntercomGroup(rootRunId)
|
|
62536
62547
|
})),
|
|
62537
62548
|
...nonAgentNodes(run).map((node) => ({
|
|
@@ -63034,7 +63045,7 @@ function knownUninitializedStage(run, stageKey) {
|
|
|
63034
63045
|
if (candidates.length !== 1)
|
|
63035
63046
|
return;
|
|
63036
63047
|
const stage = candidates[0];
|
|
63037
|
-
return (stage.status
|
|
63048
|
+
return isAddressableStageStatus(stage.status) && stage.sessionId === undefined && stage.sessionFile === undefined ? stage : undefined;
|
|
63038
63049
|
}
|
|
63039
63050
|
function pendingStageDestination(run, entry) {
|
|
63040
63051
|
if (entry.stageId !== undefined) {
|
|
@@ -67730,15 +67741,74 @@ function isWorkflowPendingStageDeliveryFailure(error) {
|
|
|
67730
67741
|
return true;
|
|
67731
67742
|
return error?.code === "pending_stage_delivery_failed";
|
|
67732
67743
|
}
|
|
67733
|
-
function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageName, commit = (action) => action()) {
|
|
67744
|
+
function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageName, commit = (action) => action(), options = {}) {
|
|
67734
67745
|
let resolveReady;
|
|
67735
67746
|
let rejectReady;
|
|
67736
67747
|
let readyPromise;
|
|
67737
67748
|
let drainError;
|
|
67738
67749
|
let terminalError;
|
|
67739
67750
|
let drainPromise;
|
|
67751
|
+
let drainedSessionId;
|
|
67740
67752
|
let currentRecipient;
|
|
67741
|
-
|
|
67753
|
+
let lastProgressAt = Date.now();
|
|
67754
|
+
let stallReported = false;
|
|
67755
|
+
const progress = () => {
|
|
67756
|
+
lastProgressAt = Date.now();
|
|
67757
|
+
};
|
|
67758
|
+
const progressingCommit = async (action) => {
|
|
67759
|
+
try {
|
|
67760
|
+
return await commit(action);
|
|
67761
|
+
} finally {
|
|
67762
|
+
progress();
|
|
67763
|
+
}
|
|
67764
|
+
};
|
|
67765
|
+
const reportStall = (sessionId) => {
|
|
67766
|
+
if (stallReported)
|
|
67767
|
+
return;
|
|
67768
|
+
stallReported = true;
|
|
67769
|
+
const undelivered = [
|
|
67770
|
+
...activeStore.pendingStageMessagesFor(runId, stageId),
|
|
67771
|
+
...stageId === stageName ? [] : activeStore.pendingStageMessagesFor(runId, stageName)
|
|
67772
|
+
].map((entry) => `${entry.message.id} (direct → ${entry.stageKey})`).concat(stickyPendingStageEntriesForStage(activeStore, runId, stageId, stageName, sessionId === undefined ? undefined : { sessionId }).map((entry) => `${entry.message.id} (sticky → ${entry.targetPath ?? entry.stageKey})`));
|
|
67773
|
+
const listed = undelivered.slice(0, 10).join(", ");
|
|
67774
|
+
const more = undelivered.length > 10 ? `, and ${undelivered.length - 10} more` : "";
|
|
67775
|
+
const message = `atomic-workflows: stage "${stageName}" (${stageId}) is starting without its queued Intercom messages: ` + `no delivery progress for ${PENDING_STAGE_READY_STALL_MS / 1000}s; ` + `drain ${drainPromise === undefined ? "not running" : "running but hung"}; ` + `undelivered: ${listed.length === 0 ? "none" : `${listed}${more}`}. ` + "They stay queued and arrive live or on the stage's next drain.";
|
|
67776
|
+
try {
|
|
67777
|
+
(options.reportStall ?? console.warn)(message);
|
|
67778
|
+
} catch {}
|
|
67779
|
+
};
|
|
67780
|
+
const watchStall = (pending, sessionId, onStall) => {
|
|
67781
|
+
let timer;
|
|
67782
|
+
let settled = false;
|
|
67783
|
+
const check = () => {
|
|
67784
|
+
if (settled)
|
|
67785
|
+
return;
|
|
67786
|
+
const remaining = lastProgressAt + PENDING_STAGE_READY_STALL_MS - Date.now();
|
|
67787
|
+
if (remaining > 0) {
|
|
67788
|
+
timer = setTimeout(check, remaining);
|
|
67789
|
+
timer.unref?.();
|
|
67790
|
+
return;
|
|
67791
|
+
}
|
|
67792
|
+
reportStall(sessionId);
|
|
67793
|
+
onStall();
|
|
67794
|
+
};
|
|
67795
|
+
const stop = () => {
|
|
67796
|
+
settled = true;
|
|
67797
|
+
clearTimeout(timer);
|
|
67798
|
+
};
|
|
67799
|
+
pending.then(stop, stop);
|
|
67800
|
+
check();
|
|
67801
|
+
};
|
|
67802
|
+
const boundedDrain = (drain, sessionId) => {
|
|
67803
|
+
const waiting = new Promise((resolve, reject) => {
|
|
67804
|
+
drain.then(resolve, reject);
|
|
67805
|
+
watchStall(drain, sessionId, resolve);
|
|
67806
|
+
});
|
|
67807
|
+
waiting.catch(() => {});
|
|
67808
|
+
return waiting;
|
|
67809
|
+
};
|
|
67810
|
+
const hasUndeliveredExact = () => activeStore.pendingStageMessagesFor(runId, stageId).length > 0 || stageId !== stageName && activeStore.pendingStageMessagesFor(runId, stageName).length > 0;
|
|
67811
|
+
const hasUndelivered = (sessionId) => hasUndeliveredExact() || stickyPendingStageEntriesForStage(activeStore, runId, stageId, stageName, sessionId === undefined ? undefined : { sessionId }).length > 0;
|
|
67742
67812
|
const pendingReady = () => {
|
|
67743
67813
|
if (readyPromise === undefined) {
|
|
67744
67814
|
readyPromise = new Promise((resolve, reject) => {
|
|
@@ -67748,35 +67818,64 @@ function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageNa
|
|
|
67748
67818
|
}
|
|
67749
67819
|
return readyPromise;
|
|
67750
67820
|
};
|
|
67821
|
+
let followUp;
|
|
67822
|
+
const startDrain = (deliver, recipient) => {
|
|
67823
|
+
if (terminalError !== undefined)
|
|
67824
|
+
return Promise.resolve();
|
|
67825
|
+
drainError = undefined;
|
|
67826
|
+
progress();
|
|
67827
|
+
const attempt = deliverPendingStageMessages(activeStore, runId, stageId, stageName, async (from, message) => {
|
|
67828
|
+
progress();
|
|
67829
|
+
await deliver(from, message);
|
|
67830
|
+
progress();
|
|
67831
|
+
}, progressingCommit, recipient);
|
|
67832
|
+
const drain = attempt.then(() => {
|
|
67833
|
+
progress();
|
|
67834
|
+
drainedSessionId = recipient?.sessionId;
|
|
67835
|
+
resolveReady?.();
|
|
67836
|
+
readyPromise = undefined;
|
|
67837
|
+
resolveReady = undefined;
|
|
67838
|
+
rejectReady = undefined;
|
|
67839
|
+
if (drainPromise === drain)
|
|
67840
|
+
drainPromise = undefined;
|
|
67841
|
+
}, (error) => {
|
|
67842
|
+
progress();
|
|
67843
|
+
drainError = error;
|
|
67844
|
+
rejectReady?.(error);
|
|
67845
|
+
readyPromise = undefined;
|
|
67846
|
+
resolveReady = undefined;
|
|
67847
|
+
rejectReady = undefined;
|
|
67848
|
+
if (drainPromise === drain)
|
|
67849
|
+
drainPromise = undefined;
|
|
67850
|
+
throw error;
|
|
67851
|
+
});
|
|
67852
|
+
drainPromise = drain;
|
|
67853
|
+
return boundedDrain(drain, recipient?.sessionId);
|
|
67854
|
+
};
|
|
67855
|
+
const requestDrain = (deliver, recipient) => {
|
|
67856
|
+
const running = drainPromise;
|
|
67857
|
+
if (running === undefined)
|
|
67858
|
+
return startDrain(deliver, recipient);
|
|
67859
|
+
if (followUp !== undefined) {
|
|
67860
|
+
followUp.deliver = deliver;
|
|
67861
|
+
followUp.recipient = recipient;
|
|
67862
|
+
return boundedDrain(followUp.settled.promise, recipient?.sessionId);
|
|
67863
|
+
}
|
|
67864
|
+
const next = { deliver, recipient, settled: Promise.withResolvers() };
|
|
67865
|
+
next.settled.promise.catch(() => {});
|
|
67866
|
+
followUp = next;
|
|
67867
|
+
running.catch(() => {}).then(() => {
|
|
67868
|
+
if (followUp === next)
|
|
67869
|
+
followUp = undefined;
|
|
67870
|
+
return requestDrain(next.deliver, next.recipient);
|
|
67871
|
+
}).then(next.settled.resolve, next.settled.reject);
|
|
67872
|
+
return boundedDrain(next.settled.promise, recipient?.sessionId);
|
|
67873
|
+
};
|
|
67751
67874
|
return {
|
|
67752
67875
|
routeCapability: workflowPendingStageRouteCapability(activeStore, runId),
|
|
67753
67876
|
deliverPending(deliver, recipient) {
|
|
67754
67877
|
currentRecipient = recipient;
|
|
67755
|
-
|
|
67756
|
-
return drainPromise;
|
|
67757
|
-
if (terminalError !== undefined)
|
|
67758
|
-
return Promise.resolve();
|
|
67759
|
-
drainError = undefined;
|
|
67760
|
-
const attempt = deliverPendingStageMessages(activeStore, runId, stageId, stageName, deliver, commit, recipient);
|
|
67761
|
-
const drain = attempt.then(() => {
|
|
67762
|
-
resolveReady?.();
|
|
67763
|
-
readyPromise = undefined;
|
|
67764
|
-
resolveReady = undefined;
|
|
67765
|
-
rejectReady = undefined;
|
|
67766
|
-
if (drainPromise === drain)
|
|
67767
|
-
drainPromise = undefined;
|
|
67768
|
-
}, (error) => {
|
|
67769
|
-
drainError = error;
|
|
67770
|
-
rejectReady?.(error);
|
|
67771
|
-
readyPromise = undefined;
|
|
67772
|
-
resolveReady = undefined;
|
|
67773
|
-
rejectReady = undefined;
|
|
67774
|
-
if (drainPromise === drain)
|
|
67775
|
-
drainPromise = undefined;
|
|
67776
|
-
throw error;
|
|
67777
|
-
});
|
|
67778
|
-
drainPromise = drain;
|
|
67779
|
-
return drainPromise;
|
|
67878
|
+
return requestDrain(deliver, recipient);
|
|
67780
67879
|
},
|
|
67781
67880
|
ready(sessionId) {
|
|
67782
67881
|
if (!hasUndelivered(sessionId)) {
|
|
@@ -67786,6 +67885,8 @@ function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageNa
|
|
|
67786
67885
|
return Promise.reject(terminalError);
|
|
67787
67886
|
if (drainError !== undefined)
|
|
67788
67887
|
return Promise.reject(drainError);
|
|
67888
|
+
if (drainPromise === undefined && sessionId !== undefined && drainedSessionId === sessionId && !hasUndeliveredExact())
|
|
67889
|
+
return;
|
|
67789
67890
|
const ready = pendingReady();
|
|
67790
67891
|
if (sessionId !== undefined && activeStore.pendingStageMessagesFor(runId, stageId).length === 0 && activeStore.pendingStageMessagesFor(runId, stageName).length === 0) {
|
|
67791
67892
|
const entries = stickyPendingStageEntriesForStage(activeStore, runId, stageId, stageName, { sessionId });
|
|
@@ -67809,6 +67910,14 @@ function createWorkflowPendingStageDelivery(activeStore, runId, stageId, stageNa
|
|
|
67809
67910
|
rejectReady?.(error);
|
|
67810
67911
|
});
|
|
67811
67912
|
}
|
|
67913
|
+
watchStall(ready, sessionId, () => {
|
|
67914
|
+
if (readyPromise !== ready)
|
|
67915
|
+
return;
|
|
67916
|
+
resolveReady?.();
|
|
67917
|
+
readyPromise = undefined;
|
|
67918
|
+
resolveReady = undefined;
|
|
67919
|
+
rejectReady = undefined;
|
|
67920
|
+
});
|
|
67812
67921
|
return ready;
|
|
67813
67922
|
},
|
|
67814
67923
|
fail(reason) {
|
|
@@ -67936,7 +68045,7 @@ function toPendingStageSender(entry) {
|
|
|
67936
68045
|
lastActivity: entry.from.lastActivity ?? entry.message.timestamp
|
|
67937
68046
|
};
|
|
67938
68047
|
}
|
|
67939
|
-
var pendingDeliveryClaims, WorkflowPendingStageDeliveryFailedError;
|
|
68048
|
+
var pendingDeliveryClaims, WorkflowPendingStageDeliveryFailedError, PENDING_STAGE_READY_STALL_MS = 60000;
|
|
67940
68049
|
var init_pending_stage_delivery2 = __esm(() => {
|
|
67941
68050
|
init_run_owner_backend();
|
|
67942
68051
|
init_store_backend();
|
|
@@ -90467,6 +90576,7 @@ init_sdk_recovery_scope();
|
|
|
90467
90576
|
init_intercom_group();
|
|
90468
90577
|
init_pending_stage_delivery();
|
|
90469
90578
|
init_pending_stage_route_capability();
|
|
90579
|
+
init_pending_stage_status();
|
|
90470
90580
|
|
|
90471
90581
|
// dist/builtin/workflows/src/shared/persistence-restore.ts
|
|
90472
90582
|
init_persistence_restore_helpers();
|
|
@@ -90587,13 +90697,6 @@ var PENDING_STAGE_MESSAGE_EVENT3 = "atomic:workflow-pending-stage-message";
|
|
|
90587
90697
|
var PENDING_STAGE_ROUTE_REVOKED_EVENT = "atomic:workflow-pending-stage-route-revoked";
|
|
90588
90698
|
var DORMANT_RUN_SUBAGENT_HOLD_EVENT = "atomic:workflow-dormant-run-subagent-hold";
|
|
90589
90699
|
var DORMANT_RUN_RECHECK_MS = 15000;
|
|
90590
|
-
var ADDRESSABLE_STAGE_STATUSES = new Set([
|
|
90591
|
-
"pending",
|
|
90592
|
-
"running",
|
|
90593
|
-
"awaiting_input",
|
|
90594
|
-
"paused",
|
|
90595
|
-
"blocked"
|
|
90596
|
-
]);
|
|
90597
90700
|
var DORMANT_ASK_REFUSAL = "Cannot ask a stage of an interrupted workflow run. Use send; Atomic will queue the message until the run is resumed.";
|
|
90598
90701
|
function registerDormantRunIntercomRoutes(pi, activeStore, deps) {
|
|
90599
90702
|
const reportWarning = createWorkflowBackgroundWarningReporter(pi);
|
|
@@ -90882,7 +90985,7 @@ function queueRefusal(result, target) {
|
|
|
90882
90985
|
return { outcome: "refused", reason: "Target workflow run is in a different intercom group" };
|
|
90883
90986
|
}
|
|
90884
90987
|
function isAddressableStage(stage) {
|
|
90885
|
-
return
|
|
90988
|
+
return isAddressableStageStatus(stage.status) && stage.nodeKind !== "tool" && stage.replayKey?.startsWith("prompt:") !== true;
|
|
90886
90989
|
}
|
|
90887
90990
|
function isDormantMessageEvent(value2) {
|
|
90888
90991
|
if (typeof value2 !== "object" || value2 === null)
|