@cueloop/client 0.1.0-alpha.79 → 0.1.0-alpha.80

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.
Files changed (56) hide show
  1. package/package.json +5 -5
  2. package/src/App.diff.test.tsx +2 -2
  3. package/src/App.export.test.tsx +3 -3
  4. package/src/App.plan-review.test.tsx +1 -1
  5. package/src/App.readonly.test.tsx +2 -2
  6. package/src/App.submit-confirm.test.tsx +7 -7
  7. package/src/App.test.tsx +11 -11
  8. package/src/App.tsx +16 -11
  9. package/src/App.walk.test.tsx +5 -5
  10. package/src/app-key-state.ts +1 -1
  11. package/src/app-screens.tsx +5 -5
  12. package/src/app-view-model.ts +5 -5
  13. package/src/components/AnnotatableFileView.stories.tsx +1 -1
  14. package/src/components/AnnotatableFileView.test.tsx +1 -1
  15. package/src/components/BareWorkbenchFileView.tsx +1 -1
  16. package/src/components/Breadcrumb.stories.tsx +1 -1
  17. package/src/components/ChangesColumn.stories.tsx +6 -1
  18. package/src/components/CompletionOverlay.stories.tsx +2 -2
  19. package/src/components/CompletionOverlay.tsx +4 -4
  20. package/src/components/ConfirmCard.stories.tsx +4 -4
  21. package/src/components/ConfirmCard.tsx +31 -32
  22. package/src/components/DiffContentView.tsx +1 -1
  23. package/src/components/KeybindsDialog.stories.tsx +1 -1
  24. package/src/components/ThreadFooter.test.tsx +12 -1
  25. package/src/components/ThreadFooter.tsx +7 -2
  26. package/src/components/ThreadTree.stories.tsx +1 -1
  27. package/src/components/ThreadTree.tsx +1 -1
  28. package/src/components/ThreadView.tsx +1 -1
  29. package/src/components/WelcomePlayground.tsx +1 -1
  30. package/src/components/__snapshots__/stories.test.tsx.snap +8 -8
  31. package/src/components/story-fixtures.ts +2 -2
  32. package/src/components/{session-tree.test.ts → thread-tree.test.ts} +2 -2
  33. package/src/config.test.ts +10 -10
  34. package/src/config.ts +6 -9
  35. package/src/integrations.test.ts +8 -8
  36. package/src/integrations.ts +4 -4
  37. package/src/intent-dispatch.test.ts +10 -10
  38. package/src/intent-dispatch.ts +15 -14
  39. package/src/key-bindings.test.ts +4 -4
  40. package/src/key-bindings.ts +10 -10
  41. package/src/keymap.test.ts +5 -5
  42. package/src/keymap.ts +7 -7
  43. package/src/thread-controller.curate.test.ts +15 -5
  44. package/src/thread-controller.derivation.test.ts +1 -1
  45. package/src/thread-controller.live-diff.test.ts +10 -10
  46. package/src/thread-controller.optimistic.test.ts +1 -1
  47. package/src/thread-controller.share.test.ts +8 -8
  48. package/src/thread-controller.switch-cache.test.ts +4 -4
  49. package/src/thread-controller.tree.test.ts +4 -4
  50. package/src/thread-controller.ts +12 -12
  51. package/src/tree-view.test.ts +1 -1
  52. package/src/tree-view.ts +3 -3
  53. package/src/use-annotation-surface.tsx +1 -1
  54. package/src/view-diff.test.ts +1 -1
  55. package/src/workbench-snapshot.test.ts +1 -1
  56. /package/src/components/{session-tree.ts → thread-tree.ts} +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cueloop/client",
3
- "version": "0.1.0-alpha.79",
3
+ "version": "0.1.0-alpha.80",
4
4
  "description": "The cueloop review UI: projection renderer, keyboard grammar, review rail",
5
5
  "homepage": "https://github.com/mmurakaru/cueloop#readme",
6
6
  "bugs": {
@@ -27,10 +27,10 @@
27
27
  "access": "public"
28
28
  },
