# Unified Runtime Streaming Implementation Plan

> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.

**Goal:** Make Skill create, evaluate, and run stream real runtime events and durable artifacts with the same behavior across local Codex, QODER Cloud Agent, AgentRun, the web UI, and existing API consumers.

**Architecture:** Runtime-specific adapters translate native events into one persisted SSE event protocol. A shared frontend consumer and timeline render the same session, progress, reasoning-summary, text, tool, artifact, and terminal events for all three modes. Provider session IDs and artifact manifests are stored with the AIPROS session so multi-turn continuation and reconnects do not depend on the entry surface.

**Tech Stack:** Node.js, Hono SSE, child-process streaming, Codex JSONL, QODER Cloud Agent SSE, React 18, Node test runner, Vite.

---

### Task 1: Define and test the unified runtime event mapping

**Files:**
- Create: `cf-worker/src/runtime/runtime-events.js`
- Create: `cf-worker/src/runtime/runtime-events.test.js`

**Step 1: Write the failing tests**

Cover these behaviors with real event objects:

```js
test('maps Codex agent messages and tool items without waiting for process close', () => {
  assert.deepEqual(mapCodexJsonEvent({
    type: 'item.completed',
    item: { type: 'agent_message', text: '正在分析' },
  }), [{ t: 'delta', v: '正在分析' }])
})

test('maps provider-visible reasoning only to reasoning_summary', () => {
  assert.deepEqual(mapCodexJsonEvent({
    type: 'item.completed',
    item: { type: 'reasoning', text: '先检查输入材料' },
  }), [{ t: 'reasoning_summary', v: '先检查输入材料' }])
})

test('maps QODER status, tool and agent message events', () => {
  assert.deepEqual(mapQoderCloudEvent({
    event: 'agent.message',
    data: { content: [{ type: 'text', text: '已完成' }] },
  }), [{ t: 'delta', v: '已完成' }])
})
```

Also test malformed JSON, unknown provider events, empty content, tool arguments, status updates, and file/artifact metadata.

**Step 2: Run tests and verify RED**

Run:

```bash
node --test cf-worker/src/runtime/runtime-events.test.js
```

Expected: FAIL because `runtime-events.js` does not exist.

**Step 3: Implement the minimal pure mappers**

Export:

```js
export function parseJsonLine(line) {}
export function mapCodexJsonEvent(event) {}
export function mapQoderCloudEvent(event) {}
export function normalizeArtifact(input, context = {}) {}
```

Unknown provider events may produce a concise `status` event when they contain a user-visible lifecycle state; they must never be copied into `reasoning_summary` by guessing.

**Step 4: Run focused tests and verify GREEN**

Run the same command. Expected: PASS.

**Step 5: Commit**

```bash
git add cf-worker/src/runtime/runtime-events.js cf-worker/src/runtime/runtime-events.test.js
git commit -m "feat: normalize runtime stream events"
```

### Task 2: Stream local CLI output while the child process is running

**Files:**
- Modify: `cf-worker/src/runtime/runtime-registry.js`
- Create: `cf-worker/src/runtime/local-runtime-stream.test.js`
- Modify: `scripts/runtime-presets.mjs`
- Modify: `scripts/runtime-presets.test.mjs`

**Step 1: Write the failing tests**

Use a temporary Node child that writes two JSONL events with a short delay. Assert the first event is yielded before the child closes and the second follows in order. Add a plain-text CLI case that yields stdout chunks as `delta` and stderr chunks as `warning`.

Test Codex invocation normalization:

```js
assert.deepEqual(
  prepareLocalRuntimeArgs({ command: 'codex', args: ['exec', '-'] }),
  ['exec', '--json', '-'],
)
```

**Step 2: Run tests and verify RED**

```bash
node --test cf-worker/src/runtime/local-runtime-stream.test.js scripts/runtime-presets.test.mjs
```

Expected: FAIL because only buffered `runLocalRuntime` exists and Codex preset has no `--json`.

**Step 3: Implement `streamLocalRuntime`**

Add an async generator:

