refactor(commands): extract command poll + dispatch + ack into commands/drain.ts#1317
Conversation
…ds/drain.ts §11e step 1 of COMMAND_PLUGIN_SKILL_ARCHITECTURE.md v1.0 (merged in reflectt-cloud as commit da6cdba3). Behavior-zero relocation of the inline command runtime that lived in cloud.ts. Moved verbatim from cloud.ts into src/commands/drain.ts: - pollAndProcessCommands() — heartbeat-tick poll, 10s active / 60s idle - handleCommand(cmd) — inline if/else dispatch by cmd.type - handleContextSync(cmd) — fetches local context, posts to cloud sync - handleRunApprove(cmd) — calls local approval-queue, acks - COMMAND_POLL_ACTIVE_MS / COMMAND_POLL_IDLE_MS constants - commandPollErrors / lastCommandPollAt counters - PendingCommand interface Same poll cadence. Same ack contract. Same inline dispatch (no registry yet — that lands in step 2). No new verbs. No auth/intake churn. cloud.ts changes: - exports state, config, isIdle, cloudGet, cloudPost so drain.ts can import them. ES live-binding semantics preserve current behavior; cloud.ts still owns the lets and reassigns config at line 368. - imports pollAndProcessCommands + the two MS constants from ./commands/drain.js. Two existing call sites (heartbeat tick line 436 and immediate-tick line 573) unchanged. - 197 net lines removed; module shrinks toward owning just the cloud-transport concern. Scope locks per @kai (msg-1777492135899) and @link (msg-1777492193718): - behavior-zero - same poll cadence - same ack contract - same inline dispatch semantics - no registry creep until step 2 - no auth/intake churn - no new verbs Gates: - typecheck: clean (npx tsc --noEmit, exit 0) - vitest: 242/242 files pass, 2608/2608 tests pass (1 pre-existing skip) - build: clean (npm run build) Step 2 follow-up (per kai: "After that, reassess before the empty registry PR") will land separately — this is step 1 only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closeout — §11e step 1 merged on 2/3 proofsPer kai (msg-1777493903222) + link (msg-1777493906404): Proofs captured on canonical staging (
Named seam (carries to step 2 reassess, not into this PR): The cloud API has no HTTP enqueue endpoint for commands. So canonical can prove clean boot + no regression, but not a naturally-queued command through the relocated drain path — because no reachable cloud enqueue path exists yet. Next reassess (per kai): decide whether "no reachable cloud enqueue path" belongs inside the next node-runtime step (§11e step 2 = empty registry + per-verb handlers), or is a separate cloud/runtime seam that stays split out. Until that call: hold per |
Summary
§11e step 1 of
COMMAND_PLUGIN_SKILL_ARCHITECTURE.mdv1.0 (reflectt-cloud merged at da6cdba3). Behavior-zero relocation of the inline command runtime that lived incloud.tsinto a dedicatedsrc/commands/drain.ts.This is step 1 only. Per @kai (msg-1777492135899): "After that, reassess before the empty registry PR." Step 2 (empty registry contract + relocate the two existing verbs into per-verb handler files) lands as a separate PR.
What moves
From
src/cloud.ts→src/commands/drain.ts, verbatim:pollAndProcessCommands()— heartbeat-tick poll, 10s active / 60s idlehandleCommand(cmd)— inline if/else dispatch bycmd.typehandleContextSync(cmd)— fetches local context, posts to cloud synchandleRunApprove(cmd)— calls local approval-queue, acksCOMMAND_POLL_ACTIVE_MS/COMMAND_POLL_IDLE_MSconstantscommandPollErrors/lastCommandPollAtcountersPendingCommandinterfacecloud.tsexportsstate,config,isIdle,cloudGet,cloudPostsodrain.tscan import them. ES live-binding semantics preserve behavior —cloud.tsstill owns thelets and reassignsconfigat line 368. The two existingpollAndProcessCommands()call sites incloud.ts(heartbeat tick + immediate kick) are unchanged.Scope locks (kai msg-1777492135899 / link msg-1777492193718)
Gates
npx tsc --noEmit— cleannpm test— 242/242 files pass, 2608/2608 tests pass (1 pre-existing skip), 31snpm run build— cleanTest plan
npm testgreen in CInpm run buildgreen in CI📬 Command polling: 10s active → 60s idlestartup log + at least one☁️ [Commands] Processing context_syncline in production logsReviewer hooks
commands/drain.tsthe right module shape? Are the cloud.ts re-exports of state/config/cloudGet/cloudPost minimal enough?🤖 Generated with Claude Code