Skip to content

refactor(doctor): drop --no-live / --sessions + dead protocol chain#1470

Merged
jackwener merged 1 commit into
mainfrom
refactor/doctor-no-live-sessions
May 11, 2026
Merged

refactor(doctor): drop --no-live / --sessions + dead protocol chain#1470
jackwener merged 1 commit into
mainfrom
refactor/doctor-no-live-sessions

Conversation

@jackwener

Copy link
Copy Markdown
Owner

Summary

@WAWQAQ directive: opencli doctor shouldn't need --no-live or --sessions. First-principles: doctor diagnoses browser-bridge health — the live connectivity probe is the core diagnostic, and session enumeration is a separate concern. Both flags are accretion.

Removing the flags surfaces a dead chain that goes with them:

  • --no-live / --sessions flags → removed
  • DoctorOptions.live / .sessions → removed
  • DoctorReport.sessions → removed
  • [SKIP] Connectivity (--no-live) render branch → removed (always-live now)
  • listSessions() in daemon-client → removed (only consumer was doctor)
  • 'sessions' action in protocol type → removed
  • BrowserSessionInfo public type → removed (no remaining consumers)
  • extension handleSessions action handler → removed (extension 1.0.12)
  • extension test reports sessions per session → removed
  • OPENCLI_BROWSER_IDLE_TIMEOUT test rewired to a still-valid action (cookies)

Net: -222 lines, 12 files. Extension bumped 1.0.11 → 1.0.12.

Test plan

  • npx tsc --noEmit root pass
  • npm run typecheck --prefix extension pass
  • npx vitest run --project unit src/doctor.test.ts → 17/17
  • npx vitest run --project extension extension/src/background.test.ts → 49/49
  • npm run check:typed-error-lint baseline 189/189
  • npm run check:silent-column-drop baseline 103/103
  • npm run build + extension build pass
  • no --no-live / --sessions / BrowserSessionInfo / listSessions / handleSessions / 'sessions' action grep remnants

Doctor's job is browser-bridge health diagnosis. The `--no-live` flag
let users skip the connectivity probe (= the core diagnostic), and
`--sessions` listed automation sessions (a separate concern not part of
health). Both flags accreted features that violated the command's
first-principles purpose.

Cleanup chain (removing dead code surfaced by the flag removal):
- `--no-live` / `--sessions` flags removed from `opencli doctor`
- `DoctorOptions.live` / `DoctorOptions.sessions` removed
- `DoctorReport.sessions` removed
- `[SKIP] Connectivity` render branch removed (always-live now)
- `listSessions()` removed (only consumer was doctor)
- `'sessions'` action removed from daemon-client protocol type
- `BrowserSessionInfo` type removed (no remaining consumers)
- extension `handleSessions` action handler removed (1.0.12)
- extension test "reports sessions per session" removed
- `OPENCLI_BROWSER_IDLE_TIMEOUT` test rewired to 'cookies' action

Verification:
- root typecheck + extension typecheck pass
- doctor.test.ts 17/17 pass
- extension/background.test.ts 49/49 pass
- typed-error-lint 189/189 baseline
- silent-column-drop 103/103 baseline
- build + extension build green

@jackwener jackwener left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. 深审过,scope 准 + 语义保留 + 验证齐。

Dead-chain 完整性 (5 层)

  1. CLI flags --no-live / --sessions → 删
  2. DoctorOptions.live / .sessions / DoctorReport.sessions → 删
  3. listSessions() daemon-client → 删(grep 全 repo 唯一 consumer 是 doctor)
  4. 'sessions'DaemonCommand.action union → 删(src/browser/daemon-client.ts:24 已不含)
  5. extension handleSessions + BrowserSessionInfo 公共类型 → 删

repo-wide grep 验证(不含 dist/ 构建产物 + CHANGELOG 说明):

  • listSessions / BrowserSessionInfo / handleSessions / --no-live / 'sessions'/"sessions" 在 src/extension/tests/docs/skills/clis 全 0 remnant ✓

