@bastani/atomic 0.9.4-alpha.9 → 0.9.4

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.
Files changed (78) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/dist/builtin/cursor/CHANGELOG.md +6 -0
  3. package/dist/builtin/cursor/package.json +2 -2
  4. package/dist/builtin/intercom/CHANGELOG.md +6 -0
  5. package/dist/builtin/intercom/package.json +1 -1
  6. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  7. package/dist/builtin/mcp/package.json +1 -1
  8. package/dist/builtin/subagents/CHANGELOG.md +27 -0
  9. package/dist/builtin/subagents/package.json +1 -1
  10. package/dist/builtin/subagents/src/slash/slash-commands.ts +2 -2
  11. package/dist/builtin/subagents/src/tui/render-result-compact.ts +3 -2
  12. package/dist/builtin/subagents/src/tui/render-result.ts +3 -3
  13. package/dist/builtin/subagents/src/tui/render-widget-graph.ts +4 -3
  14. package/dist/builtin/subagents/src/tui/render-widget.ts +10 -4
  15. package/dist/builtin/web-access/CHANGELOG.md +6 -0
  16. package/dist/builtin/web-access/package.json +1 -1
  17. package/dist/builtin/workflows/CHANGELOG.md +22 -0
  18. package/dist/builtin/workflows/package.json +1 -1
  19. package/dist/builtin/workflows/src/tui/keybindings-adapter.ts +4 -0
  20. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +4 -0
  21. package/dist/builtin/workflows/src/tui/stage-chat-view-input.ts +12 -1
  22. package/dist/builtin/workflows/src/tui/stage-chat-view-state.ts +2 -0
  23. package/dist/builtin/workflows/src/tui/stage-chat-view-types.ts +6 -0
  24. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +4 -0
  25. package/dist/builtin/workflows/src/tui/workflow-attach-pane-types.ts +4 -0
  26. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +8 -9
  27. package/dist/core/agent-session-events.d.ts.map +1 -1
  28. package/dist/core/agent-session-events.js +2 -0
  29. package/dist/core/agent-session-events.js.map +1 -1
  30. package/dist/core/agent-session-methods.d.ts +4 -1
  31. package/dist/core/agent-session-methods.d.ts.map +1 -1
  32. package/dist/core/agent-session-methods.js.map +1 -1
  33. package/dist/core/agent-session-tool-registry.d.ts.map +1 -1
  34. package/dist/core/agent-session-tool-registry.js +4 -0
  35. package/dist/core/agent-session-tool-registry.js.map +1 -1
  36. package/dist/core/agent-session.d.ts +4 -1
  37. package/dist/core/agent-session.d.ts.map +1 -1
  38. package/dist/core/agent-session.js +4 -0
  39. package/dist/core/agent-session.js.map +1 -1
  40. package/dist/core/async/format.d.ts +3 -0
  41. package/dist/core/async/format.d.ts.map +1 -0
  42. package/dist/core/async/format.js +50 -0
  43. package/dist/core/async/format.js.map +1 -0
  44. package/dist/core/async/job-manager.d.ts +39 -0
  45. package/dist/core/async/job-manager.d.ts.map +1 -0
  46. package/dist/core/async/job-manager.js +254 -0
  47. package/dist/core/async/job-manager.js.map +1 -0
  48. package/dist/core/async/session-manager.d.ts +18 -0
  49. package/dist/core/async/session-manager.d.ts.map +1 -0
  50. package/dist/core/async/session-manager.js +61 -0
  51. package/dist/core/async/session-manager.js.map +1 -0
  52. package/dist/core/async/types.d.ts +23 -0
  53. package/dist/core/async/types.d.ts.map +1 -0
  54. package/dist/core/async/types.js +2 -0
  55. package/dist/core/async/types.js.map +1 -0
  56. package/dist/core/extensions/loader-virtual-modules.d.ts +11 -0
  57. package/dist/core/extensions/loader-virtual-modules.d.ts.map +1 -1
  58. package/dist/core/extensions/loader-virtual-modules.js +20 -5
  59. package/dist/core/extensions/loader-virtual-modules.js.map +1 -1
  60. package/dist/core/tools/bash-async-execution.d.ts +25 -0
  61. package/dist/core/tools/bash-async-execution.d.ts.map +1 -0
  62. package/dist/core/tools/bash-async-execution.js +71 -0
  63. package/dist/core/tools/bash-async-execution.js.map +1 -0
  64. package/dist/core/tools/bash-async-jobs.d.ts +10 -0
  65. package/dist/core/tools/bash-async-jobs.d.ts.map +1 -1
  66. package/dist/core/tools/bash-async-jobs.js +18 -2
  67. package/dist/core/tools/bash-async-jobs.js.map +1 -1
  68. package/dist/core/tools/bash-async-output.d.ts +3 -1
  69. package/dist/core/tools/bash-async-output.d.ts.map +1 -1
  70. package/dist/core/tools/bash-async-output.js +4 -1
  71. package/dist/core/tools/bash-async-output.js.map +1 -1
  72. package/dist/core/tools/bash.d.ts +10 -4
  73. package/dist/core/tools/bash.d.ts.map +1 -1
  74. package/dist/core/tools/bash.js +20 -38
  75. package/dist/core/tools/bash.js.map +1 -1
  76. package/docs/tools.md +4 -2
  77. package/npm-shrinkwrap.json +23 -23
  78. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.4] - 2026-07-03
