chainlesschain 0.166.132 → 0.166.135

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 (32) hide show
  1. package/README.md +44 -18
  2. package/package.json +1 -1
  3. package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
  4. package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
  5. package/src/data/changelog.json +26 -0
  6. package/src/gateways/ws/llm-chat-protocol.js +1 -1
  7. package/src/gateways/ws/personal-data-hub-protocol.js +27 -11
  8. package/src/gateways/ws/session-core-protocol.js +22 -17
  9. package/src/gateways/ws/session-protocol.js +19 -8
  10. package/src/gateways/ws/ws-agent-handler.js +30 -0
  11. package/src/lib/background-launch-profile.js +37 -1
  12. package/src/lib/chat-core.js +40 -35
  13. package/src/lib/coding-task-guidance.js +10 -9
  14. package/src/lib/context-memory-kernel/message-adapter.js +5 -1
  15. package/src/lib/evolution/agent-model-projection.js +0 -1
  16. package/src/lib/evolution/governed-hub-llm.js +5 -1
  17. package/src/lib/evolution/governed-hub-resolver.js +15 -6
  18. package/src/lib/evolution/governed-hub-skill.js +2 -0
  19. package/src/lib/evolution/optional-ui-model-turn.js +13 -2
  20. package/src/lib/llm-config-defaults.js +17 -5
  21. package/src/lib/llm-connection-config.js +10 -2
  22. package/src/lib/llm-connection-probe.js +5 -2
  23. package/src/lib/llm-connection-resolution.js +7 -4
  24. package/src/lib/llm-providers.js +24 -11
  25. package/src/lib/openai-responses.js +2 -1
  26. package/src/lib/personal-data-hub-wiring.js +69 -36
  27. package/src/lib/provider-protocol.js +35 -3
  28. package/src/lib/provider-stream.js +6 -4
  29. package/src/lib/runnable-provider.js +4 -0
  30. package/src/lib/ws-chat-handler.js +22 -9
  31. package/src/runtime/agent-core.js +182 -34
  32. package/src/runtime/headless-runner.js +3 -3
package/README.md CHANGED
@@ -2,11 +2,33 @@
2
2
 