语义保留

  • live: false 分支的 minimal auto-start (bridge.connect timeout 5s → close) 不再需要——新流程 checkConnectivity() 永远跑,bridge.connect() 本身就是 auto-start 路径。closeWindow + bridge.close 在 finally 链清理 ✓
  • daemonFlaky / extensionFlaky 判定从 connectivity?.ok && ... 简化为 connectivity.ok && ...(connectivity 现在必有),逻辑一致

Test rewiring 抽查

  • OPENCLI_BROWSER_IDLE_TIMEOUTaction:'sessions'action:'cookies' 是有效 coverage——handleCommand 入口 dispatch 内 idleTimeout 提取逻辑 action-agnostic
  • doctor.test.tsmockConnect.mockResolvedValue(...) 提到 beforeEach 当 happy default,failure-path 测试用 mockRejectedValueOnce 显式驱动,覆盖等价于老 live:true/false 矩阵

Version 一致性

  • extension/manifest.json + extension/package.json 同步 1.0.11 → 1.0.12 ✓
  • compatRange: '>=1.7.0' 未改是对的——extension 删旧 action 不破老 CLI(老 CLI 即使发 'sessions' 也只是 extension 返 unknown action,CLI 已不会再发)

本地复跑

  • npx vitest run --project unit src/doctor.test.ts → 17/17 ✓
  • npx vitest run --project extension extension/src/background.test.ts → 49/49 ✓

Skill doc 一致skills/opencli-usage/SKILL.md 同步 always-live + 只剩 -v flag 一致 ✓

CI 红点确认非 PR 引起douyin/_shared/creation-id.test.js 4-char random ID 100 个偶尔碰撞——pre-existing flake,作为单独 cleanup(增加熵 / N-1 tolerance)跟踪即可,不阻塞本 PR。

Nice-to-have (不阻塞):删掉的 skips auto-start in no-live mode when daemon is already running 测试覆盖的是「daemon already running 时不重复 bridge.connect」的优化路径。新代码 checkConnectivitybridge.connect 的 fast-path(health.state ready 时直接 attach 不 spawn)继续生效,覆盖被 fast-path 自身吸收,无 coverage gap 但失去显式 regression gate。如果之后 fast-path 回归,最先发现的会是 doctor live probe 在 daemon-healthy 场景下的 latency tail。可选 follow-up:加一条 fast-path 计数 assertion。

合入授权我没有(pr-manager routing rule),但 review 这条通过。你按 WAWQAQ 在 msg=05809474 的 "review pass 才 merge" 收尾即可。

@jackwener jackwener merged commit 987d9cb into main May 11, 2026
21 of 22 checks passed
@jackwener jackwener mentioned this pull request May 11, 2026
4 tasks
jackwener added a commit that referenced this pull request May 12, 2026
…ons ref

Two follow-up blockers from #1505 review:

1. Subcommand help and structured help still rendered the command path
   without the parent's positional. `opencli browser foo state --help`
   showed `Usage: opencli browser state [options]`, which would lead
   users (and agents reading structured help) to think
   `opencli browser state` was a valid invocation. Now:

   - `commanderPath()` injects an ancestor's leading-positional placeholder
     (extracted from its `.usage()` override) between the ancestor's name
     and the next path segment when building paths upward.
   - `commandPathFromRoot()` strips placeholder segments (e.g. `<session>`)
     from the relative `name` field so agents can still address subcommands
     by their leaf name; placeholders remain in the `command` / `usage`
     display paths.
   - `program.configureHelp({ commandUsage: ... })` is applied recursively
     to every descendant of `browser`, because commander does NOT inherit
     `configureHelp` into subcommands.

   Result:
     opencli browser <session> click --help
     -> Usage: opencli browser <session> click [target] [options]

   Daemon, plugin, adapter, profile namespaces (no `.usage()` override)
   are unaffected.

