Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 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
a112e45
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
245a101
docs(webui): add the streaming-send architecture section, bilingual
fengzhi09 Oct 3, 2026
d9e181d
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
679d0fe
docs(webui): add the streaming-send architecture section, bilingual
fengzhi09 Oct 3, 2026
a078ee6
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
591ccff
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
b529543
fix(local-runtime): make an abandoned migration lease recoverable at …
fengzhi09 Oct 3, 2026
a8e56dc
feat(webui): move model and permission writes behind the engine facade
fengzhi09 Oct 3, 2026
48199c5
Reset dev-lhl to the full local integration line (B9+B10+docs+P13+P14…
fengzhi09 Oct 3, 2026
5661bb9
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
4b5e8d2
Merge main into dev-lhl
fengzhi09 Oct 3, 2026
9fdd8d1
fix(webui): surface truncated acp stderr in failure alerts
fengzhi09 Oct 3, 2026
5427f23
feat(webui): move the provider family behind the engine facade with s…
fengzhi09 Oct 3, 2026
afa995e
fix(webui): acknowledge in-flight messages explicitly instead of echo…
fengzhi09 Oct 3, 2026
6c30484
fix(webui): normalise the expected side of the provider cwd path asse…
fengzhi09 Oct 3, 2026
e68a8df
feat(webui): bridge thinkingEffort as the third config id and gate th…
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
143 changes: 138 additions & 5 deletions docs/webui.md

Large diffs are not rendered by default.

130 changes: 125 additions & 5 deletions docs/webui.zh-CN.md

Large diffs are not rendered by default.

57 changes: 54 additions & 3 deletions packages/webui/acp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,20 @@ const DEFAULT_CWD = process.cwd()
const JSON_RPC_METHOD_NOT_FOUND = -32601
const JSON_RPC_INTERNAL_ERROR = -32603

// Bounded tail of the engine subprocess's stderr.
//
// The engine reports its OWN failures on stderr — a failed migration, a
// lock it could not take, a config it refused to parse — and the crash
// alert is raised by the webui, not by the engine. Without a tail the
// whole diagnostic dies with the pipe: the operator sees only
// `mcode acp exited (code=1)` and cannot tell a lock contention from a
// missing binary. Both bounds are needed: bytes alone let one long
// stack trace push the real message out of the window, and lines alone
// let one pathological line carry megabytes.
const STDERR_TAIL_MAX_BYTES = 2048
const STDERR_TAIL_MAX_LINES = 20
const STDERR_TRUNCATION_MARKER = '[acp stderr truncated, showing the tail]'

/**
* The capabilities this client advertises in `initialize`.
*
Expand Down Expand Up @@ -96,12 +110,31 @@ export class McodeAcpClient extends EventEmitter {
// singleton (the previous PR's bug: `_mcodeAcpSingleton.alive` always
// undefined) is now actually detected and replaced on the next call.
this._alive = false
// Bounded stderr tail (see STDERR_TAIL_MAX_BYTES). Reset per
// `start()` because each start is a different subprocess.
this._stderrTail = ''
this._stderrTruncated = false
}

get alive() {
return this._alive && this.child !== null && this.started === true
}

/**
* The engine subprocess's stderr, bounded to the last ~2KB / ~20 lines,
* prefixed with a truncation marker when anything was dropped.
*
* `''` when the engine wrote nothing to stderr — a caller reporting a
* crash omits the field rather than attaching an empty string, so the
* alert it builds keeps the shape it had before this existed.
*/
get stderrTail() {
if (!this._stderrTail) return ''
const lines = this._stderrTail.split('\n')
const kept = lines.slice(-STDERR_TAIL_MAX_LINES).join('\n')
return this._stderrTruncated ? STDERR_TRUNCATION_MARKER + '\n' + kept : kept
}

async start() {
if (this.started) return this.capabilities
// Windows .cmd shim handling: Node 22+ rejects `spawn('mcode.cmd', { shell:false })`
Expand All @@ -111,6 +144,10 @@ export class McodeAcpClient extends EventEmitter {
// On Linux/macOS, plain `spawn('mcode')` walks PATH. .js/.mjs entries run under
// process.execPath on every platform.
const resolved = resolveMcodeCmd()
// A new subprocess gets a new tail: a stale line from a previous
// process would misattribute its failure to this one.
this._stderrTail = ''
this._stderrTruncated = false
let cmd, args
if (/\.(js|mjs)$/i.test(resolved)) {
cmd = process.execPath
Expand Down Expand Up @@ -156,9 +193,7 @@ export class McodeAcpClient extends EventEmitter {
this.child.stdout.setEncoding('utf8')
this.child.stdout.on('data', (chunk) => this._onData(chunk))
this.child.stderr.setEncoding('utf8')
this.child.stderr.on('data', (c) => {
if (this.debug) process.stderr.write('[acp stderr] ' + c)
})
this.child.stderr.on('data', (c) => this._onStderr(c))
this.capabilities = await this.request('initialize', {
protocolVersion: 1,
clientInfo: { name: 'mcode-webui', version: '0.1.0' },
Expand All @@ -183,6 +218,22 @@ export class McodeAcpClient extends EventEmitter {
this.pending.clear()
}

// Record the engine's stderr for the crash alert, and mirror it live
// in debug mode (the dev-loop behavior this handler had before the
// tail existed — unchanged). The tail is kept regardless of `debug`:
// in production nobody is reading the server's own stderr, which is
// precisely why the engine's message has to travel inside the alert.
_onStderr(chunk) {
if (this.debug) process.stderr.write('[acp stderr] ' + chunk)
const next = this._stderrTail + chunk
if (next.length > STDERR_TAIL_MAX_BYTES) {
this._stderrTail = next.slice(-STDERR_TAIL_MAX_BYTES)
this._stderrTruncated = true
} else {
this._stderrTail = next
}
}

_onData(chunk) {
this.buf += chunk
let nl
Expand Down
106 changes: 90 additions & 16 deletions packages/webui/docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,19 @@ not a total-turn ceiling.
`"at-capacity"` (the server is at `MAX_CONCURRENT`, which `/api/health`
reports as `maxConcurrent`)

**A 409 is terminal for that message, and it is not a failure.** The turn is
never handed to the engine, the `›` line is never written, and nothing reaches
the persisted record — a refused send cannot be half-applied, and cannot be
one the engine ran while the transcript lost. There is no send queue: "refused,
try again when the turn ends" is the whole contract.

`error` is the user-facing sentence (the composer renders it verbatim) and
`reason` is the stable machine-readable key; branch on `reason`. For
`cid-busy` and `session-busy` that sentence states the conversation is already
running a turn and the message was not delivered, rather than repeating the
internal detail — which reads "another window" and is wrong for the common
case of the same tab sending again a moment later.

### `POST /api/stop`

Cancel the current run. Tries `session/cancel` via acp (the cancel
Expand Down Expand Up @@ -1973,9 +1986,14 @@ actually read for each layer, so an operator can confirm which file
the live config came from.

Layered resolution: `MCODE_WEBUI_MODELS_CONFIG` env → cwd `models.json`
→ user-level `~/.mcode-webui/providers.json` (the PUT write target).
Same-id provider deep merge; models dedupe by id with the higher layer
winning.
→ the engine's `<engine data dir>/config.yaml` under `custom_provider`
(the PUT write target). Same-id provider deep merge; models dedupe by id
with the higher layer winning.

The env and cwd layers are deployment-owned and are never written by
any handler. The third layer used to be a webui file of its own
(`~/.mcode-webui/providers.json`); it is now the engine's own provider
store, and that file is **deprecated** — see "Provider storage" below.

**Response 200**
```json
Expand Down Expand Up @@ -2014,26 +2032,70 @@ winning.
}
```

- `sources.user` and `userPath` still name the **deprecated**
`~/.mcode-webui/providers.json`. The fields did not change and the
values did not either: both are documented as "the files this server
resolved", and an operator diagnosing a missing provider still needs
to be told what to look at. What changed is the answer — the file is
read only until the migration completes, and is never written again.
The live catalogue is the engine store; `GET /api/models` reads it
there too.
- `auth.apiKeyMasked` is the only apiKey shape returned by any route
in this surface. A test (and `scripts/check-docs-alignment.mjs`)
pins the rule: the plaintext key MUST NEVER appear in any
`/api/providers*` response, regardless of which layer held it.
**Path forms are reported as the server resolved them, and nothing is
re-resolved.** `sources.cwd` is `<process.cwd()>/models.json`, and
`process.cwd()` is the kernel-reported working directory — on macOS
that is the fully-resolved form, so a server started under `/var`
reports `/private/var/...`. That is the correct answer to "which file
did you read", and the write side uses the same resolver, so the file
the response names is the file the `PUT` will land in. `sources.user`
and `userPath` come straight from `MCODE_WEBUI_DATA_DIR` and are
reported exactly as configured.

- `sources.env` is `null` when `MCODE_WEBUI_MODELS_CONFIG` is unset;
`sources.cwd` is omitted from the layer set in that case (the env
override is the cwd file).

### `PUT /api/providers`

Validate-and-persist a v2 provider config to the user-level file
(`~/.mcode-webui/providers.json`, the file written by this handler).
The env / cwd layers are deployment-owned and never written here.

The handler atomically writes via rename (no half-written file on
disk), reloads the layer set on the next call, and broadcasts an
SSE `providers.updated` named event with the masked payload so
every connected client refreshes its catalogue without polling.
`/api/models` picks up the change on the next request — no restart
required.
Validate-and-persist a v2 provider config to the **engine's provider
store** — `<engine data dir>/config.yaml` under `custom_provider`,
written with mode `0600`. The env / cwd layers are deployment-owned and
never written here, and neither is the deprecated
`~/.mcode-webui/providers.json`.

The handler performs **one** write: a temporary file plus a single
`rename` of the whole document. There is no second file to fall out of
step, so a request either lands completely or changes nothing — a
concurrent reader always sees a whole catalogue, never a mixture, and
never a partially written YAML document. The layer set is re-read on
the next call, and the handler broadcasts an SSE `providers.updated`
named event with the masked payload so every connected client refreshes
its catalogue without polling. `/api/models` picks up the change on
the next request — no restart required.

**Capability gate.** The two write endpoints (`PUT /api/providers`
and `POST /api/providers/preset/:id/enable`) declare
`authCredentials` and gate **hard** on their sub-item
(`updateUserModelProvider` / `createUserModelProvider`). A provider
that declares the sub-item absent answers
`501 {ok:false, code:"engine_capability_not_supported", …}` rather than
acknowledging a configuration the engine will never read. On the
default `acp` transport no provider is registered yet, so the gate
reports `unregistered-transport` and the write proceeds. The three read
endpoints declare the same capability and gate **soft** — they report
degradation and keep serving.

**Legacy migration.** While the engine store carries no migration
marker, the deprecated `providers.json` is still the authority: webui
folds it into the store, losslessly, on the next read, and stamps the
marker on success — after which the file is never read again. A failed
migration (an unparseable `config.yaml`, a write that could not
complete) leaves the store untouched and the old format readable, and
the next read retries. Field-by-field equivalence is pinned by
`packages/webui/test/lib/engine/provider-migration.test.js`.

**Request**
```json
Expand All @@ -2059,15 +2121,27 @@ required.
{
"ok": true,
"providers": [ /* masked view, same shape as GET */ ],
"path": "/home/you/.mcode-webui/providers.json"
"path": "/home/you/.minimax/config.yaml",
"engineSync": { "ok": true, "written": true, "keys": ["openai_compat"] }
}
```

- `path` is the file this handler wrote: the engine's `config.yaml`.
It used to be `~/.mcode-webui/providers.json`.
- `engineSync` reports the store write itself. `written: false` means
the document would have come out unchanged (a no-op PUT does not
re-chmod a file an operator just hand-edited). It is `ok: true`
whenever the store accepted the write.
- `400 BAD_BODY` — invalid provider shape, unknown protocol, or
validation failure (each error carries a human-readable `error`
string with the offending field).
- `500 WRITE_FAILED` — disk I/O failure (the in-memory state did
not change; the operator should retry).
- `500 WRITE_FAILED` — the store refused or could not perform the
write. Two causes, and the second is the one that matters: a
`config.yaml` that does not parse is **refused, never
overwritten**, because rewriting it would destroy every engine
setting the store does not own. In both cases the previous
document is intact, the next `GET` returns the catalogue the client
already had, and the operator can retry.

### `POST /api/providers/test`

Expand Down
79 changes: 70 additions & 9 deletions packages/webui/docs/API.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,16 @@ stdin。
`"session-busy"`(另一个客户端正在跑这个会话)或 `"at-capacity"`
(服务端已达 `MAX_CONCURRENT`,即 `/api/health` 里报的 `maxConcurrent`)

**409 对这条消息是终态,而且它不是失败。** 这个回合不会交给引擎,`›` 行
不会写入,落库记录里也不会有任何东西——被拒的发送不可能被半途应用,也不可能
出现「引擎跑了、转录却丢了」的情形。这里没有发送队列,契约就是「被拒,回合
结束后再发」。

`error` 是面向用户的句子(composer 逐字渲染它),`reason` 是稳定的机读键;
按 `reason` 分支。`cid-busy` 与 `session-busy` 的句子说明「本会话正在跑一个
回合,这条消息未送达」,而不是复述内部 detail——后者写的是「另一个窗口」,
对「同一个标签页隔一会儿再发一次」这个常见情形是错的。

### `POST /api/stop`

取消当前运行。先尝试通过 acp 调用 `session/cancel`
Expand Down Expand Up @@ -1812,8 +1822,14 @@ Multipart 文件上传。保存到 `MCODE_WEBUI_UPLOAD_DIR` 并返回
现网配置来自哪个文件。

分层解析顺序:`MCODE_WEBUI_MODELS_CONFIG` 环境变量 → cwd 下的
`models.json` → 用户级 `~/.mcode-webui/providers.json`(PUT 的写入
目标)。同 id 的 provider 做深合并;模型按 id 去重,高层胜出。
`models.json` → 引擎的 `<引擎数据目录>/config.yaml` 里的
`custom_provider` 节点(PUT 的写入目标)。同 id 的 provider 做深
合并;模型按 id 去重,高层胜出。

env 与 cwd 两层由部署方拥有,任何 handler 都不写。第三层过去是
webui 自己的文件(`~/.mcode-webui/providers.json`),现在是引擎
自己的 provider 存储;那个文件已**废弃**,详见下文 `PUT /api/providers`
一节。

**响应 200**
```json
Expand Down Expand Up @@ -1856,14 +1872,30 @@ Multipart 文件上传。保存到 `MCODE_WEBUI_UPLOAD_DIR` 并返回
与 `scripts/check-docs-alignment.mjs` 一起把这条规则钉死:无论
密钥来自哪一层,明文 key 都绝不允许出现在任何
`/api/providers*` 响应中。
- `sources.user` 与 `userPath` 仍然指向**已废弃**的
`~/.mcode-webui/providers.json`。字段没变,取值也没变:两者的
文档语义都是「服务端解析了哪些文件」,运维排查 provider 缺失时
仍然需要知道该看哪里。变的是答案——该文件只在迁移完成前被读取,
此后不再被写入。真正的目录在引擎存储里,`GET /api/models` 也
是从那里读的。
**路径按服务端解析出的形态上报,不做二次解析。** `sources.cwd` 是
`<process.cwd()>/models.json`,而 `process.cwd()` 是内核返回的工作
目录——在 macOS 上它是完全解析后的形态,因此从 `/var` 下启动的服务会
上报 `/private/var/...`。这正是「你到底读了哪个文件」的正确答案;
写入侧用的是同一个解析器,所以响应里指名的文件就是 `PUT` 会落到的
文件。`sources.user` 与 `userPath` 直接来自 `MCODE_WEBUI_DATA_DIR`,
按配置原样上报。

- `MCODE_WEBUI_MODELS_CONFIG` 未设置时 `sources.env` 为 `null`;
此时 `sources.cwd` 也从层级集合中省略(环境变量覆盖的就是 cwd
那个文件)。

### `PUT /api/providers`

校验并持久化一份 v2 provider 配置到用户级文件
(`~/.mcode-webui/providers.json`,即本 handler 写入的文件)。
校验并持久化一份 v2 provider 配置到**引擎的 provider 存储**——
`<引擎数据目录>/config.yaml` 的 `custom_provider` 节点,文件权限
`0600`。env / cwd 两层由部署方拥有,本 handler 不写;已废弃的
`~/.mcode-webui/providers.json` 同样不写。
env / cwd 两层归部署方所有,永远不在这里被写。

handler 通过 rename 原子写入(磁盘上不会出现半写文件),下一次
Expand Down Expand Up @@ -1895,14 +1927,40 @@ handler 通过 rename 原子写入(磁盘上不会出现半写文件),下
{
"ok": true,
"providers": [ /* 掩码视图,形态与 GET 相同 */ ],
"path": "/home/you/.mcode-webui/providers.json"
"path": "/home/you/.minimax/config.yaml",
"engineSync": { "ok": true, "written": true, "keys": ["openai_compat"] }
}
```

- `path` 是本 handler 实际写入的文件:引擎的 `config.yaml`。它
过去是 `~/.mcode-webui/providers.json`。
- `engineSync` 报告这次存储写入本身。`written: false` 表示文档
内容不会变化——空转的 PUT 不会去重设运维刚手工编辑过的文件权限。
只要存储接受了写入,它就是 `ok: true`。

**能力门控。** 两个写端点(`PUT /api/providers` 与
`POST /api/providers/preset/:id/enable`)声明 `authCredentials`
能力,并对自己的子项(`updateUserModelProvider` /
`createUserModelProvider`)做**硬**门控。声明缺失该子项的 provider
会得到 `501 {ok:false, code:"engine_capability_not_supported", …}`,
而不是确认一份引擎永远不会读取的配置。在默认的 `acp` 传输下尚未
注册任何 provider,门控报告 `unregistered-transport`,写入照常进行。
三个读端点声明同一能力,做**软**门控——只报告降级,继续服务。

**存量迁移。** 引擎存储里没有迁移标记时,已废弃的
`providers.json` 仍然是权威来源:webui 会在每次读取时尝试把它
无损折叠进存储,成功后写入标记,该文件此后再不被读取。迁移失败
(引擎配置无法解析、写入失败)时存储保持原样,旧格式继续可读,
下一次读取会重试。目录字段逐项等价由
`packages/webui/test/lib/engine/provider-migration.test.js` 钉死。

- `400 BAD_BODY` —— provider 形态非法、协议未知,或校验失败
(每条错误都带一条可读的 `error` 文本,指出出问题的字段)。
- `500 WRITE_FAILED` —— 磁盘 I/O 失败(内存中的状态没有变化;
运维应重试)。
- `500 WRITE_FAILED` —— 存储拒绝或未能完成写入。两种成因,其
中第二种才是重点:无法解析的 `config.yaml` 会被**拒绝,绝不覆盖**,
因为覆盖会连带毁掉存储并不拥有的全部引擎配置。两种情况下前一份
文档都保持完整,随后的 `GET` 返回客户端原本就有的目录,运维可以
直接重试。

### `POST /api/providers/test`

Expand Down Expand Up @@ -2018,8 +2076,11 @@ SSE 事件,让每个已连接客户端刷新目录。下一次 `/api/models`
```

- `400 UNKNOWN_PRESET` —— `:id` 不是已知模板。
- `500 WRITE_FAILED` —— 磁盘 I/O 失败(内存中的状态没有变化;
运维应重试)。
- `500 WRITE_FAILED` —— 存储拒绝或未能完成写入。两种成因,其
中第二种才是重点:无法解析的 `config.yaml` 会被**拒绝,绝不覆盖**,
因为覆盖会连带毁掉存储并不拥有的全部引擎配置。两种情况下前一份
文档都保持完整,随后的 `GET` 返回客户端原本就有的目录,运维可以
直接重试。

---

Expand Down
2 changes: 1 addition & 1 deletion packages/webui/docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -608,7 +608,7 @@ is what lets it be re-exported from `engine/index.js` at all.
static imports, because `routes/model.js` already imported all four
**before** M3-B4 and the server's boot cost is therefore exactly what it
was. They reach `@mavis/shared/local-runtime-paths` (via `lib/config.js`)
and `js-yaml` (via `engine-provider-sync.js`), so the module is deliberately
and `js-yaml` (via `engine/provider-store.js`), so the module is deliberately
**not** re-exported from `engine/index.js`: making the shared facade — the
one import site the whole server shares, and the one `routes/plugins.js`
must stay light through — heavier than it has ever been would buy nothing.
Expand Down
2 changes: 1 addition & 1 deletion packages/webui/docs/ARCHITECTURE.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ handler 层测试因此保持封闭。
`lib/engine-catalogue.js`、`lib/models.js`、`lib/providers-config.js`——
是静态 import,因为 M3-B4 之前 `routes/model.js` 就静态 import 了这四个,
所以 server 的启动成本分文未增。但它们会经 `lib/config.js` 抵达
`@mavis/shared/local-runtime-paths`、经 `engine-provider-sync.js` 抵达
`@mavis/shared/local-runtime-paths`、经 `engine/provider-store.js` 抵达
`js-yaml`,所以这个模块**刻意没有**从 `engine/index.js` 转发导出:让
共享门面——整个 server 唯一的共享 import 站点,也是
`routes/plugins.js` 必须保持轻量的那个——比它历来更重,换不来任何东西。
Expand Down
Loading
Loading