chainlesschain 0.166.132 → 0.166.133

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,11 +2,25 @@
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.133
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 source tree targets package version **0.166.133**. 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. Over **400 focused regression tests** passed for the model-platform and self-evolution fixes; these controlled tests do not establish live-platform acceptance or public availability.
8
8
 
9
- ## CLI 0.166.131 — current public release (2026-10-29)
9
+ When this version is available from npm, install it with `npm i -g chainlesschain@0.166.133 --registry https://registry.npmjs.org`. The companion version pair is Open VSX **0.37.171** / JetBrains **0.4.188**; the CLI is published before its companion extensions. Kernel stays **0.1.11**. Consult the [current release guide](https://docs.chainlesschain.com/chainlesschain/agent-platform-release.html) for verified channel availability. Source preparation alone does not establish publication or release-gate success.
10
+
11
+ ## CLI 0.166.132 — verified public baseline before this source revision
12
+
13
+ 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`.
14
+
15
+ 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.
16
+
17
+ 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).
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ ## CLI 0.166.131 — previous public release (2026-10-29)
10
24
 
11
25
  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
26
 
@@ -64,7 +78,7 @@ Ordinary Agent, `ask`, `chat` and `cc ui` use self-evolution only when enabled.
64
78
 
65
79
  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
80
 
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.
81
+ 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
82
 
69
83
  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
84
 
@@ -239,7 +253,7 @@ Explicit Linux durable permissions require administrator provisioning. Cross-pro
239
253
  Install the CLI version published by this release:
240
254
 
241
255
  ```bash
242
- npm install -g chainlesschain@0.166.121 --registry https://registry.npmjs.org
256
+ npm install -g chainlesschain@0.166.132 --registry https://registry.npmjs.org
243
257
  chainlesschain setup
244
258
  ```
245
259
 
@@ -2412,19 +2426,23 @@ Configuration is stored at `~/.chainlesschain/config.json`. The CLI creates and
2412
2426
 
2413
2427
  ### Supported LLM Providers
2414
2428
 
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 |
2429
+ | Provider | Default Model | API Key Required |
2430
+ | -------------------------- | ---------------------- | ------------------ |
2431
+ | Ollama (Local) | qwen2.5:7b | No |
2432
+ | OpenAI | gpt-4o | Yes |
2433
+ | Anthropic | claude-opus-4-6 | Yes |
2434
+ | DeepSeek | deepseek-chat | Yes |
2435
+ | DashScope (Alibaba) | qwen-turbo | Yes |
2436
+ | Google Gemini | gemini-2.0-flash | Yes |
2437
+ | Mistral AI | mistral-large-latest | Yes |
2438
+ | Volcengine (火山引擎/豆包) | doubao-seed-1-6-251015 | Yes |
2439
+ | Kimi (月之暗面) | moonshot-v1-auto | Yes |
2440
+ | MiniMax (海螺AI) | MiniMax-Text-01 | Yes |
2441
+ | Custom (OpenAI-compatible) | Set explicitly | Platform-dependent |
2442
+
2443
+ 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.
2444
+
2445
+ 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
2446
 
2429
2447
  ## File Structure
2430
2448
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chainlesschain",
3
- "version": "0.166.132",
3
+ "version": "0.166.133",
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": "2eb0c5b1e048509362f9d980f94acee55c2ffa9f"
10
10
  }
@@ -6,5 +6,5 @@
6
6
  "imageDigest": "sha256:f162e55ad3f3f72f8a52e9d7f12b5b8db44506c2ca66a14d7d62d5cd4f12498e",
7
7
  "bytes": 934160,
8
8
  "linkage": "static",
9
- "commit": "772fcdbda85bcf91df95bfced5b476dcf4c74614"
9
+ "commit": "2eb0c5b1e048509362f9d980f94acee55c2ffa9f"
10
10
  }
@@ -1,6 +1,19 @@
1
1
  {
2
2
  "source": "CHANGELOG.md",
3
3
  "releases": [
4
+ {
5
+ "productVersion": null,
6
+ "cliVersion": "0.166.133",
7
+ "cliVersions": ["0.166.133"],
8
+ "date": "2026-10-30",
9
+ "title": "",
10
+ "sections": [
11
+ {
12
+ "heading": "Fixed - cc CLI 0.166.133: scoped mutation schemas and model platform compatibility (candidate, 2026-10-30)",
13
+ "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."
14
+ }
15
+ ]
16
+ },
4
17
  {
5
18
  "productVersion": null,
6
19
  "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
  );
@@ -10,9 +10,13 @@ import { providerFetch } from "./formal-budget-provider-scope.js";
10
10
  * - streamOllama / streamOpenAI — low-level streaming helpers
11
11
  */
12
12
 
13
- import { BUILT_IN_PROVIDERS } from "./llm-providers.js";
13
+ import {
14
+ BUILT_IN_PROVIDERS,
15
+ resolveProviderDefinition,
16
+ } from "./llm-providers.js";
14
17
  import { normalizeOllamaBaseUrl } from "./ollama-base-url.js";
15
18
  import {
19
+ assertCustomProviderConnection,
16
20
  geminiGenerateContentUrl,
17
21
  geminiRequestBody,
18
22
  geminiUsage,
@@ -262,22 +266,22 @@ export async function streamOpenAI(
262
266
  response = await providerFetch(
263
267
  `${normalizeProviderBaseUrl(baseUrl)}/chat/completions`,
264
268
  {
265
- method: "POST",
266
- headers: {
267
- "Content-Type": "application/json",
268
- Authorization: `Bearer ${apiKey}`,
269
- },
270
- body: JSON.stringify({
271
- model,
272
- messages,
273
- stream: true,
274
- // Opt-in token usage in the terminal chunk (OpenAI-compatible).
275
- // Servers that don't understand it simply ignore it.
276
- stream_options: { include_usage: true },
277
- }),
278
- signal: options.signal
279
- ? AbortSignal.any([guard.signal, options.signal])
280
- : guard.signal,
269
+ method: "POST",
270
+ headers: {
271
+ "Content-Type": "application/json",
272
+ ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}),
273
+ },
274
+ body: JSON.stringify({
275
+ model,
276
+ messages,
277
+ stream: true,
278
+ // Opt-in token usage in the terminal chunk (OpenAI-compatible).
279
+ // Servers that don't understand it simply ignore it.
280
+ stream_options: { include_usage: true },
281
+ }),
282
+ signal: options.signal
283
+ ? AbortSignal.any([guard.signal, options.signal])
284
+ : guard.signal,
281
285
  },
282
286
  );
283
287
  } catch (e) {
@@ -410,22 +414,22 @@ export async function streamAnthropic(
410
414
  response = await providerFetch(
411
415
  `${normalizeProviderBaseUrl(baseUrl)}/messages`,
412
416
  {
413
- method: "POST",
414
- headers: {
415
- "Content-Type": "application/json",
416
- "x-api-key": apiKey,
417
- "anthropic-version": "2023-06-01",
418
- },
419
- body: JSON.stringify({
420
- model,
421
- max_tokens: 4096,
422
- stream: true,
423
- ...(system ? { system } : {}),
424
- messages: convo,
425
- }),
426
- signal: options.signal
427
- ? AbortSignal.any([guard.signal, options.signal])
428
- : guard.signal,
417
+ method: "POST",
418
+ headers: {
419
+ "Content-Type": "application/json",
420
+ "x-api-key": apiKey,
421
+ "anthropic-version": "2023-06-01",
422
+ },
423
+ body: JSON.stringify({
424
+ model,
425
+ max_tokens: 4096,
426
+ stream: true,
427
+ ...(system ? { system } : {}),
428
+ messages: convo,
429
+ }),
430
+ signal: options.signal
431
+ ? AbortSignal.any([guard.signal, options.signal])
432
+ : guard.signal,
429
433
  },
430
434
  );
431
435
  } catch (e) {
@@ -666,6 +670,7 @@ export async function streamGemini(
666
670
  * @param {object} options - provider, model, baseUrl, apiKey
667
671
  */
668
672
  export async function* chatStream(messages, options) {
673
+ assertCustomProviderConnection(options.provider, options);
669
674
  const { provider, model, baseUrl, apiKey, sessionId } = options;
670
675
 
671
676
  // Token queue + waiter promise. onToken (called synchronously by each
@@ -765,14 +770,14 @@ export async function* chatStream(messages, options) {
765
770
  options,
766
771
  );
767
772
  } else {
768
- const providerDef = BUILT_IN_PROVIDERS[provider];
773
+ const providerDef = resolveProviderDefinition(provider);
769
774
  const url = normalizeProviderBaseUrl(
770
775
  baseUrl || providerDef?.baseUrl || "https://api.openai.com/v1",
771
776
  );
772
777
  const key =
773
778
  apiKey ||
774
779
  (providerDef?.apiKeyEnv ? process.env[providerDef.apiKeyEnv] : null);
775
- if (!key) {
780
+ if (!key && provider !== "custom") {
776
781
  throw new Error(
777
782
  `API key required for ${provider} (set ${providerDef?.apiKeyEnv || "API key"})`,
778
783
  );
@@ -221,7 +221,6 @@ function openAIReasoningItem(value) {
221
221
  if (Object.hasOwn(value, "summary")) {
222
222
  if (
223
223
  !Array.isArray(value.summary) ||
224
- value.summary.length < 1 ||
225
224
  value.summary.length > 16 ||
226
225
  value.summary.some(
227
226
  (part) =>
@@ -264,7 +264,10 @@ export function createGovernedHubLlm(llm, factory, options = {}, sdkPorts) {
264
264
  "A Hub model client with declared locality is required",
265
265
  );
266
266
  }
267
- if (typeof factory !== "function")
267
+ if (
268
+ typeof factory !== "function" &&
269
+ !(factory === null && options.evolutionDisabled === true)
270
+ )
268
271
  throw new TypeError("A Hub evolution factory is required");
269
272
  if (options.onFailure != null && typeof options.onFailure !== "function")
270
273
  throw new TypeError("onFailure must be a function");
@@ -298,6 +301,7 @@ export function createGovernedHubLlm(llm, factory, options = {}, sdkPorts) {
298
301
  mode: "hub-analysis",
299
302
  messages,
300
303
  signal: chatOptions.signal,
304
+ evolutionDisabled: options.evolutionDisabled === true,
301
305
  });
302
306
  },
303
307
  async transport(messages, chatOptions = {}) {