6
+
7
+ ### Added
8
+
9
+ - Added dynamic GitHub Copilot model population from the live CAPI `/models` catalog — picker-enabled chat ids are synthesized from catalog metadata while built-in `pi-ai` definitions still win — plus catalog-driven thinking-level gating so synthesized and bundled Copilot models only offer the reasoning levels CAPI's `capabilities.supports.reasoning_effort` arrays advertise.
10
+ - Added `get_entries`/`get_tree` RPC commands with `RpcClient.getEntries`/`getTree` helpers, a package `./rpc-entry` export for launching Atomic directly in RPC mode, a `session_info_changed` extension event, an `externalEditor` settings.json override for Ctrl+G, an `outputPad` setting controlling message padding, BMP image detection with PNG normalization, and separate extension-load timing namespaces (inherited from upstream Pi 0.80.3).
11
+ - Added subagent watchdog escape hatches: setting `ATOMIC_SUBAGENT_ATTEMPT_IDLE_TIMEOUT_MS` or `ATOMIC_SUBAGENT_ATTEMPT_TIMEOUT_MS` to `0` (or negative) disables the corresponding per-attempt timeout, while the `ATOMIC_SUBAGENT_ATTEMPT_KILL_GRACE_MS` SIGTERM→SIGKILL grace period always stays bounded ([#1581](https://github.com/bastani-inc/atomic/pull/1581)).
12
+
13
+ ### Changed
14
+
15
+ - Sped up startup substantially: compiled binaries now use Bun bytecode compilation (`--bytecode --format=cjs`, TUI first paint ~450ms → ~200ms), builtin extensions transpile through a persistent on-disk jiti cache at `~/.atomic/agent/cache/jiti/<version>` (~2.8x faster warm TUI starts), the interactive TUI shell paints immediately while extensions load in the background, `--version`/`-v` is a fast path (~380ms → ~25ms), and the HTML session export module loads lazily on first use.
16
+ - Synced `@earendil-works/pi-agent-core`, `pi-ai`, and `pi-tui` runtime dependencies from `^0.80.2` to `^0.80.3` across `@bastani/atomic` and all bundled first-party extensions, and changed the default OpenAI model to `gpt-5.5` (the Atomic-specific `github-copilot` default remains `gpt-5.4`).
17
+ - 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.
18
+
19
+ ### Fixed
20
+
21
+ - Fixed async `bash({ async: true })` jobs finishing silently: completed or failed session-managed background jobs now enqueue an `async-job-result` follow-up message into the originating session with duplicate suppression, retention-bounded delivery bookkeeping, full-output-path persistence for truncated previews, and lifecycle tracking that survives owner-session disposal; also fixed a leak where a failed async-manager registration left a never-executed job permanently reported as `running`.
22
+ - Fixed every builtin extension (workflows, subagents, mcp, web-access, intercom, cursor) failing to load with `Package subpath './package.json' is not defined by "exports"` on npm/bun package installs (0.9.4-alpha.9 regression): the loader now locates package roots by scanning the `node_modules` resolution chain instead of exports-encapsulated `require.resolve`, guarded by installed-layout CI smoke tests under Node on Linux and Windows.
23
+ - Fixed intercepted provider safety refusals dead-ending agent turns: canned zero-usage refusals are detected and auto-retried, structured safety-trigger errors (Anthropic `refusal` stops, OpenAI-style `finish_reason: content_filter`) are now retryable for all providers, and a static CAPI-derived limit snapshot (2026-07-02) keeps bundled GitHub Copilot models within real server-enforced context/output limits — including branded long-context tiers with hard input caps — whenever the live catalog is unavailable ([#1608](https://github.com/bastani-inc/atomic/issues/1608)).
24
+ - Fixed GitHub Copilot metadata handling: models use the live `max_output_tokens` from the catalog ([#1582](https://github.com/bastani-inc/atomic/issues/1582)), active sessions adopt live catalog metadata without a restart, and `COPILOT_GITHUB_TOKEN` env auth routes through an endpoint resolver honoring explicit base URL overrides, GitHub Enterprise server URLs, and the public Copilot routing hub, avoiding `421 Misdirected Request` failures ([#1569](https://github.com/bastani-inc/atomic/issues/1569)).
25
+ - Fixed workflow and subagent model fallback chains so request/context incompatibility failures (HTTP 400/413/422, unsupported tool/parameter, context-window overflow, `invalid_request`/`bad_request`/`too_large`) advance to the next candidate and ultimately fall back to the current user-selected model; bounded foreground and background subagent attempts with an idle watchdog and wall-clock cap where in-flight tool executions count as activity; skipped known unauthenticated providers before spawning; aligned the subagents and workflows model-failure classifiers with a cross-package conformance suite; and made the background runner spawn one default-model attempt when no candidates were ever configured ([#1580](https://github.com/bastani-inc/atomic/issues/1580), [#1581](https://github.com/bastani-inc/atomic/pull/1581)).
26
+ - Fixed the `read` tool to parse colon-delimited `file:START:END` (and grep-style `file:LINE:COL`) path selectors as line ranges instead of producing a bogus `ENOENT` ([#1585](https://github.com/bastani-inc/atomic/issues/1585)).
27
+ - Fixed post-compaction queued work to resume through the full agent continuation lifecycle with surfaced continuation failures ([#1570](https://github.com/bastani-inc/atomic/issues/1570)), removed the fixed 10-second connect-phase timeout from the global proxy-aware HTTP dispatcher so slow provider CONNECT establishment behind policy proxies no longer fails spuriously, and made headless JSON print mode exit nonzero when the final assistant turn ends with `stopReason: "error"` or `"aborted"`.
28
+ - Fixed release packaging determinism: declared `lru-cache` as a direct runtime dependency, added publish-time `npm-shrinkwrap.json` generation matching upstream Pi, and made shrinkwrap preparation hermetic by deriving `@bastani/atomic-natives` and generated platform optional package entries from local stamped metadata with deterministic registry tarball URLs.
29
+ - Fixed issues inherited from upstream Pi 0.80.3, including mid-run extension `setActiveTools` changes applying before the next provider request, `before_agent_start` system prompt overrides surviving mid-run tool changes, clean red errors (not stack traces) for invalid session files without overwriting them, `outputPad`-aware transcript rendering, visible backslashes in Markdown escape sequences, a visible incomplete-response error on output-length stops, `--no-session --session-id` for deterministic provider cache affinity, disk BMP attachment support, and a crash when undici emits an internal client error mid-stream.
30
+
31
+ ## [0.9.4-alpha.11] - 2026-07-03
32
+
33
+ ### Fixed
34
+
35
+ - Fixed async `bash({ async: true })` jobs finishing silently: completed or failed session-managed background bash jobs now enqueue an `async-job-result` follow-up message into the originating chat session automatically, while explicit completed-job polling, explicit cancellation, and parent aborts acknowledge the job and suppress duplicate or unwanted idle turns. Suppression is checked again at the streaming boundary so a completed job polled while its automatic follow-up is staged does not later deliver a duplicate. Async delivery bookkeeping is now bounded by the existing background-job retention defaults, suppressions stay tied to retained jobs so disposed-session running jobs cannot later fall back into the owner session, 12KB–50KB follow-up outputs persist their full output path before inline preview truncation, just-under-threshold raw outputs remain fully inline instead of being preview-truncated without a `fullOutputPath`, shared-manager lifecycle tracking prevents owner-session disposal from dropping later-session jobs while cleaning stale handlers from disposed fork/subagent sessions, and non-blocking delivery attempts keep one live streaming session from delaying unrelated async job completions.
36
+ - Fixed a background bash job leak where a failed async-manager registration (disposed manager/session or a capacity race mid-flight) left a never-executed job permanently reported as `running` to `__atomic_bash_job` polls; the managed job entry is now discarded when registration fails and the tool call error is surfaced unchanged.
37
+
38
+ ## [0.9.4-alpha.10] - 2026-07-03
39
+
40
+ ### Fixed
41
+
42
+ - Fixed every builtin extension (workflows, subagents, mcp, web-access, intercom, cursor) failing to load with `Package subpath './package.json' is not defined by "exports"` for npm/bun package installs of Atomic (regression in 0.9.4-alpha.9): the extension loader's installed-package alias fallback resolved host packages via `require.resolve("<pkg>/package.json")`, which Node's strict exports-map encapsulation rejects for packages like `@earendil-works/pi-ai` that do not export `./package.json` (the compiled binary and Bun-run dev paths were unaffected, which is why it slipped through). The loader now locates package roots by scanning the `node_modules` resolution chain directly, bypassing exports maps entirely while staying `import.meta.resolve()`-free to keep bytecode compilation intact. CI now guards this path with an installed-layout smoke test that runs the built package under the Node runtime on both Linux and Windows.
43
+
5
44
  ## [0.9.4-alpha.9] - 2026-07-02
6
45
 
7
46
  ### Changed
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.4] - 2026-07-03
6
+
7
+ ### Changed
8
+
9
+ - Published the stable Atomic 0.9.4 release for the Cursor provider package with its upstream pi-ai dependency aligned to `^0.80.3`; no functional Cursor provider source changes were made after 0.9.3.
10
+
5
11
  ## [0.9.4-alpha.6] - 2026-07-01
6
12
 
7
13
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/cursor",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
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.4-alpha.9",
43
+ "@bastani/atomic-natives": "0.9.4",
44
44
  "@bufbuild/protobuf": "^2.12.1",
45
45
  "@earendil-works/pi-ai": "^0.80.3"
46
46
  }
@@ -4,6 +4,12 @@ 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] - 2026-07-03
8
+
9
+ ### Changed
10
+
11
+ - Published the stable Atomic 0.9.4 release for the intercom extension with its upstream pi TUI peer dependency aligned to `^0.80.3`; no intercom extension source changes were needed after 0.9.3.
12
+
7
13
  ## [0.9.4-alpha.6] - 2026-07-01
8
14
 
9
15
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
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,12 @@ 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] - 2026-07-03
11
+
12
+ ### Changed
13
+
14
+ - Published the stable Atomic 0.9.4 release for the MCP extension with its upstream pi AI/TUI peer dependencies aligned to `^0.80.3`; no MCP extension source changes were needed after 0.9.3.
15
+
10
16
  ## [0.9.4-alpha.6] - 2026-07-01
11
17
 
12
18
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
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,33 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.4] - 2026-07-03
6
+
7
+ ### Added
8
+
9
+ - Added a watchdog escape hatch: setting `ATOMIC_SUBAGENT_ATTEMPT_IDLE_TIMEOUT_MS` or `ATOMIC_SUBAGENT_ATTEMPT_TIMEOUT_MS` to `0` (or a negative value) disables the corresponding per-attempt timeout entirely; the `ATOMIC_SUBAGENT_ATTEMPT_KILL_GRACE_MS` SIGTERM→SIGKILL grace period intentionally cannot be disabled so escalation always stays bounded ([#1581](https://github.com/bastani-inc/atomic/pull/1581)).
10
+
11
+ ### Changed
12
+
13
+ - Curated builtin agent model defaults against Atomic's agentic-coding benchmark (pass@1 / avg cost per task, 2026-07-02) under a role-based placement principle: all primaries are now `openai-codex/gpt-5.5` at the measured value point for each tier (`debugger` at `:xhigh`; `worker`, `code-simplifier`, and the analyzer/researcher agents at `:medium`; locator and pattern-finder agents at `:low`), fallback chains degrade through same-model provider mirrors first, then remaining families by descending benchmark value, with OpenRouter mirrors as the availability tail; strictly dominated models (`claude-sonnet-5`, `claude-sonnet-4.6`, `gemini-3.1-pro`, `gemini-3.5-flash`) and unbenchmarked entries (`gpt-5.4-mini`, `claude-haiku-4.5`) were removed so every chain entry corresponds to a benchmark datapoint.
14
+ - Normalized all GLM-5.2 entries to the model's two real reasoning tiers: native `zai`/`zai-coding-cn` entries now say `:high` explicitly (replacing misleading `:medium`/`:low` labels that silently ran at the "high" tier), the `debugger` chain keeps `:xhigh`, and the `openrouter/z-ai/glm-5.2` mirror is now always `:xhigh`.
15
+ - Aligned the subagents extension peer dependencies with upstream pi `^0.80.3` runtime packages.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed subagent live-detail hints to render the configured `app.tools.expand` keybinding instead of hardcoding Ctrl+O ([#1607](https://github.com/bastani-inc/atomic/issues/1607)), and fixed the running subagent widget to preserve its expanded/open state across workflow stage-node exit re-renders instead of transiently collapsing ([#1619](https://github.com/bastani-inc/atomic/issues/1619)).
20
+ - Fixed subagent model fallback so request/context incompatibility failures (HTTP 400/413/422, unsupported tool/parameter, context-window overflow, `invalid_request`/`bad_request`/`too_large` errors) advance the chain to the next candidate, falling back to the current user-selected model when no configured candidate can serve the request; refusals, content-filter/safety blocks, cancellations, and task failures still stop the chain ([#1580](https://github.com/bastani-inc/atomic/issues/1580)).
21
+ - Fixed foreground and background subagent model attempts that produced no child activity from hanging indefinitely: each candidate attempt now has a conservative idle watchdog (where in-flight tool executions count as activity) and an absolute wall-clock cap, records a retryable timeout failure, and advances to the next fallback candidate; known providers without configured auth are skipped before spawning ([#1580](https://github.com/bastani-inc/atomic/issues/1580), [#1581](https://github.com/bastani-inc/atomic/pull/1581)).
22
+ - Aligned the subagents model-failure classifier's direct-message precedence with the workflows classifier and added a cross-package conformance test suite so the two classifier copies cannot silently drift ([#1581](https://github.com/bastani-inc/atomic/pull/1581)).
23
+ - Fixed the background subagent runner to spawn one default-model attempt when no model candidates were ever configured, mirroring the foreground path instead of silently exiting 1 with no error; an explicitly empty candidate list produced by pre-spawn auth filtering is still surfaced as an error ([#1581](https://github.com/bastani-inc/atomic/pull/1581)).
24
+
25
+ ## [0.9.4-alpha.11] - 2026-07-03
26
+
27
+ ### Fixed
28
+
29
+ - Fixed subagent live-detail hints to render the configured `app.tools.expand` keybinding instead of hardcoding Ctrl+O, so remapped users see the same binding honored by attached workflow stage viewers ([#1607](https://github.com/bastani-inc/atomic/issues/1607)).
30
+ - Fixed the running subagent widget so it preserves the last known expanded/open state across workflow stage-node exit re-renders instead of transiently collapsing when the live widget context is stale until the next subagent status update ([#1619](https://github.com/bastani-inc/atomic/issues/1619)).
31
+
5
32
  ## [0.9.4-alpha.9] - 2026-07-02
6
33
 
7
34
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
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": [
@@ -1,7 +1,7 @@
1
1
  import { randomUUID } from "node:crypto";
2
2
  import * as fs from "node:fs";
3
3
  import * as path from "node:path";
4
- import type { ExtensionAPI, ExtensionContext } from "@bastani/atomic";
4
+ import { keyHint, type ExtensionAPI, type ExtensionContext } from "@bastani/atomic";
5
5
  import { Key, matchesKey } from "@earendil-works/pi-tui";
6
6
  import { discoverAgents, discoverAgentsAll, type ChainConfig } from "../agents/agents.ts";
7
7
  import type { SubagentParamsLike } from "../runs/foreground/subagent-executor.ts";
@@ -164,7 +164,7 @@ async function requestSlashRun(
164
164
  if (!ctx.hasUI) return;
165
165
  const tool = update.currentTool ? ` ${update.currentTool}` : "";
166
166
  const count = update.toolCount ?? 0;
167
- ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ctrl+o live detail`);
167
+ ctx.ui.setStatus("subagent-slash", `${count} tools${tool} | ${keyHint("app.tools.expand", "live detail")}`);
168
168
  };
169
169
 
170
170
  const onTerminalInput = ctx.hasUI
@@ -1,3 +1,4 @@
1
+ import { keyHint } from "@bastani/atomic";
1
2
  import { Container, Text, type Component } from "@earendil-works/pi-tui";
2
3
  import type { AgentProgress, AsyncJobStep, Details } from "../shared/types.ts";
3
4
  import { shortenPath } from "../shared/formatters.ts";
@@ -45,7 +46,7 @@ export function renderSingleCompact(d: Details, r: Details["results"][number], t
45
46
  c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
46
47
  const liveStatus = buildLiveStatusLine(r.progress, progressSnapshotNow);
47
48
  if (liveStatus && liveStatus !== activity) c.addChild(new Text(truncLine(theme.fg("dim", ` ${liveStatus}`), width), 0, 0));
48
- c.addChild(new Text(truncLine(theme.fg("accent", " Press ctrl+o for live detail"), width), 0, 0));
49
+ c.addChild(new Text(truncLine(theme.fg("accent", ` Press ${keyHint("app.tools.expand", "for live detail")}`), width), 0, 0));
49
50
  if (r.artifactPaths) c.addChild(new Text(truncLine(theme.fg("dim", ` output: ${shortenPath(r.artifactPaths.outputPath)}`), width), 0, 0));
50
51
  return c;
51
52
  }
@@ -141,7 +142,7 @@ export function renderMultiCompact(d: Details, theme: Theme, now?: number, pulse
141
142
  if (rRunning && rProg && "status" in rProg) {
142
143
  const activity = compactCurrentActivity(rProg, now);
143
144
  c.addChild(new Text(truncLine(theme.fg("dim", ` ⎿ ${activity}`), width), 0, 0));
144
- c.addChild(new Text(truncLine(theme.fg("accent", " Press ctrl+o for live detail"), width), 0, 0));
145
+ c.addChild(new Text(truncLine(theme.fg("accent", ` Press ${keyHint("app.tools.expand", "for live detail")}`), width), 0, 0));
145
146
  } else if (!rPending && (r.exitCode !== 0 || r.interrupted || r.detached || hasEmptyTextOutputWithoutOutputTarget(r.task, output))) {
146
147
  c.addChild(new Text(truncLine(theme.fg(r.exitCode !== 0 ? "error" : "dim", ` ⎿ ${resultStatusLine(r, output)}`), width), 0, 0));
147
148
  }
@@ -1,5 +1,5 @@
1
1
  import type { AgentToolResult } from "@earendil-works/pi-agent-core";
2
- import { getMarkdownTheme } from "@bastani/atomic";
2
+ import { getMarkdownTheme, keyHint } from "@bastani/atomic";
3
3
  import { Container, Markdown, Spacer, Text, type Component } from "@earendil-works/pi-tui";
4
4
  import type { AgentProgress, AsyncJobStep, Details } from "../shared/types.ts";
5
5
  import { formatDuration, formatTokens, formatUsage, shortenPath } from "../shared/formatters.ts";
@@ -116,7 +116,7 @@ export function renderSubagentResult(
116
116
  if (liveStatusLine) {
117
117
  c.addChild(new Text(fit(theme.fg("accent", liveStatusLine)), 0, 0));
118
118
  }
119
- c.addChild(new Text(fit(theme.fg("accent", "Press ctrl+o for live detail")), 0, 0));
119
+ c.addChild(new Text(fit(theme.fg("accent", `Press ${keyHint("app.tools.expand", "for live detail")}`)), 0, 0));
120
120
  if (r.artifactPaths) {
121
121
  c.addChild(new Text(fit(theme.fg("dim", `Artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
122
122
  }
@@ -350,7 +350,7 @@ export function renderSubagentResult(
350
350
  if (liveStatusLine) {
351
351
  c.addChild(new Text(fit(theme.fg("accent", ` ${liveStatusLine}`)), 0, 0));
352
352
  }
353
- c.addChild(new Text(fit(theme.fg("accent", " Press ctrl+o for live detail")), 0, 0));
353
+ c.addChild(new Text(fit(theme.fg("accent", ` Press ${keyHint("app.tools.expand", "for live detail")}`)), 0, 0));
354
354
  if (r.artifactPaths) {
355
355
  c.addChild(new Text(fit(theme.fg("dim", ` artifacts: ${shortenPath(r.artifactPaths.outputPath)}`)), 0, 0));
356
356
  }
@@ -1,3 +1,4 @@
1
+ import { keyHint } from "@bastani/atomic";
1
2
  import type { AsyncJobState } from "../shared/types.ts";
2
3
  import { shortenPath } from "../shared/formatters.ts";
3
4
  import { aggregateStepStatus, formatParallelOutcome } from "../shared/status-format.ts";
@@ -80,7 +81,7 @@ export function foregroundStyleWidgetStepLines(
80
81
  lines.push(` ${nestedLine}`);
81
82
  }
82
83
  if (step.status === "running") {
83
- if (!expanded) lines.push(` ${theme.fg("accent", "Press ctrl+o for live detail")}`);
84
+ if (!expanded) lines.push(` ${theme.fg("accent", `Press ${keyHint("app.tools.expand", "for live detail")}`)}`);
84
85
  const output = widgetOutputPath(job, step);
85
86
  if (output) lines.push(` ${theme.fg("dim", `output: ${shortenPath(output)}`)}`);
86
87
  if (expanded) {
@@ -147,7 +148,7 @@ export function compactSingleWidgetLines(job: AsyncJobState, theme: Theme, width
147
148
  lines.push(` ${widgetStepGlyph(step.status, theme, widgetStepRunningSeed(step, index), now)} ${itemTitle} ${index + 1}/${total}: ${themeBold(theme, step.agent)} ${theme.fg("dim", "·")} ${status}${modelDisplay}${activitySuffix}${stepStats ? ` ${theme.fg("dim", "·")} ${stepStats}` : ""}`);
148
149
  for (const nestedLine of formatNestedWidgetLines(step.children, theme, width, false, job.updatedAt, undefined, now)) lines.push(` ${nestedLine}`);
149
150
  }
150
- if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", " Press ctrl+o for live detail"));
151
+ if (job.steps.some((step) => step.status === "running")) lines.push(theme.fg("accent", ` Press ${keyHint("app.tools.expand", "for live detail")}`));
151
152
  return lines.map((line) => truncLine(line, width));
152
153
  }
153
154
 
@@ -161,6 +162,6 @@ export function fitWidgetLineBudget(lines: string[], theme: Theme, width: number
161
162
  const hiddenCount = lines.length - visibleLines;
162
163
  const hint = expanded
163
164
  ? `… ${hiddenCount} live-detail lines hidden`
164
- : `… ${hiddenCount} lines hidden · ctrl+o expands`;
165
+ : `… ${hiddenCount} lines hidden · ${keyHint("app.tools.expand", "expands")}`;
165
166
  return [...lines.slice(0, visibleLines), truncLine(theme.fg("dim", hint), width)];
166
167
  }
@@ -63,6 +63,7 @@ interface RenderRequestingContext {
63
63
  let latestWidgetCtx: ExtensionContext | undefined;
64
64
  let latestWidgetJobs: AsyncJobState[] = [];
65
65
  let latestWidgetFrameNow = 0;
66
+ let latestWidgetExpanded = false;
66
67
  let widgetTimer: ReturnType<typeof setInterval> | undefined;
67
68
  let mountedWidgetCtx: ExtensionContext | undefined;
68
69
  let mountedWidgetOwnerKey: string | undefined;
@@ -78,16 +79,21 @@ function getLatestWidgetFrameNow(): number {
78
79
 
79
80
  function getLatestWidgetExpanded(): boolean {
80
81
  // LiveWidgetComponent re-renders outside a specific renderWidget() call, so
81
- // read expansion from the latest live singleton context. If that context was
82
- // cleared or went stale, collapse safely instead of consulting a stale caller.
83
- if (!latestWidgetCtx?.hasUI) return false;
84
- return latestWidgetCtx.ui.getToolsExpanded?.() ?? false;
82
+ // remember the last expansion state observed from a live host UI. Workflow
83
+ // stage-node detach can briefly repaint the mounted singleton with a stale or
84
+ // no-UI context before the next subagent status update refreshes it; falling
85
+ // back to the cached value avoids a transient collapse while jobs keep running.
86
+ if (!latestWidgetCtx?.hasUI) return latestWidgetExpanded;
87
+ const expanded = latestWidgetCtx.ui.getToolsExpanded?.();
88
+ if (typeof expanded === "boolean") latestWidgetExpanded = expanded;
89
+ return latestWidgetExpanded;
85
90
  }
86
91
 
87
92
  function clearLatestWidgetState(): void {
88
93
  latestWidgetCtx = undefined;
89
94
  latestWidgetJobs = [];
90
95
  latestWidgetFrameNow = 0;
96
+ latestWidgetExpanded = false;
91
97
  mountedWidgetCtx = undefined;
92
98
  mountedWidgetOwnerKey = undefined;
93
99
  widgetMounted = false;
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.4] - 2026-07-03
8
+
9
+ ### Changed
10
+
11
+ - Published the stable Atomic 0.9.4 release for the web-access extension with its upstream pi TUI peer dependency aligned to `^0.80.3`; no web-access extension source changes were needed after 0.9.3.
12
+
7
13
  ## [0.9.4-alpha.6] - 2026-07-01
8
14
 
9
15
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/web-access",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
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,28 @@ 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] - 2026-07-03
10
+
11
+ ### Changed
12
+
13
+ - Curated all builtin workflow model chains against Atomic's agentic-coding benchmark (pass@1 / avg cost per task, 2026-07-02) under a role-based placement principle: reviewer stages keep best-in-class verification models (`anthropic/claude-fable-5:xhigh`, `openai-codex/gpt-5.5:xhigh`, and `zai/glm-5.2:xhigh` for third-family diversity), every other stage leads with the best measured performance-per-dollar candidate (`ralph` prompt-engineer and the `deep-research-codebase` planner move to `openai-codex/gpt-5.5:xhigh`, the explorer to `openai-codex/gpt-5.5:low`), strictly dominated models (`claude-sonnet-5`, `claude-sonnet-4.6`, `gemini-3.1-pro`, `gemini-3.5-flash`) and unbenchmarked entries (`gpt-5.4-mini`, `claude-haiku-4.5`) were dropped, and every remaining chain entry corresponds to a benchmark datapoint. `open-claude-design` keeps its Anthropic-led chain with `claude-fable-5:xhigh` primary.
14
+ - Normalized all GLM-5.2 chain entries to the model's two real reasoning tiers: native `zai`/`zai-coding-cn` entries in medium-tier chains now say `:high` explicitly (replacing misleading `:medium` labels that silently billed at the "high" tier), judgment-tier chains keep `:xhigh`, and the `openrouter/z-ai/glm-5.2` mirror is now always `:xhigh`.
15
+ - Aligned the workflows extension peer dependency with upstream pi TUI `^0.80.3`.
16
+
17
+ ### Fixed
18
+
19
+ - Fixed attached workflow stage viewers so the host `app.tools.expand` keybinding (Ctrl+O by default) toggles live-detail/tool-output expansion just like the main chat, including custom keybinding remaps ([#1607](https://github.com/bastani-inc/atomic/issues/1607)).
20
+ - Fixed workflow stage model fallback so request/context incompatibility failures (HTTP 400/413/422, unsupported tool/parameter, context-window overflow, `invalid_request`/`bad_request`/`too_large` errors) advance the chain to the next candidate, falling back to the current user-selected model when no configured candidate can serve the request; refusals, content-filter/safety blocks, cancellations, and task failures still stop the chain ([#1580](https://github.com/bastani-inc/atomic/issues/1580)).
21
+ - Fixed the builtin `goal` reviewer and `deep-research-codebase` planner model fallback chains missing the `openrouter/anthropic/claude-fable-5` mirror of their primary model, matching the ordering used by the other builtin chains.
22
+ - Classified generic provider `Connection error.` / `fetch failed` transport outages as explicit workflow transport errors so stage fallback metadata can distinguish them from ordinary provider/model outages while preserving the normal next-model fallback order.
23
+ - 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 notices.
24
+
25
+ ## [0.9.4-alpha.11] - 2026-07-03
26
+
27
+ ### Fixed
28
+
29
+ - Fixed attached workflow stage viewers so the host `app.tools.expand` keybinding (Ctrl+O by default) toggles live-detail/tool-output expansion just like the main chat, including custom keybinding remaps ([#1607](https://github.com/bastani-inc/atomic/issues/1607)).
30
+
9
31
  ## [0.9.4-alpha.9] - 2026-07-02
10
32
 
11
33
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.4-alpha.9",
3
+ "version": "0.9.4",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -38,6 +38,10 @@ export interface KeybindingsLike {
38
38
  matches(data: string, action: string): boolean;
39
39
  }
40
40
 
41
+ export const APP_ACTION = {
42
+ toolsExpand: "app.tools.expand",
43
+ } as const;
44
+
41
45
  export const TUI_ACTION = {
42
46
  editorCursorUp: "tui.editor.cursorUp",
43
47
  editorCursorDown: "tui.editor.cursorDown",
@@ -48,6 +48,8 @@ export interface OverlayUISurface {
48
48
  focusHostInlineCustomUi?: () => boolean;
49
49
  getEditorComponent?: () => PiEditorFactory | undefined;
50
50
  getChatRenderSettings?: () => OverlayChatRenderSettings | undefined;
51
+ getToolsExpanded?: () => boolean;
52
+ setToolsExpanded?: (expanded: boolean) => void;
51
53
  getFooterDataProvider?: () => ReadonlyFooterDataProvider;
52
54
  setStatus?: (key: string, value: string | undefined) => void;
53
55
  }
@@ -334,6 +336,8 @@ export function buildGraphOverlayAdapter(
334
336
  piKeybindings: keybindings,
335
337
  piEditorFactory: ui?.getEditorComponent?.(),
336
338
  getChatRenderSettings: ui?.getChatRenderSettings,
339
+ getToolsExpanded: ui?.getToolsExpanded,
340
+ setToolsExpanded: ui?.setToolsExpanded,
337
341
  footerData: ui?.getFooterDataProvider?.(),
338
342
  // Pi-tui owns terminal dimensions; thread its row count down
339
343
  // so the overlay frame fills the actual viewport rather than
@@ -3,7 +3,7 @@ import {
3
3
  handlePromptCardInput,
4
4
  isPromptEscapeInput,
5
5
  } from "./prompt-card.js";
6
- import { isKeybindingsLike } from "./keybindings-adapter.js";
6
+ import { APP_ACTION, isKeybindingsLike, matchesAction } from "./keybindings-adapter.js";
7
7
  import {
8
8
  setComponentFocused,
9
9
  setEditorFocused,
@@ -35,6 +35,7 @@ export function handleStageChatInput(
35
35
  ctx.requestRender?.();
36
36
  return true;
37
37
  }
38
+ if (handleToolsExpandInput(ctx, data)) return true;
38
39
  if (ctx.mountedCustomUi) {
39
40
  return handleMountedCustomUiInput(ctx, data);
40
41
  }
@@ -87,6 +88,16 @@ export function handleStageChatInput(
87
88
  return ctx.chatHost.handleInput(data);
88
89
  }
89
90
 
91
+ function handleToolsExpandInput(ctx: StageChatViewContext, data: string): boolean {
92
+ const keybindings = isKeybindingsLike(ctx.piKeybindings) ? ctx.piKeybindings : undefined;
93
+ if (!matchesAction(keybindings, data, APP_ACTION.toolsExpand)) return false;
94
+ const expanded = ctx.getToolsExpanded?.() === true;
95
+ ctx.setToolsExpanded?.(!expanded);
96
+ ctx.chatHost.invalidate();
97
+ ctx.requestRender?.();
98
+ return true;
99
+ }
100
+
90
101
  function handleMountedCustomUiInput(
91
102
  ctx: StageChatViewContext,
92
103
  data: string,
@@ -57,6 +57,8 @@ export function initializeStageChatView(
57
57
  ctx.piTheme = opts.piTheme;
58
58
  ctx.piKeybindings = opts.piKeybindings;
59
59
  ctx.piEditorFactory = opts.piEditorFactory;
60
+ ctx.getToolsExpanded = opts.getToolsExpanded;
61
+ ctx.setToolsExpanded = opts.setToolsExpanded;
60
62
  ctx.stageUiBroker = opts.stageUiBroker ?? stageUiBroker;
61
63
  ctx.canSubmitPrompt = opts.canSubmitPrompt;
62
64
  ctx.mountedCustomUi = null;
@@ -63,6 +63,10 @@ export interface StageChatViewOpts {
63
63
  getChatRenderSettings?: () =>
64
64
  | Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">>
65
65
  | undefined;
66
+ /** Parent host expansion state for tool-output/live-detail widgets. */
67
+ getToolsExpanded?: () => boolean;
68
+ /** Toggle parent host expansion state for tool-output/live-detail widgets. */
69
+ setToolsExpanded?: (expanded: boolean) => void;
66
70
  /** Parent footer data provider inherited from the host UI for core footer/usage rendering. */
67
71
  footerData?: ReadonlyFooterDataProvider;
68
72
  /**
@@ -133,6 +137,8 @@ export interface StageChatViewContext {
133
137
  piTheme: unknown;
134
138
  piKeybindings: unknown;
135
139
  piEditorFactory: StageChatViewOpts["piEditorFactory"] | undefined;
140
+ getToolsExpanded: (() => boolean) | undefined;
141
+ setToolsExpanded: ((expanded: boolean) => void) | undefined;
136
142
  chatHost: ChatSessionHost<NoticeEntry>;
137
143
  stageUiBroker: StageUiBroker;
138
144
  canSubmitPrompt: ((runId: string, stageId: string, promptId: string) => boolean) | undefined;
@@ -83,6 +83,8 @@ export class StageChatView implements Component, Focusable {
83
83
  private piTheme!: StageChatViewContext["piTheme"];
84
84
  private piKeybindings!: StageChatViewContext["piKeybindings"];
85
85
  private piEditorFactory!: StageChatViewContext["piEditorFactory"];
86
+ private getToolsExpanded!: StageChatViewContext["getToolsExpanded"];
87
+ private setToolsExpanded!: StageChatViewContext["setToolsExpanded"];
86
88
  private chatHost!: StageChatViewContext["chatHost"];
87
89
  private stageUiBroker!: StageChatViewContext["stageUiBroker"];
88
90
  private canSubmitPrompt!: StageChatViewContext["canSubmitPrompt"];
@@ -213,6 +215,8 @@ export class StageChatView implements Component, Focusable {
213
215
  void this.piTheme;
214
216
  void this.piKeybindings;
215
217
  void this.piEditorFactory;
218
+ void this.getToolsExpanded;
219
+ void this.setToolsExpanded;
216
220
  void this.stageUiBroker;
217
221
  void this.canSubmitPrompt;
218
222
  void this.mountingRequestId;
@@ -41,6 +41,10 @@ export interface WorkflowAttachPaneOpts {
41
41
  piEditorFactory?: (tui: TUI, theme: EditorTheme, keybindings: unknown) => EditorComponent;
42
42
  /** Parent chat rendering settings and extension renderers, inherited from the host UI. */
43
43
  getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">> | undefined;
44
+ /** Parent host tool-output/live-detail expansion state, inherited from the host UI. */
45
+ getToolsExpanded?: () => boolean;
46
+ /** Parent host tool-output/live-detail expansion setter, inherited from the host UI. */
47
+ setToolsExpanded?: (expanded: boolean) => void;
44
48
  /** Parent footer data provider, inherited so attached chats can render the core coding-agent footer. */
45
49
  footerData?: ReadonlyFooterDataProvider;
46
50
  /**
@@ -24,16 +24,9 @@ import type { ChatMessageRenderOptions, ReadonlyFooterDataProvider } from "@bast
24
24
  import type { Store } from "../shared/store.js";
25
25
  import type { GraphTheme } from "./graph-theme.js";
26
26
  import { GraphView } from "./graph-view.js";
27
- import {
28
- StageChatView,
29
- type StageChatDetachMetadata,
30
- type StageChatDetachReason,
31
- } from "./stage-chat-view.js";
27
+ import { StageChatView, type StageChatDetachMetadata, type StageChatDetachReason } from "./stage-chat-view.js";
32
28
  import { Key, matchesKey } from "./text-helpers.js";
33
- import type {
34
- StageControlHandle,
35
- StageControlRegistry,
36
- } from "../runs/foreground/stage-control-registry.js";
29
+ import type { StageControlHandle, StageControlRegistry } from "../runs/foreground/stage-control-registry.js";
37
30
  import type { StageUiBroker } from "../shared/stage-ui-broker.js";
38
31
  import type { StageSnapshot, StoreSnapshot } from "../shared/store-types.js";
39
32
  import { expandWorkflowGraph } from "../shared/expanded-workflow-graph.js";
@@ -68,6 +61,8 @@ export class WorkflowAttachPane implements Component {
68
61
  private piKeybindings?: unknown;
69
62
  private piEditorFactory?: (tui: TUI, theme: EditorTheme, keybindings: unknown) => EditorComponent;
70
63
  private getChatRenderSettings?: () => Partial<Omit<ChatMessageRenderOptions, "ui" | "cwd">> | undefined;
64
+ private getToolsExpanded?: () => boolean;
65
+ private setToolsExpanded?: (expanded: boolean) => void;
71
66
  private footerData?: ReadonlyFooterDataProvider;
72
67
  private now: () => number;
73
68
  private mode: WorkflowAttachPaneMode = "graph";
@@ -104,6 +99,8 @@ export class WorkflowAttachPane implements Component {
104
99
  this.piKeybindings = opts.piKeybindings;
105
100
  this.piEditorFactory = opts.piEditorFactory;
106
101
  this.getChatRenderSettings = opts.getChatRenderSettings;
102
+ this.getToolsExpanded = opts.getToolsExpanded;
103
+ this.setToolsExpanded = opts.setToolsExpanded;
107
104
  this.footerData = opts.footerData;
108
105
  this.now = opts.now ?? Date.now;
109
106
  this.unsubscribeStore = this.store.subscribe((snapshot) => this._handleStoreUpdate(snapshot));
@@ -197,6 +194,8 @@ export class WorkflowAttachPane implements Component {
197
194
  piKeybindings: this.piKeybindings,
198
195
  piEditorFactory: this.piEditorFactory,
199
196
  getChatRenderSettings: this.getChatRenderSettings,
197
+ getToolsExpanded: this.getToolsExpanded,
198
+ setToolsExpanded: this.setToolsExpanded,
200
199
  footerData: this.footerData,
201
200
  getViewportRows: this.getViewportRows,
202
201
  stageUiBroker: this.stageUiBroker,