Skip to content
38 linesCodeBlameRaw
1/**
2 * A session's transcript (`agent_session_events`): one statement per entry,
3 * appended in order. Here on its own so that whatever writes an entry
4 * (sessions, cards, the computer) needs nothing else from sessions.ts.
5 */
6import type { AgentComputerCommand, SessionEvent } from "@g1t/contracts";
7
8const iso = () => new Date().toISOString();
9
10/** Appends to a session's transcript. */
11export function eventStatement(db: D1Database, id: string, kind: SessionEvent["kind"], by: string | null, body: string, tool: string | null = null, outcome: string | null = null): D1PreparedStatement {
12 return db
13 .prepare(
14 `INSERT INTO agent_session_events (session_id, seq, kind, by_name, body, tool, outcome, created_at)
15 SELECT ?1, COALESCE(MAX(seq), 0) + 1, ?2, ?3, ?4, ?5, ?6, ?7 FROM agent_session_events WHERE session_id = ?1`,
16 )
17 .bind(id, kind, by, body.slice(0, 20_000), tool, outcome, iso());
18}
19
20/** How much of a command's output a session's transcript keeps: the page shows it folded. */
21const COMMAND_OUTPUT_CHARS = 8_000;
22
23/** `outcome` for a command: `exit 0 · 1.2 s`, with what went wrong. */
24export function commandOutcome(command: Pick<AgentComputerCommand, "exit_code" | "duration_ms" | "timed_out" | "truncated">): string {
25 const seconds = command.duration_ms / 1000;
26 const took = seconds < 10 ? `${seconds.toFixed(1)} s` : seconds < 90 ? `${Math.round(seconds)} s` : `${Math.round(seconds / 60)} min`;
27 return [`exit ${command.exit_code}`, took, command.timed_out ? "timed out" : null, command.truncated ? "output cut" : null].filter(Boolean).join(" · ");
28}
29
30/**
31 * A command the agent ran on its computer, as the transcript keeps it: the
32 * command on the first line, then its output (cut), the directory as
33 * `tool`, and how it ended as `outcome`.
34 */
35export function commandEvent(db: D1Database, sessionId: string, by: string, command: AgentComputerCommand): D1PreparedStatement {
36 const output = command.output.length > COMMAND_OUTPUT_CHARS ? `${command.output.slice(0, COMMAND_OUTPUT_CHARS)}\n[cut: ${command.output.length - COMMAND_OUTPUT_CHARS} more characters]` : command.output;
37 return eventStatement(db, sessionId, "command", by, `${command.cmd.split("\n")[0].slice(0, 500)}\n${output}`, command.cwd, commandOutcome(command));
38}