/** * Exporting a run: `runs//` with one HTML and one JSONL per * subagent, plus a `usage.json`. * * Two rules govern this file. * * **We reimplement nothing.** The HTML and the JSONL are pi's own * (`AgentSession.exportToHtml` / `exportToJsonl`); `usage.json` is the single * artefact we produce ourselves, because it is the only one pi does not know * about - time is measured here, and attribution per subagent is ours. * * **An export never breaks a run.** Every failure comes back as a string in * `error`, never as a throw: an export is an observer of the work, and an * observer that takes the workflow down with it is a bug. That matters most on * the interrupted path, where exporting is precisely what we are trying to * rescue. */ import type { RunSnapshot } from "../reporters/picture.ts"; import type { Usage } from "../usage.ts"; import type { MainSession, SessionPort } from "../session.ts"; /** What one subagent left on disk. Both paths are absent when nothing could be written. */ export type SessionExport = { /** The subagent this transcript belongs to, e.g. `reviewer#2`. */ id: string; /** Path of the HTML page pi rendered. Absent when it could not be produced. */ html?: string; /** Path of the JSONL transcript. Attempted separately from the HTML. */ jsonl?: string; /** Why the export did not happen. Never thrown, always reported. */ error?: string; }; /** * Creates `//` and returns its path: a directory of its own * for every call. * * The timestamp is to the second, sortable and filesystem-safe, so `ls` shows * the runs in order. Two runs started in the same second would share it, and * a run directory holds one run: the second is `-2`, then `-3`, as * the run's branch is. Each candidate is created exclusively, so two processes * never both take one. {@link newestRunFirst} orders the names. * * `` is given a `.gitignore` of its own, because the exports land **inside * the repository the run works on** and git has no reason to know about them. * Measured, both ways round: a delivery that gives its subtasks copies refuses * to put them back, because `runs/` alone makes the tree unclean; and `/build`'s * commit is a `git add -A`, which would sweep a run's transcripts into the * user's history. A file already there is left alone - it is their directory * once they have said anything about it. */ export declare function createRunDir(base?: string, now?: Date): string; /** * Orders run directory names newest first, `-10` after `-9` included, which * a plain sort would put before `-2`. */ export declare function newestRunFirst(a: string, b: string): number; /** * Turns a subagent id into a file name: `reviewer#2` → `reviewer-2`. * * `#` is legal in a file name and unusable in a URL, and these files are meant * to be opened in a browser and shared. */ export declare function exportBaseName(id: string): string; /** * The first of `base`, `base~2`, `base~3`... under which `dir` holds no file * with any of `extensions`, and that is not in `taken` as `/`: * what a later life of a run, or a second subagent in the same place, is * named, so that nothing already written is overwritten. */ export declare function freeName(dir: string, base: string, extensions: readonly string[], taken?: ReadonlySet): string; /** * Exports one **live** session into `dir`, as `.jsonl` and `.html`. * * Must be called before `dispose()`: afterwards the session is gone and the * transcript with it. Each format is attempted on its own - an in-memory * session still yields its JSONL even though pi refuses to render its HTML. */ export declare function exportSession(session: SessionPort, dir: string, id: string, name?: string): Promise; /** One subagent's line in `usage.json`. */ export type UsageReportEntry = { /** The subagent, e.g. `scout#1` - what the transcript files are named after. */ id: string; /** The agent it was spawned from. Several subagents may share one agent. */ agent: string; /** The lifetime it actually ran with, not the agent's declared default. */ lifetime: string; /** `provider/id` as pi resolved it, when pi could say. */ model?: string; /** Its last known status: what it was doing when the run ended. */ status: string; /** Whether its last turn succeeded. Absent while it is still running. */ ok?: boolean; /** The failure, when there was one. A failed subagent keeps its usage. */ error?: string; /** The last task it was given - a report of ids alone reads like nothing. */ task: string; /** How many tools it called. The cheapest signal that a turn ran away. */ toolCalls: number; /** Present when its definition says `mustCallTool: true` and it called no tool: why it failed. */ calledNoTool?: true; /** * The subagent that had this one spawned. Absent on a root. * * The list stays **flat** and carries the link, rather than nesting: the * total is a sum over the whole tree either way, every reader written * against the flat shape keeps working, and a tree is one pass away for * whoever wants one. The rows are in tree order, so reading it top to bottom * already shows the children under their parent. */ parentId?: string; /** Its {@link Usage}: time measured here, tokens as pi reported them. */ usage: Usage; /** In a flow run: the folder of its transcript, relative to the run directory - its memory scope's path, else its visit's. */ home?: string; /** In a flow run: the life it ran in, counting from 1. */ life?: number; /** In a flow run: every visit it ran, in the order they ended. */ visits?: string[]; }; /** One visit of a flow run: a node's, once, in one life. */ export type VisitUsage = { /** `deliver#2/work[1]/code`. */ path: string; /** The node's address, without iterations: `deliver/work/code`. */ node: string; kind: string; /** The agent an `agent` visit ran. */ agent?: string; /** The subagent it ran on, the one a `subagents` entry names. */ subagent?: string; life: number; ok: boolean; wallMs: number; /** The delta of pi's cumulative stats over the visit, every nested visit included. */ usage: Usage; }; /** Every visit of one node address, added up. */ export type NodeUsage = { node: string; visits: number; /** The sum of its visits', which may overlap when they ran together. */ wallMs: number; usage: Usage; }; /** One life of a flow run: its first start, or a resume. */ export type LifeUsage = { /** When it started, ISO 8601. */ startedAt: string; wallMs: number; usage: Usage; /** How it ended. A life killed before its end was `interrupted`. */ end: "ok" | "failed" | "interrupted"; /** * Rebuilt from the journal, because it never wrote its own `usage.json`: * what its ended visits cost, and nothing of a turn cut mid-way or of its * subagents. */ partial?: true; }; /** The sum over a whole run, and how many subagents it was spread over. */ export type UsageTotal = Usage & { /** Subagents in the run, failures included. */ subagents: number; /** Those that ended with `ok: false`. Counted apart: `2/3 done` hides a crash. */ failed: number; }; /** The whole `usage.json` document. */ export type UsageReport = { /** When the report was written, ISO 8601. The run's own timestamp is the directory. */ generatedAt: string; /** Wall time of the run itself, not the sum of the subagents. */ wallMs: number; /** One entry per subagent, in tree order: a child follows the parent it hangs under. */ subagents: UsageReportEntry[]; /** The sum over every subagent - failures included, because they cost too. Its `wallMs` is the run's. */ total: UsageTotal; /** Busy time over wall time: the parallelism actually achieved. */ parallelism: number; /** Where each transcript landed, and why one is missing when it is. */ exports?: SessionExport[]; /** In a flow run: every visit of every life, each life's in plan order. */ visits?: VisitUsage[]; /** In a flow run: one entry per node address, in the order first visited. */ nodes?: NodeUsage[]; /** In a flow run with a journal: each life, whose sum `total` is. */ lives?: LifeUsage[]; }; /** * Builds the report from a collected snapshot. * * A fan-out **aggregates**, it never averages: tokens and cost are sums, * `busyMs` is the sum of the branches, and `wallMs` is how long the run took. * The ratio of the last two is the only honest measure of parallelism. */ export declare function usageReport(snapshot: RunSnapshot, wallMs: number, exports?: SessionExport[]): UsageReport; /** Writes `usage.json` into `dir` and returns its path. */ export declare function writeUsageReport(dir: string, report: UsageReport): string; /** * Copies the parent session's JSONL next to the subagents' exports. * * An orchestration export that lost the parent would be half a story. HTML is * missing here on purpose: pi's HTML exporter is a method of `AgentSession`, * and an extension only ever sees a `ReadonlySessionManager` - the renderer is * not on pi's public surface. `pi --export ` turns this JSONL into the * same HTML whenever it is wanted. * * pi's own file is copied once pi has written it. Until then, which is the * whole of a run launched before the parent's first reply, the same lines * are written from what pi holds: its header and its entries, each as the * one JSON line pi's file keeps it as. Nothing is rendered, and nothing pi * does not hold is added. */ export declare function copyMainSession(session: MainSession, dir: string): SessionExport; //# sourceMappingURL=export.d.ts.map