@y9931666/dsh-plugin-crossfire 0.1.0 → 0.1.3

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 (38) hide show
  1. package/README.md +2 -0
  2. package/lib/client.js +637 -204
  3. package/lib/index.js +339 -165
  4. package/lib/{state-CXNvzj_T.js → state-B2anbn4l.js} +2 -2
  5. package/lib/{state-DzrgVdrB.js → state-DvF_Jqcg.js} +13 -1
  6. package/package.json +56 -20
  7. package/release-notes/v0.1.1.md +58 -0
  8. package/release-notes/v0.1.2.md +44 -0
  9. package/release-notes/v0.1.3.md +21 -0
  10. package/lib/types/budget.d.ts +0 -86
  11. package/lib/types/client/ReviewCard.d.ts +0 -41
  12. package/lib/types/client/SettingsView.d.ts +0 -34
  13. package/lib/types/client/brand.d.ts +0 -55
  14. package/lib/types/client/index.d.ts +0 -21
  15. package/lib/types/client/locales.d.ts +0 -19
  16. package/lib/types/client/merge.d.ts +0 -27
  17. package/lib/types/client/slot-boundary.d.ts +0 -46
  18. package/lib/types/client/wire.d.ts +0 -192
  19. package/lib/types/dimensions.d.ts +0 -47
  20. package/lib/types/engine.d.ts +0 -153
  21. package/lib/types/etag.d.ts +0 -40
  22. package/lib/types/expert-events.d.ts +0 -110
  23. package/lib/types/export-md.d.ts +0 -21
  24. package/lib/types/harness-compat.d.ts +0 -104
  25. package/lib/types/index.d.ts +0 -76
  26. package/lib/types/members.d.ts +0 -76
  27. package/lib/types/noise.d.ts +0 -29
  28. package/lib/types/preferences.d.ts +0 -134
  29. package/lib/types/prompt.d.ts +0 -16
  30. package/lib/types/redteam.d.ts +0 -115
  31. package/lib/types/review.d.ts +0 -144
  32. package/lib/types/rpc.d.ts +0 -176
  33. package/lib/types/split.d.ts +0 -142
  34. package/lib/types/state.d.ts +0 -74
  35. package/lib/types/tools.d.ts +0 -51
  36. package/lib/types/types.d.ts +0 -287
  37. package/lib/types/version.d.ts +0 -18
  38. package/lib/types/web-guard.d.ts +0 -55
@@ -1,3 +1,3 @@
1
- import { a as newReviewId, c as reviewDirOf, d as withCrossfireLock, f as writeJsonAtomic, i as listReviewIds, l as reviewLockKey, n as appendJsonLine, o as readJsonFile, p as writeTextAtomic, r as crossfireRootOf, s as readJsonLines, t as CROSSFIRE_NAMESPACE, u as stateRootOf } from "./state-DzrgVdrB.js";
1
+ import { a as newReviewId, c as reviewDirOf, d as withCrossfireLock, f as writeJsonAtomic, i as listReviewIds, l as reviewLockKey, m as writeTextAtomicWithBom, n as appendJsonLine, o as readJsonFile, p as writeTextAtomic, r as crossfireRootOf, s as readJsonLines, t as CROSSFIRE_NAMESPACE, u as stateRootOf } from "./state-DvF_Jqcg.js";
2
2
 
3
- export { CROSSFIRE_NAMESPACE, appendJsonLine, crossfireRootOf, listReviewIds, newReviewId, readJsonFile, readJsonLines, reviewDirOf, reviewLockKey, stateRootOf, withCrossfireLock, writeJsonAtomic, writeTextAtomic };
3
+ export { CROSSFIRE_NAMESPACE, appendJsonLine, crossfireRootOf, listReviewIds, newReviewId, readJsonFile, readJsonLines, reviewDirOf, reviewLockKey, stateRootOf, withCrossfireLock, writeJsonAtomic, writeTextAtomic, writeTextAtomicWithBom };
@@ -71,6 +71,18 @@ async function writeTextAtomic(file, text) {
71
71
  await writeFile(tmp, text, "utf8");
72
72
  await rename(tmp, file);
73
73
  }
74
+ /**
75
+ * 原子写一份**带 UTF-8 BOM** 的文本,供人用记事本直接打开。
76
+ *
77
+ * 为什么单独一个函数:Windows 记事本对"无 BOM 的 UTF-8"时常误判成 ANSI,中文变乱码。
78
+ * 而 `.md` 扩展名在不少机器上被别的程序抢走,双击进不了记事本。所以导出时额外写一份
79
+ * `.txt`(带 BOM)—— 用户反馈"导出来的产物我无法用记事本查看"就是这个原因。
80
+ *
81
+ * BOM 只加在这份人读副本上:`.md` 保持无 BOM,交给 Markdown 渲染器更干净。
82
+ */
83
+ async function writeTextAtomicWithBom(file, text) {
84
+ await writeTextAtomic(file, `\uFEFF${text}`);
85
+ }
74
86
  /** Atomically publish a JSON file (same-directory tmp + rename). */