3
3
  Command-line interface for installing, configuring, and managing [ChainlessChain](https://www.chainlesschain.com), including the coding agent, governed tools, sessions, memory, and IDE integrations.
4
4
 
5
- ## Source candidate 0.166.132 (unreleased, 2026-10-30)
5
+ ## CLI 0.166.135
6
6
 
7
- CLI **0.166.132** keeps Kernel **0.1.11** and pairs with Open VSX **0.37.170** / JetBrains **0.4.187** source candidates. The candidate CLI stops tools from running on a truncated response and can replan once when complete usage has been recorded and budget remains. Recovery keeps the original task available even in a small context window. Filtered responses, explicitly incomplete terminal responses, and missing task context now stop with a clear failure. Resuming a session preserves task identity when the built-in adapter combines message roles; custom callbacks and unverified metadata cannot claim that identity. After asynchronous recall, the CLI rechecks the task and its position before continuing. The readContext continuation now moves the same complete user message by its exact index, preserving metadata and avoiding duplicate task identity. The original four independent-process restart cases passed locally, and 16 host-continuation tests passed; the latest full combined validation passed 456 tests across 18 files and 10 real CLI tests across 3 files, with no skips (5 output-limit, 1 resume and 4 compaction-memory-restart). Source 7f8ef64b4bd8b429f97d2e121c440143ed7ddf3b was superseded before publication after CLI CI found four failures in each Ubuntu and Windows E2E shard 4 job (147 passed each). Its 452+6 local preparation and successful Strict Sandbox (6/6) and Kernel (3/3) checks remain historical; remaining CLI and IDE jobs were canceled, and no release tag was created. The earlier 417+5 result also remains historical. A new exact-source release matrix is required. This candidate is not published, and the original broker evaluation remains unresolved. Require complete exact-source Linux/Windows/macOS CLI CI and Strict Sandbox, OIDC publication and public readback before releasing the paired IDEs. The verified public versions and installation below remain **0.166.131 / 0.37.169 / 0.4.186**; PC/mobile remain independent **v5.0.3.143**.
7
+ This release recovers genuine OpenAI Responses JSON disconnects before accepted output, bounds stalled requests, and settles completed streaming responses without waiting for socket EOF. It preserves empty tool allowlists across resume and rejects malformed saved permission, sandbox and budget settings. HTTP/provider failures, cancellation, usage persistence and incomplete tool-call replay protections remain authoritative.
8
8
 
9
- ## CLI 0.166.131 — current public release (2026-10-29)
9
+ Install with `npm i -g chainlesschain@0.166.135 --registry https://registry.npmjs.org`, restart the chat host, then run `cc --version` and `cc doctor`. Paired IDE versions are Open VSX **0.37.172** and JetBrains **0.4.189**, recommending CLI **0.166.135**; Kernel stays **0.1.11**. Check the [release guide](https://docs.chainlesschain.com/chainlesschain/agent-platform-release.html) for channel publication status. Microsoft Marketplace and PC/mobile releases are outside this release scope.
10
+
11
+ The preceding OpenAI native cohort accepted **39/45** tasks. Runtime regression tests and this release do not change that historical result or establish formal paid/native production admission.
12
+
13
+ ## CLI 0.166.133 — verified preceding release
14
+
15
+ CLI **0.166.133** is published on npm. It preserves required empty OpenAI Responses reasoning summaries, applies the self-evolution switch to all model-owning `cc ui` entry points, and supports explicitly configured OpenAI-compatible local/LAN platforms through `custom`, with optional credentials and unchanged endpoint/model aliases. It also exposes canonical allowed mutation paths in provider tool schemas while retaining execution-time scope and file-identity checks. Controlled regressions and release gates do not establish live-platform task acceptance.
16
+
17
+ Install with `npm i -g chainlesschain@0.166.133 --registry https://registry.npmjs.org`, restart the chat host, then run `cc --version` and `cc doctor`. Companion Open VSX **0.37.171** / JetBrains **0.4.188** are public and recommend this CLI; their original payloads match the official artifacts. Kernel stays **0.1.11**. Exact source `2eb0c5b1e048509362f9d980f94acee55c2ffa9f` passed Linux, Windows and macOS CLI CI / CLI Strict Sandbox. [OIDC publication](https://github.com/chainlesschain/chainlesschain/actions/runs/41073612529) and [independent readback](https://github.com/chainlesschain/chainlesschain/actions/runs/41075409835) verified immutable package bytes and signed provenance. Consult the [current release guide](https://docs.chainlesschain.com/chainlesschain/agent-platform-release.html) for IDE channel availability. Microsoft Marketplace and PC/mobile releases are outside this release scope.
18
+
19
+ ## CLI 0.166.132 — verified public baseline before this source revision
20
+
21
+ Install: `npm i -g chainlesschain@0.166.132 --registry https://registry.npmjs.org`. Public Open VSX **0.37.170** and approved JetBrains **0.4.187** recommend this CLI; Kernel **0.1.11** is verified upstream. Restart the chat host, then run `cc --version` and `cc doctor`.
22
+
23
+ This release stops tool execution on truncated responses and permits one replan when complete usage is recorded and budget remains. Recovery preserves the original task, including through role combination, asynchronous recall and readContext continuation. Filtered or incomplete terminal responses and missing task context stop with a clear failure. No independent IDE UI change is included.
24
+
25
+ Release source: [772fcdbda85bcf91df95bfced5b476dcf4c74614](https://github.com/chainlesschain/chainlesschain/commit/772fcdbda85bcf91df95bfced5b476dcf4c74614). Exact-source [validation gates](https://github.com/chainlesschain/chainlesschain/blob/0d57ebfea919ae941a36a1f11a9ee10964722120/docs/research/cli/evidence/release132-public/gates.json), [OIDC publication run](https://github.com/chainlesschain/chainlesschain/actions/runs/40985169005), [official artifact](https://github.com/chainlesschain/chainlesschain/actions/runs/40985169005/artifacts/12946249806), [independent readback run](https://github.com/chainlesschain/chainlesschain/actions/runs/40986625203), and [public CLI readback](https://github.com/chainlesschain/chainlesschain/blob/0d57ebfea919ae941a36a1f11a9ee10964722120/docs/research/cli/evidence/release132-public/cli.json) identify this public package. [Three-package publication evidence](https://github.com/chainlesschain/chainlesschain/blob/0d57ebfea919ae941a36a1f11a9ee10964722120/docs/research/cli/evidence/release132-public/publication.json).
26
+
27
+ At historical source `abbe13ad3658ff9e2e100989a4cfefc409d9261c`, the combined local suite actually ran **456 tests across 18 files + 10 real CLI tests across 3 files**, with no skips. Subsequent fixture and native-preflight repairs compared the selected runtime components byte for byte; release source `772fcdbda85bcf91df95bfced5b476dcf4c74614` did **not** repeat that combined local suite. [Component scope evidence](https://github.com/chainlesschain/chainlesschain/blob/0d57ebfea919ae941a36a1f11a9ee10964722120/docs/research/cli/evidence/release132-native-preflight-2026-10-30/review.json) and the release source's [cloud gates](https://github.com/chainlesschain/chainlesschain/blob/0d57ebfea919ae941a36a1f11a9ee10964722120/docs/research/cli/evidence/release132-public/gates.json) retain this distinction. Failed source `40753f179a3dc18760bd64a754b28a46ba718f57` and the earlier superseded `7f8ef64b4bd8b429f97d2e121c440143ed7ddf3b` remain historical; their successful checks do not transfer to this release.
28
+
29
+ Automatic acceptance remains **34/45**, with **11** unfinished; **3 assisted deliveries** are recorded separately and are not added to that total. The original broker **403** and Memory cold SLO remain unresolved. Publication adds no task acceptance or paid/native admission: **NOT_PAID_READY / NOT_ADMITTED / HOLD** remain. Microsoft Marketplace remains deferred. PC/mobile **v5.0.3.143** remain independent.
30
+
31
+ ## CLI 0.166.131 — previous public release (2026-10-29)
10
32
 
11
33
  Install: `npm i -g chainlesschain@0.166.131 --registry https://registry.npmjs.org`. Kernel **0.1.11** was published first. Restart the chat host, then run `cc --version` and `cc doctor`. Public Open VSX **0.37.169** / JetBrains **0.4.186** both recommend CLI **0.166.131**; their downloaded packages match the official CI payloads. Microsoft Marketplace is deferred.
12
34
 
@@ -64,7 +86,7 @@ Ordinary Agent, `ask`, `chat` and `cc ui` use self-evolution only when enabled.
64
86
 
65
87
  Run `cc config set evolution.enabled true` to opt in, or `cc config set evolution.enabled false` to switch off. `cc evolution deployment enable` initializes the local signed runtime when needed; `cc evolution deployment disable` turns it off and retains deployment data. `cc evolution deployment status --json` reports the effective state. An explicit `false` also overrides inherited deployment environment variables; explicitly supplied external/managed deployments remain opt-ins when this setting is absent.
66
88
 
67
- The **Skill 自进化配置** page in `cc ui`, VS Code and JetBrains shares this setting. Quick Ask reads it for each new request; stop and restart an already running Agent session to apply changes. Switching off leaves ordinary chat, tool permissions and sandbox policy available. Enabled deployments still enforce their signatures and evidence validation; automatic candidate promotion remains `HOLD`. RRSI evaluation modules are part of the CLI evolution code, but this switch does not itself start a full RRSI campaign or publish learned candidates.
89
+ The **Skill 自进化配置** page in `cc ui`, VS Code and JetBrains shares this setting. Since CLI **0.166.133**, `cc ui` Chat, Agent, Quick Ask, streaming and Personal Data Hub model requests re-read it for each new request, including existing UI sessions. Stop and restart already running terminal or IDE Agent sessions to apply changes there. Switching off leaves ordinary chat, tool permissions and sandbox policy available. Enabled deployments still enforce their signatures and evidence validation; automatic candidate promotion remains `HOLD`. RRSI evaluation modules are part of the CLI evolution code, but this switch does not itself start a full RRSI campaign or publish learned candidates.
68
90
 
69
91
  Install this release with `npm i -g chainlesschain@0.166.127 --registry https://registry.npmjs.org`. Paired public IDE versions are Open VSX / VS Code **0.37.165** and JetBrains **0.4.182**. Their exact-source release gates and public archive readback passed; Microsoft Marketplace remains unpublished. Child package pins are unchanged. The preceding **0.166.126** JSON recovery-summary repair addresses host-truncated retained context; the same error code can also report other invalid original evidence. Exact-source Linux, Windows and macOS CLI CI and Strict Sandbox passed; GitHub Actions OIDC publication and public package readback are complete. Mobile and PC release [`v5.0.3.142`](https://github.com/chainlesschain/chainlesschain/releases/tag/v5.0.3.142) is public from frozen source `c9881a2b710cf49f0ad3d63673e0998ecc608514`; automatic promotion remains `HOLD`.
70
92
 
@@ -239,7 +261,7 @@ Explicit Linux durable permissions require administrator provisioning. Cross-pro
239
261
  Install the CLI version published by this release:
240
262
 
241
263
  ```bash
242
- npm install -g chainlesschain@0.166.121 --registry https://registry.npmjs.org
264
+ npm install -g chainlesschain@0.166.132 --registry https://registry.npmjs.org
243
265
  chainlesschain setup
244
266
  ```
245
267
 
@@ -2412,19 +2434,23 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
2412
2434
 
2413
2435
  ### Supported LLM Providers
2414
2436
 
2415
- | Provider | Default Model | API Key Required |
2416
- | -------------------------- | ---------------------- | ---------------- |
2417
- | Ollama (Local) | qwen2.5:7b | No |
2418
- | OpenAI | gpt-4o | Yes |
2419
- | Anthropic | claude-opus-4-6 | Yes |
2420
- | DeepSeek | deepseek-chat | Yes |
2421
- | DashScope (Alibaba) | qwen-turbo | Yes |
2422
- | Google Gemini | gemini-2.0-flash | Yes |
2423
- | Mistral AI | mistral-large-latest | Yes |
2424
- | Volcengine (火山引擎/豆包) | doubao-seed-1-6-251015 | Yes |
2425
- | Kimi (月之暗面) | moonshot-v1-auto | Yes |
2426
- | MiniMax (海螺AI) | MiniMax-Text-01 | Yes |
2427
- | Custom | — | Yes |
2437
+ | Provider | Default Model | API Key Required |
2438
+ | -------------------------- | ---------------------- | ------------------ |
2439
+ | Ollama (Local) | qwen2.5:7b | No |
2440
+ | OpenAI | gpt-4o | Yes |
2441
+ | Anthropic | claude-opus-4-6 | Yes |
2442
+ | DeepSeek | deepseek-chat | Yes |
2443
+ | DashScope (Alibaba) | qwen-turbo | Yes |
2444
+ | Google Gemini | gemini-2.0-flash | Yes |
2445
+ | Mistral AI | mistral-large-latest | Yes |
2446
+ | Volcengine (火山引擎/豆包) | doubao-seed-1-6-251015 | Yes |
2447
+ | Kimi (月之暗面) | moonshot-v1-auto | Yes |
2448
+ | MiniMax (海螺AI) | MiniMax-Text-01 | Yes |
2449
+ | Custom (OpenAI-compatible) | Set explicitly | Platform-dependent |
2450
+
2451
+ Use `provider: "custom"` for local or LAN model platforms exposing OpenAI-compatible Chat Completions, including LM Studio, vLLM, Xinference, LocalAI, and model-routing gateways. Set the platform's API base URL (for example `http://localhost:1234/v1` or a LAN gateway's documented base path) and exact model name. Supply its API key when required; keyless endpoints are also supported. Chat, Agent, streaming, and connection tests keep that endpoint and model instead of selecting Ollama or a cloud provider.
2452
+
2453
+ Self-evolution is optional. With `evolution.enabled: false`, `cc ui` Chat, Agent, Quick Ask, streaming, and Personal Data Hub analysis do not require an Evolution deployment certificate. Existing sessions read the setting again on subsequent requests. Enabled deployments still require valid authentication.
2428
2454
 
2429
2455
  ## File Structure
2430
2456
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.166.132",
3
+ "version": "0.166.135",
4
4
  "description": "CLI for ChainlessChain - install, configure, and manage your personal AI management system",
5
5
  "type": "module",
6
6
  "bin": {
@@ -6,5 +6,5 @@
6
6
  "imageDigest": "sha256:6395a47a5bd9b038303e94a5e93754258c8480dab64635460f973eb93dc6f4d8",
7
7
  "bytes": 778808,
8
8
  "linkage": "static",
9
- "commit": "772fcdbda85bcf91df95bfced5b476dcf4c74614"
9
+ "commit": "82aa6d703a8695721a7ccf0793a435853bc2bbf2"
10
10
  }
@@ -6,5 +6,5 @@
6
6
  "imageDigest": "sha256:f162e55ad3f3f72f8a52e9d7f12b5b8db44506c2ca66a14d7d62d5cd4f12498e",
7
7
  "bytes": 934160,
8
8
  "linkage": "static",
9
- "commit": "772fcdbda85bcf91df95bfced5b476dcf4c74614"
9
+ "commit": "82aa6d703a8695721a7ccf0793a435853bc2bbf2"
10
10
  }
@@ -1,6 +1,32 @@
1
1
  {
2
2
  "source": "CHANGELOG.md",
3
3
  "releases": [
4
+ {
5
+ "productVersion": null,
6
+ "cliVersion": "0.166.135",
7
+ "cliVersions": ["0.166.135"],
8
+ "date": "2026-10-31",
9
+ "title": "",
10
+ "sections": [
11
+ {
12
+ "heading": "Fixed - cc CLI 0.166.135: Responses disconnect recovery and launch policy preservation (candidate, 2026-10-31)",
13
+ "body": "- Recover genuine pre-output OpenAI Responses JSON disconnects with the existing bounded retry policy, distinct physical request IDs and synchronous usage settlement. Bound stalled headers and JSON reads, preserve HTTP/provider verdicts and caller cancellation, and keep workflow effects and incomplete tool streams non-replayable.\n- Settle completed Responses events without waiting for socket EOF. A hung reader cancellation no longer blocks timeout settlement; already queued transport aborts and user cancellation remain authoritative.\n- Select newly generated context handoffs before ordinary conversation so a full conversation partition cannot evict its summary. Retain the same input budget, data-only trust and oversized-summary rejection.\n- Preserve an explicit empty tool allowlist through saved launch argv and the CLI parser. Reject malformed persisted permission modes, sandbox modes and budgets instead of silently restoring weaker defaults.\n- Strengthen generic regression guidance for delegated lifecycle ownership, race controls, persisted history projections and a final response within the iteration budget. Add same-handle metadata-race and approval-cancellation controls without changing historical model candidates.\n- Give independent IDE cleanup a separate bounded deadline after the model task ends. The previous `verify-26` trace actually contains `error_max_turns` at 20/20; cleanup had obscured that result with an expired-deadline error. Historical OpenAI acceptance remains 39/45 and is not reclassified by these fixes.\n- Prepare companion Open VSX 0.37.172 and JetBrains 0.4.189 for CLI 0.166.135. Required child npm packages retain their published versions; publication must follow the exact-source multi-platform gates and upstream readback."
14
+ }
15
+ ]
16
+ },
17
+ {
18
+ "productVersion": null,
19
+ "cliVersion": "0.166.133",
20
+ "cliVersions": ["0.166.133"],
21
+ "date": "2026-10-30",
22
+ "title": "",
23
+ "sections": [
24
+ {
25
+ "heading": "Fixed - cc CLI 0.166.133: scoped mutation schemas and model platform compatibility (candidate, 2026-10-30)",
26
+ "body": "- Expose canonical allowed mutation paths in provider tool schemas for scoped write/edit calls. Explain that absolute paths returned by read tools are not valid mutation arguments in that run; execution-time permission, scope and file-identity checks remain authoritative.\n- Preserve the required `summary` array on Responses reasoning items, including empty summaries and replayed older sessions, so tool continuations do not fail with `Missing required parameter: input[n].summary`.\n- Apply the current self-evolution setting to ordinary `cc ui` Chat, Agent, streaming and Personal Data Hub analysis, skills and resolver requests. Disabled requests do not require a deployment certificate; enabled deployments retain authentication checks. Existing UI sessions re-read the setting for subsequent requests.\n- Support explicitly configured `custom` OpenAI-compatible local and LAN platforms across Chat, Agent, streaming and connection probes. Keep the platform's API base path and model alias, allow platform-specific or absent credentials, and do not relabel localhost endpoints as Ollama or fall back to an unrelated cloud account.\n- Validate the model-platform and self-evolution fixes with over 400 focused regression tests, including the actual Chat-session creation path and disabled resolver model/embedding transports. Platform compatibility uses controlled test endpoints; the reported user's live platform was not contacted.\n- Validate the generic schema contract: the new provider-wire suite passed **10/10 tests**; the combined related suites passed **34 tests**, with **1 existing Windows skip**. Local test results do not establish new real-model task success or change the historical **34/45** automatic acceptance total.\n- Prepare candidate pairing Open VSX **0.37.171** / JetBrains **0.4.188** for CLI **0.166.133**. Kernel **0.1.11** and unchanged child packages retain their versions; no independent IDE UI or client release is included.\n- Release requirements: verified public child packages, exact-source multi-OS release gates, CLI OIDC publication and public readback before IDE publication and readback. Consult versioned publication evidence for actual availability. Microsoft Marketplace remains deferred; **0.166.132 / 0.37.170 / 0.4.187** is the preceding verified public pairing."
27
+ }
28
+ ]
29
+ },
4
30
  {
5
31
  "productVersion": null,
6
32
  "cliVersion": "0.166.132",
@@ -121,7 +121,7 @@ export async function handleLlmChat(server, id, ws, message) {
121
121
  return;
122
122
  }
123
123
 
124
- if (creds.provider !== "ollama" && !creds.apiKey) {
124
+ if (!["ollama", "custom"].includes(creds.provider) && !creds.apiKey) {
125
125
  const envHint = BUILT_IN_PROVIDERS[creds.provider]?.apiKeyEnv;
126
126
  send({
127
127
  type: `${topic}.result`,
@@ -25,6 +25,7 @@ import {
25
25
  import { importPdh } from "../../lib/pdh-load-error.js";
26
26
  import { getAIChatWizard } from "../../lib/personal-data-hub-aichat-wizard.js";
27
27
  import { runDedicatedBatchCollectors } from "../../lib/pdh-dedicated-batch-collectors.js";
28
+ import { resolveServerEvolutionFactory } from "../../lib/evolution/optional-ui-model-turn.js";
28
29
  import {
29
30
  existsSync,
30
31
  unlinkSync,
@@ -150,8 +151,9 @@ export async function _tryAdbAutoPullInputPath(hub, name, options) {
150
151
  return options || {};
151
152
  }
152
153
  try {
153
- const { createHostAdbBridge } =
154
- await import("../../lib/host-adb-bridge.js");
154
+ const { createHostAdbBridge } = await import(
155
+ "../../lib/host-adb-bridge.js"
156
+ );
155
157
  const bridge = createHostAdbBridge();
156
158
  const content = await bridge.invoke("snapshot.read", {
157
159
  fileName: `${name}.json`,
@@ -192,9 +194,15 @@ async function withHub(fn, loadHub = getHub) {
192
194
  }
193
195
  }
194
196
 
195
- function governedHubLoader(factory) {
197
+ async function governedHubLoader(server) {
198
+ const factory = await resolveServerEvolutionFactory(server);
196
199
  if (typeof factory === "function")
197
200
  return () => getGovernedAnalysisHub(factory);
201
+ if (
202
+ factory === null &&
203
+ typeof server?.resolveEvolutionCompositionFactory === "function"
204
+ )
205
+ return () => getGovernedAnalysisHub(null, { evolutionDisabled: true });
198
206
  const error = new Error(
199
207
  "Personal Data Hub model egress requires an authenticated evolution composition factory",
200
208
  );
@@ -215,11 +223,13 @@ function validateEntityReference(entityType, entityId) {
215
223
 
216
224
  export const PERSONAL_DATA_HUB_HANDLERS = {
217
225
  "personal-data-hub.ask": async (msg, context = {}) => {
218
- const factory = context.server?.evolutionCompositionFactory;
219
- return withHub(async (hub) => {
220
- if (!hub.engine) throw new Error("Analysis engine unavailable");
221
- return await hub.engine.ask(msg.question, msg.options || {});
222
- }, governedHubLoader(factory));
226
+ return withHub(
227
+ async (hub) => {
228
+ if (!hub.engine) throw new Error("Analysis engine unavailable");
229
+ return await hub.engine.ask(msg.question, msg.options || {});
230
+ },
231
+ await governedHubLoader(context.server),
232
+ );
223
233
  },
224
234
 
225
235
  // Path Y: prompt context only, no LLM call. Lets web-shell / mobile host
@@ -668,7 +678,14 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
668
678
  "personal-data-hub.resolver-drain": async (msg, context = {}) =>
669
679
  withHub(async (hub) => {
670
680
  if (!hub.entityResolver) throw new Error("EntityResolver not wired");
671
- const factory = context.server?.evolutionCompositionFactory;
681
+ const factory = await resolveServerEvolutionFactory(context.server);
682
+ if (
683
+ factory === null &&
684
+ typeof context.server?.resolveEvolutionCompositionFactory === "function"
685
+ )
686
+ return await hub.drainResolver({ limit: msg.limit || 50 }, null, {
687
+ evolutionDisabled: true,
688
+ });
672
689
  return await hub.drainResolver({ limit: msg.limit || 50 }, factory);
673
690
  }),
674
691
 
@@ -691,10 +708,9 @@ export const PERSONAL_DATA_HUB_HANDLERS = {
691
708
  withHub((hub) => hub.analysisSkillNames || []),
692
709
 
693
710
  "personal-data-hub.run-skill": async (msg, context = {}) => {
694
- const factory = context.server?.evolutionCompositionFactory;
695
711
  return withHub(
696
712
  async (hub) => await hub.runSkill(msg.name, msg.options || {}),
697
- governedHubLoader(factory),
713
+ await governedHubLoader(context.server),
698
714
  );
699
715
  },
700
716
 
@@ -19,12 +19,14 @@ function fail(code, message) {
19
19
  }
20
20
 
21
21
  async function canonicalMemoryService(scopeKey) {
22
- const { resolveCliContextMemoryCutover } =
23
- await import("../../lib/context-memory-kernel/authority.js");
22
+ const { resolveCliContextMemoryCutover } = await import(
23
+ "../../lib/context-memory-kernel/authority.js"
24
+ );
24
25
  const decision = resolveCliContextMemoryCutover({ scopeKey });
25
26
  if (!decision.canonical) return null;
26
- const { createCliCanonicalMemoryService } =
27
- await import("../../lib/context-memory-kernel/memory-service.js");
27
+ const { createCliCanonicalMemoryService } = await import(
28
+ "../../lib/context-memory-kernel/memory-service.js"
29
+ );
28
30
  return createCliCanonicalMemoryService();
29
31
  }
30
32
 
@@ -104,8 +106,9 @@ async function sessionsEnd(message) {
104
106
  let consolidated = null;
105
107
  if (message.consolidate) {
106
108
  try {
107
- const { consolidateJsonlSession } =
108
- await import("../../lib/session-consolidator.js");
109
+ const { consolidateJsonlSession } = await import(
110
+ "../../lib/session-consolidator.js"
111
+ );
109
112
  consolidated = await consolidateJsonlSession(message.sessionId, {
110
113
  scope: message.scope || "session",
111
114
  scopeId: message.scopeId || null,
@@ -249,8 +252,9 @@ async function memoryDelete(message) {
249
252
  async function memoryConsolidate(message) {
250
253
  if (!message.sessionId) return fail("BAD_REQUEST", "sessionId required");
251
254
  try {
252
- const { consolidateJsonlSession } =
253
- await import("../../lib/session-consolidator.js");
255
+ const { consolidateJsonlSession } = await import(
256
+ "../../lib/session-consolidator.js"
257
+ );
254
258
  const result = await consolidateJsonlSession(message.sessionId, {
255
259
  scope: message.scope || "session",
256
260
  scopeId: message.scopeId || null,
@@ -321,16 +325,17 @@ async function streamRun(message, sender, signal, context) {
321
325
  const { buildProviderSource } = await import("../../lib/provider-stream.js");
322
326
  const { createStreamRouter } = await loadSingletons();
323
327
  const router = createStreamRouter();
324
- const { prepareGovernedModelTurn, governModelTokenSource } =
325
- await import("../../lib/evolution/governed-model-turn.js");
326
- const turn = await prepareGovernedModelTurn(
327
- context?.server?.evolutionCompositionFactory ?? null,
328
- {
329
- mode: "ws-stream",
330
- messages: [{ role: "user", content: message.prompt }],
331
- signal,
332
- },
328
+ const { governModelTokenSource } = await import(
329
+ "../../lib/evolution/governed-model-turn.js"
333
330
  );
331
+ const { prepareServerUiModelTurn } = await import(
332
+ "../../lib/evolution/optional-ui-model-turn.js"
333
+ );
334
+ const turn = await prepareServerUiModelTurn(context?.server, {
335
+ mode: "ws-stream",
336
+ messages: [{ role: "user", content: message.prompt }],
337
+ signal,
338
+ });
334
339
  const source = governModelTokenSource(
335
340
  buildProviderSource(provider, {
336
341
  model: message.model,
@@ -31,6 +31,7 @@ import {
31
31
  } from "../../lib/session-budget-production-root.js";
32
32
  import { mergePricing } from "../../lib/llm-pricing.js";
33
33
  import { ensureCanonicalSessionTranscript } from "../../lib/session-transcript-migration.js";
34
+ import { prepareServerUiModelTurn } from "../../lib/evolution/optional-ui-model-turn.js";
34
35
 
35
36
  const PAYLOAD_DIGEST = /^sha256:[0-9a-f]{64}$/;
36
37
  const RAW_AUTHORITY_HASH = /^[0-9a-f]{64}$/;
@@ -505,8 +506,9 @@ function sameSessionBudgetConfig(left, right) {
505
506
 
506
507
  async function _isPhase5EnvelopesEnabled() {
507
508
  try {
508
- const { getBetaFlags } =
509
- await import("../../lib/session-core-singletons.js");
509
+ const { getBetaFlags } = await import(
510
+ "../../lib/session-core-singletons.js"
511
+ );
510
512
  const flags = await getBetaFlags();
511
513
  return flags.isEnabled(PHASE5_ENVELOPE_FLAG);
512
514
  } catch (_e) {
@@ -532,8 +534,9 @@ async function ensureSessionHandler(
532
534
  throw error;
533
535
  }
534
536
 
535
- const { WebSocketInteractionAdapter } =
536
- await import("../../lib/interaction-adapter.js");
537
+ const { WebSocketInteractionAdapter } = await import(
538
+ "../../lib/interaction-adapter.js"
539
+ );
537
540
  const enablePhase5Envelopes = await _isPhase5EnvelopesEnabled();
538
541
  const stateSnapshot =
539
542
  typeof server.sessionManager.getSessionStateSnapshot === "function"
@@ -605,6 +608,8 @@ async function ensureSessionHandler(
605
608
  session,
606
609
  interaction: session.interaction,
607
610
  evolutionCompositionFactory: server.evolutionCompositionFactory ?? null,
611
+ prepareUiModelTurn: (context) =>
612
+ prepareServerUiModelTurn(server, context),
608
613
  });
609
614
  } else {
610
615
  const { WSAgentHandler } = await import("./ws-agent-handler.js");
@@ -661,6 +666,10 @@ async function ensureSessionHandler(
661
666
  sessionHostLease,
662
667
  sessionBudgetRoot,
663
668
  evolutionCompositionFactory: server.evolutionCompositionFactory,
669
+ resolveEvolutionCompositionFactory:
670
+ typeof server.resolveEvolutionCompositionFactory === "function"
671
+ ? () => server.resolveEvolutionCompositionFactory()
672
+ : null,
664
673
  skillOutcomeIndex: server.skillOutcomeIndex,
665
674
  skillVectorAuthority: server.skillVectorAuthority,
666
675
  ...server.runtimeAdmissionDependencies,
@@ -1733,8 +1742,9 @@ export async function handleSubAgentList(server, id, ws, message) {
1733
1742
  const sessionId = message?.sessionId || null;
1734
1743
 
1735
1744
  try {
1736
- const { SubAgentRegistry } =
1737
- await import("../../lib/sub-agent-registry.js");
1745
+ const { SubAgentRegistry } = await import(
1746
+ "../../lib/sub-agent-registry.js"
1747
+ );
1738
1748
  const registry = SubAgentRegistry.getInstance();
1739
1749
 
1740
1750
  let payload;
@@ -1796,8 +1806,9 @@ export async function handleSubAgentGet(server, id, ws, message) {
1796
1806
  }
1797
1807
 
1798
1808
  try {
1799
- const { SubAgentRegistry } =
1800
- await import("../../lib/sub-agent-registry.js");
1809
+ const { SubAgentRegistry } = await import(
1810
+ "../../lib/sub-agent-registry.js"
1811
+ );
1801
1812
  const snapshot = SubAgentRegistry.getInstance().getById(subAgentId);
1802
1813
 
1803
1814
  if (!snapshot) {
@@ -183,6 +183,7 @@ export class WSAgentHandler {
183
183
  sessionBudgetRoot = null,
184
184
  hostResourceBudget = null,
185
185
  evolutionCompositionFactory = null,
186
+ resolveEvolutionCompositionFactory = null,
186
187
  skillOutcomeIndex = null,
187
188
  skillVectorAuthority = null,
188
189
  skillRuntimeAdmission = null,
@@ -265,6 +266,15 @@ export class WSAgentHandler {
265
266
  throw new TypeError("evolutionCompositionFactory must be a function");
266
267
  }
267
268
  this._evolutionCompositionFactory = evolutionCompositionFactory;
269
+ if (
270
+ resolveEvolutionCompositionFactory !== null &&
271
+ typeof resolveEvolutionCompositionFactory !== "function"
272
+ )
273
+ throw new TypeError(
274
+ "resolveEvolutionCompositionFactory must be a function",
275
+ );
276
+ this._resolveEvolutionCompositionFactory =
277
+ resolveEvolutionCompositionFactory;
268
278
  this._skillOutcomeIndex =
269
279
  skillOutcomeIndex === null
270
280
  ? null
@@ -337,6 +347,14 @@ export class WSAgentHandler {
337
347
  : Object.freeze(hostSystemPrefix);
338
348
  }
339
349
 
350
+ async _refreshEvolutionFactory() {
351
+ if (this._resolveEvolutionCompositionFactory === null) return;
352
+ const current = await this._resolveEvolutionCompositionFactory();
353
+ if (current !== null && typeof current !== "function")
354
+ throw new TypeError("Invalid evolution composition factory");
355
+ this._evolutionCompositionFactory = current;
356
+ }
357
+
340
358
  async _prepareEvolutionTurn(requestId) {
341
359
  if (!this._evolutionCompositionFactory) return null;
342
360
  const sessionId = String(this.session.id);
@@ -876,6 +894,7 @@ export class WSAgentHandler {
876
894
  let result;
877
895
  try {
878
896
  this._sessionHostLease?.assert?.();
897
+
879
898
  result = await this._compactContextMemory(
880
899
  session.messages,
881
900
  {
@@ -1402,6 +1421,11 @@ export class WSAgentHandler {
1402
1421
  const { session } = this;
1403
1422
  this.assertSessionBudgetAdmission("WebSocket turn");
1404
1423
  this._sessionHostLease?.assert?.();
1424
+ // A resident UI must observe a changed setting before each new turn.
1425
+ if (this._resolveEvolutionCompositionFactory !== null) {
1426
+ await this._refreshEvolutionFactory();
1427
+ }
1428
+ turnSignal.throwIfAborted();
1405
1429
 
1406
1430
  if (session.canonicalJsonlSession === true) {
1407
1431
  const inputDigest =
@@ -1676,6 +1700,7 @@ export class WSAgentHandler {
1676
1700
  }
1677
1701
 
1678
1702
  const executeAgentTurn = async () => {
1703
+ turnSignal.throwIfAborted();
1679
1704
  for await (const event of this._agentLoop(
1680
1705
  session.messages,
1681
1706
  loopOptions,
@@ -2282,6 +2307,11 @@ export class WSAgentHandler {
2282
2307
  try {
2283
2308
  this.assertSessionBudgetAdmission("WebSocket compaction");
2284
2309
  this._sessionHostLease?.assert?.();
2310
+ if (this._resolveEvolutionCompositionFactory !== null) {
2311
+ await this._refreshEvolutionFactory();
2312
+ }
2313
+ this._sessionBudgetSignal?.throwIfAborted();
2314
+ this._sessionHostLease?.assert?.();
2285
2315
  compactionEvolution = await this._prepareEvolutionTurn(
2286
2316
  `compact:${requestId || randomUUID()}`,
2287
2317
  );
@@ -666,6 +666,41 @@ export function normalizeBackgroundLaunchProfile(input) {
666
666
  `Unsupported background launch profile version: ${input?.version ?? "missing"}`,
667
667
  );
668
668
  }
669
+ const rejectField = (field) => {
670
+ const error = new Error(
671
+ `Invalid background launch profile field: ${field}`,
672
+ );
673
+ error.code = "BACKGROUND_LAUNCH_PROFILE_INVALID";
674
+ throw error;
675
+ };
676
+ // A persisted policy must not silently fall back to a weaker default.
677
+ if (
678
+ input.permission?.mode != null &&
679
+ !PERMISSION_MODES.has(input.permission.mode)
680
+ )
681
+ rejectField("permission.mode");
682
+ if (input.sandbox?.mode != null && !SANDBOX_MODES.has(input.sandbox.mode))
683
+ rejectField("sandbox.mode");
684
+ for (const field of [
685
+ "maxTurns",
686
+ "maxCostUsd",
687
+ "thinkingTokens",
688
+ "maxRewakes",
689
+ "maxOuterTurns",
690
+ "goalMaxTokens",
691
+ "goalMaxCostUsd",
692
+ "goalMaxTimeMs",
693
+ ]) {
694
+ const value = input.budget?.[field];
695
+ if (value == null || value === "") continue;
696
+ const number = Number(value);
697
+ if (
698
+ !Number.isFinite(number) ||
699
+ number <= 0 ||
700
+ (!field.toLowerCase().includes("cost") && !Number.isSafeInteger(number))
701
+ )
702
+ rejectField(`budget.${field}`);
703
+ }
669
704
  const cwd = canonicalPath(input.workspace?.cwd || process.cwd());
670
705
  const safeBase = sanitizeBackgroundBaseUrl(input.llm?.baseUrl);
671
706
  const permissionMode = PERMISSION_MODES.has(input.permission?.mode)
@@ -966,7 +1001,8 @@ export function buildArgvFromBackgroundLaunchProfile(profile) {
966
1001
  pushValue(argv, "--fallback-model", model);
967
1002
  }
968
1003
  if (p.tools.allowed !== null) {
969
- pushValue(argv, "--allowed-tools", p.tools.allowed.join(","));
1004
+ // Empty means no tools; omitting this option would allow every tool.
1005
+ argv.push("--allowed-tools", p.tools.allowed.join(","));
970
1006
  }
971
1007
  pushValue(argv, "--disallowed-tools", p.tools.disallowed.join(","));
972
1008
  pushValue(argv, "--permission-mode", p.permission.mode);