@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.
package/src/client.ts CHANGED
@@ -11,7 +11,7 @@ import type {
11
11
  Artifact,
12
12
  DiffFileStatus,
13
13
  HunkRejection,
14
- ReviewSession,
14
+ Thread,
15
15
  VerdictKind,
16
16
  WorkspaceKey,
17
17
  } from "@cueloop/schema";
@@ -29,7 +29,7 @@ import type { SharedMerge } from "./api";
29
29
 
30
30
  export type { SharedMerge } from "./api";
31
31
  import { cueloopHome, ownerTokenPath, socketPath } from "./paths";
32
- import { Params, SessionRecordSchema, DiffFileContentsSchema } from "./validate";
32
+ import { Params, ThreadRecordSchema, DiffFileContentsSchema } from "./validate";
33
33
  import type { WorkingTreeDiff } from "./working-tree";
34
34
  import { DAEMON_VERSION } from "./version";
35
35
 
@@ -66,25 +66,31 @@ const HerdrTabResultSchema = v.nullable(v.object({ tabId: v.string(), paneId: v.
66
66
  export interface SessionClient {
67
67
  onEvent(listener: (event: EventFrame) => void): () => void;
68
68
  subscribe(): Promise<void>;
69
- sessionGet(id: string): Promise<ReviewSession>;
70
- sessionList(filter?: { status?: "pending" | "resolved" }): Promise<ReviewSession[]>;
69
+ sessionGet(id: string): Promise<Thread>;
70
+ sessionList(filter?: { status?: "pending" | "resolved" }): Promise<Thread[]>;
71
+ /** Add a comment; the primary annotate method. `sessionAnnotate` is the retained alias. */
72
+ sessionComment(
73
+ id: string,
74
+ annotation: Omit<Annotation, "createdAt">,
75
+ authorName?: string,
76
+ ): Promise<Thread>;
71
77
  sessionAnnotate(
72
78
  id: string,
73
79
  annotation: Omit<Annotation, "createdAt">,
74
80
  authorName?: string,
75
- ): Promise<ReviewSession>;
81
+ ): Promise<Thread>;
76
82
  /** Remove a comment; a non-owner connection removes only the comments of the author it is bound to. */
77
- sessionRemoveAnnotation(id: string, annotationId: string): Promise<ReviewSession>;
78
- sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Promise<ReviewSession>;
83
+ sessionRemoveAnnotation(id: string, annotationId: string): Promise<Thread>;
84
+ sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Promise<Thread>;
79
85
  /** Cut the `blockIndex`-th block of the working copy. */
80
- sessionCutBlock(id: string, blockIndex: number): Promise<ReviewSession>;
86
+ sessionCutBlock(id: string, blockIndex: number): Promise<Thread>;
81
87
  /** Re-insert the `baseBlockIndex`-th block of the submitted revision before `line` (default: the end). */
82
- sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number): Promise<ReviewSession>;
88
+ sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number): Promise<Thread>;
83
89
  /** Replace a diff review's reject decisions; the working copy follows. */
84
- sessionCurate(id: string, rejections: HunkRejection[]): Promise<ReviewSession>;
85
- sessionSetViewed(id: string, viewedPaths: string[]): Promise<ReviewSession>;
90
+ sessionCurate(id: string, rejections: HunkRejection[]): Promise<Thread>;
91
+ sessionSetViewed(id: string, viewedPaths: string[]): Promise<Thread>;
86
92
  /** Rename a session's display title; an empty title restores the derived default. */
87
- sessionSetTitle(id: string, title: string): Promise<ReviewSession>;
93
+ sessionSetTitle(id: string, title: string): Promise<Thread>;
88
94
  /** Tracked, repo-relative file paths for the session's workspace; a client with no local repo (a share) omits it. */
89
95
  projectFiles?(sessionId: string): Promise<string[]>;
90
96
  /** UTF-8 contents of a repo-relative file, or null when it cannot be read safely; omitted by a client with no local repo. */
@@ -98,32 +104,27 @@ export interface SessionClient {
98
104
  /** The live working-tree diff (patch plus per-file contents) at `cwd`. */
99
105
  repoDiff?(cwd: string): Promise<WorkingTreeDiff>;
100
106
  /** Find-or-create the per-repo workbench thread for `cwd`, so a bare launch's first comment persists. */
101
- sessionWorkbench?(cwd: string): Promise<ReviewSession>;
107
+ sessionWorkbench?(cwd: string): Promise<Thread>;
102
108
  /** Move a branch's tip (the current one, or `branch` after switching to it) back to an entry on its path; a summary records the abandoned segment. */
103
- sessionNavigate(
104
- id: string,
105
- entryId: string,
106
- summary?: string,
107
- branch?: string,
108
- ): Promise<ReviewSession>;
109
+ sessionNavigate(id: string, entryId: string, summary?: string, branch?: string): Promise<Thread>;
109
110
  /** Start a branch at the current tip and switch to it. */
110
- sessionBranch(id: string, name: string): Promise<ReviewSession>;
111
- sessionSwitch(id: string, branch: string): Promise<ReviewSession>;
111
+ sessionBranch(id: string, name: string): Promise<Thread>;
112
+ sessionSwitch(id: string, branch: string): Promise<Thread>;
112
113
  /** Name the current tip as a checkpoint. */
113
- sessionLabel(id: string, label: string): Promise<ReviewSession>;
114
+ sessionLabel(id: string, label: string): Promise<Thread>;
114
115
  /** Copy the current path into a new session; returns the fork. */
115
- sessionFork(id: string): Promise<ReviewSession>;
116
- sessionSetShareId(id: string, shareId: string): Promise<ReviewSession>;
117
- sessionMergeShared(id: string, incoming: SharedMerge): Promise<ReviewSession>;
116
+ sessionFork(id: string): Promise<Thread>;
117
+ sessionSetShareId(id: string, shareId: string): Promise<Thread>;
118
+ sessionMergeShared(id: string, incoming: SharedMerge): Promise<Thread>;
118
119
  sessionDelete(id: string): Promise<void>;
119
120
  /** Record the caller's own identity name (collaborator self-naming on a share). */
120
- sessionSetSelfName(id: string, name: string): Promise<ReviewSession>;
121
+ sessionSetSelfName(id: string, name: string): Promise<Thread>;
121
122
  sessionResolve(
122
123
  id: string,
123
124
  verdictKind: VerdictKind,
124
125
  summary: string,
125
126
  actionBodies?: Record<string, string>,
126
- ): Promise<ReviewSession>;
127
+ ): Promise<Thread>;
127
128
  close(): void;
128
129
  }
