@cyber-dash-tech/revela 0.17.6 → 0.17.7

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 (35) hide show
  1. package/README.md +26 -46
  2. package/README.zh-CN.md +26 -46
  3. package/bin/revela.ts +98 -0
  4. package/lib/edit/prompt.ts +6 -2
  5. package/lib/edit/server.ts +2 -2
  6. package/lib/inspect/prompt.ts +5 -1
  7. package/lib/refine/comment-requests.ts +77 -0
  8. package/lib/refine/open.ts +5 -2
  9. package/lib/refine/prompt-bridge.ts +219 -0
  10. package/lib/refine/qa-suppression.ts +41 -0
  11. package/lib/refine/server.ts +122 -34
  12. package/lib/runtime/index.ts +225 -0
  13. package/lib/runtime/research.ts +175 -0
  14. package/lib/runtime/review.ts +270 -0
  15. package/lib/runtime/story.ts +53 -0
  16. package/package.json +6 -1
  17. package/plugin.ts +4 -2
  18. package/plugins/revela/.codex-plugin/plugin.json +37 -0
  19. package/plugins/revela/.mcp.json +11 -0
  20. package/plugins/revela/assets/README.md +2 -0
  21. package/plugins/revela/hooks/hooks.json +28 -0
  22. package/plugins/revela/hooks/revela_guard.ts +10 -0
  23. package/plugins/revela/hooks/revela_post_write_notice.ts +18 -0
  24. package/plugins/revela/mcp/revela-server.ts +504 -0
  25. package/plugins/revela/mcp/runtime-resolver.ts +109 -0
  26. package/plugins/revela/skills/revela-design/SKILL.md +20 -0
  27. package/plugins/revela/skills/revela-domain/SKILL.md +18 -0
  28. package/plugins/revela/skills/revela-export/SKILL.md +21 -0
  29. package/plugins/revela/skills/revela-init/SKILL.md +36 -0
  30. package/plugins/revela/skills/revela-make-deck/SKILL.md +37 -0
  31. package/plugins/revela/skills/revela-research/SKILL.md +38 -0
  32. package/plugins/revela/skills/revela-review-deck/SKILL.md +33 -0
  33. package/plugins/revela/skills/revela-story/SKILL.md +24 -0
  34. package/tools/decks.ts +10 -78
  35. package/tools/research-save.ts +8 -72
