Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion LICENSES/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ their upstream originals byte for byte.
revision. It is copied in its original language.

The public source revision declares version 3.14.0. It is distinct from the
Desktop 3.14.1 artifact pinned by this project's runtime lock. These notices
Desktop 3.16.2 artifact pinned by this project's runtime lock. These notices
preserve upstream attribution; they are not a claim that this CLI was built
from that exact commit. The third-party inventory covers multiple upstream
distributions and may list components absent from our npm package.
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -328,13 +328,16 @@ is forwarded to the runtime unchanged.

Workflow creation and saved-workflow startup require the native workflow mode
to be enabled explicitly: start the CLI with `--workflow-mode alwaysOn` or
`--workflow-mode onDemand` (the runtime default is `disabled`). During a
workflow approval, `Change concurrency` and, when supported, `Change default
model` apply only to that workflow. From `/workflows`, `Adjust concurrency`
changes a live run in place without replaying or restarting it. `Save workflow`
writes the run's actual script to `.zcode/workflows`; `/workflows saved` starts
a new run from that definition and validates its arguments through the native
runtime.
`--workflow-mode onDemand` (the runtime default is `disabled`). Tool search is
opt-in per launch: `--toolsearch-mode auto|native|local|off` (default `off`).
`auto` uses the provider's native tool-search protocol when available and falls
back to local discovery, `native` requires an Anthropic Messages provider, and
`local` requires tool-call support. During a workflow approval, `Change
concurrency` and, when supported, `Change default model` apply only to that
workflow. From `/workflows`, `Adjust concurrency` changes a live run in place
without replaying or restarting it. `Save workflow` writes the run's actual
script to `.zcode/workflows`; `/workflows saved` starts a new run from that
definition and validates its arguments through the native runtime.

`/queue` manages local next-turn drafts and pauses automatic sending while its
picker is open. Inputs already accepted by the runtime cannot be edited here.
Expand Down
12 changes: 6 additions & 6 deletions docs/CONFIGURATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ custom MCP servers.
| `oauth` | HTTP / SSE | Optional OAuth configuration; see below |
| `enabled` | All | Set to `false` to disable a server while keeping its configuration |
| `timeoutMs` | All | Optional positive number in milliseconds; use this field instead of `timeout` or `startup_timeout_sec` |
| `protocolVersion` | All | Optional `"auto"`, `"legacy"` or `"2026-07-28"` in runtime 0.16.9; normally leave unset for automatic negotiation |
| `protocolVersion` | All | Optional `"auto"`, `"legacy"` or `"2026-07-28"` in runtime 0.17.0; normally leave unset for automatic negotiation |

