@cueloop/client 0.1.0-alpha.14 → 0.1.0-alpha.15

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,16 +1,16 @@
1
1
  {
2
2
  "name": "@cueloop/client",
3
- "version": "0.1.0-alpha.14",
3
+ "version": "0.1.0-alpha.15",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "exports": {
7
7
  ".": "./src/index.ts"
8
8
  },
9
9
  "dependencies": {
10
- "@cueloop/daemon": "0.1.0-alpha.14",
11
- "@cueloop/extension-api": "0.1.0-alpha.14",
12
- "@cueloop/integration-obsidian": "0.1.0-alpha.14",
13
- "@cueloop/schema": "0.1.0-alpha.14",
10
+ "@cueloop/daemon": "0.1.0-alpha.15",
11
+ "@cueloop/extension-api": "0.1.0-alpha.15",
12
+ "@cueloop/integration-obsidian": "0.1.0-alpha.15",
13
+ "@cueloop/schema": "0.1.0-alpha.15",
14
14
  "@opentui/core": "^0.5.1",
15
15
  "@opentui/keymap": "0.5.1",
16
16
  "@opentui/react": "^0.5.1",
@@ -60,37 +60,39 @@ async function submitApprove(setup: Setup): Promise<void> {
60
60
  }
61
61
 
62
62
  describe("completion overlay", () => {
63
- test("submit with auto-close off shows the prompt; ⏎ exits", async () => {
63
+ test("submit with auto-close off counts down from 5; ⏎ exits now", async () => {
64
64
  let exited = -1;
65
65
  const setup = await renderApp((code) => (exited = code));
66
66
  await submitApprove(setup);
67
- const frame = setup.captureCharFrame();
68
- expect(frame).toContain("✓ review approved");
69
- expect(frame).toContain("a always close after submit");
67
+ const frame = await waitForText(setup, "closing in 5s");
68
+ expect(frame).toContain("review approved");
69
+ expect(frame).toContain("close [return]");
70
+ expect(frame).toContain("return to plan [esc]");
70
71
  await press(setup, "enter");
71
72
  expect(exited).toBe(0);
72
73
  });
73
74
 
74
- test("a opts in: persists the config and starts the countdown", async () => {
75
+ test("a remembers the countdown as the persisted default", async () => {
75
76
  const setup = await renderApp();
76
77
  await submitApprove(setup);
78
+ await waitForText(setup, "closing in 5s");
77
79
  await press(setup, "a");
78
- await waitForText(setup, "closing in 3s");
79
80
  expect(existsSync(configPath)).toBe(true);
80
- expect(readFileSync(configPath, "utf8")).toContain("auto_close = 3");
81
- // each countdown second is one manual-clock tick, not wall time
81
+ expect(readFileSync(configPath, "utf8")).toContain("auto_close = 5");
82
+ // the countdown keeps running; each second is one manual-clock tick
82
83
  clock.advance(1000);
83
- await waitForText(setup, "closing in 2s");
84
+ await waitForText(setup, "closing in 4s");
84
85
  });
85
86
 
86
87
  test("esc dismisses to the resolved read-only view", async () => {
87
88
  const setup = await renderApp();
88
89
  await submitApprove(setup);
90
+ await waitForText(setup, "closing in 5s");
89
91
  // a bare ESC sits in the input parser's escape-sequence disambiguation
90
92
  // window before it is delivered - the frame wait absorbs it
91
93
  await press(setup, "escape");
92
94
  const frame = await waitForText(setup, "resolved: approve");
93
- expect(frame).not.toContain("always close after submit");
95
+ expect(frame).not.toContain("closing in");
94
96
  });
95
97
 
96
98
  test("configured auto_close counts down and exits without interaction", async () => {
@@ -72,7 +72,7 @@ describe("diff review", () => {
72
72
 
73
73
  await press(setup, "enter"); // submit
74
74
  await press(setup, "enter"); // confirm request_changes
75
- await waitForText(setup, "✎ feedback sent");
75
+ await waitForText(setup, "feedback sent");
76
76
  const resolved = server.core.sessionGet(session.id);
77
77
  expect(resolved.verdict!.feedback).toContain("new Map()");
78
78
  expect(resolved.verdict!.feedback).toContain("Map needs an eviction story.");
@@ -121,7 +121,7 @@ describe("rail submit confirm", () => {
121
121
  await setup.mockInput.typeText("Tighten the steps.");
122
122
  await press(setup, "enter");
123
123
  // the completion flow after submit is unchanged
124
- await waitForText(setup, "✎ feedback sent");
124
+ await waitForText(setup, "feedback sent");
125
125
  const stored = server.core.sessionGet(session.id);
126
126
  expect(stored.status).toBe("resolved");
127
127
  expect(stored.verdict!.kind).toBe("request_changes");
package/src/App.test.tsx CHANGED
@@ -148,13 +148,13 @@ describe("submit", () => {
148
148
  expect(setup.captureCharFrame()).toContain("[Changes]");
149
149
  await type(setup, "Expand the steps.");
150
150
  await press(setup, "enter");
151
- await waitForText(setup, "✎ feedback sent");
151
+ await waitForText(setup, "feedback sent");
152
152
  const stored = server.core.sessionGet(session.id);
153
153
  expect(stored.status).toBe("resolved");
154
154
  expect(stored.verdict!.kind).toBe("request_changes");
155
155
  expect(stored.verdict!.feedback).toContain("Needs a phase list.");
156
156
  // submit hands the reviewer back to the agent via the completion overlay
157
- expect(setup.captureCharFrame()).toContain("✎ feedback sent");
157
+ expect(setup.captureCharFrame()).toContain("feedback sent");
158
158
  });
159
159
 
160
160
  test("approve via ←/→ verdict cycling", async () => {
@@ -33,18 +33,17 @@ export function CompletionOverlay({ verdict, completion, status, returnsTo, them
33
33
  alignItems: "center",
34
34
  }}
35
35
  >
36
- <text fg={approved ? tokens.green : tokens.accent}>{approved ? "✓ review approved" : "✎ feedback sent"}</text>
36
+ <text fg={approved ? tokens.green : tokens.accent}>{approved ? "review approved" : "feedback sent"}</text>
37
37
  <text> </text>
38
38
  <text fg={tokens.text}>The agent has your {approved ? "approval" : "feedback"} and is unblocked.</text>
39
+ {returnsTo ? <text fg={tokens.textDim}>returning to {returnsTo} on close</text> : null}
39
40
  {status ? <text fg={tokens.textDim}>{status}</text> : null}
40
41
  <text> </text>
41
- {completion.phase === "counting" ? (
42
- <text fg={tokens.textDim}>
43
- closing in {completion.remaining}s{returnsTo ? ` - returning to ${returnsTo}` : ""} · ⏎ now · esc stay
44
- </text>
45
- ) : (
46
- <text fg={tokens.textDim}>⏎ close · a always close after submit (3s) · esc stay</text>
47
- )}
42
+ <text fg={tokens.textDim}>
43
+ close [return]
44
+ {completion.phase === "counting" ? ` · closing in ${completion.remaining}s` : ""}
45
+ {" · return to plan [esc] · always [a]"}
46
+ </text>
48
47
  </box>
49
48
  );
50
49
  }
@@ -262,11 +262,11 @@ exports[`stories catalog CompletionOverlay/ApprovedPrompt renders, snapshots, an
262
262
 
263
263
 
264
264
 
265
- ✓ review approved
265
+ review approved
266
266
 
267
267
  The agent has your approval and is unblocked.
268
268
 
269
- ⏎ close · a always close after submit (3s) · esc stay
269
+ close [return] · return to plan [esc] · always [a]
270
270
 
271
271
 
272
272
 
@@ -289,13 +289,13 @@ exports[`stories catalog CompletionOverlay/FeedbackCounting renders, snapshots,
289
289
 
290
290
 
291
291
 
292
-
293
- ✎ feedback sent
292
+ feedback sent
294
293
 
295
294
  The agent has your feedback and is unblocked.
295
+ returning to agent/worker-3 on close
296
296
  exported to vault/cueloop/plan.md
297
297
 
298
- closing in 3s - returning to agent/worker-3 · ⏎ now · esc stay
298
+ close [return] · closing in 3s · return to plan [esc] · always [a]
299
299
 
300
300
 
301
301
 
@@ -76,7 +76,7 @@ describe("completion overlay", () => {
76
76
  [prompt, "j", []],
77
77
  [counting, "return", [{ t: "finishReview" }]],
78
78
  [counting, "q", [{ t: "finishReview" }]],
79
- [counting, "a", []],
79
+ [counting, "a", [{ t: "optInAutoClose" }]],
80
80
  [counting, "escape", [{ t: "dismissCompletion" }]],
81
81
  ];
82
82
  for (const [s, name, expected] of table) {
package/src/keymap.ts CHANGED
@@ -86,7 +86,7 @@ export function reduceKey(state: KeyState, key: KeyInput, resolvedAction?: strin
86
86
  }
87
87
  if (state.overlay === "completion-prompt" || state.overlay === "completion-counting") {
88
88
  if (name === "return" || name === "enter" || name === "q") return [{ t: "finishReview" }];
89
- if (name === "a" && state.overlay === "completion-prompt") return [{ t: "optInAutoClose" }];
89
+ if (name === "a") return [{ t: "optInAutoClose" }];
90
90
  if (name === "escape") return [{ t: "dismissCompletion" }];
91
91
  return [];
92
92
  }
@@ -30,8 +30,8 @@ import { persistAutoClose, type AutoClose, type CueloopConfig } from "./config";
30
30
 
31
31
  /**
32
32
  * Post-submit lifecycle (a review pane should hand you back to the agent,
33
- * not linger): idle → prompt (auto-close off: offer the choice) or
34
- * counting (configured delay) → exit. esc dismisses to the resolved view.
33
+ * not linger): idle → counting (a visible countdown, the default) → exit.
34
+ * esc dismisses to the resolved view; a remembers the countdown as default.
35
35
  */
36
36
  export type Completion =
37
37
  | { phase: "idle" }
@@ -39,6 +39,9 @@ export type Completion =
39
39
  | { phase: "counting"; remaining: number }
40
40
  | { phase: "dismissed" };
41
41
 
42
+ /** Seconds the completion overlay counts down before it hands back. */
43
+ export const DEFAULT_AUTO_CLOSE = 5;
44
+
42
45
  export interface ControllerSnapshot {
43
46
  session: ReviewSession | null;
44
47
  inbox: ReviewSession[] | null;
@@ -366,7 +369,9 @@ class Controller implements ReviewController {
366
369
  if (!session) return;
367
370
  this.client!.sessionResolve(session.id, verdict, summary)
368
371
  .then((resolved) => {
369
- this.update({ session: resolved, status: `review submitted - ${verdict.replace("_", " ")}` });
372
+ // The completion overlay heading already states the verdict, so the
373
+ // status line stays empty here - only export/error messages fill it.
374
+ this.update({ session: resolved, status: "" });
370
375
  // notes-vault export: guarded by config, default is manual (no-op)
371
376
  const obsidian = this.obsidian;
372
377
  const exporter = this.exporters.get("obsidian");
@@ -375,14 +380,13 @@ class Controller implements ReviewController {
375
380
  this.setStatus(r.success && r.path ? `exported to ${r.path}` : `export failed: ${r.error ?? "unknown"}`);
376
381
  });
377
382
  }
378
- // hand the reviewer back to the agent: prompt or count down per config;
379
- // inside herdr with a known return pane, closing is the default
383
+ // Hand the reviewer back to the agent. The default is a visible
384
+ // countdown from DEFAULT_AUTO_CLOSE that closes on its own; esc stays,
385
+ // a remembers the choice. A configured delay overrides; 0 closes now.
380
386
  const delay = this.autoClose;
381
- const returns = returnPaneFor(resolved.artifact.meta.herdrPane) !== undefined;
382
387
  if (delay === 0) this.finishReview();
383
388
  else if (typeof delay === "number") this.startCounting(delay);
384
- else if (returns) this.startCounting(3);
385
- else this.update({ completion: { phase: "prompt" } });
389
+ else this.startCounting(DEFAULT_AUTO_CLOSE);
386
390
  })
387
391
  .catch((e: unknown) => this.setStatus(String(e instanceof Error ? e.message : e)));
388
392
  }
@@ -407,13 +411,13 @@ class Controller implements ReviewController {
407
411
  }
408
412
 
409
413
  optInAutoClose(): void {
410
- // opt in: close in 3s from now on, persisted to the user config
414
+ // Remember the default countdown for future submits, persisted to the user
415
+ // config. The countdown is already running; this only makes it the default.
411
416
  try {
412
- persistAutoClose(3);
417
+ persistAutoClose(DEFAULT_AUTO_CLOSE);
413
418
  } catch {
414
419
  // a read-only config dir must not block closing the review
415
420
  }
416
- this.autoClose = 3;
417
- this.startCounting(3);
421
+ this.autoClose = DEFAULT_AUTO_CLOSE;
418
422
  }
419
423
  }