@@ -0,0 +1,219 @@
1
+ import { spawn } from "child_process"
2
+ import type { InspectionResult } from "../inspection-context/result"
3
+
4
+ export type ReviewPromptAction = "comment" | "inspect"
5
+ export type ReviewPromptBridgeKind = "opencode" | "codex-exec"
6
+
7
+ export interface ReviewPromptInput {
8
+ action: ReviewPromptAction
9
+ prompt: string
10
+ workspaceRoot: string
11
+ file: string
12
+ requestId?: string
13
+ timeoutMs?: number
14
+ }
15
+
16
+ export type ReviewPromptResult =
17
+ | { ok: true; status: "sent" | "completed"; result?: InspectionResult; raw?: string }
18
+ | { ok: false; status: "failed" | "unsupported"; error: string; raw?: string }
19
+
20
+ export interface ReviewPromptBridge {
21
+ kind: ReviewPromptBridgeKind
22
+ send(input: ReviewPromptInput): Promise<ReviewPromptResult>
23
+ }
24
+
25
+ export interface CodexExecRunResult {
26
+ exitCode: number | null
27
+ stdout: string
28
+ stderr: string
29
+ }
30
+
31
+ export type CodexExecRunner = (input: {
32
+ prompt: string
33
+ workspaceRoot: string
34
+ timeoutMs: number
35
+ }) => Promise<CodexExecRunResult>
36
+
37
+ export function createOpenCodeReviewPromptBridge(client: any, sessionID: string): ReviewPromptBridge {
38
+ return {
39
+ kind: "opencode",
40
+ async send(input) {
41
+ if (!client?.session?.prompt || !sessionID) {
42
+ return {
43
+ ok: false,
44
+ status: "failed",
45
+ error: "OpenCode Review bridge requires client.session.prompt and sessionID.",
46
+ }
47
+ }
48
+ await client.session.prompt({
49
+ path: { id: sessionID },
50
+ body: {
51
+ parts: [{ type: "text", text: input.prompt }],
52
+ },
53
+ })
54
+ return { ok: true, status: "sent" }
55
+ },
56
+ }
57
+ }
58
+
59
+ export function createCodexExecReviewPromptBridge(options: {
60
+ runner?: CodexExecRunner
61
+ timeoutMs?: number
62
+ } = {}): ReviewPromptBridge {
63
+ const runner = options.runner ?? runCodexExec
64
+ const timeoutMs = options.timeoutMs ?? 120_000
65
+ return {
66
+ kind: "codex-exec",
67
+ async send(input) {
68
+ const output = await runner({
69
+ prompt: input.prompt,
70
+ workspaceRoot: input.workspaceRoot,
71
+ timeoutMs: input.timeoutMs ?? timeoutMs,
72
+ })
73
+ const raw = [output.stdout, output.stderr].filter(Boolean).join("\n")
74
+ if (output.exitCode !== 0) {
75
+ return {
76
+ ok: false,
77
+ status: "failed",
78
+ error: `codex exec failed with exit code ${output.exitCode ?? "unknown"}.`,
79
+ raw,
80
+ }
81
+ }
82
+ if (input.action === "comment") return { ok: true, status: "completed", raw }
83
+ const result = extractInspectionResult(output.stdout)
84
+ if (!result) {
85
+ return {
86
+ ok: false,
87
+ status: "failed",
88
+ error: "codex exec did not return a valid inspection result JSON object.",
89
+ raw,
90
+ }
91
+ }
92
+ return { ok: true, status: "completed", result, raw }
93
+ },
94
+ }
95
+ }
96
+
97
+ async function runCodexExec(input: {
98
+ prompt: string
99
+ workspaceRoot: string
100
+ timeoutMs: number
101
+ }): Promise<CodexExecRunResult> {
102
+ return new Promise((resolve) => {
103
+ const child = spawn("codex", ["exec", "--json", "--ephemeral", "-C", input.workspaceRoot, input.prompt], {
104
+ stdio: ["ignore", "pipe", "pipe"],
105
+ })
106
+ let stdout = ""
107
+ let stderr = ""
108
+ const timer = setTimeout(() => {
109
+ child.kill()
110
+ resolve({
111
+ exitCode: 124,
112
+ stdout,
113
+ stderr: `${stderr}${stderr ? "\n" : ""}codex exec timed out after ${input.timeoutMs}ms.`,
114
+ })
115
+ }, input.timeoutMs)
116
+ child.stdout?.on("data", (chunk) => {
117
+ stdout += chunk.toString()
118
+ })
119
+ child.stderr?.on("data", (chunk) => {
120
+ stderr += chunk.toString()
121
+ })
122
+ child.on("error", (error) => {
123
+ clearTimeout(timer)
124
+ resolve({ exitCode: 127, stdout, stderr: error.message })
125
+ })
126
+ child.on("close", (code) => {
127
+ clearTimeout(timer)
128
+ resolve({ exitCode: code, stdout, stderr })
129
+ })
130
+ })
131
+ }
132
+
133
+ function extractInspectionResult(stdout: string): InspectionResult | undefined {
134
+ const direct = parseJson(stdout)
135
+ const fromDirect = findInspectionResult(direct)
136
+ if (fromDirect) return fromDirect
137
+
138
+ for (const line of stdout.split(/\r?\n/).reverse()) {
139
+ const parsed = parseJson(line)
140
+ const found = findInspectionResult(parsed)
141
+ if (found) return found
142
+ }
143
+
144
+ for (const block of extractJsonBlocks(stdout).reverse()) {
145
+ const parsed = parseJson(block)
146
+ const found = findInspectionResult(parsed)
147
+ if (found) return found
148
+ }
149
+ }
150
+
151
+ function findInspectionResult(value: unknown): InspectionResult | undefined {
152
+ if (!value) return undefined
153
+ if (typeof value === "string") return findInspectionResult(parseJson(value))
154
+ if (Array.isArray(value)) {
155
+ for (const item of value) {
156
+ const found = findInspectionResult(item)
157
+ if (found) return found
158
+ }
159
+ return undefined
160
+ }
161
+ if (typeof value !== "object") return undefined
162
+ const record = value as Record<string, unknown>
163
+ if (record.version === 1 && typeof record.status === "string" && record.cards && typeof record.cards === "object") {
164
+ return record as unknown as InspectionResult
165
+ }
166
+ for (const key of ["result", "output", "message", "content", "text", "final", "lastMessage"]) {
167
+ const found = findInspectionResult(record[key])
168
+ if (found) return found
169
+ }
170
+ return undefined
171
+ }
172
+
173
+ function parseJson(value: string | undefined): unknown {
174
+ const text = value?.trim()
175
+ if (!text) return undefined
176
+ try {
177
+ return JSON.parse(text)
178
+ } catch {
179
+ return undefined
180
+ }
181
+ }
182
+
183
+ function extractJsonBlocks(text: string): string[] {
184
+ const blocks: string[] = []
185
+ let depth = 0
186
+ let start = -1
187
+ let inString = false
188
+ let escaped = false
189
+ for (let index = 0; index < text.length; index += 1) {
190
+ const char = text[index]
191
+ if (inString) {
192
+ if (escaped) {
193
+ escaped = false
194
+ } else if (char === "\\") {
195
+ escaped = true
196
+ } else if (char === "\"") {
197
+ inString = false
198
+ }
199
+ continue
200
+ }
201
+ if (char === "\"") {
202
+ inString = true
203
+ continue
204
+ }
205
+ if (char === "{") {
206
+ if (depth === 0) start = index
207
+ depth += 1
208
+ continue
209
+ }
210
+ if (char === "}") {
211
+ depth -= 1
212
+ if (depth === 0 && start >= 0) {
213
+ blocks.push(text.slice(start, index + 1))
214
+ start = -1
215
+ }
216
+ }
217
+ }
218
+ return blocks
219
+ }
@@ -0,0 +1,41 @@
1
+ import { resolve } from "path"
2
+
3
+ export interface ReviewApplyFixArtifactQaSuppressionInput {
4
+ workspaceRoot: string
5
+ file: string
6
+ sessionID?: string
7
+ ttlMs?: number
8
+ }
9
+
10
+ const DEFAULT_TTL_MS = 5 * 60 * 1000
11
+ const suppressions = new Map<string, number>()
12
+
13
+ export function suppressReviewApplyFixArtifactQa(input: ReviewApplyFixArtifactQaSuppressionInput): void {
14
+ const key = suppressionKey(input)
15
+ if (!key) return
16
+ suppressions.set(key, Date.now() + (input.ttlMs ?? DEFAULT_TTL_MS))
17
+ }
18
+
19
+ export function shouldSuppressReviewApplyFixArtifactQa(input: ReviewApplyFixArtifactQaSuppressionInput): boolean {
20
+ const key = suppressionKey(input)
21
+ if (!key) return false
22
+ const expiresAt = suppressions.get(key)
23
+ if (!expiresAt) return false
24
+ if (Date.now() > expiresAt) {
25
+ suppressions.delete(key)
26
+ return false
27
+ }
28
+ return true
29
+ }
30
+
31
+ export function clearReviewApplyFixArtifactQaSuppressionsForTests(): void {
32
+ suppressions.clear()
33
+ }
34
+
35
+ function suppressionKey(input: ReviewApplyFixArtifactQaSuppressionInput): string {
36
+ const sessionID = input.sessionID?.trim()
37
+ if (!sessionID) return ""
38
+ const workspaceRoot = resolve(input.workspaceRoot)
39
+ const file = resolve(workspaceRoot, input.file)
40
+ return `${workspaceRoot}\0${file}\0${sessionID}`
41
+ }
@@ -9,10 +9,13 @@ import { buildPrompt } from "../prompt-builder"
9
9
  import type { InspectionElementSnapshot } from "../inspection-context/match"
