@bastani/atomic 0.9.28-alpha.5 → 0.9.29-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.
Files changed (44) hide show
  1. package/CHANGELOG.md +95 -0
  2. package/dist/builtin/intercom/CHANGELOG.md +7 -0
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/CHANGELOG.md +6 -0
  5. package/dist/builtin/mcp/package.json +1 -1
  6. package/dist/builtin/subagents/CHANGELOG.md +12 -0
  7. package/dist/builtin/subagents/package.json +1 -1
  8. package/dist/builtin/web-access/package.json +1 -1
  9. package/dist/builtin/workflows/CHANGELOG.md +41 -0
  10. package/dist/builtin/workflows/package.json +1 -1
  11. package/dist/builtin/workflows/src/extension/index.bundle.mjs +2 -2
  12. package/dist/builtin/workflows/src/index.js +2 -2
  13. package/dist/core/execution-model-router.d.ts +1 -1
  14. package/dist/core/execution-model-router.d.ts.map +1 -1
  15. package/dist/core/execution-model-router.js +37 -15
  16. package/dist/core/execution-model-router.js.map +1 -1
  17. package/dist/core/model-routing-constraints.d.ts +2 -0
  18. package/dist/core/model-routing-constraints.d.ts.map +1 -1
  19. package/dist/core/model-routing-constraints.js.map +1 -1
  20. package/dist/core/service-tier-warnings.d.ts +4 -0
  21. package/dist/core/service-tier-warnings.d.ts.map +1 -0
  22. package/dist/core/service-tier-warnings.js +7 -0
  23. package/dist/core/service-tier-warnings.js.map +1 -0
  24. package/dist/core/settings-manager-basic-accessors.js +8 -4
  25. package/dist/core/settings-manager-basic-accessors.js.map +1 -1
  26. package/dist/core/settings-types.d.ts +5 -1
  27. package/dist/core/settings-types.d.ts.map +1 -1
  28. package/dist/core/settings-types.js.map +1 -1
  29. package/dist/modes/interactive/interactive-render-chat.js +10 -0
  30. package/dist/modes/interactive/interactive-render-chat.js.map +1 -1
  31. package/dist/modes/print-mode.d.ts.map +1 -1
  32. package/dist/modes/print-mode.js +5 -0
  33. package/dist/modes/print-mode.js.map +1 -1
  34. package/docs/codemode.md +1 -1
  35. package/docs/models/model-selection.md +1 -1
  36. package/docs/models/reference.md +2 -2
  37. package/docs/providers.md +6 -4
  38. package/docs/settings.md +18 -6
  39. package/docs/subagents/reference.md +4 -4
  40. package/docs/workflows/api-reference.md +4 -4
  41. package/docs/workflows/authoring.md +4 -0
  42. package/docs/workflows/operations.md +1 -1
  43. package/npm-shrinkwrap.json +32 -32
  44. package/package.json +3 -3
