feat: bili daemon subcommand — per-session dynamic-port proxy (#518) - #523
feat: bili daemon subcommand — per-session dynamic-port proxy (#518)#523ranxianglei wants to merge 1 commit into
Conversation
[bot] 🏷 Review started. Pulling the branch ( |
[bot] 🏷 Review complete — LGTM. Implementation matches the spec point-for-point, and every pre-flight claim checked out under independent verification. Details below. What I verified (not just trusted)Pre-flight, re-run independently on the PR head:
End-to-end smoke on the built artifact (
Design assessment (why the approach holds):
Minor findings (all non-blocking, your call)
None of these rise to a separate issue per the reporting bar — they're nits/theoretical, so reported here only. Verdict: ready to merge from my side. If you'd like, I can push a follow-up commit addressing items 1–3 (one-line fixes each); otherwise ship as-is and handle them whenever. |
|
Review vs master 3cd85b6 (post v0.1.89, kernel 0.0.56): Sound core (kept for the rework):
Drift vs master:
Given #524 stacks on this, sequence: family decision (#513) → rework #523 → rebase #524 on it. |
Thin launcher for agent-side plugins to start an isolated proxy per session: always a fresh instance (never attaches), dynamic port via ephemeral bind-0 probe with EADDRINUSE fallback, per-session handshake through BILI_RESULT_FILE so parallel daemons never clobber each other, and #414 parent-gone reaping via BILI_PARENT_PID (triple-state: flag > env > no watcher + warning). Prints one JSON line {origin,port,pid,logPath} to stdout on success; exit non-zero + stderr on failure. - launcher.ts: LaunchOptions +fresh/resultFile/parentPid, exported allocateDynamicPort, spawnDaemonProxy/runDaemon - server.ts: announceListening also writes the per-session result file when BILI_RESULT_FILE is set; finishShutdown unlinks it (ownership-guarded) - cli.ts: `daemon` command + --parent-pid validation (exit 2 on bad value) - tests: unit (deps-injection), parseArgs cases, real-process integration incl. parent-gone reaping
e515720 to
51547cc
Compare
|
Rebased onto master 5836f78 (was 237 commits behind, 2 conflicts in
Pre-flight on rebased head 51547cc: typecheck ✓ · 1250/1252 tests (2 known env fails in plugin-agent.test.ts, unrelated; +12 daemon tests pass) · build ✓. |
📦 Built Package ArtifactBranch: Option A — Install from npm PR tag (recommended)npm install -g billion-context@pr-523Each push to this PR publishes a new version under the Option B — Download artifact
tar xzf billion-context-pr523.tgz
npm install -g packageThis comment is automatically updated on each push. |
[bot] 🏷 Rebased head Conflict-resolution audit (the part that can actually break)
One new nit introduced by "keep both sides" (non-blocking): Independent pre-flight on
|
What
New thin subcommand
bili daemonfor agent-side plugins to start an isolated proxy per session (native mode A from #513):Design (per issue spec)
ensureProxyRunning— same launch-token handshake, health polling, and launcher 端口 TOCTOU(探测-释放-重bind 无重试):可静默挂上陌生 bili 或 20s 挂死;且被启动代理强制 --debug 制造 12MB/天日志 #407 EADDRINUSE retry chain; newfreshoption skips the attach step so sessions never share an instance.allocateDynamicPort()does an ephemeral bind-0 probe; an explicit--port/ACP_PORTis still preferred first, with automatic fallback on conflict (findFreePortnow reuses it).tmp/bili-daemon-<uuid>.json, passes it viaBILI_RESULT_FILE; child writes it atomically after binding (alongside the usual global instance file, which stays last-writer-wins for MCP-shell/install discovery). Parent polls ONLY its own file — parallel daemons can't clobber each other, and the poll loop no longer needs any new dep surface.BILI_PARENT_PID=<host pid>and reuses the existing Windows 上 launcher 子代理从无优雅退出:stopProxy 直接 TerminateProcess,SIGBREAK-flush 全史 0 次触发,防抖窗状态+进行中轮被拦腰 #414 watcher. Triple-state semantics:--parent-pidflag > inheritedBILI_PARENT_PIDenv > no watcher at all (+ stderr warning). It deliberately does NOT default to the daemon CLI's own pid — the CLI exits right after printing JSON, which would suicide the proxy within one 2s tick.{origin, port, pid, logPath}on stdout, exit 0. Failure → stderr + exit 1. Usesprocess.exitCode(notprocess.exit) so stdout flushes before exit.announceListeningalso writes the per-session file whenBILI_RESULT_FILEis set;finishShutdownunlinks it with an ownership guard (only if the record'sinstanceIdis ours), covering the case where the parent died before startup completed.Files
src/launcher.ts—LaunchOptions.{fresh,resultFile,parentPid},allocateDynamicPort(),spawnDaemonProxy(),runDaemon()src/server.ts— per-session result-file write/cleanupsrc/cli.ts—daemoncommand +--parent-pid(validated positive integer, exit 2 otherwise)tests/daemon.test.ts(new) — 8 unit tests, deps-injection style like the launcher tests: fresh-skips-attach vs control, env plumbing (all three parentPid states), resultFile-only polling (decoy global instance), timeout path, dynamic port allocation,runDaemonstdout/exitCode/tmpdir-cleanup, env fallback + no-parent warningtests/e2e-daemon.test.ts(new) — real processes: spawnnode --import tsx src/index.ts daemon --parent-pid <dummy>, parse the JSON line, hit/__bili/health, kill the dummy parent and assert the proxy self-exits within the watcher window (~2s observed)tests/cli-parseargs.test.ts— parse/validation cases for the new commandREADME.md— shortPer-session proxy (bili daemon)sectionPre-flight
npm run typecheck— cleannpm test— 958/959 pass. The single failure (resolveClientCommand: codex/claude resolve to themselves) is pre-existing and environment-specific: this sandbox has a real/usr/bin/codexon PATH, so PATH resolution returns the absolute path. Verified by running the same test on pristine master in a clean worktree — identical failure. Unrelated to this change.npm run build— clean; smoke-tested the built artifact:node dist/index.js daemon --parent-pid <pid>→ handshake JSON, health OK, proxy reaped ~2s after parent kill.No version bump (content branch); no CHANGELOG entry (release territory).