```js
export async function* streamLocalRuntime({ runtimeRow, prompt, workdir, signal, timeoutMs }) {
  // spawn once; parse stdout as it arrives; yield normalized events
}
```

Requirements:

- Add `--json` exactly once for Codex invocations.
- Parse complete JSONL lines incrementally and flush the final partial line.
- Yield `delta`, `reasoning_summary`, `tool_call`, `tool_result`, `status`, and `warning` while the process is alive.
- Kill the child on abort/timeout and preserve the current error behavior.
- Keep `runLocalRuntime` as a compatibility collector for `cf-worker/src/routes/invoke.js`.

Remove the local prompt sentence that requires final-only output. Replace it with:

```text
根据对话直接工作。信息不足时可以先追问；信息足够时继续执行。可按需生成文件到当前工作目录。
```

**Step 4: Run focused tests and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 5: Commit**

```bash
git add cf-worker/src/runtime/runtime-registry.js cf-worker/src/runtime/local-runtime-stream.test.js scripts/runtime-presets.mjs scripts/runtime-presets.test.mjs
git commit -m "feat: stream local runtime events"
```

### Task 3: Feed live local events into the persisted Skill run job

**Files:**
- Modify: `cf-worker/src/routes/skill-run.js`
- Create: `cf-worker/src/routes/skill-run-local-stream.test.js`

**Step 1: Write the failing integration test**

Start a local runtime fixture that emits two events before exit. Consume `/api/skill-run/stream` and assert event order is:

```text
session -> status -> first delta -> second delta -> artifact/files -> done
```

The test must assert the first delta timestamp precedes child close and that the route does not run the current post-close `assistantText.match(...800...)` loop.

**Step 2: Run test and verify RED**

```bash
node --test cf-worker/src/routes/skill-run-local-stream.test.js
```

Expected: FAIL because the local branch currently waits for `runLocalRuntime()` to resolve.

**Step 3: Consume `streamLocalRuntime` in `createSkillRunJob`**

Append `delta` content to `assistantText`, count real tool events, and call `job.emit(evt)` immediately for every native event. Preserve session event persistence and reconnect replay.

**Step 4: Run focused test and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 5: Commit**

```bash
git add cf-worker/src/routes/skill-run.js cf-worker/src/routes/skill-run-local-stream.test.js
git commit -m "feat: persist live local runtime events"
```

### Task 4: Reuse QODER Cloud sessions and forward its native event stream

**Files:**
- Modify: `cf-worker/src/runtime/qoder-cloud-agent.js`
- Modify: `cf-worker/src/runtime/qoder-cloud-agent.test.js`
- Modify: `cf-worker/src/routes/skill-run.js`
- Modify: `cf-worker/src/routes/skill-run-session.test.js`

**Step 1: Write the failing tests**

Add tests proving:

- First turn creates a QODER session and emits `provider_session` with its ID.
- Second turn with `providerSessionId` sends directly to the existing session and does not POST `/sessions`.
- SSE event IDs and turn IDs are preserved.
- `agent.message`, status, tool, user-visible reasoning-summary, and file events are normalized in arrival order.
- Historical replay does not terminate the current turn.

**Step 2: Run tests and verify RED**

```bash
node --test cf-worker/src/runtime/qoder-cloud-agent.test.js cf-worker/src/routes/skill-run-session.test.js
```

Expected: FAIL because every turn creates a new QODER session and only `agent.message`/terminal events are handled.

**Step 3: Implement provider session persistence**

Extend the session state with:

```js
{
  providerSession: { provider: 'qoder-cloud-agent', id: '...', lastEventId: '...', turnId: '...' }
}
```

Pass it into `runQoderCloudAgent`. Create only when absent; otherwise reuse the stored Cloud Session. Persist provider session updates through `job.emit` and `.skill-run-state.json`.

**Step 4: Forward native QODER events**

Use `mapQoderCloudEvent` and yield mapped events immediately. Keep raw provider event name/ID only as metadata. Do not render unknown payloads as hidden reasoning.

**Step 5: Run focused tests and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 6: Commit**

```bash
git add cf-worker/src/runtime/qoder-cloud-agent.js cf-worker/src/runtime/qoder-cloud-agent.test.js cf-worker/src/routes/skill-run.js cf-worker/src/routes/skill-run-session.test.js
git commit -m "feat: resume qoder cloud sessions"
```

### Task 5: Guarantee durable artifacts for create, evaluate, and run

**Files:**
- Modify: `cf-worker/src/routes/skill-run.js`
- Create: `cf-worker/src/routes/skill-run-artifacts.test.js`
- Modify: `src/services/generatedPackageFiles.js`
- Modify: `src/services/generatedPackageFiles.test.js`

**Step 1: Write the failing tests**

For each mode, assert that a text-only runtime result produces one durable fallback artifact:

```text
create   -> CREATE_RESULT.md
evaluate -> EVALUATION_REPORT.md
run      -> RUN_RESULT.md
```

When real runtime files exist, assert they remain in the manifest and the fallback file does not replace them. Assert artifacts survive reconnect and session reload.

**Step 2: Run tests and verify RED**

```bash
node --test cf-worker/src/routes/skill-run-artifacts.test.js src/services/generatedPackageFiles.test.js
```

Expected: FAIL because text-only results have no file artifact and only `files` is exposed.

**Step 3: Implement artifact manifest creation**

At successful completion:

- List all generated session files.
- If the list has no user artifact and `assistantText` exists, write the mode-specific Markdown result.
- Emit both backward-compatible `files` and normalized `artifact` events.
- Store artifact metadata in session state without embedding file bytes.

**Step 4: Run focused tests and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 5: Commit**

```bash
git add cf-worker/src/routes/skill-run.js cf-worker/src/routes/skill-run-artifacts.test.js src/services/generatedPackageFiles.js src/services/generatedPackageFiles.test.js
git commit -m "feat: persist runtime artifacts for every mode"
```

### Task 6: Extend the shared SSE consumer and timeline reducer

**Files:**
- Modify: `src/services/skillRunStream.js`
- Modify: `src/services/skillRunStream.test.js`
- Create: `src/services/runtimeTimeline.js`
- Create: `src/services/runtimeTimeline.test.js`

**Step 1: Write the failing tests**

Assert dispatch for `status`, `reasoning_summary`, `warning`, `artifact`, provider session metadata, heartbeat, and done. Add reducer tests ensuring:

- Delta chunks coalesce into the current assistant block.
- Tool calls/results and reasoning summaries keep arrival order.
- Heartbeats update elapsed/liveness but never clear the last status.
- Replayed event IDs do not duplicate timeline entries.

**Step 2: Run tests and verify RED**

```bash
node --test src/services/skillRunStream.test.js src/services/runtimeTimeline.test.js
```

Expected: FAIL because those event types and reducer do not exist.

**Step 3: Implement handlers and reducer**

Add handlers:

```js
onStatus(event)
onReasoningSummary(event)
onWarning(event)
onArtifact(event)
onProviderSession(event)
```

Keep `onEvent` and existing handlers backward-compatible.

**Step 4: Run focused tests and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 5: Commit**

```bash
git add src/services/skillRunStream.js src/services/skillRunStream.test.js src/services/runtimeTimeline.js src/services/runtimeTimeline.test.js
git commit -m "feat: consume unified runtime timeline events"
```

### Task 7: Render one runtime timeline in create, evaluate, and run

**Files:**
- Create: `src/components/RuntimeTimeline.jsx`
- Modify: `src/components/AgentEventLine.jsx`
- Modify: `src/pages/CreateSkillChat.jsx`
- Modify: `src/components/EvaluationPanel.jsx`
- Modify: `src/pages/SkillSandboxPage.jsx`
- Modify: `src/services/createSkillChatRouting.test.js`
- Modify: `src/services/systemSkillsUi.test.js`

**Step 1: Write the failing source and reducer tests**

Assert all three surfaces import `RuntimeTimeline` and route the shared handler set into it. Assert heartbeats do not call `setStreamTip('')` or `setStreamError('')`.

