cueloop 0.1.0-alpha.90 → 0.1.0-alpha.91

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",
3
- "version": "0.1.0-alpha.90",
3
+ "version": "0.1.0-alpha.91",
4
4
  "description": "Terminal review surface for coding agents: annotate plans and diffs, return a message, send structured feedback back",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -30,11 +30,11 @@
30
30
  "build:binary": "bun build --compile --minify --external cpu-features ./src/main.ts --outfile dist/cueloop"
31
31
  },
32
32
  "dependencies": {
33
- "@cueloop/adapters": "0.1.0-alpha.90",
34
- "@cueloop/client": "0.1.0-alpha.90",
35
- "@cueloop/daemon": "0.1.0-alpha.90",
36
- "@cueloop/extension-api": "0.1.0-alpha.90",
37
- "@cueloop/schema": "0.1.0-alpha.90",
33
+ "@cueloop/adapters": "0.1.0-alpha.91",
34
+ "@cueloop/client": "0.1.0-alpha.91",
35
+ "@cueloop/daemon": "0.1.0-alpha.91",
36
+ "@cueloop/extension-api": "0.1.0-alpha.91",
37
+ "@cueloop/schema": "0.1.0-alpha.91",
38
38
  "@modelcontextprotocol/server": "2.0.0",
39
39
  "@valibot/to-json-schema": "1.4.0",
40
40
  "valibot": "^1.5.0"
package/src/main.ts CHANGED
@@ -107,6 +107,7 @@ const commandHandlers: CommandHandlers = {
107
107
  stop: async () => (await import("./daemon-control")).stopCommand(),
108
108
  restart: async () => (await import("./daemon-control")).restartCommand(),
109
109
  plan: (rest) => planCommand(rest),
110
+ pair: (rest) => pairCommand(rest),
110
111
  reply: (rest) => replyCommand(rest),
111
112
  diff: (rest) => diffCommand(rest),
112
113
  prototype: (rest) => prototypeCommand(rest),
@@ -278,6 +279,34 @@ async function diffCommand(argv: string[]): Promise<number> {
278
279
  return runTui(workbench.id, "review");
279
280
  }
280
281
 
282
+ async function pairCommand(argv: string[]): Promise<number> {
283
+ const parsed = parseArgs(argv);
284
+ const client = await DaemonClient.connect({ autostart: true });
285
+ let thread: Thread;
286
+
287
+ try {
288
+ const sessionId = parsed.positional[0];
289
+
290
+ if (sessionId !== undefined) {
291
+ thread = await client.sessionGet(sessionId);
292
+ if (thread.artifact.meta.workbench !== true || thread.status !== "pending") {
293
+ throw new Error(`Thread ${sessionId} is not an open workbench`);
294
+ }
295
+ } else {
296
+ thread = await client.sessionWorkbench(process.cwd());
297
+ }
298
+ } finally {
299
+ client.close();
300
+ }
301
+ if (parsed.flags["no-tui"] === true) {
302
+ console.log(JSON.stringify({ threadId: thread.id }));
303
+
304
+ return 0;
305
+ }
306
+
307
+ return runTui(thread.id, "pair");
308
+ }
309
+
281
310
  /**
282
311
  * `cueloop review` disambiguates create from open by intent:
283
312
  * - bare `cueloop review`, `--open`/`--latest`, or a `ses_*` selector opens a
@@ -310,10 +339,16 @@ async function reviewEntry(argv: string[]): Promise<number> {
310
339
  * omitted) restores the remembered one. Resolve the factory here so the heavy client
311
340
  * module stays lazily imported for non-TUI commands.
312
341
  */
313
- async function runTui(sessionId?: string, layout?: "review" | "plan"): Promise<number> {
314
- const { runClient, reviewLayout, planLayout } = await import("@cueloop/client");
342
+ async function runTui(sessionId?: string, layout?: "review" | "plan" | "pair"): Promise<number> {
343
+ const { runClient, pairLayout, reviewLayout, planLayout } = await import("@cueloop/client");
315
344
  const resolved =
316
- layout === "review" ? reviewLayout() : layout === "plan" ? planLayout() : undefined;
345
+ layout === "review"
346
+ ? reviewLayout()
347
+ : layout === "plan"
348
+ ? planLayout()
349
+ : layout === "pair"
350
+ ? pairLayout()
351
+ : undefined;
317
352
 
318
353
  return runClient({ sessionId, layout: resolved });
319
354
  }
@@ -331,6 +366,7 @@ function printHelp(): void {
331
366
  " cueloop reply [id|title] open the latest pending reply review (the agent's previous message)",
332
367
  " cueloop diff [id|title] review your working tree (untracked files included);",
333
368
  " with a clean tree, open the latest pending diff review",
369
+ " cueloop pair [thread-id] open a live workbench; --no-tui prints its Thread ID",
334
370
  " cueloop review <pr> review a pull request (--no-tui prints the session)",
335
371
  " cueloop prototype <file.md> review a component design doc (or open the latest by id/title)",
336
372
  "",
@@ -12,6 +12,7 @@ import {
12
12
  newAnnotationId,
13
13
  type Anchor,
14
14
  type Annotation,
15
+ type AnnotationTarget,
15
16
  } from "@cueloop/schema";
16
17
  import * as v from "valibot";
17
18
  import { DaemonClient } from "@cueloop/daemon/client";
@@ -127,7 +128,7 @@ async function annotateAnchor(
127
128
  flags: Record<string, string | boolean>,
128
129
  client: DaemonClient,
129
130
  id: string,
130
- ): Promise<{ anchor: Anchor; replyTo?: string }> {
131
+ ): Promise<{ anchor: Anchor; replyTo?: string; target?: AnnotationTarget }> {
131
132
  const replyTo = stringFlag(flags, "reply-to");
132
133
 
133
134
  if (replyTo !== undefined) {
@@ -137,7 +138,9 @@ async function annotateAnchor(
137
138
  if (!root) throw new Error(`no comment ${replyTo} to reply to`);
138
139
 
139
140
  // a reply to a reply still hangs off the discussion's root comment
140
- return { anchor: root.anchor, replyTo: root.replyTo ?? root.id };
141
+ return root.target === undefined
142
+ ? { anchor: root.anchor, replyTo: root.replyTo ?? root.id }
143
+ : { anchor: root.anchor, replyTo: root.replyTo ?? root.id, target: root.target };
141
144
  }
142
145
  const selector = stringFlag(flags, "selector");
143
146
  const quote =
@@ -163,8 +166,8 @@ async function sessionAnnotateCommand({
163
166
  const id = required(positional[1], "session id");
164
167
  const author = stringFlag(flags, "author");
165
168
  const body = await annotateBody(flags, client, id);
166
- const { anchor, replyTo } = await annotateAnchor(flags, client, id);
167
- const annotation: Omit<Annotation, "createdAt"> = {
169
+ const { anchor, replyTo, target } = await annotateAnchor(flags, client, id);
170
+ const base: Omit<Annotation, "createdAt"> = {
168
171
  id: stringFlag(flags, "annotation-id") ?? newAnnotationId(),
169
172
  kind: stringFlag(flags, "kind") ?? "comment",
170
173
  anchor,
@@ -172,7 +175,9 @@ async function sessionAnnotateCommand({
172
175
  author,
173
176
  };
174
177
 
175
- if (replyTo !== undefined) annotation.replyTo = replyTo;
178
+ if (replyTo !== undefined) base.replyTo = replyTo;
179
+ const annotation: Omit<Annotation, "createdAt"> =
180
+ target === undefined ? base : { ...base, target };
176
181
  out(await client.sessionAnnotate(id, annotation, stringFlag(flags, "author-name")));
177
182
 
178
183
  return 0;
@@ -340,6 +345,7 @@ async function sessionEventsCommand({
340
345
  }: SessionContext): Promise<number> {
341
346
  const id = required(positional[1], "session id");
342
347
  const once = flags.once === true;
348
+ const ready = flags.ready === true;
343
349
  const stream = new Promise<number>((resolve) => {
344
350
  client.onEvent((event) => {
345
351
  if (event.sessionId !== id) return;
@@ -347,11 +353,24 @@ async function sessionEventsCommand({
347
353
  if (once) resolve(0);
348
354
  });
349
355
  });
356
+ let finished = false;
357
+ const disconnect = new Promise<number>((resolve) => {
358
+ client.onDisconnect(() => {
359
+ if (finished) return;
360
+ console.error("daemon connection closed - restart cueloop session events to resume");
361
+ resolve(1);
362
+ });
363
+ });
350
364
 
351
365
  await client.subscribe();
352
366
  await client.sessionGet(id);
367
+ if (ready) console.log(JSON.stringify({ event: "events.ready", sessionId: id }));
368
+
369
+ const code = await Promise.race([stream, disconnect]);
370
+
371
+ finished = true;
353
372
 
354
- return stream;
373
+ return code;
355
374
  }
356
375
 
357
376
  async function sessionSendMessageCommand({