75
87
  async function writeJsonAtomic(file, value) {
76
88
  await writeTextAtomic(file, JSON.stringify(value, null, 2));
@@ -135,4 +147,4 @@ async function listReviewIds(stateRoot) {
135
147
  }
136
148
 
137
149
  //#endregion
138
- export { newReviewId as a, reviewDirOf as c, withCrossfireLock as d, writeJsonAtomic as f, listReviewIds as i, reviewLockKey as l, appendJsonLine as n, readJsonFile as o, writeTextAtomic as p, crossfireRootOf as r, readJsonLines as s, CROSSFIRE_NAMESPACE as t, stateRootOf as u };
150
+ export { newReviewId as a, reviewDirOf as c, withCrossfireLock as d, writeJsonAtomic as f, listReviewIds as i, reviewLockKey as l, writeTextAtomicWithBom as m, appendJsonLine as n, readJsonFile as o, writeTextAtomic as p, crossfireRootOf as r, readJsonLines as s, CROSSFIRE_NAMESPACE as t, stateRootOf as u };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@y9931666/dsh-plugin-crossfire",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "publishConfig": {
5
5
  "access": "public",
6
6
  "registry": "https://registry.npmjs.org/"
@@ -68,7 +68,7 @@
68
68
  },
69
69
  "scripts": {
70
70
  "typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit",
71
- "test": "node --test test/",
71
+ "test": "node --test --test-isolation=none \"test/*.test.mjs\"",
72
72
  "test:inline": "node --test --test-isolation=none \"test/*.test.mjs\"",
73
73
  "build": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.client.json --noEmit && tsdown -c tsdown.config.ts && tsc -p tsconfig.json --emitDeclarationOnly && tsc -p tsconfig.client.json --emitDeclarationOnly",
74
74
  "bundle:client": "tsdown -c tsdown.config.ts"
@@ -94,24 +94,60 @@
94
94
  "react": "^18.2.0"
95
95
  },
96
96
  "peerDependenciesMeta": {
97
- "@deepseek-ai/cordis": { "optional": true },
98
- "@deepseek-ai/schemastery": { "optional": true },
99
- "@deepseek-ai/dsh-agent": { "optional": true },
100
- "@deepseek-ai/dsh-atomic-write": { "optional": true },
101
- "@deepseek-ai/dsh-client-connection": { "optional": true },
102
- "@deepseek-ai/dsh-client-locale": { "optional": true },
103
- "@deepseek-ai/dsh-client-ui-conversation": { "optional": true },
104
- "@deepseek-ai/dsh-client-ui-renderer": { "optional": true },
105
- "@deepseek-ai/dsh-client-ui-settings": { "optional": true },
106
- "@deepseek-ai/dsh-client-ui-tool": { "optional": true },
107
- "@deepseek-ai/dsh-host-webserver": { "optional": true },
108
- "@deepseek-ai/dsh-llm": { "optional": true },
109
- "@deepseek-ai/dsh-session": { "optional": true },
110
- "@deepseek-ai/dsh-subagent": { "optional": true },
111
- "@deepseek-ai/dsh-system-prompt": { "optional": true },
112
- "@deepseek-ai/dsh-tools": { "optional": true },
113
- "@deepseek-ai/dsh-workspace": { "optional": true },
114
- "react": { "optional": true }
97
+ "@deepseek-ai/cordis": {
98
+ "optional": true
99
+ },
100
+ "@deepseek-ai/schemastery": {
101
+ "optional": true
102
+ },
103
+ "@deepseek-ai/dsh-agent": {
104
+ "optional": true
105
+ },
106
+ "@deepseek-ai/dsh-atomic-write": {
107
+ "optional": true
108
+ },
109
+ "@deepseek-ai/dsh-client-connection": {
110
+ "optional": true
111
+ },
112
+ "@deepseek-ai/dsh-client-locale": {
113
+ "optional": true
114
+ },
115
+ "@deepseek-ai/dsh-client-ui-conversation": {
116
+ "optional": true
117
+ },
118
+ "@deepseek-ai/dsh-client-ui-renderer": {
119
+ "optional": true
120
+ },
121
+ "@deepseek-ai/dsh-client-ui-settings": {
122
+ "optional": true
123
+ },
124
+ "@deepseek-ai/dsh-client-ui-tool": {
125
+ "optional": true
126
+ },
127
+ "@deepseek-ai/dsh-host-webserver": {
128
+ "optional": true
129
+ },
130
+ "@deepseek-ai/dsh-llm": {
131
+ "optional": true
132
+ },
133
+ "@deepseek-ai/dsh-session": {
134
+ "optional": true
135
+ },
136
+ "@deepseek-ai/dsh-subagent": {
137
+ "optional": true
138
+ },
139
+ "@deepseek-ai/dsh-system-prompt": {
140
+ "optional": true
141
+ },
142
+ "@deepseek-ai/dsh-tools": {
143
+ "optional": true
144
+ },
145
+ "@deepseek-ai/dsh-workspace": {
146
+ "optional": true
147
+ },
148
+ "react": {
149
+ "optional": true
150
+ }
115
151
  },