For OAuth, `oauth.type` accepts `"authorization_code"` or `"client_credentials"`.
Both accept `clientId`, `clientSecret`, `clientName` and `scope`; client credentials
Expand Down Expand Up @@ -200,10 +200,10 @@ Plugin controls remain under `plugins`, hook declarations under
These paths distinguish the packaged CLI from the upstream application. The
source references below are pinned to upstream
[`29628c9` / `v3.14.3`](https://github.com/zai-org/ZCode/tree/29628c9acdb81b703bbd4080c207a0e7ce5e276e).
This package's runtime lock selects the separate Desktop 3.15.3 artifact; the
This package's runtime lock selects the separate Desktop 3.16.2 artifact; the
public source snapshot is not an assertion of that artifact's build commit.
The configuration boundaries were rechecked on 2026-10-16 against
[`aac4755` / `v3.15.1`](https://github.com/zai-org/ZCode/tree/aac4755666d09fdcd70272fcf063c077a639015f).
The configuration boundaries were rechecked on 2026-10-20 against
[`0a1fc56` / `0.17.0`](https://github.com/zai-org/ZCode/tree/0a1fc56d279fd1001f78387bd81aec1c26767411).
The pinned links below retain the original audit references.

| Client / source | User MCP file | JSON path |
Expand Down Expand Up @@ -452,8 +452,8 @@ inspects live runs, changes concurrency in place, and saves the current script;
`/workflows saved` lists project and global definitions and starts a new run
from a definition rather than replaying old progress.

In runtime 3.15.1, risk confirmations are single-use: the picker offers only
**Allow once** and **Deny**, never a project or session-wide grant. Guarded is a
Risk confirmations are single-use: the picker offers only **Allow once** and
**Deny**, never a project or session-wide grant. Guarded is a
rule-based confirmation policy, not a sandbox or a guarantee that every
potentially destructive command will be recognized. Planning restrictions still
take precedence when Plan is enabled.
Expand Down
10 changes: 5 additions & 5 deletions docs/CONFIGURATION.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ Runtime 每次启动都会读取此文件,修改 MCP 设置后重启 ZCode 即
| `oauth` | HTTP / SSE | 可选,OAuth 配置,见下文 |
| `enabled` | 全部 | 设为 `false` 可禁用服务器并保留配置 |
| `timeoutMs` | 全部 | 可选,正数,单位为毫秒;超时应使用此字段,`timeout` 和 `startup_timeout_sec` 不生效 |
| `protocolVersion` | 全部 | Runtime 0.16.9 支持可选值 `"auto"`、`"legacy"`、`"2026-07-28"`;通常省略,使用自动协商 |
| `protocolVersion` | 全部 | Runtime 0.17.0 支持可选值 `"auto"`、`"legacy"`、`"2026-07-28"`;通常省略,使用自动协商 |

`oauth.type` 支持 `"authorization_code"` 和 `"client_credentials"`。
两者都支持 `clientId`、`clientSecret`、`clientName`、`scope`;client credentials 模式
Expand Down Expand Up @@ -173,10 +173,10 @@ MCP 服务器按名称合并。当前 runtime 中,**同名服务器的用户

下面区分本发行包与上游应用。源码引用固定在上游
[`29628c9` / `v3.14.3`](https://github.com/zai-org/ZCode/tree/29628c9acdb81b703bbd4080c207a0e7ce5e276e)。
本项目 runtime lock 选择的是独立发布的 Desktop 3.15.3 产物,不能据此认定该产物
本项目 runtime lock 选择的是独立发布的 Desktop 3.16.2 产物,不能据此认定该产物
由这个公开源码提交构建。
2026-10-16 已针对
[`aac4755` / `v3.15.1`](https://github.com/zai-org/ZCode/tree/aac4755666d09fdcd70272fcf063c077a639015f)
2026-10-20 已针对
[`0a1fc56` / `0.17.0`](https://github.com/zai-org/ZCode/tree/0a1fc56d279fd1001f78387bd81aec1c26767411)
复核配置边界;下方固定链接保留原始审计出处。

| 客户端/配置源 | 用户级 MCP 文件 | JSON 路径 |
Expand Down Expand Up @@ -383,7 +383,7 @@ CLI 提供四种权限模式:`build`(变更前确认)、`edit`(自动编
`/workflows` 可查看运行中的工作流、原地调整并发或保存当前脚本;`/workflows saved`
列出项目级和全局已保存定义,并从定义启动一个新的 run,而不是回放旧进度。

runtime 3.15.1 的风险确认只允许本次授权:选择器仅提供 **Allow once** 和 **Deny**,
风险确认只允许本次授权:选择器仅提供 **Allow once** 和 **Deny**,
不会提供项目或会话级永久放行。自主模式是基于规则的确认策略,不是沙箱,
也不保证识别所有潜在破坏性指令。Plan 开启时,计划模式的执行限制仍然优先。

Expand Down
9 changes: 5 additions & 4 deletions docs/DEVELOPMENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,9 @@ focus, search, selection, images and overlays. `bun run verify:tui-perf` runs
the complete quality and performance gate, including those smoke checks.
The three runtime patches
`http-no-content`, `bigmodel-oauth` and `cli-help-contract` also remain in the
patch plan: Desktop 3.15.3 already supplies their behavior, while their
verification and legacy branches still support older local Desktop artifacts.
patch plan as required verification points: Desktop 3.16.2 already supplies
their behavior, so they report `already_present` and never rewrite the runtime.
Synchronization still fails if a future upstream build drops the behavior.

## TUI startup compatibility

Expand Down Expand Up @@ -285,8 +286,8 @@ value; an environment override remains authoritative.
layers explicitly:

```text
zcode-app-cli 3.3.6-4
zcode-runtime 0.15.2
zcode-app-cli 3.16.2-37
zcode-runtime 0.17.0
```

## OAuth login override
Expand Down
38 changes: 22 additions & 16 deletions docs/SQLITE_CONCURRENCY.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,21 @@ SQLite and bounded backoff, not by the process-local queue.

The operation allowlist and maintenance SQL were reviewed against upstream
`zai-org/ZCode` commit `29628c9acdb81b703bbd4080c207a0e7ce5e276e`.
On 2026-10-16 the store implementation was rechecked against
`aac4755666d09fdcd70272fcf063c077a639015f` (public 3.15.1); the
`sqlite-session-store.ts` facade is unchanged between those revisions. These
source references do not identify the separate Desktop artifact's build commit.
The required
`sqlite-write-recovery` patch validates both post-migration hooks and the native
cleanup transaction. Missing, ambiguous or partially patched anchors stop
synchronization. The implementation is in `src/runtime-sqlite-recovery.ts`,
with scoped native waits and cleanup admission in its two SQLite helpers;
`vendor/cli-config.cjs` is their existing distribution boundary.

Required regression coverage includes both store-open paths, recovered and
On 2026-10-20 the store implementation was rechecked against
`0a1fc56d279fd1001f78387bd81aec1c26767411` (public 0.17.0 / Desktop 3.16.2).
That revision replaces the synchronous constructor and the separate
`openStartup()` migration with one `SqliteSessionStore.open()` path, adds
`softDeleteSession`, and runs checksum-validated migrations with a backup
directory. These source references do not identify the separate Desktop
artifact's build commit.
The required `sqlite-write-recovery` patch validates the single post-migration
hook and the native cleanup transaction. Missing, ambiguous or partially
patched anchors stop synchronization. The implementation is in
`src/runtime-sqlite-recovery.ts`, with scoped native waits and cleanup admission
in its two SQLite helpers; `vendor/cli-config.cjs` is their existing
distribution boundary.

Required regression coverage includes the single store-open path, recovered and
exhausted locks, responsive timers during backoff, same-process connections,
input-promotion rollback, ordered updates, close during recovery, preserved
non-BUSY errors, and sustained writes from 10–15 independent processes. Cleanup
Expand All @@ -64,9 +67,12 @@ The CLI's shared session database uses SQLite WAL. Readers can overlap a writer,
but different CLI processes still serialize writes to this file. The original
#163 mitigation sets a 10-second connection timeout after initialization. This
remains the default outside the scoped retryable calls described above.
Both the synchronous constructor and asynchronous `openStartup()` path apply this
setting **after** migrations finish. Startup migration lock budgets, short busy
waits, backoff, rollback, and failure cleanup are unchanged.
Desktop 3.16.2 opens the store once through `SqliteSessionStore.open()`; its
migration helper restores a 5-second `busy_timeout` in `finally`, so the patch
re-applies 10 seconds on the same connection before the store is returned.
Startup migration lock budgets, short busy waits, backoff, rollback, and failure
cleanup are unchanged. The runtime creates a migration backup under the CLI data
root and validates `schema_migration` checksums before applying SQL.

This is a bounded contention mitigation, not unlimited concurrency or a retry of
an entire agent turn. A lock held beyond the budget can still fail. The change
Expand Down Expand Up @@ -121,7 +127,7 @@ is not used as a substitute for Node's SQLite driver.

## Follow-up boundary

The current lock selects Desktop 3.15.3 / runtime 0.16.9. Rebuild with
The current lock selects Desktop 3.16.2 / runtime 0.17.0. Rebuild with
`sync:locked` before release validation; `sync:local` records a local App source
and does not establish the locked installer's provenance. The regressions do
not establish that every production workload is free of contention. Keep #162
Expand Down
4 changes: 2 additions & 2 deletions docs/THIRD_PARTY_CONTENT.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ compatibility patches applied.

The [upstream notices shipped here](../LICENSES/README.md) come from a separately
pinned public source revision. That revision declares version 3.14.0; the current
runtime lock selects Desktop 3.14.1. The notices are a conservative upstream
runtime lock selects Desktop 3.16.2. The notices are a conservative upstream
inventory, not proof that the extracted binary was built from that exact commit
or that every listed component is included in this npm package. Review artifact
contents and applicable terms when updating the runtime.
Expand All @@ -41,7 +41,7 @@ patches; the patch report is in `vendor/extraction.json`.

## Plugin declarations

These are the declarations observed in the Desktop 3.14.1 packages, not a
These are the declarations observed in the Desktop 3.16.2 packages, not a
replacement for nested dependency or asset licenses:

| Package directory | Manifest declaration |
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "zcode-app-cli",
"version": "3.15.3-37",
"version": "3.16.2-37",
"description": "Unofficial terminal client for the ZCode agent runtime",
"keywords": [
"agent",
Expand Down
2 changes: 1 addition & 1 deletion scripts/runtime-shared-oauth-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ export function patchRuntimeSharedOAuth(runtime: string): string {

const resolver = namedFunction(runtime, "createCodingPlanApiKeyResolver");
const resolverSource = runtime.slice(resolver.start, resolver.end);
// 3.15.3 sends bare business tokens to metadata endpoints and Bearer tokens
// 3.16.2 sends bare business tokens to metadata endpoints and Bearer tokens
// to project issuance. Both forms must participate in native 401 recovery.
replace(resolver, /new Headers\(([A-Za-z_$][\w$]*)\.headers\)\.get\("Authorization"\)===`Bearer \$\{([A-Za-z_$][\w$]*)\.business\.token\}`/gu,
match => `(new Headers(${match[1]}.headers).get("Authorization")===${match[2]}.business.token||${match[0]})`, "business credential rejection");
Expand Down
69 changes: 48 additions & 21 deletions scripts/runtime-sqlite-patches.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const pragma = `pragma busy_timeout = ${sqliteBusyTimeoutMs}`;
const helper = 'require(require("node:path").join(__dirname,"cli-config.cjs"))';
const identifier = "[A-Za-z_$][\\w$]*";
const boundedIdentifier = "[A-Za-z_$][\\w$]{0,80}";
const storeVariable = "$zStore";

function escape(value: string): string {
return value.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
Expand All @@ -13,36 +14,58 @@ function count(source: string, pattern: RegExp): number {
return [...source.matchAll(pattern)].length;
}

/** Both open paths must retain their steady-state timeout after migration. */
/**
* Desktop 3.16.2 / runtime 0.17.0 has one async store-open path:
*
* let migration = await runSqliteSessionMigrationsAsync(db, dbPath, options);
* return new SqliteSessionStore(db, dbPath, options, migration);
*
* The migration helper restores its 25 ms native wait to 5 s in `finally`.
* Keep a 10 s steady-state wait on the same connection before the store is
* handed to callers.
*/
const openAnchor = new RegExp(
`let (${identifier})=await (${identifier})\\((${identifier}),(${identifier}),(${identifier}\\?\\?\\{lockWaitTimeoutMs:${identifier}\\})\\);return new (${identifier})\\(([^)]*)\\)`,
"u"
);
const openedAnchor = new RegExp(
`let (${identifier})=await (${identifier})\\((${identifier}),(${identifier}),(${identifier}\\?\\?\\{lockWaitTimeoutMs:${identifier}\\})\\);(${identifier})\\.exec\\("${escape(pragma)}"\\);`,
"u"
);
const storeRecovery = new RegExp(
`let \\${storeVariable}=new (${identifier})\\(([^)]*)\\);${escape(helper)}\\.installSqliteWriteRecovery\\(\\${storeVariable}\\);return \\${storeVariable}`,
"u"
);

export function hasRuntimeSqliteBusyTimeout(runtime: string): boolean {
const recovery = escape(`,${helper}.installSqliteWriteRecovery(`);
const sync = new RegExp(`try\\{${identifier}!==${identifier}&&\\(${identifier}\\(this\\.db,this\\.dbPath,${identifier}\\),this\\.db\\.exec\\("${pragma}"\\)(?:${recovery}this\\))?\\)\\}catch`, "gu");
const startup = new RegExp(`try\\{return await ${identifier}\\((${identifier})\\.db,\\1\\.dbPath,${identifier}\\),\\1\\.db\\.exec\\("${pragma}"\\)(?:${recovery}\\1\\))?,\\1\\}catch`, "gu");
return count(runtime, sync) === 1 && count(runtime, startup) === 1;
const opened = openedAnchor.exec(runtime);
if (!opened || opened[6] !== opened[3]) return false;
const after = runtime.slice(opened.index + opened[0].length, opened.index + opened[0].length + 400);
return /^(?:return new [A-Za-z_$][\w$]*\([^)]*\)|let \$zStore=new [A-Za-z_$][\w$]*\([^)]*\);)/u.test(after)
&& runtime.split(`.exec("${pragma}")`).length === 2;
}

export function patchRuntimeSqliteBusyTimeout(runtime: string): string {
if (hasRuntimeSqliteBusyTimeout(runtime)) return runtime;
const sync = /try\{([A-Za-z_$][\w$]*)!==([A-Za-z_$][\w$]*)&&([A-Za-z_$][\w$]*)\(this\.db,this\.dbPath,([A-Za-z_$][\w$]*)\)\}catch/gu;
const startup = /try\{return await ([A-Za-z_$][\w$]*)\(([A-Za-z_$][\w$]*)\.db,\2\.dbPath,([A-Za-z_$][\w$]*)\),\2\}catch/gu;
if (count(runtime, sync) !== 1 || count(runtime, startup) !== 1) {
throw new Error("ZCode runtime is incompatible with the SQLite busy-timeout patch (store migration anchors missing or ambiguous).");
const anchor = openAnchor.exec(runtime);
if (!anchor || count(runtime, new RegExp(openAnchor.source, "gu")) !== 1) {
throw new Error("ZCode runtime is incompatible with the SQLite busy-timeout patch (store open anchor missing or ambiguous).");
}
const patched = runtime.replace(sync, (_match, mode, deferred, migrate, timeout) =>
`try{${mode}!==${deferred}&&(${migrate}(this.db,this.dbPath,${timeout}),this.db.exec("${pragma}"))}catch`
).replace(startup, (_match, migrate, store, options) =>
`try{return await ${migrate}(${store}.db,${store}.dbPath,${options}),${store}.db.exec("${pragma}"),${store}}catch`
const [, migration, migrate, db, dbPath, options, store, args] = anchor;
const patched = runtime.replace(
anchor[0],
`let ${migration}=await ${migrate}(${db},${dbPath},${options});${db}.exec("${pragma}");return new ${store}(${args})`
);
if (!hasRuntimeSqliteBusyTimeout(patched)) throw new Error("SQLite busy-timeout patch failed postcondition verification.");
return patched;
}

export function hasRuntimeSqliteWriteRecovery(runtime: string): boolean {
const sync = `${helper}.installSqliteWriteRecovery(this)`;
const startup = new RegExp(`${escape(`.db.exec("${pragma}"),${helper}.installSqliteWriteRecovery(`)}(${identifier})\\),\\1\\}catch`, "gu");
return hasRuntimeSqliteBusyTimeout(runtime)
&& runtime.split(sync).length === 2
&& count(runtime, startup) === 1
if (!hasRuntimeSqliteBusyTimeout(runtime)) return false;
const opened = openedAnchor.exec(runtime);
if (!opened) return false;
const after = runtime.slice(opened.index + opened[0].length, opened.index + opened[0].length + 600);
return count(after, new RegExp(storeRecovery.source, "gu")) === 1
&& runtime.split(`${helper}.pruneSqliteUsage(`).length === 2;
}

Expand Down Expand Up @@ -79,9 +102,13 @@ export function patchRuntimeSqliteWriteRecovery(runtime: string): string {
throw new Error("SQLite recovery patch is partial or ambiguous.");
}
if (!hasRuntimeSqliteBusyTimeout(runtime)) throw new Error("SQLite recovery requires the post-migration timeout patch.");
const timeout = new RegExp(`(${identifier})\\.db\\.exec\\("${pragma}"\\)`, "gu");
if (count(runtime, timeout) !== 2) throw new Error("SQLite recovery open boundaries are ambiguous.");
let patched = runtime.replace(timeout, (call, store) => `${call},${helper}.installSqliteWriteRecovery(${store})`);
const opened = openedAnchor.exec(runtime);
if (!opened) throw new Error("SQLite recovery store-open anchor is missing.");
const after = runtime.slice(opened.index + opened[0].length);
const storeReturn = /^return new ([A-Za-z_$][\w$]*)\(([^)]*)\)/u.exec(after);
if (!storeReturn) throw new Error("SQLite recovery store construction is missing or ambiguous.");
const patchedOpen = `${opened[0]}let ${storeVariable}=new ${storeReturn[1]}(${storeReturn[2]});${helper}.installSqliteWriteRecovery(${storeVariable});return ${storeVariable}`;
let patched = runtime.slice(0, opened.index) + patchedOpen + after.slice(storeReturn[0].length);
patched = patchUsageCleanup(patched);
if (!hasRuntimeSqliteWriteRecovery(patched)) throw new Error("SQLite recovery patch failed postcondition verification.");
return patched;
Expand Down
Loading
Loading