fix(cli): reject default-surface flags leaked onto the web subcommand

ds-review-bot: `dsh web -p task`, `dsh web --resume s`, and `dsh --config c.yml
web` reached the web action with those values in program.opts() but the action
ignored them and served — silently dropping mode-specific inputs. The web
action now reads the parent opts and fails loud (exit 1) on a leaked
--config/-p/--resume, matching the root mode's mixing guard. Covered in
args.spec.ts.

Also (ds-review-bot): tui-demo/README documented the removed `dsh
[path-to-cordis.yml]` positional form; corrected to bare `dsh` / `dsh --config
<path>`. Agent Note + Chinese pair note the web-leak guard.
This commit is contained in:
Turtle
2026-07-25 17:24:39 +08:00
parent d616d4ca50
commit 5a06b9e926
6 changed files with 21 additions and 6 deletions

View File

@@ -112,7 +112,17 @@ export function parseDshArgs(argv: readonly string[], version: string): DshInvoc
.option('--host <host>', 'override the config bind host (127.0.0.1 or 0.0.0.0)')
.option('--port <port>', 'override the config listen port (0 requests an OS-assigned port)')
.option('--dev', 'mount the client HMR driver and watch plugin bundles for rebuilds')
.action((options: WebOptions) => { resolved = resolveWeb(options) })
.action((options: WebOptions) => {
// Commander parses the parent (default-surface) options on either side of
// the subcommand into `program.opts()`. `web` shares none of them, so a
// leaked `--config`/`-p`/`--resume` is a mistyped invocation that must
// fail loud rather than silently start the web server and drop it.
const parent = program.opts<{ config?: string; prompt?: string; resume?: string }>()
if (parent.config !== undefined || parent.prompt !== undefined || parent.resume !== undefined) {
program.error('error: web takes none of --config, -p/--prompt, or --resume')
}
resolved = resolveWeb(options)
})
try {
program.parse(argv, { from: 'user' })

View File

@@ -47,6 +47,11 @@ describe('parseDshArgs', () => {
expect(exitCode(['-p', 'x', '--resume', 's'])).toBe(1)
expect(exitCode(['--bogus'])).toBe(1)
expect(exitCode(['bogus-positional'])).toBe(1)
// A default-surface flag on either side of `web` leaks into program.opts()
// but the web subcommand shares none of them: reject rather than serve.
expect(exitCode(['web', '-p', 'task'])).toBe(1)
expect(exitCode(['web', '--resume', 's'])).toBe(1)
expect(exitCode(['--config', 'c.yml', 'web'])).toBe(1)
})
it('exits 0 for --help (disclosing web) and --version', () => {