@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/package.json +2 -2
- package/src/api.test.ts +3 -3
- package/src/api.ts +45 -52
- package/src/capabilities.ts +1 -0
- package/src/client.ts +82 -79
- package/src/herdr-pane.test.ts +2 -2
- package/src/herdr-pane.ts +2 -2
- package/src/herdr-tab-store.ts +1 -1
- package/src/retention.test.ts +6 -6
- package/src/retention.ts +2 -2
- package/src/review.test.ts +2 -2
- package/src/review.ts +5 -5
- package/src/server.ts +5 -0
- package/src/share-blob.ts +6 -6
- package/src/store.conformance.test.ts +9 -9
- package/src/store.jsonl.test.ts +20 -20
- package/src/store.ts +35 -24
- package/src/testing/store-conformance.ts +18 -18
- package/src/validate.test.ts +11 -11
- package/src/validate.ts +17 -6
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cueloop/daemon",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.70",
|
|
4
4
|
"description": "The cueloop session daemon and its client: socket protocol, atomic store, resumable waits",
|
|
5
5
|
"homepage": "https://github.com/mmurakaru/cueloop#readme",
|
|
6
6
|
"bugs": {
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"access": "public"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@cueloop/schema": "0.1.0-alpha.
|
|
38
|
+
"@cueloop/schema": "0.1.0-alpha.70",
|
|
39
39
|
"@pierre/diffs": "^1.3.6",
|
|
40
40
|
"effect": "^4.0.0-rc.115",
|
|
41
41
|
"valibot": "^1.4.2"
|
package/src/api.test.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
4
|
import { join } from "node:path";
|
|
5
5
|
import { DaemonCore, verdictResponse } from "./api";
|
|
6
|
-
import {
|
|
6
|
+
import { ThreadStore } from "./store";
|
|
7
7
|
import { derivePath, tipOf, type Artifact, type WorkspaceKey } from "@cueloop/schema";
|
|
8
8
|
import { MAX_BLOB_BYTES, packSessionBlob, unpackSessionBlob } from "./share-blob";
|
|
9
9
|
|
|
@@ -420,7 +420,7 @@ describe("persistence and recovery", () => {
|
|
|
420
420
|
// Arrange
|
|
421
421
|
core.sessionCreate({ workspace: WS, artifact: PLAN });
|
|
422
422
|
await Bun.write(join(home, "sessions", "broken.json"), "{ not json");
|
|
423
|
-
const store = new
|
|
423
|
+
const store = new ThreadStore(home);
|
|
424
424
|
|
|
425
425
|
// Act
|
|
426
426
|
const report = store.recover();
|
|
@@ -558,7 +558,7 @@ describe("the session history records every write as an entry", () => {
|
|
|
558
558
|
const legacy = { ...created };
|
|
559
559
|
|
|
560
560
|
delete legacy.history;
|
|
561
|
-
new
|
|
561
|
+
new ThreadStore(home).upsert(legacy);
|
|
562
562
|
|
|
563
563
|
// Act
|
|
564
564
|
const rebooted = new DaemonCore(home);
|
package/src/api.ts
CHANGED
|
@@ -37,14 +37,14 @@ import {
|
|
|
37
37
|
type NewEntry,
|
|
38
38
|
type Artifact,
|
|
39
39
|
type Identity,
|
|
40
|
-
type
|
|
40
|
+
type Thread,
|
|
41
41
|
type SessionHistory,
|
|
42
42
|
type Verdict,
|
|
43
43
|
type VerdictKind,
|
|
44
44
|
type WorkspaceKey,
|
|
45
45
|
} from "@cueloop/schema";
|
|
46
46
|
import { curateDiff } from "./curate";
|
|
47
|
-
import {
|
|
47
|
+
import { ThreadStore, withHistory } from "./store";
|
|
48
48
|
import { pruneExpiredSessions, resolveCleanupPeriodDays } from "./retention";
|
|
49
49
|
import { HerdrTabStore, type HerdrTabHandle } from "./herdr-tab-store";
|
|
50
50
|
import { DiffWatcher } from "./diff-watcher";
|
|
@@ -78,9 +78,9 @@ export interface DaemonEvent {
|
|
|
78
78
|
type EventListener = (event: DaemonEvent) => void;
|
|
79
79
|
|
|
80
80
|
export class DaemonCore {
|
|
81
|
-
readonly store:
|
|
81
|
+
readonly store: ThreadStore;
|
|
82
82
|
readonly herdrTabs: HerdrTabStore;
|
|
83
|
-
private waiters = new Map<string, ((session:
|
|
83
|
+
private waiters = new Map<string, ((session: Thread) => void)[]>();
|
|
84
84
|
private listeners = new Set<EventListener>();
|
|
85
85
|
private seq = 0;
|
|
86
86
|
/** Drives diff hot-reload: watches each live diff session's repo for working-tree changes. */
|
|
@@ -92,10 +92,10 @@ export class DaemonCore {
|
|
|
92
92
|
*/
|
|
93
93
|
private readonly diffRefreshGenerations = new Map<string, number>();
|
|
94
94
|
/** In-flight workbench creations keyed by project, so concurrent bare launches share one thread. */
|
|
95
|
-
private readonly workbenchCreation = new Map<string, Promise<
|
|
95
|
+
private readonly workbenchCreation = new Map<string, Promise<Thread>>();
|
|
96
96
|
|
|
97
97
|
constructor(home: string) {
|
|
98
|
-
this.store = new
|
|
98
|
+
this.store = new ThreadStore(home);
|
|
99
99
|
this.store.recover();
|
|
100
100
|
pruneExpiredSessions(this.store, resolveCleanupPeriodDays(), Date.now());
|
|
101
101
|
this.herdrTabs = new HerdrTabStore(home);
|
|
@@ -136,9 +136,9 @@ export class DaemonCore {
|
|
|
136
136
|
return this.store.list().some((session) => session.status === "pending");
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
-
sessionCreate(params: { workspace: WorkspaceKey; artifact: Artifact }):
|
|
139
|
+
sessionCreate(params: { workspace: WorkspaceKey; artifact: Artifact }): Thread {
|
|
140
140
|
const now = new Date().toISOString();
|
|
141
|
-
const session:
|
|
141
|
+
const session: Thread = {
|
|
142
142
|
schemaVersion: SCHEMA_VERSION,
|
|
143
143
|
id: this.newSessionId(now),
|
|
144
144
|
workspace: params.workspace,
|
|
@@ -163,7 +163,7 @@ export class DaemonCore {
|
|
|
163
163
|
return `ses_${now.replace(/\D/g, "").slice(0, 14)}_${(++this.seq).toString(36)}${Math.random().toString(36).slice(2, 6)}`;
|
|
164
164
|
}
|
|
165
165
|
|
|
166
|
-
sessionGet(id: string):
|
|
166
|
+
sessionGet(id: string): Thread {
|
|
167
167
|
const session = this.store.get(id);
|
|
168
168
|
|
|
169
169
|
if (!session) throw new DaemonError("not_found", `no session ${id}`);
|
|
@@ -174,7 +174,7 @@ export class DaemonCore {
|
|
|
174
174
|
sessionList(filter?: {
|
|
175
175
|
status?: "pending" | "resolved";
|
|
176
176
|
workspace?: Partial<WorkspaceKey>;
|
|
177
|
-
}):
|
|
177
|
+
}): Thread[] {
|
|
178
178
|
return this.store.list().filter((session) => {
|
|
179
179
|
if (filter?.status && session.status !== filter.status) return false;
|
|
180
180
|
if (filter?.workspace?.repoRoot && session.workspace.repoRoot !== filter.workspace.repoRoot)
|
|
@@ -191,7 +191,7 @@ export class DaemonCore {
|
|
|
191
191
|
* otherwise parks until sessionResolve fires or timeoutMs elapses (null =
|
|
192
192
|
* still pending - the caller re-polls later; the verdict is never lost).
|
|
193
193
|
*/
|
|
194
|
-
sessionWait(id: string, timeoutMs: number): Promise<
|
|
194
|
+
sessionWait(id: string, timeoutMs: number): Promise<Thread | null> {
|
|
195
195
|
const current = this.sessionGet(id);
|
|
196
196
|
|
|
197
197
|
if (current.status === "resolved") return Promise.resolve(current);
|
|
@@ -207,7 +207,7 @@ export class DaemonCore {
|
|
|
207
207
|
if (waiterIndex !== -1) list.splice(waiterIndex, 1);
|
|
208
208
|
resolve(null);
|
|
209
209
|
}, timeoutMs);
|
|
210
|
-
const waiter = (session:
|
|
210
|
+
const waiter = (session: Thread) => {
|
|
211
211
|
if (done) return;
|
|
212
212
|
done = true;
|
|
213
213
|
clearTimeout(timer);
|
|
@@ -227,7 +227,7 @@ export class DaemonCore {
|
|
|
227
227
|
id: string,
|
|
228
228
|
annotation: Omit<Annotation, "createdAt">,
|
|
229
229
|
authorName?: string,
|
|
230
|
-
):
|
|
230
|
+
): Thread {
|
|
231
231
|
const session = this.mutable(id);
|
|
232
232
|
// a welcome-playground note is ephemeral by design: never persisted, so never fed back
|
|
233
233
|
if (annotationTarget(annotation).kind === "welcome") return session;
|
|
@@ -265,7 +265,7 @@ export class DaemonCore {
|
|
|
265
265
|
* agent acting as that author and may remove only that author's comments;
|
|
266
266
|
* the owner (no `onBehalfOf`) may remove any.
|
|
267
267
|
*/
|
|
268
|
-
sessionRemoveAnnotation(id: string, annotationId: string, onBehalfOf?: string):
|
|
268
|
+
sessionRemoveAnnotation(id: string, annotationId: string, onBehalfOf?: string): Thread {
|
|
269
269
|
const session = this.mutable(id);
|
|
270
270
|
const target = session.annotations.find((candidate) => candidate.id === annotationId);
|
|
271
271
|
|
|
@@ -292,7 +292,7 @@ export class DaemonCore {
|
|
|
292
292
|
}
|
|
293
293
|
|
|
294
294
|
/** Register a display name for a participant; how a collaborator or an agent names itself. */
|
|
295
|
-
sessionSetParticipantName(id: string, author: string, name: string):
|
|
295
|
+
sessionSetParticipantName(id: string, author: string, name: string): Thread {
|
|
296
296
|
const session = this.mutable(id);
|
|
297
297
|
|
|
298
298
|
session.participants = registerParticipant(session, author, name).participants;
|
|
@@ -303,7 +303,7 @@ export class DaemonCore {
|
|
|
303
303
|
}
|
|
304
304
|
|
|
305
305
|
/** The reviewer's working copy; undefined clears it (revert all edits). */
|
|
306
|
-
sessionSetWorkingCopy(id: string, workingCopy: string | undefined):
|
|
306
|
+
sessionSetWorkingCopy(id: string, workingCopy: string | undefined): Thread {
|
|
307
307
|
const session = this.mutable(id);
|
|
308
308
|
const entryId = this.applyWorkingCopy(session, workingCopy);
|
|
309
309
|
|
|
@@ -318,7 +318,7 @@ export class DaemonCore {
|
|
|
318
318
|
* marks, so concurrent or stale clients converge instead of overwriting
|
|
319
319
|
* each other. An empty array is the explicit reset.
|
|
320
320
|
*/
|
|
321
|
-
sessionSetViewed(id: string, viewedPaths: string[]):
|
|
321
|
+
sessionSetViewed(id: string, viewedPaths: string[]): Thread {
|
|
322
322
|
const session = this.mutable(id);
|
|
323
323
|
|
|
324
324
|
if (viewedPaths.length === 0) delete session.viewedPaths;
|
|
@@ -330,7 +330,7 @@ export class DaemonCore {
|
|
|
330
330
|
}
|
|
331
331
|
|
|
332
332
|
/** Rename a session's display title; an empty title clears it back to the derived default. */
|
|
333
|
-
sessionSetTitle(id: string, title: string):
|
|
333
|
+
sessionSetTitle(id: string, title: string): Thread {
|
|
334
334
|
const session = this.mutable(id);
|
|
335
335
|
const trimmed = title.trim();
|
|
336
336
|
|
|
@@ -383,7 +383,7 @@ export class DaemonCore {
|
|
|
383
383
|
* or lazily creates it with the working-tree diff as its artifact, so a bare launch persists its
|
|
384
384
|
* first comment without an agent submission. Owner-only.
|
|
385
385
|
*/
|
|
386
|
-
async workbenchSession(cwd: string): Promise<
|
|
386
|
+
async workbenchSession(cwd: string): Promise<Thread> {
|
|
387
387
|
const workspace = await resolveWorkspace(cwd);
|
|
388
388
|
const key = workspace.rootCommit ?? "_standalone";
|
|
389
389
|
// an open workbench is reused; a resolved one is immutable and would reject the note, so a fresh
|
|
@@ -422,7 +422,7 @@ export class DaemonCore {
|
|
|
422
422
|
}
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
-
sessionSetShareId(id: string, shareId: string):
|
|
425
|
+
sessionSetShareId(id: string, shareId: string): Thread {
|
|
426
426
|
const session = this.mutable(id);
|
|
427
427
|
|
|
428
428
|
session.shareId = shareId;
|
|
@@ -447,7 +447,7 @@ export class DaemonCore {
|
|
|
447
447
|
* Cut one block of the reviewer's working copy - the `blockIndex`-th block
|
|
448
448
|
* of the working text - so it serializes into the diff the agent receives.
|
|
449
449
|
*/
|
|
450
|
-
sessionCutBlock(id: string, blockIndex: number):
|
|
450
|
+
sessionCutBlock(id: string, blockIndex: number): Thread {
|
|
451
451
|
const session = this.mutable(id);
|
|
452
452
|
const working = session.workingCopy ?? session.artifact.content;
|
|
453
453
|
const block = parseBlocks(working)[blockIndex];
|
|
@@ -467,7 +467,7 @@ export class DaemonCore {
|
|
|
467
467
|
* revision - into the working copy before `line` (default: the end). A copy
|
|
468
468
|
* that reads as the submitted revision again is dropped, not stored.
|
|
469
469
|
*/
|
|
470
|
-
sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number):
|
|
470
|
+
sessionRestoreBlock(id: string, baseBlockIndex: number, line?: number): Thread {
|
|
471
471
|
const session = this.mutable(id);
|
|
472
472
|
const base = session.artifact.content;
|
|
473
473
|
const block = parseBlocks(base)[baseBlockIndex];
|
|
@@ -500,7 +500,7 @@ export class DaemonCore {
|
|
|
500
500
|
* patch they leave, or clears when nothing is rejected. Needs the full file
|
|
501
501
|
* contents a working-tree diff carries; a PR diff cannot be curated.
|
|
502
502
|
*/
|
|
503
|
-
sessionCurate(id: string, rejections: HunkRejection[]):
|
|
503
|
+
sessionCurate(id: string, rejections: HunkRejection[]): Thread {
|
|
504
504
|
const session = this.mutable(id);
|
|
505
505
|
|
|
506
506
|
if (session.artifact.type !== "diff") {
|
|
@@ -530,7 +530,7 @@ export class DaemonCore {
|
|
|
530
530
|
* on their own name). Everything lands on the branch the share follows, and
|
|
531
531
|
* the record then shows its current path again, wherever the owner stands.
|
|
532
532
|
*/
|
|
533
|
-
sessionMergeShared(id: string, incoming: SharedMerge):
|
|
533
|
+
sessionMergeShared(id: string, incoming: SharedMerge): Thread {
|
|
534
534
|
const session = this.mutable(id);
|
|
535
535
|
const branch = this.shareBranchOf(session);
|
|
536
536
|
const known = new Set(
|
|
@@ -583,7 +583,7 @@ export class DaemonCore {
|
|
|
583
583
|
verdictKind: VerdictKind,
|
|
584
584
|
summary: string,
|
|
585
585
|
actionBodies?: Record<string, string>,
|
|
586
|
-
):
|
|
586
|
+
): Thread {
|
|
587
587
|
const session = this.mutable(id);
|
|
588
588
|
const verdict: Verdict = {
|
|
589
589
|
kind: verdictKind,
|
|
@@ -626,7 +626,7 @@ export class DaemonCore {
|
|
|
626
626
|
id: string,
|
|
627
627
|
content: string,
|
|
628
628
|
addressedAnnotationIds: string[] = [],
|
|
629
|
-
):
|
|
629
|
+
): Thread {
|
|
630
630
|
const session = this.sessionGet(id);
|
|
631
631
|
const now = new Date().toISOString();
|
|
632
632
|
const revisionNumber = session.revisions.length + 1;
|
|
@@ -688,7 +688,7 @@ export class DaemonCore {
|
|
|
688
688
|
* summary records them as a branch-summary entry at the target. The view
|
|
689
689
|
* follows the path: on `main`, the agent's next revision lands there.
|
|
690
690
|
*/
|
|
691
|
-
sessionNavigate(id: string, entryId: string, summary?: string, branch?: string):
|
|
691
|
+
sessionNavigate(id: string, entryId: string, summary?: string, branch?: string): Thread {
|
|
692
692
|
const session = this.mutable(id);
|
|
693
693
|
const moved = this.tree(id, () => {
|
|
694
694
|
const history = this.historyOf(session);
|
|
@@ -706,7 +706,7 @@ export class DaemonCore {
|
|
|
706
706
|
}
|
|
707
707
|
|
|
708
708
|
/** Start a branch at the current tip and switch to it; `main` stays where it is. */
|
|
709
|
-
sessionBranch(id: string, name: string):
|
|
709
|
+
sessionBranch(id: string, name: string): Thread {
|
|
710
710
|
const session = this.mutable(id);
|
|
711
711
|
|
|
712
712
|
this.refreshView(
|
|
@@ -718,7 +718,7 @@ export class DaemonCore {
|
|
|
718
718
|
return session;
|
|
719
719
|
}
|
|
720
720
|
|
|
721
|
-
sessionSwitch(id: string, branch: string):
|
|
721
|
+
sessionSwitch(id: string, branch: string): Thread {
|
|
722
722
|
const session = this.mutable(id);
|
|
723
723
|
|
|
724
724
|
this.refreshView(
|
|
@@ -731,7 +731,7 @@ export class DaemonCore {
|
|
|
731
731
|
}
|
|
732
732
|
|
|
733
733
|
/** Name the current tip as a checkpoint to navigate back to. */
|
|
734
|
-
sessionLabel(id: string, label: string):
|
|
734
|
+
sessionLabel(id: string, label: string): Thread {
|
|
735
735
|
const session = this.mutable(id);
|
|
736
736
|
|
|
737
737
|
session.history = labelTip(this.historyOf(session), label);
|
|
@@ -746,13 +746,13 @@ export class DaemonCore {
|
|
|
746
746
|
* comments, labels, and participant names travel; verdicts, edits, and the
|
|
747
747
|
* share do not. A resolved session can be forked.
|
|
748
748
|
*/
|
|
749
|
-
sessionFork(id: string):
|
|
749
|
+
sessionFork(id: string): Thread {
|
|
750
750
|
const source = this.sessionGet(id);
|
|
751
751
|
const history = this.tree(id, () => forkHistory(this.historyOf(source)));
|
|
752
752
|
const now = new Date().toISOString();
|
|
753
753
|
const known = [...source.annotations, ...(source.shelvedAnnotations ?? [])];
|
|
754
754
|
const view = viewOfPath(history, known);
|
|
755
|
-
const fork:
|
|
755
|
+
const fork: Thread = {
|
|
756
756
|
schemaVersion: SCHEMA_VERSION,
|
|
757
757
|
id: this.newSessionId(now),
|
|
758
758
|
workspace: source.workspace,
|
|
@@ -832,12 +832,12 @@ export class DaemonCore {
|
|
|
832
832
|
}
|
|
833
833
|
}
|
|
834
834
|
|
|
835
|
-
private watchIfDiffSession(session:
|
|
835
|
+
private watchIfDiffSession(session: Thread): void {
|
|
836
836
|
if (isLiveDiffSession(session))
|
|
837
837
|
this.diffWatcher.trackDiffRepo(session.workspace.repoRoot, session.id);
|
|
838
838
|
}
|
|
839
839
|
|
|
840
|
-
private unwatchIfDiffSession(session:
|
|
840
|
+
private unwatchIfDiffSession(session: Thread): void {
|
|
841
841
|
if (session.artifact.type === "diff")
|
|
842
842
|
this.diffWatcher.untrackDiffRepo(session.workspace.repoRoot, session.id);
|
|
843
843
|
}
|
|
@@ -846,10 +846,7 @@ export class DaemonCore {
|
|
|
846
846
|
* Set or clear the working copy and, when the reviewer's text changed, record
|
|
847
847
|
* it as a reviewer revision on the current branch. Returns that entry's id.
|
|
848
848
|
*/
|
|
849
|
-
private applyWorkingCopy(
|
|
850
|
-
session: ReviewSession,
|
|
851
|
-
workingCopy: string | undefined,
|
|
852
|
-
): string | undefined {
|
|
849
|
+
private applyWorkingCopy(session: Thread, workingCopy: string | undefined): string | undefined {
|
|
853
850
|
const before = session.workingCopy ?? session.artifact.content;
|
|
854
851
|
const next =
|
|
855
852
|
workingCopy === undefined || workingCopy === session.artifact.content
|
|
@@ -871,7 +868,7 @@ export class DaemonCore {
|
|
|
871
868
|
}
|
|
872
869
|
|
|
873
870
|
/** The session's history; a record without a revision has none and cannot be moved through. */
|
|
874
|
-
private historyOf(session:
|
|
871
|
+
private historyOf(session: Thread): SessionHistory {
|
|
875
872
|
const history = withHistory(session).history;
|
|
876
873
|
|
|
877
874
|
if (!history) throw new DaemonError("invalid_params", `session ${session.id} has no history`);
|
|
@@ -890,7 +887,7 @@ export class DaemonCore {
|
|
|
890
887
|
}
|
|
891
888
|
|
|
892
889
|
/** Store a moved history and make the record show its active path. */
|
|
893
|
-
private refreshView(session:
|
|
890
|
+
private refreshView(session: Thread, history: SessionHistory): void {
|
|
894
891
|
session.history = history;
|
|
895
892
|
applyPathView(
|
|
896
893
|
session,
|
|
@@ -900,7 +897,7 @@ export class DaemonCore {
|
|
|
900
897
|
}
|
|
901
898
|
|
|
902
899
|
/** Append an entry on the session's current branch; a session without a head has no history to extend. */
|
|
903
|
-
private record(session:
|
|
900
|
+
private record(session: Thread, entry: NewEntry): string | undefined {
|
|
904
901
|
const history = withHistory(session).history;
|
|
905
902
|
|
|
906
903
|
if (!history) return undefined;
|
|
@@ -912,12 +909,12 @@ export class DaemonCore {
|
|
|
912
909
|
}
|
|
913
910
|
|
|
914
911
|
/** Append an entry on main, leaving the reviewer's current branch where it is. */
|
|
915
|
-
private recordOnMain(session:
|
|
912
|
+
private recordOnMain(session: Thread, entry: NewEntry): string | undefined {
|
|
916
913
|
return this.recordOn(session, MAIN_BRANCH, entry);
|
|
917
914
|
}
|
|
918
915
|
|
|
919
916
|
/** Append an entry on a named branch, leaving the reviewer's current branch where it is. */
|
|
920
|
-
private recordOn(session:
|
|
917
|
+
private recordOn(session: Thread, branch: string, entry: NewEntry): string | undefined {
|
|
921
918
|
const history = withHistory(session).history;
|
|
922
919
|
|
|
923
920
|
if (!history) return undefined;
|
|
@@ -929,13 +926,13 @@ export class DaemonCore {
|
|
|
929
926
|
}
|
|
930
927
|
|
|
931
928
|
/** The branch a share follows; a share made before branches existed follows main. */
|
|
932
|
-
private shareBranchOf(session:
|
|
929
|
+
private shareBranchOf(session: Thread): string {
|
|
933
930
|
const branch = session.shareBranch ?? MAIN_BRANCH;
|
|
934
931
|
|
|
935
932
|
return session.history?.tips[branch] === undefined ? MAIN_BRANCH : branch;
|
|
936
933
|
}
|
|
937
934
|
|
|
938
|
-
private mutable(id: string):
|
|
935
|
+
private mutable(id: string): Thread {
|
|
939
936
|
const session = this.sessionGet(id);
|
|
940
937
|
|
|
941
938
|
if (session.status === "resolved")
|
|
@@ -953,11 +950,7 @@ export { DaemonError };
|
|
|
953
950
|
* revision's text, and a comment addressed by a revision off the fork's path
|
|
954
951
|
* is open again.
|
|
955
952
|
*/
|
|
956
|
-
function forkedAnnotation(
|
|
957
|
-
annotation: Annotation,
|
|
958
|
-
source: ReviewSession,
|
|
959
|
-
fork: ReviewSession,
|
|
960
|
-
): Annotation {
|
|
953
|
+
function forkedAnnotation(annotation: Annotation, source: Thread, fork: Thread): Annotation {
|
|
961
954
|
const { resolution, ...open } = annotation;
|
|
962
955
|
|
|
963
956
|
if (resolution === undefined) return { ...annotation };
|
|
@@ -972,12 +965,12 @@ function forkedAnnotation(
|
|
|
972
965
|
}
|
|
973
966
|
|
|
974
967
|
/** A pending diff session: the state that warrants hot-reload watching of its working tree. */
|
|
975
|
-
function isLiveDiffSession(session:
|
|
968
|
+
function isLiveDiffSession(session: Thread): boolean {
|
|
976
969
|
return session.status === "pending" && session.artifact.type === "diff";
|
|
977
970
|
}
|
|
978
971
|
|
|
979
972
|
/** Convenience for adapters: map a resolved session to the agent contract. */
|
|
980
|
-
export function verdictResponse(session:
|
|
973
|
+
export function verdictResponse(session: Thread) {
|
|
981
974
|
if (!session.verdict) throw new DaemonError("pending", "session has no verdict");
|
|
982
975
|
|
|
983
976
|
return { allow: verdictAllows(session.verdict.kind), feedback: session.verdict.feedback };
|
package/src/capabilities.ts
CHANGED
|
@@ -27,6 +27,7 @@ const PRIMITIVE_ROLES = {
|
|
|
27
27
|
"session.list": "any",
|
|
28
28
|
"session.wait": "any",
|
|
29
29
|
"session.annotate": "any",
|
|
30
|
+
"session.comment": "any",
|
|
30
31
|
// any role may call it; a non-owner must act on behalf of an author and stays scoped to it
|
|
31
32
|
"session.removeAnnotation": "any",
|
|
32
33
|
"session.setParticipantName": "any",
|