@agentic-kit/run-log 0.3.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/README.md +11 -9
  2. package/esm/file-store.d.ts +1 -1
  3. package/esm/file-store.js +3 -2
  4. package/esm/index.d.ts +4 -2
  5. package/esm/index.js +4 -2
  6. package/esm/mirror.d.ts +63 -0
  7. package/esm/mirror.js +87 -0
  8. package/esm/projectors/parts.d.ts +1 -1
  9. package/esm/projectors/parts.js +1 -1
  10. package/esm/projectors/session.d.ts +2 -2
  11. package/esm/projectors/session.js +8 -6
  12. package/esm/projectors/spans.d.ts +1 -1
  13. package/esm/projectors/spans.js +1 -1
  14. package/esm/projectors/tool-state.js +1 -1
  15. package/esm/projectors/usage.d.ts +1 -1
  16. package/esm/projectors/usage.js +2 -2
  17. package/esm/record.d.ts +17 -14
  18. package/esm/record.js +19 -14
  19. package/esm/store.d.ts +6 -3
  20. package/esm/store.js +3 -2
  21. package/esm/transcripts/format.d.ts +25 -0
  22. package/esm/transcripts/format.js +29 -0
  23. package/file-store.d.ts +1 -1
  24. package/file-store.js +2 -1
  25. package/index.d.ts +4 -2
  26. package/index.js +17 -12
  27. package/mirror.d.ts +63 -0
  28. package/mirror.js +91 -0
  29. package/package.json +2 -2
  30. package/projectors/parts.d.ts +1 -1
  31. package/projectors/parts.js +1 -1
  32. package/projectors/session.d.ts +2 -2
  33. package/projectors/session.js +7 -5
  34. package/projectors/spans.d.ts +1 -1
  35. package/projectors/spans.js +1 -1
  36. package/projectors/tool-state.js +1 -1
  37. package/projectors/usage.d.ts +1 -1
  38. package/projectors/usage.js +2 -2
  39. package/record.d.ts +17 -14
  40. package/record.js +20 -15
  41. package/store.d.ts +6 -3
  42. package/store.js +2 -1
  43. package/transcripts/format.d.ts +25 -0
  44. package/transcripts/format.js +33 -0
  45. /package/esm/{pi-entry.d.ts → transcripts/pi-entry.d.ts} +0 -0
  46. /package/esm/{pi-entry.js → transcripts/pi-entry.js} +0 -0
  47. /package/{pi-entry.d.ts → transcripts/pi-entry.d.ts} +0 -0
  48. /package/{pi-entry.js → transcripts/pi-entry.js} +0 -0
package/file-store.js CHANGED
@@ -58,7 +58,8 @@ class FileRunLogStore {
58
58
  seq: existing.length + written.length + 1,
59
59
  entry,
60
60
  ...(options.recordedAt ? { recordedAt: options.recordedAt } : {}),
61
- piSessionVersion: options.piSessionVersion ?? record_1.SUPPORTED_PI_SESSION_VERSION
61
+ ...(options.transcriptFormat === undefined ? {} : { transcriptFormat: options.transcriptFormat }),
62
+ ...(options.transcriptVersion === undefined ? {} : { transcriptVersion: options.transcriptVersion })
62
63
  }));
63
64
  seen.add(key);
64
65
  }
package/index.d.ts CHANGED
@@ -8,11 +8,13 @@
8
8
  */
9
9
  export { constructiveEntryTypes, type EntryTypeDefinition, EntryTypeRegistry, type EntryVisibility } from './entry-types';
10
10
  export { follow, type FollowOptions, readAll } from './follow';
11
- export { assertPiSessionEntry, contentText, isAssistantMessage, isPiBranchSummaryEntry, isPiCompactionEntry, isPiMessageEntry, isPiSessionHeader, isToolResultMessage, type PiAssistantMessage, type PiBashExecutionMessage, type PiBranchSummaryEntry, type PiCompactionEntry, type PiContent, type PiCustomMessage, type PiEntryBase, type PiImageContent, type PiMessage, type PiMessageEntry, type PiOtherEntry, type PiSessionEntry, type PiSessionHeader, type PiSummaryMessage, type PiTextContent, type PiThinkingContent, type PiToolCallContent, type PiToolResultMessage, type PiUsage, type PiUsageCost, type PiUserMessage, toolCalls } from './pi-entry';
11
+ export { type SessionEntrySource, SessionMirror, type SessionMirrorOptions } from './mirror';
12
12
  export { type BashPart, type Conversation, type ConversationPart, type CustomPart, projectParts, type SummaryPart, type TextPart, type ThinkingPart, type ToolPart, type ToolStatus, type UnknownPart } from './projectors/parts';
13
13
  export { parseSessionJsonl, projectSession, type SessionProjection, type SessionProjectionOptions } from './projectors/session';
14
14
  export { type ApprovalSpan, type ModelSpan, projectSpans, type Span, type SpanBase, type SpanKind, type SpanProjection, type SpanStatus, type SpanTiming, type ToolSpan } from './projectors/spans';
15
15
  export { APPROVAL_REQUEST_TYPE, APPROVAL_RESOLUTION_TYPE, type ApprovalRequestInput, approvalRequestMessage, type ApprovalResolutionInput, approvalResolutionMessage, type ApprovalState, assertApprovalRequestDetails, assertApprovalResolutionDetails, assertGateDecisionDetails, GATE_DECISION_TYPE, type GateDecisionDetails, gateDecisionMessage, type GateDecisionState, projectToolState, type ToolCallState, type ToolCallStatus, type ToolStateProjection } from './projectors/tool-state';