package/CHANGELOG.md CHANGED
@@ -2,6 +2,101 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.29-alpha.1] - 2026-10-08
6
+
7
+ ### Added
8
+
9
+ - Added `gpt-6.1-sol-ultrafast` and `gpt-5.6-sol-ultrafast` model variants for `openai` and `openai-api`, and `gpt-6.1-sol-ultrafast` for `openai-codex`. GPT-6.1 Sol Ultrafast is priced at OpenAI's published $12 input and $60 output per million tokens; GPT-5.6 Sol Ultrafast is a limited preview without published rates, priced at six times Standard. Ultrafast access depends on your OpenAI account ([#3529](https://github.com/bastani-inc/atomic/issues/3529)).
10
+ - Added OpenAI's GPT Image models (`gpt-image-2.5-flare`, `gpt-image-2.5-sunburst`, `gpt-image-2`, `gpt-image-1.5`, `gpt-image-1`, `gpt-image-1-mini`) as `openai-images` image models for codemode's `models.generateImages()`, extensions, and workflow `generateImages()` calls. They reuse your OpenAI API key, saved for `openai-api` or `openai` or set in `OPENAI_API_KEY`, and have no separate login; a ChatGPT sign-in cannot use them. See [Subscription and API key at the same time](docs/providers.md#subscription-and-api-key-at-the-same-time) ([#3527](https://github.com/bastani-inc/atomic/issues/3527)).
11
+ - Added the `modelRouting.allowedModels` and `modelRouting.excludedModels` settings, which keep workflow stages and subagents with `model: "auto"` from routing to models you did not allow, including through fallbacks. They take full model IDs or glob patterns, matched like `enabledModels` and `--models`. If they leave no eligible model, the stage or subagent fails before launch with an error naming the setting. `routerSelection` now also lists the `candidates` the router could choose from after filtering. See [modelRouting](docs/settings.md#modelrouting) ([#3528](https://github.com/bastani-inc/atomic/issues/3528)).
12
+
13
+ ### Changed
14
+
15
+ - When the OpenAI API or the ChatGPT Codex backend rejects a Fast or Ultrafast tier, Atomic retries the request once at the default tier instead of failing the turn and shows a warning, for example `ultrafast isn't available for gpt-5.6-sol on this account; ran at default`. With an OpenAI API key, Atomic shows the same warning when the server reports a slower tier than the model you selected. Interactive sessions show it under the response, print mode writes it to stderr, and JSON mode includes it in the message's `diagnostics`. With ChatGPT sign-in (`openai` or `openai-codex`) there is no such warning, because the ChatGPT backend reports `default` for every tier it applies ([#3529](https://github.com/bastani-inc/atomic/issues/3529)).
16
+
17
+ ## [0.9.28] - 2026-10-08
18
+
19
+ ### Added
20
+
21
+ - Added `fromSignal` to the `session_shutdown` event. It is `true` when a `quit` comes from a host `SIGTERM` or `SIGHUP` instead of Ctrl+D or `/quit`, and `runtimeHost.dispose({ fromSignal: true })` sets it for SDK hosts ([#3492](https://github.com/bastani-inc/atomic/issues/3492)).
22
+ - Added the API-key-only providers `openai-api` and `anthropic-api`, so you can keep a ChatGPT or Claude subscription login on `openai`/`anthropic` and still use an API key for the same models. See [Subscription and API key at the same time](docs/providers.md#subscription-and-api-key-at-the-same-time).
23
+ - `session.compact()` and extension `ctx.compact()` accept `compactionModel` to pick the compaction model for one run without changing the `compactionModel` setting. An unknown ID rejects before the active run is interrupted. See [Compaction and tree navigation](docs/sdk.md#compaction-and-tree-navigation).
24
+ - Added terminal program-status reporting through OSC 7501 for runs, compaction, extension prompts, and sign-in. Compatible terminals can show when Atomic is working, waiting for input, done, or has failed.
25
+ - Added recorded response and tool execution durations. Bash tool results show their execution time, including after a session is resumed.
26
+ - Added OpenAI's GPT-6 Luna classifier through the Decisions API with `OPENAI_API_KEY`, and image input for codemode's `models.classify()` context. See [Classifier models](docs/models.md#use-classifier-models).
27
+ - Added native classification for llama.cpp decision models through `/v1/systemone`. Decision-only models stay out of the chat selector, while chat models keep the next-token classifier fallback. Cached native classifiers remain available after restart.
28
+ - Added exact-name `+name` and `-name` modifiers to CLI `--tools` and SDK `tools`, for example `atomic --tools +codemode,-write`. They change the resolved defaults instead of replacing them and persist across `/reload`.
29
+ - Added `compactionModel` and a **Compaction model** selector in `/settings` to choose a compactor independently of the chat model ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
30
+ - Added verbatim classifier compaction with registered models such as `typesafe/jev-latest`, retaining protected context and recent messages ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
31
+ - Added Morph verbatim compaction with `morph/morph-compactor`, authenticated through `/login morph` or `MORPH_API_KEY` ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
32
+ - Added `session.workflows.observe(observer)`, so SDK hosts can follow the workflow runs the agent launches without writing an extension. The observer receives a snapshot first, then changes in order. Root activity now includes an optional `graph` that lists each stage and `ctx.tool` node with its status, parents, and pending prompt, plus any run-level prompts ([#3476](https://github.com/bastani-inc/atomic/issues/3476)).
33
+ - Added `RunOpts.onStageSessionEvent` so workflow `run()` callers receive each stage session's events, such as message updates and tool executions, tagged with the run and stage ids. Events continue across fallback-model sessions and include nested workflow stages ([#3474](https://github.com/bastani-inc/atomic/issues/3474)).
34
+ - Added `createTranscript()`, which turns session events into the assistant's output as ordered, JSON-serializable text, thinking, and tool call parts, with each tool call joined to its result ([#3475](https://github.com/bastani-inc/atomic/issues/3475)).
35
+ - Added `createAgentSessionAdapter(baseOptions?)` to `@bastani/atomic/workflows`, which builds the default workflow stage-session adapter with `createAgentSession` options shared by every stage ([#3472](https://github.com/bastani-inc/atomic/issues/3472)).
36
+ - Added `ctx.isPresentationOnly` so extension lifecycle hooks can leave execution to the isolated interactive engine while retaining host-side UI ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
37
+ - Exported `publishExtensionContextEffect(ctx, effect, phase?)` with a synchronous commit phase for generation identity handoff, and `registerExtensionContextRetirementEffect(ctx, dispose)` for observer cleanup before authority revocation ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
38
+ - Added Azure Foundry Chat Completions with `azure/deepseek-v4-pro`, using the existing Azure endpoint and deployment-name settings.
39
+ - Added `*` patterns to `--tools` and `--exclude-tools`, including MCP tool names.
40
+ - Added `--no-mcp` to disable built-in MCP support for one run, including interactive sessions.
41
+ - Added `ExtensionAPI.getWorkflowHostModules()` to supply shared host module instances for the exact workflow imports `@bastani/atomic`, `@bastani/pi-ai`, and `@bastani/pi-ai/providers/all`, avoiding repeated package loading ([#3454](https://github.com/bastani-inc/atomic/issues/3454)).
42
+
43
+ ### Changed
44
+
45
+ - `openai-decisions/gpt-6-luna` no longer has its own `/login` entry, so you do not need to sign in again. The classifier uses the API key saved for `openai-api`, then the one saved for `openai`, then `OPENAI_API_KEY`. A ChatGPT sign-in on `openai` cannot call the Decisions API and is not used, so the classifier stays unavailable until one of those keys exists. See [Classifier models](docs/models.md#use-classifier-models).
46
+ - Moved the GPT-6 Luna classifier from `openai/gpt-6-luna` to its own API-key-only provider, `openai-decisions/gpt-6-luna`, named "GPT-6 Luna Decisions". `openai/gpt-6-luna` now always means the chat model, and the classifier stays available while `openai` uses ChatGPT sign-in. Update `routerModel`, `compactionModel`, `structured_output` and codemode references that meant the classifier. See [Classifier models](docs/models.md#use-classifier-models).
47
+ - Codemode labels returned output items with numbered headers and places console output in a separate trailing block.
48
+ - Codemode now says that `ALL_TOOLS`, `searchTools()`, and `describeTool()` list only script-callable tools, and `searchTools()`/`describeTool()` answer an exact model-only tool name such as `subagent` with a hint to call it directly instead of returning nothing ([#3510](https://github.com/bastani-inc/atomic/issues/3510))
49
+ - Changed `atomic mcp login --timeout` to limit the whole sign-in, including requests to the authorization server, instead of only the wait for the browser.
50
+ - Compaction planners now receive structured per-message lines instead of a numbered transcript ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
51
+ - Compaction cards show the backend and model used, including `summary (pi fallback)` for policy-refusal summaries ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
52
+ - Home and End move the editor cursor to the start and end of the line. Ctrl+Home and Ctrl+End jump to the top and bottom of the fullscreen transcript.
53
+ - Renamed the Azure provider to `azure`. Existing provider references remain supported, and startup migrates credentials and configuration without replacing existing `azure` entries. Project files are migrated only when trusted.
54
+ - Codemode built-ins are frozen in pi 1.0.4. Patches to built-ins are ignored instead of crashing the host.
55
+
56
+ ### Fixed
57
+
58
+ - Fixed the startup project trust prompt opening inside the fully painted interface. Atomic now shows the prompt on its own and paints the interface after you choose.
59
+ - Fixed the "This project is not trusted" warning staying in the transcript after you trusted the project at the startup prompt. The warning still appears when you decline trust or leave the project untrusted.
60
+ - Fixed Herdr restoring panes without their Atomic conversation after a Herdr restart. With Herdr 0.9.2 or newer, Atomic now reports `atomic --session <id>` (plus `--session-dir` for a custom session directory) so Herdr can reopen the same session in the same pane, and keeps it registered when Herdr or the system ends Atomic with `SIGTERM` or `SIGHUP`. Quitting with Ctrl+D or `/quit` still clears it, and older Herdr versions keep status reporting without restore ([#3492](https://github.com/bastani-inc/atomic/issues/3492)).
61
+ - Updated the sandbox example extension's lockfile to `shell-quote` 1.12.0, resolving the `quote()` command-injection advisory [GHSA-pqg4-j6r4-53mv](https://github.com/advisories/GHSA-pqg4-j6r4-53mv) in its `@anthropic-ai/sandbox-runtime` dependency.
62
+ - Fixed hyperlinks being disabled in Herdr panes, while keeping image protocols off even when outer-terminal environment variables are inherited ([#10573](https://github.com/earendil-works/pi/issues/10573)).
63
+ - Fixed `outputPad` applying inconsistently to tool results, shell output, summaries, skill invocations, and custom transcript entries. Changing it in `/settings` now updates existing blocks without rebuilding the transcript.
64
+ - Fixed fullscreen text selections surviving a transcript rebuild and selecting unrelated replacement text.
65
+ - Fixed extension-registered MCP servers silently remaining unused when an SDK host has no MCP contribution consumer. Atomic now reports each unhandled server once.
66
+ - Fixed a false `Timeout waiting for response to resume_queued_messages` error when you send a message while a paused queue is still waiting for the agent's current turn to finish, and the follow-up `Agent is already processing` error caused by the stale paused state. The resume now waits for the turn instead of giving up after 30 seconds ([#3493](https://github.com/bastani-inc/atomic/issues/3493)).
67
+ - Fixed extension `session_shutdown` handlers being cut off when you quit the interactive CLI. The engine now gets up to 5 seconds to finish extension shutdown, instead of about 750 ms, before it is force-stopped.
68
+ - Fixed slow extension loading and `/reload` on Windows. Imports such as `./helper.js` that point at TypeScript sources no longer make the loader try hundreds of missing files before finding `helper.ts`.
69
+ - Fixed `/mcp` waiting for startup connections before opening. The manager now updates live and stays usable while enabling, disabling, or reconnecting servers.
70
+ - Fixed images being dropped as unresizable under `node --watch` when Node sends its own messages on the image resize worker channel.
71
+ - Fixed codemode's tool description omitting `await` for `searchTools()`, `describeTool()`, and `describeNamespace()`, which could make scripts return unresolved promises instead of tool information.
72
+ - Fixed slash commands and shell-command drafts typed during interactive startup running before Enter, including when launching with `--model` or `--provider`. Enter-terminated input stays ordered and unfinished text remains in the editor.
73
+ - Fixed a stuck MCP server startup blocking re-enabling the server or shutting down after it was disabled.
74
+ - Fixed clipboard paste doing nothing in Termux, and failed copies there omitting the Termux:API install hint.
75
+ - Fixed `!` and RPC `bash` output keeping fragments of color codes, such as a stray `m`, when a code was split across output chunks.
76
+ - Fixed MCP OAuth sign-ins that could not be cancelled while waiting on the authorization server and kept running after the session ended. The sign-in screen now cancels with Esc at every step, session shutdown aborts a running sign-in, and each request to the authorization server times out after 15 seconds.
77
+ - Fixed shutdown waiting up to 15 seconds to refresh an MCP OAuth token that was about to expire, only to close the server's session.
78
+ - Policy refusals from any chat compaction model, including `auto`, explicit chat-model IDs, and borrowed fallback entries, now try pi's summary compaction on the same model before continuing through fallback models. Summary requests retry recoverable errors; policy refusals are never retried. Classifier and Morph failures advance directly to fallback models ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
79
+ - Morph compaction failures now include the HTTP status and a bounded, credential-redacted response excerpt in error diagnostics ([#3470](https://github.com/bastani-inc/atomic/issues/3470)).
80
+ - Fixed Herdr publishing duplicate pane activity when startup workflow recovery refreshes an unchanged activity snapshot ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
81
+ - Fixed Herdr skipping a return to an earlier pane activity after an intervening report timed out or failed ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
82
+ - Fixed project `.atomic/settings.json`, extensions, skills, and other trust-gated resources being ignored without notice in untrusted projects. Interactive sessions now show a warning that points to `/trust`, matching upstream pi.
83
+ - Fixed `@bastani/atomic/workflows` type declarations disagreeing with the runtime: `StageSnapshot` now declares `parentIds`, `executionOrder`, `model`, `startedAt`, `endedAt`, and `durationMs`; `Store` now declares `snapshot()`, `graphSnapshot()`, and `subscribe()`; and an `AgentSessionAdapter.create` can return `createAgentSession(...)` directly without a type cast ([#3473](https://github.com/bastani-inc/atomic/issues/3473)).
84
+ - Fixed `run()` from `@bastani/atomic/workflows` failing at the first `ctx.task` or `ctx.stage` with "prompt adapter not configured" when `opts.adapters` was omitted. Stages now default to in-process `createAgentSession` sessions with in-memory session managers ([#3472](https://github.com/bastani-inc/atomic/issues/3472)).
85
+ - Fixed startup extension confirmations, including workflow recovery prompts, losing keyboard focus during terminal initialization ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
86
+ - Fixed queued protected workflow notices producing `SessionClosed` replies during reload. Successful reload now preserves those notices for the replacement extensions without releasing an explicitly paused queue ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
87
+ - Fixed idle workflow status and control commands not displaying their output in isolated interactive sessions, including after `/reload` ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
88
+ - Fixed extension reload skipping remaining commit effects and predecessor shutdown when a commit effect and an SDK error observer both throw. Reload now retains both failures while keeping the replacement extensions active ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
89
+ - Fixed syntax colors leaking across lines in multiline tokens and interpolation substitutions inheriting string colors.
90
+ - Invalid router and provider-list settings retain their configuration validation errors during Azure provider migration.
91
+ - Fixed codemode `tools.read()` returning text instead of an image block for image files. Pass the result to `image()` to display it ([#10251](https://github.com/earendil-works/pi/issues/10251)).
92
+ - Fixed MCP OAuth dynamic registration omitting `application_type`, which caused redirect URI rejections on OpenID Connect servers ([#10493](https://github.com/earendil-works/pi/issues/10493)).
93
+ - Fixed `--tools` removing MCP tools when selecting codemode. MCP tools remain callable unless the allowlist contains an entry starting with `mcp__`; unmatched tools are not declared directly.
94
+ - Fixed shutdown leaving MCP connections open while a server was still connecting ([#10249](https://github.com/earendil-works/pi/issues/10249)).
95
+ - Fixed hidden tools appearing in prompt rules and skill-reading hints. Codemode tool declarations now include each tool's prompt guidelines ([#10343](https://github.com/earendil-works/pi/issues/10343)).
96
+ - Fixed interactive quit waiting on pending extension commands, which could leave connecting MCP servers running.
97
+ - Fixed HTTP/2 pending stream cancellations being treated as user cancellations instead of retryable transport failures.
98
+ - Updated the bundled `proxy-addr` dependency to 2.0.8, fixing IP spoofing through IPv4-mapped IPv6 trusted subnets.
99
+
5
100
  ## [0.9.28-alpha.5] - 2026-10-08
6
101
 
7
102
  ### Added
@@ -4,6 +4,13 @@ All notable changes to the `pi-intercom` extension will be documented in this fi
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.9.28] - 2026-10-08
8
+
9
+ ### Fixed
10
+
11
+ - Fixed a slow-starting intercom broker taking over a live broker's socket, which left a connected session unreachable by `intercom list` and `send`, so messages to it were silently not delivered ([#3505](https://github.com/bastani-inc/atomic/issues/3505)).
12
+ - Fixed intercom occasionally reporting `Intercom not connected: connect ENOENT …/broker.sock` when a session connected just as an idle broker was shutting down.
13
+
7
14
  ## [0.9.28-alpha.5] - 2026-10-08
8
15
 
9
16
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/intercom",
3
- "version": "0.9.28-alpha.5",
3
+ "version": "0.9.29-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,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.28] - 2026-10-08
11
+
12
+ ### Fixed
13
+
14
+ - Fixed MCP OAuth dynamic registration omitting `application_type`, which caused redirect URI rejections on OpenID Connect servers ([#10493](https://github.com/earendil-works/pi/issues/10493)).
15
+
10
16
  ## [0.9.28-alpha.1] - 2026-10-05
11
17
 
12
18
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/mcp",
3
- "version": "0.9.28-alpha.5",
3
+ "version": "0.9.29-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic's pi-compatible MCP integration for server connections, OAuth, tools, resources, and terminal management.",
6
6
  "contributors": [
@@ -2,6 +2,18 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.9.29-alpha.1] - 2026-10-08
6
+
7
+ ### Added
8
+
9
+ - Subagents with `model: "auto"` honor the new `modelRouting.allowedModels` and `modelRouting.excludedModels` settings for the routed model, its fallbacks, and the current chat model used when routing fails, and the launch fails with an error naming the setting when no allowed model remains. A subagent result's `routerSelection` now lists the `candidates` the router could choose from ([#3528](https://github.com/bastani-inc/atomic/issues/3528)).
10
+
11
+ ## [0.9.28] - 2026-10-08
12
+
13
+ ### Changed
14
+
15
+ - Updated the bundled Cua Driver skill to upstream `cua-driver-rs-v0.34.0`, with focused runtime, workflow, and visual guides covering authorization, exact targets, observation, and outcome verification.
16
+
5
17
  ## [0.9.28-alpha.4] - 2026-10-07
6
18
 
7
19
  ### Changed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/subagents",
3
- "version": "0.9.28-alpha.5",
3
+ "version": "0.9.29-alpha.1",
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.28-alpha.5",
3
+ "version": "0.9.29-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,47 @@ 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.29-alpha.1] - 2026-10-08
10
+
11
+ ### Added
12
+
13
+ - Stages with `model: "auto"`, including builtin workflow stages that do not name a model, honor the new `modelRouting.allowedModels` and `modelRouting.excludedModels` settings for the routed model and every fallback, and fail before launch with an error naming the setting when no allowed model remains. A stage's `routerSelection` in `workflow` status now lists the `candidates` the router could choose from ([#3528](https://github.com/bastani-inc/atomic/issues/3528)).
14
+
15
+ ## [0.9.28] - 2026-10-08
16
+
17
+ ### Added
18
+
19
+ - Added a Claude Haiku 5.5 prompt-engineering guide based on Anthropic's model-specific guidance, covering effort, search grounding, JSON/tool interactions, completion, verification, and refusal handling.
20
+ - Added `createAgentSessionAdapter(baseOptions?)`, which builds the default workflow stage-session adapter with `createAgentSession` options shared by every stage ([#3472](https://github.com/bastani-inc/atomic/issues/3472)).
21
+
22
+ ### Changed
23
+
24
+ - Updated the bundled Impeccable skill to 4.5.0 with engine 0.1.11, adding the `generate` playbook, component-review guidance, and reading, operating, and persuasion design modes. Atomic's telemetry and update-check defaults remain off.
25
+ - Changed the `resumeInFlight` default from `"ask"` to `"never"`. Atomic no longer prompts at startup to resume interrupted durable workflows; resume them with `/workflow resume`, or set `resumeInFlight` to `"ask"` or `"auto"` in the workflow config to restore startup recovery.
26
+
27
+ ### Fixed
28
+
29
+ - Fixed managed PostgreSQL health checks failing a workflow stage on a healthy but busy host: an unanswered check now retries with a short backoff for up to 15 seconds, the connection limit is 3 seconds instead of 1, and the "did not answer a health check in time" diagnostic clears once PostgreSQL answers again ([#3491](https://github.com/bastani-inc/atomic/issues/3491))
30
+ - Fixed Atomic hanging at startup, never accepting input, when the current directory had interrupted durable workflows and `resumeInFlight` was `ask`. Startup no longer waits for workflow recovery; the "Resume interrupted workflows?" prompt now appears after the session is ready.
31
+ - Fixed startup recovery and SDK crash recovery skipping interrupted workflows when the project directory was reached through a symlink, such as `/tmp` or `/var` on macOS.
32
+ - Fixed long workflow runs retaining a full metadata snapshot for every checkpoint and message update. Atomic now compacts superseded snapshots, removes their duplicate input payloads, and bounds settled delivery state across root and child runs, including failed-stage sticky receipts, while preserving resume and message deduplication ([#3467](https://github.com/bastani-inc/atomic/issues/3467)).
33
+ - Fixed archived sticky message receipts being ignored when a failed stage retries delivery without an explicit recipient or through live Intercom transport. Retry forwarding and confirmation deduplication survive cold resume, while a new recipient session can still receive the message ([#3467](https://github.com/bastani-inc/atomic/issues/3467)).
34
+ - Fixed sticky messages delivered as pre-start context being forwarded again to the same live session, including after receipt archival and cold resume. Concurrent confirmations remain deduplicated when a stage finishes before they are saved, while a new recipient session can still receive the message ([#3467](https://github.com/bastani-inc/atomic/issues/3467)).
35
+ - Fixed interrupted sticky receipt saves losing delivery counts after retry or cold resume and incorrectly reporting delivered messages as undeliverable ([#3467](https://github.com/bastani-inc/atomic/issues/3467)).
36
+ - Fixed Goal infrastructure failures incorrectly ending the goal as `needs_human`. The ledger now stays `active`, and direct resume in the same session retries the interrupted stage and completes its turn without quitting Atomic. Malformed reviewer output, including exhausted schema corrections, still requires human attention. Older synthetic failure verdicts with identifiable database/checkpoint diagnostics are also recoverable; ambiguous old failures remain unchanged ([#3466](https://github.com/bastani-inc/atomic/issues/3466)).
37
+ - Fixed Goal treating exhausted reviewer corrections for invalid `structured_output` inputs, including whitespace-only instructions and empty named state, as resumable infrastructure failures instead of `needs_human` ([#3466](https://github.com/bastani-inc/atomic/issues/3466)).
38
+ - Fixed Goal reviewer corrections hiding an earlier provider failure when a later `structured_output` call has invalid inputs. If no correction succeeds, the goal remains resumable instead of becoming `needs_human` ([#3466](https://github.com/bastani-inc/atomic/issues/3466)).
39
+ - Fixed `run()` failing at the first `ctx.task` or `ctx.stage` with "prompt adapter not configured" when `opts.adapters` was omitted. Stages now default to in-process `createAgentSession` sessions with in-memory session managers ([#3472](https://github.com/bastani-inc/atomic/issues/3472)).
40
+ - Fixed `resumeInFlight` having no effect at startup. `auto` now resumes eligible interrupted durable workflows, `ask` requests confirmation when a UI is available, and `never` leaves them for `/workflow resume`. Paused, quit, blocked, failed, awaiting-input, and live-owned runs are not automatically restarted ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
41
+ - Fixed startup recovery of agent-owned workflows retaining the previous session's ownership. Safely recovered runs now allow the current session's agent to inspect and control them ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
42
+ - Fixed recovered workflows disconnecting Intercom with pending-stage authorization errors when workflow extension modules load more than once ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
43
+ - Fixed recovered agent stages failing with a stale extension context after accepted startup recovery. Committed host generations hand off synchronously and retired Store observers detach before authority revocation, independently of shutdown handler order ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
44
+ - Fixed startup recovery offering or restarting another project's workflows when projects share a database. Automatic recovery now requires the original invocation directory to match the current working directory; cross-project recovery remains available through `/workflow resume` ([#3468](https://github.com/bastani-inc/atomic/issues/3468)).
45
+ - Fixed durable-workflow shutdown returning before ownership and protected SQL connections terminated, including pending SQL acquisitions, so immediate recovery no longer races a stale executor lock ([#3489](https://github.com/bastani-inc/atomic/issues/3489)).
46
+ - Attached stage chats honor configured transcript jump shortcuts, defaulting to Ctrl+Home and Ctrl+End, without intercepting the editor's Home and End keys.
47
+ - Fixed slow workflow discovery for imports of `@bastani/atomic`, `@bastani/pi-ai`, and `@bastani/pi-ai/providers/all` by sharing the running CLI's modules instead of loading them again for each workflow file. The host's versions take precedence over project copies for these three exact specifiers. These imports no longer need project-local packages in standalone-binary installs; workflow files and relative imports still refresh on reload ([#3454](https://github.com/bastani-inc/atomic/issues/3454)).
48
+ - Fixed workflow stage questions in one process hosting several SDK sessions reaching the wrong session. A stage's `ask_user_question`, readiness question, or `ctx.ui.custom` prompt could go to whichever sibling session started last, so its own `HostInput.questionnaire` was never called and the stage stayed `awaiting_input`. Each session now keeps its own workflow store, stage UI broker, and run registries ([#3456](https://github.com/bastani-inc/atomic/issues/3456)).
49
+
9
50
  ## [0.9.28-alpha.5] - 2026-10-08
10
51
 
11
52
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bastani/workflows",
3
- "version": "0.9.28-alpha.5",
3
+ "version": "0.9.29-alpha.1",
4
4
  "private": true,
5
5
  "description": "Atomic extension for multi-stage workflow authoring and execution.",
6
6
  "contributors": [
@@ -712,10 +712,10 @@ function isRouterSelection(value2) {
712
712
  const fields = pair;
713
713
  return Object.keys(pair).length === 2 && typeof fields.model === "string" && (fields.effort === null || isReasoningLevel(fields.effort));
714
714
  };
715
- return Object.keys(record).every((key) => ["model", "effort", "fallbacks"].includes(key)) && typeof record.model === "string" && (record.effort === null || isReasoningLevel(record.effort)) && (record.fallbacks === undefined || Array.isArray(record.fallbacks) && record.fallbacks.length <= 2 && record.fallbacks.every(isPair) && new Set([
715
+ return Object.keys(record).every((key) => ["model", "effort", "fallbacks", "candidates"].includes(key)) && typeof record.model === "string" && (record.effort === null || isReasoningLevel(record.effort)) && (record.fallbacks === undefined || Array.isArray(record.fallbacks) && record.fallbacks.length <= 2 && record.fallbacks.every(isPair) && new Set([
716
716
  record.model,
717
717
  ...record.fallbacks.map((pair) => pair.model)
718
- ]).size === record.fallbacks.length + 1);
718
+ ]).size === record.fallbacks.length + 1) && (record.candidates === undefined || Array.isArray(record.candidates) && record.candidates.every((id) => typeof id === "string"));
719
719
  }
720
720
  function isModelAttempts(value2) {
721
721
  return Array.isArray(value2) && value2.every((attempt) => {
@@ -710,10 +710,10 @@ function isRouterSelection(value) {
710
710
  const fields = pair;
711
711
  return Object.keys(pair).length === 2 && typeof fields.model === "string" && (fields.effort === null || isReasoningLevel(fields.effort));
712
712
  };
713
- return Object.keys(record).every((key) => ["model", "effort", "fallbacks"].includes(key)) && typeof record.model === "string" && (record.effort === null || isReasoningLevel(record.effort)) && (record.fallbacks === undefined || Array.isArray(record.fallbacks) && record.fallbacks.length <= 2 && record.fallbacks.every(isPair) && new Set([
713
+ return Object.keys(record).every((key) => ["model", "effort", "fallbacks", "candidates"].includes(key)) && typeof record.model === "string" && (record.effort === null || isReasoningLevel(record.effort)) && (record.fallbacks === undefined || Array.isArray(record.fallbacks) && record.fallbacks.length <= 2 && record.fallbacks.every(isPair) && new Set([
714
714
  record.model,
715
715
  ...record.fallbacks.map((pair) => pair.model)
716
- ]).size === record.fallbacks.length + 1);
716
+ ]).size === record.fallbacks.length + 1) && (record.candidates === undefined || Array.isArray(record.candidates) && record.candidates.every((id) => typeof id === "string"));
717
717
  }
718
718
  function isModelAttempts(value) {
719
719
  return Array.isArray(value) && value.every((attempt) => {
@@ -7,7 +7,7 @@ export interface ModelRoutingContext {
7
7
  readonly modelRegistry: Pick<ModelRegistry, "getAll" | "getAvailable" | "streamSimple" | "containsConfiguredCredential"> & Partial<Pick<ModelRegistry, "getProviderAuthStatus" | "getProviderAuth" | "getClassifierModel" | "classify">>;
8
8
  readonly model?: Model<Api>;
9
9
  getRouterModel(): string;
10
- /** Provider filters from settings.json `modelRouting`; absent means every provider is a candidate. */
10
+ /** Provider and model filters from settings.json `modelRouting`; absent means every model is a candidate. */
11
11
  getModelRouting?(): ModelRoutingSettings;
12
12
  }
13
13
  export interface ModelRoute {
@@ -1 +1 @@
1
- {"version":3,"file":"execution-model-router.d.ts","sourceRoot":"","sources":["../../src/core/execution-model-router.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,GAAG,EAKR,KAAK,KAAK,EACV,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AASzD,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAMN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAC3B,aAAa,EACb,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,8BAA8B,CAC3E,GACA,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,uBAAuB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,UAAU,CAAC,CAAC,CAAC;IAC/G,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,cAAc,IAAI,MAAM,CAAC;IACzB,sGAAsG;IACtG,eAAe,CAAC,IAAI,oBAAoB,CAAC;CACzC;AACD,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,aAAa,IAAI,IAAI,CAAC;IACtB,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IACnD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAExC,YAAY,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,UAAU,EAI1D;CACD;AA6CD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAChD,GAAG,EAAE,mBAAmB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,WAAW,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,+FAA+F;IAC/F,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC,GAAG,OAAO,CAAC,UAAU,CAAC,CAmXtB"}
1
+ {"version":3,"file":"execution-model-router.d.ts","sourceRoot":"","sources":["../../src/core/execution-model-router.ts"],"names":[],"mappings":"AAEA,OAAO,EACN,KAAK,GAAG,EAKR,KAAK,KAAK,EACV,MAAM,gBAAgB,CAAC;AAGxB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAUzD,OAAO,EAEN,KAAK,gBAAgB,EACrB,KAAK,iBAAiB,EAEtB,MAAM,gCAAgC,CAAC;AAGxC,OAAO,EAMN,KAAK,SAAS,EACd,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAGhE,MAAM,WAAW,mBAAmB;IACnC,QAAQ,CAAC,aAAa,EAAE,IAAI,CAC3B,aAAa,EACb,QAAQ,GAAG,cAAc,GAAG,cAAc,GAAG,8BAA8B,CAC3E,GACA,OAAO,CAAC,IAAI,CAAC,aAAa,EAAE,uBAAuB,GAAG,iBAAiB,GAAG,oBAAoB,GAAG,UAAU,CAAC,CAAC,CAAC;IAC/G,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAC5B,cAAc,IAAI,MAAM,CAAC;IACzB,6GAA6G;IAC7G,eAAe,CAAC,IAAI,oBAAoB,CAAC;CACzC;AACD,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,eAAe,EAAE,iBAAiB,CAAC;IAC5C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,aAAa,IAAI,IAAI,CAAC;IACtB,WAAW,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;CACzD;AAED;;;;GAIG;AACH,qBAAa,yBAA0B,SAAQ,KAAK;IACnD;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,UAAU,CAAC;IAExC,YAAY,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,EAAE,UAAU,EAI1D;CACD;AA6CD,wBAAsB,mBAAmB,CAAC,KAAK,EAAE;IAChD,GAAG,EAAE,mBAAmB,CAAC;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,WAAW,CAAC,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC1C,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,kEAAkE;IAClE,SAAS,CAAC,EAAE,iBAAiB,CAAC;IAC9B,+FAA+F;IAC/F,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,wBAAwB,CAAC,EAAE,OAAO,CAAC;CACnC,GAAG,OAAO,CAAC,UAAU,CAAC,CA4YtB"}
@@ -3,6 +3,7 @@ import { join } from "node:path";
3
3
  import { containsKnownEnvCredential, getServiceTierCost, getSupportedThinkingLevels, isModelType, } from "@bastani/pi-ai";
4
4
  import { Type } from "typebox";
5
5
  import { getDocsPath } from "../config.js";
6
+ import { resolveModelScopeFromModels } from "./model-resolver-scope.js";
6
7
  import { ROUTING_REQUEST_BYTES } from "./model-routing-bytes.js";
7
8
  import { describeOption, distinctTop, rankCandidates, } from "./model-routing-candidates.js";
8
9
  import { eligiblePair, parseModelConstraints, } from "./model-routing-constraints.js";
@@ -74,13 +75,25 @@ export async function routeExecutionModel(input) {
74
75
  const statedNeeds = parseTaskNeeds(input.taskNeeds);
75
76
  // settings.json provider lists are defaults that only the call itself may
76
77
  // replace; every constraint's own provider lists are enforced by eligiblePair.
77
- const { allowedProviders = [], excludedProviders = [] } = input.overrideProviderSettings
78
- ? {}
79
- : (ctx.getModelRouting?.() ?? {});
78
+ const routing = ctx.getModelRouting?.() ?? {};
79
+ const { allowedProviders = [], excludedProviders = [] } = input.overrideProviderSettings ? {} : routing;
80
+ // The model lists are the operator's gate and apply to every call (#3528).
81
+ const { allowedModels = [], excludedModels = [] } = routing;
80
82
  const providerPermitted = (provider) => (allowedProviders.length === 0 || allowedProviders.includes(provider)) && !excludedProviders.includes(provider);
81
- const catalog = () => ctx.modelRegistry
82
- .getAvailable()
83
- .filter((model) => isModelType(model, "chat") && providerPermitted(model.provider))
83
+ const matching = (patterns, models) => new Set(resolveModelScopeFromModels([...patterns], models).scopedModels.map(({ model }) => `${model.provider}/${model.id}`));
84
+ const permittedModels = () => {
85
+ const models = ctx.modelRegistry.getAvailable();
86
+ const allowed = allowedModels.length ? matching(allowedModels, models) : undefined;
87
+ const excluded = matching(excludedModels, models);
88
+ return models.filter((model) => {
89
+ const id = `${model.provider}/${model.id}`;
90
+ return (isModelType(model, "chat") &&
91
+ providerPermitted(model.provider) &&
92
+ (allowed === undefined || allowed.has(id)) &&
93
+ !excluded.has(id));
94
+ });
95
+ };
96
+ const catalog = () => permittedModels()
84
97
  // Ultrafast is billed above Standard and gated by account access. It is selectable
85
98
  // manually, but auto must receive an exact caller allowance or restore an explicit decision.
86
99
  .filter((model) => model.fastRoute?.serviceTier !== "ultrafast" ||
@@ -95,10 +108,20 @@ export async function routeExecutionModel(input) {
95
108
  .filter((entry) => entry.pairs.length > 0);
96
109
  const available = catalog();
97
110
  const pairs = available.flatMap((entry) => entry.pairs);
98
- if (!pairs.length)
99
- throw new Error(allowedProviders.length || excludedProviders.length
100
- ? "Auto routing has no eligible model/effort pairs. Check configured providers, modelConstraints, and the modelRouting allowedProviders/excludedProviders settings."
111
+ if (!pairs.length) {
112
+ const providerSettings = allowedProviders.length > 0 || excludedProviders.length > 0;
113
+ const gateSettings = [
114
+ ...(providerSettings ? ["modelRouting allowedProviders/excludedProviders"] : []),
115
+ ...(allowedModels.length ? ["modelRouting.allowedModels"] : []),
116
+ ...(excludedModels.length ? ["modelRouting.excludedModels"] : []),
117
+ ];
118
+ const noun = gateSettings.length === 1 && !providerSettings ? "setting" : "settings";
119
+ throw new Error(gateSettings.length
120
+ ? `Auto routing has no eligible model/effort pairs. Check configured providers, modelConstraints, and the ${new Intl.ListFormat("en", { type: "conjunction" }).format(gateSettings)} ${noun}.`
101
121
  : "Auto routing has no eligible model/effort pairs. Check configured providers and modelConstraints.");
122
+ }
123
+ // The effective post-filter pool, echoed in routerSelection for audit (#3528).
124
+ const candidates = [...new Set(available.map((entry) => `${entry.model.provider}/${entry.model.id}`))];
102
125
  let selection = input.selection;
103
126
  if (selection === undefined) {
104
127
  const settings = { getRouterModel: () => ctx.getRouterModel() };
@@ -129,7 +152,7 @@ export async function routeExecutionModel(input) {
129
152
  const pair = CURRENT_MODEL_EFFORT_PREFERENCE.map((effort) => entry?.pairs.find((candidate) => candidate.effort === effort)).find((candidate) => candidate !== undefined);
130
153
  if (pair === undefined)
131
154
  return undefined;
132
- return routeExecutionModel({ ...input, selection: { model: pair.model, effort: pair.effort } });
155
+ return routeExecutionModel({ ...input, selection: { model: pair.model, effort: pair.effort, candidates } });
133
156
  };
134
157
  // Any failure before a model is chosen is a total inference failure: the
135
158
  // router and its chat structured-output fallback both failed (#3206).
@@ -234,6 +257,7 @@ export async function routeExecutionModel(input) {
234
257
  : seeing;
235
258
  const usable = roomy.length ? roomy : seeing;
236
259
  const pairsFor = new Map(usable.map((entry) => [`${entry.model.provider}/${entry.model.id}`, entry.pairs]));
260
+ const routedCandidates = [...pairsFor.keys()];
237
261
  const toCandidate = (model) => {
238
262
  const routeCost = model.fastRoute?.serviceTier
239
263
  ? getServiceTierCost(model, model.fastRoute.serviceTier)
@@ -256,11 +280,7 @@ export async function routeExecutionModel(input) {
256
280
  // but price and recency still compare them with every model the user could route to.
257
281
  const callerListed = constraints.some((constraint) => (constraint.allowedModels?.length ?? 0) > 0);
258
282
  const reference = callerListed
259
- ? ctx.modelRegistry
260
- .getAvailable()
261
- .filter((model) => isModelType(model, "chat") &&
262
- providerPermitted(model.provider) &&
263
- (!needs.needsImages || model.input.includes("image")))
283
+ ? permittedModels().filter((model) => !needs.needsImages || model.input.includes("image"))
264
284
  : usable.map((entry) => entry.model);
265
285
  const standings = rankCandidates(catalogEvals, reference.map(toCandidate), needs);
266
286
  const ranked = standings.filter((candidate) => pairsFor.has(candidate.model));
@@ -332,6 +352,7 @@ export async function routeExecutionModel(input) {
332
352
  })),
333
353
  }
334
354
  : {}),
355
+ candidates: routedCandidates,
335
356
  };
336
357
  }
337
358
  const fallbacks = selection.fallbacks?.map((pair) => Object.freeze({ model: pair.model, effort: pair.effort }));
@@ -343,6 +364,7 @@ export async function routeExecutionModel(input) {
343
364
  model: selection.model,
344
365
  effort: selection.effort,
345
366
  ...(fallbacks?.length ? { fallbacks: Object.freeze(fallbacks) } : {}),
367
+ ...(selection.candidates ? { candidates: Object.freeze([...selection.candidates]) } : {}),
346
368
  });
347
369
  const hasPair = (pair) => catalog().some((entry) => entry.pairs.some((p) => p.model === pair.model && p.effort === pair.effort));
348
370
  const allowsModel = (model, effort) => {