@yangzhe1991/dsh-code-review 0.2.2 → 0.3.0
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/README.md +11 -1
- package/README.zh.md +13 -1
- package/lib/client.js +252 -14
- package/lib/client.js.map +3 -3
- package/lib/index.js +133 -6
- package/lib/index.js.map +2 -2
- package/lib/types/index.d.ts +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
|
|
10
10
|
**dsh-code-review** is a browser plugin for the [DSH (DeepSeek Harness)](https://github.com/deepseek-ai/deepseek-harness) web UI that turns **git diff output** in a conversation into a one-click **code review page in its own browser tab** — a two-column, human-friendly view (old file on the left, new file on the right, both with **line numbers**), free from the chat window's width/height limits.
|
|
11
11
|
|
|
12
|
+
Since **0.3.0** it can also delegate the review itself: a one-click **AI Review** button spawns a **clean-context subagent** that independently reviews the change and **discusses with the main agent until they reach consensus** — a second pair of eyes that never saw the conversation that produced the code.
|
|
13
|
+
|
|
12
14
|
The conversation itself stays untouched: when a git diff shows up, a small **"open in new tab"** button appears on the block (a markdown diff code block, or the bash tool row that produced the diff — even while collapsed). One click opens the review page.
|
|
13
15
|
|
|
14
16
|
When the conversation mentions a **local `.diff` / `.patch` file path**, the path automatically becomes a **view link button** — a path inside backticks gets an inline pill right after it, a bare-text path gets a right-aligned button row under the message. One click opens that file's code review page (the browser cannot read local files, so the plugin host half reads them — absolute paths only, `.diff`/`.patch` suffix only, ≤ 5 MB).
|
|
15
17
|
|
|
16
18
|
## Compatibility
|
|
17
19
|
|
|
18
|
-
- **dsh ≥ 0.1.2-alpha.4** — fully supported since **0.1.5**. The web UI restructure (chat snapshot moved from `useSession((s) => s.chat)` to the session-standard `useChat` hook; markdown code-block DOM wrapped in a `bannerWrap` layer) is handled on both detection paths: markdown diff blocks and collapsed bash tool rows. Verified against **dsh 0.1.3-alpha.2** since **0.2.1**, and against **dsh 0.2.0-rc.1** since **0.2.2
|
|
20
|
+
- **dsh ≥ 0.1.2-alpha.4** — fully supported since **0.1.5**. The web UI restructure (chat snapshot moved from `useSession((s) => s.chat)` to the session-standard `useChat` hook; markdown code-block DOM wrapped in a `bannerWrap` layer) is handled on both detection paths: markdown diff blocks and collapsed bash tool rows. Verified against **dsh 0.1.3-alpha.2** since **0.2.1**, and against **dsh 0.2.0-rc.1** since **0.2.2**, and against **dsh 0.2.1-rc.2** since **0.3.0** (AI review staging route + composer auto-submit verified there).
|
|
21
|
+
- **AI review tool requirements** — the instruction asks the main agent for `spawn_teammate(context: "fresh")` (Agent Teams profiles) or the `subagent` tool plus `send_message` (plain delegation setups). On hosts without any delegation tool the agent is told to say so and review with fresh eyes itself. Auto-submit uses the composer `submit` action (dsh 0.2.x); on older hosts the instruction is filled into the draft and you press Enter yourself.
|
|
19
22
|
- **Why 0.2.2 exists** — dsh 0.2.0-rc.1 checks every declared `@deepseek-ai/dsh-*` peer range against the *runtime version* and skips any plugin whose ranges do not match (`^0.1.3-alpha.2` excludes `0.2.0-rc.1`). No plugin code changed: **0.2.2** only widens those ranges to `^0.1.3-alpha.2 || ^0.2.0-rc.1`. On 0.2.0-rc.1 an already-installed **0.2.1** can also be admitted without upgrading, by granting the exact-version exemption (`dsh plugin --profile <profile> allow-version @yangzhe1991/dsh-code-review@0.2.1 --dsh-version 0.2.0-rc.1 --accept-risk`).
|
|
20
23
|
- **dsh 0.1.0-rc.x** — still supported via the legacy snapshot path.
|
|
24
|
+
- **Fixed in 0.3.0** — on the standalone review page, the "copy raw diff" button's success feedback referenced a script-scoped variable and threw a silent `ReferenceError` (the copy itself still worked); the texts are now defined globally before use.
|
|
21
25
|
|
|
22
26
|
## Features
|
|
23
27
|
|
|
@@ -31,6 +35,7 @@ When the conversation mentions a **local `.diff` / `.patch` file path**, the pat
|
|
|
31
35
|
- 🎨 **Theme-matched** — the page inherits the DSH theme colors at open time, so light/dark mode stays consistent.
|
|
32
36
|
- 📜 **Full-page scrolling** — no height caps; thousands of lines scroll naturally, and long lines wrap.
|
|
33
37
|
- 💬 **Inline comments** — hover any line number and click to add a comment right on that line (like GitHub reviews). Comments accumulate with a per-line count badge; when done, submit them all at once — as **Looks Good To Me ✓** or **plain comments** — and they are written into the DSH composer as `file:line — comment` lines, ready to send to the agent.
|
|
38
|
+
- 🤖 **One-click AI review via a clean-context subagent (since 0.3.0)** — every place that shows the "open in new tab" button (markdown diff blocks, bash tool rows, terminal cards, and the standalone review page) also gets an **AI Review** button. One click: the diff is staged to a temp file by the host half, an instruction is written into the composer and sent automatically, and the main agent then (1) spawns a subagent with a **fresh, clean context** — `spawn_teammate` with `context: "fresh"` or the `subagent` tool, never fork — (2) has it review the diff as an independent reviewer (it may read sources and run tests), and (3) **discusses every disputed finding via `send_message` until both sides agree on each conclusion**, finally reporting the jointly confirmed findings (P0/P1/P2), the suspicions ruled out, and the verdict (LGTM / needs changes). Fixes are only applied after you confirm.
|
|
34
39
|
- 🔗 **One-click local diff paths** — absolute `…/xxx.diff` / `…/xxx.patch` paths mentioned in the conversation become link buttons: backtick-wrapped paths get an inline pill right after them, bare-text paths get a right-aligned button row under the message, and paths listed in bash tool output join the tool row's button row. Clicking opens that file's code review page; on read failure (missing / not a diff / over 5 MB) the button briefly turns red.
|
|
35
40
|
- 🌐 **Bilingual UI** — follows the DSH interface language (official locale service): a Chinese UI gets Chinese buttons and review page, everything else gets English.
|
|
36
41
|
|
|
@@ -50,11 +55,16 @@ When the conversation mentions a **local `.diff` / `.patch` file path**, the pat
|
|
|
50
55
|
- backtick-wrapped paths (e.g. `` `/Users/me/proj/patch.diff` ``) get an **inline pill button** right after the path;
|
|
51
56
|
- bare-text paths get a **right-aligned button row under the message** (one pill per path); paths inside reasoning blocks and code blocks are not captured (collapsed by default / have their own button mechanism);
|
|
52
57
|
- paths listed in bash tool output join the **tool row's button row**.
|
|
58
|
+
4. **AI Review (since 0.3.0)** — the 🤖 button rides along on all of the above surfaces plus the standalone review page's top bar.
|
|
53
59
|
|
|
54
60
|
Streaming output is handled gracefully: the button only appears once the content has been stable for ~1 second, so half-streamed diffs never produce a broken page.
|
|
55
61
|
|
|
56
62
|
When a path button is clicked, the plugin's host half (a route registered on `webServer`) reads the local file (absolute paths only, `~` expansion, `.diff`/`.patch` suffix, ≤ 5 MB, no CORS), and the browser half renders it into the standalone review page; on read failure the button briefly turns red. **The route registration requires a `dsh web` restart** (the host half registers it at startup); browser-half changes only need a hard refresh.
|
|
57
63
|
|
|
64
|
+
### How AI review works
|
|
65
|
+
|
|
66
|
+
The browser has no official API to create subagents — the delegation tools (`spawn_teammate` / `subagent`) live with the main agent. So the button drives the main agent through the composer: the diff text is `POST`ed to a second host route (`/dsh-code-review/ai-review`), which stages it as a **temp file** (server-named, 0600, ≤ 5 MB, validated to look like a git diff, auto-pruned after 24 h, capped at 1000 files) — keeping the instruction short and the big diff **out of the main conversation's context**. The browser half then writes a structured instruction into the composer and **auto-submits** it (on older hosts without the submit action it only fills the draft, and the button honestly reports "Draft filled"). The instruction requires the main agent to: spawn a subagent with a **fresh context** (never fork), hand it the staged file path, have it verify suspicions against real sources/tests, **dispute findings via `send_message` until consensus on every item**, and summarize the agreed conclusions — waiting for your confirmation before fixing anything. If the staging route is unavailable (host half not restarted after upgrade) and the diff is small, the diff is inlined into the instruction as a fallback; otherwise the button shows a brief error. The write route is same-origin guarded (requires `Content-Type: application/json` and rejects `Sec-Fetch-Site: cross-site`; a missing header passes for curl compatibility), and the standalone page's AI button auto-recovers with a failure hint after 10 s without an ack.
|
|
67
|
+
|
|
58
68
|
## Install (30 seconds)
|
|
59
69
|
|
|
60
70
|
```sh
|
package/README.zh.md
CHANGED
|
@@ -9,15 +9,19 @@
|
|
|
9
9
|
|
|
10
10
|
**dsh-code-review** 是 [DSH (DeepSeek Harness)](https://github.com/deepseek-ai/deepseek-harness) Web UI 的浏览器插件:把对话里的 **git diff 输出**变成一键打开的**独立标签页 Code Review 页面** —— 双列、对人类友好的视图(左侧旧文件、右侧新文件,两侧都带**行号**),完全不受聊天窗口宽度/高度限制。
|
|
11
11
|
|
|
12
|
+
自 **0.3.0** 起还能把审查这件事本身也委派出去:一键 **AI 审查**按钮创建一个**干净上下文的 sub agent**,独立审查这段变更,并与主 agent **往返讨论直到达成一致** —— 一双从未见过产出这段代码的对话历史的「第二双眼睛」。
|
|
13
|
+
|
|
12
14
|
聊天窗口本身保持原样:只要出现 git diff,对应块(markdown diff 代码块、或产生 diff 的 bash 工具行——折叠状态也显示)上就自动出现一个**「在新标签页打开」**小按钮,点一下即打开审查页。
|
|
13
15
|
|
|
14
16
|
对话里提到**本地 .diff / .patch 文件路径**时,路径会自动变成**查看链接按钮**——反引号里的路径原地变成小胶囊按钮,纯文本路径在消息下方补一行按钮;点击直接打开该文件的 Code Review 审查页(浏览器读不了本地文件,由插件宿主侧代读,只读绝对路径、仅限 .diff/.patch 后缀、≤5MB)。
|
|
15
17
|
|
|
16
18
|
## 兼容性
|
|
17
19
|
|
|
18
|
-
- **dsh ≥ 0.1.2-alpha.4** — 自 **0.1.5** 起完整支持。Web UI 重构(聊天快照从 `useSession((s) => s.chat)` 迁移到会话标准的 `useChat` hook;markdown 代码块 DOM 包了一层 `bannerWrap` 层)在两条检测路径上均已适配:markdown diff 块与折叠的 bash 工具行。自 **0.2.1** 起在 **dsh 0.1.3-alpha.2** 上验证通过,自 **0.2.2** 起在 **dsh 0.2.0-rc.1**
|
|
20
|
+
- **dsh ≥ 0.1.2-alpha.4** — 自 **0.1.5** 起完整支持。Web UI 重构(聊天快照从 `useSession((s) => s.chat)` 迁移到会话标准的 `useChat` hook;markdown 代码块 DOM 包了一层 `bannerWrap` 层)在两条检测路径上均已适配:markdown diff 块与折叠的 bash 工具行。自 **0.2.1** 起在 **dsh 0.1.3-alpha.2** 上验证通过,自 **0.2.2** 起在 **dsh 0.2.0-rc.1** 上验证通过,自 **0.3.0** 起在 **dsh 0.2.1-rc.2** 上验证通过(含 AI 审查暂存路由与 composer 自动发送)。
|
|
21
|
+
- **AI 审查对工具的要求** —— 指令要求主 agent 用 `spawn_teammate(context: "fresh")`(Agent Teams profile)或 `subagent` 工具加 `send_message`(普通委派装配)。宿主上没有任何委派工具时,agent 会被要求明确说明并自行以干净视角审查。自动发送依赖 composer 的 `submit` action(dsh 0.2.x);旧版宿主上指令只填入草稿,由你自己按回车发送。
|
|
19
22
|
- **0.2.2 为什么存在** —— dsh 0.2.0-rc.1 会把每个声明的 `@deepseek-ai/dsh-*` peer 范围与**运行时版本**比对,不匹配就跳过整个插件(`^0.1.3-alpha.2` 不含 `0.2.0-rc.1`)。本版**没有代码改动**,只是把这些范围放宽为 `^0.1.3-alpha.2 || ^0.2.0-rc.1`。在 0.2.0-rc.1 上,已装好的 **0.2.1** 也可以不升级直接放行:授予精确版本豁免即可(`dsh plugin --profile <profile> allow-version @yangzhe1991/dsh-code-review@0.2.1 --dsh-version 0.2.0-rc.1 --accept-risk`)。
|
|
20
23
|
- **dsh 0.1.0-rc.x** — 仍通过旧版快照路径支持。
|
|
24
|
+
- **0.3.0 顺带修复** —— 独立审查页「复制原始 diff」的成功反馈引用了脚本作用域变量,静默抛 `ReferenceError`(复制本身仍成功);现改为全局定义在前再使用。
|
|
21
25
|
|
|
22
26
|
## 功能
|
|
23
27
|
|
|
@@ -31,6 +35,7 @@
|
|
|
31
35
|
- 🎨 **主题一致** —— 打开时继承 DSH 当前主题色,深浅色模式自动跟随。
|
|
32
36
|
- 📜 **整页滚动** —— 无高度限制,几千行自然滚动,超长行折行显示。
|
|
33
37
|
- 💬 **行内评论** —— 悬停任意行号点击即可在该行提评论(类似 GitHub review),行号旁显示评论数徽章;攒齐后一次性提交 —— 可选 **Looks Good To Me ✓** 或**纯评论**,提交后自动写入 DSH 对话框(`文件名:行号 — 评论` 逐条列出),直接发给 agent。
|
|
38
|
+
- 🤖 **一键 AI 审查(0.3.0 起)** —— 所有带「在新标签页打开」按钮的地方(markdown diff 块、bash 工具行、终端卡片、独立审查页)都多一个 **AI 审查**按钮。一键完成:diff 由宿主侧暂存为临时文件,指令自动写入对话框并发送,主 agent 随即 ① 创建**全新干净上下文**的 sub agent(`spawn_teammate` 传 `context: "fresh"`,或 `subagent` 工具;禁止 fork);② 让它以独立审查员身份审查 diff(可读源码、跑测试验证怀疑点);③ 对每条有分歧的发现用 `send_message` **往返讨论,直到双方对每一条结论达成一致**,最后汇报共同确认的问题清单(P0/P1/P2)、讨论后排除的怀疑点与最终结论(LGTM / 需修改);确认修改前不会自动动代码。
|
|
34
39
|
- 🔗 **本地路径一键查看** —— 对话里出现 `…/xxx.diff`/`…/xxx.patch` 绝对路径时自动变成链接按钮:反引号内的路径原地变成小胶囊按钮,纯文本路径在消息下方补一行右对齐按钮;bash 工具输出里列出的路径也会出现在工具行按钮排上。点击后打开该文件的 Code Review 页,读取失败(文件不存在/非 diff/超 5MB)时按钮短暂变红提示。
|
|
35
40
|
- 🌐 **中英双语** —— 跟随 DSH 界面语言(官方 locale 服务):中文界面显示中文按钮与审查页,其他语言一律英文。
|
|
36
41
|
|
|
@@ -50,11 +55,16 @@
|
|
|
50
55
|
- 反引号内的路径(如 `` `/Users/me/proj/patch.diff` ``)原地变成**内联胶囊按钮**,跟在路径后面;
|
|
51
56
|
- 纯文本路径在**消息下方补一行右对齐按钮**(多条路径逐个列出);思考块与代码块内的路径不捕获(前者默认折叠、后者已有自身机制);
|
|
52
57
|
- bash 工具输出里列出的路径加在**工具行按钮排**上。
|
|
58
|
+
4. **AI 审查(0.3.0 起)** —— 🤖 按钮随上述所有入口出现,独立审查页顶栏也有一个。
|
|
53
59
|
|
|
54
60
|
流式输出有专门处理:内容稳定约 1 秒后按钮才出现,半截 diff 不会产生残缺页面。
|
|
55
61
|
|
|
56
62
|
点击路径按钮时:插件宿主侧(`webServer` 注册的路由)读取本地文件内容(仅绝对路径、支持 `~` 展开、仅 `.diff`/`.patch` 后缀、≤5MB、不设 CORS),浏览器侧再把内容渲染成独立审查页;读取失败时按钮短暂变红提示。**读取需重启 `dsh web` 才生效**(node 半在宿主启动时注册路由),浏览器半改动硬刷新即可。
|
|
57
63
|
|
|
64
|
+
### AI 审查的工作方式
|
|
65
|
+
|
|
66
|
+
浏览器端没有创建 sub agent 的官方 API —— 委派工具(`spawn_teammate` / `subagent`)在主 agent 手里。所以按钮通过对话框驱动主 agent:diff 文本 `POST` 到宿主侧第二个路由(`/dsh-code-review/ai-review`),暂存为**临时文件**(服务端命名、0600 权限、≤5MB、内容必须形似 git diff、24 小时自动过期清理、目录内上限 1000 个文件)—— 指令保持简短,大 diff **不进主对话上下文**。随后浏览器半把结构化指令写入对话框并**自动发送**(旧版宿主没有自动发送 action 时只填草稿,按钮如实反馈「已填入草稿」)。指令要求主 agent:创建**全新上下文**的 sub agent(禁止 fork),把暂存文件路径交给它,让它对着真实源码/测试验证怀疑点,**用 `send_message` 对每条分歧往返讨论直到达成一致**,最后汇总双方确认的结论 —— 动手修改前先等你确认。若暂存路由不可用(升级后宿主未重启)且 diff 较小,则把 diff 内联进指令降级发送;过大则按钮短暂报错提示。写路由带同源守卫:要求 `Content-Type: application/json` 且拒绝 `Sec-Fetch-Site: cross-site`(头缺失放行,兼容 curl 调试);独立页 AI 按钮点击 10 秒无回执自动恢复并提示失败。
|
|
67
|
+
|
|
58
68
|
## 安装(30 秒)
|
|
59
69
|
|
|
60
70
|
```sh
|
|
@@ -75,6 +85,8 @@ dsh plugin --profile web add link:/path/to/@yangzhe1991/dsh-code-review
|
|
|
75
85
|
|
|
76
86
|
本地路径 chip 的分工:消息行(assistant/用户)由同一扫描器按行探测,反引号内的路径在 code 元素后原地插内联按钮,纯文本路径在行尾追加一排按钮;bash 工具结果里的路径走数据层(与 diff 按钮同一条数据链路)。文本节点只读不拆(对 React 管理的文本节点做拆分会破坏流式渲染),所以纯文本路径不做原地改造。点击后浏览器半先同步开占位窗(防弹窗拦截),再向宿主路由 `GET /dsh-code-review/diff?path=…` 拉取内容,成功即导航到独立审查页。
|
|
77
87
|
|
|
88
|
+
AI 审查按钮随「在新标签页打开」一起注入(同一行容器,详见上节「AI 审查的工作方式」);独立审查页的 AI 按钮通过 `postMessage` 把 diff 原文回传给 DSH 页面(blob 页没有宿主路由与 composer 的访问权),由 DSH 页面走同一个发起函数,回执确认后按钮显示「已发起 ✓」。
|
|
89
|
+
|
|
78
90
|
点击按钮时,**在用户手势内同步**构建自包含 HTML 页面(内联样式 + 继承的主题变量 + 双列内容,全部 HTML 转义),通过 **Blob URL** 打开 —— 不依赖服务器任何路由,同步调用的 `window.open` 也不会被弹窗拦截。解析器是纯模块(`src/client/diff-parse.ts`,`test/parse.test.mjs` 有单测),支持:多文件 diff、带行号跟踪的 hunk、删除/新增配对成变更行(数量不等时多出的行退化为单侧行)、`` 标记、新增/删除/重命名/二进制文件、无 `diff --git` 头的裸 patch、`git show` 的提交说明前缀。
|
|
79
91
|
|
|
80
92
|
## 开发
|
package/lib/client.js
CHANGED
|
@@ -793,6 +793,12 @@ var CR_TEXTS = {
|
|
|
793
793
|
collapse: "\u6536\u8D77",
|
|
794
794
|
expandTitle: "\u5C55\u5F00\u539F\u59CB diff \u6587\u672C",
|
|
795
795
|
collapseTitle: "\u6536\u8D77\u539F\u59CB diff \u6587\u672C",
|
|
796
|
+
aiReview: "\u{1F916} AI \u5BA1\u67E5",
|
|
797
|
+
aiReviewTitle: "\u4E00\u952E\u521B\u5EFA\u5E72\u51C0\u4E0A\u4E0B\u6587\u7684 sub agent \u72EC\u7ACB\u5BA1\u67E5\u8FD9\u6BB5\u53D8\u66F4,\u5E76\u4E0E\u5B83\u8BA8\u8BBA\u5230\u8FBE\u6210\u4E00\u81F4(\u6307\u4EE4\u4F1A\u81EA\u52A8\u53D1\u9001\u5230\u5F53\u524D\u4F1A\u8BDD)",
|
|
798
|
+
aiSent: "\u5DF2\u53D1\u8D77 \u2713",
|
|
799
|
+
aiDrafted: "\u5DF2\u586B\u5165\u8349\u7A3F",
|
|
800
|
+
aiFailed: "\u53D1\u8D77\u5931\u8D25",
|
|
801
|
+
aiFailedTitle: "diff \u6682\u5B58\u8DEF\u7531\u4E0D\u53EF\u7528\u4E14\u5185\u5BB9\u8FC7\u5927\u65E0\u6CD5\u5185\u8054,\u8BF7\u91CD\u542F dsh web \u540E\u91CD\u8BD5",
|
|
796
802
|
viewPath: "\u67E5\u770B",
|
|
797
803
|
viewPathTitle: (path) => `\u5728\u72EC\u7ACB\u6807\u7B7E\u9875\u67E5\u770B\u672C\u5730 diff \u6587\u4EF6:${path}`,
|
|
798
804
|
pathReadFailed: "\u8BFB\u53D6\u5931\u8D25",
|
|
@@ -823,6 +829,12 @@ var CR_TEXTS = {
|
|
|
823
829
|
collapse: "Collapse",
|
|
824
830
|
expandTitle: "Show raw diff text",
|
|
825
831
|
collapseTitle: "Hide raw diff text",
|
|
832
|
+
aiReview: "\u{1F916} AI Review",
|
|
833
|
+
aiReviewTitle: "One-click spawn a clean-context subagent to independently review this change and discuss until consensus (the instruction is sent to the current session automatically)",
|
|
834
|
+
aiSent: "Sent \u2713",
|
|
835
|
+
aiDrafted: "Draft filled",
|
|
836
|
+
aiFailed: "Failed",
|
|
837
|
+
aiFailedTitle: "The diff staging route is unavailable and the content is too large to inline; restart dsh web and retry",
|
|
826
838
|
viewPath: "View",
|
|
827
839
|
viewPathTitle: (path) => `Open the local diff file in a separate tab: ${path}`,
|
|
828
840
|
pathReadFailed: "Failed to read",
|
|
@@ -869,9 +881,45 @@ function stringTexts(texts) {
|
|
|
869
881
|
cancel: texts.cancel,
|
|
870
882
|
noNewlineTitle: texts.noNewlineTitle,
|
|
871
883
|
openerClosed: texts.openerClosed,
|
|
872
|
-
noSession: texts.noSession
|
|
884
|
+
noSession: texts.noSession,
|
|
885
|
+
aiReview: texts.aiReview,
|
|
886
|
+
aiSent: texts.aiSent,
|
|
887
|
+
aiDrafted: texts.aiDrafted,
|
|
888
|
+
aiFailed: texts.aiFailed
|
|
873
889
|
};
|
|
874
890
|
}
|
|
891
|
+
function buildReviewPrompt(ref, lang = "zh") {
|
|
892
|
+
if (lang === "zh") {
|
|
893
|
+
const body2 = ref.kind === "file" ? `\u53D8\u66F4\u7684 git diff \u5DF2\u4FDD\u5B58\u5728\u672C\u5730\u6587\u4EF6:${ref.path}(\u7EDD\u5BF9\u8DEF\u5F84,\u53EF\u76F4\u63A5\u8BFB\u53D6)\u3002` : `\u53D8\u66F4\u7684 git diff \u5982\u4E0B(\u5DF2\u5185\u8054,\u8BF7\u539F\u6837\u8F6C\u4EA4\u7ED9 sub agent):
|
|
894
|
+
|
|
895
|
+
\`\`\`diff
|
|
896
|
+
${ref.text}
|
|
897
|
+
\`\`\``;
|
|
898
|
+
return `\u3010AI \u4EE3\u7801\u5BA1\u67E5\u3011\u8BF7\u521B\u5EFA\u4E00\u4E2A\u5168\u65B0\u4E0A\u4E0B\u6587(clean context)\u7684 sub agent,\u4EE5\u72EC\u7ACB\u5BA1\u67E5\u5458\u8EAB\u4EFD\u5BA1\u67E5\u8FD9\u6B21\u4EE3\u7801\u53D8\u66F4 \u2014\u2014 \u4E0D\u8981\u4F60\u81EA\u5DF1\u76F4\u63A5\u5BA1\u67E5,\u4E5F\u4E0D\u8981\u7528\u7EE7\u627F\u4E0A\u4E0B\u6587\u7684\u65B9\u5F0F\u3002
|
|
899
|
+
|
|
900
|
+
${body2}
|
|
901
|
+
|
|
902
|
+
\u6267\u884C\u6B65\u9AA4:
|
|
903
|
+
1. \u521B\u5EFA sub agent:\u4F18\u5148\u7528 spawn_teammate \u4E14 context \u4F20 "fresh";\u6CA1\u6709\u8BE5\u5DE5\u5177\u5C31\u7528 subagent \u5DE5\u5177;\u7981\u6B62\u4F7F\u7528 subagent_fork / fork(\u4F1A\u7EE7\u627F\u5F53\u524D\u5BF9\u8BDD\u4E0A\u4E0B\u6587,\u4E0D\u5E72\u51C0)\u3002\u5982\u679C\u4E24\u8005\u90FD\u4E0D\u53EF\u7528,\u660E\u786E\u544A\u8BC9\u6211\u4F60\u7F3A\u5C11\u59D4\u6D3E\u80FD\u529B,\u518D\u81EA\u884C\u4EE5\u5E72\u51C0\u89C6\u89D2\u6309\u4E0B\u9762\u7684\u6807\u51C6\u5BA1\u67E5\u3002
|
|
904
|
+
2. \u7ED9 sub agent \u7684\u4EFB\u52A1(\u5B8C\u6574\u8F6C\u8FBE):\u4F60\u662F\u72EC\u7ACB\u4EE3\u7801\u5BA1\u67E5\u5458,\u5BA1\u67E5\u8FD9\u4EFD git diff \u2014\u2014 \u91CD\u70B9\u68C0\u67E5\u6B63\u786E\u6027\u3001\u8FB9\u754C\u6761\u4EF6\u3001\u5E76\u53D1\u4E0E\u5B89\u5168\u3001\u9519\u8BEF\u5904\u7406\u3001\u4E0E\u4EE3\u7801\u5E93\u65E2\u6709\u6A21\u5F0F/\u98CE\u683C\u7684\u4E00\u81F4\u6027;\u53EF\u4EE5\u8BFB\u53D6\u5DE5\u4F5C\u533A\u76F8\u5173\u6E90\u7801\u3001\u5FC5\u8981\u65F6\u8FD0\u884C\u6784\u5EFA\u6216\u6D4B\u8BD5\u9A8C\u8BC1\u6000\u7591\u70B9;\u8F93\u51FA\u95EE\u9898\u6E05\u5355,\u6BCF\u6761\u5305\u542B\u300C\u6587\u4EF6:\u884C\u53F7\u3001\u4E25\u91CD\u7EA7\u522B(P0/P1/P2)\u3001\u95EE\u9898\u63CF\u8FF0\u4E0E\u7406\u7531\u300D,\u786E\u8BA4\u6CA1\u6709\u95EE\u9898\u5C31\u660E\u786E\u8F93\u51FA LGTM\u3002
|
|
905
|
+
3. \u62FF\u5230\u5BA1\u67E5\u7ED3\u679C\u540E,\u5BF9\u6709\u5206\u6B67\u6216\u9700\u8981\u6F84\u6E05\u7684\u6761\u76EE,\u7528 send_message \u4E0E sub agent \u5F80\u8FD4\u8BA8\u8BBA(\u5B83\u8BEF\u89E3\u7684\u8BA9\u5B83\u91CD\u65B0\u6838\u5B9E,\u786E\u5B9E\u5B58\u5728\u7684\u95EE\u9898\u4F60\u8981\u627F\u8BA4),\u76F4\u5230\u53CC\u65B9\u5BF9\u6BCF\u4E00\u6761\u7ED3\u8BBA\u90FD\u8FBE\u6210\u4E00\u81F4\u3002
|
|
906
|
+
4. \u6700\u540E\u7ED9\u6211\u6C47\u603B:\u53CC\u65B9\u786E\u8BA4\u7684\u95EE\u9898\u6E05\u5355(\u6309 P0/P1/P2 \u6392\u5E8F)\u3001\u8BA8\u8BBA\u540E\u6392\u9664\u7684\u6000\u7591\u70B9\u3001\u6700\u7EC8\u7ED3\u8BBA(LGTM \u6216\u9700\u4FEE\u6539)\u3002\u786E\u8BA4\u9700\u8981\u4FEE\u6539\u7684,\u7B49\u6211\u786E\u8BA4\u540E\u518D\u52A8\u624B\u3002`;
|
|
907
|
+
}
|
|
908
|
+
const body = ref.kind === "file" ? `The git diff of the change is saved at: ${ref.path} (absolute path, readable directly).` : `The git diff of the change is below (inlined; hand it to the subagent as-is):
|
|
909
|
+
|
|
910
|
+
\`\`\`diff
|
|
911
|
+
${ref.text}
|
|
912
|
+
\`\`\``;
|
|
913
|
+
return `[AI code review] Spawn a clean-context subagent to independently review this change as a separate reviewer \u2014 do not review it yourself, and do not use a context-inheriting method.
|
|
914
|
+
|
|
915
|
+
${body}
|
|
916
|
+
|
|
917
|
+
Steps:
|
|
918
|
+
1. Spawn the subagent: prefer spawn_teammate with context "fresh"; if that tool is unavailable use the subagent tool; never subagent_fork / fork (they inherit this conversation's context). If neither is available, tell me you lack delegation capability, then review it yourself with fresh eyes against the criteria below.
|
|
919
|
+
2. The subagent's task (relay in full): you are an independent code reviewer examining this git diff \u2014 focus on correctness, edge cases, concurrency and safety, error handling, and consistency with the codebase's existing patterns and style; you may read related sources in the workspace and run builds or tests to verify suspicions; output a findings list where each item has "file:line, severity (P0/P1/P2), description and reasoning", or an explicit LGTM if nothing is wrong.
|
|
920
|
+
3. After receiving the findings, use send_message to go back and forth with the subagent on every disputed or unclear item (have it re-verify what it misread; acknowledge what is genuinely wrong) until you both agree on every single conclusion.
|
|
921
|
+
4. Finally summarize for me: the jointly confirmed findings (ordered P0/P1/P2), suspicions ruled out during the discussion, and the final verdict (LGTM or needs changes). Wait for my confirmation before fixing anything.`;
|
|
922
|
+
}
|
|
875
923
|
function buildCommentReport(comments, kind, lang = "zh") {
|
|
876
924
|
const texts = CR_TEXTS[lang];
|
|
877
925
|
if (kind === "lgtm" && comments.length === 0) return texts.reportLgtmBare;
|
|
@@ -964,6 +1012,7 @@ ${STANDALONE_CSS}
|
|
|
964
1012
|
<header class="topbar">
|
|
965
1013
|
<span class="stats">${statsText}</span>
|
|
966
1014
|
<span class="topbar-actions">
|
|
1015
|
+
<button class="btn" id="ai-review" type="button">${texts.aiReview}</button>
|
|
967
1016
|
<button class="btn" id="submit-comments" type="button">${texts.submitComments}</button>
|
|
968
1017
|
<button class="btn" id="copy-raw" type="button">${texts.copyRaw}</button>
|
|
969
1018
|
</span>
|
|
@@ -981,18 +1030,78 @@ ${STANDALONE_CSS}
|
|
|
981
1030
|
</details>
|
|
982
1031
|
</div>
|
|
983
1032
|
<script>
|
|
1033
|
+
/* \u5168\u5C40\u6587\u672C\u8868 + \u539F\u59CB diff \u5168\u5C40\u6302\u8F7D:\u590D\u5236\u4E0E AI \u5BA1\u67E5\u4E24\u4E2A\u811A\u672C\u5171\u7528,
|
|
1034
|
+
* \u53EA\u5185\u5D4C\u4E00\u4EFD\u3002(0.2.2 \u53CA\u4E4B\u524D T \u5B9A\u4E49\u5728\u4E0B\u65B9 IIFE \u91CC,copy-raw \u56DE\u8C03
|
|
1035
|
+
* \u5F15\u7528\u5B83\u4F1A\u629B ReferenceError \u2014\u2014 \u590D\u5236\u6210\u529F\u540E\u6309\u94AE\u6587\u6848\u4E0D\u53D8\u5316,\u672C\u7248\u4FEE\u590D\u3002) */
|
|
1036
|
+
var T0 = ${JSON.stringify(stringTexts(texts))}
|
|
1037
|
+
window.__dshCrRaw = ${rawJson}
|
|
984
1038
|
document.getElementById('copy-raw').addEventListener('click', function (event) {
|
|
985
|
-
var raw =
|
|
1039
|
+
var raw = window.__dshCrRaw
|
|
986
1040
|
if (navigator.clipboard && navigator.clipboard.writeText) {
|
|
987
1041
|
navigator.clipboard.writeText(raw).then(function () {
|
|
988
1042
|
var btn = event.currentTarget
|
|
989
|
-
btn.textContent =
|
|
990
|
-
setTimeout(function () { btn.textContent =
|
|
1043
|
+
btn.textContent = T0.copied
|
|
1044
|
+
setTimeout(function () { btn.textContent = T0.copyRaw }, 1200)
|
|
991
1045
|
})
|
|
992
1046
|
}
|
|
993
1047
|
})
|
|
994
1048
|
<\/script>
|
|
995
1049
|
<script>
|
|
1050
|
+
/* AI \u5BA1\u67E5:\u628A diff \u539F\u6587 postMessage \u56DE DSH \u9875\u9762,\u7531\u540E\u8005\u6682\u5B58\u6587\u4EF6\u3001
|
|
1051
|
+
* \u7EC4\u88C5\u6307\u4EE4\u5E76\u5199\u5165\u5BF9\u8BDD\u6846\u53D1\u9001(\u72EC\u7ACB\u9875\u662F blob \u6E90,\u6CA1\u6709\u5BBF\u4E3B\u8DEF\u7531\u4E0E
|
|
1052
|
+
* composer \u7684\u8BBF\u95EE\u6743)\u3002\u56DE\u6267\u786E\u8BA4\u540E\u518D\u6062\u590D\u6309\u94AE\u3002
|
|
1053
|
+
* \u515C\u5E95:DSH \u9875\u53EF\u80FD\u5DF2\u88AB\u786C\u5237\u65B0(\u5B83\u7684 reviewWindows \u6E05\u7A7A\u540E\u4F1A\u628A\u672C\u7A97\u7684
|
|
1054
|
+
* \u6D88\u606F\u5F53\u964C\u751F\u6765\u6E90\u5FFD\u7565 \u2192 \u6C38\u65E0\u56DE\u6267),\u70B9\u51FB 10 \u79D2\u65E0\u56DE\u6267\u5373\u6062\u590D\u6309\u94AE\u5E76
|
|
1055
|
+
* \u63D0\u793A\u5931\u8D25;\u6B64\u540E\u8FDF\u5230\u7684\u56DE\u6267\u5E42\u7B49\u65E0\u5BB3(\u6309\u94AE\u5DF2\u6062\u590D,\u6587\u6848\u518D\u7F6E\u4E00\u6B21\u800C\u5DF2)\u3002 */
|
|
1056
|
+
(function () {
|
|
1057
|
+
var btn = document.getElementById('ai-review')
|
|
1058
|
+
var pendingTimer = null
|
|
1059
|
+
function clearPending() {
|
|
1060
|
+
if (pendingTimer !== null) {
|
|
1061
|
+
clearTimeout(pendingTimer)
|
|
1062
|
+
pendingTimer = null
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
btn.addEventListener('click', function () {
|
|
1066
|
+
var target = window.opener
|
|
1067
|
+
if (!target) {
|
|
1068
|
+
alert(T0.openerClosed)
|
|
1069
|
+
return
|
|
1070
|
+
}
|
|
1071
|
+
btn.disabled = true
|
|
1072
|
+
clearPending()
|
|
1073
|
+
pendingTimer = setTimeout(function () {
|
|
1074
|
+
pendingTimer = null
|
|
1075
|
+
if (!btn.disabled) return
|
|
1076
|
+
btn.disabled = false
|
|
1077
|
+
btn.textContent = T0.aiFailed
|
|
1078
|
+
setTimeout(function () {
|
|
1079
|
+
if (!btn.disabled) btn.textContent = T0.aiReview
|
|
1080
|
+
}, 3000)
|
|
1081
|
+
}, 10000)
|
|
1082
|
+
target.postMessage({ type: 'dsh-code-review-ai', text: window.__dshCrRaw }, '*')
|
|
1083
|
+
})
|
|
1084
|
+
window.addEventListener('message', function (event) {
|
|
1085
|
+
if (event.source !== window.opener) return
|
|
1086
|
+
var data = event.data
|
|
1087
|
+
if (!data || data.type !== 'dsh-code-review-ai-ack') return
|
|
1088
|
+
clearPending()
|
|
1089
|
+
if (data.ok === true) {
|
|
1090
|
+
/* drafted=true:DSH \u9875\u662F\u65E7\u5BBF\u4E3B(\u65E0 submit),\u6307\u4EE4\u53EA\u8FDB\u4E86\u8349\u7A3F,
|
|
1091
|
+
* \u9700\u8981\u7528\u6237\u56DE DSH \u624B\u52A8\u56DE\u8F66\u53D1\u9001 \u2014\u2014 \u6587\u6848\u5982\u5B9E\u533A\u5206\u3002 */
|
|
1092
|
+
btn.textContent = data.drafted === true ? T0.aiDrafted : T0.aiSent
|
|
1093
|
+
setTimeout(function () {
|
|
1094
|
+
btn.textContent = T0.aiReview
|
|
1095
|
+
btn.disabled = false
|
|
1096
|
+
}, 2000)
|
|
1097
|
+
} else {
|
|
1098
|
+
btn.disabled = false
|
|
1099
|
+
alert(T0.aiFailed)
|
|
1100
|
+
}
|
|
1101
|
+
})
|
|
1102
|
+
})()
|
|
1103
|
+
<\/script>
|
|
1104
|
+
<script>
|
|
996
1105
|
/* \u884C\u5185\u8BC4\u8BBA:\u70B9\u51FB\u884C\u53F7\u683C\u63D0\u8BC4\u8BBA,\u6512\u9F50\u540E\u63D0\u4EA4\u56DE DSH \u5BF9\u8BDD\u6846(postMessage)\u3002 */
|
|
997
1106
|
(function () {
|
|
998
1107
|
var T = ${JSON.stringify(stringTexts(texts))}
|
|
@@ -1178,6 +1287,30 @@ var BUTTON_CSS = `
|
|
|
1178
1287
|
color: var(--dsw-alias-label-primary);
|
|
1179
1288
|
border-color: var(--dsw-alias-border-l3);
|
|
1180
1289
|
}
|
|
1290
|
+
/* AI \u5BA1\u67E5\u6309\u94AE:\u54C1\u724C\u84DD\u63CF\u8FB9\u7A7A\u5FC3,\u4E0E\u4E3B\u6309\u94AE(\u5B9E\u5FC3)\u533A\u5206\u5C42\u7EA7\u3001\u6BD4\u6B21\u6309\u94AE\u9192\u76EE */
|
|
1291
|
+
.dsh-cr-aibtn {
|
|
1292
|
+
flex: none;
|
|
1293
|
+
font-size: 12px;
|
|
1294
|
+
line-height: 18px;
|
|
1295
|
+
color: var(--dsw-alias-state-business-primary, #3b6fe0);
|
|
1296
|
+
background: transparent;
|
|
1297
|
+
border: 1px solid var(--dsw-alias-state-business-primary, #3b6fe0);
|
|
1298
|
+
border-radius: 8px;
|
|
1299
|
+
padding: 1px 10px;
|
|
1300
|
+
cursor: pointer;
|
|
1301
|
+
white-space: nowrap;
|
|
1302
|
+
}
|
|
1303
|
+
.dsh-cr-aibtn:hover {
|
|
1304
|
+
background: color-mix(in srgb, var(--dsw-alias-state-business-primary, #3b6fe0) 10%, transparent);
|
|
1305
|
+
}
|
|
1306
|
+
.dsh-cr-aibtn:disabled {
|
|
1307
|
+
opacity: 0.6;
|
|
1308
|
+
cursor: default;
|
|
1309
|
+
}
|
|
1310
|
+
.dsh-cr-aibtn[data-error='true'] {
|
|
1311
|
+
color: var(--dsw-alias-state-error-primary, #d92d20);
|
|
1312
|
+
border-color: var(--dsw-alias-state-error-primary, #d92d20);
|
|
1313
|
+
}
|
|
1181
1314
|
/* \u6309\u94AE\u884C\u5BB9\u5668:\u4E24\u4E2A\u6309\u94AE\u88C5\u8FDB\u4E00\u4E2A\u53F3\u5BF9\u9F50\u7684\u72EC\u7ACB\u884C\u3002
|
|
1182
1315
|
* \u65B0\u524D\u7AEF\u4EE3\u7801\u5757\u7ED3\u6784\u662F bannerWrap(\u5757\u7EA7 sticky)\u5305\u7740 banner,\u76F4\u63A5 append
|
|
1183
1316
|
* \u4F1A\u88AB\u6324\u5728 banner \u4E4B\u4E0B\u4E14\u9760\u5DE6;\u5B98\u65B9\u590D\u5236\u6309\u94AE\u6240\u5728 .action \u4E5F\u662F flex \u5E76\u6392,
|
|
@@ -1286,7 +1419,72 @@ function openReviewTab(text) {
|
|
|
1286
1419
|
window.setTimeout(() => URL.revokeObjectURL(url), 6e4);
|
|
1287
1420
|
}
|
|
1288
1421
|
var reviewWindows = /* @__PURE__ */ new Set();
|
|
1289
|
-
var
|
|
1422
|
+
var composerApi = null;
|
|
1423
|
+
var AI_REVIEW_ROUTE = "/dsh-code-review/ai-review";
|
|
1424
|
+
var AI_INLINE_LIMIT = 12e3;
|
|
1425
|
+
var aiInFlight = /* @__PURE__ */ new Set();
|
|
1426
|
+
async function startAiReview(diffText, btn) {
|
|
1427
|
+
const api = composerApi;
|
|
1428
|
+
if (btn !== null && aiInFlight.has(btn)) return "failed";
|
|
1429
|
+
if (api === null) {
|
|
1430
|
+
console.warn("[dsh-code-review] AI \u5BA1\u67E5\u4E0D\u53EF\u7528:\u5F53\u524D\u6CA1\u6709\u6253\u5F00\u4E2D\u7684\u4F1A\u8BDD(composer \u672A\u6CE8\u518C)");
|
|
1431
|
+
return "failed";
|
|
1432
|
+
}
|
|
1433
|
+
if (btn !== null) {
|
|
1434
|
+
aiInFlight.add(btn);
|
|
1435
|
+
btn.setAttribute("disabled", "true");
|
|
1436
|
+
}
|
|
1437
|
+
const texts = CR_TEXTS[detectLang()];
|
|
1438
|
+
const finish = (outcome) => {
|
|
1439
|
+
if (btn === null || !btn.isConnected) {
|
|
1440
|
+
if (btn !== null) aiInFlight.delete(btn);
|
|
1441
|
+
return outcome;
|
|
1442
|
+
}
|
|
1443
|
+
aiInFlight.delete(btn);
|
|
1444
|
+
btn.removeAttribute("disabled");
|
|
1445
|
+
if (outcome === "sent" || outcome === "drafted") {
|
|
1446
|
+
btn.textContent = outcome === "sent" ? texts.aiSent : texts.aiDrafted;
|
|
1447
|
+
window.setTimeout(() => {
|
|
1448
|
+
if (btn.isConnected) btn.textContent = texts.aiReview;
|
|
1449
|
+
}, 2e3);
|
|
1450
|
+
} else {
|
|
1451
|
+
btn.dataset.error = "true";
|
|
1452
|
+
btn.textContent = texts.aiFailed;
|
|
1453
|
+
btn.title = texts.aiFailedTitle;
|
|
1454
|
+
window.setTimeout(() => {
|
|
1455
|
+
if (!btn.isConnected) return;
|
|
1456
|
+
delete btn.dataset.error;
|
|
1457
|
+
btn.textContent = texts.aiReview;
|
|
1458
|
+
btn.title = texts.aiReviewTitle;
|
|
1459
|
+
}, 3500);
|
|
1460
|
+
}
|
|
1461
|
+
return outcome;
|
|
1462
|
+
};
|
|
1463
|
+
try {
|
|
1464
|
+
let ref;
|
|
1465
|
+
try {
|
|
1466
|
+
const res = await fetch(AI_REVIEW_ROUTE, {
|
|
1467
|
+
method: "POST",
|
|
1468
|
+
headers: { "Content-Type": "application/json" },
|
|
1469
|
+
body: JSON.stringify({ text: diffText })
|
|
1470
|
+
});
|
|
1471
|
+
if (!res.ok) throw new Error(`http ${res.status}`);
|
|
1472
|
+
const data = await res.json();
|
|
1473
|
+
if (data.ok !== true || typeof data.path !== "string") throw new Error(data.error ?? "bad-response");
|
|
1474
|
+
ref = { kind: "file", path: data.path };
|
|
1475
|
+
} catch (err) {
|
|
1476
|
+
console.warn("[dsh-code-review] AI \u5BA1\u67E5 diff \u6682\u5B58\u5931\u8D25,\u5C1D\u8BD5\u5185\u8054\u964D\u7EA7:", err);
|
|
1477
|
+
if (diffText.length > AI_INLINE_LIMIT) return finish("failed");
|
|
1478
|
+
ref = { kind: "inline", text: diffText };
|
|
1479
|
+
}
|
|
1480
|
+
api.setDraft(buildReviewPrompt(ref, detectLang()));
|
|
1481
|
+
api.submit?.();
|
|
1482
|
+
return finish(api.submit !== void 0 ? "sent" : "drafted");
|
|
1483
|
+
} catch (err) {
|
|
1484
|
+
console.warn("[dsh-code-review] AI \u5BA1\u67E5\u53D1\u8D77\u5931\u8D25:", err);
|
|
1485
|
+
return finish("failed");
|
|
1486
|
+
}
|
|
1487
|
+
}
|
|
1290
1488
|
function sanitizeComments(raw) {
|
|
1291
1489
|
if (!Array.isArray(raw)) return [];
|
|
1292
1490
|
const out = [];
|
|
@@ -1302,15 +1500,30 @@ function sanitizeComments(raw) {
|
|
|
1302
1500
|
function handleReviewMessage(event) {
|
|
1303
1501
|
const data = event.data;
|
|
1304
1502
|
if (typeof data !== "object" || data === null) return;
|
|
1305
|
-
if (data.type !== "dsh-code-review-comments") return;
|
|
1503
|
+
if (data.type !== "dsh-code-review-comments" && data.type !== "dsh-code-review-ai") return;
|
|
1306
1504
|
if (!reviewWindows.has(event.source)) return;
|
|
1505
|
+
const source = event.source;
|
|
1506
|
+
if (data.type === "dsh-code-review-ai") {
|
|
1507
|
+
const text = typeof data.text === "string" ? data.text : "";
|
|
1508
|
+
if (text === "" || !isDiffText(text)) {
|
|
1509
|
+
source.postMessage({ type: "dsh-code-review-ai-ack", ok: false }, "*");
|
|
1510
|
+
return;
|
|
1511
|
+
}
|
|
1512
|
+
void startAiReview(text, null).then((outcome) => {
|
|
1513
|
+
source.postMessage({
|
|
1514
|
+
type: "dsh-code-review-ai-ack",
|
|
1515
|
+
ok: outcome !== "failed",
|
|
1516
|
+
drafted: outcome === "drafted"
|
|
1517
|
+
}, "*");
|
|
1518
|
+
});
|
|
1519
|
+
return;
|
|
1520
|
+
}
|
|
1307
1521
|
const comments = sanitizeComments(data.comments);
|
|
1308
1522
|
const kind = data.kind === "lgtm" ? "lgtm" : "comment";
|
|
1309
1523
|
if (comments.length === 0 && kind !== "lgtm") return;
|
|
1310
1524
|
const report = buildCommentReport(comments, kind, detectLang());
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
composerWriter(report);
|
|
1525
|
+
if (composerApi !== null) {
|
|
1526
|
+
composerApi.setDraft(report);
|
|
1314
1527
|
source.postMessage({ type: "dsh-code-review-ack", ok: true }, "*");
|
|
1315
1528
|
} else {
|
|
1316
1529
|
source.postMessage({ type: "dsh-code-review-ack", ok: false }, "*");
|
|
@@ -1341,6 +1554,22 @@ function ensureButtons(host, getText, fold) {
|
|
|
1341
1554
|
});
|
|
1342
1555
|
row.appendChild(btn);
|
|
1343
1556
|
}
|
|
1557
|
+
if (row.querySelector("[data-dsh-cr-ai]") === null) {
|
|
1558
|
+
const btn = document.createElement("button");
|
|
1559
|
+
btn.type = "button";
|
|
1560
|
+
btn.className = "dsh-cr-aibtn";
|
|
1561
|
+
btn.dataset.dshCrAi = "";
|
|
1562
|
+
btn.textContent = texts.aiReview;
|
|
1563
|
+
btn.title = texts.aiReviewTitle;
|
|
1564
|
+
btn.addEventListener("click", (event) => {
|
|
1565
|
+
event.stopPropagation();
|
|
1566
|
+
event.preventDefault();
|
|
1567
|
+
const text = getText();
|
|
1568
|
+
if (text === null || !isDiffText(text)) return;
|
|
1569
|
+
void startAiReview(text, btn);
|
|
1570
|
+
});
|
|
1571
|
+
row.appendChild(btn);
|
|
1572
|
+
}
|
|
1344
1573
|
if (fold !== null && row.querySelector("[data-dsh-cr-fold]") === null) {
|
|
1345
1574
|
const btn = document.createElement("button");
|
|
1346
1575
|
btn.type = "button";
|
|
@@ -1357,11 +1586,11 @@ function ensureButtons(host, getText, fold) {
|
|
|
1357
1586
|
}
|
|
1358
1587
|
}
|
|
1359
1588
|
function removeButtons(host) {
|
|
1360
|
-
for (const btn of host.querySelectorAll("[data-dsh-cr-btn], [data-dsh-cr-fold]")) {
|
|
1589
|
+
for (const btn of host.querySelectorAll("[data-dsh-cr-btn], [data-dsh-cr-fold], [data-dsh-cr-ai]")) {
|
|
1361
1590
|
btn.remove();
|
|
1362
1591
|
}
|
|
1363
1592
|
const row = host.querySelector("[data-dsh-cr-row]");
|
|
1364
|
-
if (row instanceof HTMLElement && row.querySelector("[data-dsh-cr-btn], [data-dsh-cr-fold], [data-dsh-cr-pathchip]") === null) {
|
|
1593
|
+
if (row instanceof HTMLElement && row.querySelector("[data-dsh-cr-btn], [data-dsh-cr-fold], [data-dsh-cr-ai], [data-dsh-cr-pathchip]") === null) {
|
|
1365
1594
|
row.remove();
|
|
1366
1595
|
}
|
|
1367
1596
|
}
|
|
@@ -1878,10 +2107,19 @@ function DiffToolButtonWatcher({ useSession, useChat, sessionId, inputActions })
|
|
|
1878
2107
|
if (chat !== void 0) syncDiffToolTexts(String(sessionId), chat);
|
|
1879
2108
|
}, [chat, sessionId]);
|
|
1880
2109
|
(0, import_react.useEffect)(() => {
|
|
1881
|
-
const
|
|
1882
|
-
|
|
2110
|
+
const api = {
|
|
2111
|
+
setDraft: (text) => {
|
|
2112
|
+
inputActions?.setDraft(text);
|
|
2113
|
+
}
|
|
2114
|
+
};
|
|
2115
|
+
if (typeof inputActions?.submit === "function") {
|
|
2116
|
+
api.submit = () => {
|
|
2117
|
+
inputActions.submit?.();
|
|
2118
|
+
};
|
|
2119
|
+
}
|
|
2120
|
+
composerApi = api;
|
|
1883
2121
|
return () => {
|
|
1884
|
-
if (
|
|
2122
|
+
if (composerApi === api) composerApi = null;
|
|
1885
2123
|
};
|
|
1886
2124
|
}, [inputActions]);
|
|
1887
2125
|
return null;
|