16
16
  export { modelKey, type ModelUsage, projectUsage, type RunUsage, type UsageTotals } from './projectors/usage';
17
- export { assertOrdered, assertRunEventRecord, idempotencyKey, RUN_LOG_WRAPPER_VERSION, type RunEventRecord, SUPPORTED_PI_SESSION_VERSION, wrapEntry, type WrapEntryOptions } from './record';
17
+ export { assertOrdered, assertRunEventRecord, idempotencyKey, RUN_LOG_WRAPPER_VERSION, type RunEventRecord, wrapEntry, type WrapEntryOptions } from './record';
18
18
  export { type AppendOptions, cursorAfter, MemoryRunLogStore, type RunLogAppendStore, type RunLogCursor, type RunLogPage, type RunLogReadStore, type RunLogStore, START } from './store';
19
+ export { assertTranscriptFormat, PI_TRANSCRIPT_FORMAT, SUPPORTED_PI_TRANSCRIPT_VERSION, type TranscriptFormat } from './transcripts/format';
20
+ export { assertPiSessionEntry, contentText, isAssistantMessage, isPiBranchSummaryEntry, isPiCompactionEntry, isPiMessageEntry, isPiSessionHeader, isToolResultMessage, type PiAssistantMessage, type PiBashExecutionMessage, type PiBranchSummaryEntry, type PiCompactionEntry, type PiContent, type PiCustomMessage, type PiEntryBase, type PiImageContent, type PiMessage, type PiMessageEntry, type PiOtherEntry, type PiSessionEntry, type PiSessionHeader, type PiSummaryMessage, type PiTextContent, type PiThinkingContent, type PiToolCallContent, type PiToolResultMessage, type PiUsage, type PiUsageCost, type PiUserMessage, toolCalls } from './transcripts/pi-entry';
package/index.js CHANGED
@@ -8,23 +8,15 @@
8
8
  * `@agentic-kit/run-log/file-store`.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.START = exports.MemoryRunLogStore = exports.cursorAfter = exports.wrapEntry = exports.SUPPORTED_PI_SESSION_VERSION = exports.RUN_LOG_WRAPPER_VERSION = exports.idempotencyKey = exports.assertRunEventRecord = exports.assertOrdered = exports.projectUsage = exports.modelKey = exports.projectToolState = exports.gateDecisionMessage = exports.GATE_DECISION_TYPE = exports.assertGateDecisionDetails = exports.assertApprovalResolutionDetails = exports.assertApprovalRequestDetails = exports.approvalResolutionMessage = exports.approvalRequestMessage = exports.APPROVAL_RESOLUTION_TYPE = exports.APPROVAL_REQUEST_TYPE = exports.projectSpans = exports.projectSession = exports.parseSessionJsonl = exports.projectParts = exports.toolCalls = exports.isToolResultMessage = exports.isPiSessionHeader = exports.isPiMessageEntry = exports.isPiCompactionEntry = exports.isPiBranchSummaryEntry = exports.isAssistantMessage = exports.contentText = exports.assertPiSessionEntry = exports.readAll = exports.follow = exports.EntryTypeRegistry = exports.constructiveEntryTypes = void 0;
11
+ exports.toolCalls = exports.isToolResultMessage = exports.isPiSessionHeader = exports.isPiMessageEntry = exports.isPiCompactionEntry = exports.isPiBranchSummaryEntry = exports.isAssistantMessage = exports.contentText = exports.assertPiSessionEntry = exports.SUPPORTED_PI_TRANSCRIPT_VERSION = exports.PI_TRANSCRIPT_FORMAT = exports.assertTranscriptFormat = exports.START = exports.MemoryRunLogStore = exports.cursorAfter = exports.wrapEntry = exports.RUN_LOG_WRAPPER_VERSION = exports.idempotencyKey = exports.assertRunEventRecord = exports.assertOrdered = exports.projectUsage = exports.modelKey = exports.projectToolState = exports.gateDecisionMessage = exports.GATE_DECISION_TYPE = exports.assertGateDecisionDetails = exports.assertApprovalResolutionDetails = exports.assertApprovalRequestDetails = exports.approvalResolutionMessage = exports.approvalRequestMessage = exports.APPROVAL_RESOLUTION_TYPE = exports.APPROVAL_REQUEST_TYPE = exports.projectSpans = exports.projectSession = exports.parseSessionJsonl = exports.projectParts = exports.SessionMirror = exports.readAll = exports.follow = exports.EntryTypeRegistry = exports.constructiveEntryTypes = void 0;
12
12
  var entry_types_1 = require("./entry-types");
13
13
  Object.defineProperty(exports, "constructiveEntryTypes", { enumerable: true, get: function () { return entry_types_1.constructiveEntryTypes; } });
14
14
  Object.defineProperty(exports, "EntryTypeRegistry", { enumerable: true, get: function () { return entry_types_1.EntryTypeRegistry; } });
15
15
  var follow_1 = require("./follow");
16
16
  Object.defineProperty(exports, "follow", { enumerable: true, get: function () { return follow_1.follow; } });
17
17
  Object.defineProperty(exports, "readAll", { enumerable: true, get: function () { return follow_1.readAll; } });
