Skip to content

Add task control plane gates#38

Merged
moose-lab merged 7 commits into
mainfrom
codex/issue-37-control-plane
Jun 9, 2026
Merged

Add task control plane gates#38
moose-lab merged 7 commits into
mainfrom
codex/issue-37-control-plane

Conversation

@moose-lab

Copy link
Copy Markdown
Owner

Summary

  • Add engine-neutral [DEVLOG_STAGE] and [DEVLOG_GATE] stdout parsing with persisted current_stage / gate_status state on tasks and sessions.
  • Add human gate pause/resume handling through a dedicated resolve_gate session action, keeping approval replies isolated from the normal send queue.
  • Surface stage and needs-input state on task cards, task details, session cards, and session headers, with global control-plane stream refresh for task/session lists.

Closes #37

Test Plan

  • git diff --check origin/main..HEAD
  • TZ=Asia/Shanghai bun run test src/core/__tests__/quality-gates.test.ts
  • bun run typecheck
  • bun run quality:ci
  • Browser smoke on http://127.0.0.1:3000/tasks and /sessions: no console errors, no horizontal overflow

Audit Notes

@claude

claude Bot commented Jun 9, 2026

Copy link
Copy Markdown

Claude finished @moose-lab's task in 2m 47s —— View job


PR #38 Review — Add task control plane gates

Findings

  • [must-fix] Duplicate parseGateStatus with diverging fallbackcontrol-plane-state.ts (exported, used by UI) and control-plane-protocol.ts (private, used internally when reading gate_status back from DB) both implement parseGateStatus independently. They differ: the state module returns created_at: "" for a missing field; the protocol module returns new Date(0).toISOString(). Any future fix to one won't propagate to the other. The UI module should import from one canonical source. control-plane-state.ts:1-39 vs control-plane-protocol.ts:212-232. Fix this →

  • [must-fix] migrateControlPlaneColumns called twice in getDb()db.ts calls migrateControlPlaneColumns(_db) at line 40 (before the idle-status migration block) and again at line 179 (after the agent column migrations). On every cold start the PRAGMA table_info queries run twice and the columns-exist check runs twice per table. More importantly it's a correctness hazard: if any future migration between those two call sites modifies schema, the second call will see different state than expected. Fix this →

  • [must-fix] resolveGate leaves isProcessing = true if writeGateResponse failsprocess-manager.ts:1267 sets sp.isProcessing = true before calling writeGateResponse. writeGateResponse has an early-return error path (stdin not writable, line 1297–1302) that logs a warning but does not reset isProcessing. The session then blocks all new messages until the 3-minute watchdog fires. The gate_status has already been cleared from DB at this point, so the gate won't reappear. The fix is to reset sp.isProcessing = false in the early-return path of writeGateResponse, or reset it in the caller on write failure. Fix this →

  • [should-fix] Gate resolve failure is silent in the UItask-detail-dialog.tsx:209 returns early when !res.ok with no error state or toast shown to the user. If the gate resolve API returns a 409 (e.g., "no pending gate" — possible if the session was killed between render and submit), the button just stops spinning with no feedback. Add an error display. Fix this →

  • [should-fix] Dual EventSource connections on pages that mount both hooksuse-tasks.ts and use-sessions.ts each independently open new EventSource("/api/devlog/stream"). On the Kanban board (/tasks), if both hooks are mounted simultaneously there are two SSE connections to the same endpoint. Each control-plane event triggers two independent refetch calls. This doubles backend load and causes brief double-render. A shared context or a single global SSE hook would fix this.

  • [nit] Global status: running emit lacks session contextprocess-manager.ts:1290 emits streamManager.emit("global", { type: "status", status: "running" }). The status event shape doesn't include session_id, so a global subscriber can't tell which session went running. The per-session emit on line 1289 is fine; the global one is ambiguous. Compare to the control_plane_gate_resolved event on line 1280 which correctly includes session_id.

  • [nit] isRecord helper defined three times — identical implementations in control-plane-protocol.ts:204, control-plane-state.ts:37, and process-manager.ts:515. Consider a shared @/core/utils export.


Overall assessment: The core architecture is clean — single-line marker parsing, safe JSON validation at boundaries, idempotent DB migrations, and proper SIGSTOP/SIGCONT gate pause are all well-done. Tests cover the protocol and state transitions thoroughly. The three must-fix items above are real defects (not just style); the rest are quality improvements.

Verdict: REQUEST_CHANGES

@moose-lab moose-lab merged commit e7a18b4 into main Jun 9, 2026
4 checks passed
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