@cueloop/daemon 0.1.0-alpha.69 → 0.1.0-alpha.70

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.
@@ -16,9 +16,9 @@ import {
16
16
  } from "node:fs";
17
17
  import { tmpdir } from "node:os";
18
18
  import { join } from "node:path";
19
- import { SessionStore } from "./store";
19
+ import { ThreadStore } from "./store";
20
20
  import { migratedSessionsDir, sessionsDir, threadBucket } from "./paths";
21
- import { createTestSessionRecord } from "./testing/store-conformance";
21
+ import { createTestThreadRecord } from "./testing/store-conformance";
22
22
 
23
23
  function tmpHome(): string {
24
24
  return mkdtempSync(join(tmpdir(), "cueloop-jsonl-"));
@@ -27,10 +27,10 @@ function tmpHome(): string {
27
27
  describe("JSONL thread store", () => {
28
28
  test("a thread is written under a per-project bucket keyed by root commit", () => {
29
29
  const home = tmpHome();
30
- const store = new SessionStore(home);
30
+ const store = new ThreadStore(home);
31
31
  store.recover();
32
32
  store.upsert(
33
- createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z", {
33
+ createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z", {
34
34
  workspace: { repoRoot: "/repo", branch: "main", rootCommit: "abcdef0123456789" },
35
35
  }),
36
36
  );
@@ -51,9 +51,9 @@ describe("JSONL thread store", () => {
51
51
 
52
52
  test("a repo-less thread lands in the standalone bucket", () => {
53
53
  const home = tmpHome();
54
- const store = new SessionStore(home);
54
+ const store = new ThreadStore(home);
55
55
  store.recover();
56
- store.upsert(createTestSessionRecord("ses_s", "2026-09-01T00:00:00.000Z"));
56
+ store.upsert(createTestThreadRecord("ses_s", "2026-09-01T00:00:00.000Z"));
57
57
 
58
58
  expect(readdirSync(threadBucket(undefined, home))).toContain("ses_s.jsonl");
59
59
  });
@@ -61,10 +61,10 @@ describe("JSONL thread store", () => {
61
61
  test("boot migrates the old whole-record file and parks the original", () => {
62
62
  const home = tmpHome();
63
63
  mkdirSync(sessionsDir(home), { recursive: true });
64
- const legacy = createTestSessionRecord("ses_old", "2026-09-01T00:00:00.000Z");
64
+ const legacy = createTestThreadRecord("ses_old", "2026-09-01T00:00:00.000Z");
65
65
  writeFileSync(join(sessionsDir(home), "ses_old.json"), JSON.stringify(legacy));
66
66
 
67
- const store = new SessionStore(home);
67
+ const store = new ThreadStore(home);
68
68
  const report = store.recover();
69
69
 
70
70
  expect(report.recovered).toEqual(["ses_old"]);
@@ -79,10 +79,10 @@ describe("JSONL thread store", () => {
79
79
  test("migration never overwrites a newer JSONL with an older legacy snapshot", () => {
80
80
  const home = tmpHome();
81
81
  // a newer JSONL already holds this thread (e.g. written after a rollback)
82
- const store = new SessionStore(home);
82
+ const store = new ThreadStore(home);
83
83
  store.recover();
84
84
  store.upsert(
85
- createTestSessionRecord("ses_x", "2026-09-01T00:00:00.000Z", {
85
+ createTestThreadRecord("ses_x", "2026-09-01T00:00:00.000Z", {
86
86
  status: "pending",
87
87
  workspace: { repoRoot: "/repo", branch: "newer" },
88
88
  }),
@@ -92,13 +92,13 @@ describe("JSONL thread store", () => {
92
92
  writeFileSync(
93
93
  join(sessionsDir(home), "ses_x.json"),
94
94
  JSON.stringify(
95
- createTestSessionRecord("ses_x", "2026-09-01T00:00:00.000Z", {
95
+ createTestThreadRecord("ses_x", "2026-09-01T00:00:00.000Z", {
96
96
  workspace: { repoRoot: "/repo", branch: "older" },
97
97
  }),
98
98
  ),
99
99
  );
100
100
 
101
- const reopened = new SessionStore(home);
101
+ const reopened = new ThreadStore(home);
102
102
  reopened.recover();
103
103
  // the newer JSONL wins; the legacy snapshot is parked, not applied
104
104
  expect(reopened.get("ses_x")?.workspace.branch).toBe("newer");
@@ -107,12 +107,12 @@ describe("JSONL thread store", () => {
107
107
 
108
108
  test("a long log compacts to a single line, and the record survives it", () => {
109
109
  const home = tmpHome();
110
- const store = new SessionStore(home);
110
+ const store = new ThreadStore(home);
111
111
  store.recover();
112
112
  // more upserts than the compaction threshold
113
113
  for (let iteration = 0; iteration < 60; iteration++) {
114
114
  store.upsert(
115
- createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z", {
115
+ createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z", {
116
116
  status: "pending",
117
117
  workspace: { repoRoot: "/repo", branch: `b${iteration}` },
118
118
  }),
@@ -125,34 +125,34 @@ describe("JSONL thread store", () => {
125
125
  .filter((l) => l.trim());
126
126
  expect(lines.length).toBeLessThan(60);
127
127
  // the latest snapshot survives the compaction and a restart
128
- const reopened = new SessionStore(home);
128
+ const reopened = new ThreadStore(home);
129
129
  reopened.recover();
130
130
  expect(reopened.get("ses_a")?.workspace.branch).toBe("b59");
131
131
  });
132
132
 
133
133
  test("a crash's torn trailing line is skipped; the last valid snapshot wins", () => {
134
134
  const home = tmpHome();
135
- const store = new SessionStore(home);
135
+ const store = new ThreadStore(home);
136
136
  store.recover();
137
- store.upsert(createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z"));
137
+ store.upsert(createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z"));
138
138
 
139
139
  // simulate a crash mid-append: a half-written line at the end
140
140
  const file = join(threadBucket(undefined, home), "ses_a.jsonl");
141
141
  appendFileSync(file, '{"id":"ses_a","half');
142
142
 
143
- const reopened = new SessionStore(home);
143
+ const reopened = new ThreadStore(home);
144
144
  const report = reopened.recover();
145
145
  expect(report.recovered).toEqual(["ses_a"]);
146
146
  expect(reopened.get("ses_a")?.id).toBe("ses_a");
147
147
 
148
148
  // an update after a torn recovery must not fuse onto the torn fragment and vanish on restart
149
149
  reopened.upsert(
150
- createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z", {
150
+ createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z", {
151
151
  status: "pending",
152
152
  workspace: { repoRoot: "/repo", branch: "after-tear" },
153
153
  }),
154
154
  );
155
- const restarted = new SessionStore(home);
155
+ const restarted = new ThreadStore(home);
156
156
  restarted.recover();
157
157
  expect(restarted.get("ses_a")?.workspace.branch).toBe("after-tear");
158
158
  });
package/src/store.ts CHANGED
@@ -7,7 +7,7 @@
7
7
  * that fail to parse are skipped and reported, never deleted; records from before histories existed
8
8
  * are given one on read.
9
9
  *
10
- * `SessionRepository` is the contract every adapter satisfies; the conformance
10
+ * `ThreadRepository` is the contract every adapter satisfies; the conformance
11
11
  * suite in ./testing/store-conformance.ts pins it for the file store and the
12
12
  * in-memory store alike.
13
13
  */
@@ -24,9 +24,9 @@ import {
24
24
  writeFileSync,
25
25
  } from "node:fs";
26
26
  import { dirname, join } from "node:path";
27
- import { historyFromLinear, type ReviewSession } from "@cueloop/schema";
27
+ import { historyFromLinear, type Thread } from "@cueloop/schema";
28
28
  import { migratedSessionsDir, sessionsDir, threadBucket, threadsDir } from "./paths";
29
- import { validateSessionRecord } from "./validate";
29
+ import { validateThreadRecord } from "./validate";
30
30
 
31
31
  /** A thread's log is rewritten to a single snapshot line once it grows past this many, so an
32
32
  * active thread's file never bloats unbounded while writes stay cheap appends in between. */
@@ -38,13 +38,13 @@ export interface RecoveryReport {
38
38
  }
39
39
 
40
40
  /** What the daemon needs from session storage. */
41
- export interface SessionRepository {
41
+ export interface ThreadRepository {
42
42
  /** Load what is stored; called once on boot. */
43
43
  recover(): RecoveryReport;
44
- get(id: string): ReviewSession | undefined;
44
+ get(id: string): Thread | undefined;
45
45
  /** Every session, oldest first. */
46
- list(): ReviewSession[];
47
- upsert(session: ReviewSession): void;
46
+ list(): Thread[];
47
+ upsert(session: Thread): void;
48
48
  /** True when a session was removed. */
49
49
  delete(id: string): boolean;
50
50
  }
@@ -54,19 +54,19 @@ export interface SessionRepository {
54
54
  * one. A record with no revision has no head to derive from and keeps
55
55
  * reading without a history - migration never loses a record.
56
56
  */
57
- export function withHistory(session: ReviewSession): ReviewSession {
57
+ export function withHistory(session: Thread): Thread {
58
58
  if (session.history || session.revisions.length === 0) return session;
59
59
 
60
60
  return { ...session, history: historyFromLinear(session) };
61
61
  }
62
62
 
63
63
  /** Records in the order `list()` promises: oldest first. */
64
- function oldestFirst(sessions: Iterable<ReviewSession>): ReviewSession[] {
64
+ function oldestFirst(sessions: Iterable<Thread>): Thread[] {
65
65
  return [...sessions].sort((left, right) => left.createdAt.localeCompare(right.createdAt));
66
66
  }
67
67
 
68
- export class SessionStore implements SessionRepository {
69
- private sessions = new Map<string, ReviewSession>();
68
+ export class ThreadStore implements ThreadRepository {
69
+ private sessions = new Map<string, Thread>();
70
70
  /** id -> its JSONL path (the bucket depends on the record's root commit, so it is tracked). */
71
71
  private files = new Map<string, string>();
72
72
  /** id -> lines in its log, so upsert knows when to compact instead of append. */
@@ -124,10 +124,10 @@ export class SessionStore implements SessionRepository {
124
124
  for (const file of readdirSync(legacyDir)) {
125
125
  if (!file.endsWith(".json")) continue;
126
126
  const legacyPath = join(legacyDir, file);
127
- let session: ReviewSession;
127
+ let session: Thread;
128
128
 
129
129
  try {
130
- const parsed = validateSessionRecord(JSON.parse(readFileSync(legacyPath, "utf8")));
130
+ const parsed = validateThreadRecord(JSON.parse(readFileSync(legacyPath, "utf8")));
131
131
 
132
132
  if (!parsed.ok) throw new Error(`invalid record - ${parsed.error}`);
133
133
  session = withHistory(parsed.value);
@@ -155,7 +155,7 @@ export class SessionStore implements SessionRepository {
155
155
  }
156
156
 
157
157
  /** Rewrite the thread to a single snapshot line, atomically - the first write and every compaction. */
158
- private writeWhole(session: ReviewSession): void {
158
+ private writeWhole(session: Thread): void {
159
159
  const filePath =
160
160
  this.files.get(session.id) ??
161
161
  join(threadBucket(session.workspace.rootCommit, this.home), `${session.id}.jsonl`);
@@ -169,15 +169,15 @@ export class SessionStore implements SessionRepository {
169
169
  this.lineCounts.set(session.id, 1);
170
170
  }
171
171
 
172
- get(id: string): ReviewSession | undefined {
172
+ get(id: string): Thread | undefined {
173
173
  return this.sessions.get(id);
174
174
  }
175
175
 
176
- list(): ReviewSession[] {
176
+ list(): Thread[] {
177
177
  return oldestFirst(this.sessions.values());
178
178
  }
179
179
 
180
- upsert(session: ReviewSession): void {
180
+ upsert(session: Thread): void {
181
181
  this.sessions.set(session.id, session);
182
182
  const filePath = this.files.get(session.id);
183
183
  const lines = this.lineCounts.get(session.id) ?? 0;
@@ -223,7 +223,7 @@ function readThread(filePath: string) {
223
223
  } catch {
224
224
  continue;
225
225
  }
226
- const parsed = validateSessionRecord(record);
226
+ const parsed = validateThreadRecord(record);
227
227
 
228
228
  if (parsed.ok) {
229
229
  return {
@@ -241,8 +241,8 @@ function readThread(filePath: string) {
241
241
  * in for what a file store finds on recovery, so validation and migration are
242
242
  * exercised the same way.
243
243
  */
244
- export class MemorySessionStore implements SessionRepository {
245
- private sessions = new Map<string, ReviewSession>();
244
+ export class MemoryThreadStore implements ThreadRepository {
245
+ private sessions = new Map<string, Thread>();
246
246
 
247
247
  constructor(private readonly seed: unknown[] = []) {}
248
248
 
@@ -250,7 +250,7 @@ export class MemorySessionStore implements SessionRepository {
250
250
  const report: RecoveryReport = { recovered: [], skipped: [] };
251
251
 
252
252
  this.seed.forEach((record, index) => {
253
- const parsed = validateSessionRecord(record);
253
+ const parsed = validateThreadRecord(record);
254
254
 
255
255
  if (!parsed.ok) {
256
256
  report.skipped.push({ file: `seed[${index}]`, error: `invalid record - ${parsed.error}` });
@@ -266,15 +266,15 @@ export class MemorySessionStore implements SessionRepository {
266
266
  return report;
267
267
  }
268
268
 
269
- get(id: string): ReviewSession | undefined {
269
+ get(id: string): Thread | undefined {
270
270
  return this.sessions.get(id);
271
271
  }
272
272
 
273
- list(): ReviewSession[] {
273
+ list(): Thread[] {
274
274
  return oldestFirst(this.sessions.values());
275
275
  }
276
276
 
277
- upsert(session: ReviewSession): void {
277
+ upsert(session: Thread): void {
278
278
  this.sessions.set(session.id, session);
279
279
  }
280
280
 
@@ -282,3 +282,14 @@ export class MemorySessionStore implements SessionRepository {
282
282
  return this.sessions.delete(id);
283
283
  }
284
284
  }
285
+
286
+ /** @deprecated use ThreadRepository */
287
+ export type SessionRepository = ThreadRepository;
288
+ /** @deprecated use ThreadStore */
289
+ export const SessionStore = ThreadStore;
290
+ /** @deprecated use ThreadStore */
291
+ export type SessionStore = ThreadStore;
292
+ /** @deprecated use MemoryThreadStore */
293
+ export const MemorySessionStore = MemoryThreadStore;
294
+ /** @deprecated use MemoryThreadStore */
295
+ export type MemorySessionStore = MemoryThreadStore;
@@ -8,21 +8,21 @@
8
8
  */
9
9
 
10
10
  import { describe, expect, test } from "bun:test";
11
- import { SCHEMA_VERSION, type ReviewSession } from "@cueloop/schema";
12
- import type { SessionRepository } from "../store";
11
+ import { SCHEMA_VERSION, type Thread } from "@cueloop/schema";
12
+ import type { ThreadRepository } from "../store";
13
13
 
14
14
  export interface StoreHarness {
15
15
  /** A fresh, empty adapter that will recover `records` on `recover()`. */
16
- open: (records: unknown[]) => SessionRepository;
16
+ open: (records: unknown[]) => ThreadRepository;
17
17
  /** A new adapter over whatever `store` persisted, as after a daemon restart. */
18
- restart: (store: SessionRepository) => SessionRepository;
18
+ restart: (store: ThreadRepository) => ThreadRepository;
19
19
  }
20
20
 
21
- export function createTestSessionRecord(
21
+ export function createTestThreadRecord(
22
22
  id: string,
23
23
  createdAt: string,
24
- overrides: Partial<ReviewSession> = {},
25
- ): ReviewSession {
24
+ overrides: Partial<Thread> = {},
25
+ ): Thread {
26
26
  return {
27
27
  schemaVersion: SCHEMA_VERSION,
28
28
  id,
@@ -37,15 +37,15 @@ export function createTestSessionRecord(
37
37
  };
38
38
  }
39
39
 
40
- export function runSessionStoreConformance(name: string, harness: StoreHarness): void {
40
+ export function runThreadStoreConformance(name: string, harness: StoreHarness): void {
41
41
  describe(`${name} - session repository contract`, () => {
42
42
  test("a stored session reads back whole and lists oldest first", () => {
43
43
  // Arrange
44
44
  const store = harness.open([]);
45
45
 
46
46
  store.recover();
47
- const newer = createTestSessionRecord("ses_b", "2026-09-02T00:00:00.000Z");
48
- const older = createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z");
47
+ const newer = createTestThreadRecord("ses_b", "2026-09-02T00:00:00.000Z");
48
+ const older = createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z");
49
49
 
50
50
  // Act
51
51
  store.upsert(newer);
@@ -62,11 +62,11 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
62
62
  const store = harness.open([]);
63
63
 
64
64
  store.recover();
65
- store.upsert(createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z"));
65
+ store.upsert(createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z"));
66
66
 
67
67
  // Act
68
68
  store.upsert(
69
- createTestSessionRecord("ses_a", "2026-09-01T00:00:00.000Z", { status: "resolved" }),
69
+ createTestThreadRecord("ses_a", "2026-09-01T00:00:00.000Z", { status: "resolved" }),
70
70
  );
71
71
 
72
72
  // Assert
@@ -82,8 +82,8 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
82
82
  const store = harness.open([]);
83
83
 
84
84
  store.recover();
85
- store.upsert(createTestSessionRecord("ses_keep", "2026-09-01T00:00:00.000Z"));
86
- store.upsert(createTestSessionRecord("ses_gone", "2026-09-02T00:00:00.000Z"));
85
+ store.upsert(createTestThreadRecord("ses_keep", "2026-09-01T00:00:00.000Z"));
86
+ store.upsert(createTestThreadRecord("ses_gone", "2026-09-02T00:00:00.000Z"));
87
87
  store.delete("ses_gone");
88
88
 
89
89
  // Act
@@ -98,7 +98,7 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
98
98
 
99
99
  test("a record without a history reads as a one-branch tree whose head is the artifact", () => {
100
100
  // Arrange: written before histories existed, with two revisions and a comment
101
- const legacy = createTestSessionRecord("ses_old", "2026-09-01T00:00:00.000Z", {
101
+ const legacy = createTestThreadRecord("ses_old", "2026-09-01T00:00:00.000Z", {
102
102
  artifact: { type: "plan", content: "v2", meta: {} },
103
103
  revisions: [
104
104
  { revision: 1, content: "v1", submittedAt: "2026-09-01T00:00:00.000Z" },
@@ -135,7 +135,7 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
135
135
  test("a record with no revision still recovers, without a history", () => {
136
136
  // Arrange
137
137
  const store = harness.open([
138
- createTestSessionRecord("ses_empty", "2026-09-01T00:00:00.000Z", { revisions: [] }),
138
+ createTestThreadRecord("ses_empty", "2026-09-01T00:00:00.000Z", { revisions: [] }),
139
139
  ]);
140
140
 
141
141
  // Act
@@ -148,7 +148,7 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
148
148
 
149
149
  test("a record that already carries a history keeps it untouched", () => {
150
150
  // Arrange
151
- const withTree = createTestSessionRecord("ses_tree", "2026-09-01T00:00:00.000Z", {
151
+ const withTree = createTestThreadRecord("ses_tree", "2026-09-01T00:00:00.000Z", {
152
152
  history: {
153
153
  entries: [
154
154
  {
@@ -177,7 +177,7 @@ export function runSessionStoreConformance(name: string, harness: StoreHarness):
177
177
  test("a record that fails validation is skipped and reported, never dropped from the report", () => {
178
178
  // Arrange
179
179
  const store = harness.open([
180
- createTestSessionRecord("ses_good", "2026-09-01T00:00:00.000Z"),
180
+ createTestThreadRecord("ses_good", "2026-09-01T00:00:00.000Z"),
181
181
  { schemaVersion: SCHEMA_VERSION, id: "ses_bad" },
182
182
  ]);
183
183
 
@@ -11,12 +11,12 @@ import {
11
11
  ArtifactSchema,
12
12
  IdentitySchema,
13
13
  RevisionSchema,
14
- SessionRecordSchema,
14
+ ThreadRecordSchema,
15
15
  VerdictSchema,
16
16
  WorkspaceSchema,
17
17
  isKnownMethod,
18
18
  parseParams,
19
- validateSessionRecord,
19
+ validateThreadRecord,
20
20
  } from "./validate";
21
21
  import {
22
22
  ARTIFACT_TYPES,
@@ -26,7 +26,7 @@ import {
26
26
  type Artifact,
27
27
  type ArtifactMeta,
28
28
  type Identity,
29
- type ReviewSession,
29
+ type Thread,
30
30
  type Revision,
31
31
  type Verdict,
32
32
  type WorkspaceKey,
@@ -132,7 +132,7 @@ describe("parseParams", () => {
132
132
  });
133
133
  });
134
134
 
135
- describe("validateSessionRecord", () => {
135
+ describe("validateThreadRecord", () => {
136
136
  const record = {
137
137
  schemaVersion: SCHEMA_VERSION,
138
138
  id: "ses_1",
@@ -146,12 +146,12 @@ describe("validateSessionRecord", () => {
146
146
  };
147
147
 
148
148
  test("accepts a valid record", () => {
149
- expect(validateSessionRecord(record).ok).toBe(true);
149
+ expect(validateThreadRecord(record).ok).toBe(true);
150
150
  });
151
151
 
152
152
  test("rejects a foreign schema version with a readable reason", () => {
153
153
  // Act
154
- const result = validateSessionRecord({ ...record, schemaVersion: "99" });
154
+ const result = validateThreadRecord({ ...record, schemaVersion: "99" });
155
155
 
156
156
  // Assert
157
157
  expect(result.ok).toBe(false);
@@ -160,7 +160,7 @@ describe("validateSessionRecord", () => {
160
160
 
161
161
  test("rejects a structurally broken record", () => {
162
162
  // Act
163
- const result = validateSessionRecord({ ...record, revisions: "nope" });
163
+ const result = validateThreadRecord({ ...record, revisions: "nope" });
164
164
 
165
165
  // Assert
166
166
  expect(result.ok).toBe(false);
@@ -235,7 +235,7 @@ describe("wire pins", () => {
235
235
  name: "Al",
236
236
  handle: "abc",
237
237
  };
238
- const fullSession: Required<ReviewSession> = {
238
+ const fullSession: Required<Thread> = {
239
239
  schemaVersion: SCHEMA_VERSION,
240
240
  id: "ses_1",
241
241
  workspace: fullWorkspace,
@@ -298,7 +298,7 @@ describe("wire pins", () => {
298
298
  };
299
299
 
300
300
  // Act
301
- const parsed = validateSessionRecord(record);
301
+ const parsed = validateThreadRecord(record);
302
302
 
303
303
  // Assert
304
304
  expect(parsed.ok).toBe(false);
@@ -317,9 +317,9 @@ describe("wire pins", () => {
317
317
  expect(entryKeys(RevisionSchema)).toEqual(keys(fullRevision));
318
318
  expect(entryKeys(VerdictSchema)).toEqual(keys(fullVerdict));
319
319
  expect(entryKeys(IdentitySchema)).toEqual(keys(fullIdentity));
320
- expect(entryKeys(SessionRecordSchema)).toEqual(keys(fullSession));
320
+ expect(entryKeys(ThreadRecordSchema)).toEqual(keys(fullSession));
321
321
  // persisted annotations carry the stamped createdAt
322
- const stored = SessionRecordSchema.entries.annotations.item;
322
+ const stored = ThreadRecordSchema.entries.annotations.item;
323
323
 
324
324
  expect(entryKeys(stored)).toEqual(keys(fullAnnotation));
325
325
  });
package/src/validate.ts CHANGED
@@ -20,7 +20,7 @@ import {
20
20
  type ArtifactMeta,
21
21
  type DiffFileContents,
22
22
  type Identity,
23
- type ReviewSession,
23
+ type Thread,
24
24
  type HunkRejection,
25
25
  type Revision,
26
26
  type SessionHistory,
@@ -153,6 +153,12 @@ export const Params = {
153
153
  annotation: AnnotationSchema,
154
154
  authorName: v.optional(v.string()),
155
155
  }),
156
+ // "session.comment" is the primary annotate method; "session.annotate" stays an accepted alias
157
+ "session.comment": v.object({
158
+ id: SessionId,
159
+ annotation: AnnotationSchema,
160
+ authorName: v.optional(v.string()),
161
+ }),
156
162
  "session.removeAnnotation": v.object({ id: SessionId, annotationId: NonEmpty }),
157
163
  "session.setParticipantName": v.object({ id: SessionId, author: NonEmpty, name: NonEmpty }),
158
164
  "session.setWorkingCopy": v.object({ id: SessionId, workingCopy: v.optional(v.string()) }),
@@ -315,7 +321,7 @@ export const SessionHistorySchema = v.pipe(
315
321
  );
316
322
 
317
323
  /** Persisted records are validated on recovery: a bad file is skipped, not fatal. */
318
- export const SessionRecordSchema = v.object({
324
+ export const ThreadRecordSchema = v.object({
319
325
  schemaVersion: v.literal(SCHEMA_VERSION),
320
326
  id: NonEmpty,
321
327
  workspace: WorkspaceSchema,
@@ -343,12 +349,12 @@ export const SessionRecordSchema = v.object({
343
349
  shareBranch: v.optional(v.string()),
344
350
  owner: v.optional(v.string()),
345
351
  participants: v.optional(v.array(IdentitySchema)),
346
- } satisfies EntriesOf<ReviewSession>);
352
+ } satisfies EntriesOf<Thread>);
347
353
 
348
- export function validateSessionRecord(
354
+ export function validateThreadRecord(
349
355
  raw: Parameters<typeof v.safeParse>[1],
350
- ): { ok: true; value: v.InferOutput<typeof SessionRecordSchema> } | { ok: false; error: string } {
351
- const result = v.safeParse(SessionRecordSchema, raw);
356
+ ): { ok: true; value: v.InferOutput<typeof ThreadRecordSchema> } | { ok: false; error: string } {
357
+ const result = v.safeParse(ThreadRecordSchema, raw);
352
358
 
353
359
  if (result.success) return { ok: true, value: result.output };
354
360
  const issue = result.issues[0]!;
@@ -356,3 +362,8 @@ export function validateSessionRecord(
356
362
 
357
363
  return { ok: false, error: `${path ? path + ": " : ""}${issue.message}` };
358
364
  }
365
+
366
+ /** @deprecated use ThreadRecordSchema */
367
+ export const SessionRecordSchema = ThreadRecordSchema;
368
+ /** @deprecated use validateThreadRecord */
369
+ export const validateSessionRecord = validateThreadRecord;