18
- var pi_entry_1 = require("./pi-entry");
19
- Object.defineProperty(exports, "assertPiSessionEntry", { enumerable: true, get: function () { return pi_entry_1.assertPiSessionEntry; } });
20
- Object.defineProperty(exports, "contentText", { enumerable: true, get: function () { return pi_entry_1.contentText; } });
21
- Object.defineProperty(exports, "isAssistantMessage", { enumerable: true, get: function () { return pi_entry_1.isAssistantMessage; } });
22
- Object.defineProperty(exports, "isPiBranchSummaryEntry", { enumerable: true, get: function () { return pi_entry_1.isPiBranchSummaryEntry; } });
23
- Object.defineProperty(exports, "isPiCompactionEntry", { enumerable: true, get: function () { return pi_entry_1.isPiCompactionEntry; } });
24
- Object.defineProperty(exports, "isPiMessageEntry", { enumerable: true, get: function () { return pi_entry_1.isPiMessageEntry; } });
25
- Object.defineProperty(exports, "isPiSessionHeader", { enumerable: true, get: function () { return pi_entry_1.isPiSessionHeader; } });
26
- Object.defineProperty(exports, "isToolResultMessage", { enumerable: true, get: function () { return pi_entry_1.isToolResultMessage; } });
27
- Object.defineProperty(exports, "toolCalls", { enumerable: true, get: function () { return pi_entry_1.toolCalls; } });
18
+ var mirror_1 = require("./mirror");
19
+ Object.defineProperty(exports, "SessionMirror", { enumerable: true, get: function () { return mirror_1.SessionMirror; } });
28
20
  var parts_1 = require("./projectors/parts");
29
21
  Object.defineProperty(exports, "projectParts", { enumerable: true, get: function () { return parts_1.projectParts; } });
30
22
  var session_1 = require("./projectors/session");
