Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
ab59020
docs(webui): the engine layer has six files, not five (webui-parity 107)
fengzhi09 Oct 1, 2026
1dc559a
test(webui): M2 capability-declaration snapshot vs the real host (eng…
fengzhi09 Oct 1, 2026
8c085fa
test(webui): point the capability snapshot at the engine layer's real…
fengzhi09 Oct 1, 2026
aa5ab47
fix(webui): stop the shell from carrying one session's state into ano…
fengzhi09 Oct 1, 2026
af01e0e
refactor(webui): the plugins and turn-diff routes take the host from …
fengzhi09 Oct 1, 2026
f1842ba
test(webui): make the run-mirror, first-turn-guard and mavis-usage su…
fengzhi09 Oct 1, 2026
726d286
refactor(webui): the plugins and turn-diff routes take the host from …
fengzhi09 Oct 1, 2026
a4fad96
test(webui): make the run-mirror, first-turn-guard and mavis-usage su…
fengzhi09 Oct 1, 2026
e7c0ce9
feat(webui): the five read endpoints ask the engine facade, not the t…
fengzhi09 Oct 1, 2026
e053ae7
feat(webui): the session-tree and export endpoints ask the engine fac…
fengzhi09 Oct 1, 2026
4fb8267
feat(webui): the usage endpoints ask the engine facade, and the deriv…
fengzhi09 Oct 1, 2026
88b9a48
fix(webui): rebase M3-B3 onto M3-B2, register B2's two tmp prefixes, …
fengzhi09 Oct 1, 2026
6bc24bd
feat(webui): the account, model and capability reads ask the engine f…
fengzhi09 Oct 2, 2026
eb2a429
feat(webui): #73 swaps the ACP wire table for the 14-key engine-capab…
fengzhi09 Oct 2, 2026
2baf051
fix(webui): stop two B4 comments describing behaviour the code no lon…
fengzhi09 Oct 2, 2026
edf2b1e
feat(webui): move the session write family behind the engine facade
fengzhi09 Oct 2, 2026
1506cc2
fix(webui): drop whitespace text nodes in markdown tables and dedupe …
fengzhi09 Oct 2, 2026
8cca235
fix(webui): sweep the non-flipping inverted text token off primary su…
fengzhi09 Oct 2, 2026
eecd8c0
feat(webui): move session switch behind the engine facade
fengzhi09 Oct 2, 2026
0cfd51f
Merge main into dev-lhl
fengzhi09 Oct 2, 2026
e4cf052
chore: allowlist the leak-tripwire fixture in model-reads tests
fengzhi09 Oct 2, 2026
3f5b8d2
test(webui): pin session-writes cleanup-orphans test to isolated paths
fengzhi09 Oct 2, 2026
e7df93d
chore: ignore gitleaks fingerprints of deliberate test fixtures
fengzhi09 Oct 2, 2026
62814ff
chore: make the gitleaks fixture allowlists path-only
fengzhi09 Oct 2, 2026
3074010
feat(webui): move interrupt and load endpoints behind the engine facade
fengzhi09 Oct 3, 2026
063a43a
fix(webui): take the plan's 5s abort force-kill bound by product call
fengzhi09 Oct 3, 2026
90cf85e
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
a9af820
docs(webui): add session-switch, interrupt and session-load to the ar…
fengzhi09 Oct 3, 2026
4d904c3
docs(webui): add the missing zh-CN section for the B5 write family
fengzhi09 Oct 3, 2026
fdc3ff2
fix(webui): make webui-only session delete return promptly instead of…
fengzhi09 Oct 3, 2026
dab453d
fix(webui): retire lossy streaming mirrors when the engine transcript…
fengzhi09 Oct 3, 2026
7138b5b
feat(webui): add the streaming-send capability gate and pure stream b…
fengzhi09 Oct 3, 2026
a2223f4
feat(webui): run send on the runtime transport behind the engine facade
fengzhi09 Oct 3, 2026
8b51fdd
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
964c0cf
feat(webui): answer set-mode and set-config-option with structured 50…
fengzhi09 Oct 3, 2026
bdde1eb
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
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
25 changes: 23 additions & 2 deletions docs/webui.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,14 @@ Current declarations (both transcribed from the audited matrix and re-verified a
| sessionCrud | full | full |
| streamingSend | full | full |
| interrupt | full | full |
| toolSkillInvocation | full | full |
| toolSkillInvocation | partial — missing `setMode` (no session-mode write; M3-B9) | partial — missing `setMode` |
| turnDiff | full | none (implementation-absent on the adapter) |
| turnRewindRedo | full | partial — missing `reapplyTurnDiff` |
| plugins | full | partial — missing `previewGithubPlugin`, `importGithubPlugin`, `listEnabledPlugins` |
| mcp | full | full |
| subagents | partial — missing `getDelegationSnapshot`, `stopDelegation` (they live on the adapter's access-context, not the CliService surface) | full |
| usageStats | full | full |
| authCredentials | full | full |
| authCredentials | partial — missing `setConfigOption` (the GENERIC config write; M3-B9) | partial — missing `setConfigOption` |
| updateCheck | none (interface-absent) | none (implementation-absent) |
| fileReadWrite | partial — missing `file-write` | partial — missing `file-write` |
| gitOperations | partial — missing `git-diff`, `git-commit`, `git-branch` | partial — missing `git-diff`, `git-commit`, `git-branch` |
Expand Down Expand Up @@ -237,6 +237,27 @@ Default provider is `local-runtime-v2` (the only registered host provider until

501, not 400/404/500: the request was well-formed; the *engine provider* lacks the feature. This mirrors the existing `unsupported` → 501 mapping in `routes/protocol.js`. The frontend treats `engine_capability_not_supported` as expected degradation (hide the entry point per the level table), never as an error toast.

### Behaviour change: the two mode-write endpoints (M3-B9)

`POST /api/protocol/set-mode` (#67) and `POST /api/protocol/set-config-option` (#68) sit behind a **hard** capability gate, and they are the first endpoints in the migration whose answers change for some deployments. The change has exactly one trigger — *the connected engine provider declares the capability absent* — and it is worth being precise about, because everything outside it is unchanged byte for byte.

| Request | Before | After |
| --- | --- | --- |
| #67, provider declares `toolSkillInvocation.setMode` | forwarded to the engine; whatever it answered | `501 {ok:false, code:"engine_capability_not_supported", capability:"toolSkillInvocation", provider, missing:["setMode"], reason, error}` |
| #68 with any config id other than `model` / `permissionMode`, provider declares `authCredentials.setConfigOption` absent | forwarded to the engine; whatever it answered | `501 {… capability:"authCredentials", missing:["setConfigOption"] …}` |
| #68 with `model` or `permissionMode` | forwarded to the engine | **unchanged** — the bridge below |
| any of the above, the engine itself answers `unsupported` | `501 {ok:false, code:"unsupported", fallback:"send_plan_as_prompt"}` | **unchanged, including the `fallback` field** |
| any of the above, the provider does **not** declare the capability absent (including every request on the default `acp` transport) | unchanged | **unchanged** |

Two consequences of that table are deliberate rather than incidental:

- **The capability 501 carries no `fallback`.** The hint is the degraded action for a feature that exists and whose call failed. Where the engine has no mode write at all there is nothing to degrade to, and advertising `send_plan_as_prompt` from a "this is not available" response would offer a workaround for a missing feature. The engine's own `unsupported` refusal keeps its hint.
- **On the default `acp` transport nothing changes at all.** No provider is registered for `acp` until migration step M4, so the gate reports `unregistered-transport` and every response is the pre-M3 one. The refusals above are reachable on the `runtime` transport, where `local-runtime-v2` is the registered provider.

**The bridge.** A provider can refuse the *generic* config-option write and still have the two dedicated writers webui's own controls depend on. #68's gate therefore asks for a sub-item derived from the request: `model` asks for `selectModel` and `permissionMode` asks for `setPermissionMode`, both of which pass a provider that denies `setConfigOption`; every other config id asks for `setConfigOption` and gets the 501. The exemption is exactly two named ids — never a prefix, never a default — and it does not survive a `none`: a provider with no `authCredentials` at all has no dedicated writer either.

**What the user sees.** The permission-mode selector and the model selector are hidden, not disabled and not accompanied by an error message (`webapp/lib/engine-capabilities.ts`, wired in `webapp/components/composer.tsx`). A toast would report a failure for something the user was never able to do, offer nothing to act on, and reappear on every click. The rule is fail-open: the controls are shown until the declaration positively says the engine cannot do it, so a failed or slow `/api/engine-capabilities` request never removes a working control.

### Migration state and constraints

- **M1 done in this batch**: host construction (`createCatalogueHost`) moved verbatim into `server/engine/providers/local-runtime-v2.js`; `runtime-host.js` re-exports it, so every existing importer is untouched. No existing route's behaviour changed; `GET /api/engine-capabilities` is a new, additive endpoint.
Expand Down
25 changes: 23 additions & 2 deletions docs/webui.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -202,14 +202,14 @@ webui 服务端新增了一个内部引擎层 `packages/webui/server/engine/`,
| sessionCrud | full | full |
| streamingSend | full | full |
| interrupt | full | full |
| toolSkillInvocation | full | full |
| toolSkillInvocation | partial——缺 `setMode`(无会话模式写入面,M3-B9) | partial——缺 `setMode` |
| turnDiff | full | none(adapter 实现无) |
| turnRewindRedo | full | partial——缺 `reapplyTurnDiff` |
| plugins | full | partial——缺 `previewGithubPlugin`、`importGithubPlugin`、`listEnabledPlugins` |
| mcp | full | full |
| subagents | partial——缺 `getDelegationSnapshot`、`stopDelegation`(在 adapter 上下文,不在 CliService 面) | full |
| usageStats | full | full |
| authCredentials | full | full |
| authCredentials | partial——缺 `setConfigOption`(**通用**配置项写入面,M3-B9) | partial——缺 `setConfigOption` |
| updateCheck | none(接口无) | none(实现无) |
| fileReadWrite | partial——缺 `file-write` | partial——缺 `file-write` |
| gitOperations | partial——缺 `git-diff`、`git-commit`、`git-branch` | partial——缺 `git-diff`、`git-commit`、`git-branch` |
Expand Down Expand Up @@ -237,6 +237,27 @@ GET /api/engine-capabilities[?provider=<id>]

用 501 而非 400/404/500:请求本身没写错,是**引擎面缺这个功能**——与 `routes/protocol.js` 既有的 `unsupported` → 501 同款。前端把 `engine_capability_not_supported` 当作**预期降级**(按上表三档隐藏入口),不弹错误提示。

### 行为变更:两个 mode 写端点(M3-B9)

`POST /api/protocol/set-mode`(#67)与 `POST /api/protocol/set-config-option`(#68)挂在**硬**能力门后,是迁移过程中第一批**会在部分部署上改变应答**的端点。变更只有一个触发条件——*当前引擎 provider 声明该能力不存在*。这条线必须画清楚,因为线外的一切逐字节不变。

| 请求 | 变更前 | 变更后 |
| --- | --- | --- |
| #67,provider 声明 `toolSkillInvocation.setMode` 缺失 | 请求照发给引擎,引擎答什么就是什么 | `501 {ok:false, code:"engine_capability_not_supported", capability:"toolSkillInvocation", provider, missing:["setMode"], reason, error}` |
| #68 用 `model` / `permissionMode` 以外的任何 config id,且 provider 声明 `authCredentials.setConfigOption` 缺失 | 请求照发给引擎 | `501 {… capability:"authCredentials", missing:["setConfigOption"] …}` |
| #68 用 `model` 或 `permissionMode` | 请求照发给引擎 | **不变**——见下面的桥接 |
| 以上任一,而**引擎自己**答 `unsupported` | `501 {ok:false, code:"unsupported", fallback:"send_plan_as_prompt"}` | **逐字节不变,`fallback` 字段也保留** |
| 以上任一,而 provider 并未声明该能力缺失(**包括默认 `acp` 传输下的全部请求**) | 不变 | **不变** |

表里两处是刻意为之,不是顺带:

- **能力 501 不带 `fallback`。** 这个提示是「功能存在、但这次调用失败」的降级动作。引擎压根没有模式写入面时,没有任何东西可以降级过去;从一个「此功能不可用」的应答里推销 `send_plan_as_prompt`,等于给一个缺失的功能兜售替代方案。引擎自身的 `unsupported` 拒绝保留它的提示。
- **默认 `acp` 传输下什么都不变。** M4 把 ACP 包成 provider 之前,没有 provider 认领 `acp`,门报 `unregistered-transport`,每个应答都是 M3 之前的那个。上面的拒绝只在 `runtime` 传输上可达——那里注册的 provider 是 `local-runtime-v2`。

**桥接。** provider 可以拒绝**通用**配置项写入,同时仍保有 webui 自己的两个控件依赖的专用写入面。因此 #68 的门按请求推导子项:`model` 问 `selectModel`、`permissionMode` 问 `setPermissionMode`,两者都能通过一个拒绝 `setConfigOption` 的 provider;其余任何 config id 问 `setConfigOption`,拿到 501。豁免严格只有两个具名 id——绝不是前缀,绝不是默认分支——而且它撑不过 `none`:完全没有 `authCredentials` 的 provider 同样没有专用写入面。

**用户看到什么。** 权限模式选择器与模型选择器被**隐藏**,不是禁用,也不配任何错误提示(`webapp/lib/engine-capabilities.ts`,接线在 `webapp/components/composer.tsx`)。toast 会为一件用户从来就做不到的事报一次失败、无从处理、而且每点一次就再报一次。这条规则是 fail-open 的:控件会一直显示,直到声明明确说引擎做不到——因此一次失败或超时的 `/api/engine-capabilities` 请求绝不会拿掉一个本来能用的控件。

### 迁移状态与边界

- **本批只做迁移第一步 M1**:host 构造(`createCatalogueHost`)原样移入 `engine/providers/local-runtime-v2.js`,`runtime-host.js` 转发导出,既有引用方零改动;没有任何现有路由行为变化,`GET /api/engine-capabilities` 是纯新增端点。
Expand Down
37 changes: 35 additions & 2 deletions packages/webui/server/engine/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,10 @@
// (engine/host.js), so the plugins and turn-diff routes no longer name
// lib/acp-client.js. M3 batches B1 (#9 #10 #72 #74 #75), B2 (#8 #11),
// B3 (#15 #16 #17 #19), B4 (#20 #57 #73), B5 (#7 #4 #6), B6 (#3),
// B7 (#13 #69 #70 #71), B8a (#12's pure layer + gate) and B8b (#12's
// runner + route branch) done. The rest of M3, then M4, will route
// B7 (#13 #69 #70 #71), B8a (#12's pure layer + gate), B8b (#12's
// runner + route branch) and B9 (#67 #68 — the first family whose gate
// changes what a client sees, gated HARD on purpose; see the
// mode-writes.js block below) done. The rest of M3, then M4, will route
// their consumers through this facade one endpoint family at a time.

import { ENGINE_CAPABILITY_KEYS } from "./capabilities.js";
Expand Down Expand Up @@ -360,6 +362,37 @@ export {
loadFailureWireCode,
resolveSessionLoadProvider,
} from "./session-load.js";
// The SESSION MODE WRITE family (step M3, batch B9): #67 set-mode, #68
// set-config-option. Same cycle, same TDZ rule, same reasoning:
// mode-writes.js's `MODE_WRITE_ENDPOINTS` and
// `MODE_WRITE_BRIDGED_CONFIG_IDS` are both literals and every binding it
// needs is read inside a function body; a new top-level `const X =
// SOMETHING_FROM_INDEX` there breaks this re-export exactly as it would
// anywhere else. Its only static imports are `engine/capabilities.js`
// and `engine/index.js`; the RPC wrapper and the config are reached
// through `await import()` inside the data-plane functions.
//
// Both endpoints gate HARD, and this is the one M3 family where the hard
// gate is the batch's REASON rather than a consequence of having no
// fallback: it is the first family that deliberately changes what a
// client sees, and the entire change is "a provider that declares the
// capability absent answers the gate's 501 instead of having the write
// forwarded". `MODE_WRITE_BRIDGED_CONFIG_IDS` is the other half of
// that sentence — the two config ids webui's own controls depend on
// (`model`, `permissionMode`) are exempt from the generic-write
// refusal, and the frontend reads the same two names to decide which
// controls to hide.
export {
MODE_WRITE_BRIDGED_CONFIG_IDS,
MODE_WRITE_ENDPOINTS,
assertModeWriteCapability,
resolveModeWriteProvider,
resolveModeWriteSubItem,
setConfigOptionFailureStatus,
setEngineSessionConfigOption,
setEngineSessionMode,
setModeFailureStatus,
} from "./mode-writes.js";

/**
* Registered providers. `transport` records which wire form the provider
Expand Down
Loading
Loading