@bastani/atomic 0.9.3-alpha.6 → 0.9.4-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/dist/builtin/cursor/CHANGELOG.md +30 -0
- package/dist/builtin/cursor/package.json +2 -2
- package/dist/builtin/intercom/CHANGELOG.md +12 -0
- package/dist/builtin/intercom/package.json +1 -1
- package/dist/builtin/mcp/CHANGELOG.md +16 -0
- package/dist/builtin/mcp/package.json +1 -1
- package/dist/builtin/subagents/CHANGELOG.md +23 -0
- package/dist/builtin/subagents/package.json +1 -1
- package/dist/builtin/web-access/CHANGELOG.md +12 -0
- package/dist/builtin/web-access/package.json +1 -1
- package/dist/builtin/workflows/CHANGELOG.md +32 -0
- package/dist/builtin/workflows/README.md +4 -4
- package/dist/builtin/workflows/package.json +1 -1
- package/dist/builtin/workflows/src/engine/run-durable-finalize.ts +4 -2
- package/dist/builtin/workflows/src/engine/run-returned-status.ts +41 -0
- package/dist/builtin/workflows/src/engine/run.ts +6 -4
- package/dist/builtin/workflows/src/extension/config-file-loader.ts +1 -1
- package/dist/builtin/workflows/src/extension/config-loader.ts +1 -1
- package/dist/builtin/workflows/src/extension/lifecycle-notifications.ts +37 -12
- package/dist/builtin/workflows/src/shared/persistence-restore-helpers.ts +1 -1
- package/dist/builtin/workflows/src/shared/store-public-types.ts +3 -2
- package/dist/builtin/workflows/src/shared/store-run-methods.ts +6 -1
- package/dist/core/http-dispatcher.d.ts +3 -3
- package/dist/core/http-dispatcher.d.ts.map +1 -1
- package/dist/core/http-dispatcher.js +19 -6
- package/dist/core/http-dispatcher.js.map +1 -1
- package/dist/core/settings-manager-basic-accessors.d.ts.map +1 -1
- package/dist/core/settings-manager-basic-accessors.js +1 -1
- package/dist/core/settings-manager-basic-accessors.js.map +1 -1
- package/dist/modes/print-mode.d.ts.map +1 -1
- package/dist/modes/print-mode.js +15 -13
- package/dist/modes/print-mode.js.map +1 -1
- package/docs/settings.md +4 -4
- package/docs/workflows.md +5 -3
- package/npm-shrinkwrap.json +4299 -0
- package/package.json +7 -4
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Restored upstream Pi's provider retry default by leaving `retry.provider.maxRetries` unset/zero unless users configure it explicitly, so Atomic's agent-level retry observes provider transport failures directly instead of the SDK retrying them first.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Aligned Atomic's global HTTP dispatcher with upstream Pi by removing the fixed 10-second connect-phase timeout and installing undici's fetch implementation alongside the configured proxy dispatcher, avoiding spurious provider `Connection error` failures behind policy/proxy egress layers.
|
|
14
|
+
- Made headless JSON print mode return a nonzero exit code when the final assistant turn ends with `stopReason: "error"` or `"aborted"`, so Pier and other harnesses can mark provider failures as agent errors.
|
|
15
|
+
- Declared `lru-cache` as a direct runtime dependency and added publish-time `npm-shrinkwrap.json` generation, matching upstream Pi's deterministic npm installs and fixing strict pnpm global installs that could not resolve Atomic's hashline and fetch-tool cache imports.
|
|
16
|
+
- Updated user-facing workflow documentation for the blocked lifecycle notice state, the default `workflowNotifications.notifyOn` set, and the reserved top-level workflow output `status` convention for returned `failed`/`blocked` run statuses.
|
|
17
|
+
|
|
18
|
+
## [0.9.3] - 2026-06-29
|
|
19
|
+
|
|
20
|
+
### Breaking Changes
|
|
21
|
+
|
|
22
|
+
- Replaced the legacy exact-replacement `edit` tool API with the hashline-only `input` script schema; callers must use snapshot tags from `read`/`search`/`write`/`edit` instead of `path` + `edits[]` or `oldText`/`newText`.
|
|
23
|
+
- Tightened the model-facing `read`, `find`, and `search` schemas to the new builtin contracts (`read` path selectors, required `find.paths`, and the narrowed `search` option set).
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- Added first-run onboarding that routes pasted tickets, specs, and tasks into normal Atomic sessions with scope-estimation guidance and workflow handoff next steps.
|
|
28
|
+
- Added internal workflow-stage session marking so workflow-created sessions stay out of ordinary `/resume`, `atomic -r`, and `--continue` history while remaining accessible through workflow resume/status surfaces.
|
|
29
|
+
- Added first-class `find`/`search` builtins, hashline snapshot anchors, hashline edit scripts with stale-tag safety, a disabled-by-default Bash Interceptor toggle, Rust-backed `pty:true` bash execution, and native glob/grep/search bindings for oh-my-pi parity.
|
|
30
|
+
|
|
31
|
+
### Changed
|
|
32
|
+
|
|
33
|
+
- Synced bundled upstream Pi runtime packages to `^0.80.2` and routed legacy pi-ai imports through the temporary `/compat` entrypoint, including virtual-module and Jiti aliases for first-party and user-installed extensions.
|
|
34
|
+
- Enabled provider/SDK retries by default, raised the standard HTTP idle timeout to 10 minutes, and added a 10-second connect-phase timeout so transient socket drops retry while unreachable hosts fail fast.
|
|
35
|
+
- Accounted for image content blocks in context-window estimates and compaction budgets, raised delegated workflow/subagent nesting to the shared five-level maximum, and exported the canonical schema-aware flattened-argument helper used by host and MCP tool execution.
|
|
36
|
+
- Updated workflow and user-facing documentation so structured, validation-heavy, implementation, debugging, migration, and loop-shaped requests are routed to workflows by default.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
|
|
40
|
+
- Fixed custom tool renderer cleanup, persisted-context replay after context compaction, and multiple first-run onboarding edge cases around placeholders, file references, isolated config discovery, saved tasks, `/import`, `/model`, and `/new`.
|
|
41
|
+
- Completed the oh-my-pi builtin parity pass across `read`, `write`, `find`, `search`, `edit`, `bash`, archive, document, URL, internal-resource, conflict, and SQLite selectors, including pagination, truncation metadata, native cache invalidation, copied-hashline stripping, async/PTY behavior, and cross-platform path handling.
|
|
42
|
+
- Hardened generated-file, URL, `local://`, SQLite, archive, native PTY/search, and bash-interceptor paths against traversal, SSRF, unsafe raw SQL/archive cases, stale cache writes, native panics, leaked async output, and provider-hostile argument shapes.
|
|
43
|
+
- Fixed compiled release binary packaging and cross-platform package tests by externalizing `mupdf`, preparing native bindings and fixtures in CI, running the coding-agent Vitest suite under Bun, and hardening Windows path, color, and process-spawn coverage.
|
|
44
|
+
|
|
5
45
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
6
46
|
|
|
7
47
|
### Changed
|
|
@@ -2,6 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Published a synchronized Atomic 0.9.4-alpha.1 prerelease for the Cursor provider package; no functional Cursor provider changes were made after 0.9.3.
|
|
10
|
+
|
|
11
|
+
## [0.9.3] - 2026-06-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added scoped Cursor image-input support for known multimodal Claude, Composer, Gemini, GPT, Kimi, and Grok 4.3 model families, including selected-image request serialization and mixed text/image MCP tool-result serialization.
|
|
16
|
+
- Added `cursor/grok-4.3` to the estimated fallback catalog.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Aligned the Cursor provider dependency on upstream pi-ai `^0.80.2` and retargeted legacy provider/model imports to the `@earendil-works/pi-ai/compat` entrypoint.
|
|
21
|
+
- Resolved Cursor model context windows and max output tokens from Atomic's bundled pi-ai model catalog while preserving positive live limits, ignoring bogus non-positive values, and keeping conservative estimates only for Cursor-only models.
|
|
22
|
+
- Marked Cursor OAuth as **Cursor (Experimental)** in the `/login` picker.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Fixed effort-variant-only Cursor models failing no-thinking requests by recording and sending a concrete default variant instead of an unsendable synthesized base id.
|
|
27
|
+
- Preserved explicit `1M` Cursor context floors across fallback catalog matches and made the pi-ai catalog a runtime dependency so limit fallback remains available at provider startup.
|
|
28
|
+
- Exposed `xhigh` only for Cursor models whose live or estimated catalog includes a real `xhigh`/`max` variant, with saved `xhigh` selections falling back to the nearest sendable variant.
|
|
29
|
+
- Rejected empty or malformed base64 image payloads during Cursor selected-image and MCP image serialization while accepting valid MIME-wrapped base64 with whitespace.
|
|
30
|
+
|
|
31
|
+
### Removed
|
|
32
|
+
|
|
33
|
+
- Removed outdated Cursor Grok 4.20 fallback entries and stopped advertising unsupported Grok-family Cursor IDs as image-capable.
|
|
34
|
+
|
|
5
35
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
6
36
|
|
|
7
37
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/cursor",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Experimental first-party Atomic extension for Cursor OAuth, model discovery, and streaming provider registration.",
|
|
6
6
|
"contributors": [
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
}
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
|
-
"@bastani/atomic-natives": "0.9.
|
|
43
|
+
"@bastani/atomic-natives": "0.9.4-alpha.1",
|
|
44
44
|
"@bufbuild/protobuf": "^2.0.0",
|
|
45
45
|
"@earendil-works/pi-ai": "^0.80.2"
|
|
46
46
|
}
|
|
@@ -4,6 +4,18 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Published a synchronized Atomic 0.9.4-alpha.1 prerelease for the intercom extension; no intercom extension changes were made after 0.9.3.
|
|
12
|
+
|
|
13
|
+
## [0.9.3] - 2026-06-29
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Published the stable Atomic 0.9.3 release for the intercom extension with its upstream pi TUI peer dependency aligned to `^0.80.2`; no intercom extension source changes were needed after 0.9.2.
|
|
18
|
+
|
|
7
19
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
8
20
|
|
|
9
21
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/intercom",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension providing a private coordination channel between parent and child agent sessions. Fork of: https://github.com/nicobailon/pi-intercom",
|
|
6
6
|
"contributors": [
|
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Published a synchronized Atomic 0.9.4-alpha.1 prerelease for the MCP extension; no MCP extension changes were made after 0.9.3.
|
|
15
|
+
|
|
16
|
+
## [0.9.3] - 2026-06-29
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Aligned the MCP extension peer dependencies with upstream pi `^0.80.2` and retargeted legacy provider/model imports to `@earendil-works/pi-ai/compat`, preserving MCP sampling behavior under the updated pi-ai package layout.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Made `unflattenToolArguments` schema-aware so literal dotted top-level argument keys such as `filter.name` are preserved unless the tool input schema proves the key represents a nested path, while bracket-indexed array/object keys are still reconstructed for direct-tool and proxy/gateway calls.
|
|
25
|
+
|
|
10
26
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
11
27
|
|
|
12
28
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension that adapts MCP (Model Context Protocol) servers into the coding agent. Fork of: https://github.com/nicobailon/pi-mcp-adapter",
|
|
6
6
|
"contributors": [
|
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Published a synchronized Atomic 0.9.4-alpha.1 prerelease for the subagents extension; no subagents extension changes were made after 0.9.3.
|
|
10
|
+
|
|
11
|
+
## [0.9.3] - 2026-06-29
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- Added OpenRouter fallback coverage to bundled subagent definitions so delegated codebase analysis, research, debugging, simplification, and worker sessions can recover through OpenRouter-hosted frontier, GLM, and Gemini providers when native providers are unavailable.
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- Aligned the subagents extension peer dependencies with upstream pi `^0.80.2` runtime packages and retargeted legacy provider/model imports to `@earendil-works/pi-ai/compat`.
|
|
20
|
+
- Raised the default and hard maximum subagent nesting budget to five delegated levels, extending nested-run observability to the same depth.
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Prevented async and foreground subagent status widgets from flickering or unmounting during host UI refreshes, tall-panel redraws, and reset-and-hydrate cycles with active background runs.
|
|
25
|
+
- Fixed live subagent result animation cleanup so terminal workflow rows evict animation registry entries instead of leaving stale intervals.
|
|
26
|
+
- Synced upstream subagent hardening for compact tool-call summaries, nested fanout call/result history, duplicate dispatch rejection, provider-safe chain schemas, failed foreground diagnostics, builtin `search` MCP allowlist filtering, and updated builtin `find`/`search` tool guidance.
|
|
27
|
+
|
|
5
28
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
6
29
|
|
|
7
30
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/subagents",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
4
4
|
"private": true,
|
|
5
5
|
"description": "Atomic extension for delegating tasks to subagents with chains, parallel execution, and TUI clarification. Fork of: https://github.com/nicobailon/pi-subagents",
|
|
6
6
|
"contributors": [
|
|
@@ -4,6 +4,18 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [0.9.4-alpha.1] - 2026-06-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Published a synchronized Atomic 0.9.4-alpha.1 prerelease for the web-access extension; no web-access extension changes were made after 0.9.3.
|
|
12
|
+
|
|
13
|
+
## [0.9.3] - 2026-06-29
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Published the stable Atomic 0.9.3 release for the web-access extension with its upstream pi TUI peer dependency aligned to `^0.80.2` and legacy pi-ai summary/model helper imports retargeted to the `/compat` entrypoint; no web-access extension source changes were needed after 0.9.2.
|
|
18
|
+
|
|
7
19
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
8
20
|
|
|
9
21
|
### Changed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bastani/web-access",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.4-alpha.1",
|
|
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,38 @@ 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.4-alpha.1] - 2026-06-29
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- Fixed workflow lifecycle notices for structured workflow outputs whose returned `status` is `failed` or `blocked`: the runtime now records those runs as failed/blocked instead of successful completions, preserves their returned output, carries blocked summaries into lifecycle notice reasons, marks returned failures non-resumable with explicit terminal failure metadata, and emits failure/blocked lifecycle cards rather than success/checkmark notices.
|
|
14
|
+
|
|
15
|
+
## [0.9.3] - 2026-06-29
|
|
16
|
+
|
|
17
|
+
### Breaking Changes
|
|
18
|
+
|
|
19
|
+
- Enabled workflow durability by default with a zero-infrastructure per-workflow file backend under `~/.atomic/workflow-durable`, replacing the previous process-local in-memory default and removing the old `ATOMIC_WORKFLOW_DURABLE_DIR` opt-in path. Explicit in-memory durability remains available through custom/test backends or `ATOMIC_WORKFLOW_DURABLE=0`/`false`/`off`/`memory`.
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
|
|
23
|
+
- Added `StageContext.sendUserMessage(content, { deliverAs? })` so workflow authors can inject follow-on user turns into live stage sessions after `prompt()` resolves while preserving MCP scope, abort handling, and structured-output guards.
|
|
24
|
+
- Added cross-session durable workflow resumability with cached `ctx.tool`, `ctx.ui`, `ctx.stage`/`ctx.task`/`ctx.chain`/`ctx.parallel`, and child-workflow checkpoints, plus `/workflow resume` discovery of durable workflow history from session metadata and the durable backend.
|
|
25
|
+
- Added optional DBOS-backed durability through the lazily loaded `@dbos-inc/dbos-sdk`, including DBOS read-side hydration for resumable workflows when `DBOS_SYSTEM_DATABASE_URL` is set.
|
|
26
|
+
- Added OpenRouter fallback coverage to builtin `goal`, `ralph`, `deep-research-codebase`, and `open-claude-design` workflow model configs.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- Marked workflow stage sessions as internal so they are excluded from normal Atomic resume history while remaining inspectable and resumable through workflow-specific commands and tools.
|
|
31
|
+
- Revised builtin `goal`, `ralph`, and `open-claude-design` prompts to emphasize full objective completion, stable non-ordinal artifacts, a shared five-level subagent nesting budget, and model-facing workflow guidance that routes non-trivial structured work to workflows by default.
|
|
32
|
+
- Aligned the workflows extension peer dependency with upstream pi TUI `^0.80.2`.
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- Fixed workflow graph and attached-stage chat interactions, including direct mouse-click activation, mouse/trackpad wheel capture, copy-mode toggling, stale `Working...` spinners, terminal-row animation cleanup, and post-terminal subagent progress handling.
|
|
37
|
+
- Hardened durable resume, replay, checkpoint, and listing behavior across file and DBOS backends, including pending prompts, terminal/non-resumable states, stale cache entries, repeated quit/resume cycles, child workflow scope isolation, checkpoint identity hashing, retries, cancellation, and durable write failures.
|
|
38
|
+
- Fixed `/workflow resume` picker behavior for live, paused, failed, and cross-session durable workflows so it matches the standard Atomic resume selector and handles empty, headless, mixed live/durable, and cache-only states consistently.
|
|
39
|
+
- Fixed builtin workflow continuity and configuration edge cases, including `open-claude-design` generator/feedback forking, isolated workflow config discovery, durable schema-backed stage replay, parallel fail-fast finalization, and custom-backend propagation into child workflows.
|
|
40
|
+
|
|
9
41
|
## [0.9.3-alpha.6] - 2026-06-29
|
|
10
42
|
|
|
11
43
|
### Added
|
|
@@ -31,18 +31,18 @@ Adding workflow files under `.atomic/workflows/` (project scope) or `~/.atomic/a
|
|
|
31
31
|
|
|
32
32
|
### Workflow lifecycle notifications
|
|
33
33
|
|
|
34
|
-
Workflow lifecycle notices are enabled by default. They send steer prompts into the main chat/model context when a run completes or
|
|
34
|
+
Workflow lifecycle notices are enabled by default. They send steer prompts into the main chat/model context when a run completes, fails, or ends blocked. Awaiting-input prompts are tracked for dedupe/restore, but they do not wake the main chat agent. Configure lifecycle tracking in the same extension config file:
|
|
35
35
|
|
|
36
36
|
```json
|
|
37
37
|
{
|
|
38
38
|
"workflowNotifications": {
|
|
39
39
|
"enabled": true,
|
|
40
|
-
"notifyOn": ["completed", "failed", "awaiting_input"]
|
|
40
|
+
"notifyOn": ["completed", "failed", "blocked", "awaiting_input"]
|
|
41
41
|
}
|
|
42
42
|
}
|
|
43
43
|
```
|
|
44
44
|
|
|
45
|
-
Set `enabled` to `false` to disable all lifecycle notices, or narrow `notifyOn` to a non-empty list of selected events. Completion and
|
|
45
|
+
Set `enabled` to `false` to disable all lifecycle notices, or narrow `notifyOn` to a non-empty list of selected events. Completion, failure, and blocked lifecycle notices are emitted for top-level workflow runs, use steer delivery, and wake an idle model so the lifecycle update enters the model context when it happens. Nested child workflow completion/failure is reflected inside the expanded parent graph instead of producing separate top-level completion cards. Awaiting-input states are tracked for dedupe/restore, but workflows do not enqueue main-chat `/workflow connect` cards for them; prompt state remains visible through workflow status/connect surfaces, avoiding stale actionable cards if a prompt resolves while the main chat is streaming.
|
|
46
46
|
|
|
47
47
|
When a stage human-in-the-loop prompt is answered from the workflow TUI/stage chat, workflows also emits a separate display-only `workflows:hil-answer-notice` custom message. It records the answer for user-visible audit, but it does not wake the main agent, enter LLM context, or authorize answering later workflow prompts. Answers sent by the main-chat `workflow` tool do not emit this notice because the tool result already tells the main agent what happened.
|
|
48
48
|
|
|
@@ -736,7 +736,7 @@ Config-based discovery (`~/.atomic/agent/extensions/workflow/config.json` or `.a
|
|
|
736
736
|
},
|
|
737
737
|
"workflowNotifications": {
|
|
738
738
|
"enabled": true,
|
|
739
|
-
"notifyOn": ["completed", "failed", "awaiting_input"]
|
|
739
|
+
"notifyOn": ["completed", "failed", "blocked", "awaiting_input"]
|
|
740
740
|
}
|
|
741
741
|
}
|
|
742
742
|
```
|
|
@@ -33,11 +33,13 @@ export async function finalizeDurableTerminalStatus(input: DurableTerminalFinali
|
|
|
33
33
|
if (!input.isRoot) return;
|
|
34
34
|
const status = input.runSnapshot.status;
|
|
35
35
|
const isExitTerminal = input.runSnapshot.exited === true && status !== "running";
|
|
36
|
-
|
|
36
|
+
const isReturnedBlockedTerminal = status === "blocked" && input.runSnapshot.endedAt !== undefined;
|
|
37
|
+
if (status !== "failed" && status !== "killed" && !isExitTerminal && !isReturnedBlockedTerminal) return;
|
|
37
38
|
|
|
38
39
|
const durableStatus = toDurableStatus(status);
|
|
39
40
|
if (durableStatus !== undefined) {
|
|
40
|
-
|
|
41
|
+
const resumable = status === "blocked" ? false : input.runSnapshot.resumable;
|
|
42
|
+
input.durableBackend.setWorkflowStatus(input.runId, durableStatus, undefined, resumable);
|
|
41
43
|
}
|
|
42
44
|
try {
|
|
43
45
|
await input.durableBackend.flush?.();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { RunEndMetadata } from "../shared/store-public-types.js";
|
|
2
|
+
import type { WorkflowOutputValues } from "../shared/types.js";
|
|
3
|
+
|
|
4
|
+
export interface ReturnedRunStatus {
|
|
5
|
+
readonly status: "completed" | "failed" | "blocked";
|
|
6
|
+
readonly error?: string;
|
|
7
|
+
readonly metadata?: RunEndMetadata;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function classifyReturnedRunStatus(result: WorkflowOutputValues | undefined): ReturnedRunStatus {
|
|
11
|
+
const returnedStatus = result?.["status"];
|
|
12
|
+
if (returnedStatus !== "failed" && returnedStatus !== "blocked") {
|
|
13
|
+
return { status: "completed" };
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const summary = result?.["summary"];
|
|
17
|
+
const error = typeof summary === "string" && summary.trim().length > 0
|
|
18
|
+
? summary.trim()
|
|
19
|
+
: `Workflow returned status ${JSON.stringify(returnedStatus)}.`;
|
|
20
|
+
return {
|
|
21
|
+
status: returnedStatus,
|
|
22
|
+
error,
|
|
23
|
+
metadata: returnedStatus === "failed"
|
|
24
|
+
? returnedFailureMetadata(error)
|
|
25
|
+
: returnedBlockedMetadata(),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function returnedFailureMetadata(error: string): RunEndMetadata {
|
|
30
|
+
return {
|
|
31
|
+
failureKind: "unknown",
|
|
32
|
+
failureRecoverability: "non_recoverable",
|
|
33
|
+
failureDisposition: "terminal_failed",
|
|
34
|
+
failureMessage: error,
|
|
35
|
+
resumable: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function returnedBlockedMetadata(): RunEndMetadata {
|
|
40
|
+
return { resumable: false };
|
|
41
|
+
}
|
|
@@ -41,6 +41,7 @@ import {
|
|
|
41
41
|
import { assertWorkflowRunOutputs, normalizeWorkflowRunOutput } from "../runs/foreground/executor-outputs.js";
|
|
42
42
|
import { isWorkflowDefinition, workflowDefinitionRequirementMessage } from "../runs/foreground/executor-child-helpers.js";
|
|
43
43
|
import { getDurableBackend } from "../durable/factory.js";
|
|
44
|
+
import { classifyReturnedRunStatus } from "./run-returned-status.js";
|
|
44
45
|
import { createToolPrimitive, createCheckpointIdGenerator } from "../durable/tool-primitive.js";
|
|
45
46
|
import { persistDurableCacheEntry } from "../durable/resume-catalog.js";
|
|
46
47
|
import { createDurableStagePrimitive, createDurableTaskPrimitive, recordStageCheckpoint, createStageReplayKeyGenerator, recordCachedStageWithTracker } from "../durable/stage-primitive.js";
|
|
@@ -423,15 +424,16 @@ export async function run<
|
|
|
423
424
|
assertWorkflowRunOutputs(def.name, result, def.outputs);
|
|
424
425
|
assertWorkflowCreatedStage(runSnapshot);
|
|
425
426
|
await durableBackend.flush?.();
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
427
|
+
const returned = classifyReturnedRunStatus(result);
|
|
428
|
+
const recorded = activeStore.recordRunEnd(runId, returned.status, result, returned.error, returned.metadata);
|
|
429
|
+
appendRunEndWhenRecorded(opts.persistence, recorded, { runId, status: returned.status, result, ...(returned.error !== undefined ? { error: returned.error } : {}), ...(returned.metadata ?? {}), ts: Date.now() });
|
|
430
|
+
durableBackend.setWorkflowStatus(runId, returned.status, undefined, returned.metadata?.resumable);
|
|
429
431
|
await durableBackend.flush?.();
|
|
430
432
|
if (opts.persistence && durableBackend.persistent) {
|
|
431
433
|
const cacheEntry = durableBackend.toCacheEntry(runId);
|
|
432
434
|
if (cacheEntry) persistDurableCacheEntry(opts.persistence, cacheEntry);
|
|
433
435
|
}
|
|
434
|
-
return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status:
|
|
436
|
+
return reconcileTerminalRunResult(runId, runSnapshot, activeStore, { status: returned.status, result, error: returned.error }, opts.onRunEnd);
|
|
435
437
|
} catch (err) {
|
|
436
438
|
const selectedExit = findWorkflowExitSignal(err, exitScope) ?? findWorkflowExitSignal(ownController.signal.reason, exitScope);
|
|
437
439
|
if (selectedExit !== undefined) return await finalizers.finalizeWorkflowExit(selectedExit);
|
|
@@ -68,7 +68,7 @@ function validateConfig(value: unknown): string | null {
|
|
|
68
68
|
}
|
|
69
69
|
for (const item of notifyOn) {
|
|
70
70
|
if (!isWorkflowLifecycleNoticeKind(item)) {
|
|
71
|
-
return `"workflowNotifications.notifyOn" entries must be "completed", "failed", or "awaiting_input", got ${JSON.stringify(item)}`;
|
|
71
|
+
return `"workflowNotifications.notifyOn" entries must be "completed", "failed", "blocked", or "awaiting_input", got ${JSON.stringify(item)}`;
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
}
|
|
@@ -182,7 +182,7 @@ export const WORKFLOW_CONFIG_DEFAULTS = {
|
|
|
182
182
|
resumeInFlight: "ask" as const,
|
|
183
183
|
workflowNotifications: {
|
|
184
184
|
enabled: true,
|
|
185
|
-
notifyOn: ["completed", "failed", "awaiting_input"] as const,
|
|
185
|
+
notifyOn: ["completed", "failed", "blocked", "awaiting_input"] as const,
|
|
186
186
|
},
|
|
187
187
|
} as const;
|
|
188
188
|
|
|
@@ -21,11 +21,12 @@ import { renderWorkflowNoticeCard, type WorkflowNoticeTone } from "../tui/workfl
|
|
|
21
21
|
export const LIFECYCLE_NOTICE_CUSTOM_TYPE = "workflows:lifecycle-notice";
|
|
22
22
|
export const LIFECYCLE_NOTICE_SNIPPET_LIMIT = 240;
|
|
23
23
|
|
|
24
|
-
export type WorkflowLifecycleNoticeKind = "completed" | "failed" | "awaiting_input";
|
|
24
|
+
export type WorkflowLifecycleNoticeKind = "completed" | "failed" | "blocked" | "awaiting_input";
|
|
25
25
|
|
|
26
26
|
export const WORKFLOW_LIFECYCLE_NOTICE_KINDS = [
|
|
27
27
|
"completed",
|
|
28
28
|
"failed",
|
|
29
|
+
"blocked",
|
|
29
30
|
"awaiting_input",
|
|
30
31
|
] as const satisfies readonly WorkflowLifecycleNoticeKind[];
|
|
31
32
|
|
|
@@ -95,8 +96,9 @@ export function seedWorkflowLifecycleNotificationState(
|
|
|
95
96
|
): void {
|
|
96
97
|
for (const run of snapshot.runs) {
|
|
97
98
|
if (!isTopLevelWorkflowRun(run)) continue;
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
const noticeKind = terminalNoticeKind(run);
|
|
100
|
+
if (noticeKind !== undefined && run.endedAt !== undefined) {
|
|
101
|
+
state.deliveredTerminalRuns.add(terminalRunKey(noticeKind, run.id));
|
|
100
102
|
}
|
|
101
103
|
if (run.pendingPrompt !== undefined) {
|
|
102
104
|
state.deliveredInputPrompts.add(runAwaitingInputKey(run.id, run.pendingPrompt));
|
|
@@ -186,9 +188,10 @@ export function installWorkflowLifecycleNotifications(
|
|
|
186
188
|
|
|
187
189
|
const emitTerminalNoticeOnce = (
|
|
188
190
|
run: RunSnapshot,
|
|
189
|
-
kind: "completed" | "failed",
|
|
191
|
+
kind: "completed" | "failed" | "blocked",
|
|
190
192
|
): void => {
|
|
191
|
-
|
|
193
|
+
const noticeKind = terminalNoticeKind(run);
|
|
194
|
+
if (noticeKind !== kind || run.endedAt === undefined || !notifyOn.has(kind)) {
|
|
192
195
|
return;
|
|
193
196
|
}
|
|
194
197
|
|
|
@@ -231,6 +234,7 @@ export function installWorkflowLifecycleNotifications(
|
|
|
231
234
|
if (!isTopLevelWorkflowRun(run)) continue;
|
|
232
235
|
emitTerminalNoticeOnce(run, "completed");
|
|
233
236
|
emitTerminalNoticeOnce(run, "failed");
|
|
237
|
+
emitTerminalNoticeOnce(run, "blocked");
|
|
234
238
|
|
|
235
239
|
if (!notifyOn.has("awaiting_input")) continue;
|
|
236
240
|
emitRunAwaitingInputNoticeOnce(run);
|
|
@@ -273,6 +277,10 @@ export function formatWorkflowLifecycleNoticeText(details: WorkflowLifecycleNoti
|
|
|
273
277
|
const errorText = details.error ? `: ${details.error}` : "";
|
|
274
278
|
return `✗ Workflow "${workflowName}" failed (run ${details.runId}${stageText})${errorText}. Inspect: /workflow status ${details.runId}`;
|
|
275
279
|
}
|
|
280
|
+
if (details.kind === "blocked") {
|
|
281
|
+
const errorText = details.error ? `: ${details.error}` : "";
|
|
282
|
+
return `! Workflow "${workflowName}" ended blocked (run ${details.runId})${errorText}. Inspect: /workflow status ${details.runId}`;
|
|
283
|
+
}
|
|
276
284
|
const prompt = details.promptMessage ? ` Prompt: ${details.promptMessage}` : "";
|
|
277
285
|
if (details.scope === "run") {
|
|
278
286
|
return `? Workflow "${workflowName}" needs input (run ${details.runId}).${prompt} Respond: /workflow connect ${details.runId} to answer this run-level prompt.`;
|
|
@@ -286,18 +294,19 @@ export function formatWorkflowLifecycleNoticeText(details: WorkflowLifecycleNoti
|
|
|
286
294
|
|
|
287
295
|
function makeTerminalNotice(
|
|
288
296
|
run: RunSnapshot,
|
|
289
|
-
kind: "completed" | "failed",
|
|
297
|
+
kind: "completed" | "failed" | "blocked",
|
|
290
298
|
): WorkflowLifecycleNoticeDetails {
|
|
291
299
|
const failedStage = run.failedStageId
|
|
292
300
|
? run.stages.find((stage) => stage.id === run.failedStageId)
|
|
293
301
|
: undefined;
|
|
302
|
+
const error = run.error ?? (kind === "blocked" ? run.exitReason : undefined);
|
|
294
303
|
return {
|
|
295
304
|
kind,
|
|
296
305
|
scope: "run",
|
|
297
306
|
runId: run.id,
|
|
298
307
|
workflowName: run.name,
|
|
299
308
|
status: run.status,
|
|
300
|
-
...(
|
|
309
|
+
...(error ? { error: truncateSnippet(error) } : {}),
|
|
301
310
|
...(run.failedStageId ? { failedStageId: run.failedStageId } : {}),
|
|
302
311
|
...(failedStage ? { stageId: failedStage.id, stageName: failedStage.name } : {}),
|
|
303
312
|
...(run.durationMs !== undefined ? { durationMs: run.durationMs } : {}),
|
|
@@ -320,7 +329,15 @@ function jsonString(value: string): string {
|
|
|
320
329
|
return JSON.stringify(value);
|
|
321
330
|
}
|
|
322
331
|
|
|
323
|
-
function
|
|
332
|
+
function terminalNoticeKind(run: RunSnapshot): "completed" | "failed" | "blocked" | undefined {
|
|
333
|
+
if (run.status === "failed" || run.status === "blocked") return run.status;
|
|
334
|
+
if (run.status !== "completed") return undefined;
|
|
335
|
+
const returnedStatus = run.result?.["status"];
|
|
336
|
+
if (returnedStatus === "failed" || returnedStatus === "blocked") return returnedStatus;
|
|
337
|
+
return "completed";
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function terminalRunKey(kind: "completed" | "failed" | "blocked", runId: string): string {
|
|
324
341
|
return `${kind}:${runId}`;
|
|
325
342
|
}
|
|
326
343
|
|
|
@@ -361,19 +378,27 @@ function renderLifecycleNoticeCard(
|
|
|
361
378
|
details: WorkflowLifecycleNoticeDetails,
|
|
362
379
|
opts: { width: number; theme?: GraphTheme; fallbackText: string },
|
|
363
380
|
): string[] {
|
|
364
|
-
const tone: WorkflowNoticeTone = details.kind === "failed"
|
|
381
|
+
const tone: WorkflowNoticeTone = details.kind === "failed"
|
|
382
|
+
? "error"
|
|
383
|
+
: details.kind === "awaiting_input" || details.kind === "blocked"
|
|
384
|
+
? "warning"
|
|
385
|
+
: "success";
|
|
365
386
|
const title = details.kind === "failed"
|
|
366
387
|
? "WORKFLOW FAILED"
|
|
367
388
|
: details.kind === "awaiting_input"
|
|
368
389
|
? "WORKFLOW INPUT"
|
|
369
|
-
:
|
|
370
|
-
|
|
390
|
+
: details.kind === "blocked"
|
|
391
|
+
? "WORKFLOW BLOCKED"
|
|
392
|
+
: "WORKFLOW COMPLETE";
|
|
393
|
+
const glyph = details.kind === "failed" ? "✗" : details.kind === "awaiting_input" ? "?" : details.kind === "blocked" ? "!" : "✓";
|
|
371
394
|
const stage = details.stageName ?? details.failedStageId ?? details.stageId;
|
|
372
395
|
const headline = details.kind === "failed"
|
|
373
396
|
? `Workflow "${details.workflowName}" failed`
|
|
374
397
|
: details.kind === "awaiting_input"
|
|
375
398
|
? `Workflow "${details.workflowName}" needs input`
|
|
376
|
-
:
|
|
399
|
+
: details.kind === "blocked"
|
|
400
|
+
? `Workflow "${details.workflowName}" ended blocked`
|
|
401
|
+
: `Workflow "${details.workflowName}" completed`;
|
|
377
402
|
return renderWorkflowNoticeCard({
|
|
378
403
|
title,
|
|
379
404
|
glyph,
|
|
@@ -335,7 +335,7 @@ export function restoreTerminalRuns(entries: readonly SessionEntry[], store: Sto
|
|
|
335
335
|
const exitReason = end["exitReason"];
|
|
336
336
|
const resumable = end["resumable"];
|
|
337
337
|
const restoredAuthorExit = isWorkflowExitTerminalStatus(status) &&
|
|
338
|
-
(exited === true ||
|
|
338
|
+
(exited === true || typeof exitReason === "string");
|
|
339
339
|
if (status === "completed" && !restoredAuthorExit && stages.some((stage) => stage.status !== "completed")) continue;
|
|
340
340
|
store.recordRunStart({
|
|
341
341
|
id: runId,
|
|
@@ -83,8 +83,9 @@ export interface Store {
|
|
|
83
83
|
* Records the end of a run.
|
|
84
84
|
* Returns `true` if state changed, `false` if the run was not found or
|
|
85
85
|
* already in a terminal state (completed | failed | killed | skipped | cancelled | blocked).
|
|
86
|
-
* `result` is applied for intentional success/exit statuses (completed | skipped | cancelled | blocked)
|
|
87
|
-
*
|
|
86
|
+
* `result` is applied for intentional success/exit statuses (completed | skipped | cancelled | blocked)
|
|
87
|
+
* and for workflows that intentionally return a failed status with structured outputs.
|
|
88
|
+
* `error` is applied for status "failed" | "killed" | "blocked".
|
|
88
89
|
*/
|
|
89
90
|
recordRunEnd(
|
|
90
91
|
runId: string,
|
|
@@ -82,11 +82,12 @@ export function createRunStoreMethods(context: StoreContext): RunStoreMethods {
|
|
|
82
82
|
run.pausedAt = undefined;
|
|
83
83
|
}
|
|
84
84
|
run.durationMs = elapsedRunMs(run, run.endedAt);
|
|
85
|
+
if (result !== undefined && shouldStoreRunResult(status)) run.result = result;
|
|
85
86
|
const wasBlocked = run.blockedAt !== undefined || run.failureDisposition === "active_blocked";
|
|
86
87
|
delete run.blockedAt;
|
|
87
88
|
if (status === "completed" || status === "skipped" || status === "cancelled" || status === "blocked") {
|
|
88
|
-
if (result !== undefined) run.result = result;
|
|
89
89
|
clearRunFailureMetadata(run);
|
|
90
|
+
if (status === "blocked" && error !== undefined) run.error = error;
|
|
90
91
|
if (metadata !== undefined) applyRunEndMetadata(run, metadata);
|
|
91
92
|
} else {
|
|
92
93
|
if (wasBlocked && error === undefined) delete run.error;
|
|
@@ -227,3 +228,7 @@ export function createRunStoreMethods(context: StoreContext): RunStoreMethods {
|
|
|
227
228
|
},
|
|
228
229
|
};
|
|
229
230
|
}
|
|
231
|
+
|
|
232
|
+
function shouldStoreRunResult(status: RunStatus): boolean {
|
|
233
|
+
return status === "completed" || status === "skipped" || status === "cancelled" || status === "blocked" || status === "failed";
|
|
234
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
export declare const DEFAULT_HTTP_IDLE_TIMEOUT_MS = 600000;
|
|
2
|
-
/** Connect-phase timeout so a black-holed/firewalled host fails fast instead of hanging until the OS TCP timeout. */
|
|
3
|
-
export declare const HTTP_CONNECT_TIMEOUT_MS = 10000;
|
|
4
2
|
export declare const HTTP_IDLE_TIMEOUT_CHOICES: readonly [{
|
|
5
3
|
readonly label: "30 sec";
|
|
6
4
|
readonly timeoutMs: 30000;
|
|
@@ -28,7 +26,9 @@ export declare function formatHttpIdleTimeoutMs(timeoutMs: number): string;
|
|
|
28
26
|
* Keep HTTP/2 disabled for now because some Node/undici combinations have
|
|
29
27
|
* produced stream-reset crashes, and use a configurable idle timeout so stale
|
|
30
28
|
* connections are eventually reclaimed while long-running requests remain
|
|
31
|
-
* supported.
|
|
29
|
+
* supported. Do not install a fixed connect-phase timeout here: under Pier and
|
|
30
|
+
* other policy/proxy layers, CONNECT establishment can be slower than normal
|
|
31
|
+
* internet egress and should surface through the provider/agent retry path.
|
|
32
32
|
*/
|
|
33
33
|
export declare function configureHttpDispatcher(timeoutMs?: number): void;
|
|
34
34
|
//# sourceMappingURL=http-dispatcher.d.ts.map
|