@y9931666/dsh-plugin-crossfire 0.1.1 → 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 +160 -19
  3. package/lib/index.js +1 -1
  4. package/package.json +1 -1
  5. package/release-notes/v0.1.2.md +44 -0
  6. package/release-notes/v0.1.3.md +21 -0
  7. package/lib/types/budget.d.ts +0 -86
  8. package/lib/types/client/ReviewCard.d.ts +0 -41
  9. package/lib/types/client/SettingsView.d.ts +0 -34
  10. package/lib/types/client/brand.d.ts +0 -55
  11. package/lib/types/client/defectTitle.d.ts +0 -23
  12. package/lib/types/client/index.d.ts +0 -21
  13. package/lib/types/client/locales.d.ts +0 -19
  14. package/lib/types/client/merge.d.ts +0 -27
  15. package/lib/types/client/promptView.d.ts +0 -63
  16. package/lib/types/client/session-cache.d.ts +0 -49
  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 -172
  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 -89
  26. package/lib/types/members.d.ts +0 -143
  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 -152
  32. package/lib/types/rpc.d.ts +0 -176
  33. package/lib/types/split.d.ts +0 -156
  34. package/lib/types/state.d.ts +0 -84
  35. package/lib/types/tools.d.ts +0 -59
  36. package/lib/types/types.d.ts +0 -308
  37. package/lib/types/version.d.ts +0 -18
  38. package/lib/types/web-guard.d.ts +0 -55