2. `skills/opencli-browser/SKILL.md` still referenced
   `opencli browser sessions`, which was removed in #1470. Replaced the
   sentence with the underlying invariant ("Bound sessions have no
   OpenCLI idle-close timer; the binding lasts until `unbind`, tab close,
   window close, or daemon restart") without mentioning the deleted
   command.

Tests:
  - cli.test.ts: structured help expectations updated to include
    `<session>` in command/usage paths (3 tests)
  - cli-argv-preprocess.test.ts: 12 tests still green
  - 1136/1137 unit+extension green (1 unrelated skip)
  - typed-error-lint baseline 189
  - silent-column-drop baseline 103
jackwener added a commit that referenced this pull request May 12, 2026
…al (#1505)

* refactor(browser): replace --session flag with <sessionname> positional

The `--session <name>` flag was semantically required but syntactically
optional, which is an anti-pattern. Required + flag is a contradiction:
flag form implies "optional", required is a runtime patch on top. Session
is OpenCLI's "operation target" identifier — the natural form for that is
a positional argument, like `docker exec <container> <cmd>` or
`git checkout <branch>`.

New surface:

  opencli browser <sessionname> open https://x.com
  opencli browser <sessionname> click 12
  opencli browser <sessionname> bind
  opencli browser <sessionname> unbind

Commander 14 cannot natively combine a parent positional with subcommand
dispatch — the parent's positional is shadowed by subcommand matching. To
bridge that, main.ts now pre-processes argv: when the token after `browser`
is non-flag and not a known subcommand name, it is treated as the
sessionname and rewritten to the internal `--session <name>` flag form
before commander parses it. Help text on the `browser` command is
overridden via `.usage('<sessionname> <command> [options]')` so users see
the positional form.

Reserved subcommand names (33) are listed in cli-argv-preprocess.ts and
tested for parity with cli.ts subcommand registrations. If a future
subcommand is added, the test fails loudly.

Synced surfaces:
  - README.md / README.zh-CN.md — all examples
  - docs/guide/browser-bridge.md (+ zh)
  - skills/opencli-browser/SKILL.md (bind/unbind, examples, table)
  - skills/opencli-usage/SKILL.md
  - tests/e2e/browser-tabs.test.ts
  - CHANGELOG.md (Unreleased BREAKING)

The internal `--session` flag and the unit tests calling
`program.parseAsync(['...', 'browser', '--session', 'foo', ...])` are
preserved as a stable internal API: tests bypass main.ts pre-processing
and exercise commander directly. The pre-processor has its own targeted
test file (cli-argv-preprocess.test.ts, 10 tests, all green).

Verification:
  - npx tsc --noEmit — pass
  - npx vitest run --project unit — 1073/1074 pass (1 unrelated skip)
  - npx vitest run --project extension — 61/61 pass
  - npm run check:typed-error-lint — baseline 189
  - npm run check:silent-column-drop — baseline 103

* fix(cli-argv): only rewrite when `browser` is the root command

The preprocessor was looping through every argv slot and would mis-rewrite
occurrences of the literal word `browser` deeper in argv (e.g. `opencli
adapter init browser/x` or arg values containing `browser`).

Now the preprocessor walks past leading root flags + their values to
identify the root command token, and only acts when that token is
`browser`. The full set of root value-consuming flags
(`ROOT_VALUE_FLAGS`) is documented inline and kept in sync with the
`program.option()` calls in cli.ts.

Adds regression tests:
  - `opencli adapter init browser x` not rewritten
  - URL/path values containing `browser` not rewritten
  - `list browser state` (different root command) not rewritten
  - `--profile work browser foo state` correctly identifies `foo` as
    sessionname (not as --profile's value)
  - `--profile=work` long-form-with-equals consumes one slot only
  - boolean flags (`-v`) don't consume the next value

12/12 preprocessor tests pass.

* fix(cli-argv): hide --session flag, fail-fast on retired form, rename to <session>

Three blockers in #1505 review:

1. `--session` flag was still visible in `opencli browser --help` and could
   be used as a public entrance, contradicting "positional only" UX.
   Fix: switch from `.requiredOption()` to `.addOption(new Option(...).hideHelp())`.
   The flag is preserved as an internal API for the daemon protocol and direct
   `program.parseAsync` callers (tests), but is no longer documented or
   surfaced in structured help.

2. `opencli browser --session foo state` still succeeded. Now the argv
   preprocessor throws `BrowserSessionArgvError` when root `browser` is
   followed by `--session`, and main.ts catches it and exits with a
   user-facing usage error pointing to the positional form.

3. Missing-session error message exposed the internal flag:
   `required option '--session <name>' not specified`. Now `getBrowserSession()`
   in the action body throws `<session> is a required positional argument:
   opencli browser <session> <command>`, and commander no longer guards the
   hidden option.

Also (per @WAWQAQ) rename placeholder `<sessionname>` -> `<session>` everywhere
user-facing — shorter, matches CLI convention. The help text "<session> is a
required positional: pass the name of the browser session..." carries the
"name" semantics in description, not in the placeholder itself.

Sync surfaces:
  - src/cli.ts — usage line, addOption with hideHelp, descriptions
  - src/cli-argv-preprocess.ts — throw on --session form
  - src/cli-argv-preprocess.test.ts — refusal test for old form
  - src/cli.test.ts — assertions updated for hidden option + new error path
  - src/help.ts — read `_usage` private field to respect `.usage()` override
    (commander's `.usage()` getter returns auto-generated form if not set,
    which would otherwise pollute every namespace's usage string)
  - src/main.ts — catch BrowserSessionArgvError, stderr + exit
  - README.md / README.zh-CN.md
  - docs/guide/browser-bridge.md / docs/zh/guide/browser-bridge.md
  - skills/opencli-browser/SKILL.md / skills/opencli-usage/SKILL.md
  - CHANGELOG.md

Manual smoke tests (against built dist):
  - `opencli browser --help` shows `Usage: opencli browser <session> <command> [options]`
  - `opencli browser --help` Options block does NOT show `--session`
  - `opencli browser --session foo state` → friendly error, no commander stacktrace
  - `opencli browser state` → `<session> is a required positional argument: opencli browser <session> <command>`
  - `opencli browser foo state` → parses correctly

* fix: inject <session> into subcommand help paths and drop stale sessions ref

Two follow-up blockers from #1505 review:

1. Subcommand help and structured help still rendered the command path
   without the parent's positional. `opencli browser foo state --help`
   showed `Usage: opencli browser state [options]`, which would lead
   users (and agents reading structured help) to think
   `opencli browser state` was a valid invocation. Now:

   - `commanderPath()` injects an ancestor's leading-positional placeholder
     (extracted from its `.usage()` override) between the ancestor's name
     and the next path segment when building paths upward.
   - `commandPathFromRoot()` strips placeholder segments (e.g. `<session>`)
     from the relative `name` field so agents can still address subcommands
     by their leaf name; placeholders remain in the `command` / `usage`
     display paths.
   - `program.configureHelp({ commandUsage: ... })` is applied recursively
     to every descendant of `browser`, because commander does NOT inherit
     `configureHelp` into subcommands.

   Result:
     opencli browser <session> click --help
     -> Usage: opencli browser <session> click [target] [options]

   Daemon, plugin, adapter, profile namespaces (no `.usage()` override)
   are unaffected.

2. `skills/opencli-browser/SKILL.md` still referenced
   `opencli browser sessions`, which was removed in #1470. Replaced the
   sentence with the underlying invariant ("Bound sessions have no
   OpenCLI idle-close timer; the binding lasts until `unbind`, tab close,
   window close, or daemon restart") without mentioning the deleted
   command.

Tests:
  - cli.test.ts: structured help expectations updated to include
    `<session>` in command/usage paths (3 tests)
  - cli-argv-preprocess.test.ts: 12 tests still green
  - 1136/1137 unit+extension green (1 unrelated skip)
  - typed-error-lint baseline 189
  - silent-column-drop baseline 103
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant