|
|
|
@@ -57,13 +57,13 @@
|
|
|
|
|
- failure path: `ClassifyError(err, phaseID, requestID, sessionID, workspaceID)` with workspaceID from `result.WorkDir` (soft failure) / `req.WorkDir` (resume) / `""` (hard failure)
|
|
|
|
|
- `X-Request-Id` response header + `stream.WithRequestID`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Extend DonePayload + EmitDoneWithWorkspace with tests**
|
|
|
|
|
- [x] **Step 1: Extend DonePayload + EmitDoneWithWorkspace with tests**
|
|
|
|
|
|
|
|
|
|
`internal/stream/types.go`: add `WorkspaceID string \`json:"workspace_id,omitempty"\`` to `DonePayload`.
|
|
|
|
|
`internal/stream/stream.go`: add `EmitDoneWithWorkspace(sessionID, workspaceID string, duration time.Duration)` mirroring `EmitDone` (keep `EmitDone` untouched).
|
|
|
|
|
`internal/stream/stream_test.go`: assert `workspace_id` present/omitted in marshaled done events.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Rework `handleRunTaskStream` in `internal/server/sse.go`**
|
|
|
|
|
- [x] **Step 2: Rework `handleRunTaskStream` in `internal/server/sse.go`**
|
|
|
|
|
|
|
|
|
|
1. Set `X-Request-Id` (same pattern as `handleChatStream`) before SSE headers; use `stream.WithRequestID(r.Context(), requestID)`.
|
|
|
|
|
2. Decode into `taskStreamRequest` (embedded DTO keeps existing JSON contract).
|
|
|
|
@@ -71,7 +71,7 @@
|
|
|
|
|
4. Existing stage/task branches: capture result; on error choose workspaceID = `result.WorkDir` (soft failure, result != nil) else `""`; emit `EmitErrorInfo(ClassifyError(err, lastPhaseID, requestID, "", wsID))`; on success emit `EmitDoneWithWorkspace("task_run"/"stage_run", result.WorkDir, time.Since(start))`.
|
|
|
|
|
5. Build must stay green: `go build ./... && go vet ./internal/server ./internal/stream`.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
|
- [x] **Step 3: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add internal/stream/types.go internal/stream/stream.go internal/stream/stream_test.go internal/server/sse.go
|
|
|
|
@@ -89,19 +89,19 @@ git commit -m "feat(api): task stream request-id, resume mode and workspace-awar
|
|
|
|
|
- Consumes: `httptest` server with `Server` wired to a stub `BizdevService` (or invalid backend), `handleRunTaskStream`
|
|
|
|
|
- Produces: SSE-level assertions on `event: error` / `error_info` / `done.workspace_id` / `X-Request-Id`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Transient injection test (test-report follow-up #2)**
|
|
|
|
|
- [x] **Step 1: Transient injection test (test-report follow-up #2)**
|
|
|
|
|
|
|
|
|
|
Point `Backend` at an unreachable port or a stub service returning a dial error. Assert the SSE body contains `event: error` with `error_info.class` == `transient` and `actions` containing `retry`, plus `X-Request-Id` header non-empty.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Soft-failure classification test**
|
|
|
|
|
- [x] **Step 2: Soft-failure classification test**
|
|
|
|
|
|
|
|
|
|
Stub service returns a result with `WorkDir = "/tmp/ws-x"` and `Success=false` plus an error (or `ErrorReason`). Assert `error_info.class == "resumable"`, `workspace_id == "/tmp/ws-x"`, `actions` == `[continue, restart]`.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Resume branch test**
|
|
|
|
|
- [x] **Step 3: Resume branch test**
|
|
|
|
|
|
|
|
|
|
POST body with `work_dir` set to a stub workspace dir; assert the stub `ResumeOrModifyWorkspace` receives the request and the stream ends with `done` carrying `workspace_id`.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Run + Commit**
|
|
|
|
|
- [x] **Step 4: Run + Commit**
|
|
|
|
|
|
|
|
|
|
Run: `go test ./internal/server ./internal/stream -count=1` (record known env-related failures if any appear).
|
|
|
|
|
Commit:
|
|
|
|
@@ -126,15 +126,15 @@ git commit -m "test(server): task stream transient injection, classification and
|
|
|
|
|
- `buildTaskRequest(task: TaskRunContext): Record<string, unknown>` and `buildResumeRequest(task, workDir)` (adds `work_dir`)
|
|
|
|
|
- `applyPhaseEvent(strip: PhaseEvent[], phase?: {phase_id?; message?; step_index?; total_steps?; progress?}): PhaseEvent[]`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Mapper with tests (icon table aligned with CLI `bizdev-cli/main.go:516-534`)**
|
|
|
|
|
- [x] **Step 1: Mapper with tests (icon table aligned with CLI `bizdev-cli/main.go:516-534`)**
|
|
|
|
|
|
|
|
|
|
Cover: unknown phase default ⏳; new phase appends `running` and completes predecessors; existing phase updates label in place; `step_completed`/`stage_completed`/`task_failed` close the running entry; label suffix `第x/y步` when step_index/total_steps valid, or `z%` when progress > 0. Run `node --test` per repo convention.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Client with tests**
|
|
|
|
|
- [x] **Step 2: Client with tests**
|
|
|
|
|
|
|
|
|
|
Reuse `parseSSEChunk`; tests assert `buildTaskRequest` includes five project fields + `task_key` + `user_id`, and `buildResumeRequest` adds `work_dir`.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Commit**
|
|
|
|
|
- [x] **Step 3: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add web/src/api/taskStream.ts web/src/api/taskStream.test.ts web/src/engine/phaseEventMapper.ts web/src/engine/phaseEventMapper.test.ts
|
|
|
|
@@ -153,15 +153,15 @@ git commit -m "feat(web): add task SSE client and live phase-event mapper"
|
|
|
|
|
- Consumes: `streamTask`, `buildTaskRequest`, `buildResumeRequest`, `applyPhaseEvent`
|
|
|
|
|
- Produces: `runTaskAttempt(task, asstId, mode: 'run' | 'resume' | 'restart', attemptIndex)`; template-keyword messages hit real SSE; `handleErrorAction` supports `retry` (chat+task), `continue` (resume via `errorInfo.workspaceId`), `restart` (confirm → fresh run)
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Types** — `ChatMessage.taskRequest?: TaskRunContext` with `{ taskKey; projectName; customerName; deviceType; intendedUse; targetMarket; userId?; projectId? }`; `ChatMessage.taskWorkspaceId?: string`.
|
|
|
|
|
- [x] **Step 1: Types** — `ChatMessage.taskRequest?: TaskRunContext` with `{ taskKey; projectName; customerName; deviceType; intendedUse; targetMarket; userId?; projectId? }`; `ChatMessage.taskWorkspaceId?: string`.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: `runTaskAttempt`** — mirror `runStreamAttempt`: abort-previous-controller; consume `phase`/`status` → `livePhaseLabel` + `phaseEvents` via `applyPhaseEvent`; `delta` → content; `deliverable` → append `📄 已生成文档:<title>` line; `error` → `failOrRetry(mapErrorInfo(ev))`; `done` → `completed` + persist `done.workspace_id` into `taskWorkspaceId`; network catch → `buildNetworkErrorInfo` (transient).
|
|
|
|
|
- [x] **Step 2: `runTaskAttempt`** — mirror `runStreamAttempt`: abort-previous-controller; consume `phase`/`status` → `livePhaseLabel` + `phaseEvents` via `applyPhaseEvent`; `delta` → content; `deliverable` → append `📄 已生成文档:<title>` line; `error` → `failOrRetry(mapErrorInfo(ev))`; `done` → `completed` + persist `done.workspace_id` into `taskWorkspaceId`; network catch → `buildNetworkErrorInfo` (transient).
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 3: Trigger switch** — in `handleSendMessage`, when `matchedTemplate` is truthy (and not a DocGov shortcut), insert user + placeholder with `taskRequest` and call `runTaskAttempt`; remove the mock template-fill branch body (DocGov shortcuts and `isGenDocShortcut` mock stay).
|
|
|
|
|
- [x] **Step 3: Trigger switch** — in `handleSendMessage`, when `matchedTemplate` is truthy (and not a DocGov shortcut), insert user + placeholder with `taskRequest` and call `runTaskAttempt`; remove the mock template-fill branch body (DocGov shortcuts and `isGenDocShortcut` mock stay).
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 4: Open continue/restart** — delete the `info.actions.filter((a) => a === 'retry')` line in `failOrRetry`; extend `handleErrorAction`: `retry` → chat `runStreamAttempt` / task `runTaskAttempt(...,'run',0)`; `continue` (require `errorInfo.workspaceId`) → `runTaskAttempt(...,'resume',0)` keeping `phaseEvents`, label「正在继续任务…」; `restart` → `window.confirm` then `runTaskAttempt(...,'restart',0)` with content/phaseEvents reset.
|
|
|
|
|
- [x] **Step 4: Open continue/restart** — delete the `info.actions.filter((a) => a === 'retry')` line in `failOrRetry`; extend `handleErrorAction`: `retry` → chat `runStreamAttempt` / task `runTaskAttempt(...,'run',0)`; `continue` (require `errorInfo.workspaceId`) → `runTaskAttempt(...,'resume',0)` keeping `phaseEvents`, label「正在继续任务…」; `restart` → `window.confirm` then `runTaskAttempt(...,'restart',0)` with content/phaseEvents reset.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 5: Build + Commit** — `cd web && npm run build` → SUCCESS.
|
|
|
|
|
- [x] **Step 5: Build + Commit** — `cd web && npm run build` → SUCCESS.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add web/src/types/workbench.ts web/src/App.tsx
|
|
|
|
@@ -175,9 +175,9 @@ git commit -m "feat(web): drive long tasks from SSE with live phases and continu
|
|
|
|
|
**Files:**
|
|
|
|
|
- Modify: `web/src/components/chat/MessageBubble.tsx`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Styling + copy** — distinguish `restart` (secondary/danger outline) vs `retry`/`continue` (primary); cancelled task bubbles read「已停止任务(可重新开始)」; keep P1 XSS-safe rendering untouched.
|
|
|
|
|
- [x] **Step 1: Styling + copy** — distinguish `restart` (secondary/danger outline) vs `retry`/`continue` (primary); cancelled task bubbles read「已停止任务(可重新开始)」; keep P1 XSS-safe rendering untouched.
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 2: Build + Commit** — `cd web && npm run build && npm test` → SUCCESS.
|
|
|
|
|
- [x] **Step 2: Build + Commit** — `cd web && npm run build && npm test` → SUCCESS.
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add web/src/components/chat/MessageBubble.tsx
|
|
|
|
@@ -191,12 +191,12 @@ git commit -m "fix(web): distinguish recovery action styles for long-task failur
|
|
|
|
|
**Files:**
|
|
|
|
|
- Create: `.doc/test-reports/2026-09-09-longtask-phase-live-resume-ux-test-report.md`
|
|
|
|
|
|
|
|
|
|
- [ ] **Step 1: Ensure dev stack up** (agent-dev `:8080`, pi-agent healthy, vite proxy).
|
|
|
|
|
- [ ] **Step 2: Case A** — template message (命中 feasibility_reg 等): ≤1s wait bubble, PhaseEventStrip lights up with real phases, real reply; capture SSE event types with timestamps.
|
|
|
|
|
- [ ] **Step 3: Case B** — interrupt (stop pi-agent container or CDP block) mid-run: `TASK_INTERRUPTED` resumable bubble with 继续任务/重新开始 + diag line; restore service → 继续任务 resumes the same workspace to completion; restart creates a fresh workspace.
|
|
|
|
|
- [ ] **Step 4: Report** per `.cursor/rules/test-reports-standard.mdc` fixed sections 一–七, PASS/FAIL per case.
|
|
|
|
|
- [ ] **Step 5: Regression** — `go test ./... -count=1` and `cd web && npm test`; document known unrelated failures.
|
|
|
|
|
- [ ] **Step 6: Commit**
|
|
|
|
|
- [x] **Step 1: Ensure dev stack up** (agent-dev `:8080`, pi-agent healthy, vite proxy).
|
|
|
|
|
- [x] **Step 2: Case A** — template message (命中 feasibility_reg 等): ≤1s wait bubble, PhaseEventStrip lights up with real phases, real reply; capture SSE event types with timestamps.
|
|
|
|
|
- [x] **Step 3: Case B** — interrupt (stop pi-agent container or CDP block) mid-run: `TASK_INTERRUPTED` resumable bubble with 继续任务/重新开始 + diag line; restore service → 继续任务 resumes the same workspace to completion; restart creates a fresh workspace.
|
|
|
|
|
- [x] **Step 4: Report** per `.cursor/rules/test-reports-standard.mdc` fixed sections 一–七, PASS/FAIL per case.
|
|
|
|
|
- [x] **Step 5: Regression** — `go test ./... -count=1` and `cd web && npm test`; document known unrelated failures.
|
|
|
|
|
- [x] **Step 6: Commit**
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
git add .doc/test-reports/2026-09-09-longtask-phase-live-resume-ux-test-report.md
|
|
|
|
|