Assert create mode keeps the input enabled after a completed turn and does not automatically insert a blocking publish card into every assistant turn.

**Step 2: Run tests and verify RED**

```bash
node --test src/services/createSkillChatRouting.test.js src/services/systemSkillsUi.test.js src/services/runtimeTimeline.test.js
```

Expected: FAIL because each surface owns a divergent event renderer.

**Step 3: Implement `RuntimeTimeline`**

Render:

- Current status and elapsed seconds.
- Collapsible provider-visible reasoning summaries.
- Streaming assistant text with cursor.
- Tool call/results.
- Artifacts with preview/download.
- Warnings/errors and reconnect state.

Throttle visual text updates to 33-100ms while retaining every source chunk in the reducer.

**Step 4: Integrate all three modes**

Use the same timeline/event reducer in create, evaluate, and run. Preserve mode-specific actions such as publish, adopt, repack, and retry outside the timeline.

Update creator prompts and welcome copy so the runtime may ask follow-up questions. The latest completed create result offers one explicit “一键创建并立即发布 Skill” action; the platform does not make a quality decision or insert a clarification harness.

**Step 5: Run focused tests and verify GREEN**

Run the Step 2 command. Expected: PASS.

**Step 6: Commit**

```bash
git add src/components/RuntimeTimeline.jsx src/components/AgentEventLine.jsx src/pages/CreateSkillChat.jsx src/components/EvaluationPanel.jsx src/pages/SkillSandboxPage.jsx src/services/createSkillChatRouting.test.js src/services/systemSkillsUi.test.js
git commit -m "feat: unify runtime streaming UI"
```

### Task 8: Verify API compatibility and full automated regression

**Files:**
- Modify: `cf-worker/src/routes/invoke.js`
- Create: `cf-worker/src/routes/invoke-stream-compat.test.js`
- Modify: `src/pages/Architecture.jsx`

**Step 1: Write compatibility tests**

Assert existing invoke consumers still receive their expected final response while `/skill-run/stream` consumers receive the new event protocol. Assert create/evaluate/run request bodies and auth behavior are unchanged.

**Step 2: Run compatibility test and verify RED/GREEN**

```bash
node --test cf-worker/src/routes/invoke-stream-compat.test.js
```

Implement only the compatibility collector or adapter required by the failing assertions.

**Step 3: Update architecture documentation**

Document native runtime event adapters, provider-session continuation, artifact manifests, and shared frontend timeline.

**Step 4: Run the full suite and build**

```bash
node --test src/services/*.test.js scripts/*.test.mjs cf-worker/src/routes/skill-run-*.test.js cf-worker/src/routes/invoke-stream-compat.test.js cf-worker/src/runtime/*.test.js
VITE_API_BASE=/api VITE_PUBLIC_BASE_URL=http://localhost:4177 npm run build
git diff --check
```

Expected: all tests PASS, production build succeeds, and diff check is clean.

**Step 5: Commit**

```bash
git add cf-worker/src/routes/invoke.js cf-worker/src/routes/invoke-stream-compat.test.js src/pages/Architecture.jsx
git commit -m "docs: document unified runtime streaming"
```

### Task 9: Perform six real Chrome acceptance runs

**Files:**
- No new production files expected.

**Step 1: Start the production build on port 4177**

Keep the server attached and verify `/api/skill-run` reports a configured engine.

**Step 2: Test local Codex create/evaluate/run**

For each mode, use Chrome rather than direct API calls. Verify before completion:

- A visible native status or text event appears.
- Elapsed time continues updating during silence.
- A follow-up answer continues the same AIPROS/Codex session when the runtime asks a question.
- At least one artifact can be previewed or downloaded.

**Step 3: Test QODER Cloud Agent create/evaluate/run**

Repeat the same browser checks. Verify the second turn reuses the same QODER Cloud session ID and no duplicate historical terminal event appears.

**Step 4: Verify reconnect**

Reload or briefly interrupt one active session, reconnect, and confirm missing events replay once in order without losing artifacts.

**Step 5: Leave Chrome on the final deliverable page**

Keep the final successful timeline/artifact view open for the user.