@@ -51,9 +43,22 @@ Object.defineProperty(exports, "assertOrdered", { enumerable: true, get: functio
51
43
  Object.defineProperty(exports, "assertRunEventRecord", { enumerable: true, get: function () { return record_1.assertRunEventRecord; } });
52
44
  Object.defineProperty(exports, "idempotencyKey", { enumerable: true, get: function () { return record_1.idempotencyKey; } });
53
45
  Object.defineProperty(exports, "RUN_LOG_WRAPPER_VERSION", { enumerable: true, get: function () { return record_1.RUN_LOG_WRAPPER_VERSION; } });
54
- Object.defineProperty(exports, "SUPPORTED_PI_SESSION_VERSION", { enumerable: true, get: function () { return record_1.SUPPORTED_PI_SESSION_VERSION; } });
55
46
  Object.defineProperty(exports, "wrapEntry", { enumerable: true, get: function () { return record_1.wrapEntry; } });
56
47
  var store_1 = require("./store");
57
48
  Object.defineProperty(exports, "cursorAfter", { enumerable: true, get: function () { return store_1.cursorAfter; } });
58
49
  Object.defineProperty(exports, "MemoryRunLogStore", { enumerable: true, get: function () { return store_1.MemoryRunLogStore; } });
59
50
  Object.defineProperty(exports, "START", { enumerable: true, get: function () { return store_1.START; } });
51
+ var format_1 = require("./transcripts/format");
52
+ Object.defineProperty(exports, "assertTranscriptFormat", { enumerable: true, get: function () { return format_1.assertTranscriptFormat; } });
53
+ Object.defineProperty(exports, "PI_TRANSCRIPT_FORMAT", { enumerable: true, get: function () { return format_1.PI_TRANSCRIPT_FORMAT; } });
54
+ Object.defineProperty(exports, "SUPPORTED_PI_TRANSCRIPT_VERSION", { enumerable: true, get: function () { return format_1.SUPPORTED_PI_TRANSCRIPT_VERSION; } });
55
+ var pi_entry_1 = require("./transcripts/pi-entry");
56
+ Object.defineProperty(exports, "assertPiSessionEntry", { enumerable: true, get: function () { return pi_entry_1.assertPiSessionEntry; } });
57
+ Object.defineProperty(exports, "contentText", { enumerable: true, get: function () { return pi_entry_1.contentText; } });
58
+ Object.defineProperty(exports, "isAssistantMessage", { enumerable: true, get: function () { return pi_entry_1.isAssistantMessage; } });
59
+ Object.defineProperty(exports, "isPiBranchSummaryEntry", { enumerable: true, get: function () { return pi_entry_1.isPiBranchSummaryEntry; } });
60
+ Object.defineProperty(exports, "isPiCompactionEntry", { enumerable: true, get: function () { return pi_entry_1.isPiCompactionEntry; } });
61
+ Object.defineProperty(exports, "isPiMessageEntry", { enumerable: true, get: function () { return pi_entry_1.isPiMessageEntry; } });
62
+ Object.defineProperty(exports, "isPiSessionHeader", { enumerable: true, get: function () { return pi_entry_1.isPiSessionHeader; } });
63
+ Object.defineProperty(exports, "isToolResultMessage", { enumerable: true, get: function () { return pi_entry_1.isToolResultMessage; } });
64
+ Object.defineProperty(exports, "toolCalls", { enumerable: true, get: function () { return pi_entry_1.toolCalls; } });
package/mirror.d.ts ADDED
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Mirroring a harness's session into the run log.
3
+ *
4
+ * The harness owns the session: it appends entries to its own append-only
5
+ * transcript. Harnesses generally expose no "entry appended" event to subscribe
6
+ * to, so the mirror drains instead — after anything that could have appended, it
7
+ * takes the entries it has not seen yet and appends them to the run log
8
+ * verbatim. Index-based draining is sound precisely because the transcript is
9
+ * append-only: entries are never rewritten or removed, only branched from.
10
+ *
11
+ * A switch/fork/new-session replaces the entry list under the same source, so the
12
+ * read position is keyed to the session header's id and resets when that id
13
+ * changes; entries carried into the new session are absorbed by the store's
14
+ * idempotency (entry ids).
15
+ *
16
+ * Harness-neutral: the adapter owns only the subscription that calls `drain()`
17
+ * and the format the entries are validated as, so this is testable without a
18
+ * running agent.
19
+ */
20
+ import type { RunEventRecord } from './record';
21
+ import type { RunLogAppendStore } from './store';
22
+ import type { TranscriptFormat } from './transcripts/format';
23
+ import { type PiSessionEntry } from './transcripts/pi-entry';
24
+ /** The slice of a harness session manager the mirror needs. */
25
+ export interface SessionEntrySource {
26
+ getHeader(): unknown;
27
+ getEntries(): readonly unknown[];
28
+ }
29
+ export interface SessionMirrorOptions {
30
+ runId: string;
31
+ store: RunLogAppendStore;
32
+ /**
33
+ * Narrow each entry before it is appended. Defaults to the pi transcript
34
+ * reader, which is the only entry shape the record currently types; an adapter
35
+ * for another transcript supplies its own.
36
+ */
37
+ assertEntry?: (entry: unknown) => PiSessionEntry;
38
+ /** Transcript format the entries are encoded in, e.g. `pi`. */
39
+ transcriptFormat?: TranscriptFormat;
40
+ /** Transcript format version the entries are produced under. */
41
+ transcriptVersion?: number;
42
+ }
43
+ export declare class SessionMirror {
44
+ private readonly runId;
45
+ private readonly store;
46
+ private readonly assertEntry;
47
+ private readonly transcriptFormat;
48
+ private readonly transcriptVersion;
49
+ private source;
50
+ private sessionId;
51
+ private consumed;
52
+ private headerMirrored;
53
+ private tail;
54
+ constructor(options: SessionMirrorOptions);
55
+ /** Point the mirror at a session source. Safe to call on every event. */
56
+ bind(source: SessionEntrySource): void;
57
+ /**
58
+ * Append everything the mirror has not seen yet. Drains are serialized, so
59
+ * concurrent callers cannot interleave batches and break run-log ordering.
60
+ */
61
+ drain(): Promise<RunEventRecord[]>;
62
+ private flushOnce;
63
+ }
package/mirror.js ADDED
@@ -0,0 +1,91 @@
1
+ "use strict";
2
+ /**
3
+ * Mirroring a harness's session into the run log.
4
+ *
5
+ * The harness owns the session: it appends entries to its own append-only
6
+ * transcript. Harnesses generally expose no "entry appended" event to subscribe
7
+ * to, so the mirror drains instead — after anything that could have appended, it
8
+ * takes the entries it has not seen yet and appends them to the run log
9
+ * verbatim. Index-based draining is sound precisely because the transcript is
10
+ * append-only: entries are never rewritten or removed, only branched from.
11
+ *
12
+ * A switch/fork/new-session replaces the entry list under the same source, so the
13
+ * read position is keyed to the session header's id and resets when that id
14
+ * changes; entries carried into the new session are absorbed by the store's
15
+ * idempotency (entry ids).
16
+ *
17
+ * Harness-neutral: the adapter owns only the subscription that calls `drain()`
18
+ * and the format the entries are validated as, so this is testable without a
19
+ * running agent.
20
+ */
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.SessionMirror = void 0;
23
+ const pi_entry_1 = require("./transcripts/pi-entry");
24
+ class SessionMirror {
25
+ runId;
26
+ store;
27
+ assertEntry;
28
+ transcriptFormat;
29
+ transcriptVersion;
30
+ source = null;
31
+ sessionId = null;
32
+ consumed = 0;
33
+ headerMirrored = false;
34
+ tail = Promise.resolve();
35
+ constructor(options) {
36
+ this.runId = options.runId;
37
+ this.store = options.store;
38
+ this.assertEntry = options.assertEntry ?? pi_entry_1.assertPiSessionEntry;
39
+ this.transcriptFormat = options.transcriptFormat;
40
+ this.transcriptVersion = options.transcriptVersion;
41
+ }
42
+ /** Point the mirror at a session source. Safe to call on every event. */
43
+ bind(source) {
44
+ this.source = source;
45
+ }
46
+ /**
47
+ * Append everything the mirror has not seen yet. Drains are serialized, so
48
+ * concurrent callers cannot interleave batches and break run-log ordering.
49
+ */
50
+ drain() {
51
+ const run = this.tail.then(() => this.flushOnce());
52
+ this.tail = run.then(() => undefined, () => undefined);
53
+ return run;
54
+ }
55
+ async flushOnce() {
56
+ const source = this.source;
57
+ if (!source)
58
+ return [];
59
+ const header = source.getHeader();
60
+ const sessionId = headerSessionId(header);
61
+ if (sessionId !== null && sessionId !== this.sessionId) {
62
+ this.sessionId = sessionId;
63
+ this.consumed = 0;
64
+ this.headerMirrored = false;
65
+ }
66
+ const batch = [];
67
+ if (!this.headerMirrored && header !== null && header !== undefined)
68
+ batch.push(this.assertEntry(header));
69
+ const entries = source.getEntries();
70
+ const upto = entries.length;
71
+ for (let i = this.consumed; i < upto; i += 1)
72
+ batch.push(this.assertEntry(entries[i]));
73
+ if (batch.length === 0)
74
+ return [];
75
+ const written = await this.store.append(this.runId, batch, {
76
+ ...(this.transcriptFormat === undefined ? {} : { transcriptFormat: this.transcriptFormat }),
77
+ ...(this.transcriptVersion === undefined ? {} : { transcriptVersion: this.transcriptVersion })
78
+ });
79
+ // Advance only after a successful append: a failed drain is retried whole.
80
+ this.headerMirrored = true;
81
+ this.consumed = upto;
82
+ return written;
83
+ }
84
+ }
85
+ exports.SessionMirror = SessionMirror;
86
+ function headerSessionId(header) {
87
+ if (typeof header !== 'object' || header === null)
88
+ return null;
89
+ const id = header.id;
90
+ return typeof id === 'string' && id.length > 0 ? id : null;
91
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-kit/run-log",
3
- "version": "0.3.0",
3
+ "version": "0.5.0",
4
4
  "author": "Constructive <developers@constructive.io>",
5
5
  "description": "The append-only agent run log — pi session entries stored verbatim under a run/seq wrapper, with projections for transcript, usage, tool state and resumable sessions",
6
6
  "main": "index.js",
@@ -35,5 +35,5 @@
35
35
  "run-log",
36
36
  "constructive"
37
37
  ],
38
- "gitHead": "6880388c8cd5fb21745decff8bd23c3a430338b3"
38
+ "gitHead": "b5b6b9f05276b7e9cbd3d1f4acd31dd315c353ea"
39
39
  }
@@ -7,8 +7,8 @@
7
7
  * `unknown` part rather than disappearing — a log written by a newer pi still
8
8
  * renders, minus the detail this version understands.
9
9
  */
10
- import { type PiSessionEntry } from '../pi-entry';
11
10
  import type { RunEventRecord } from '../record';
11
+ import { type PiSessionEntry } from '../transcripts/pi-entry';
12
12
  export type ToolStatus = 'requested' | 'completed' | 'failed';
13
13
  export interface PartBase {
14
14
  /** Sequence of the record that introduced the part — a stable React key. */
@@ -10,7 +10,7 @@
10
10
  */
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.projectParts = projectParts;
13
- const pi_entry_1 = require("../pi-entry");
13
+ const pi_entry_1 = require("../transcripts/pi-entry");
14
14
  /**
15
15
  * Project records into a conversation. Pure and total: the same records always
16
16
  * produce the same parts, whichever host wrote them.
@@ -9,8 +9,8 @@
9
9
  * Returns a string rather than writing a file: the projection is pure, and the
10
10
  * node-side write lives in `@agentic-kit/run-log/file-store`.
11
11
  */
12
- import { type PiSessionEntry } from '../pi-entry';
13
12
  import { type RunEventRecord } from '../record';
13
+ import { type PiSessionEntry } from '../transcripts/pi-entry';
14
14
  export interface SessionProjectionOptions {
15
15
  /** Used when the log carries no session header (a run logged headerless). */
16
16
  sessionId?: string;
@@ -22,7 +22,7 @@ export interface SessionProjection {
22
22
  jsonl: string;
23
23
  /** Entries in file order, header first. */
24
24
  entries: PiSessionEntry[];
25
- piSessionVersion: number;
25
+ transcriptVersion: number;
26
26
  }
27
27
  /**
28
28
  * Project records into a pi session file. Throws when the records cannot form a
@@ -13,8 +13,9 @@
13
13
  Object.defineProperty(exports, "__esModule", { value: true });
14
14
  exports.projectSession = projectSession;
15
15
  exports.parseSessionJsonl = parseSessionJsonl;
16
- const pi_entry_1 = require("../pi-entry");
17
16
  const record_1 = require("../record");
17
+ const format_1 = require("../transcripts/format");
18
+ const pi_entry_1 = require("../transcripts/pi-entry");
18
19
  /**
19
20
  * Project records into a pi session file. Throws when the records cannot form a
20
21
  * loadable session — an unresumable session must fail at projection time, not
@@ -22,11 +23,12 @@ const record_1 = require("../record");
22
23
  */
23
24
  function projectSession(records, options = {}) {
24
25
  (0, record_1.assertOrdered)(records);
25
- const versions = new Set(records.map((record) => record.piSessionVersion));
26
+ (0, format_1.assertTranscriptFormat)(records, format_1.PI_TRANSCRIPT_FORMAT);
27
+ const versions = new Set(records.map((record) => record.transcriptVersion));
26
28
  if (versions.size > 1) {
27
29
  throw new Error(`run log mixes pi session versions (${Array.from(versions).sort().join(', ')}); migrate the older entries before projecting a session`);
28
30
  }
29
- const piSessionVersion = records[0]?.piSessionVersion ?? record_1.SUPPORTED_PI_SESSION_VERSION;
31
+ const transcriptVersion = records[0]?.transcriptVersion ?? format_1.SUPPORTED_PI_TRANSCRIPT_VERSION;
30
32
  const entries = records.map((record) => record.entry);
31
33
  const headerIndex = entries.findIndex(pi_entry_1.isPiSessionHeader);
32
34
  if (headerIndex > 0) {
@@ -37,7 +39,7 @@ function projectSession(records, options = {}) {
37
39
  ? entries[0]
38
40
  : {
39
41
  type: 'session',
40
- version: piSessionVersion,
42
+ version: transcriptVersion,
41
43
  id: options.sessionId ?? records[0]?.runId ?? 'run-log',
42
44
  timestamp: options.timestamp ?? records[0]?.recordedAt ?? new Date().toISOString(),
43
45
  ...(options.cwd ? { cwd: options.cwd } : {})
@@ -45,7 +47,7 @@ function projectSession(records, options = {}) {
45
47
  const ordered = [header, ...body];
46
48
  return {
47
49
  entries: ordered,
48
- piSessionVersion,
50
+ transcriptVersion,
49
51
  jsonl: ordered.map((entry) => JSON.stringify(entry)).join('\n') + '\n'
50
52
  };
51
53
  }
@@ -15,8 +15,8 @@
15
15
  * `open` with no duration rather than being closed at the last record: a run
16
16
  * still in flight must not look like one that finished.
17
17
  */
18
- import { type PiUsage } from '../pi-entry';
19
18
  import type { RunEventRecord } from '../record';
19
+ import { type PiUsage } from '../transcripts/pi-entry';
20
20
  export type SpanKind = 'model' | 'tool' | 'approval';
21
21
  export type SpanStatus = 'open' | 'ok' | 'error' | 'denied';
22
22
  /**
@@ -18,7 +18,7 @@
18
18
  */
19
19
  Object.defineProperty(exports, "__esModule", { value: true });
20
20
  exports.projectSpans = projectSpans;
21
- const pi_entry_1 = require("../pi-entry");
21
+ const pi_entry_1 = require("../transcripts/pi-entry");
22
22
  const tool_state_1 = require("./tool-state");
23
23
  const ms = (from, to) => {
24
24
  const start = Date.parse(from);
@@ -15,7 +15,7 @@ exports.projectToolState = projectToolState;
15
15
  exports.assertGateDecisionDetails = assertGateDecisionDetails;
16
16
  exports.assertApprovalRequestDetails = assertApprovalRequestDetails;
17
17
  exports.assertApprovalResolutionDetails = assertApprovalResolutionDetails;
18
- const pi_entry_1 = require("../pi-entry");
18
+ const pi_entry_1 = require("../transcripts/pi-entry");
19
19
  /** `customType` of an approval request written by the gate extension. */
20
20
  exports.APPROVAL_REQUEST_TYPE = 'constructive.approval.request';
21
21
  /** `customType` of the human's answer to a request. */
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * This projection describes what a run *observed*. It is reconciliation input,
11
11
  * never the billing authority — a gateway-observed record is (see
12
- * `@agentic-kit/pi-ext-metered-model`).
12
+ * `@agentic-kit/metering`).
13
13
  */
14
14
  import type { RunEventRecord } from '../record';
15
15
  export interface UsageTotals {
@@ -10,12 +10,12 @@
10
10
  *
11
11
  * This projection describes what a run *observed*. It is reconciliation input,
12
12
  * never the billing authority — a gateway-observed record is (see
13
- * `@agentic-kit/pi-ext-metered-model`).
13
+ * `@agentic-kit/metering`).
14
14
  */
15
15
  Object.defineProperty(exports, "__esModule", { value: true });
16
16
  exports.modelKey = void 0;
17
17
  exports.projectUsage = projectUsage;
18
- const pi_entry_1 = require("../pi-entry");
18
+ const pi_entry_1 = require("../transcripts/pi-entry");
19
19
  const empty = () => ({
20
20
  input: 0,
21
21
  output: 0,
package/record.d.ts CHANGED
@@ -1,17 +1,17 @@
1
1
  /**
2
- * The run log record: four platform-owned fields around a verbatim pi entry.
2
+ * The run log record: five platform-owned fields around a verbatim harness entry.
3
3
  *
4
- * The wrapper exists to give an entry identity (which run) and order (which
5
- * position) across hosts — nothing else. It deliberately does not re-encode pi's
6
- * semantics, because pi already versions and migrates its own session format;
7
- * `piSessionVersion` records which version an entry was written under so a
8
- * reader can hand old entries to pi's migrations instead of guessing.
4
+ * The wrapper exists to give an entry identity (which run), order (which
5
+ * position) and readability (which transcript) across hosts — nothing else. It
6
+ * deliberately does not re-encode a harness's semantics, because a harness
7
+ * already versions and migrates its own session format; `transcriptFormat` says
8
+ * which reader can parse the entry, and `transcriptVersion` says which version
9
+ * that reader must migrate from.
9
10
  */
10
- import { type PiSessionEntry } from './pi-entry';
11
- /** Version of the wrapper itself — bumped only if these four fields change. */
11
+ import { type TranscriptFormat } from './transcripts/format';
12
+ import { type PiSessionEntry } from './transcripts/pi-entry';
13
+ /** Version of the wrapper itself — bumped only if these fields change. */
12
14
  export declare const RUN_LOG_WRAPPER_VERSION = 1;
13
- /** The pi session format version this package projects without migration. */
14
- export declare const SUPPORTED_PI_SESSION_VERSION = 3;
15
15
  export interface RunEventRecord {
16
16
  /** The run this entry belongs to. */
17
17
  runId: string;
@@ -19,9 +19,11 @@ export interface RunEventRecord {
19
19
  seq: number;
20
20
  /** When the platform durably recorded the entry (ISO 8601). */
21
21
  recordedAt: string;
22
- /** pi's session format version at write time. */
23
- piSessionVersion: number;
24
- /** The pi session entry, byte-for-byte as pi produced it. */
22
+ /** The transcript the entry is encoded in, e.g. `pi`. */
23
+ transcriptFormat: TranscriptFormat;
24
+ /** That transcript's format version at write time. */
25
+ transcriptVersion: number;
26
+ /** The harness's session entry, byte-for-byte as it was produced. */
25
27
  entry: PiSessionEntry;
26
28
  }
27
29
  export interface WrapEntryOptions {
@@ -29,7 +31,8 @@ export interface WrapEntryOptions {
29
31
  seq: number;
30
32
  entry: PiSessionEntry;
31
33
  recordedAt?: string;
32
- piSessionVersion?: number;
34
+ transcriptFormat?: TranscriptFormat;
35
+ transcriptVersion?: number;
33
36
  }
34
37
  export declare function wrapEntry(options: WrapEntryOptions): RunEventRecord;
35
38
  /**
package/record.js CHANGED
@@ -1,24 +1,24 @@
1
1
  "use strict";
2
2
  /**
3
- * The run log record: four platform-owned fields around a verbatim pi entry.
3
+ * The run log record: five platform-owned fields around a verbatim harness entry.
4
4
  *
5
- * The wrapper exists to give an entry identity (which run) and order (which
6
- * position) across hosts — nothing else. It deliberately does not re-encode pi's
7
- * semantics, because pi already versions and migrates its own session format;
8
- * `piSessionVersion` records which version an entry was written under so a
9
- * reader can hand old entries to pi's migrations instead of guessing.
5
+ * The wrapper exists to give an entry identity (which run), order (which
6
+ * position) and readability (which transcript) across hosts — nothing else. It
7
+ * deliberately does not re-encode a harness's semantics, because a harness
8
+ * already versions and migrates its own session format; `transcriptFormat` says
9
+ * which reader can parse the entry, and `transcriptVersion` says which version
10
+ * that reader must migrate from.
10
11
  */
11
12
  Object.defineProperty(exports, "__esModule", { value: true });
12
- exports.SUPPORTED_PI_SESSION_VERSION = exports.RUN_LOG_WRAPPER_VERSION = void 0;
13
+ exports.RUN_LOG_WRAPPER_VERSION = void 0;
13
14
  exports.wrapEntry = wrapEntry;
14
15
  exports.assertRunEventRecord = assertRunEventRecord;
15
16
  exports.idempotencyKey = idempotencyKey;
16
17
  exports.assertOrdered = assertOrdered;
17
- const pi_entry_1 = require("./pi-entry");
18
- /** Version of the wrapper itself — bumped only if these four fields change. */
18
+ const format_1 = require("./transcripts/format");
19
+ const pi_entry_1 = require("./transcripts/pi-entry");
20
+ /** Version of the wrapper itself — bumped only if these fields change. */
19
21
  exports.RUN_LOG_WRAPPER_VERSION = 1;
20
- /** The pi session format version this package projects without migration. */
21
- exports.SUPPORTED_PI_SESSION_VERSION = 3;
22
22
  function wrapEntry(options) {
23
23
  if (!options.runId)
24
24
  throw new TypeError('a run log record needs a runId');
@@ -29,7 +29,8 @@ function wrapEntry(options) {
29
29
  runId: options.runId,
30
30
  seq: options.seq,
31
31
  recordedAt: options.recordedAt ?? new Date().toISOString(),
32
- piSessionVersion: options.piSessionVersion ?? exports.SUPPORTED_PI_SESSION_VERSION,
32
+ transcriptFormat: options.transcriptFormat ?? format_1.PI_TRANSCRIPT_FORMAT,
33
+ transcriptVersion: options.transcriptVersion ?? format_1.SUPPORTED_PI_TRANSCRIPT_VERSION,
33
34
  entry: (0, pi_entry_1.assertPiSessionEntry)(options.entry)
34
35
  };
35
36
  }
@@ -52,14 +53,18 @@ function assertRunEventRecord(value) {
52
53
  if (typeof record.recordedAt !== 'string') {
53
54
  throw new TypeError(`run log record ${record.runId}#${String(record.seq)} must carry recordedAt`);
54
55
  }
55
- if (!Number.isInteger(record.piSessionVersion)) {
56
- throw new TypeError(`run log record ${record.runId}#${String(record.seq)} must carry an integer piSessionVersion`);
56
+ if (typeof record.transcriptFormat !== 'string' || record.transcriptFormat.length === 0) {
57
+ throw new TypeError(`run log record ${record.runId}#${String(record.seq)} must carry a non-empty transcriptFormat`);
58
+ }
59
+ if (!Number.isInteger(record.transcriptVersion)) {
60
+ throw new TypeError(`run log record ${record.runId}#${String(record.seq)} must carry an integer transcriptVersion`);
57
61
  }
58
62
  return {
59
63
  runId: record.runId,
60
64
  seq: record.seq,
61
65
  recordedAt: record.recordedAt,
62
- piSessionVersion: record.piSessionVersion,
66
+ transcriptFormat: record.transcriptFormat,
67
+ transcriptVersion: record.transcriptVersion,
63
68
  entry: (0, pi_entry_1.assertPiSessionEntry)(record.entry)
64
69
  };
65
70
  }
package/store.d.ts CHANGED
@@ -5,8 +5,9 @@
5
5
  * with their storage — Postgres in constructive-db, JSONL in `./file-store`,
6
6
  * memory here for tests and for a run that has not been persisted yet.
7
7
  */
8
- import type { PiSessionEntry } from './pi-entry';
9
8
  import { type RunEventRecord } from './record';
9
+ import type { TranscriptFormat } from './transcripts/format';
10
+ import type { PiSessionEntry } from './transcripts/pi-entry';
10
11
  /** Read position: `afterSeq` is exclusive, so `0` means "from the start". */
11
12
  export interface RunLogCursor {
12
13
  afterSeq: number;
@@ -18,8 +19,10 @@ export interface RunLogPage {
18
19
  cursor: RunLogCursor;
19
20
  }
20
21
  export interface AppendOptions {
21
- /** pi session format version the entries were produced under. */
22
- piSessionVersion?: number;
22
+ /** Transcript the entries are encoded in. Defaults to pi's. */
23
+ transcriptFormat?: TranscriptFormat;
24
+ /** That transcript's format version the entries were produced under. */
25
+ transcriptVersion?: number;
23
26
  recordedAt?: string;
24
27
  }
25
28
  export interface RunLogAppendStore {
package/store.js CHANGED
@@ -29,7 +29,8 @@ class MemoryRunLogStore {
29
29
  seq: records.length + written.length + 1,
30
30
  entry,
31
31
  ...(options.recordedAt ? { recordedAt: options.recordedAt } : {}),
32
- piSessionVersion: options.piSessionVersion ?? record_1.SUPPORTED_PI_SESSION_VERSION
32
+ ...(options.transcriptFormat === undefined ? {} : { transcriptFormat: options.transcriptFormat }),
33
+ ...(options.transcriptVersion === undefined ? {} : { transcriptVersion: options.transcriptVersion })
33
34
  });
34
35
  written.push(record);
35
36
  seen.add(key);
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Which transcript a record's entry is encoded in.
3
+ *
4
+ * The log stores a harness's own entries verbatim, so a reader cannot know how
5
+ * to project a record without being told what it is looking at. The format is
6
+ * therefore carried *per record*, not per run: a run resumed under a different
7
+ * harness, or migrated between them, stays one ordered log.
8
+ *
9
+ * Deliberately a string rather than a closed union — a format is a value that
10
+ * ships with an adapter, so adding one must not require a release of this
11
+ * package. Known formats are declared as constants here.
12
+ */
13
+ export type TranscriptFormat = string;
14
+ /** pi's session-file entries (`@earendil-works/pi-coding-agent`). */
15
+ export declare const PI_TRANSCRIPT_FORMAT = "pi";
16
+ /** The pi session format version this package projects without migration. */
17
+ export declare const SUPPORTED_PI_TRANSCRIPT_VERSION = 3;
18
+ /**
19
+ * Narrow a record's format before projecting it. A projector reads one format;
20
+ * handing it another must fail loudly rather than render an empty conversation
21
+ * out of entries it did not understand.
22
+ */
23
+ export declare function assertTranscriptFormat(records: readonly {
24
+ transcriptFormat: TranscriptFormat;
25
+ }[], expected: TranscriptFormat): void;
@@ -0,0 +1,33 @@
1
+ "use strict";
2
+ /**
3
+ * Which transcript a record's entry is encoded in.
4
+ *
5
+ * The log stores a harness's own entries verbatim, so a reader cannot know how
6
+ * to project a record without being told what it is looking at. The format is
7
+ * therefore carried *per record*, not per run: a run resumed under a different
8
+ * harness, or migrated between them, stays one ordered log.
9
+ *
10
+ * Deliberately a string rather than a closed union — a format is a value that
11
+ * ships with an adapter, so adding one must not require a release of this
12
+ * package. Known formats are declared as constants here.
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.SUPPORTED_PI_TRANSCRIPT_VERSION = exports.PI_TRANSCRIPT_FORMAT = void 0;
16
+ exports.assertTranscriptFormat = assertTranscriptFormat;
17
+ /** pi's session-file entries (`@earendil-works/pi-coding-agent`). */
18
+ exports.PI_TRANSCRIPT_FORMAT = 'pi';
19
+ /** The pi session format version this package projects without migration. */
20
+ exports.SUPPORTED_PI_TRANSCRIPT_VERSION = 3;
21
+ /**
22
+ * Narrow a record's format before projecting it. A projector reads one format;
23
+ * handing it another must fail loudly rather than render an empty conversation
24
+ * out of entries it did not understand.
25
+ */
26
+ function assertTranscriptFormat(records, expected) {
27
+ const formats = new Set(records.map((record) => record.transcriptFormat));
28
+ formats.delete(expected);
29
+ if (formats.size === 0)
30
+ return;
31
+ throw new Error(`run log carries transcript format(s) ${Array.from(formats).sort().join(', ')}; ` +
32
+ `this projector reads "${expected}" entries only`);
33
+ }
File without changes
File without changes
File without changes