129
130
 
@@ -374,84 +375,86 @@ export class DaemonClient implements SessionClient {
374
375
  subscribe(): Promise<void> {
375
376
  return this.request("events.subscribe", {}, EmptyResultSchema).then(() => undefined);
376
377
  }
377
- sessionCreate(workspace: WorkspaceKey, artifact: Artifact): Promise<ReviewSession> {
378
- return this.request("session.create", { workspace, artifact }, SessionRecordSchema);
378
+ sessionCreate(workspace: WorkspaceKey, artifact: Artifact): Promise<Thread> {
379
+ return this.request("session.create", { workspace, artifact }, ThreadRecordSchema);
379
380
  }
380
- sessionGet(id: string): Promise<ReviewSession> {
381
- return this.request("session.get", { id }, SessionRecordSchema);
381
+ sessionGet(id: string): Promise<Thread> {
382
+ return this.request("session.get", { id }, ThreadRecordSchema);
382
383
  }
383
- sessionList(filter?: { status?: "pending" | "resolved" }): Promise<ReviewSession[]> {
384
- return this.request("session.list", { filter }, v.array(SessionRecordSchema));
384
+ sessionList(filter?: { status?: "pending" | "resolved" }): Promise<Thread[]> {
385
+ return this.request("session.list", { filter }, v.array(ThreadRecordSchema));
385
386
  }
386
387
  /** Long-poll; null = still pending after timeoutMs (re-poll to collect). */
387
- sessionWait(id: string, timeoutMs: number): Promise<ReviewSession | null> {
388
+ sessionWait(id: string, timeoutMs: number): Promise<Thread | null> {
388
389
  return this.request(
389
390
  "session.wait",
390
391
  { id, timeoutMs },
391
- v.nullable(SessionRecordSchema),
392
+ v.nullable(ThreadRecordSchema),
392
393
  timeoutMs + 10_000,
393
394
  );
394
395
  }
396
+ sessionComment(
397
+ id: string,
398
+ annotation: Omit<Annotation, "createdAt">,
399
+ authorName?: string,
400
+ ): Promise<Thread> {
401
+ return this.request("session.comment", { id, annotation, authorName }, ThreadRecordSchema);
402
+ }
395
403
  sessionAnnotate(
396
404
  id: string,
397
405
  annotation: Omit<Annotation, "createdAt">,
398
406
  authorName?: string,
399
- ): Promise<ReviewSession> {
400
- return this.request("session.annotate", { id, annotation, authorName }, SessionRecordSchema);
407
+ ): Promise<Thread> {
408
+ return this.request("session.annotate", { id, annotation, authorName }, ThreadRecordSchema);
401
409
  }
402
- sessionRemoveAnnotation(id: string, annotationId: string): Promise<ReviewSession> {
403
- return this.request("session.removeAnnotation", { id, annotationId }, SessionRecordSchema);
410
+ sessionRemoveAnnotation(id: string, annotationId: string): Promise<Thread> {
411
+ return this.request("session.removeAnnotation", { id, annotationId }, ThreadRecordSchema);
404
412
  }
405
413
  /** Register a display name for a participant - a collaborator's or an agent's own, on a share or locally. */
406
- sessionSetParticipantName(id: string, author: string, name: string): Promise<ReviewSession> {
407
- return this.request("session.setParticipantName", { id, author, name }, SessionRecordSchema);
414
+ sessionSetParticipantName(id: string, author: string, name: string): Promise<Thread> {
415
+ return this.request("session.setParticipantName", { id, author, name }, ThreadRecordSchema);
408
416
  }
409
- sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Promise<ReviewSession> {
410
- return this.request("session.setWorkingCopy", { id, workingCopy }, SessionRecordSchema);
417
+ sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Promise<Thread> {
418
+ return this.request("session.setWorkingCopy", { id, workingCopy }, ThreadRecordSchema);
411
419
  }
412
- sessionCutBlock(id: string, blockIndex: number): Promise<ReviewSession> {
413
- return this.request("session.cutBlock", { id, blockIndex }, SessionRecordSchema);
420
+ sessionCutBlock(id: string, blockIndex: number): Promise<Thread> {
421
+ return this.request("session.cutBlock", { id, blockIndex }, ThreadRecordSchema);
414
422
  }
415
- sessionNavigate(
416
- id: string,
417
- entryId: string,
418
- summary?: string,
419
- branch?: string,
420
- ): Promise<ReviewSession> {
423
+ sessionNavigate(id: string, entryId: string, summary?: string, branch?: string): Promise<Thread> {
421
424
  const params: v.InferInput<(typeof Params)["session.navigate"]> = { id, entryId };
422
425
 
423
426
  if (summary !== undefined) params.summary = summary;
424
427
  if (branch !== undefined) params.branch = branch;
425
428
 
426
- return this.request("session.navigate", params, SessionRecordSchema);
429
+ return this.request("session.navigate", params, ThreadRecordSchema);
427
430
  }
428
- sessionBranch(id: string, name: string): Promise<ReviewSession> {
429
- return this.request("session.branch", { id, name }, SessionRecordSchema);
431
+ sessionBranch(id: string, name: string): Promise<Thread> {
432
+ return this.request("session.branch", { id, name }, ThreadRecordSchema);
430
433
  }
431
- sessionSwitch(id: string, branch: string): Promise<ReviewSession> {
432
- return this.request("session.switch", { id, branch }, SessionRecordSchema);
434
+ sessionSwitch(id: string, branch: string): Promise<Thread> {
435
+ return this.request("session.switch", { id, branch }, ThreadRecordSchema);
433
436
  }
434
- sessionLabel(id: string, label: string): Promise<ReviewSession> {
435
- return this.request("session.label", { id, label }, SessionRecordSchema);
437
+ sessionLabel(id: string, label: string): Promise<Thread> {
438
+ return this.request("session.label", { id, label }, ThreadRecordSchema);
436
439
  }
437
- sessionFork(id: string): Promise<ReviewSession> {
438
- return this.request("session.fork", { id }, SessionRecordSchema);
440
+ sessionFork(id: string): Promise<Thread> {
441
+ return this.request("session.fork", { id }, ThreadRecordSchema);
439
442
  }
440
- sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number): Promise<ReviewSession> {
443
+ sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number): Promise<Thread> {
441
444
  return this.request(
442
445
  "session.restoreBlock",
443
446
  line === undefined ? { id, baseBlockIndex } : { id, baseBlockIndex, line },
444
- SessionRecordSchema,
447
+ ThreadRecordSchema,
445
448
  );
446
449
  }
447
- sessionCurate(id: string, rejections: HunkRejection[]): Promise<ReviewSession> {
448
- return this.request("session.curate", { id, rejections }, SessionRecordSchema);
450
+ sessionCurate(id: string, rejections: HunkRejection[]): Promise<Thread> {
451
+ return this.request("session.curate", { id, rejections }, ThreadRecordSchema);
449
452
  }
450
- sessionSetViewed(id: string, viewedPaths: string[]): Promise<ReviewSession> {
451
- return this.request("session.setViewed", { id, viewedPaths }, SessionRecordSchema);
453
+ sessionSetViewed(id: string, viewedPaths: string[]): Promise<Thread> {
454
+ return this.request("session.setViewed", { id, viewedPaths }, ThreadRecordSchema);
452
455
  }
453
- sessionSetTitle(id: string, title: string): Promise<ReviewSession> {
454
- return this.request("session.setTitle", { id, title }, SessionRecordSchema);
456
+ sessionSetTitle(id: string, title: string): Promise<Thread> {
457
+ return this.request("session.setTitle", { id, title }, ThreadRecordSchema);
455
458
  }
456
459
  projectFiles(sessionId: string): Promise<string[]> {
457
460
  return this.request("session.projectFiles", { id: sessionId }, v.array(v.string()));
@@ -479,24 +482,24 @@ export class DaemonClient implements SessionClient {
479
482
  v.object({ patch: v.string(), files: v.array(DiffFileContentsSchema) }),
480
483
  );
481
484
  }
482
- sessionWorkbench(cwd: string): Promise<ReviewSession> {
483
- return this.request("session.workbench", { cwd }, SessionRecordSchema);
485
+ sessionWorkbench(cwd: string): Promise<Thread> {
486
+ return this.request("session.workbench", { cwd }, ThreadRecordSchema);
484
487
  }
485
488
  /** Re-capture a diff session's working tree; changed=true when the patch moved and an event fired. */
486
489
  sessionRefreshDiff(id: string): Promise<{ changed: boolean }> {
487
490
  return this.request("session.refreshDiff", { id }, RefreshDiffResultSchema);
488
491
  }
489
- sessionSetShareId(id: string, shareId: string): Promise<ReviewSession> {
490
- return this.request("session.setShareId", { id, shareId }, SessionRecordSchema);
492
+ sessionSetShareId(id: string, shareId: string): Promise<Thread> {
493
+ return this.request("session.setShareId", { id, shareId }, ThreadRecordSchema);
491
494
  }
492
- sessionMergeShared(id: string, incoming: SharedMerge): Promise<ReviewSession> {
493
- return this.request("session.mergeShared", { id, ...incoming }, SessionRecordSchema);
495
+ sessionMergeShared(id: string, incoming: SharedMerge): Promise<Thread> {
496
+ return this.request("session.mergeShared", { id, ...incoming }, ThreadRecordSchema);
494
497
  }
495
498
  sessionDelete(id: string): Promise<void> {
496
499
  return this.request("session.delete", { id }, EmptyResultSchema).then(() => undefined);
497
500
  }
498
501
  /** Local sessions have no collaborator self-name; the share client owns this. */
499
- sessionSetSelfName(id: string, _name: string): Promise<ReviewSession> {
502
+ sessionSetSelfName(id: string, _name: string): Promise<Thread> {
500
503
  return this.sessionGet(id);
501
504
  }
502
505
  sessionResolve(
@@ -504,22 +507,22 @@ export class DaemonClient implements SessionClient {
504
507
  verdictKind: VerdictKind,
505
508
  summary: string,
506
509
  actionBodies?: Record<string, string>,
507
- ): Promise<ReviewSession> {
510
+ ): Promise<Thread> {
508
511
  return this.request(
509
512
  "session.resolve",
510
513
  { id, verdictKind, summary, actionBodies },
511
- SessionRecordSchema,
514
+ ThreadRecordSchema,
512
515
  );
513
516
  }
514
517
  sessionSubmitRevision(
515
518
  id: string,
516
519
  content: string,
517
520
  addressedAnnotationIds: string[] = [],
518
- ): Promise<ReviewSession> {
521
+ ): Promise<Thread> {
519
522
  return this.request(
520
523
  "session.submitRevision",
521
524
  { id, content, addressedAnnotationIds },
522
- SessionRecordSchema,
525
+ ThreadRecordSchema,
523
526
  );
524
527
  }
525
528
  /** herdr adapter scratch: the tab opened for a review; local-only, off the SessionClient contract. */
@@ -4,7 +4,7 @@ import { afterAll, describe, expect, test } from "bun:test";
4
4
  import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
- import type { ReviewSession } from "@cueloop/schema";
7
+ import type { Thread } from "@cueloop/schema";
8
8
  import { openHerdrPane, openHerdrPaneForReview } from "./herdr-pane";
9
9
  import type { HerdrTabPersistence } from "./herdr-pane";
10
10
  import type { HerdrTabHandle } from "./herdr-tab-store";
@@ -58,7 +58,7 @@ function fakePersistence(initial: HerdrTabHandle | null = null) {
58
58
  };
59
59
  }
60
60
 
61
- function newSession(overrides: Partial<ReviewSession> = {}): ReviewSession {
61
+ function newSession(overrides: Partial<Thread> = {}): Thread {
62
62
  return {
63
63
  schemaVersion: "1",
64
64
  id: "ses_new1",
package/src/herdr-pane.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  * and never blocks review creation; each spawn is bounded by a timeout.
7
7
  */
8
8
 
9
- import { detectHerdr, type HerdrEnv, type ReviewSession } from "@cueloop/schema";
9
+ import { detectHerdr, type HerdrEnv, type Thread } from "@cueloop/schema";
10
10
  import * as v from "valibot";
11
11
  import type { HerdrTabHandle } from "./herdr-tab-store";
12
12
 
@@ -117,7 +117,7 @@ function focusHerdrTab(binPath: string, tabId: string): void {
117
117
  * its handle. No-op outside herdr.
118
118
  */
119
119
  export async function openHerdrPaneForReview(
120
- session: ReviewSession,
120
+ session: Thread,
121
121
  persistence: HerdrTabPersistence,
122
122
  env: HerdrEnv = process.env,
123
123
  ): Promise<void> {
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * herdr adapter scratch, kept out of the core session record: the tab cueloop
3
3
  * opened to render each review, keyed by session id. One JSON map, atomic write.
4
- * The core `ReviewSession` never mentions herdr; this side-store is the whole
4
+ * The core `Thread` never mentions herdr; this side-store is the whole
5
5
  * leak boundary.
6
6
  */
7
7
 
@@ -4,8 +4,8 @@ import { describe, expect, test } from "bun:test";
4
4
  import { mkdirSync, mkdtempSync, utimesSync, writeFileSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
- import type { ReviewSession, SessionStatus } from "@cueloop/schema";
8
- import { SessionStore } from "./store";
7
+ import type { Thread, SessionStatus } from "@cueloop/schema";
8
+ import { ThreadStore } from "./store";
9
9
  import { reportsDir } from "./paths";
10
10
  import {
11
11
  DEFAULT_CLEANUP_PERIOD_DAYS,
@@ -28,7 +28,7 @@ function daysAgo(days: number): string {
28
28
  return new Date(NOW_MS - days * DAY_MS).toISOString();
29
29
  }
30
30
 
31
- function session(id: string, createdAt: string, status: SessionStatus = "resolved"): ReviewSession {
31
+ function session(id: string, createdAt: string, status: SessionStatus = "resolved"): Thread {
32
32
  return {
33
33
  schemaVersion: "1",
34
34
  id,
@@ -112,7 +112,7 @@ describe("pruneExpiredSessions", () => {
112
112
  test("deletes resolved sessions past the window and keeps recent ones", () => {
113
113
  // Arrange
114
114
  const home = tempDir("cueloop-retention-home-");
115
- const store = new SessionStore(home);
115
+ const store = new ThreadStore(home);
116
116
 
117
117
  store.upsert(session("ses_old", daysAgo(40)));
118
118
  store.upsert(session("ses_new", daysAgo(2)));
@@ -129,7 +129,7 @@ describe("pruneExpiredSessions", () => {
129
129
  test("never deletes an active pending session however old", () => {
130
130
  // Arrange
131
131
  const home = tempDir("cueloop-retention-home-");
132
- const store = new SessionStore(home);
132
+ const store = new ThreadStore(home);
133
133
 
134
134
  store.upsert(session("ses_active", daysAgo(400), "pending"));
135
135
 
@@ -144,7 +144,7 @@ describe("pruneExpiredSessions", () => {
144
144
  test("a period of zero prunes nothing", () => {
145
145
  // Arrange
146
146
  const home = tempDir("cueloop-retention-home-");
147
- const store = new SessionStore(home);
147
+ const store = new ThreadStore(home);
148
148
 
149
149
  store.upsert(session("ses_old", daysAgo(999)));
150
150
 
package/src/retention.ts CHANGED
@@ -2,7 +2,7 @@ import { readdirSync, readFileSync, rmSync, statSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import * as v from "valibot";
5
- import type { SessionStore } from "./store";
5
+ import type { ThreadStore } from "./store";
6
6
 
7
7
  export const DEFAULT_CLEANUP_PERIOD_DAYS = 30;
8
8
  export const LATEST_REPORT_FILENAME = "report.md";
@@ -68,7 +68,7 @@ export function isExpired(createdAt: string, periodDays: number, nowMs: number):
68
68
  }
69
69
 
70
70
  export function pruneExpiredSessions(
71
- store: SessionStore,
71
+ store: ThreadStore,
72
72
  periodDays: number,
73
73
  nowMs: number,
74
74
  ): string[] {
@@ -4,7 +4,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
4
  import { mkdtempSync, rmSync } from "node:fs";
5
5
  import { tmpdir } from "node:os";
6
6
  import { join } from "node:path";
7
- import type { ReviewSession } from "@cueloop/schema";
7
+ import type { Thread } from "@cueloop/schema";
8
8
  import { DaemonServer } from "./server";
9
9
  import { DaemonClient } from "./client";
10
10
  import { awaitResolve, openReview, resolveWorkspace } from "./review";
@@ -246,7 +246,7 @@ describe("awaitVerdict: chunked loop (the pi shape)", () => {
246
246
  test("onProgress sees fresh sessions between chunks; the verdict ends the loop", async () => {
247
247
  // Arrange
248
248
  const review = await openReview(client, { type: "plan", content: PLAN, cwd: home });
249
- const seen: ReviewSession[] = [];
249
+ const seen: Thread[] = [];
250
250
  const waiting = review.awaitVerdict({
251
251
  timeoutMs: Infinity,
252
252
  pollMs: 100,
package/src/review.ts CHANGED
@@ -12,7 +12,7 @@ import {
12
12
  newAnnotationId,
13
13
  type ArtifactType,
14
14
  type DiffFileContents,
15
- type ReviewSession,
15
+ type Thread,
16
16
  type WorkspaceKey,
17
17
  } from "@cueloop/schema";
18
18
  import { verdictResponse } from "./api";
@@ -129,7 +129,7 @@ export interface AwaitVerdictOptions {
129
129
  /** Chunk length for the poll loop; between chunks the session is re-read for progress. */
130
130
  pollMs?: number;
131
131
  /** Called with the fresh session after each chunk that is still pending. */
132
- onProgress?: (session: ReviewSession) => void;
132
+ onProgress?: (session: Thread) => void;
133
133
  signal?: AbortSignal;
134
134
  }
135
135
 
@@ -137,13 +137,13 @@ export interface AwaitVerdictOptions {
137
137
  export interface VerdictOutcome {
138
138
  allow: boolean;
139
139
  feedback: string;
140
- session: ReviewSession;
140
+ session: Thread;
141
141
  }
142
142
 
143
143
  export class ReviewHandle {
144
144
  constructor(
145
145
  private readonly client: DaemonClient,
146
- readonly session: ReviewSession,
146
+ readonly session: Thread,
147
147
  ) {}
148
148
 
149
149
  get id(): string {
@@ -184,7 +184,7 @@ export class ReviewHandle {
184
184
  }
185
185
  }
186
186
 
187
- function outcome(session: ReviewSession): VerdictOutcome {
187
+ function outcome(session: Thread): VerdictOutcome {
188
188
  return { ...verdictResponse(session), session };
189
189
  }
190
190
 
package/src/server.ts CHANGED
@@ -308,6 +308,11 @@ export class DaemonServer {
308
308
 
309
309
  return this.core.sessionAnnotate(params.id, params.annotation, params.authorName);
310
310
  },
311
+ "session.comment": (_connection, request) => {
312
+ const params = parseParams("session.comment", request.params);
313
+
314
+ return this.core.sessionAnnotate(params.id, params.annotation, params.authorName);
315
+ },
311
316
  "session.removeAnnotation": (connection, request) => {
312
317
  const params = parseParams("session.removeAnnotation", request.params);
313
318
 
package/src/share-blob.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * The share-blob wire format: how a ReviewSession travels from `cueloop share`
2
+ * The share-blob wire format: how a Thread travels from `cueloop share`
3
3
  * to the gateway. The planner's CLI packs (this is the only thing it can do -
4
4
  * it holds no encryption key); the gateway unpacks, then encrypts for R2.
5
5
  *
@@ -10,8 +10,8 @@
10
10
  */
11
11
 
12
12
  import { gzipSync, gunzipSync } from "node:zlib";
13
- import type { ReviewSession } from "@cueloop/schema";
14
- import { validateSessionRecord } from "./validate";
13
+ import type { Thread } from "@cueloop/schema";
14
+ import { validateThreadRecord } from "./validate";
15
15
 
16
16
  /** Decompressed ceiling for one shared session (ADR 0004's payload cap). */
17
17
  export const MAX_BLOB_BYTES = 1024 * 1024;
@@ -24,12 +24,12 @@ export const DEFAULT_SHARE_HOST = "cueloop.dev";
24
24
  export const DEFAULT_SHARE_PORT = 22;
25
25
 
26
26
  /** Serialise + compress a session for upload. */
27
- export function packSessionBlob(session: ReviewSession): Buffer {
27
+ export function packSessionBlob(session: Thread): Buffer {
28
28
  return gzipSync(Buffer.from(JSON.stringify(session), "utf8"));
29
29
  }
30
30
 
31
31
  /** Decompress + validate an uploaded blob, or throw a precise reason. */
32
- export function unpackSessionBlob(bytes: Uint8Array): ReviewSession {
32
+ export function unpackSessionBlob(bytes: Uint8Array): Thread {
33
33
  let json: string;
34
34
 
35
35
  try {
@@ -47,7 +47,7 @@ export function unpackSessionBlob(bytes: Uint8Array): ReviewSession {
47
47
  } catch {
48
48
  throw new Error("blob is not valid JSON");
49
49
  }
50
- const parsed = validateSessionRecord(raw);
50
+ const parsed = validateThreadRecord(raw);
51
51
 
52
52
  if (!parsed.ok) throw new Error(`blob is not a valid session: ${parsed.error}`);
53
53
 
@@ -2,16 +2,16 @@ import { mkdtempSync, writeFileSync } from "node:fs";
2
2
  import * as v from "valibot";
3
3
  import { tmpdir } from "node:os";
4
4
  import { join } from "node:path";
5
- import { MemorySessionStore, SessionStore, type SessionRepository } from "./store";
5
+ import { MemoryThreadStore, ThreadStore, type ThreadRepository } from "./store";
6
6
  import { sessionsDir } from "./paths";
7
- import { runSessionStoreConformance } from "./testing/store-conformance";
7
+ import { runThreadStoreConformance } from "./testing/store-conformance";
8
8
 
9
- const homes = new WeakMap<SessionRepository, string>();
9
+ const homes = new WeakMap<ThreadRepository, string>();
10
10
 
11
- runSessionStoreConformance("file store", {
11
+ runThreadStoreConformance("file store", {
12
12
  open: (records) => {
13
13
  const home = mkdtempSync(join(tmpdir(), "cueloop-store-"));
14
- const store = new SessionStore(home);
14
+ const store = new ThreadStore(home);
15
15
 
16
16
  // records land on disk the way an earlier daemon would have written them;
17
17
  // the file name comes from the record's id when it has one
@@ -26,7 +26,7 @@ runSessionStoreConformance("file store", {
26
26
  return store;
27
27
  },
28
28
  restart: (store) => {
29
- const reopened = new SessionStore(homes.get(store)!);
29
+ const reopened = new ThreadStore(homes.get(store)!);
30
30
 
31
31
  homes.set(reopened, homes.get(store)!);
32
32
 
@@ -34,8 +34,8 @@ runSessionStoreConformance("file store", {
34
34
  },
35
35
  });
36
36
 
37
- runSessionStoreConformance("memory store", {
38
- open: (records) => new MemorySessionStore(records),
37
+ runThreadStoreConformance("memory store", {
38
+ open: (records) => new MemoryThreadStore(records),
39
39
  // nothing persists past the instance: a restart recovers what it was seeded with
40
- restart: (store) => new MemorySessionStore(store.list()),
40
+ restart: (store) => new MemoryThreadStore(store.list()),
41
41
  });