@jayyuen66/dsh-ocr-review 0.1.8 → 0.1.9
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/CHANGELOG.md +36 -0
- package/README.md +9 -8
- package/README.zh-CN.md +9 -8
- package/THIRD_PARTY_NOTICES.md +2 -1
- package/client.js +916 -543
- package/cordis.patch.yml +1 -1
- package/docs/DETAIL.md +28 -0
- package/docs/DETAIL.zh.md +28 -0
- package/host.js +112 -41
- package/locale/en.json +7 -0
- package/locale/zh.json +7 -0
- package/package.json +50 -42
package/cordis.patch.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ocr-review:open-code-review 工作区审查插件(bundle)。
|
|
2
2
|
# 双面包机制:本包含 Host 半(host.ts:7 个工具 ocr_review/ocr_scan/
|
|
3
|
-
# ocr_delegate_preview/ocr_delegate_rule/ocr_session + 由 `Config` 上 `.volatile()`
|
|
3
|
+
# ocr_delegate_preview/ocr_delegate_rule/ocr_session/ocr_rules_check/ocr_llm + 由 `Config` 上 `.volatile()`
|
|
4
4
|
# 隐式投影的设置面(命名空间就是下面这条的 id)+ 设置卡的 `ocrReview` Remote 读面)与
|
|
5
5
|
# Client 半(src/client-entry.ts → client.js:设置卡片,从宿主设置服务的
|
|
6
6
|
# llm-pi-ai.providers 选 provider/model 应用到
|
package/docs/DETAIL.md
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Open code review
|
|
2
|
+
|
|
3
|
+
Runs the external `ocr` command line over a change set, reports the comments it produces, and manages which model does the reviewing.
|
|
4
|
+
|
|
5
|
+
## What the card changes
|
|
6
|
+
|
|
7
|
+
- **Model** — `ocrProvider` and `ocrModel` are not stored settings: they project the host's model namespace, and entries without a usable key are shown but disabled. `chatGptModel` reflects the external CLI's own login state, which this plugin reads but does not own.
|
|
8
|
+
- **Review parameters** — `effort` (`auto`/`low`/`medium`/`high`) is how many review passes run: deeper review costs more time. `language` chooses Chinese or English output. `autoVerify` (on) runs `ocr llm test` after you pick a provider and model, so a wrong choice fails at pick time rather than at first review. `maxComments` (default 12; 0 means all, including comments that cannot be anchored to a line). `timeoutMinutes` (0 requests the host's ceiling; a positive value is minutes, still clamped by the host). `ocrConfigPath` — empty means `~/.opencodereview/config.json`; a non-empty value must be `<dir>/.opencodereview/config.json` and is honoured by redirecting `HOME` (POSIX only).
|
|
9
|
+
|
|
10
|
+
Six of these are read per call, so a change applies on the next review without restarting the host.
|
|
11
|
+
|
|
12
|
+
## Two separate things called MCP
|
|
13
|
+
|
|
14
|
+
The card's MCP-servers editor writes the **external `ocr` tool's own** configuration. It is unrelated to the `integrationMode` / `mcpPort` surface of the Scrapling plugin, and nothing in this plugin serves MCP itself.
|
|
15
|
+
|
|
16
|
+
## Restart-only values
|
|
17
|
+
|
|
18
|
+
`llmTestTimeoutMs` (90000), `startupVerify` (on: the endpoint self-check runs at load), `ocrBackgroundMaxMs` (0 = no backstop window), `stdoutMaxBytes` (400000), `ocrNoAutoUpdate` (false: upstream auto-upgrade stays on unless you say otherwise) and `ocrInstallHint` (`both`/`brew`/`npm`). Set them in the `config:` block of this plugin's row in `cordis.patch.yml`.
|
|
19
|
+
|
|
20
|
+
## What it needs from outside
|
|
21
|
+
|
|
22
|
+
The `ocr` command line, expected at version **1.13.3**; the expectation is declared in code and mirrored in the package's manifest and READMEs, so a mismatch is reported rather than papered over. Install paths are `brew install open-code-review` or `npm i -g @alibaba-group/open-code-review@1.13.3`. A missing binary is reported as "command not found"; the case where a launcher exists but the binary does not says exactly that. The version probe always forces `OCR_NO_UPDATE=1` so probing cannot trigger an upgrade.
|
|
23
|
+
|
|
24
|
+
API credentials are not stored here: the plugin writes a `api_key_cmd` that calls a helper script, so the key is fetched at run time instead of landing in a config file. If that script is missing, the card warns rather than sending an unauthenticated request.
|
|
25
|
+
|
|
26
|
+
## Further detail
|
|
27
|
+
|
|
28
|
+
The tool list exposed to the model, how background reviews become host jobs and the argument guard's flag table are in the package README (`README.md` / `README.zh-CN.md`).
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# 代码评审(open-code-review)
|
|
2
|
+
|
|
3
|
+
调用外部 `ocr` 命令行评审一个变更集,回报它给出的意见,并管理用来评审的模型。
|
|
4
|
+
|
|
5
|
+
## 设置卡上改的是什么
|
|
6
|
+
|
|
7
|
+
- **模型** — `ocrProvider` 与 `ocrModel` 不是本插件存的值:它们是宿主模型命名空间的投影,没有可用密钥的条目照样列出但禁用。`chatGptModel` 反映的是外部命令行自己的登录态,本插件读它但不拥有它。
|
|
8
|
+
- **审查参数** — `effort`(`auto`/`low`/`medium`/`high`)是跑几轮审查,越深越费时。`language` 选中文或英文输出。`autoVerify`(默认开)在选完提供方与模型后跑一次 `ocr llm test`,选错当场就知道,而不是等第一次评审失败。`maxComments`(默认 12;0 表示全部,包括钉不到行上的意见)。`timeoutMinutes`(0 表示向宿主要上限;正数是分钟数,仍由宿主收口)。`ocrConfigPath`——留空就是 `~/.opencodereview/config.json`;填了就必须是 `<目录>/.opencodereview/config.json` 的形状,生效方式是重定向 `HOME`(仅 POSIX)。
|
|
9
|
+
|
|
10
|
+
其中六项每次调用现读,改完下一次评审生效,不需要重启宿主。
|
|
11
|
+
|
|
12
|
+
## 两处都叫 MCP,但不是同一件事
|
|
13
|
+
|
|
14
|
+
卡上的 MCP servers 编辑器改的是**外部 `ocr` 工具自己的**配置。它与「Scrapling 网页抓取」那套 `integrationMode` / `mcpPort` 的 MCP 面没有关系,本插件也不对外提供 MCP 服务。
|
|
15
|
+
|
|
16
|
+
## 要重启的值
|
|
17
|
+
|
|
18
|
+
`llmTestTimeoutMs`(90000)、`startupVerify`(默认开:装载时自检端点)、`ocrBackgroundMaxMs`(0 表示不设兜底窗口)、`stdoutMaxBytes`(400000)、`ocrNoAutoUpdate`(默认 false,即保留上游的自动升级,除非你明确关掉)、`ocrInstallHint`(`both`/`brew`/`npm`)。写在本插件注册行的 `config:` 块,改值随重启生效。
|
|
19
|
+
|
|
20
|
+
## 需要外部提供什么
|
|
21
|
+
|
|
22
|
+
`ocr` 命令行,期望版本 **1.13.3**;这个期望写在代码里,并镜像到包清单与两份 README,所以版本不符是被报出来的,不是被掩盖的。装法是 `brew install open-code-review` 或 `npm i -g @alibaba-group/open-code-review@1.13.3`。命令行不存在时按「找不到命令」报;只有垫片没有真二进制时说的也是这一件。版本探测一律强制 `OCR_NO_UPDATE=1`,以免探测顺手触发升级。
|
|
23
|
+
|
|
24
|
+
API 凭据不存这里:本插件写的是一个 `api_key_cmd`,运行时的密钥由那支辅助脚本现取,不进配置文件。脚本缺失时卡上给出告警,而不是发一次没有鉴权的请求。
|
|
25
|
+
|
|
26
|
+
## 更完整的说明
|
|
27
|
+
|
|
28
|
+
暴露给模型的工具清单、后台评审如何落成宿主作业、参数守卫的旗标对照表都写在随包 README(`README.md` / `README.zh-CN.md`)。
|
package/host.js
CHANGED
|
@@ -591,16 +591,16 @@ function arrayViolations(schema, value, path, walk) {
|
|
|
591
591
|
* 能嵌套任意多个候选起点,而「run 内是否存在数字或大写」这类前瞻要把后面整段 run 反向
|
|
592
592
|
* 回溯一遍——逐起点相乘就是 O(n²)(实测 400_000 字符 51 秒,正好是 `stdoutMaxBytes`
|
|
593
593
|
* 的默认档)。run 是首个 `-` 之后的那一段:前缀只含字母,故首个 `-` 必是分界。 */
|
|
594
|
-
const STANDALONE_KEY = /\b[A-Za-z]{2,5}-[
|
|
594
|
+
const STANDALONE_KEY = /\b[A-Za-z]{2,5}-[\w-]{20,}\b/gu;
|
|
595
595
|
/** `Authorization: <scheme> <value>` / `Proxy-Authorization: …`:认**头名**而不是
|
|
596
596
|
* 枚举认证方案。头名本身就是「后面跟着凭据」的信号,Basic / Token / Digest 乃至
|
|
597
597
|
* vendor 自定义 scheme 都不会漏;而只认头名还避开了正文里「token based
|
|
598
598
|
* authentication」这类措辞被误抹。scheme 组连同其后的空白一起捕获,回填时凭据被
|
|
599
599
|
* 替换而 scheme 词保留。 */
|
|
600
|
-
const AUTH_HEADER_VALUE = /(?<name>\b(?:proxy-)?authorization["'\s]*[:=]["'\s]*)(
|
|
600
|
+
const AUTH_HEADER_VALUE = /(?<name>\b(?:proxy-)?authorization["'\s]*[:=]["'\s]*)(?<scheme>[a-z][\w-]*[ \t]+)?[^\s"',;)}\]]{4,}/giu;
|
|
601
601
|
/** 裸 scheme + 值(没有头名时 OCR 也会整行回显)。scheme 只留 bearer:它在散文中几乎
|
|
602
602
|
* 不出现,而 token / basic 之类会撞上「token count」「basic usage」等普通措辞。 */
|
|
603
|
-
const BEARER_VALUE = /(?<scheme>\bbearer[ \t]+)[
|
|
603
|
+
const BEARER_VALUE = /(?<scheme>\bbearer[ \t]+)[\w.~+/=-]{4,}/giu;
|
|
604
604
|
/** `api_key: "xxx"`、`apiKey=xxx`、`token: xxx`、`x-api-key: xxx` 等赋值形态。
|
|
605
605
|
* 值下限取 4 且容许一个 scheme 词:`token: Basic <base64>` 里赋值形态先能吃到的只是
|
|
606
606
|
* scheme 词(Basic 恰好 5 字符),下限更高时整条失配,后面那个 base64 就原样进了日志。
|
|
@@ -611,16 +611,25 @@ const BEARER_VALUE = /(?<scheme>\bbearer[ \t]+)[a-z0-9._~+/=-]{4,}/giu;
|
|
|
611
611
|
*
|
|
612
612
|
* 字符类写 `[A-Z0-9]` 而不是 `[A-Za-z0-9]`:这条正则带 `i` 标志,两者等价,
|
|
613
613
|
* 而重复的字符区间会让静态检查报 duplicates-in-character-class。 */
|
|
614
|
-
const KEY_ASSIGNMENT = /(?<name>(?<![A-Z0-9])(?:api[_-]?key|
|
|
614
|
+
const KEY_ASSIGNMENT = /(?<name>(?<![A-Z0-9])(?:api[_-]?key|access[_-]?key|secret[_-]?key|client[_-]?secret|password|token)["'\s]*[:=]["'\s]*)(?<scheme>[a-z][\w-]*[ \t]+)?[^\s"',;)}\]]{4,}/giu;
|
|
615
615
|
/** GitHub 系 token 的**下划线**形态(`ghp_…`、`github_pat_…`):STANDALONE_KEY 要求
|
|
616
616
|
* 「字母前缀 + 连字符」分界,这类值里没有连字符,前缀后又紧跟下划线,故单列一条。
|
|
617
617
|
* 不并进 STANDALONE_KEY 是为了不放开那里的连字符要求——那条要求同时承担「别把
|
|
618
618
|
* kebab 小写路径当 key 抹掉」的职责。 */
|
|
619
|
-
const UNDERSCORE_TOKEN = /\b(?:gh[pousr]|github_pat)_
|
|
619
|
+
const UNDERSCORE_TOKEN = /\b(?:gh[pousr]|github_pat)_\w{20,}\b/gu;
|
|
620
620
|
const REDACTION = "***REDACTED***";
|
|
621
|
-
/**
|
|
622
|
-
|
|
623
|
-
|
|
621
|
+
/** token 尾部至少一枚非 `-` 的字母/数字。 */
|
|
622
|
+
const TOKEN_TAIL_CHARACTER = /[0-9A-Z]/u;
|
|
623
|
+
/** 独立 key 形态的替换臂:随机串段里真有数字或大写才抹,否则原样交回(文件路径要保持可读)。
|
|
624
|
+
* 两侧任一是路径分隔符的匹配是路径段而非 key:ocr CLI 会把仓库路径拍平成 kebab 段嵌进
|
|
625
|
+
* session 的 file_path(`…/sessions/Users-yuanjiang-Documents-…/uuid.jsonl`),原路径里
|
|
626
|
+
* 只要有大写组件(macOS 的 Documents/Desktop 一类)就过得了「数字或大写」那道护栏——
|
|
627
|
+
* 裸 key 从不出现在斜杠两侧,路径段也从不出现在别处,按上下文分比按字符集猜稳。 */
|
|
628
|
+
function redactStandaloneKey(match, offset, whole) {
|
|
629
|
+
const before = whole[offset - 1];
|
|
630
|
+
const after = whole[offset + match.length];
|
|
631
|
+
if (before === "/" || after === "/" || before === "\\" || after === "\\") return match;
|
|
632
|
+
return TOKEN_TAIL_CHARACTER.test(match.slice(match.indexOf("-") + 1)) ? REDACTION : match;
|
|
624
633
|
}
|
|
625
634
|
/**
|
|
626
635
|
* 抹掉文本里 key 形状的片段。
|
|
@@ -693,6 +702,9 @@ function assertToolOutput(schema, value, toolName, messages) {
|
|
|
693
702
|
return value;
|
|
694
703
|
}
|
|
695
704
|
// lib/argv-guard.ts
|
|
705
|
+
/** 归一后剥掉的尾随分隔符。 */
|
|
706
|
+
const TRAILING_POSIX_SLASHES = /\/+$/u;
|
|
707
|
+
const TRAILING_PATH_SEPARATORS = /[\\/]+$/u;
|
|
696
708
|
/**
|
|
697
709
|
* POSIX sh 单引号转义:' → '\''。把任意字符串安全放进单引号内,防注入。
|
|
698
710
|
*
|
|
@@ -724,11 +736,11 @@ function assertAbsoluteRoot(root, messages) {
|
|
|
724
736
|
if (trimmed.includes("\0")) throw new Error(format(messages.noNulBytes, { name: "repo" }));
|
|
725
737
|
if (trimmed.startsWith("/")) {
|
|
726
738
|
const normalized = path.posix.normalize(trimmed);
|
|
727
|
-
return normalized.length > 1 ? normalized.replace(
|
|
739
|
+
return normalized.length > 1 ? normalized.replace(TRAILING_POSIX_SLASHES, "") : normalized;
|
|
728
740
|
}
|
|
729
741
|
if (process.platform === "win32" && path.win32.isAbsolute(trimmed)) {
|
|
730
742
|
const normalized = path.win32.normalize(trimmed);
|
|
731
|
-
return normalized.length > 3 ? normalized.replace(
|
|
743
|
+
return normalized.length > 3 ? normalized.replace(TRAILING_PATH_SEPARATORS, "") : normalized;
|
|
732
744
|
}
|
|
733
745
|
throw new Error(format(messages.repoMustBeAbsolute, { root: trimmed }));
|
|
734
746
|
}
|
|
@@ -887,8 +899,10 @@ function resolveOutputFormat(value) {
|
|
|
887
899
|
return REVIEW_OUTPUT_FORMATS.find((candidate) => candidate === value) ?? "json";
|
|
888
900
|
}
|
|
889
901
|
// lib/cli.ts
|
|
890
|
-
/** `
|
|
891
|
-
|
|
902
|
+
/** Homebrew 安装路径(`Cellar` / `Homebrew` 任一段)。 */
|
|
903
|
+
const HOMEBREW_PATH = /(?:^|[/\\])(?:Cellar|Homebrew)(?:[/\\]|$)/iu;
|
|
904
|
+
/** `ocr` 版本行:`open-code-review v1.13.3`(允许预发布/构建元数据)。 */
|
|
905
|
+
const OCR_VERSION = /open-code-review\s+v?(?<version>\d+\.\d+(?:\.\d+)?(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?)/u;
|
|
892
906
|
/** `--exclude` 的模式条数上限。**不是审查范围的上限**,只是防一条失控输入把命令串撑爆;
|
|
893
907
|
* 真实使用里几十条就到头,给到 2000 是为了"尽量不截断"——超了会明确告警,不静默丢。 */
|
|
894
908
|
const EXCLUDE_LIST_LIMIT = 2e3;
|
|
@@ -898,6 +912,8 @@ const PATH_LIST_LIMIT = 2e3;
|
|
|
898
912
|
const RULE_FILE_MAX_BYTES = 262144;
|
|
899
913
|
const BACKGROUND_FILE_MAX_BYTES = 1048576;
|
|
900
914
|
const MAX_GIT_PROCS_FLAG = "--max-git-procs";
|
|
915
|
+
/** `ocr review|scan|delegate preview` 的背景说明 flag(三处命令构造共用一条)。
|
|
916
|
+
* pushTextFlag 按 `flag.slice(2)` 反推报错用的参数名,故值必须带 `--` 前缀。 */
|
|
901
917
|
const BACKGROUND_FLAG = "--background";
|
|
902
918
|
/**
|
|
903
919
|
* 本包的契约是按外部 `ocr` CLI 的哪一版核对的(README 的前置条件、装法建议与工具回显里
|
|
@@ -1185,7 +1201,7 @@ function realPathOrSelf(candidate) {
|
|
|
1185
1201
|
function inferOcrInstallChannel(candidate) {
|
|
1186
1202
|
const resolved = realPathOrSelf(candidate);
|
|
1187
1203
|
if (resolved.includes("node_modules")) return "npm";
|
|
1188
|
-
return
|
|
1204
|
+
return HOMEBREW_PATH.test(resolved) ? "brew" : "unknown";
|
|
1189
1205
|
}
|
|
1190
1206
|
/** brew 的 formula 名与 npm 的包名不同:formula 不带 scope。 */
|
|
1191
1207
|
const OCR_BREW_FORMULA = "open-code-review";
|
|
@@ -1324,7 +1340,7 @@ function buildVersionCommand() {
|
|
|
1324
1340
|
* 读不出即 null:未发布构建的 `<Version>` 是 `dev`,没有版本号可报,猜一个等于编一个。
|
|
1325
1341
|
*/
|
|
1326
1342
|
function parseOcrVersion(text) {
|
|
1327
|
-
return
|
|
1343
|
+
return OCR_VERSION.exec(text)?.groups?.["version"] ?? null;
|
|
1328
1344
|
}
|
|
1329
1345
|
/**
|
|
1330
1346
|
* `--format` 交给 OCR 的取值:本包只认 json——`lib/parse.ts` 的解析层只解 JSON 一份形状。
|
|
@@ -1729,6 +1745,8 @@ function aggregateComments(comments) {
|
|
|
1729
1745
|
};
|
|
1730
1746
|
}
|
|
1731
1747
|
// lib/parse.ts
|
|
1748
|
+
/** 只认十进制整数的行号/计数文本。 */
|
|
1749
|
+
const DECIMAL_INTEGER = /^\d+$/u;
|
|
1732
1750
|
/**
|
|
1733
1751
|
* OCR 评审 JSON 的 status 枚举(判据在 CLI 侧 `cmd/opencodereview/output.go` 的 jsonOutput
|
|
1734
1752
|
* 装配与 `internal/session/manifest.go` 的 TerminalState)。
|
|
@@ -1784,7 +1802,7 @@ function num(value) {
|
|
|
1784
1802
|
if (typeof value === "number") return nonNegativeInt(value);
|
|
1785
1803
|
if (typeof value === "string") {
|
|
1786
1804
|
const trimmed = value.trim();
|
|
1787
|
-
if (
|
|
1805
|
+
if (!DECIMAL_INTEGER.test(trimmed)) return 0;
|
|
1788
1806
|
return nonNegativeInt(Number(trimmed));
|
|
1789
1807
|
}
|
|
1790
1808
|
return 0;
|
|
@@ -1849,7 +1867,7 @@ function capLines(lines, total, messages) {
|
|
|
1849
1867
|
* 否则 "x" 或 -1 会被 num 折成 0,把「读不出」伪装成「零失败」)。 */
|
|
1850
1868
|
function isCount(value) {
|
|
1851
1869
|
if (typeof value === "number") return Number.isSafeInteger(value) && value >= 0;
|
|
1852
|
-
return typeof value === "string" &&
|
|
1870
|
+
return typeof value === "string" && DECIMAL_INTEGER.test(value.trim());
|
|
1853
1871
|
}
|
|
1854
1872
|
/**
|
|
1855
1873
|
* 解析 `tool_calls`。字段缺失或为 null(这一场没有工具调用记录)返回 null;
|
|
@@ -2931,6 +2949,8 @@ const ROUTING_ORDER = 1555;
|
|
|
2931
2949
|
// lib/provider-projection.ts
|
|
2932
2950
|
/** llm-pi-ai 命名空间里 provider 表的字段名。 */
|
|
2933
2951
|
const PROVIDERS_FIELD = "providers";
|
|
2952
|
+
/** provider 的 baseURL 只认 http(s)。 */
|
|
2953
|
+
const HTTP_URL$1 = /^https?:\/\//iu;
|
|
2934
2954
|
/**
|
|
2935
2955
|
* llm-pi-ai 的 `api` 协议 → OCR config 的 `protocol` 映射。
|
|
2936
2956
|
*
|
|
@@ -3005,7 +3025,7 @@ function providerFromRaw(name, raw) {
|
|
|
3005
3025
|
const api = apiKeyOf(entry["api"]);
|
|
3006
3026
|
const protocol = api === null ? "openai" : OCR_PROTOCOL_BY_API.get(api) ?? api;
|
|
3007
3027
|
const baseURL = typeof entry["baseURL"] === "string" ? entry["baseURL"].trim() : "";
|
|
3008
|
-
if (!baseURL ||
|
|
3028
|
+
if (!baseURL || !HTTP_URL$1.test(baseURL)) return null;
|
|
3009
3029
|
const modelsRaw = Array.isArray(entry["models"]) ? entry["models"] : [];
|
|
3010
3030
|
const models = modelsRaw.map((model) => modelFromRaw(model)).filter((model) => model !== null);
|
|
3011
3031
|
if (modelsRaw.length > 0 && models.length === 0) return null;
|
|
@@ -3156,6 +3176,11 @@ async function markKeyFlags(gateway, providers) {
|
|
|
3156
3176
|
// lib/config-store.ts
|
|
3157
3177
|
/** api_key_cmd 脚本名(pluginDir 兜底路径的唯一拼法来源)。 */
|
|
3158
3178
|
const GET_CRED_SCRIPT = path.join("scripts", "get-cred.mjs");
|
|
3179
|
+
/** 尾部反斜杠 / 翻倍反斜杠 / env 键 / 行首 node 命令。 */
|
|
3180
|
+
const TRAILING_BACKSLASHES = /\\+$/u;
|
|
3181
|
+
const DOUBLED_BACKSLASHES = /(?:\\\\)+$/u;
|
|
3182
|
+
const ENV_KEY = /^[A-Z0-9_]+$/u;
|
|
3183
|
+
const LEADING_NODE_CMD = /^node[ \t]+/u;
|
|
3159
3184
|
/** 未知值 → 安全字符串(非字符串给 '')。 */
|
|
3160
3185
|
function strOf(value) {
|
|
3161
3186
|
return typeof value === "string" ? value : "";
|
|
@@ -3228,7 +3253,7 @@ function readExistingText(configPath, messages) {
|
|
|
3228
3253
|
* 保证常规路径形态正确)。
|
|
3229
3254
|
*/
|
|
3230
3255
|
function shqDyn(value) {
|
|
3231
|
-
if (process.platform === "win32") return `"${value.replaceAll("\"", "\"\"").replace(
|
|
3256
|
+
if (process.platform === "win32") return `"${value.replaceAll("\"", "\"\"").replace(TRAILING_BACKSLASHES, (backslashes) => backslashes + backslashes)}"`;
|
|
3232
3257
|
return `'${value.replaceAll("'", String.raw`'\''`)}'`;
|
|
3233
3258
|
}
|
|
3234
3259
|
/** api_key_cmd 值构造:脚本路径与 env key 名(及可选的 dsh 数据目录)shq 转义后
|
|
@@ -3239,7 +3264,7 @@ function shqDyn(value) {
|
|
|
3239
3264
|
* env key 白名单维持 ^[A-Z0-9_]+$(刻意比宿主 CredentialRef 的语法紧:OCR 侧
|
|
3240
3265
|
* 惯例大写,且这是写进 config.json 的可信输入)。 */
|
|
3241
3266
|
function buildApiKeyCmd(scriptPath, envKey, dshHome, messages) {
|
|
3242
|
-
if (
|
|
3267
|
+
if (!ENV_KEY.test(envKey)) throw new Error(format(messages.badEnvKey, { key: envKey }));
|
|
3243
3268
|
const parts = [
|
|
3244
3269
|
"node",
|
|
3245
3270
|
shqDyn(scriptPath),
|
|
@@ -3319,7 +3344,7 @@ function readDqToken(body, start) {
|
|
|
3319
3344
|
}
|
|
3320
3345
|
if (!closed) return null;
|
|
3321
3346
|
return {
|
|
3322
|
-
value: value.replace(
|
|
3347
|
+
value: value.replace(DOUBLED_BACKSLASHES, (backslashes) => "\\".repeat(backslashes.length / 2)),
|
|
3323
3348
|
next: i
|
|
3324
3349
|
};
|
|
3325
3350
|
}
|
|
@@ -3361,13 +3386,13 @@ function readDqTokens(body) {
|
|
|
3361
3386
|
* 把用户手写的凭据取用方式弄坏,而那正是本包明令不该插手的部分。
|
|
3362
3387
|
*/
|
|
3363
3388
|
function parseApiKeyCmd(command) {
|
|
3364
|
-
const body = command.replace(
|
|
3389
|
+
const body = command.replace(LEADING_NODE_CMD, "");
|
|
3365
3390
|
if (body === command) return null;
|
|
3366
3391
|
const tokens = readShqTokens(body) ?? readDqTokens(body);
|
|
3367
3392
|
if (tokens === null) return null;
|
|
3368
3393
|
const script = tokens[0] ?? "";
|
|
3369
3394
|
const envKey = tokens[1] ?? "";
|
|
3370
|
-
if (script === "" || tokens.length < 2 || tokens.length > 3 ||
|
|
3395
|
+
if (script === "" || tokens.length < 2 || tokens.length > 3 || !ENV_KEY.test(envKey)) return null;
|
|
3371
3396
|
return {
|
|
3372
3397
|
script,
|
|
3373
3398
|
envKey,
|
|
@@ -3818,6 +3843,8 @@ const OCR_REMOTE_NAMESPACE = "ocrReview";
|
|
|
3818
3843
|
// lib/mcp-config.ts
|
|
3819
3844
|
/** 整体上限:MCP 配置是一份人工维护的小表,不承载大文件。 */
|
|
3820
3845
|
const MCP_MAX_BYTES = 65536;
|
|
3846
|
+
/** remote MCP server 的 url 只认 http(s)。 */
|
|
3847
|
+
const HTTP_URL = /^https?:\/\//iu;
|
|
3821
3848
|
const MCP_MAX_SERVERS = 100;
|
|
3822
3849
|
const MCP_MAX_ARRAY = 100;
|
|
3823
3850
|
const MCP_MAX_TEXT = 4096;
|
|
@@ -3907,7 +3934,7 @@ function normalizeRemote(raw, name, messages) {
|
|
|
3907
3934
|
if (raw["command"] !== void 0 || raw["args"] !== void 0 || raw["env"] !== void 0 || raw["setup"] !== void 0) return failure(name, "type", "remote does not accept command/args/env/setup", messages);
|
|
3908
3935
|
const url = textField(raw["url"], name, "url", messages);
|
|
3909
3936
|
if (!url.ok) return url;
|
|
3910
|
-
if (
|
|
3937
|
+
if (!HTTP_URL.test(url.value)) return failure(name, "url", "must start with http:// or https://", messages);
|
|
3911
3938
|
const entry = {
|
|
3912
3939
|
type: "remote",
|
|
3913
3940
|
url: url.value
|
|
@@ -4118,10 +4145,13 @@ function isLiveOcrStatus(status) {
|
|
|
4118
4145
|
* @param jobs 官方注册表
|
|
4119
4146
|
* @returns {void}
|
|
4120
4147
|
*/
|
|
4121
|
-
function pruneOcrJobHistory(jobs) {
|
|
4122
|
-
const settled = jobs.list().filter((job) => job.kind === OCR_JOB_KIND && !isLiveOcrStatus(job.status));
|
|
4148
|
+
function pruneOcrJobHistory(jobs, owner, ledger) {
|
|
4149
|
+
const settled = jobs.list(owner).filter((job) => job.kind === OCR_JOB_KIND && !isLiveOcrStatus(job.status));
|
|
4123
4150
|
const excess = settled.length - 9;
|
|
4124
|
-
for (const job of settled.slice(0, Math.max(excess, 0)))
|
|
4151
|
+
for (const job of settled.slice(0, Math.max(excess, 0))) {
|
|
4152
|
+
jobs.remove(job.id, owner);
|
|
4153
|
+
ledger.delete(job.id);
|
|
4154
|
+
}
|
|
4125
4155
|
}
|
|
4126
4156
|
/**
|
|
4127
4157
|
* 在 controller 闸门下起一条未拥有作业,起完立刻摘掉。
|
|
@@ -4169,10 +4199,44 @@ async function enforceOcrDeadline(jobs, id, reason, backgroundMaxMs) {
|
|
|
4169
4199
|
* @param reason 写进模型可见 detail 的理由(双语字典取)
|
|
4170
4200
|
* @returns {void}
|
|
4171
4201
|
*/
|
|
4172
|
-
function
|
|
4173
|
-
const
|
|
4174
|
-
|
|
4175
|
-
|
|
4202
|
+
function reclaimSessionJobs(jobs, reason, owner, ledger) {
|
|
4203
|
+
for (const job of jobs.list(owner)) if (job.kind === OCR_JOB_KIND && job.owner === owner) {
|
|
4204
|
+
ledger.delete(job.id);
|
|
4205
|
+
if (isLiveOcrStatus(job.status)) jobs.kill(job.id, owner, reason);
|
|
4206
|
+
else jobs.remove(job.id, owner);
|
|
4207
|
+
}
|
|
4208
|
+
}
|
|
4209
|
+
/** 卸载时收一条有主作业:id 只有台账知道,kill/remove 必须带 owner 作 caller。 */
|
|
4210
|
+
function reclaimOwnedJob(jobs, id, owner, reason) {
|
|
4211
|
+
try {
|
|
4212
|
+
if (isLiveOcrStatus(jobs.get(id, owner).status)) jobs.kill(id, owner, reason);
|
|
4213
|
+
else jobs.remove(id, owner);
|
|
4214
|
+
} catch {}
|
|
4215
|
+
}
|
|
4216
|
+
/**
|
|
4217
|
+
* 卸载时收掉本包起过的全部作业:无主的经 `list()` 枚举,有主的只有台账知道 id
|
|
4218
|
+
* (注册表对没有 caller 的视图隐藏它们,且 kill/remove 必须带 owner 作 caller)。
|
|
4219
|
+
*/
|
|
4220
|
+
function reclaimAllJobs(jobs, reason, ledger) {
|
|
4221
|
+
for (const job of jobs.list()) if (job.kind === OCR_JOB_KIND) {
|
|
4222
|
+
if (isLiveOcrStatus(job.status)) jobs.kill(job.id, void 0, reason);
|
|
4223
|
+
else jobs.remove(job.id);
|
|
4224
|
+
}
|
|
4225
|
+
const owned = new Map(ledger);
|
|
4226
|
+
ledger.clear();
|
|
4227
|
+
for (const [id, owner] of owned) if (owner !== void 0) reclaimOwnedJob(jobs, id, owner, reason);
|
|
4228
|
+
}
|
|
4229
|
+
/** 工具调用发起时的会话 id;形状不对就当没有(agentless)。 */
|
|
4230
|
+
function sessionIdOf(exec) {
|
|
4231
|
+
const id = exec.agent?.session?.id;
|
|
4232
|
+
return typeof id === "string" && id.length > 0 ? id : void 0;
|
|
4233
|
+
}
|
|
4234
|
+
/** 该调用可绑定的作业 owner:会话 id 存在、且宿主注册表里确有这条活 Agent。
|
|
4235
|
+
* 给不出活 Agent 时退回 agentless —— 拿一个注册表不认的 id 去 start 只会整条失败。 */
|
|
4236
|
+
function ownerOf(host, exec) {
|
|
4237
|
+
const id = sessionIdOf(exec);
|
|
4238
|
+
const agents = host.get("agents");
|
|
4239
|
+
return id !== void 0 && agents?.get(id) !== void 0 ? id : void 0;
|
|
4176
4240
|
}
|
|
4177
4241
|
/** 当前会话工作区(repo 缺省回退源);非字符串视为缺。
|
|
4178
4242
|
* 官方形状:`agent?: Agent` → `session: Session` → `header: SessionHeader` →
|
|
@@ -4393,7 +4457,7 @@ async function outcomeWhenSettled(proc) {
|
|
|
4393
4457
|
* @param job 这次后台评审的描述
|
|
4394
4458
|
* @returns 后台启动回执(canonical 对象,模型侧契约)
|
|
4395
4459
|
*/
|
|
4396
|
-
async function startBackground(host, job) {
|
|
4460
|
+
async function startBackground(host, job, ledger) {
|
|
4397
4461
|
const registry = host.get("jobs");
|
|
4398
4462
|
if (registry === void 0) throw new Error("background review unavailable: the host provides no ctx.jobs (load @deepseek-ai/dsh-jobs-local); run it with wait:true instead");
|
|
4399
4463
|
const spec = host.shell.resolve({
|
|
@@ -4405,13 +4469,14 @@ async function startBackground(host, job) {
|
|
|
4405
4469
|
sandboxPolicy: ocrSandboxPolicy(job.workdir)
|
|
4406
4470
|
});
|
|
4407
4471
|
const proc = await host.shell.execute(spec);
|
|
4408
|
-
pruneOcrJobHistory(registry);
|
|
4472
|
+
pruneOcrJobHistory(registry, job.owner, ledger);
|
|
4409
4473
|
const messages = localeMessages(host);
|
|
4410
4474
|
let jobId;
|
|
4411
4475
|
try {
|
|
4412
4476
|
jobId = startUnderOcrController(registry, {
|
|
4413
4477
|
kind: OCR_JOB_KIND,
|
|
4414
4478
|
label: `${job.tool} ${job.repo}`,
|
|
4479
|
+
...job.owner === void 0 ? {} : { owner: job.owner },
|
|
4415
4480
|
output: reviewSources(proc, messages),
|
|
4416
4481
|
run: () => ({
|
|
4417
4482
|
cancel: () => {
|
|
@@ -4424,6 +4489,7 @@ async function startBackground(host, job) {
|
|
|
4424
4489
|
killProc(proc);
|
|
4425
4490
|
throw error;
|
|
4426
4491
|
}
|
|
4492
|
+
ledger.set(jobId, job.owner);
|
|
4427
4493
|
const backstopMinutes = Math.round(job.backgroundMaxMs / 6e4);
|
|
4428
4494
|
enforceOcrDeadline(registry, jobId, format(messages.jobReasonTimeout, { minutes: String(backstopMinutes) }), job.backgroundMaxMs);
|
|
4429
4495
|
return {
|
|
@@ -5359,7 +5425,7 @@ async function runScanPreview(run) {
|
|
|
5359
5425
|
});
|
|
5360
5426
|
}
|
|
5361
5427
|
}
|
|
5362
|
-
function registerReviewTool(host, config, probe, versionSink) {
|
|
5428
|
+
function registerReviewTool(host, config, probe, versionSink, ledger) {
|
|
5363
5429
|
const msgs = localeMessages(host);
|
|
5364
5430
|
define(host, {
|
|
5365
5431
|
name: "ocr_review",
|
|
@@ -5481,10 +5547,11 @@ function registerReviewTool(host, config, probe, versionSink) {
|
|
|
5481
5547
|
workdir,
|
|
5482
5548
|
tool: "ocr_review",
|
|
5483
5549
|
repo,
|
|
5550
|
+
owner: ownerOf(host, exec),
|
|
5484
5551
|
stdoutMaxBytes: config.stdoutMaxBytes,
|
|
5485
5552
|
backgroundMaxMs: config.ocrBackgroundMaxMs,
|
|
5486
5553
|
env: ocrEnv(config)
|
|
5487
|
-
});
|
|
5554
|
+
}, ledger);
|
|
5488
5555
|
try {
|
|
5489
5556
|
await runForeground(host, guarded, workdir, tuningOf(config), exec.signal);
|
|
5490
5557
|
} catch (error) {
|
|
@@ -5508,7 +5575,7 @@ function registerReviewTool(host, config, probe, versionSink) {
|
|
|
5508
5575
|
}
|
|
5509
5576
|
});
|
|
5510
5577
|
}
|
|
5511
|
-
function registerScanTool(host, config, probe, versionSink) {
|
|
5578
|
+
function registerScanTool(host, config, probe, versionSink, ledger) {
|
|
5512
5579
|
const msgs = localeMessages(host);
|
|
5513
5580
|
define(host, {
|
|
5514
5581
|
name: "ocr_scan",
|
|
@@ -5616,10 +5683,11 @@ function registerScanTool(host, config, probe, versionSink) {
|
|
|
5616
5683
|
workdir,
|
|
5617
5684
|
tool: "ocr_scan",
|
|
5618
5685
|
repo,
|
|
5686
|
+
owner: ownerOf(host, exec),
|
|
5619
5687
|
stdoutMaxBytes: config.stdoutMaxBytes,
|
|
5620
5688
|
backgroundMaxMs: config.ocrBackgroundMaxMs,
|
|
5621
5689
|
env: ocrEnv(config)
|
|
5622
|
-
});
|
|
5690
|
+
}, ledger);
|
|
5623
5691
|
try {
|
|
5624
5692
|
await runForeground(host, guarded, workdir, tuningOf(config), exec.signal);
|
|
5625
5693
|
} catch (error) {
|
|
@@ -6006,26 +6074,29 @@ function apply(ctx, config, probe) {
|
|
|
6006
6074
|
const host = resolveHost(ctx);
|
|
6007
6075
|
const endpointVerify = { value: null };
|
|
6008
6076
|
const versionSink = { value: null };
|
|
6077
|
+
const jobLedger = /* @__PURE__ */ new Map();
|
|
6009
6078
|
if (config.startupVerify) {
|
|
6010
6079
|
probeEndpointAtApply(host, config, endpointVerify);
|
|
6011
6080
|
probeVersionAtApply(host, config, versionSink);
|
|
6012
6081
|
}
|
|
6013
6082
|
ctx.effect(() => () => {
|
|
6014
6083
|
const jobs = ctx.get("jobs");
|
|
6015
|
-
if (jobs !== void 0)
|
|
6084
|
+
if (jobs !== void 0) reclaimAllJobs(jobs, localeMessages(ctx).jobReasonUnload, jobLedger);
|
|
6016
6085
|
}, "ocr-review: background jobs");
|
|
6017
|
-
ctx.effect(() => ctx.on("session/disposed", () => {
|
|
6086
|
+
ctx.effect(() => ctx.on("session/disposed", (session) => {
|
|
6018
6087
|
const jobs = ctx.get("jobs");
|
|
6019
6088
|
if (jobs === void 0) return;
|
|
6089
|
+
const owner = session.id;
|
|
6090
|
+
if (typeof owner !== "string" || owner.length === 0) return;
|
|
6020
6091
|
try {
|
|
6021
|
-
|
|
6092
|
+
reclaimSessionJobs(jobs, localeMessages(ctx).jobReasonSessionStopped, owner, jobLedger);
|
|
6022
6093
|
} catch {}
|
|
6023
6094
|
}), "ocr-review: session stop reaps background runs");
|
|
6024
6095
|
host.inject(["settings"], (child) => {
|
|
6025
6096
|
child.effect(() => child.settings.configure({ auto: false }, host.fiber));
|
|
6026
6097
|
});
|
|
6027
|
-
registerReviewTool(host, config, probe, versionSink);
|
|
6028
|
-
registerScanTool(host, config, probe, versionSink);
|
|
6098
|
+
registerReviewTool(host, config, probe, versionSink, jobLedger);
|
|
6099
|
+
registerScanTool(host, config, probe, versionSink, jobLedger);
|
|
6029
6100
|
registerDelegatePreviewTool(host, config, probe);
|
|
6030
6101
|
registerDelegateRuleTool(host, config, probe);
|
|
6031
6102
|
registerSessionTool(host, config, probe);
|
package/locale/en.json
ADDED
package/locale/zh.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jayyuen66/dsh-ocr-review",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.9",
|
|
4
4
|
"description": "dsh 工作区代码审查插件:把 open-code-review(ocr CLI)封装为 7 个模型工具 + 设置卡片(从宿主设置服务的 provider 清单选 provider/model 应用到 OCR 配置)。",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai-code-review",
|
|
@@ -22,6 +22,9 @@
|
|
|
22
22
|
"url": "git+https://github.com/JayYuen666/dsh-ocr-review.git"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
|
+
"locale/zh.json",
|
|
26
|
+
"locale/en.json",
|
|
27
|
+
"docs",
|
|
25
28
|
"icon.svg",
|
|
26
29
|
"host.js",
|
|
27
30
|
"client.js",
|
|
@@ -40,6 +43,7 @@
|
|
|
40
43
|
".": "./host.js",
|
|
41
44
|
"./client": "./client.js",
|
|
42
45
|
"./cordis.patch.yml": "./cordis.patch.yml",
|
|
46
|
+
"./locale/*.json": "./locale/*.json",
|
|
43
47
|
"./package.json": "./package.json"
|
|
44
48
|
},
|
|
45
49
|
"publishConfig": {
|
|
@@ -47,47 +51,51 @@
|
|
|
47
51
|
"registry": "https://registry.npmjs.org"
|
|
48
52
|
},
|
|
49
53
|
"dependencies": {
|
|
50
|
-
"@jayyuen66/dsh-plugin-shared": "0.1
|
|
51
|
-
"js-yaml": "^5.4.3"
|
|
54
|
+
"@jayyuen66/dsh-plugin-shared": "0.2.1",
|
|
55
|
+
"js-yaml": "^5.4.3",
|
|
56
|
+
"zod": "4.6.7"
|
|
52
57
|
},
|
|
53
58
|
"devDependencies": {
|
|
54
|
-
"@deepseek-ai/cordis": "4.0.
|
|
55
|
-
"@deepseek-ai/dsh-agent": "0.2.
|
|
56
|
-
"@deepseek-ai/dsh-api-remotes": "0.2.
|
|
57
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.
|
|
58
|
-
"@deepseek-ai/dsh-client-locale": "0.2.
|
|
59
|
-
"@deepseek-ai/dsh-client-store": "0.2.
|
|
60
|
-
"@deepseek-ai/dsh-client-ui-layout": "0.2.
|
|
61
|
-
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.
|
|
62
|
-
"@deepseek-ai/dsh-client-ui-renderer": "0.2.
|
|
63
|
-
"@deepseek-ai/dsh-client-ui-settings": "0.2.
|
|
64
|
-
"@deepseek-ai/dsh-client-ui-slots": "0.2.
|
|
65
|
-
"@deepseek-ai/dsh-credentials": "0.2.
|
|
66
|
-
"@deepseek-ai/dsh-home-paths": "0.2.
|
|
67
|
-
"@deepseek-ai/dsh-host-webserver": "0.2.
|
|
68
|
-
"@deepseek-ai/dsh-jobs": "0.2.
|
|
69
|
-
"@deepseek-ai/dsh-jobs-local": "0.2.
|
|
70
|
-
"@deepseek-ai/dsh-output-retention": "0.2.
|
|
71
|
-
"@deepseek-ai/dsh-package-manifest": "0.2.
|
|
72
|
-
"@deepseek-ai/dsh-ptc-runtime": "0.2.
|
|
73
|
-
"@deepseek-ai/dsh-sandbox": "0.2.
|
|
74
|
-
"@deepseek-ai/dsh-scope": "0.2.
|
|
75
|
-
"@deepseek-ai/dsh-session": "0.2.
|
|
76
|
-
"@deepseek-ai/dsh-session-projection": "0.2.
|
|
77
|
-
"@deepseek-ai/dsh-settings": "0.2.
|
|
78
|
-
"@deepseek-ai/dsh-shell": "0.2.
|
|
79
|
-
"@deepseek-ai/dsh-subprocess": "0.2.
|
|
80
|
-
"@deepseek-ai/dsh-system-prompt": "0.2.
|
|
81
|
-
"@deepseek-ai/dsh-tools": "0.2.
|
|
82
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.
|
|
83
|
-
"@deepseek-ai/dsh-user-approval": "0.2.
|
|
84
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
85
|
-
"@
|
|
59
|
+
"@deepseek-ai/cordis": "4.0.6-alpha.1",
|
|
60
|
+
"@deepseek-ai/dsh-agent": "0.2.2-alpha.2",
|
|
61
|
+
"@deepseek-ai/dsh-api-remotes": "0.2.2-alpha.2",
|
|
62
|
+
"@deepseek-ai/dsh-atomic-write": "0.2.2-alpha.2",
|
|
63
|
+
"@deepseek-ai/dsh-client-locale": "0.2.2-alpha.2",
|
|
64
|
+
"@deepseek-ai/dsh-client-store": "0.2.2-alpha.2",
|
|
65
|
+
"@deepseek-ai/dsh-client-ui-layout": "0.2.2-alpha.2",
|
|
66
|
+
"@deepseek-ai/dsh-client-ui-plugin-manager": "0.2.2-alpha.2",
|
|
67
|
+
"@deepseek-ai/dsh-client-ui-renderer": "0.2.2-alpha.2",
|
|
68
|
+
"@deepseek-ai/dsh-client-ui-settings": "0.2.2-alpha.2",
|
|
69
|
+
"@deepseek-ai/dsh-client-ui-slots": "0.2.2-alpha.2",
|
|
70
|
+
"@deepseek-ai/dsh-credentials": "0.2.2-alpha.2",
|
|
71
|
+
"@deepseek-ai/dsh-home-paths": "0.2.2-alpha.2",
|
|
72
|
+
"@deepseek-ai/dsh-host-webserver": "0.2.2-alpha.2",
|
|
73
|
+
"@deepseek-ai/dsh-jobs": "0.2.2-alpha.2",
|
|
74
|
+
"@deepseek-ai/dsh-jobs-local": "0.2.2-alpha.2",
|
|
75
|
+
"@deepseek-ai/dsh-output-retention": "0.2.2-alpha.2",
|
|
76
|
+
"@deepseek-ai/dsh-package-manifest": "0.2.2-alpha.2",
|
|
77
|
+
"@deepseek-ai/dsh-ptc-runtime": "0.2.2-alpha.2",
|
|
78
|
+
"@deepseek-ai/dsh-sandbox": "0.2.2-alpha.2",
|
|
79
|
+
"@deepseek-ai/dsh-scope": "0.2.2-alpha.2",
|
|
80
|
+
"@deepseek-ai/dsh-session": "0.2.2-alpha.2",
|
|
81
|
+
"@deepseek-ai/dsh-session-projection": "0.2.2-alpha.2",
|
|
82
|
+
"@deepseek-ai/dsh-settings": "0.2.2-alpha.2",
|
|
83
|
+
"@deepseek-ai/dsh-shell": "0.2.2-alpha.2",
|
|
84
|
+
"@deepseek-ai/dsh-subprocess": "0.2.2-alpha.2",
|
|
85
|
+
"@deepseek-ai/dsh-system-prompt": "0.2.2-alpha.2",
|
|
86
|
+
"@deepseek-ai/dsh-tools": "0.2.2-alpha.2",
|
|
87
|
+
"@deepseek-ai/dsh-typert-protocol": "0.2.2-alpha.2",
|
|
88
|
+
"@deepseek-ai/dsh-user-approval": "0.2.2-alpha.2",
|
|
89
|
+
"@deepseek-ai/schemastery": "3.18.6-alpha.1",
|
|
90
|
+
"@e18e/eslint-plugin": "0.8.1",
|
|
91
|
+
"@types/node": "26.6.6",
|
|
86
92
|
"@types/react": "19.3.0",
|
|
87
93
|
"@vitest/coverage-v8": "5.0.5",
|
|
94
|
+
"eslint-plugin-regexp": "3.3.2",
|
|
88
95
|
"eslint-plugin-sonarjs": "4.2.2",
|
|
89
|
-
"
|
|
90
|
-
"
|
|
96
|
+
"eslint-plugin-zod": "5.2.2",
|
|
97
|
+
"oxfmt": "0.75.0",
|
|
98
|
+
"oxlint": "1.90.0",
|
|
91
99
|
"oxlint-tsgolint": "7.0.2005",
|
|
92
100
|
"react": "^19.3.0",
|
|
93
101
|
"rolldown": "1.2.15",
|
|
@@ -96,11 +104,11 @@
|
|
|
96
104
|
"vitest": "5.0.5"
|
|
97
105
|
},
|
|
98
106
|
"peerDependencies": {
|
|
99
|
-
"@deepseek-ai/dsh-atomic-write": "0.2.1-rc.3",
|
|
100
|
-
"@deepseek-ai/dsh-credentials": "0.2.1-rc.3",
|
|
101
|
-
"@deepseek-ai/dsh-home-paths": "0.2.1-rc.3",
|
|
102
|
-
"@deepseek-ai/dsh-typert-protocol": "0.2.1-rc.3",
|
|
103
|
-
"@deepseek-ai/schemastery": "3.18.
|
|
107
|
+
"@deepseek-ai/dsh-atomic-write": "^0.2.1-rc.3",
|
|
108
|
+
"@deepseek-ai/dsh-credentials": "^0.2.1-rc.3",
|
|
109
|
+
"@deepseek-ai/dsh-home-paths": "^0.2.1-rc.3",
|
|
110
|
+
"@deepseek-ai/dsh-typert-protocol": "^0.2.1-rc.3",
|
|
111
|
+
"@deepseek-ai/schemastery": "3.18.6-alpha.1"
|
|
104
112
|
},
|
|
105
113
|
"icon": "icon.svg",
|
|
106
114
|
"engines": {
|