10
10
  import { buildInspectionPrompt } from "../inspect/prompt"
11
11
  import { projectWorkspaceElement } from "../inspect/request"
12
- import { createInspectRequest, failInspectRequest, getInspectRequest } from "../inspect/requests"
12
+ import { completeInspectRequest, createInspectRequest, failInspectRequest, getInspectRequest } from "../inspect/requests"
13
13
  import { saveMediaAsset } from "../media/save"
14
14
  import { searchRemoteImages, type ImageCandidate } from "../media/search"
15
15
  import type { MediaAssetRecord, MediaPurpose } from "../media/types"
16
+ import { completeCommentRequest, createCommentRequest, failCommentRequest, getCommentRequest } from "./comment-requests"
17
+ import { createOpenCodeReviewPromptBridge, type ReviewPromptBridge } from "./prompt-bridge"
18
+ import { suppressReviewApplyFixArtifactQa } from "./qa-suppression"
16
19
  import { annotateVisualEditTargets, applyVisualTargetChanges, type VisualEditTarget } from "./visual-targets"
17
20
 
18
21
  const TOKEN_BYTES = 24
@@ -27,8 +30,9 @@ interface EditAsset {
27
30
 
28
31
  interface EditSession {
29
32
  token: string
30
- client: any
31
- sessionID: string
33
+ client?: any
34
+ sessionID?: string
35
+ promptBridge: ReviewPromptBridge
32
36
  deck: string
33
37
  file: string
34
38
  absoluteFile: string
@@ -47,7 +51,14 @@ export type RefineMode = "edit" | "inspect"
47
51
 
48
52
  export interface RefineServerHandle {
49
53
  baseUrl: string
50
- getOrCreateSession(input: { client: any; sessionID: string; workspaceRoot: string; deck: EditableDeck; mode?: RefineMode }): EditServerSessionResult
54
+ getOrCreateSession(input: {
55
+ client?: any
56
+ sessionID?: string
57
+ workspaceRoot: string
58
+ deck: EditableDeck
59
+ mode?: RefineMode
60
+ promptBridge?: ReviewPromptBridge
61
+ }): EditServerSessionResult
51
62
  }
52
63
 
53
64
  export interface EditServerSessionResult {
@@ -80,6 +91,7 @@ export function startRefineServer(): RefineServerHandle {
80
91
  if (existing) {
81
92
  existing.session.client = input.client
82
93
  existing.session.sessionID = input.sessionID
94
+ existing.session.promptBridge = input.promptBridge ?? createOpenCodeReviewPromptBridge(input.client, input.sessionID ?? "")
83
95
  existing.session.deck = input.deck.slug
84
96
  existing.session.file = input.deck.file
85
97
  existing.session.workspaceRoot = resolve(input.workspaceRoot)
@@ -97,6 +109,7 @@ export function startRefineServer(): RefineServerHandle {
97
109
  token,
98
110
  client: input.client,
99
111
  sessionID: input.sessionID,
112
+ promptBridge: input.promptBridge ?? createOpenCodeReviewPromptBridge(input.client, input.sessionID ?? ""),
100
113
  deck: input.deck.slug,
101
114
  file: input.deck.file,
102
115
  absoluteFile: input.deck.absoluteFile,
@@ -182,6 +195,12 @@ async function handleRequest(req: Request): Promise<Response> {
182
195
  return handleComment(req, session.value)
183
196
  }
184
197
 
198
+ if (url.pathname === "/api/comment-result" && req.method === "GET") {
199
+ const session = validateSession(url.searchParams.get("token"))
200
+ if (!session.ok) return session.response
201
+ return handleCommentResult(url.searchParams.get("requestId"), session.value)
202
+ }
203
+
185
204
  if (url.pathname === "/api/inspect" && req.method === "POST") {
186
205
  const session = validateSession(url.searchParams.get("token"))
187
206
  if (!session.ok) return session.response
@@ -727,19 +746,51 @@ async function handleComment(req: Request, session: EditSession): Promise<Respon
727
746
  comment,
728
747
  elements,
729
748
  comments,
749
+ suppressAutomaticArtifactQa: true,
730
750
  })
731
751
  const deckVersion = readDeckVersion(session).version
752
+ const requestId = typeof (body as any).requestId === "string" && (body as any).requestId.trim()
753
+ ? (body as any).requestId.trim()
754
+ : randomBytes(10).toString("base64url")
755
+ createCommentRequest({ requestId, deckVersion })
756
+ suppressReviewApplyFixArtifactQa({
757
+ workspaceRoot: session.workspaceRoot,
758
+ file: session.file,
759
+ sessionID: session.sessionID,
760
+ })
732
761
 
733
- await session.client.session.prompt({
734
- path: { id: session.sessionID },
735
- body: {
736
- parts: [{ type: "text", text: prompt }],
737
- },
762
+ void session.promptBridge.send({
763
+ action: "comment",
764
+ prompt,
765
+ workspaceRoot: session.workspaceRoot,
766
+ file: session.file,
767
+ requestId,
768
+ }).then((result) => {
769
+ if (result.ok) {
770
+ completeCommentRequest(requestId)
771
+ } else {
772
+ failCommentRequest(requestId, result.error)
773
+ }
774
+ }).catch((error: unknown) => {
775
+ const message = error instanceof Error ? error.message : String(error)
776
+ failCommentRequest(requestId, message)
738
777
  })
739
778
 
740
779
  session.lastActiveAt = Date.now()
741
780
  scheduleIdleStop()
742
- return jsonResponse({ ok: true, deckVersion })
781
+ return jsonResponse({ ok: true, requestId, commentRequestId: requestId, deckVersion, status: "pending" })
782
+ }
783
+
784
+ function handleCommentResult(requestId: string | null, session: EditSession): Response {
785
+ if (!requestId) return jsonResponse({ ok: false, error: "Missing requestId" }, 400)
786
+ const request = getCommentRequest(requestId)
787
+ if (!request) return jsonResponse({ ok: false, requestId, error: "Comment request not found" }, 404)
788
+ session.lastActiveAt = Date.now()
789
+ scheduleIdleStop()
790
+ if (request.status === "failed" || request.status === "expired") {
791
+ return jsonResponse({ ok: true, requestId, status: request.status, deckVersion: request.deckVersion, error: request.error || "Review agent failed" })
792
+ }
793
+ return jsonResponse({ ok: true, requestId, status: request.status, deckVersion: request.deckVersion })
743
794
  }
744
795
 
745
796
  async function handleInspect(req: Request, session: EditSession): Promise<Response> {
@@ -766,22 +817,29 @@ async function handleInspect(req: Request, session: EditSession): Promise<Respon
766
817
  session.lastActiveAt = Date.now()
767
818
  scheduleIdleStop()
768
819
 
769
- void session.client.session.prompt({
770
- path: { id: session.sessionID },
771
- body: {
772
- parts: [{
773
- type: "text",
774
- text: buildInspectionPrompt({
775
- requestId,
776
- file: session.file,
777
- language,
778
- comment,
779
- projection: staleReason
780
- ? { ...projection, stale: { stale: true, reason: staleReason } } as any
781
- : projection,
782
- }),
783
- }],
784
- },
820
+ const prompt = buildInspectionPrompt({
821
+ requestId,
822
+ file: session.file,
823
+ language,
824
+ comment,
825
+ delivery: session.promptBridge.kind === "codex-exec" ? "json" : "tool",
826
+ projection: staleReason
827
+ ? { ...projection, stale: { stale: true, reason: staleReason } } as any
828
+ : projection,
829
+ })
830
+
831
+ void session.promptBridge.send({
832
+ action: "inspect",
833
+ prompt,
834
+ workspaceRoot: session.workspaceRoot,
835
+ file: session.file,
836
+ requestId,
837
+ }).then((result) => {
838
+ if (result.ok && result.result) {
839
+ completeInspectRequest(requestId, result.result)
840
+ } else if (!result.ok) {
841
+ failInspectRequest(requestId, result.error)
842
+ }
785
843
  }).catch((error: unknown) => {
786
844
  const message = error instanceof Error ? error.message : String(error)
787
845
  failInspectRequest(requestId, message)
@@ -2038,14 +2096,14 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2038
2096
  });
2039
2097
  const body = await res.json().catch(() => ({}));
2040
2098
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to send comment');
2041
- updatePendingCommentStatus(commentId, 'sent', { baseDeckVersion: body.deckVersion || state.deckVersion });
2099
+ updatePendingCommentStatus(commentId, 'sent', { baseDeckVersion: body.deckVersion || state.deckVersion, requestId: body.commentRequestId || body.requestId || '' });
2042
2100
  if (pendingCommentStatus(commentId) !== 'updated') setStatus('Comment sent. Waiting for deck update...');
2043
- state.sendingEdit = false;
2044
- updateSendState();
2101
+ if (body.commentRequestId || body.requestId) pollCommentResult(commentId, body.commentRequestId || body.requestId);
2045
2102
  } catch (error) {
2046
2103
  updatePendingCommentStatus(commentId, 'failed');
2047
- state.sendingEdit = false;
2048
2104
  reportError(error);
2105
+ } finally {
2106
+ state.sendingEdit = false;
2049
2107
  updateSendState();
2050
2108
  }
2051
2109
  }
@@ -2398,8 +2456,9 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2398
2456
  });
2399
2457
  const body = await res.json().catch(() => ({}));
2400
2458
  if (!res.ok || !body.ok) throw new Error(body.error || 'Failed to send asset placement');
2401
- updatePendingCommentStatus(commentId, 'sent', { baseDeckVersion: body.deckVersion || state.deckVersion });
2459
+ updatePendingCommentStatus(commentId, 'sent', { baseDeckVersion: body.deckVersion || state.deckVersion, requestId: body.commentRequestId || body.requestId || '' });
2402
2460
  if (pendingCommentStatus(commentId) !== 'updated') setStatus('Asset placement sent. Waiting for deck update...');
2461
+ if (body.commentRequestId || body.requestId) pollCommentResult(commentId, body.commentRequestId || body.requestId);
2403
2462
  } catch (error) {
2404
2463
  updatePendingCommentStatus(commentId, 'failed');
2405
2464
  reportError(error);
@@ -2483,6 +2542,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2483
2542
  createdAt: Date.now(),
2484
2543
  baseDeckVersion: state.deckVersion,
2485
2544
  updatedVersion: null,
2545
+ requestId: '',
2486
2546
  });
2487
2547
  renderCommentThread();
2488
2548
  return id;
@@ -2531,6 +2591,34 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2531
2591
  return state.pendingComments.find((comment) => comment.id === id)?.status || '';
2532
2592
  }
2533
2593
 
2594
+ async function pollCommentResult(commentId, requestId) {
2595
+ if (!requestId) return;
2596
+ for (let attempt = 0; attempt < 140; attempt++) {
2597
+ await delay(1000);
2598
+ if (pendingCommentStatus(commentId) === 'updated') return;
2599
+ try {
2600
+ const res = await fetch('/api/comment-result?token=' + encodeURIComponent(token) + '&requestId=' + encodeURIComponent(requestId), { cache: 'no-store' });
2601
+ const body = await res.json().catch(() => ({}));
2602
+ if (!res.ok || !body.ok) throw new Error(body.error || 'Comment result failed');
2603
+ if (body.status === 'failed' || body.status === 'expired') {
2604
+ updatePendingCommentStatus(commentId, 'failed');
2605
+ setStatus(body.error || 'Review agent failed to apply the comment.');
2606
+ return;
2607
+ }
2608
+ if (body.status === 'completed') {
2609
+ return;
2610
+ }
2611
+ } catch (error) {
2612
+ reportError(error);
2613
+ return;
2614
+ }
2615
+ }
2616
+ if (pendingCommentStatus(commentId) !== 'updated') {
2617
+ updatePendingCommentStatus(commentId, 'failed');
2618
+ setStatus('Review agent timed out before applying the comment.');
2619
+ }
2620
+ }
2621
+
2534
2622
  function renderCommentThread() {
2535
2623
  els.commentThread.textContent = '';
2536
2624
  state.pendingComments.forEach((comment) => {
@@ -2555,8 +2643,8 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2555
2643
  if (status === 'updated') return 'Deck file updated';
2556
2644
  if (status === 'stale') return 'Still waiting for deck file update';
2557
2645
  if (status === 'failed') return 'Failed to send';
2558
- if (status === 'sending') return 'Sending to OpenCode...';
2559
- return 'Sent to OpenCode';
2646
+ if (status === 'sending') return 'Sending to Review agent...';
2647
+ return 'Sent to Review agent';
2560
2648
  }
2561
2649
 
2562
2650
  function targetFromPointer(event) {
@@ -2735,7 +2823,7 @@ export function renderRefineShell(token: string, defaultMode: RefineMode = "edit
2735
2823
  }
2736
2824
  if (body.status === 'failed' || body.status === 'expired') throw new Error(body.error || 'Insight failed');
2737
2825
  }
2738
- throw new Error('Insight timed out while waiting for OpenCode result');
2826
+ throw new Error('Insight timed out while waiting for Review agent result');
2739
2827
  }
2740
2828
 
2741
2829
  function collectReferenceSnapshot() {