feat(kanban): resume runs' chat via Refine + isolate the event stream

Two intertwined changes that both touch the orchestrator hook + run console:

Isolate the agent event stream (perf):
- useRunStream owns the SSE stream + event log locally inside AgentRunBar, so a
  burst of streamed events re-renders only the console — never the board page or
  card modal (which was causing frame drops at run start).
- useOrchestrator is now a registry only; lifecycle events reflect back up via
  stable patchRun/reflectBevy reflectors (effect deps depend on those, not the
  whole object, avoiding a stream-teardown loop).

Session resume for Refine:
- Runs now persist their pi session (drop --no-session); each fresh run captures
  its session JSONL path into a new agent_runs.session_file column (additive,
  idempotent migration).
- Refine resumes the prior run's actual session (--session <path> → appends) in
  that run's own worktree (inherited, never owned), sending the operator's
  feedback as the next message in the same conversation with full prior context.
- owns_worktree guards remove()/cleanup so a refinement never destroys the
  owning run's worktree; bad refinement targets return 409.
- AgentRunBar shows Refine only for settled runs with a recorded session.
EOF && echo "" && git log --oneline -3
This commit is contained in:
2026-06-17 18:34:05 -04:00
parent 407bc4f790
commit 6531dc00df
10 changed files with 391 additions and 182 deletions

View File

@@ -58,13 +58,16 @@ orchestrator.post('/runs', async (c) => {
const prompt = typeof body.prompt === 'string' ? body.prompt : undefined;
const useWorktree = body.useWorktree !== false; // default true
const cleanupOnFinish = body.cleanupOnFinish === true;
const refineRunId = typeof body.refineRunId === 'string' ? body.refineRunId : undefined;
try {
const run = runManager.start(card, { cardId, prompt, useWorktree, cleanupOnFinish });
const run = runManager.start(card, { cardId, prompt, useWorktree, cleanupOnFinish, refineRunId });
return c.json({ run }, 201);
} catch (err) {
const message = err instanceof Error ? err.message : 'failed to start run';
return c.json({ error: message }, 500);
// A bad refinement target (missing run / cleaned-up worktree) is a client error.
const status = message.startsWith('refinement') ? 409 : 500;
return c.json({ error: message }, status);
}
});