29
29
  "dependencies": {
30
- "@cueloop/daemon": "0.1.0-alpha.79",
31
- "@cueloop/extension-api": "0.1.0-alpha.79",
32
- "@cueloop/integration-obsidian": "0.1.0-alpha.79",
33
- "@cueloop/schema": "0.1.0-alpha.79",
30
+ "@cueloop/daemon": "0.1.0-alpha.80",
31
+ "@cueloop/extension-api": "0.1.0-alpha.80",
32
+ "@cueloop/integration-obsidian": "0.1.0-alpha.80",
33
+ "@cueloop/schema": "0.1.0-alpha.80",
34
34
  "@opentui/core": "^0.5.11",
35
35
  "@opentui/keymap": "0.5.11",
36
36
  "@opentui/react": "^0.5.11",
@@ -105,8 +105,8 @@ describe("diff review", () => {
105
105
  await waitForText(setup, "feedback sent");
106
106
  const resolved = server.core.sessionGet(session.id);
107
107
 
108
- expect(resolved.verdict!.feedback).toContain("new Map()");
109
- expect(resolved.verdict!.feedback).toContain("Map needs an eviction story.");
108
+ expect(resolved.message!.body).toContain("new Map()");
109
+ expect(resolved.message!.body).toContain("Map needs an eviction story.");
110
110
  });
111
111
 
112
112
  test("pasting an image into a diff comment drops in an [Image #n] placeholder", async () => {
@@ -43,7 +43,7 @@ beforeEach(() => {
43
43
 
44
44
  writeFileSync(
45
45
  configPath,
46
- `[integrations.obsidian]\nvault = ${JSON.stringify(vault)}\nexportOn = "resolve"\n`,
46
+ `[integrations.obsidian]\nvault = ${JSON.stringify(vault)}\nexportOn = "message"\n`,
47
47
  );
48
48
  restoreUserConfig = isolateUserConfig(home, "config.toml");
49
49
  server = new DaemonServer({ home, idleExitMs: 0 });
@@ -74,7 +74,7 @@ describe("obsidian export on resolve", () => {
74
74
  await waitForText(setup, "cueloop");
75
75
 
76
76
  // Act
77
- await pressKey(setup, "RETURN", { meta: true }); // open the submit overlay, keep the default verdict (approve)
77
+ await pressKey(setup, "RETURN", { meta: true }); // open the submit overlay, keep the default message (approve)
78
78
 
79
79
  // Assert
80
80
  await waitForText(setup, "[Approve]");
@@ -98,7 +98,7 @@ describe("obsidian export on resolve", () => {
98
98
 
99
99
  expect(written).toContain("source: cueloop");
100
100
  expect(written).toContain(`session: ${session.id}`);
101
- expect(written).toContain("verdict: approve");
101
+ expect(written).toContain("message: approved");
102
102
  expect(written).toContain("Move the store atomically.");
103
103
  expect(setup.captureCharFrame()).toContain("exported to");
104
104
  });
@@ -125,7 +125,7 @@ describe("share button", () => {
125
125
 
126
126
  test("a resolved plan hides Edit and Share (no re-sharing a finished review)", async () => {
127
127
  // Arrange - resolve the session before opening it
128
- server.core.sessionResolve(session.id, "approve", "");
128
+ server.core.sessionSendMessage(session.id, "approved", "");
129
129
 
130
130
  // Act
131
131
  const setup = await renderApp();
@@ -137,14 +137,14 @@ describe("observer primitives are blocked", () => {
137
137
 
138
138
  describe("a resolved review is read-only for its owner too", () => {
139
139
  test("typing, deleting a card, and editing answer review submitted - read-only", async () => {
140
- // Arrange: a comment exists and the verdict is in
140
+ // Arrange: a comment exists and the message is in
141
141
  server.core.sessionAnnotate(session.id, {
142
142
  id: "a_done",
143
143
  kind: "comment",
144
144
  anchor: makeAnchor(parseBlocks(PLAN), 2, 0, 10),
145
145
  body: "settled",
146
146
  });
147
- server.core.sessionResolve(session.id, "approve", "");
147
+ server.core.sessionSendMessage(session.id, "approved", "");
148
148
  const setup = await renderReadyApp(<App home={home} sessionId={session.id} />, {
149
149
  width: 120,
150
150
  height: 32,
@@ -1,4 +1,4 @@
1
- /** The send-message confirm: cmd+enter opens a centered overlay with a verdict selector, summary input, and word-buttons. Char-frame assertions over the real App and an in-process daemon, like App.test.tsx. */
1
+ /** The send-message confirm: cmd+enter opens a centered overlay with a message selector, summary input, and word-buttons. Char-frame assertions over the real App and an in-process daemon, like App.test.tsx. */
2
2
 
3
3
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
4
4
  import { mkdtempSync, rmSync } from "node:fs";
@@ -89,12 +89,12 @@ describe("send message confirm", () => {
89
89
  const setup = await renderApp();
90
90
 
91
91
  // Assert - the footer carries the send control before the overlay opens
92
- expect(setup.captureCharFrame()).toContain("send message");
92
+ expect(setup.captureCharFrame()).toContain("Send message (0)");
93
93
 
94
94
  // Act
95
95
  await pressKey(setup, "RETURN", { meta: true });
96
96
 
97
- // Assert - the overlay: verdict selector and word-buttons
97
+ // Assert - the overlay: message selector and word-buttons
98
98
  await waitForText(setup, "[Approve]"); // nothing pending: approve default
99
99
  const frame = setup.captureCharFrame();
100
100
 
@@ -103,7 +103,7 @@ describe("send message confirm", () => {
103
103
  expect(frame).toContain(" cancel ");
104
104
  });
105
105
 
106
- test("left/right cycles the verdict selector in the overlay", async () => {
106
+ test("left/right cycles the message selector in the overlay", async () => {
107
107
  // Arrange
108
108
  const setup = await renderApp();
109
109
 
@@ -122,7 +122,7 @@ describe("send message confirm", () => {
122
122
  await press(setup, "right");
123
123
 
124
124
  // Assert
125
- await waitForText(setup, "[Comment]");
125
+ await waitForText(setup, "[Approve]");
126
126
 
127
127
  // Act
128
128
  await press(setup, "left");
@@ -157,7 +157,7 @@ describe("send message confirm", () => {
157
157
  // Act
158
158
  await pressKey(setup, "RETURN", { meta: true });
159
159
 
160
- // Assert - opens on the default verdict, then cycle to request changes
160
+ // Assert - opens on the default message, then cycle to request changes
161
161
  await waitForText(setup, "[Approve]");
162
162
  await press(setup, "right");
163
163
  await waitForText(setup, "[Changes]");
@@ -171,7 +171,7 @@ describe("send message confirm", () => {
171
171
  const stored = server.core.sessionGet(session.id);
172
172
 
173
173
  expect(stored.status).toBe("resolved");
174
- expect(stored.verdict!.kind).toBe("request_changes");
174
+ expect(stored.message!.outcome).toBe("changes_requested");
175
175
  });
176
176
 
177
177
  test("typing / in the summary opens the skills/actions palette", async () => {
package/src/App.test.tsx CHANGED
@@ -79,7 +79,7 @@ describe("plan rendering", () => {
79
79
  expect(frame).toContain("Context");
80
80
  expect(frame).toContain("persists sessions to disk atomically");
81
81
  expect(frame).toContain("· move the store");
82
- expect(frame).toContain("send message");
82
+ expect(frame).toContain("Send message (");
83
83
  });
84
84
 
85
85
  test("a direct open still populates the Threads sidebar with other pending reviews", async () => {
@@ -191,7 +191,7 @@ describe("thread view grammar", () => {
191
191
  });
192
192
 
193
193
  describe("submit", () => {
194
- test("cmd+⏎ opens the rail confirm card; verdict + summary resolve the session", async () => {
194
+ test("cmd+⏎ opens the rail confirm card; message + summary resolve the session", async () => {
195
195
  // Arrange
196
196
  const setup = await renderApp();
197
197
 
@@ -203,7 +203,7 @@ describe("submit", () => {
203
203
  // Act: with no composer open the same chord opens submit
204
204
  await pressKey(setup, "RETURN", { meta: true });
205
205
 
206
- // Assert - the card opens on the default verdict, approve
206
+ // Assert - the card opens on the default message, approve
207
207
  await waitForText(setup, "[Approve]");
208
208
 
209
209
  // Act - cycle to request changes, then send with a summary
@@ -217,13 +217,13 @@ describe("submit", () => {
217
217
  const stored = server.core.sessionGet(session.id);
218
218
 
219
219
  expect(stored.status).toBe("resolved");
220
- expect(stored.verdict!.kind).toBe("request_changes");
221
- expect(stored.verdict!.feedback).toContain("Needs a phase list.");
220
+ expect(stored.message!.outcome).toBe("changes_requested");
221
+ expect(stored.message!.body).toContain("Needs a phase list.");
222
222
  // submit hands the reviewer back to the agent via the completion overlay
223
223
  expect(setup.captureCharFrame()).toContain("feedback sent");
224
224
  });
225
225
 
226
- test("approve via ←/→ verdict cycling", async () => {
226
+ test("approve via ←/→ message cycling", async () => {
227
227
  // Arrange
228
228
  const setup = await renderApp();
229
229
 
@@ -237,8 +237,8 @@ describe("submit", () => {
237
237
  await pressKey(setup, "RETURN", { meta: true });
238
238
 
239
239
  // Assert
240
- await waitForState(setup, () => server.core.sessionGet(session.id).verdict !== undefined);
241
- expect(server.core.sessionGet(session.id).verdict!.kind).toBe("approve");
240
+ await waitForState(setup, () => server.core.sessionGet(session.id).message !== undefined);
241
+ expect(server.core.sessionGet(session.id).message!.outcome).toBe("approved");
242
242
  });
243
243
  });
244
244
 
@@ -261,7 +261,7 @@ describe("no-thread shell", () => {
261
261
  await press(setup, "enter");
262
262
 
263
263
  // Assert
264
- await waitForText(setup, "send message");
264
+ await waitForText(setup, "Send message (");
265
265
  });
266
266
 
267
267
  test("picking a thread from the no-thread shell keeps the Threads sidebar open", async () => {
@@ -278,7 +278,7 @@ describe("no-thread shell", () => {
278
278
 
279
279
  // Act - open the thread under the cursor
280
280
  await press(setup, "enter");
281
- await waitForText(setup, "send message");
281
+ await waitForText(setup, "Send message (");
282
282
 
283
283
  // Assert - the sidebar stayed open across the swap: both titles are on screen,
284
284
  // and the non-opened one can only come from the still-open sidebar
@@ -344,7 +344,7 @@ describe("no-thread shell", () => {
344
344
  await press(setup, "enter");
345
345
 
346
346
  // Assert
347
- await waitForText(setup, "send message");
347
+ await waitForText(setup, "Send message (");
348
348
  });
349
349
  });
350
350
 
package/src/App.tsx CHANGED
@@ -35,7 +35,7 @@ import {
35
35
  } from "./theme-presets";
36
36
  import type { Theme } from "./theme";
37
37
  import { createReviewController, type ShareTransport } from "./thread-controller";
38
- import type { SessionClient } from "@cueloop/daemon/client";
38
+ import type { ThreadClient } from "@cueloop/daemon/client";
39
39
  import { createIntentDispatch, type Mode, type RailTab } from "./intent-dispatch";
40
40
  import { reduceKey, type KeyState } from "./keymap";
41
41
  import { KeyBindings, type CheatsheetSection } from "./key-bindings";
@@ -45,7 +45,7 @@ import { Button } from "./components/primitives/Button";
45
45
  import { ShareDialog } from "./components/ShareDialog";
46
46
  import { shareDialogStore } from "./components/share-dialog-store";
47
47
  import { Toolbar } from "./components/primitives/Toolbar";
48
- import { groupInbox, projectName, threadTitle } from "./components/session-tree";
48
+ import { groupInbox, projectName, threadTitle } from "./components/thread-tree";
49
49
  import { ThreadTree } from "./components/ThreadTree";
50
50
  import { ChangesFileTree } from "./components/ChangesColumn";
51
51
  import { MenuControlProvider, useMenuControlState } from "./components/menu-control";
@@ -70,7 +70,7 @@ import {
70
70
  } from "./thread-chords";
71
71
  import { type DiffFoldControls } from "./components/DiffContentView";
72
72
  import { commentCountsByFile } from "./view-diff";
73
- import { annotationTarget, threadShareLinks } from "@cueloop/schema";
73
+ import { annotationTarget, isAddressed, isAgentNote, threadShareLinks } from "@cueloop/schema";
74
74
  import type {
75
75
  Annotation,
76
76
  Artifact,
@@ -78,7 +78,7 @@ import type {
78
78
  Identity,
79
79
  ShareLink,
80
80
  Thread,
81
- VerdictKind,
81
+ MessageOutcome,
82
82
  } from "@cueloop/schema";
83
83
  import { PrototypePixels } from "./prototype-pixels";
84
84
  import type { PrototypeElement } from "./prototype-browser";
@@ -121,12 +121,12 @@ export interface AppProps {
121
121
  /** Timer source for the auto-close countdown; tests inject a ManualClock. */
122
122
  clock?: Clock;
123
123
  /** Session source; the sharing gateway injects a blob-backed client. */
124
- openClient?: () => Promise<SessionClient>;
124
+ openClient?: () => Promise<ThreadClient>;
125
125
  shareTransport?: ShareTransport;
126
126
  /**
127
127
  * Who is at the keyboard. `owner` is the local planner (default). `observer`
128
128
  * is a passive `cueloop serve` watcher (read-only). `collaborator` is a share
129
- * viewer: annotates, but cannot edit the plan or submit an agent verdict.
129
+ * viewer: annotates, but cannot edit the plan or submit an agent message.
130
130
  */
131
131
  role?: "owner" | "observer" | "collaborator";
132
132
  /**
@@ -581,7 +581,7 @@ export function App({
581
581
  const [autoClose, setAutoClose] = useState<AutoClose>("off");
582
582
  // unified or side-by-side diff; split only lays out when the Changes pane is zoomed
583
583
  const [diffView, setDiffView] = useState<DiffViewMode>("split");
584
- const [defaultVerdict, setDefaultVerdict] = useState<VerdictKind>("approve");
584
+ const [defaultMessage, setDefaultMessage] = useState<MessageOutcome>("approved");
585
585
  const [focusedAnnotationId, setFocusedAnnotationId] = useState<string | undefined>(undefined);
586
586
  const [selectedCurationId, setSelectedCurationId] = useState<string | undefined>(undefined);
587
587
  const [railTab, setRailTab] = useState<RailTab>("review");
@@ -625,7 +625,7 @@ export function App({
625
625
  setSkills(loadSkills(config.skillsPath));
626
626
  setAutoClose(config.ui.autoClose);
627
627
  setDiffView(config.ui.diffView);
628
- setDefaultVerdict(config.ui.defaultVerdict);
628
+ setDefaultMessage(config.ui.defaultMessage);
629
629
  setPinnedIds(new Set(config.ui.pins));
630
630
  controller.applyConfig(config);
631
631
  }, [session?.workspace.repoRoot, controller, keyBindings, appearance]);
@@ -827,7 +827,7 @@ export function App({
827
827
  inboxCursor,
828
828
  mode,
829
829
  session,
830
- defaultVerdict,
830
+ defaultMessage,
831
831
  focusedAnnotationId,
832
832
  selectedCurationId,
833
833
  railTab,
@@ -1057,12 +1057,12 @@ export function App({
1057
1057
  authorNames,
1058
1058
  );
1059
1059
 
1060
- if (isCompletionOverlayPhase(completion) && activeSession.verdict)
1060
+ if (isCompletionOverlayPhase(completion) && activeSession.message)
1061
1061
  return (
1062
1062
  <CompletionScreen
1063
1063
  theme={theme}
1064
1064
  session={activeSession}
1065
- verdict={activeSession.verdict.kind}
1065
+ message={activeSession.message.outcome}
1066
1066
  completion={completion}
1067
1067
  status={status}
1068
1068
  onClose={() => dispatch({ type: "finishReview" })}
@@ -1123,6 +1123,11 @@ export function App({
1123
1123
  branch={activeSession.workspace.branch}
1124
1124
  onSubmit={onSubmitRequest}
1125
1125
  canSubmit={canSubmitReview(isOwner, resolved, observer)}
1126
+ pendingAnnotations={
1127
+ activeSession.annotations.filter(
1128
+ (annotation) => !isAddressed(annotation) && !isAgentNote(annotation),
1129
+ ).length
1130
+ }
1126
1131
  theme={theme}
1127
1132
  />
1128
1133
  );
@@ -159,7 +159,7 @@ describe("the guided walk", () => {
159
159
  await press(setup, "enter");
160
160
 
161
161
  // Assert
162
- await waitForText(setup, "send message");
162
+ await waitForText(setup, "Send message (");
163
163
  expect(setup.captureCharFrame()).toContain("3/3 files viewed");
164
164
 
165
165
  // Act
@@ -248,7 +248,7 @@ describe("the guided walk", () => {
248
248
 
249
249
  // Act
250
250
  // a note is agent context, not reviewer feedback: it neither counts nor
251
- // flips the default verdict, and never comes back in the feedback doc
251
+ // flips the default message, and never comes back in the feedback doc
252
252
  await press(setup, "escape");
253
253
  await waitForTextGone(setup, "agent note");
254
254
  await pressKey(setup, "RETURN", { meta: true });
@@ -260,8 +260,8 @@ describe("the guided walk", () => {
260
260
  await waitForState(setup, () => server.core.sessionGet(session.id).status === "resolved");
261
261
  const resolved = server.core.sessionGet(session.id);
262
262
 
263
- expect(resolved.verdict!.kind).toBe("approve");
264
- expect(resolved.verdict!.feedback).not.toContain("Swaps the stale line");
263
+ expect(resolved.message!.outcome).toBe("approved");
264
+ expect(resolved.message!.body).not.toContain("Swaps the stale line");
265
265
  });
266
266
 
267
267
  test("the sheet dims behind the wizard; the preview keeps the diff colors", async () => {
@@ -286,7 +286,7 @@ describe("the guided walk", () => {
286
286
 
287
287
  test("walking a resolved review answers read-only", async () => {
288
288
  // Arrange
289
- server.core.sessionResolve(session.id, "approve", "");
289
+ server.core.sessionSendMessage(session.id, "approved", "");
290
290
  const setup = await renderApp();
291
291
 
292
292
  // Act - renderApp already waited for the diff to load; the session is resolved
@@ -44,7 +44,7 @@ export function buildKeyState(params: {
44
44
  keys,
45
45
  readOnly: observer,
46
46
  canEditPlan: isOwner,
47
- canSubmitVerdict: isOwner,
47
+ canSubmitMessage: isOwner,
48
48
  canShare: isOwner,
49
49
  overlay,
50
50
  view: !session ? "inbox" : isDiff ? "diff" : "plan",
@@ -1,6 +1,6 @@
1
1
  import { ScrollArea } from "./components/ScrollArea";
2
2
  import React, { useEffect, useMemo, useState, type Dispatch, type SetStateAction } from "react";
3
- import type { DiffFileContents, Thread, VerdictKind } from "@cueloop/schema";
3
+ import type { DiffFileContents, Thread, MessageOutcome } from "@cueloop/schema";
4
4
  import { returnPaneFor } from "@cueloop/schema";
5
5
  import type { Theme } from "./theme";
6
6
  import type { QuickAction } from "./config";
@@ -16,7 +16,7 @@ import type { SettingsCategory, SettingsValues } from "./components/SettingsDial
16
16
  import type { SettingsNav } from "./use-settings-dialog";
17
17
  import { CLIENT_VERSION } from "./version";
18
18
  import { ThemeProvider } from "./components/theme-context";
19
- import type { InboxRow } from "./components/session-tree";
19
+ import type { InboxRow } from "./components/thread-tree";
20
20
  import { SettingsDialog } from "./components/SettingsDialog";
21
21
  import { CompletionOverlay } from "./components/CompletionOverlay";
22
22
  import { ThreadTree } from "./components/ThreadTree";
@@ -404,19 +404,19 @@ export function NoThreadShell(props: {
404
404
  export function CompletionScreen(props: {
405
405
  theme: Theme;
406
406
  session: Thread;
407
- verdict: VerdictKind;
407
+ message: MessageOutcome;
408
408
  completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
409
409
  status: string;
410
410
  onClose: () => void;
411
411
  onBackToPlan: () => void;
412
412
  onAlways: () => void;
413
413
  }): React.ReactNode {
414
- const { theme, session, verdict, completion, status, onClose, onBackToPlan, onAlways } = props;
414
+ const { theme, session, message, completion, status, onClose, onBackToPlan, onAlways } = props;
415
415
 
416
416
  return (
417
417
  <ThemeProvider theme={theme}>
418
418
  <CompletionOverlay
419
- verdict={verdict}
419
+ message={message}
420
420
  completion={completion}
421
421
  status={status}
422
422
  onClose={onClose}
@@ -1,5 +1,5 @@
1
1
  import type { Dispatch, MutableRefObject, SetStateAction } from "react";
2
- import type { Thread, VerdictKind } from "@cueloop/schema";
2
+ import type { Thread, MessageOutcome } from "@cueloop/schema";
3
3
  import type { Mode } from "./intent-dispatch";
4
4
  import type { Intent, KeyState } from "./keymap";
5
5
  import type { Completion } from "./thread-controller";
@@ -85,7 +85,7 @@ export function buildHeaderItems(params: {
85
85
  ...(resolved
86
86
  ? [
87
87
  {
88
- label: `resolved: ${session.verdict!.kind.replace("_", " ")}`,
88
+ label: `resolved: ${session.message!.outcome.replace("_", " ")}`,
89
89
  tone: "green" as const,
90
90
  },
91
91
  ]
@@ -144,7 +144,7 @@ export function buildSubmitConfirmState(
144
144
  if (mode.type !== "submit") return null;
145
145
 
146
146
  return {
147
- verdict: mode.verdict,
147
+ message: mode.message,
148
148
  summary: mode.summary,
149
149
  quickActions: deps.quickActions,
150
150
  viewedSummary:
@@ -155,8 +155,8 @@ export function buildSubmitConfirmState(
155
155
  liveInput.current = summary;
156
156
  setMode({ ...mode, summary });
157
157
  },
158
- onSelectVerdict: (verdict: VerdictKind) => setMode({ ...mode, verdict }),
159
- onSubmit: () => dispatch({ type: "submitVerdict" }),
158
+ onSelectMessage: (message: MessageOutcome) => setMode({ ...mode, message }),
159
+ onSubmit: () => dispatch({ type: "submitMessage" }),
160
160
  onCancel: () => dispatch({ type: "closeOverlay" }),
161
161
  };
162
162
  }
@@ -15,7 +15,7 @@ const SESSION: Thread = {
15
15
  artifact: { type: "plan", content: "# Plan\n", meta: {} },
16
16
  revisions: [{ revision: 1, content: "# Plan\n", submittedAt: "2026-01-01T00:00:00Z" }],
17
17
  annotations: [],
18
- verdict: null,
18
+ message: null,
19
19
  status: "pending",
20
20
  createdAt: "2026-01-01T00:00:00Z",
21
21
  };
@@ -21,7 +21,7 @@ function planSession(): Thread {
21
21
  artifact: { type: "plan", content: "# Plan\n", meta: {} },
22
22
  revisions: [{ revision: 1, content: "# Plan\n", submittedAt: "2026-01-01T00:00:00Z" }],
23
23
  annotations: [],
24
- verdict: null,
24
+ message: null,
25
25
  status: "pending",
26
26
  createdAt: "2026-01-01T00:00:00Z",
27
27
  };
@@ -23,7 +23,7 @@ export function draftThread(): Thread {
23
23
  artifact: { type: "diff", content: "", meta: {} },
24
24
  revisions: [],
25
25
  annotations: [],
26
- verdict: null,
26
+ message: null,
27
27
  status: "pending",
28
28
  createdAt: "1970-01-01T00:00:00.000Z",
29
29
  };
@@ -11,7 +11,7 @@ export const SessionHeader: Story = {
11
11
  items={[
12
12
  { label: "cueloop", tone: "accent" },
13
13
  { label: "Migration Plan · rev 2", tone: "dim" },
14
- { label: "resolved: approve", tone: "green" },
14
+ { label: "resolved: approved", tone: "green" },
15
15
  ]}
16
16
  />
17
17
  ),
@@ -18,7 +18,12 @@ const files: DiffFileContents[] = [
18
18
  newContents: "c",
19
19
  status: "added",
20
20
  },
21
- { path: "packages/daemon/src/review.ts", oldContents: "d", newContents: "", status: "deleted" },
21
+ {
22
+ path: "packages/daemon/src/thread-review.ts",
23
+ oldContents: "d",
24
+ newContents: "",
25
+ status: "deleted",
26
+ },
22
27
  { path: "README.md", oldContents: "e", newContents: "f", status: "modified" },
23
28
  ];
24
29
 
@@ -10,7 +10,7 @@ const noop = () => {};
10
10
  export const ApprovedPrompt: Story = {
11
11
  render: () => (
12
12
  <CompletionOverlay
13
- verdict="approve"
13
+ message="approved"
14
14
  completion={{ phase: "prompt" }}
15
15
  status=""
16
16
  onClose={noop}
@@ -24,7 +24,7 @@ export const ApprovedPrompt: Story = {
24
24
  export const FeedbackCounting: Story = {
25
25
  render: () => (
26
26
  <CompletionOverlay
27
- verdict="request_changes"
27
+ message="changes_requested"
28
28
  completion={{ phase: "counting", remaining: 3 }}
29
29
  status="exported to vault/cueloop/plan.md"
30
30
  returnsTo="agent/worker-3"
@@ -5,14 +5,14 @@
5
5
  */
6
6
 
7
7
  import React from "react";
8
- import type { VerdictKind } from "@cueloop/schema";
8
+ import type { MessageOutcome } from "@cueloop/schema";
9
9
  import type { Theme } from "../theme";
10
10
  import { useComponentTheme } from "./theme-context";
11
11
  import { Toolbar } from "./primitives/Toolbar";
12
12
  import { Button } from "./primitives/Button";
13
13
 
14
14
  export interface CompletionOverlayProps {
15
- verdict: VerdictKind;
15
+ message: MessageOutcome;
16
16
  completion: { phase: "prompt" } | { phase: "counting"; remaining: number };
17
17
  /** Latest status line (e.g. the vault-export path) stays visible here. */
18
18
  status: string;
@@ -25,7 +25,7 @@ export interface CompletionOverlayProps {
25
25
  }
26
26
 
27
27
  export function CompletionOverlay({
28
- verdict,
28
+ message,
29
29
  completion,
30
30
  status,
31
31
  returnsTo,
@@ -35,7 +35,7 @@ export function CompletionOverlay({
35
35
  theme,
36
36
  }: CompletionOverlayProps): React.ReactNode {
37
37
  const tokens = useComponentTheme(theme);
38
- const approved = verdict === "approve";
38
+ const approved = message === "approved";
39
39
 
40
40
  return (
41
41
  <box
@@ -8,21 +8,21 @@ export const meta: StoryMeta = { title: "Cards/ConfirmCard" };
8
8
 
9
9
  const callbacks = {
10
10
  onInput: () => {},
11
- onSelectVerdict: () => {},
11
+ onSelectMessage: () => {},
12
12
  onSubmit: () => {},
13
13
  onCancel: () => {},
14
14
  quickActions: DEFAULT_QUICK_ACTIONS,
15
15
  };
16
16
 
17
17
  export const ApproveDefault: Story = {
18
- render: () => <ConfirmCard verdict="approve" summary="" {...callbacks} />,
18
+ render: () => <ConfirmCard message="approved" summary="" {...callbacks} />,
19
19
  expectedColors: [DARK.green, DARK.accent],
20
20
  size: { width: 40, height: 14 },
21
21
  };
22
22
 
23
23
  export const RequestChanges: Story = {
24
24
  render: () => (
25
- <ConfirmCard verdict="request_changes" summary="Tighten the steps." {...callbacks} />
25
+ <ConfirmCard message="changes_requested" summary="Tighten the steps." {...callbacks} />
26
26
  ),
27
27
  expectedColors: [DARK.red],
28
28
  size: { width: 40, height: 14 },
@@ -30,7 +30,7 @@ export const RequestChanges: Story = {
30
30
 
31
31
  export const WithViewedSummary: Story = {
32
32
  render: () => (
33
- <ConfirmCard verdict="approve" summary="" viewedSummary="2/3 files viewed" {...callbacks} />
33
+ <ConfirmCard message="approved" summary="" viewedSummary="2/3 files viewed" {...callbacks} />
34
34
  ),
35
35
  expectedColors: [DARK.green, DARK.textDim],
36
36
  size: { width: 40, height: 14 },