@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
package/README.md CHANGED
@@ -2,19 +2,21 @@
2
2
 
3
3
  **English** | [中文](README.zh-CN.md)
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-546%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-609%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
6
6
 
7
7
  <p align="center">
8
8
  <img src="assets/img/logo.png" alt="Revela" width="560" />
9
9
  </p>
10
10
 
11
- Revela is an [OpenCode](https://opencode.ai) plugin that turns local sources and research into a traceable narrative graph, then renders that graph into briefs and presentation decks.
11
+ Revela works from [OpenCode](https://opencode.ai) and Codex to turn source materials, research, data, and intent into trusted, traceable, presentation-ready decision artifacts.
12
12
 
13
- The narrative graph records the core elements needed to generate a brief or deck: audience, decision, claims, evidence, sources, risks, objections, and open gaps.
13
+ Its narrative workspace records the core elements needed to generate a brief or deck: audience, decision, claims, evidence, sources, risks, objections, and open gaps.
14
14
 
15
15
  ## Install
16
16
 
17
- Add Revela to your `opencode.json`:
17
+ ### OpenCode
18
+
19
+ Install Revela through `opencode.json` with the npm package `@cyber-dash-tech/revela`:
18
20
 
19
21
  ```json
20
22
  {
@@ -27,6 +29,19 @@ Restart OpenCode.
27
29
 
28
30
  To install globally, add the same entry to `~/.config/opencode/opencode.json`.
29
31
 
32
+ ### Codex
33
+
34
+ Install Revela through the Codex Git marketplace:
35
+
36
+ ```bash
37
+ codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref main
38
+ codex plugin add revela@revela
39
+ ```
40
+
41
+ Install from the full repository ref. Do not use a sparse checkout limited to `plugins/revela`; the Codex plugin resolves the shared runtime, built-in designs, and domains from the repository snapshot.
42
+
43
+ Use a release tag instead of `main` when you want a pinned install.
44
+
30
45
  ## Built-In Designs
31
46
 
32
47
  Revela includes built-in deck designs:
@@ -55,6 +70,8 @@ Switch designs with:
55
70
  /revela design --use summit
56
71
  ```
57
72
 
73
+ In Codex, ask Revela to list or switch designs; the plugin uses the active design when making decks.
74
+
58
75
  ## Domains
59
76
 
60
77
  Domains add topic-specific narrative guidance, such as consulting, product, or investor communication. Use them when you want Revela to adapt story framing to a specific context.
@@ -63,52 +80,15 @@ Domains add topic-specific narrative guidance, such as consulting, product, or i
63
80
  /revela domain
64
81
  ```
65
82
 
66
- ## Quick Start: Make An HTML Deck
83
+ In Codex, ask Revela to list or switch domains; the active domain guides narrative framing during init, research, and story work.
67
84
 
68
- 1. Initialize the narrative workspace from your local source materials.
85
+ ## Quick Start
69
86
 
70
- ```text
71
- /revela init
72
- ```
73
-
74
- 2. Research missing evidence and bind useful findings to claims.
75
-
76
- ```text
77
- /revela research
78
- ```
79
-
80
- Skip this step if your local materials already provide enough support.
87
+ Start with the local source materials and intent that should ground the communication. Revela identifies the audience, decision, claims, evidence, risks, objections, and gaps that shape the narrative.
81
88
 
82
- 3. Inspect the claim flow before rendering.
89
+ When evidence is missing, use research to gather findings and bind only the supported parts back to the narrative. Before rendering, read the Story view to inspect the claim flow, evidence support, caveats, and open gaps.
83
90
 
84
- ```text
85
- /revela story
86
- ```
87
-
88
- Use this to check the audience, decision, claims, evidence, gaps, risks, and objections.
89
-
90
- 4. Generate the HTML deck.
91
-
92
- ```text
93
- /revela make --deck
94
- ```
95
-
96
- Revela writes the deck under `decks/` and uses the current narrative, deck plan, and active design.
97
-
98
- 5. Review or revise the deck.
99
-
100
- ```text
101
- /revela review --deck
102
- ```
103
-
104
- See [Review A Deck](#review-a-deck) for Insight and Comment.
105
-
106
- 6. Export if needed.
107
-
108
- ```text
109
- /revela export --deck pdf decks/example.html
110
- /revela export --deck pptx decks/example.html
111
- ```
91
+ Make a deck or brief from the canonical narrative when the story is ready to present. Review the artifact with Insight to understand support and traceability, use Comment for targeted changes, and export to PDF or PPTX when you need a shareable file.
112
92
 
113
93
  ## Review A Deck
114
94
 
package/README.zh-CN.md CHANGED
@@ -2,19 +2,21 @@
2
2
 
3
3
  [English](README.md) | **中文**
4
4
 
5
- [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-546%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
5
+ [![npm version](https://img.shields.io/npm/v/@cyber-dash-tech/revela)](https://www.npmjs.com/package/@cyber-dash-tech/revela) [![license](https://img.shields.io/npm/l/@cyber-dash-tech/revela)](LICENSE) [![tests](https://img.shields.io/badge/tests-609%20passing-brightgreen)](tests/) [![OpenCode plugin](https://img.shields.io/badge/OpenCode-plugin-blue)](https://opencode.ai) [![Bun](https://img.shields.io/badge/Bun-%E2%89%A51.0-orange)](https://bun.sh)
6
6
 
7
7
  <p align="center">
8
8
  <img src="assets/img/logo.png" alt="Revela" width="560" />
9
9
  </p>
10
10
 
11
- Revela 是一个 [OpenCode](https://opencode.ai) 插件,用来把本地材料和调研结果转成可追踪的叙事图谱,再基于这个图谱生成 brief presentation deck
11
+ Revela 可在 [OpenCode](https://opencode.ai) Codex 中使用,把来源材料、调研、数据和用户意图转成可信、可追踪、可直接用于决策沟通的 narrative artifact
12
12
 
13
- 叙事图谱用 graph 方式记录生成 brief 或 deck 所需的关键要素:受众、决策目标、论点、论据、资料来源、风险、潜在质疑和待补齐的信息。
13
+ 它的 narrative workspace 会记录生成 brief 或 deck 所需的关键要素:受众、决策目标、论点、论据、资料来源、风险、潜在质疑和待补齐的信息。
14
14
 
15
15
  ## 安装
16
16
 
17
- `opencode.json` 中加入 Revela:
17
+ ### OpenCode
18
+
19
+ 通过 `opencode.json` 安装 npm package `@cyber-dash-tech/revela`:
18
20
 
19
21
  ```json
20
22
  {
@@ -27,6 +29,19 @@ Revela 是一个 [OpenCode](https://opencode.ai) 插件,用来把本地材料
27
29
 
28
30
  如果想全局安装,把同样配置写到 `~/.config/opencode/opencode.json`。
29
31
 
32
+ ### Codex
33
+
34
+ 通过 Codex Git marketplace 安装 Revela:
35
+
36
+ ```bash
37
+ codex plugin marketplace add https://github.com/cyber-dash-tech/revela --ref main
38
+ codex plugin add revela@revela
39
+ ```
40
+
41
+ 请从完整仓库 ref 安装。不要使用仅包含 `plugins/revela` 的 sparse checkout;Codex plugin 会从仓库快照中解析 shared runtime、内置 designs 和 domains。
42
+
43
+ 如果需要固定版本,把 `main` 换成 release tag。
44
+
30
45
  ## 内置设计
31
46
 
32
47
  Revela 内置多个 deck design:
@@ -55,6 +70,8 @@ Revela 内置多个 deck design:
55
70
  /revela design --use summit
56
71
  ```
57
72
 
73
+ 在 Codex 中,可以直接让 Revela 列出或切换 design;生成 deck 时会使用 active design。
74
+
58
75
  ## Domains
59
76
 
60
77
  Domain 提供特定场景的叙事 guidance,例如 consulting、product 或 investor communication。需要让 Revela 按具体沟通场景调整 story framing 时使用。
@@ -63,52 +80,15 @@ Domain 提供特定场景的叙事 guidance,例如 consulting、product 或 in
63
80
  /revela domain
64
81
  ```
65
82
 
66
- ## Quick Start:生成 HTML Deck
83
+ Codex 中,可以直接让 Revela 列出或切换 domain;active domain 会用于 init、research 和 story 阶段的叙事 framing。
67
84
 
68
- 1. 从本地来源材料初始化 narrative workspace。
85
+ ## Quick Start
69
86
 
70
- ```text
71
- /revela init
72
- ```
73
-
74
- 2. 补充缺失证据,并把有效 findings 绑定到 claims。
75
-
76
- ```text
77
- /revela research
78
- ```
79
-
80
- 如果本地材料已经足够支撑 story,可以跳过这一步。
87
+ 从本地来源材料和沟通意图开始,让 Revela 识别受众、决策目标、论点、论据、风险、潜在质疑和信息缺口。
81
88
 
82
- 3. 在生成 deck 前检查 claim flow。
89
+ 证据不足时,用 research 补充 findings,并只把被来源明确支持的部分绑定回 narrative。渲染前先阅读 Story view,检查 claim flow、证据支撑、caveat 和 open gap
83
90
 
84
- ```text
85
- /revela story
86
- ```
87
-
88
- 用它检查 audience、decision、claims、evidence、gaps、risks 和 objections。
89
-
90
- 4. 生成 HTML deck。
91
-
92
- ```text
93
- /revela make --deck
94
- ```
95
-
96
- Revela 会把 deck 写到 `decks/`,并使用当前 narrative、deck plan 和 active design。
97
-
98
- 5. Review 或修改 deck。
99
-
100
- ```text
101
- /revela review --deck
102
- ```
103
-
104
- 详见 [Review Deck](#review-deck)。
105
-
106
- 6. 按需导出。
107
-
108
- ```text
109
- /revela export --deck pdf decks/example.html
110
- /revela export --deck pptx decks/example.html
111
- ```
91
+ 当 story 已经适合呈现时,从 canonical narrative 生成 deck 或 brief。之后用 Insight 理解 artifact 的支撑关系和可追踪性,用 Comment 做定向修改,需要分享文件时再导出为 PDF 或 PPTX。
112
92
 
113
93
  ## Review Deck
114
94
 
package/bin/revela.ts ADDED
@@ -0,0 +1,98 @@
1
+ #!/usr/bin/env bun
2
+
3
+ type CommandResult = unknown | Promise<unknown>
4
+
5
+ const [argvCommand, ...args] = process.argv.slice(2)
6
+ const command = process.env.REVELA_CLI_COMMAND || argvCommand
7
+
8
+ if (!command || command === "help" || command === "--help" || command === "-h") {
9
+ printHelp()
10
+ process.exit(0)
11
+ }
12
+
13
+ if (command === "mcp") {
14
+ await import("../plugins/revela/mcp/revela-server")
15
+ }
16
+ else {
17
+ const runtime = await import("../lib/runtime/index")
18
+ const options = parseArgs(args)
19
+
20
+ try {
21
+ let result: CommandResult
22
+ if (command === "doctor") result = runtime.doctor(options)
23
+ else if (command === "compile") result = runtime.compileNarrative(options)
24
+ else if (command === "markdown-qa") result = runtime.markdownQa(options)
25
+ else if (command === "deck-plan") result = runtime.readDeckPlan(options)
26
+ else if (command === "deck-foundation") result = runtime.createDeckFoundation(required(options, ["outputPath", "title", "language"]))
27
+ else if (command === "qa") result = runtime.runDeckQa(required(options, ["file"]))
28
+ else if (command === "review-read") result = runtime.reviewDeckRead(required(options, ["file"]))
29
+ else if (command === "export-pdf") result = runtime.exportPdf(required(options, ["file"]))
30
+ else if (command === "export-pptx") result = runtime.exportPptx(required(options, ["file"]))
31
+ else if (command === "design-list") result = runtime.designList()
32
+ else if (command === "design-read") result = runtime.designRead(options)
33
+ else if (command === "design-use") result = runtime.designActivate(required(options, ["name"]))
34
+ else if (command === "domain-list") result = runtime.domainList()
35
+ else if (command === "domain-read") result = runtime.domainRead(options)
36
+ else if (command === "domain-use") result = runtime.domainActivate(required(options, ["name"]))
37
+ else {
38
+ throw new Error(`Unknown command: ${command}`)
39
+ }
40
+
41
+ process.stdout.write(`${JSON.stringify(await result, null, 2)}\n`)
42
+ } catch (e) {
43
+ process.stderr.write(`${e instanceof Error ? e.message : String(e)}\n`)
44
+ process.exit(1)
45
+ }
46
+ }
47
+
48
+ function parseArgs(values: string[]): Record<string, any> {
49
+ const result: Record<string, any> = {}
50
+ for (let i = 0; i < values.length; i++) {
51
+ const arg = values[i]
52
+ if (!arg.startsWith("--")) throw new Error(`Unexpected argument: ${arg}`)
53
+ const key = arg.slice(2)
54
+ const next = values[i + 1]
55
+ if (next === undefined || next.startsWith("--")) {
56
+ result[key] = true
57
+ continue
58
+ }
59
+ result[key] = parseValue(next)
60
+ i++
61
+ }
62
+ return result
63
+ }
64
+
65
+ function parseValue(value: string): unknown {
66
+ if (value === "true") return true
67
+ if (value === "false") return false
68
+ return value
69
+ }
70
+
71
+ function required(input: Record<string, any>, keys: string[]): Record<string, any> {
72
+ const missing = keys.filter((key) => input[key] === undefined || input[key] === "")
73
+ if (missing.length > 0) throw new Error(`Missing required option(s): ${missing.map((key) => `--${key}`).join(", ")}`)
74
+ return input
75
+ }
76
+
77
+ function printHelp(): void {
78
+ process.stdout.write(`Revela CLI
79
+
80
+ Usage:
81
+ revela mcp
82
+ revela doctor [--workspaceRoot <path>]
83
+ revela compile [--workspaceRoot <path>]
84
+ revela markdown-qa [--workspaceRoot <path>] [--scope touched|affected|full] [--strictness authoring|readiness|render]
85
+ revela deck-plan [--workspaceRoot <path>]
86
+ revela deck-foundation --outputPath <path> --title <title> --language <tag> [--workspaceRoot <path>] [--designName <name>] [--mode create|repair] [--overwrite true]
87
+ revela qa --file <path> [--workspaceRoot <path>]
88
+ revela review-read --file <path> [--workspaceRoot <path>] [--format json|markdown]
89
+ revela export-pdf --file <path> [--workspaceRoot <path>]
90
+ revela export-pptx --file <path> [--workspaceRoot <path>]
91
+ revela design-list
92
+ revela design-read [--name <design>]
93
+ revela design-use --name <design>
94
+ revela domain-list
95
+ revela domain-read [--name <domain>]
96
+ revela domain-use --name <domain>
97
+ `)
98
+ }
@@ -26,6 +26,7 @@ export interface EditCommentPayload extends EditSelectedElementPayload {
26
26
  comments?: EditCommentDraftPayload[]
27
27
  asset?: Record<string, unknown>
28
28
  drop?: Record<string, unknown>
29
+ suppressAutomaticArtifactQa?: boolean
29
30
  }
30
31
 
31
32
  export function buildEditPrompt(payload: EditCommentPayload): string {
@@ -59,6 +60,10 @@ export function buildEditPrompt(payload: EditCommentPayload): string {
59
60
  asset: payload.asset,
60
61
  drop: payload.drop,
61
62
  }
63
+ const qaInstruction = payload.suppressAutomaticArtifactQa
64
+ ? `- Do not run artifact QA after this edit and do not keep editing just to satisfy post-write QA. The Review UI will refresh from the deck file version change; QA can be run later through an explicit Review, QA, or export workflow.`
65
+ : `- Artifact QA runs automatically after deck writes/patches/edits. It checks deck HTML contract, design component compliance, exact 1920x1080 slide geometry, scrollbars, element overflow, text clipping, and claim/evidence content-density warnings.
66
+ - If the tool result reports hard QA errors, fix them with the smallest targeted patch and let the post-write QA run again. Refine opens automatically only after hard errors pass; warnings such as thin claim/evidence substance do not block opening.`
62
67
 
63
68
  return `The user left a visual edit comment on a Revela slide deck.
64
69
 
@@ -95,7 +100,6 @@ Instructions:
95
100
  - For targeted artifact-level edits, patch ${"`decks/*.html`"} directly. Do not call ${"`revela-decks`"} action ${"`review`"} as a precondition, and do not let ${"`writeReadiness`"}, ${"`planReview`"}, or ${"`slide_plan_unconfirmed`"} block the patch.
96
101
  - Do not patch or write ${"`DECKS.json`"} directly. If state must change, use the ${"`revela-decks`"} tool.
97
102
  - Apply the edit to ${payload.file} with the smallest targeted HTML patch that satisfies the comment.
98
- - Artifact QA runs automatically after deck writes/patches/edits. It checks deck HTML contract, design component compliance, exact 1920x1080 slide geometry, scrollbars, element overflow, text clipping, and claim/evidence content-density warnings.
99
- - If the tool result reports hard QA errors, fix them with the smallest targeted patch and let the post-write QA run again. Refine opens automatically only after hard errors pass; warnings such as thin claim/evidence substance do not block opening.
103
+ ${qaInstruction}
100
104
  - If the comment is ambiguous, ask one concise clarification question instead of guessing.`
101
105
  }
@@ -1003,8 +1003,8 @@ export function renderEditorShell(token: string): string {
1003
1003
  if (status === 'updated') return 'Deck file updated';
1004
1004
  if (status === 'stale') return 'Still waiting for deck file update';
1005
1005
  if (status === 'failed') return 'Failed to send';
1006
- if (status === 'sending') return 'Sending to OpenCode...';
1007
- return 'Sent to OpenCode';
1006
+ if (status === 'sending') return 'Sending to Review agent...';
1007
+ return 'Sent to Review agent';
1008
1008
  }
1009
1009
 
1010
1010
  function targetFromPointer(event) {
@@ -6,9 +6,11 @@ export function buildInspectionPrompt(input: {
6
6
  projection: InspectionPromptProjection
7
7
  language?: string
8
8
  comment?: string
9
+ delivery?: "tool" | "json"
9
10
  }): string {
10
11
  const language = normalizeInspectLanguage(input.language)
11
12
  const comment = typeof input.comment === "string" && input.comment.trim() ? input.comment.trim() : ""
13
+ const delivery = input.delivery ?? "tool"
12
14
  return `A user selected slide content in Revela Evidence Inspector. The selection may contain one referenced element, a whole slide, or multiple referenced elements selected with Cmd/Ctrl-click.
13
15
 
14
16
  Target file: ${input.file}
@@ -20,7 +22,9 @@ Use the structured projection below to produce the final inspector cards. This i
20
22
 
21
23
  Language boundary: the selected display language affects only human-readable card copy. Preserve all claim ids, canonical claim ids, evidence binding ids, source paths, findings files, URLs, numbers, quoted/source facts, caveats, artifact ids, and coverage statuses exactly as grounded in the projection. If the display language is Auto, use projection.deck.language when available; otherwise follow the user's/browser context or default to English.
22
24
 
23
- Return the result only by calling the \`revela-inspection-result\` tool with this request id. Do not answer in chat.
25
+ ${delivery === "json"
26
+ ? "Return only a single JSON object that matches the final inspector result schema. Do not wrap it in Markdown. Do not call tools. Do not edit files."
27
+ : "Return the result only by calling the `revela-inspection-result` tool with this request id. Do not answer in chat."}
24
28
 
25
29
  Required card model:
26
30
  - User inspect comment: if present, answer it through the Purpose and Source cards first. If it asks about trust, provenance, evidence, factuality, or where a number came from, prioritize Source. If it asks why something is on the slide or what it is doing, prioritize Purpose.
@@ -0,0 +1,77 @@
1
+ export type CommentRequestStatus = "pending" | "completed" | "failed" | "expired"
2
+
3
+ export interface PendingCommentRequest {
4
+ requestId: string
5
+ status: CommentRequestStatus
6
+ deckVersion: string
7
+ createdAt: number
8
+ updatedAt: number
9
+ error?: string
10
+ }
11
+
12
+ const REQUEST_TTL_MS = 120 * 1000
13
+ const requests = new Map<string, PendingCommentRequest>()
14
+
15
+ export function createCommentRequest(input: {
16
+ requestId: string
17
+ deckVersion: string
18
+ }): PendingCommentRequest {
19
+ cleanupCommentRequests()
20
+ const now = Date.now()
21
+ const request: PendingCommentRequest = {
22
+ requestId: input.requestId,
23
+ status: "pending",
24
+ deckVersion: input.deckVersion,
25
+ createdAt: now,
26
+ updatedAt: now,
27
+ }
28
+ requests.set(input.requestId, request)
29
+ return request
30
+ }
31
+
32
+ export function getCommentRequest(requestId: string): PendingCommentRequest | undefined {
33
+ cleanupCommentRequests()
34
+ const request = requests.get(requestId)
35
+ if (!request) return undefined
36
+ if (request.status === "pending" && Date.now() - request.createdAt > REQUEST_TTL_MS) {
37
+ request.status = "expired"
38
+ request.error = "Review agent timed out before completing the comment request."
39
+ request.updatedAt = Date.now()
40
+ }
41
+ return request
42
+ }
43
+
44
+ export function completeCommentRequest(requestId: string): PendingCommentRequest | undefined {
45
+ const request = getCommentRequest(requestId)
46
+ if (!request || request.status !== "pending") return request
47
+ request.status = "completed"
48
+ request.updatedAt = Date.now()
49
+ return request
50
+ }
51
+
52
+ export function failCommentRequest(requestId: string, error: string): PendingCommentRequest | undefined {
53
+ const request = getCommentRequest(requestId)
54
+ if (!request || request.status !== "pending") return request
55
+ request.status = "failed"
56
+ request.error = error
57
+ request.updatedAt = Date.now()
58
+ return request
59
+ }
60
+
61
+ export function cleanupCommentRequests(now = Date.now()): void {
62
+ for (const [requestId, request] of requests) {
63
+ if (request.status === "pending" && now - request.createdAt > REQUEST_TTL_MS) {
64
+ request.status = "expired"
65
+ request.error = "Review agent timed out before completing the comment request."
66
+ request.updatedAt = now
67
+ continue
68
+ }
69
+ if (request.status !== "pending" && now - request.updatedAt > REQUEST_TTL_MS) {
70
+ requests.delete(requestId)
71
+ }
72
+ }
73
+ }
74
+
75
+ export function clearCommentRequestsForTests(): void {
76
+ requests.clear()
77
+ }
@@ -8,6 +8,7 @@ import { ensureEditableDeckState } from "../edit/deck-state"
8
8
  import { openUrl } from "../edit/open"
9
9
  import { resolveEditableDeck, type EditableDeck } from "../edit/resolve-deck"
10
10
  import { buildPrompt } from "../prompt-builder"
11
+ import type { ReviewPromptBridge } from "./prompt-bridge"
11
12
  import { startRefineServer, type RefineMode } from "./server"
12
13
 
13
14
  export interface OpenRefineDeckResult {
@@ -27,12 +28,13 @@ export interface EnsureRefineDeckOpenResult extends OpenRefineDeckResult {
27
28
  }
28
29
 
29
30
  export interface OpenRefineDeckOptions {
30
- client: any
31
- sessionID: string
31
+ client?: any
32
+ sessionID?: string
32
33
  workspaceRoot: string
33
34
  mode?: RefineMode
34
35
  openBrowser?: boolean
35
36
  openUrl?: (url: string) => void
37
+ promptBridge?: ReviewPromptBridge
36
38
  }
37
39
 
38
40
  export function openRefineDeck(target: string, options: OpenRefineDeckOptions): OpenRefineDeckResult {
@@ -70,6 +72,7 @@ function openRefineDeckInternal(
70
72
  workspaceRoot: options.workspaceRoot,
71
73
  deck,
72
74
  mode,
75
+ promptBridge: options.promptBridge,
73
76
  })
74
77
  const url = `${refineServer.baseUrl}/refine?token=${encodeURIComponent(session.token)}`
75
78
  const shouldOpen = options.openBrowser !== false && !(behavior.skipLiveSession && session.live)