330db43b5f
Each turn (assistant response / tool result) is appended to a JSONL file at ~/.uncaged/workflow/sessions/<sessionId>.jsonl during the loop. On completion, the JSONL is read back, each turn is stored as a CAS node, and the detail payload references them as a flat turns[] array in chronological order. The session file is then deleted. Benefits: - Real-time observability: tail -f the JSONL to watch loop progress - Crash recovery: partial JSONL survives process death - Zero write contention: one file per session - Detail stays a flat array for easy consumption by CLI/dashboard Changes: - New session.ts: initSessionDir, appendSessionTurn, readSessionTurns, removeSession - loop.ts: append JSONL each turn instead of accumulating in-memory - detail.ts: reads session JSONL → persists turns to CAS → stores detail - agent.ts: passes storageRoot/sessionId to loop, cleans up session on completion - types.ts: remove index from TurnPayload (order is implicit in JSONL/array) - schemas.ts: sync with type changes Ref: #433
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { bootstrap, putSchema, type Store } from "@uncaged/json-cas";
|
|
|
|
import { BUILTIN_DETAIL_SCHEMA, BUILTIN_TURN_SCHEMA } from "./schemas.js";
|
|
import { readSessionTurns } from "./session.js";
|
|
import type { BuiltinDetailPayload } from "./types.js";
|
|
|
|
type BuiltinSchemaHashes = {
|
|
turn: string;
|
|
detail: string;
|
|
};
|
|
|
|
export async function registerBuiltinSchemas(store: Store): Promise<BuiltinSchemaHashes> {
|
|
await bootstrap(store);
|
|
const [turn, detail] = await Promise.all([
|
|
putSchema(store, BUILTIN_TURN_SCHEMA),
|
|
putSchema(store, BUILTIN_DETAIL_SCHEMA),
|
|
]);
|
|
return { turn, detail };
|
|
}
|
|
|
|
/** Read session jsonl, persist each turn to CAS, return detail hash. */
|
|
export async function storeBuiltinDetail(
|
|
store: Store,
|
|
storageRoot: string,
|
|
sessionId: string,
|
|
model: string,
|
|
startedAtMs: number,
|
|
nowMs: number = Date.now(),
|
|
): Promise<{ detailHash: string; turnCount: number }> {
|
|
const schemas = await registerBuiltinSchemas(store);
|
|
const turns = await readSessionTurns(storageRoot, sessionId);
|
|
|
|
const turnHashes: string[] = [];
|
|
for (const turn of turns) {
|
|
const hash = await store.put(schemas.turn, turn);
|
|
turnHashes.push(hash);
|
|
}
|
|
|
|
const duration = Math.max(0, nowMs - startedAtMs);
|
|
const detail: BuiltinDetailPayload = {
|
|
sessionId,
|
|
model,
|
|
duration,
|
|
turnCount: turnHashes.length,
|
|
turns: turnHashes,
|
|
};
|
|
const detailHash = await store.put(schemas.detail, detail);
|
|
return { detailHash, turnCount: turnHashes.length };
|
|
}
|