Skip to content

feat(runtime-bridge): SW-C — aif-handoff WebSocket status read-back (getStatus + awaitDone) - #296

Merged
artyhoo merged 1 commit into
stagingfrom
feat/runtime-bridge-status-readback
May 29, 2026
Merged

feat(runtime-bridge): SW-C — aif-handoff WebSocket status read-back (getStatus + awaitDone)#296
artyhoo merged 1 commit into
stagingfrom
feat/runtime-bridge-status-readback

Conversation

@artyhoo

@artyhoo artyhoo commented May 29, 2026

Copy link
Copy Markdown
Owner

Phase 1 Sub-wave C — aif-handoff WebSocket status read-back

Umbrella: aif-handoff-runtime-bridge-iphase. Replaces SW-B's getStatus/awaitDone placeholders in AifHandoffBackend with real implementations backed by aif-handoff's REST + WebSocket surfaces. New module src/aifWsStatus.ts (WS consumer + REST snapshot helper + status-enum mapping). Scope: packages/runtime-bridge/ only.

MUST COVER (kickoff §3 SW-C, lines 206–214)

# Requirement Where
1 WS consumer to status stream aifWsStatus.awaitTaskDonews://localhost:3009/ws
2 Client-side filter by taskId-in-payload (NOT server-topic) aifWsStatus.ts if (payload.id !== taskId) return — verified against real source that broadcast() fans out to all clients (no server-side topic), so client filtering is mandatory
3 Events → state.md (§5 Handoff row) appendStateLine appends [ISO] taskId=<id> status=<status>
4 Termination: done / error / disconnect / reconnect done/verified→success; blocked_external→!success (resolves); disconnect→bounded reconnect (≤3)→unavailable
5 Paired-negative: ours→update; others→ignore; disconnect→state.md note + graceful error Tests 1/2/3 + the cold-QA fix below
6 Bridge-discipline: WS drop → awaitDone raises unavailable AifHandoffBackend.awaitDone integration test
7 §1.7 forward+backward below

Cold-QA (T19) finding — fixed in commit 2

Reviewing the SW-C commit (abb6593) against item 5, I found the state.md-notes-the-disconnection half was missing — appendStateFile only fired on status messages, never on the disconnect path (the graceful-error half was present). Commit 390aaba completes it: onclose now appends event=ws_disconnected … on both reconnect and terminal paths, with 2 added paired tests (PASS: state.md records the disconnect; NEGATIVE: a clean done does not spuriously write a disconnect event).

T16 — schema verified against real aif-handoff source (not assumed)

  • toTaskBroadcastPayload returns { id, title, status }payload.id is the taskId — packages/api/src/repositories/tasks.ts:21
  • WS endpoint /ws via upgradeWebSocketpackages/api/src/ws.ts:27
  • Broadcast frame { type, payload: toTaskBroadcastPayload(task) }, status changes use task:updatedpackages/api/src/routes/tasks.ts:419,438
  • broadcast() sends to all connected clients (no server topic filter) — packages/api/src/ws.ts:75-84 → confirms SW-A's finding; client-side filter is correct
  • TASK_STATUSES = backlog|planning|plan_ready|implementing|review|blocked_external|done|verified (no error/failed) — packages/shared/src/types.ts

§1.7 forward+backward

  • Forward-check: complies with no-paid-llm-in-ci.md §1 (zero LLM/API calls — pure WS/REST client); build-first-reuse-default.md (ADAPT, not BUILD — extends SW-B's RuntimeBackend interface verbatim and wraps aif-handoff's existing broadcast stream with a thin client-side filter; no re-implementation of the event bus); dual-implementation-discipline.md (@dual-pair: runtime-bridge-aif-handoff marker retained on AifHandoffBackend.ts:68). Testability via DI (WebSocketImpl) because vi.stubGlobal cannot intercept named imports from node:http.
  • Backward-check: additive only — no existing artefact superseded. AifHandoffConfig gains wsUrl/stateFilePath/WebSocketImpl (all optional, defaults derived); getStatus/awaitDone replace SW-B placeholders behind the unchanged RuntimeBackend contract. No rule/principle/SKILL touched (so no §1.7 commit trailer required — pre-push s17 confirms no-op).

Verification (self, human verifies nothing)

🟢 Auto-merge on green CI (staging-trunk model).

…ot silent drop)

Cold-QA (T19) of the SW-C commit found a gap against kickoff §3 SW-C item 5:
"aif-handoff disconnect → state.md notes the disconnection AND awaitDone
returns a graceful error (NOT silent drop)". The graceful-error half was
implemented (BackendError 'unavailable'); the state.md-note half was not —
appendStateFile only fired on status messages, never on the disconnect path.

Fix:
- aifWsStatus.ts: generalise appendStateFile → appendStateLine(content); the
  onclose handler now appends `event=ws_disconnected …` on both the reconnect
  path (attempts_left=N) and the terminal give-up path (terminal=true) before
  surfacing BackendError('unavailable').
- aif-status-readback.test.ts: +2 paired tests — (a) disconnect with
  stateFilePath set → state.md contains event=ws_disconnected + terminal=true
  AND awaitDone rejects unavailable (both halves of item 5); (b) NEGATIVE — a
  clean done does NOT spuriously write a ws_disconnected event.

32 tests pass (was 30). Typecheck clean.

Prior-art: skipped — completes SW-C item 5 on existing files (state.md disconnect note + paired test); no new capability, no new dependency, no new ≥80 LOC file.
@artyhoo
artyhoo enabled auto-merge (squash) May 29, 2026 23:10
@artyhoo
artyhoo merged commit 3df4dfd into staging May 29, 2026
22 checks passed
artyhoo added a commit that referenced this pull request May 31, 2026
…uthored autonomously by aif) (#318)

The "human-in-the-loop" half of the aif-question-loop: pulls every task aif parked
for human input (manualReviewRequired / blocked_external / non-empty blockedReason)
from GET /tasks and prints them in one place (--json for piping into a chat).
Sibling of dispatch.ts (#313) → await.ts (#296) → questions.ts.

Provenance: this is the FIRST file authored end-to-end by aif-handoff via the
runtime-bridge autonomous dispatch (task 048fa1d1, 2026-05-31) and brought into the
repo by the coordinator. Reviewed: pure testable functions, Superset-agnostic, env
config per sibling convention, entrypoint-guarded. Verified in this repo: 10/10
tests pass, typecheck clean.

Pure functions (isParked / selectParked / parkedReason / formatHuman / fetchTasks /
parseQuestionsArgs) + entrypoint guard so importing for tests is a no-op. Task fields
verified live against GET /tasks (2026-05-31), not assumed.

Prior-art: design docs/superpowers/specs/2026-05-31-aif-question-loop-design.md §2 reuse-vs-build table — verdict BUILD (thin) for cli/questions.ts; BFR §3 deep search deferred to SW-B per design §6; reuses runtime-bridge siblings #313/#296, no new dependency.
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