116
152
  "devDependencies": {
117
153
  "@deepseek-ai/cordis": "4.0.4",
@@ -0,0 +1,58 @@
1
+ # 针锋 Crossfire v0.1.1
2
+
3
+ 修复版。全部改动来自**用针锋自己审自己**的两轮红队评审(GPT-5.6-Terra 与默认路由各一轮),
4
+ 以及评审过程中暴露出来的一个真实可用性缺陷。
5
+
6
+ ## 修了什么
7
+
8
+ ### 1. 设置页**完全无法保存**(最严重)
9
+
10
+ **症状**:角色预设、阵容预设、默认强度、预算**全部存不进去**,界面显示「保存失败」。
11
+
12
+ **根因**(不是 UI 问题,是身份问题):宿主的 slot 契约里,`settings.section` 的
13
+ `standardProps` **不含 `sessionId`**(只有 `conversation.view` / `tool.call.toolview` 含),
14
+ 而 `crossfire/prefs.set` 在宿主侧要求"必须声称一个身份"。于是卡片里能保存、设置页必然失败。
15
+
16
+ **修法**:新增 `src/client/session-cache.ts`,把会话标签页/轨迹卡片从宿主注入里拿到的**真实**
17
+ 身份幂等记下来,设置页提交时声明同一个值。缓存为空时**绝不编造身份**(宁可让宿主按规则拒绝)。
18
+
19
+ ### 2. 「打穿立场」链路从未生效
20
+
21
+ **症状**:专家在发言里明明写了"针对立场:打穿",但每条缺陷的 `strikesStance` 都是 `false` ——
22
+ 卡片上的「打穿立场」标签和"打穿优先排序"是死的。
23
+
24
+ **根因**:`split.ts` 的 `splitUtterance()` 被要求判断"是否打穿立场",但它的参数里
25
+ **根本没有立场文本**,提示词只能靠正文里的字面标记去猜。
26
+
27
+ **修法**:立场全文随拆分请求下发;立场为空时在提示词里明说"未填写立场、不得标打穿";
28
+ 另加一道本地兜底,在 LLM 拆分失败退到启发式时仍能识别「针对立场:打穿」
29
+ (并特意区分「**未**打穿」,不让子串误匹配)。
30
+
31
+ ### 3. 视觉与缓存
32
+
33
+ - **状态药丸四态同色**:顶部状态区的药丸底色此前恒为品牌蓝,而抬头徽章是四态四色,
34
+ 于是同一句「攻击中」在同一屏以黄色与蓝色各出现一次。现在药丸与徽章共用同一套主题令牌。
35
+ - **折叠态标题溢出**:标题行此前没有单行约束,最长 80 字的标题会把折叠体撑成 2–4 行。
36
+ 现在 `nowrap + ellipsis + overflow-wrap: anywhere`。
37
+ - **客户端废掉宿主的 ETag/304**:`fetchReviews` 用了 `cache: 'no-store'`,浏览器因此
38
+ 永不回传 `If-None-Match`,每 2 秒一次全量读盘 + 全量传输。改为 `no-cache`
39
+ (语义仍是"每次回源校验",但允许保存副本)。
40
+
41
+ ## 版本号
42
+
43
+ `package.json` 与 `src/version.ts` 必须逐字一致(`test/version.test.mjs` 强制)。
44
+
45
+ ## 安装
46
+
47
+ ```sh
48
+ dsh plugin --profile web add @y9931666/dsh-plugin-crossfire
49
+ ```
50
+
51
+ 安装后**重启 Harness 进程**并刷新浏览器。要求 DeepSeek Harness `0.2.0-rc.2+`。
52
+
53
+ ## 已知限制(未变)
54
+
55
+ - 状态锁仍是**进程内**的:多窗口同时操作同一场评审时,后写者覆盖前者。
56
+ - 设置页的身份来自浏览器侧缓存:**冷启动后直接打开设置页**、且本次会话从未渲染过
57
+ 「针锋」标签页或任意一张卡片时,保存仍会被宿主拒绝(这是刻意的 —— 插件不伪造身份)。
58
+ 看一眼「针锋」标签页再保存即可。
@@ -0,0 +1,44 @@
1
+ # 针锋 Crossfire v0.1.2
2
+
3
+ **观点标题压缩版。** 只动展示逻辑,不碰红队协议、不碰提示词。
4
+
5
+ ## 修了什么
6
+
7
+ ### 观点标题过长且含无用信息(用户实测反馈)
8
+
9
+ **症状**:折叠态的标题一行显示不下,后面被截断;而且标题里塞了大段路径。
10
+
11
+ 用户原话:"标题过长,导致后面无法显示;其次这个标题有很多无用的信息,就比如说路径的位置。"
12
+
13
+ **根因不在提示词、也不在红队。** 红队的报告信封格式是 `观点 N(指向 X,维度Y)`,其中 `X`
14
+ 是**完整路径**(如 `dsh-plugin-crossfire/src/client/ReviewCard.module.css:106-176`)——
15
+ 红队写完整路径是**对的**,审计时要能定位到文件。问题出在展示层:
16
+ `defectTitle()` 把整个前缀**原样当标题**返回,40 多字符的路径直接把 80 字上限吃满。
17
+
18
+ **修法**:标题压缩成 `观点 N · 文件名:行号 · 维度`。
19
+
20
+ | | 改前 | 改后 |
21
+ | --- | --- | --- |
22
+ | 标题 | `观点 2(指向 dsh-plugin-crossfire/src/client/ReviewCard.module.css:106-176,维度 交…` | `观点 2 · ReviewCard.module.css:106-176 · 交互` |
23
+
24
+ 三条刻意的设计:
25
+
26
+ 1. **完整路径仍然留在缺陷正文里**(展开即可看到)—— 可追溯性一点没丢,只是不再占标题。
27
+ 2. **行号保留**(`ReviewCard.module.css:106-176`),因为它是"跳到哪一行"的关键信息。
28
+ 3. **形状不认识时原样退回**(例如旧格式 `观点 2 (指向 y)`、或括号里缺 `维度`)——
29
+ 不硬拼一个残缺标题。
30
+
31
+ Windows 反斜杠路径(`src\client\wire.ts:205-215`)同样压成文件名。
32
+
33
+ ## 新增断言
34
+
35
+ `test/review-card.test.mjs` 增补 5 条,覆盖:完整路径压缩、反斜杠路径、裸文件名、
36
+ "形状不认识时退回原前缀"、以及"压缩结果必须显著短于原始长信封且仍受 80 字上限约束"。
37
+
38
+ ## 安装
39
+
40
+ ```sh
41
+ dsh plugin --profile web add @y9931666/dsh-plugin-crossfire
42
+ ```
43
+
44
+ 装后**重启 DeepSeek Harness 进程**并刷新浏览器。要求 DeepSeek Harness `0.2.0-rc.2+`、Node `>= 22`。
@@ -0,0 +1,21 @@
1
+ # v0.1.3 — 界面视觉优化
2
+
3
+ 本版只改界面,不改任何工具语义、状态机与数据格式。
4
+
5
+ ## 评审卡片
6
+
7
+ - **状态色条**:每条缺陷左侧 3px 竖条按表态着色(认定=绿 / 驳回=红 / 待定=灰)。一条评审常有十几条缺陷,此前只能靠底部按钮描边逐条辨认状态,现在分布一眼可见。
8
+ - **间距与规格**:缺陷卡间距 4px → 8px,内边距 10→12,圆角 8→10,正文行高 1.65。
9
+ - **三态按钮**:统一 28px 高,不再随内边距漂移。
10
+ - **折叠标题**:信封压缩失败时退成「观点 N · 核心句」,不再把完整定位串(`F:\…\ReviewCard.tsx:154-…`)塞进标题;完整路径仍保留在展开后的正文里。
11
+ - **状态区文案**:「待定 9 / 表态 9」→「待定 9 / 共 9 条」,与左侧「待裁决」药丸不再重复。
12
+
13
+ ## 设置页
14
+
15
+ - 区块改为**卡片分组**(边框 + 12px 圆角 + 浅底 + 16px 内边距),首个标题区块不套卡片。
16
+ - 控件统一:输入 32px 高 / 8px 圆角 / 320 宽;分段按钮从连体改为有间隙的胶囊组。
17
+ - 新增长度为 16×16 的内联 SVG 图标(`src/client/icons.tsx`):零依赖、`currentColor` 随主题变色。
18
+
19
+ ## 测试
20
+
21
+ - `test/review-card.test.mjs` 更新标题兜底断言;全量 226 项通过。
@@ -1,86 +0,0 @@
1
- /**
2
- * Crossfire 预算:一条**熔断线**,不是一台状态机。
3
- *
4
- * ## 为什么刻意没有 active / muted / ended
5
- *
6
- * 圆桌的预算有三态与"闭麦":一次会议是持续存在的对象,预算耗尽只是把它**调成
7
- * 静音**,之后还能靠 `roundtable_set_budget` 补预算复活。针锋没有多轮辩论,
8
- * 预算的角色因此完全不同 —— 它唯一的作用是**在超限的那一刻把后续工作停下来并
9
- * 报错**。停下不改变任何评审状态:`status` 由 `review.ts` 独占管理,本模块
10
- * **一行都不写 `record.status`**(这是设计边界,不是疏忽)。
11
- *
12
- * ## 两条判据(与设置页「0 = 不限制」严格一致)
13
- *
14
- * - `rounds`:`usedRounds > maxRounds`。设 N 轮就能开满 N 轮,只有想开第 N+1 轮
15
- * 才触顶 —— `>=` 会让 `maxRounds = 10` 实际只能开 9 轮,对面向用户的设置项
16
- * 不直觉(圆桌已经踩过这个坑,注释里写明了原因,这里沿用同一口径)。
17
- * - `tokens`:`usedTokens >= maxTokens`,且仅当 `maxTokens > 0` 时才判。
18
- * - 两条轴的 `max*` 为 `0` 时该轴**不限制**(设置页对这四个数字字段都写着
19
- * 「0 = 不限制」,实现必须与文案一致,否则用户关掉限制反而被立刻熔断)。
20
- *
21
- * ## 调用方契约
22
- *
23
- * `chargeTokens` / `beginPass` 都是**原地修改** `record` 的纯计算,不落盘 ——
24
- * 落盘由调用方负责(状态写入统一走 `state.ts` 的原子写 + 进程内锁)。
25
- *
26
- * @module dsh-plugin-crossfire/budget
27
- */
28
- import type { CrossfireRecord } from './types.ts';
29
- /**
30
- * 估算文本的 token 成本(粗估:CJK 0.6/字,其它非空白 0.3/字符,向上取整)。
31
- *
32
- * 口径与圆桌 `budget.ts` 的 `estimateTokens` **完全一致**(逐字符 `for...of`
33
- * 按码点遍历,`code >= 0x2e80` 记 CJK,空白不计入 other,最后 `Math.ceil`)。
34
- * 这是刻意照搬而不是重新设计:两个插件对同一段中文给出的预算数字必须可比。
35
- *
36
- * 再次强调:这是一个**已说出的文本长度估算**,不是模型真实用量。
37
- */
38
- export declare function estimateTokens(text: string): number;
39
- /**
40
- * 哪条预算轴被突破(无则 `undefined`)。
41
- *
42
- * 语义(与圆桌一致,见文件头):两条轴的 `max*` 为 `0` 表示该轴不限制。
43
- * 返回的是**轴名**,不是布尔 —— 调用方需要把它透出给用户,让"是哪条线断了"
44
- * 可诊断,而不是笼统的一句"预算用完了"。
45
- */
46
- export declare function budgetExceeded(record: CrossfireRecord): 'rounds' | 'tokens' | undefined;
47
- /**
48
- * 预算熔断错误。
49
- *
50
- * 不是"会议已结束",也不是"会议被静音":针锋只有这一种停止原因,且**可恢复**
51
- * —— 用户把设置里的上限调大即可继续(状态从未被改动,所以不存在"复活"一说)。
52
- */
53
- export declare class CrossfireMutedError extends Error {
54
- readonly axis: 'rounds' | 'tokens';
55
- constructor(reviewId: string, axis: 'rounds' | 'tokens');
56
- }
57
- /**
58
- * 熔断门禁:超限即抛 {@link CrossfireMutedError},否则什么都不做。
59
- *
60
- * **不修改 `record`**(与圆桌 `markMuted` 的本质区别):针锋不把"超预算"写成
61
- * 一个持久状态,因为那个状态会反过来挡住用户自救的路径 —— 圆桌的真实事故就是
62
- * `ensureActive` 先改状态再抛错,导致连补预算/导出都被同一个门禁拒绝,花完预算
63
- * 的会议永远卡在 `active` 却什么都做不了。这里只抛、不改,问题无从被固化。
64
- *
65
- * @param record - 被检查的评审记录。
66
- * @param options.allowMuted - 收尾路径(导出、表态、定稿)必须保持可达,
67
- * 否则用户连"把已经打出来的缺陷看完并裁决"都做不了;置 `true` 时直接放行。
68
- */
69
- export declare function assertBudget(record: CrossfireRecord, options?: {
70
- allowMuted?: boolean;
71
- }): void;
72
- /**
73
- * 把一段专家输出计入 token 用量(**原地修改** `record`,由调用方负责落盘)。
74
- *
75
- * 只在文本非空时累加,避免空发言把 `usedTokens` 上一堆无意义的 `+0` 调用;
76
- * 估算口径交给 {@link estimateTokens},本函数不做任何额外加权。
77
- */
78
- export declare function chargeTokens(record: CrossfireRecord, text: string): void;
79
- /**
80
- * 推进一轮:`reviewPass += 1` 并同步 `budget.usedRounds`,返回新的轮次。
81
- *
82
- * 这里**不**做预算检查:门禁由调用方在动手之前调用 {@link assertBudget},因为
83
- * "先检查后推进"的顺序一旦反过来,第 N+1 轮的副作用就已经发生了。
84
- * `usedRounds` 与 `reviewPass` 必须同进同退,两者脱钩会让预算判据失去意义。
85
- */
86
- export declare function beginPass(record: CrossfireRecord): number;
@@ -1,41 +0,0 @@
1
- /**
2
- * 针锋的 Chat 卡片(注册在 `tool.call.toolview`,key = `crossfire_open`)。
3
- *
4
- * ## 为什么卡片只认领一个工具名
5
- *
6
- * 宿主的 toolview 是按 **wire 工具名** dispatch 的,一次工具调用渲染一张。如果给
7
- * 七个工具都注册,同一场评审会沿轨迹长出七张一模一样的卡。所以只认领
8
- * `crossfire_open`(卡片第一次出现的位置),后续的 attack / collect / finish
9
- * 调用渲染成通用工具行;而卡片本身**每秒轮询快照**,内容自动跟着状态机走。
10
- *
11
- * ## 四态与直推(O1)
12
- *
13
- * `draft → attacking → judging → settled`,卡片按当前态只给出**下一步**那一个按钮,
14
- * 点击即经 RPC 直推 host 状态机 —— 不必等一个模型回合。这正是针锋比圆桌简洁的
15
- * 地方。
16
- *
17
- * ## 一条不可越界的规则
18
- *
19
- * 表态(认定 / 驳回 / 待定)只能在这里发生。卡片是唯一入口,因为**没有对应的
20
- * agent 工具**,也永远不该有。
21
- *
22
- * @module dsh-plugin-crossfire/client/ReviewCard
23
- */
24
- import { type ReactElement } from 'react';
25
- import { type RpcCaller } from './wire.ts';
26
- /**
27
- * 卡片收到的注入面(`tool.call.toolview` 不保证给 inject,因此组件对它容错)。
28
- *
29
- * `sessionId` **不是**我们注入的:它是宿主给每个 slot 组件的**标准注入** ——
30
- * 运行时 `Slots.listSubTree` 在 `tool.call.toolview` 的 `standardProps` 里列出的
31
- * 正是 `sessionId: SessionId`(已核实,不是推断)。圆桌的 tab 组件同样从 props 取它。
32
- * 组件用 `Partial<>` 同时接住两类来源。
33
- */
34
- export interface CrossfireCardInjected {
35
- rpc: RpcCaller;
36
- t: (key: string) => string;
37
- /** 当前会话 id(宿主标准注入提供;拿不到时组件退化为空串)。 */
38
- sessionId?: string;
39
- }
40
- /** 轮询间隔。与圆桌一致:本机 HTTP,1 秒足够跟手。 */
41
- export declare function CrossfireCard(props: Partial<CrossfireCardInjected>): ReactElement;
@@ -1,34 +0,0 @@
1
- /**
2
- * 针锋的设置页(`settings.section`,id `crossfire`)。
3
- *
4
- * ## 覆盖范围
5
- *
6
- * 五块:默认强度、维度池(固定池展示 + 自定义增删)、预算、**角色预设**、
7
- * **阵容预设**,末尾是四条边界说明与 locale 侧的批次批注。
8
- *
9
- * 角色预设与阵容预设是**纯本地偏好**(`crossfire/preferences.json`):它们只
10
- * 回答"谁来"(key / 角色说明 / 模型路由),不描述"怎么开"——没有维度、没有流程。
11
- *
12
- * ## 三条实现约定
13
- *
14
- * - **选项类改动立即落库**:点击/枚举类操作直接提交 `crossfire/prefs.set`,
15
- * 成功后用服务端返回的 prefs 覆盖本地快照。
16
- * - **数字输入走"本地草稿 + 失焦提交"**:避免每敲一个字符就写一次盘;四个数字
17
- * 字段都遵循 `0 = 不限制`。
18
- * - **渲染只读快照**:服务端返回的 prefs 是唯一真相,本地不做长驻乐观状态,
19
- * 否则一次失败的保存会让界面显示一个并不存在的值。唯一的例外是正在编辑的
20
- * 草稿(还没提交),以及"整条条目文本/路由原样就是旧值"时的本地重试。
21
- *
22
- * 长度与数量上限在 host `preferences.ts` 里重新净化一次,本页只是提前拦截并给出
23
- * 即时反馈。
24
- *
25
- * @module dsh-plugin-crossfire/client/SettingsView
26
- */
27
- import { type ReactElement } from 'react';
28
- import type { RpcCaller } from './wire.ts';
29
- /** slot entry 收到的注入面(见 client/index.ts)。 */
30
- export interface CrossfireSettingsInjected {
31
- rpc: RpcCaller;
32
- t: (key: string) => string;
33
- }
34
- export declare function CrossfireSettings(props: CrossfireSettingsInjected): ReactElement;
@@ -1,55 +0,0 @@
1
- /**
2
- * Provider 品牌标识(彩色圆角块 + 缩写)—— 零资源依赖。
3
- *
4
- * ## 为什么不用真 logo
5
- *
6
- * 圆桌会议的做法是 `PROVIDER_BRAND`(正则 → key)+ `brand-logos.generated.ts`
7
- * (**330 KB 的 base64 图片**)。本插件刻意**不复制那个文件**:它会把包体积撑大
8
- * 约三倍,而收益只是"块里换成图片"。这里只取同一份方案的**缩写 + 品牌色**:
9
- * 品牌色圆角块 + 2–3 字母缩写,代价为零。
10
- *
11
- * ## 一条必须守住的边界
12
- *
13
- * 品牌色是**第三方商标色**,不是主题令牌 —— 主题令牌无法表达"DeepSeek 是蓝的"。
14
- * 因此它只用于**小面积的身份标记**(头像块),文字一律保持 `currentColor`:
15
- * 浅色/深色主题下可读性由底色与白字的对比度保证,与主题无关。
16
- * 除此之外本模块不产生任何颜色。
17
- *
18
- * 与圆桌 `RoundTableView.tsx` 的 `PROVIDER_BRAND` 保持同源,但**不共享代码**:
19
- * 两个插件各自独立演进,跨插件 import 会把一份 1888 行的客户端模块拖进本包的
20
- * 类型程序(`tsconfig.client.json` 只 include `src/client`)。
21
- *
22
- * @module dsh-plugin-crossfire/client/brand
23
- */
24
- /** 一位 provider(或一个模型路由)的展示用品牌标识。 */
25
- export interface Brand {
26
- /** 2–3 字母缩写(最长 3 位,保证块内不折行)。 */
27
- abbr: string;
28
- /** 品牌底色(圆角块背景);文字固定白色。 */
29
- color: string;
30
- }
31
- /**
32
- * provider 名 → 品牌标识。空 / 未识别时退化为中性块。
33
- *
34
- * 未识别的兜底与圆桌一致:取名称里的字母数字前两位大写;一个字符都没有时(例如
35
- * provider 为 `***`)用 `?`,宁可不说话也不编一个假缩写。
36
- */
37
- export declare function providerBrand(provider: string | undefined): Brand;
38
- /**
39
- * provider + model 的**路由标记**缩写。
40
- *
41
- * 刻意**只返回 provider 的缩写**:这个块画的是 provider 的品牌色,若里面写的是模型名
42
- * 的首字母(`deepseek-v4-flash` → `DEFL`),一个颜色块就同时表达了两个互相矛盾的身份
43
- * —— 用户会以为 `DEFL` 是某个 provider。块的语义必须单一:**底色与字母都只属于
44
- * provider**;模型是哪一档由块右边那行完整路由文本回答(`routeText`)。
45
- *
46
- * 参数收下 `model` 只为了在模型缺失时也能给出同一个标记(两者行为一致),
47
- * 保留它是为了让调用点读起来是"给一个路由",而不是"给一个 provider"。
48
- */
49
- export declare function routeAbbr(provider: string | undefined, model: string | undefined): string;
50
- /**
51
- * provider + model 的完整可读串(`deepseek-official / deepseek-v4-flash`)。
52
- *
53
- * 缩写块有 `title`,但 `title` 在触屏上读不到 —— 列表行因此同时给出完整路由文本。
54
- */
55
- export declare function routeText(provider: string | undefined, model: string | undefined): string;
@@ -1,21 +0,0 @@
1
- /**
2
- * dsh-plugin-crossfire — browser half.
3
- *
4
- * 批 1 只注册一个 entry:`settings.section`(id `crossfire`)—— 设置页。
5
- * 批 3 会加 `tool.call.toolview`(key = `crossfire_*` 工具名)承载轨迹内卡片;
6
- * `conversation.chat.node` **不可用**(宿主固定 key 表,插件加不了新 kind),
7
- * 这一点已实测,写在这里以免以后有人再试一次。
8
- *
9
- * ## inject 为什么只有两个名字
10
- *
11
- * cordis 的 `inject` 是**加载门禁**:列进去的服务必须存在,否则整个 client 插件
12
- * 保持 PENDING —— 表现是"设置页根本不存在"且没有任何报错。`connection` 因此
13
- * 刻意**不列**,改用 `ctx.get('connection')` 懒取(它就是下面 RPC 兜底传输需要
14
- * 的东西)。这是圆桌 v0.2.21 用一次真实故障换来的教训。
15
- *
16
- * @module dsh-plugin-crossfire/client
17
- */
18
- import type { Context } from '@deepseek-ai/cordis';
19
- /** Required services: the two slot registries plus locale (see module doc). */
20
- export declare const inject: string[];
21
- export declare function apply(ctx: Context): void;
@@ -1,19 +0,0 @@
1
- /**
2
- * Locale dictionaries for the Crossfire browser UI.
3
- *
4
- * 命名空间 `crossfire` 与圆桌会议的 `roundtable` **完全分开**:两个插件共享一个
5
- * 命名空间会让任意一方加键就撞上另一方。
6
- *
7
- * @module dsh-plugin-crossfire/client/locales
8
- */
9
- export declare const NS = "crossfire";
10
- /** 本插件用到的全部文案键。新增键必须同时补 zh 与 en,否则 Record 类型会报错。 */
11
- export type CrossfireKey = 'renderFailed' | 'renderFailedHint' | 'tab' | 'settingsNav' | 'settingsTitle' | 'settingsIntro' | 'boundaryTitle' | 'boundaryLine1' | 'boundaryLine2' | 'boundaryLine3' | 'boundaryLine4' | 'defaultIntensity' | 'intensityQuick' | 'intensityStandard' | 'intensityDeep' | 'intensityHint' | 'intensityPerDimension' | 'intensityCrossYes' | 'intensityCrossNo' | 'intensityRecheckCap' | 'dimensionsTitle' | 'dimensionsHint' | 'poolLabel' | 'customLabel' | 'customEmpty' | 'customPlaceholder' | 'customAdd' | 'customRemove' | 'customLimit' | 'customInvalid' | 'budgetTitle' | 'budgetHint' | 'budgetRounds' | 'budgetTokens' | 'expertMaxTokens' | 'expertMaxOpinions' | 'unlimited' | 'loading' | 'loadFailed' | 'saved' | 'saveFailed' | 'batchNote' | 'cardTitle' | 'statusDraft' | 'statusAttacking' | 'statusJudging' | 'statusSettled' | 'statusNext' | 'statusStrip' | 'statusHintDraft' | 'statusHintAttacking' | 'statusHintJudging' | 'statusHintSettled' | 'passBadge' | 'subjectLabel' | 'stanceLabel' | 'stanceEmpty' | 'dimensionsLabel' | 'expertsLabel' | 'expertPending' | 'expertRunning' | 'expertDone' | 'expertFailed' | 'expertSilent' | 'expertDefects' | 'expertReported' | 'expertSubmitted' | 'expertNoOutput' | 'impactTitle' | 'countEndorsed' | 'countRejected' | 'countPending' | 'defectEmpty' | 'defectsTitle' | 'evidenceRepro' | 'evidenceArgument' | 'strikesStance' | 'dimensionFallback' | 'recheckLabel' | 'recheckNote' | 'verdictFixed' | 'verdictUnfixed' | 'verdictPartial' | 'verdictUndecided' | 'recheckNoteNeedsVerdict' | 'btnAttack' | 'btnCollect' | 'btnFinish' | 'btnRecheck' | 'btnExport' | 'btnEndorse' | 'btnReject' | 'btnPending' | 'rejectPrompt' | 'rejectPlaceholder' | 'rejectConfirm' | 'rejectCancel' | 'busyWorking' | 'noReviews' | 'cardHint' | 'actionFailed' | 'cardFooterNote' | 'noSessionHint' | 'expand' | 'collapse' | 'expandSection' | 'collapseSection' | 'expertsTitle' | 'expertRosterHint' | 'expertRoleLabel' | 'expertRolePlaceholder' | 'expertRoleEdit' | 'expertRoleSaved' | 'expertRoleFromRoster' | 'stanceSectionTitle' | 'stanceExpand' | 'stanceCollapse' | 'explainDismiss' | 'recheckApprovalAsk' | 'recheckApprovalYes' | 'recheckApprovalNo' | 'settingsExplainTitle' | 'settingsExplainRestore' | 'settingsExplainNone' | 'settingsExplainCount' | 'stanceEdit' | 'stanceSave' | 'stanceSaved' | 'stanceHint' | 'exportDone' | 'exportFailed' | 'settingsPresetsTitle' | 'settingsPresetsHint' | 'settingsPresetsEmpty' | 'presetName' | 'presetRole' | 'presetProvider' | 'presetModel' | 'modelInherit' | 'presetAdd' | 'presetAddCount' | 'presetLimitInline' | 'modelRoute' | 'modelCatalogLoading' | 'modelCatalogFailed' | 'modelCatalogEmpty' | 'presetProviderNeeded' | 'presetSave' | 'presetCancel' | 'presetEdit' | 'presetDelete' | 'presetInvalid' | 'presetLimit' | 'presetSaved' | 'settingsSquadsTitle' | 'settingsSquadsHint' | 'settingsSquadsEmpty' | 'squadAdd' | 'squadAddCount' | 'squadName' | 'squadMembers' | 'squadMemberKey' | 'squadMemberRole' | 'squadAddMember' | 'squadRemoveMember' | 'squadSave' | 'squadCancel' | 'squadEdit' | 'squadDelete' | 'squadInvalid' | 'squadSaved' | 'squadLimit' | 'squadMemberCount';
12
- declare module '@deepseek-ai/dsh-client-ui-slots' {
13
- interface LocaleNamespaceMap {
14
- /** The crossfire settings page copy. */
15
- crossfire: CrossfireKey;
16
- }
17
- }
18
- export declare const zh: Record<CrossfireKey, string>;
19
- export declare const en: Record<CrossfireKey, string>;
@@ -1,27 +0,0 @@
1
- /**
2
- * 预设 / 阵容的列表合并规则(纯函数)。
3
- *
4
- * ## 为什么单独一个模块
5
- *
6
- * 它修的是一个**静默数据丢失**:设置页编辑既有条目后保存,界面报"已保存",改动却被
7
- * 丢掉。链路是
8
- * 客户端 append 同 id 条目(旧比较判据在编辑场景下必然走 append)
9
- * → host `sanitizeRolePresets` 按 id 去重、保留**第一个**(= 编辑前的旧值)
10
- * → 界面拿到回包里的旧值,用户的编辑无声消失。
11
- *
12
- * 症状与"保存失败"完全不同(没有任何错误提示),所以只有断言能钉住它。
13
- *
14
- * 放在独立的 `.ts` 里而不是 `SettingsView.tsx` 内:Node 的 type stripping 支持 `.ts`,
15
- * 但**不支持 `.tsx`**(JSX 无法剥离),所以测试只能 import `.ts` 模块。
16
- *
17
- * @module dsh-plugin-crossfire/client/merge
18
- */
19
- import type { WireRolePreset, WireRoleSquad } from './wire.ts';
20
- /**
21
- * 把一条预设并入数组:**同 id 就地替换,新 id 才追加**。
22
- *
23
- * 位置保持不变(编辑不应把条目挪到末尾,否则列表顺序会在每次编辑后跳动)。
24
- */
25
- export declare function mergePreset(list: readonly WireRolePreset[], preset: WireRolePreset): WireRolePreset[];
26
- /** 阵容版本:与 {@link mergePreset} 同一规则(同 id 就地替换,新 id 追加)。 */
27
- export declare function mergeSquad(list: readonly WireRoleSquad[], squad: WireRoleSquad): WireRoleSquad[];
@@ -1,46 +0,0 @@
1
- /**
2
- * Render-failure boundary for the Crossfire slot entries.
3
- *
4
- * WHY THIS EXISTS: DSH retires a slot entry that throws during render
5
- * (`onEntryError` + abdication) —— 隔离本身是对的,但对用户来说就是"针锋的
6
- * 设置页根本不存在",且任何地方都没有提示。那种静默消失正是最难从外部诊断的
7
- * 故障形态。
8
- *
9
- * 给每个 entry 套一层 boundary,就把"崩溃"变成一块写明错误的可读面板,同时把
10
- * 同样的信息(含组件栈)镜像到浏览器控制台。boundary 刻意不渲染自己的任何数据,
11
- * 因此它不可能成为第二个故障源;刷新后 React 一旦渲染出健康树,面板即被换掉。
12
- *
13
- * @module dsh-plugin-crossfire/client/slot-boundary
14
- */
15
- import { Component, type ComponentType, type ErrorInfo, type ReactNode } from 'react';
16
- /** The inject face every Crossfire slot entry receives (see client/index.ts). */
17
- export interface SlotEntryFace {
18
- /** Locale-bound translator for the `crossfire` namespace. */
19
- t?: (key: string) => string;
20
- }
21
- interface SlotBoundaryProps {
22
- title: string;
23
- hint: string;
24
- children?: ReactNode;
25
- }
26
- interface SlotBoundaryState {
27
- error: Error | null;
28
- }
29
- /** Class-component boundary (React only catches render errors in class components). */
30
- export declare class SlotBoundary extends Component<SlotBoundaryProps, SlotBoundaryState> {
31
- state: SlotBoundaryState;
32
- static getDerivedStateFromError(error: Error): SlotBoundaryState;
33
- componentDidCatch(error: Error, info: ErrorInfo): void;
34
- render(): ReactNode;
35
- }
36
- /**
37
- * Wrap one slot component so a render crash renders a report instead of
38
- * vanishing. The inner component keeps receiving every prop it got before
39
- * (owner share + standard seats + the inject face), so this is transparent on
40
- * the happy path.
41
- *
42
- * @param Inner - the registered slot component.
43
- * @returns a component with the same props contract.
44
- */
45
- export declare function withSlotBoundary<P extends object>(Inner: ComponentType<P>): ComponentType<P>;
46
- export {};