mirror of
https://github.com/rowboatlabs/rowboat.git
synced 2026-07-12 21:02:17 +02:00
feat(migration): port legacy runs into the turn/session runtime at startup
One-time startup migration that scans ~/.rowboat/runs/*.jsonl and converts
each legacy run into the new event-sourced runtime, so existing chats and
agent history are visible and continuable after the runtime rewrite.
Mapping (a run is one whole conversation; turn boundaries are user messages):
- copilot_chat run -> 1 session (sessionId = original runId) + one turn per
user message (turn ids <runId>-tNNN).
- every other run -> a single standalone turn whose id IS the original runId,
so live-note (lastRunId) and background-task (runs.log)
history views resolve it via sessions:getTurn with no
renderer change.
- code_session runs are skipped (Code mode still uses the old runtime).
- convert.ts: pure convertRun(); synthesizes a reduceTurn/reduceSession-legal
event log (exact request refs, permission replay, denials -> runtime isError
results, reasoning parts preserved) and validates via the reducers before
returning.
- migrate.ts: defensive IO runner. Successful runs are moved to runs-archive/
(that move is the idempotency guard); failures are left in place (still
served by the runs:fetch fallback) and retried next launch. Per-run
try/catch never blocks boot. Writes config/runs-migration.json.
- main.ts: runs before sessions.initialize() and logs an [runs-migration]
summary (N turns across M sessions, skipped/failed counts).
- Tests: 13 cases over 4 real (redacted) run fixtures — conversion, session
chaining, deny->error, reasoning preservation, transcript fidelity,
archive-on-success, skip, quarantine, idempotency, and read-back through the
real FSTurnRepo/FSSessionRepo.
Dry-run on real data: 19 runs -> 3 sessions + 21 turns, 0 failures.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a8420f99ce
commit
17f18a35a1
9 changed files with 1096 additions and 0 deletions
|
|
@ -36,6 +36,7 @@ import { backgroundTaskEventConsumer } from "@x/core/dist/background-tasks/event
|
|||
import { init as initLocalSites, shutdown as shutdownLocalSites } from "@x/core/dist/local-sites/server.js";
|
||||
import { shutdown as shutdownAnalytics } from "@x/core/dist/analytics/posthog.js";
|
||||
import { identifyIfSignedIn } from "@x/core/dist/analytics/identify.js";
|
||||
import { migrateRuns } from "@x/core/dist/migrations/runs/migrate.js";
|
||||
|
||||
import { initConfigs } from "@x/core/dist/config/initConfigs.js";
|
||||
import { getAgentSlackCliStatus } from "@x/core/dist/slack/agent-slack-exec.js";
|
||||
|
|
@ -389,6 +390,25 @@ app.whenReady().then(async () => {
|
|||
// start runs watcher
|
||||
startRunsWatcher();
|
||||
|
||||
// One-time: port legacy runs/*.jsonl into the new turn/session runtime.
|
||||
// Must run BEFORE the session index is built so migrated sessions are picked
|
||||
// up by the startup scan. Fully defensive — never blocks boot.
|
||||
try {
|
||||
const migration = migrateRuns();
|
||||
if (migration.scanned > 0) {
|
||||
console.log(
|
||||
`[runs-migration] migrated ${migration.migratedTurns} turn(s) across ` +
|
||||
`${migration.migratedSessions} session(s) from ${migration.scanned} run(s) ` +
|
||||
`(${migration.skipped} skipped, ${migration.failed.length} failed)`,
|
||||
);
|
||||
for (const failure of migration.failed) {
|
||||
console.warn(`[runs-migration] left in place (failed): ${failure.file} — ${failure.error}`);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[runs-migration] pass failed:', error);
|
||||
}
|
||||
|
||||
// New runtime: build the in-memory session index (startup scan) before the
|
||||
// renderer can list sessions, then forward the session bus to windows.
|
||||
await container.resolve<ISessions>('sessions').initialize();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue