@cueloop/daemon 0.1.0-alpha.93 → 0.1.0-alpha.94

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueloop/daemon",
3
- "version": "0.1.0-alpha.93",
3
+ "version": "0.1.0-alpha.94",
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": {
@@ -39,8 +39,8 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@cueloop/extension-api": "0.1.0-alpha.93",
43
- "@cueloop/schema": "0.1.0-alpha.93",
42
+ "@cueloop/extension-api": "0.1.0-alpha.94",
43
+ "@cueloop/schema": "0.1.0-alpha.94",
44
44
  "@pierre/diffs": "^1.4.1",
45
45
  "effect": "^4.0.0-rc.116",
46
46
  "smol-toml": "^1.8.0",
package/src/api.ts CHANGED
@@ -142,7 +142,10 @@ export class DaemonCore {
142
142
  */
143
143
  private readonly diffRefreshGenerations = new Map<string, number>();
144
144
  /** In-flight workbench creations keyed by project, so concurrent bare launches share one thread. */
145
- private readonly workbenchCreation = new Map<string, Promise<Thread>>();
145
+ private readonly workbenchCreation = new Map<
146
+ string,
147
+ Promise<{ session: Thread; diff: VcsDiffSnapshot & { vcs: string } }>
148
+ >();
146
149
 
147
150
  constructor(home: string) {
148
151
  this.store = new ThreadStore(home);
@@ -618,8 +621,35 @@ export class DaemonCore {
618
621
  * first comment without an agent submission. Owner-only.
619
622
  */
620
623
  async workbenchSession(cwd: string): Promise<Thread> {
621
- const selected = await this.vcsSources.select(cwd);
622
- const workspace = await resolveWorkspace(cwd);
624
+ const opened = await this.openWorkbench(cwd);
625
+
626
+ return opened.session;
627
+ }
628
+
629
+ /** Return the workbench and its current diff from one capture. */
630
+ async workbenchReview(
631
+ cwd: string,
632
+ ): Promise<{ session: Thread; diff: VcsDiffSnapshot & { vcs: string } }> {
633
+ const opened = await this.openWorkbench(cwd);
634
+
635
+ if (opened.diff) return { session: opened.session, diff: opened.diff };
636
+
637
+ return {
638
+ session: opened.session,
639
+ diff: await this.vcsSources.capture(
640
+ opened.session.workspace.repoRoot,
641
+ opened.session.artifact.meta.vcs,
642
+ ),
643
+ };
644
+ }
645
+
646
+ private async openWorkbench(
647
+ cwd: string,
648
+ ): Promise<{ session: Thread; diff?: VcsDiffSnapshot & { vcs: string } }> {
649
+ const [selected, workspace] = await Promise.all([
650
+ this.vcsSources.select(cwd),
651
+ resolveWorkspace(cwd),
652
+ ]);
623
653
  workspace.repoRoot = selected.repoRoot;
624
654
  const key = `${workspace.rootCommit ?? selected.repoRoot}:${selected.adapter.id}`;
625
655
  // an open workbench is reused; a resolved one is immutable and would reject the note, so a fresh
@@ -635,13 +665,13 @@ export class DaemonCore {
635
665
  (session.artifact.meta.vcs ?? "git") === selected.adapter.id,
636
666
  );
637
667
 
638
- if (open) return open;
668
+ if (open) return { session: open };
639
669
  // serialize concurrent bare launches for the same repo onto one creation, so they share a thread
640
670
  const inFlight = this.workbenchCreation.get(key);
641
671
 
642
672
  if (inFlight) return inFlight;
643
- const creation = this.vcsSources.capture(cwd, selected.adapter.id).then((diff) =>
644
- this.sessionCreate({
673
+ const creation = this.vcsSources.capture(cwd, selected.adapter.id).then((diff) => ({
674
+ session: this.sessionCreate({
645
675
  workspace,
646
676
  artifact: {
647
677
  type: "diff",
@@ -657,7 +687,8 @@ export class DaemonCore {
657
687
  },
658
688
  },
659
689
  }),
660
- );
690
+ diff,
691
+ }));
661
692
 
662
693
  this.workbenchCreation.set(key, creation);
663
694
  try {
@@ -47,6 +47,7 @@ const PRIMITIVE_ROLES = {
47
47
  "repo.changes": "owner",
48
48
  "repo.diff": "owner",
49
49
  "session.workbench": "owner",
50
+ "session.workbenchReview": "owner",
50
51
  // the tree is the owner's: collaborators comment on the branch a share follows
51
52
  "session.navigate": "owner",
52
53
  "session.branch": "owner",
package/src/client.ts CHANGED
@@ -595,6 +595,24 @@ export class DaemonClient implements ThreadClient {
595
595
  sessionWorkbench(cwd: string): Promise<Thread> {
596
596
  return this.request("session.workbench", { cwd }, ThreadRecordSchema);
597
597
  }
598
+ /** Open a workbench with the diff snapshot used for its first frame. */
599
+ workbenchReview(cwd: string): Promise<{ session: Thread; diff: WorkingTreeDiff }> {
600
+ return this.request(
601
+ "session.workbenchReview",
602
+ { cwd },
603
+ v.object({
604
+ session: ThreadRecordSchema,
605
+ diff: v.object({
606
+ patch: v.string(),
607
+ files: v.array(DiffFileContentsSchema),
608
+ vcs: v.string(),
609
+ source: v.optional(
610
+ v.object({ changeId: v.optional(v.string()), revisionId: v.string() }),
611
+ ),
612
+ }),
613
+ }),
614
+ );
615
+ }
598
616
  /** Re-capture a diff session's working tree; changed=true when the patch moved and an event fired. */
599
617
  sessionRefreshDiff(id: string): Promise<{ changed: boolean }> {
600
618
  return this.request("session.refreshDiff", { id }, RefreshDiffResultSchema);
package/src/server.ts CHANGED
@@ -457,6 +457,8 @@ export class DaemonServer {
457
457
  },
458
458
  "session.workbench": (_connection, request) =>
459
459
  this.core.workbenchSession(parseParams("session.workbench", request.params).cwd),
460
+ "session.workbenchReview": (_connection, request) =>
461
+ this.core.workbenchReview(parseParams("session.workbenchReview", request.params).cwd),
460
462
  "session.refreshDiff": (_connection, request) => {
461
463
  const params = parseParams("session.refreshDiff", request.params);
462
464
 
@@ -51,22 +51,27 @@ function earliestRootCommit(revList: string | null): string | undefined {
51
51
 
52
52
  /** Workspace key resolution: repo root, branch, and the project identity (root commit + remote) from the cwd. */
53
53
  export async function resolveWorkspace(cwd = process.cwd()): Promise<WorkspaceKey> {
54
- const gitRepoRoot = await git(["rev-parse", "--show-toplevel"], cwd);
55
- const jjRepoRoot = await jjRoot(cwd);
54
+ const [gitRepoRoot, jjRepoRoot] = await Promise.all([
55
+ git(["rev-parse", "--show-toplevel"], cwd),
56
+ jjRoot(cwd),
57
+ ]);
56
58
  const nativeJjRoot =
57
59
  jjRepoRoot && (!gitRepoRoot || jjRepoRoot.length > gitRepoRoot.length) ? jjRepoRoot : null;
58
60
  const repoRoot = nativeJjRoot ?? gitRepoRoot ?? cwd;
59
- const branch = nativeJjRoot
60
- ? "jj"
61
- : ((await git(["rev-parse", "--abbrev-ref", "HEAD"], cwd)) ?? "detached");
61
+ const [gitBranch, isShallow, rootList, gitRemote] = nativeJjRoot
62
+ ? [null, null, null, null]
63
+ : await Promise.all([
64
+ git(["rev-parse", "--abbrev-ref", "HEAD"], cwd),
65
+ git(["rev-parse", "--is-shallow-repository"], cwd),
66
+ git(["rev-list", "--max-parents=0", "--date-order", "HEAD"], cwd),
67
+ git(["remote", "get-url", "origin"], cwd),
68
+ ]);
69
+ const branch = nativeJjRoot ? "jj" : (gitBranch ?? "detached");
62
70
  // a shallow clone's oldest commit is the graft boundary, not the true root, so
63
71
  // it would key a different project than a full clone - leave it unset instead
64
- const shallow =
65
- nativeJjRoot !== null || (await git(["rev-parse", "--is-shallow-repository"], cwd)) === "true";
66
- const rootCommit = shallow
67
- ? undefined
68
- : earliestRootCommit(await git(["rev-list", "--max-parents=0", "--date-order", "HEAD"], cwd));
69
- const remote = nativeJjRoot ? null : await git(["remote", "get-url", "origin"], cwd);
72
+ const shallow = nativeJjRoot !== null || isShallow === "true";
73
+ const rootCommit = shallow ? undefined : earliestRootCommit(rootList);
74
+ const remote = nativeJjRoot ? null : gitRemote;
70
75
 
71
76
  const workspace: WorkspaceKey = { repoRoot, branch };
72
77
  // a repo with no commits (or a shallow clone) has no reliable root, so the thread stays standalone
package/src/validate.ts CHANGED
@@ -275,6 +275,7 @@ export const Params = {
275
275
  "repo.changes": v.object({ cwd: NonEmpty }),
276
276
  "repo.diff": v.object({ cwd: NonEmpty, vcs: v.optional(NonEmpty) }),
277
277
  "session.workbench": v.object({ cwd: NonEmpty }),
278
+ "session.workbenchReview": v.object({ cwd: NonEmpty }),
278
279
  "session.navigate": v.object({
279
280
  id: SessionId,
280
281
  entryId: NonEmpty,