@@ -1,308 +0,0 @@
1
- /**
2
- * Crossfire(针锋)的领域类型。
3
- *
4
- * 设计边界(与圆桌会议刻意不同,务必守住):
5
- * - 没有 nodes / edges / topology —— 专家是行,不是图。
6
- * - 没有 charter 的多模式 —— 只有一种角色:红队。
7
- * - 没有 kb / skill / proxy-thinking 字段 —— 这个插件不读知识库、不传 skill。
8
- *
9
- * @module dsh-plugin-crossfire/types
10
- */
11
- /**
12
- * 固定维度池。
13
- *
14
- * 为什么必须固定:抽样 5 份真实圆桌 `review.json`,`dimension` 实值是
15
- * `维度②`(×5) / `维度④`(×1) / `其他`(×4) / 空串(×1) —— 只有 1 份给出了
16
- * 有意义的 `架构/产品/成本`。原因是 LLM 拆分经常失败并退到本地启发式兜底,
17
- * 而兜底把段首的圆圈数字直接当成了维度名。用户看到的因此是一堆编号。
18
- * 固定池 + 落库归一化是这件事的唯一可靠解法。
19
- */
20
- export declare const DIMENSION_POOL: readonly ["安全", "数据", "交互", "性能", "兼容", "流程", "反例"];
21
- /** 池内维度的字面值类型。 */
22
- export type PoolDimension = (typeof DIMENSION_POOL)[number];
23
- /** 一次评审实际采用的维度(池内值,或用户在设置页自建的自定义维度)。 */
24
- export type Dimension = string;
25
- /** 攻击强度:只影响专家数、额外轮次与复审上限,不改变界面结构。 */
26
- export type Intensity = 'quick' | 'standard' | 'deep';
27
- /** 强度的生效规则(单一来源,客户端与服务端共用同一张表)。 */
28
- export interface IntensitySpec {
29
- /** 每个维度拉几位专家。 */
30
- expertsPerDimension: number;
31
- /** 收集之后是否自动追加一轮交叉质证。 */
32
- crossExamination: boolean;
33
- /** 闭环复审上限(首轮 + 最多复审 maxReviewPass-1 次)。 */
34
- maxReviewPass: number;
35
- /** 界面上一句话说明。 */
36
- label: string;
37
- }
38
- /** 三档强度的完整定义。 */
39
- export declare const INTENSITY_SPECS: Readonly<Record<Intensity, IntensitySpec>>;
40
- /**
41
- * 评审状态机。
42
- *
43
- * draft → attacking → judging → settled
44
- * ↑ │
45
- * └──────────┘ recheck(复审:只核对旧账)
46
- *
47
- * 表态(认定/驳回)只允许发生在 `judging`;`settled` 之后要改结论必须走复审。
48
- */
49
- export type ReviewStatus = 'draft' | 'attacking' | 'judging' | 'settled';
50
- /** 缺陷三态:用户没有表态 / 认定是真缺陷 / 审阅后否定。三态可互切。 */
51
- export type DefectStatus = 'pending' | 'endorsed' | 'rejected';
52
- /** 复审核对结论(第二轮起,只针对上一轮已认定的缺陷)。 */
53
- export type RecheckVerdict = 'fixed' | 'unfixed' | 'partial';
54
- /**
55
- * 证据分级(C1)。
56
- *
57
- * `repro` = 可复现步骤(代码/bug 类);`argument` = 论证链(设计类)。
58
- * 这道分级是为了挡住最廉价的红队噪音:让设计类缺陷**不许**编造
59
- * "运行某命令即可复现"这类伪复现步骤。
60
- */
61
- export interface CrossfireEvidence {
62
- kind: 'repro' | 'argument';
63
- /** 证据正文:编号复现步骤,或论证链。≤600 字符。 */
64
- text: string;
65
- }
66
- /** 一条缺陷(可能是一条专家发言拆分而来)。 */
67
- export interface Defect {
68
- /** 行 id:`${utteranceId}#${seq}`(拆分后 seq≥1;整条未拆则 seq=0)。 */
69
- id: string;
70
- /** 来源发言 id(collect 的幂等键:同一发言只收集一次)。 */
71
- utteranceId: string;
72
- /** 提出该缺陷的专家 key。 */
73
- expertKey: string;
74
- /** 缺陷内容(单条,只含一个缺陷)。 */
75
- content: string;
76
- /** 维度标签。**必须**是本次评审采用的维度之一(见 normalizeDimension)。 */
77
- dimension: Dimension;
78
- /** 维度越界时置 true:表示落库时被归一化到最近的池内值。 */
79
- dimensionFallback?: boolean;
80
- /** 原文引用子串(≤120 字符,规范化后必须是发言原文的子串)。 */
81
- quote?: string;
82
- /** 证据分级。 */
83
- evidence?: CrossfireEvidence;
84
- /** 是否打穿了用户立场中的某条理由(R5)。 */
85
- strikesStance?: boolean;
86
- /** 三态。 */
87
- status: DefectStatus;
88
- /** 驳回理由(驳回必填;改回其它态时不清除,供追溯)。 */
89
- rejectReason?: string;
90
- /** 复审核对结果(第二轮起才有)。 */
91
- /**
92
- * 复审核对结论。
93
- *
94
- * `verdict` **刻意是可选的**,而且"缺席"是一个有意义的状态:**尚未判断**。
95
- *
96
- * 它此前是必填、且开轮时被机器写成 `'unfixed'`("未被证明修复之前都算没修"),
97
- * 于是复审轮一打开,每条核对项的三态按钮里"未修复"就已经是高亮选中态,而备注框
98
- * 旁那个"保存"按钮会把这个**机器默认值**当作人做的表态提交上去(红队评审抓出的
99
- * 真实缺陷)。那正好违背本插件最核心的边界:**结论只能由人下**。
100
- *
101
- * 现在的语义:
102
- * - `verdict` 缺席 = 人还没判断(按钮不高亮,且不允许借"保存备注"顺手落一个结论);
103
- * - 有值 = 人明确选过(含 `unfixed` —— 那才是"人判定为未修复")。
104
- */
105
- recheck?: {
106
- pass: number;
107
- verdict?: RecheckVerdict;
108
- note?: string;
109
- };
110
- ts: number;
111
- /** 发言内序号:0=整条未拆分;≥1=拆分出的第 N 条。 */
112
- seq: number;
113
- }
114
- /** 用户的立场与理由(针锋的"锋":红队必须逐条回应这里的每条理由)。 */
115
- export interface Stance {
116
- /** 正文。≤2000 字符。 */
117
- text: string;
118
- /** 来源:手写 / 自动提炼 / 提炼后手改。`edited` 之后不再被自动提炼覆盖。 */
119
- origin: 'manual' | 'distilled' | 'edited';
120
- distilledAt?: number;
121
- updatedAt: number;
122
- }
123
- /** 一位红队专家在本场评审里的状态。 */
124
- export interface RedTeamExpert {
125
- /** 专家 key(同时是子代理 label 的一部分)。 */
126
- key: string;
127
- /** 这位专家负责的维度。 */
128
- dimension: Dimension;
129
- /**
130
- * 宿主的子会话 id(`startContinuable` 的 `childId`)。
131
- *
132
- * 拉起失败时为空 —— 此时 `status` 必为 `failed` 且 `note` 有可读原因。
133
- * `expert-events.ts` 靠它把 `subagent/end` 事件认回是哪位专家。
134
- */
135
- childId?: string;
136
- provider?: string;
137
- model?: string;
138
- /**
139
- * 这位专家的**用户补充角色定位**(来自角色预设或阵容成员,可空)。
140
- *
141
- * 它被追加进 persona,用来把"安全维度的红队"细化成"熟悉密钥管理的那位红队"。
142
- * 它有两条硬边界,实现在 `redteam.ts`:
143
- * 1. 只能**补充专长**,不能覆盖九条硬约束(只读、不产出方案、不迎合等)——
144
- * 否则用户只要写一句"你可以修改文件",专家就能越过只读边界;
145
- * 2. 缺席时 persona 与从前逐字一致(老记录不受影响)。
146
- */
147
- role?: string;
148
- /**
149
- * 这位专家**真正下发出去的那份 persona 的逐字副本**。
150
- *
151
- * ## 为什么必须落盘(红队抓出的"原提示词"缺陷)
152
- *
153
- * persona 是每次开打时由 `expertPersona()` 现场重建的。攻击开始之后用户还能改
154
- * 专家的 role,用户也能改立场 —— 而**已经下发的那一份不会变**。于是"查看原提示词"
155
- * 端点若拿当前记录重建,返回的文本在任何"开打后改字段"的场景下都不是原文,
156
- * 却能以上下文自洽的样子显示出来:一句看起来精确、实际是伪造的"原文"。
157
- *
158
- * 所以这里落盘的是**同一次调用的返回值**(`members.ts` 里先提成局部常量,再同时
159
- * 用于下发与落盘),端点优先返回它,只有旧记录缺这个字段时才回退到现场重建。
160
- *
161
- * 缺席 = 这份记录创建于本字段之前(旧记录),不是"没有 persona"。
162
- *
163
- * **注意(写在这里是给下一个改 schema 的人)**:新字段必须同时登记进
164
- * `review.ts` 的 `sanitizeExpert()` 白名单 —— 那是一个显式字段清单,未登记的
165
- * 字段会在每次 `readRecord()` 时被静默丢弃,功能会"落盘了但读不到",
166
- * 而且不看断言根本发现不了。
167
- */
168
- persona?: string;
169
- status: 'pending' | 'running' | 'done' | 'failed' | 'silent';
170
- /**
171
- * 这位专家产出的**缺陷**条数。
172
- *
173
- * 它只在 `crossfire_collect` 之后才有值:一条发言可能拆成多条缺陷,所以在收集
174
- * 之前这个数字**必然是 0** —— 那不是"专家什么都没找到",而是"还没拆"。卡片因此
175
- * 必须同时显示 {@link reportCount},否则用户会读到一句谎话(真实评审里出现过:
176
- * 专家报告"已提交 3 条",卡面却写 0,且这笔 token 已经计过费)。
177
- */
178
- defectCount: number;
179
- /** 这位专家**提交过几次发言**(`crossfire_report` 的次数,实时更新)。 */
180
- reportCount?: number;
181
- /** 失败/静默原因(诊断用)。 */
182
- note?: string;
183
- }
184
- /** 一轮已完成的评审快照(闭环复审的核对依据)。 */
185
- export interface PassSnapshot {
186
- pass: number;
187
- subject: string;
188
- stanceText: string;
189
- dimensions: Dimension[];
190
- intensity: Intensity;
191
- defects: Defect[];
192
- finishedAt: number;
193
- /** 该轮修订说明(定稿时附,供下一轮核对旧缺陷是否修复)。 */
194
- revisedSummary?: string;
195
- }
196
- /** 待验证对象。 */
197
- export interface Subject {
198
- /** 一句话标题(卡片抬头 + 导出标题)。 */
199
- title: string;
200
- /** 正文:方案、成果、代码摘要或文件内容。 */
201
- content: string;
202
- /** 来源:手动粘贴 / 当前对话提炼 / 某个交付文件。 */
203
- source: 'manual' | 'chat' | 'file';
204
- /** 来源引用(文件路径,或对话锚点)。 */
205
- sourceRef?: string;
206
- }
207
- /** 预算。 */
208
- export interface CrossfireBudget {
209
- maxRounds: number;
210
- maxTokens: number;
211
- usedRounds: number;
212
- usedTokens: number;
213
- }
214
- /** 一场针锋评审的完整记录(`<stateRoot>/crossfire/<reviewId>/review.json`)。 */
215
- export interface CrossfireRecord {
216
- /**
217
- * schema 版本。**缺失视为 1**(与圆桌同款判定),当前为 1。
218
- *
219
- * 这是新插件的起点:刻意**不继承**圆桌 review.json 的 v1→v2 迁移包袱。
220
- * crossfire 的目录是独立的,不可能读到圆桌的记录。
221
- */
222
- schemaVersion: 1;
223
- reviewId: string;
224
- status: ReviewStatus;
225
- subject: Subject;
226
- stance: Stance;
227
- /** 本次采用的维度(池内 + 自定义)。 */
228
- dimensions: Dimension[];
229
- intensity: Intensity;
230
- experts: RedTeamExpert[];
231
- defects: Defect[];
232
- /** 当前第几轮(首轮 = 1)。 */
233
- reviewPass: number;
234
- /** 复审上限(由强度档决定)。 */
235
- maxReviewPass: number;
236
- /** 之前各轮的已定稿快照。 */
237
- history: PassSnapshot[];
238
- budget: CrossfireBudget;
239
- /** 从圆桌会议移交时记录来源会议 id(v0.1.0 只写不读)。 */
240
- sourceMeeting?: string;
241
- /**
242
- * 开启这场评审的会话 id。
243
- *
244
- * 用途只有一个:**校验调用者**。主持人工具都要求传 `review_id`,若没有这层,
245
- * 任何会话都能操作别人的评审。它与人无关、不含任何用户信息,因此不构成"用户
246
- * 画像"。缺失(旧记录)时跳过校验而不是拒绝。
247
- */
248
- ownerSessionId?: string;
249
- startedAt: number;
250
- updatedAt: number;
251
- /** 定稿时间(status → settled 时写)。 */
252
- settledAt?: number;
253
- }
254
- /** 一条专家发言(`transcript.jsonl` 的一行)。 */
255
- export interface ExpertUtterance {
256
- id: string;
257
- /** 发言专家的 key。 */
258
- expertKey: string;
259
- /**
260
- * `speech` = 专家主动调用 `crossfire_report` 提交;
261
- * `auto-capture` = 宿主 `subagent/end` 自动补捕(专家没 report 就结束了)。
262
- *
263
- * 关键:`auto-capture` **绝不进入缺陷收集**。圆桌的真实数据里出现过
264
- * `[当前状态] 已收到主持人任务,完成对 17 项清单的四维红队审查…` 这类过程
265
- * 叙述被当成缺陷收进裁决列表,污染了用户的表态面。
266
- */
267
- kind: 'speech' | 'auto-capture';
268
- content: string;
269
- /** 属于第几轮评审。 */
270
- pass: number;
271
- ts: number;
272
- }
273
- /** 一条轻量用户行为记录(`user-actions.jsonl`)。 */
274
- export interface UserAction {
275
- id: string;
276
- ts: number;
277
- kind: string;
278
- text: string;
279
- }
280
- /** 用户自建的角色预设:只描述"一位专家该怎么配"。 */
281
- export interface RolePreset {
282
- id: string;
283
- /** 预设名称,如"安全审查"。 */
284
- name: string;
285
- /** 角色说明(写进红队 persona)。 */
286
- role: string;
287
- provider?: string;
288
- model?: string;
289
- }
290
- /** 阵容预设里的一位成员。 */
291
- export interface RoleSquadMember {
292
- key: string;
293
- role: string;
294
- provider?: string;
295
- model?: string;
296
- }
297
- /**
298
- * 用户自建的阵容预设:一次把多位专家排进队列。
299
- *
300
- * 与 {@link RolePreset} 的分工用类型钉死——阵容只回答"谁来",不描述"怎么开":
301
- * 它保存 key/角色/模型,没有维度、没有流程、没有轮次。这条边界在圆桌里已经
302
- * 被证明有效(否则"阵容"会演化成第二套会议配置)。
303
- */
304
- export interface RoleSquad {
305
- id: string;
306
- name: string;
307
- members: RoleSquadMember[];
308
- }
@@ -1,18 +0,0 @@
1
- /**
2
- * 插件版本号的源码侧单一来源。
3
- *
4
- * `package.json` 的 `version` 是发布侧的权威值,两者必须一致:
5
- * `test/version.test.mjs` 会在不一致时直接让测试失败。这道闸门沿用圆桌会议
6
- * 的教训——那边 `renderReviewMarkdown` 的导出头部曾长期硬编码旧版本号,而
7
- * package.json 已经走远,导出物里的版本号说了谎却没有任何测试会发现。
8
- *
9
- * @module dsh-plugin-crossfire/version
10
- */
11
- /** 当前插件版本(必须与 package.json 的 version 逐字一致)。 */
12
- export declare const PLUGIN_VERSION = "0.1.1";
13
- /** 导出物头部使用的插件标识行。 */
14
- export declare const PLUGIN_ID = "@y9931666/dsh-plugin-crossfire v0.1.1";
15
- /** DeepSeek Harness 版本区间(导出头部展示用)。
16
- * 必须与 `compatibility.json` 的 `recommendedHost` 一致 —— 由
17
- * `test/version.test.mjs` 强制。 */
18
- export declare const HARNESS_RANGE = "DeepSeek Harness 0.2.0-rc.2+";
@@ -1,55 +0,0 @@
1
- /**
2
- * Web 路由的认证栅栏(第 2 批 / P5)。
3
- *
4
- * 插件自建的两条 Web 路由(`/plugins/dsh-plugin-crossfire/rpc` 与 `.../state`)
5
- * 注册在宿主 `webServer` 上,而宿主 webserver 自述 "knows no harness concepts"、
6
- * handler 只有裸 `(req, res)` —— 栅栏**不会自动施加**。宿主为此专门暴露了
7
- * `connection.requestRejection()`,其文档原文就是:"Apply Connection's
8
- * Host/Origin checks and browser authentication to another Web route"。
9
- *
10
- * 不接它的后果是真实的:本机任意网页(DNS rebinding 后同源,或 `text/plain`
11
- * 简单 POST 免预检)都能命中插件路由 —— 删会议、改连线、读走全部评审内容与
12
- * 发言。宿主自己的 `/api` 有栅栏,插件这两条路由曾是缺口。
13
- *
14
- * @module @y9931666/dsh-plugin-crossfire/web-guard
15
- */
16
- import type { IncomingMessage } from 'node:http';
17
- /** The single Connection ability this guard needs. */
18
- export interface ConnectionFence {
19
- requestRejection(request: {
20
- headers: IncomingMessage['headers'];
21
- }): 401 | 403 | undefined;
22
- }
23
- /** Request-body cap for the plugin RPC route (1 MiB — same order as the host's own API). */
24
- export declare const MAX_RPC_BODY_BYTES = 1048576;
25
- /** Pick the fence out of a possibly-absent service (never load-gating). */
26
- export declare function connectionFenceOf(service: unknown): ConnectionFence | undefined;
27
- /**
28
- * 建一个"每次调用都重新取栅栏"的探针。
29
- *
30
- * ## 为什么不是一次性解析(这是一条真实的安全修复)
31
- *
32
- * 插件路由只在第一次注册时创建,而注册带标记位、不会重来。若在**注册那一刻**把
33
- * `connectionFenceOf(ctx.get('connection'))` 的结果快照进闭包,那么:
34
- * - 那一刻 `connection` 还没挂载 → 快照到 `undefined` → 两条路由**永久**没有信任
35
- * 栅栏(DNS rebinding 后的同源页面、或免预检的 `text/plain` 简单 POST 都能命中);
36
- * - 宿主稍后才挂上 `connection` 也不会重新注册,缺口不会被补上。
37
- *
38
- * 这就是一条 fail-open 窗口。改成每次请求实时取之后,栅栏一旦可用就立即生效,代价
39
- * 只是一次属性探测。抽成函数是为了能被单测直接钉住(见 `test/web-guard.test.mjs`)。
40
- *
41
- * @param getService - 返回当前的 `connection` 服务(缺席时为 `undefined`)。
42
- * @param onAbsent - 只在**首次**发现栅栏缺席时调用一次(用于记一条降级日志)。
43
- */
44
- export declare function fenceProbe(getService: () => unknown, onAbsent?: () => void): () => ConnectionFence | undefined;
45
- /**
46
- * Decide whether one inbound request may proceed.
47
- *
48
- * @returns the HTTP status to reject with, or `undefined` to proceed.
49
- *
50
- * A composition without the Connection service has no fence to apply and is let
51
- * through — the plugin must stay usable in minimal profiles. A fence that
52
- * *throws* is treated as a rejection: this is a security boundary, and failing
53
- * open would silently undo it.
54
- */
55
- export declare function rejectWebRequest(fence: ConnectionFence | undefined, headers: IncomingMessage['headers']): 401 | 403 | undefined;