refactor(cmdline): trim the command-line seams to existing interfaces

The web runtime creates its dev-mode client-hmr row in the root tree
after Loader settlement with plain loader.create, deleting the vendored
Entry.enableRuntime state machine and dsh-cmdline's enableRow export.
Include declares the existing EntryGroup.key tree-carrier marker instead
of the EntryConfigResolver protocol (its own path stays literal; nothing
used a dynamic path). The launcher recognizes no app row: SIGTERM exits
0 on every surface, every boot watches its user patch layers, and the
headless runner exits through ctx.appExit, deleting ctx.headlessIo. Also
restores the vendor README rescope entry to the position the
rescope-vendor exact-edit anchor requires, fixing the master hygiene
regression.
This commit is contained in:
Turtle
2026-08-11 14:17:13 +08:00
parent 2d2b89825e
commit c598989d08
32 changed files with 312 additions and 299 deletions
+2 -2
View File
@@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write apps/cli/reference/README.md
README.md: cef687dc392f97886ea18b162e8f668a44ce2284
README.zh.md: f6721ec256d404e2b602fb2ed921b41c03633a82
README.md: 8a38677868f85d4a5b24376a96f0e336c13fa89c
README.zh.md: c0ee1a5fabb8eec6bd34a8a386b2e2409d570e55
+3 -3
View File
@@ -52,7 +52,7 @@ Git-hosted plugins that ship sources build during install through their `prepare
## Web alias
`dsh web` is a hardcoded alias for `--profile web`; the flags after it belong to the web app, whose ordinary bundle provider parses them. `--host` and `--port` override the composed values of the rows that carry them, repeatable `--trusted-host` contributes invocation authorities through `ctx.webRuntime.trustedHosts` (a deployment expression concatenates its own authorities), and `--dev` switches the web-runtime row to development mode and enables the client-plugin HMR receiver the bundle ships disabled; it expects a separate `pnpm run dev:web` watcher for no-refresh client bundle updates.
`dsh web` is a hardcoded alias for `--profile web`; the flags after it belong to the web app, whose ordinary bundle provider parses them. `--host` and `--port` override the composed values of the rows that carry them, repeatable `--trusted-host` contributes invocation authorities through `ctx.webRuntime.trustedHosts` (a deployment expression concatenates its own authorities), and `--dev` switches the web-runtime row to development mode, which mounts the client-plugin HMR receiver row after Loader settlement; it expects a separate `pnpm run dev:web` watcher for no-refresh client bundle updates.
```sh
dsh web
@@ -63,9 +63,9 @@ dsh web --help
The production Web runner needs built package and frontend artifacts (`pnpm run build`). It serves `http://127.0.0.1:3080` by default. Binding all interfaces also trusts the machine's discovered LAN IP literals; `--trusted-host` adds named authorities accepted by the `/api` browser-trust fence.
Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.
Process shutdown gives the plugin tree up to five seconds to dispose. The first `SIGINT`/`SIGTERM` starts that graceful drain`SIGTERM` is a supervisor's ordinary stop request and exits 0 on every surface, `SIGINT` reports 130; a second signal forces immediate exit. If one-shot normal completion is already stuck in disposal, the first `Ctrl+C` is the escalation and exits immediately instead of being swallowed.
All modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. Long-lived surfaces watch valid edits of both `cordis.patch.yml` layers (profile and home) and reapply them transactionally; one-shot runs read the files once at startup.
All modes treat the invoking directory as the default workspace root, load applicable `AGENTS.md` or `CLAUDE.md` instructions with a 65,536-byte render budget, and use an in-memory SQLite session content index. Every profile boot watches valid edits of both `cordis.patch.yml` layers (profile and home) and reapplies them transactionally; a one-shot surface exits through its bounded shutdown, which disposes the watchers.
New sessions default to the `workspace-write` permission preset. Bash and filesystem mutations are restricted to the session workspace and platform temporary roots; reads, network access, and process visibility are not confined. `DSH_PERMISSION_MODE` changes the process fallback. Stored General-settings permissions affect later Web sessions, not an already-open one.
+3 -3
View File
@@ -52,7 +52,7 @@ Git 托管、随附源码的插件在安装期间通过其 `prepare` 脚本构
## Web 别名
`dsh web``--profile web` 的硬编码别名;写在它之后的 flag 属于 web 应用,由组合包中的普通提供方解析。`--host``--port` 覆盖承载它们的那些行的组合取值,可重复的 `--trusted-host` 通过 `ctx.webRuntime.trustedHosts` 提供本次调用的 authority(部署表达式会拼接自己的 authority),`--dev` 把 web-runtime 行切换到开发模式并启用组合包以禁用状态交付的客户端插件 HMR(热模块替换)接收器;若要无刷新更新客户端 bundle,还需单独运行 `pnpm run dev:web` watcher。
`dsh web``--profile web` 的硬编码别名;写在它之后的 flag 属于 web 应用,由组合包中的普通提供方解析。`--host``--port` 覆盖承载它们的那些行的组合取值,可重复的 `--trusted-host` 通过 `ctx.webRuntime.trustedHosts` 提供本次调用的 authority(部署表达式会拼接自己的 authority),`--dev` 把 web-runtime 行切换到开发模式,由其在 Loader 结算后挂载客户端插件 HMR(热模块替换)接收器;若要无刷新更新客户端 bundle,还需单独运行 `pnpm run dev:web` watcher。
```sh
dsh web
@@ -63,9 +63,9 @@ dsh web --help
生产 Web 运行器需要已构建的包和前端产物(`pnpm run build`)。默认服务地址是 `http://127.0.0.1:3080`。绑定所有接口时,还会信任机器自动发现的 LAN IP 字面量;`--trusted-host` 可添加 `/api` 浏览器信任围栏接受的具名 authority。
进程关闭时会给插件树最多 5 秒完成 dispose。第一次 `SIGINT`/`SIGTERM` 启动该优雅排空;第二次信号强制立即退出。如果一次性运行正常结束时已经卡在 dispose 中,第一次 `Ctrl+C` 就会升格并立即退出,而不会被吞掉。
进程关闭时会给插件树最多 5 秒完成 dispose。第一次 `SIGINT`/`SIGTERM` 启动该优雅排空——`SIGTERM` 是监督进程的普通停止请求,在所有 surface 上以 0 退出,`SIGINT` 报告 130;第二次信号强制立即退出。如果一次性运行正常结束时已经卡在 dispose 中,第一次 `Ctrl+C` 就会升格并立即退出,而不会被吞掉。
所有模式都将调用目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md``CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。常驻 surface 监视两个 `cordis.patch.yml` 层(profile 与 home)的有效编辑并以事务方式重新应用;一次性运行只在启动时读取这些文件一次
所有模式都将调用目录作为默认 workspace 根目录,以 65,536 字节渲染预算加载适用的 `AGENTS.md``CLAUDE.md` 指令,并使用内存 SQLite 会话内容索引。每次 profile 启动都监视两个 `cordis.patch.yml` 层(profile 与 home)的有效编辑并以事务方式重新应用;一次性 surface 经由有界关闭退出,关闭会先 dispose 监视器
新会话默认使用 `workspace-write` 权限预设。Bash 和文件系统修改仅限于会话 workspace 与平台临时根目录;读取、网络访问和进程可见性不受限制。`DSH_PERMISSION_MODE` 更改进程后备值。General settings 中存储的权限影响后续 Web 会话,不改变已打开的会话。
+7 -23
View File
@@ -38,7 +38,6 @@ const SHIPPED_PRESET_ROOT = fileURLToPath(new URL('../config/agent-presets/', im
const USER_PRESET_DIR = '.agent-presets'
import { DSH_ENVIRONMENT_KEY, type EnvironmentSnapshot } from '@deepseek-ai/dsh-environment'
import { provideCmdline } from '@deepseek-ai/dsh-cmdline'
import type { HeadlessIo } from '@deepseek-ai/dsh-headless'
import { createProcessShutdown, type ProcessShutdown } from './process-shutdown.ts'
import { resolveWindowsShellLayer } from './windows-shell.ts'
@@ -60,9 +59,6 @@ export const INSTALL_ANCHOR = fileURLToPath(new URL('../package.json', import.me
/** The session-telemetry row id the DSH_TELEMETRY_DISABLED switch targets. */
const TELEMETRY_ROW_ID = 'telemetry-otel'
/** The one-shot runner row: its presence means this composition exits by itself. */
const HEADLESS_ROW_ID = 'headless-runner'
/** The empty root entry list every profile tree patches over. */
const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
# each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
@@ -206,11 +202,6 @@ function suppressSignalShutdownError(signal: AbortSignal, error: unknown): void
*/
export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Context; shutdown: ProcessShutdown }> {
const composed = composeProfile(options.profile, options.patchFiles)
// A one-shot composition ends by itself, which changes what a signal means
// and makes watching the user's patch layer pointless.
const headlessRow = composed.rows.get(HEADLESS_ROW_ID)
const oneShot = headlessRow !== undefined && headlessRow.disabled !== true
const app: { current?: Context } = {}
const shutdown = createProcessShutdown(async () => { await app.current?.fiber.dispose() })
const signalShutdown = new AbortController()
@@ -220,7 +211,10 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
}
// Signals own teardown throughout the startup window, not only after boot()
// settles: an inserted provider can publish before sibling rows finish mounting.
process.on('SIGTERM', () => { interrupt(oneShot ? 143 : 0) })
// SIGTERM is a supervisor's ordinary stop request and exits 0 on every
// surface — the launcher does not know whether the app considered its work
// complete; SIGINT is a user interrupt and reports 130.
process.on('SIGTERM', () => { interrupt(0) })
process.on('SIGINT', () => { interrupt(130) })
installFailLoud(NAME, process, async () => {
await app.current?.fiber.dispose()
@@ -246,9 +240,6 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
...loadOptionalPatches(NAME, homePatchPath()) ?? [],
...composed.overlays,
])
// One-shot runs exit through the runner; watching would only hold the
// process open after its exit request.
const watchProfilePatch = !oneShot
// Cloned for the same insert-aliasing reason as composeLive: the boot
// application must not mutate the objects later reloads recompose from.
const ctx = await boot(NAME, rootConfig, structuredClone(allPatches(composed)), (hostCtx) => {
@@ -262,22 +253,15 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
args: options.args,
exit: code => void shutdown.shutdown(code),
})
if (oneShot) {
const io: HeadlessIo = {
stdout: process.stdout,
stderr: process.stderr,
exit: (code) => { void shutdown.shutdown(code) },
}
hostCtx.provide('headlessIo', io)
}
})
app.current = ctx
// A surface can dispose the whole tree while boot or this post-boot watcher
// setup is still in flight. Loader presence and fiber state own
// liveness; the local signal fact distinguishes that expected exit race
// from a real HMR error.
if (watchProfilePatch
&& !signalShutdown.signal.aborted
// Watching is unconditional: a one-shot surface exits through its bounded
// shutdown, which disposes the watchers before the loop drains.
if (!signalShutdown.signal.aborted
&& ctx.fiber.state === FiberState.ACTIVE
&& ctx.get('loader') !== undefined) {
try {