@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.
- package/README.md +2 -0
- package/lib/client.js +160 -19
- package/lib/index.js +1 -1
- package/package.json +1 -1
- package/release-notes/v0.1.2.md +44 -0
- package/release-notes/v0.1.3.md +21 -0
- package/lib/types/budget.d.ts +0 -86
- package/lib/types/client/ReviewCard.d.ts +0 -41
- package/lib/types/client/SettingsView.d.ts +0 -34
- package/lib/types/client/brand.d.ts +0 -55
- package/lib/types/client/defectTitle.d.ts +0 -23
- package/lib/types/client/index.d.ts +0 -21
- package/lib/types/client/locales.d.ts +0 -19
- package/lib/types/client/merge.d.ts +0 -27
- package/lib/types/client/promptView.d.ts +0 -63
- package/lib/types/client/session-cache.d.ts +0 -49
- package/lib/types/client/slot-boundary.d.ts +0 -46
- package/lib/types/client/wire.d.ts +0 -192
- package/lib/types/dimensions.d.ts +0 -47
- package/lib/types/engine.d.ts +0 -172
- package/lib/types/etag.d.ts +0 -40
- package/lib/types/expert-events.d.ts +0 -110
- package/lib/types/export-md.d.ts +0 -21
- package/lib/types/harness-compat.d.ts +0 -104
- package/lib/types/index.d.ts +0 -89
- package/lib/types/members.d.ts +0 -143
- package/lib/types/noise.d.ts +0 -29
- package/lib/types/preferences.d.ts +0 -134
- package/lib/types/prompt.d.ts +0 -16
- package/lib/types/redteam.d.ts +0 -115
- package/lib/types/review.d.ts +0 -152
- package/lib/types/rpc.d.ts +0 -176
- package/lib/types/split.d.ts +0 -156
- package/lib/types/state.d.ts +0 -84
- package/lib/types/tools.d.ts +0 -59
- package/lib/types/types.d.ts +0 -308
- package/lib/types/version.d.ts +0 -18
- package/lib/types/web-guard.d.ts +0 -55
|
@@ -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,23 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 观点标题:一条缺陷卡片的"抬头"。
|
|
3
|
-
*
|
|
4
|
-
* 红队的报告信封要求每条发言以 `观点 N(指向 X,维度Y):正文` 开头,而这个前缀
|
|
5
|
-
* 恰恰就是最有信息量的一行(编号 + 定位 + 维度)。用户反馈:"观点虽然说我可以理解,
|
|
6
|
-
* 把完整内容给写出来了,但我觉得还是要在最开始那里立一个标题,这样目的明确"。
|
|
7
|
-
*
|
|
8
|
-
* ## 为什么单独一个模块
|
|
9
|
-
*
|
|
10
|
-
* 标题按钮是**折叠态唯一可见的内容**,所以这里的两条边界(空括号信封、内嵌换行)
|
|
11
|
-
* 一旦退化,肉眼看到的是"整张卡片塌成一团",而不是一条错误文案。它是纯字符串函数,
|
|
12
|
-
* 因此抽出来直接单测 —— 不需要 jsdom / react 测试环境(本仓库只有 `node:test`)。
|
|
13
|
-
*
|
|
14
|
-
* 兜底顺序:① 用信封前缀(`观点 N(指向 X,维度Y)`)→ ② 未按格式提交时取首句
|
|
15
|
-
* (按换行、再按中英文句末标点切,限长)。**绝不**返回空串 —— 标题位空了比不显示更糟。
|
|
16
|
-
*/
|
|
17
|
-
/**
|
|
18
|
-
* 从一条缺陷正文里导出标题。
|
|
19
|
-
*
|
|
20
|
-
* @param content 缺陷原文(可以是任意多行文本)。
|
|
21
|
-
* @returns 非空标题:信封前缀,或首句/首行,最长 {@link TITLE_MAX} 字符。
|
|
22
|
-
*/
|
|
23
|
-
export declare function defectTitle(content: string): 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' | 'expertPromptView' | 'expertPromptTitle' | '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,63 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* "查看专家原提示词"的展开状态机。
|
|
3
|
-
*
|
|
4
|
-
* ## 它修的是什么
|
|
5
|
-
*
|
|
6
|
-
* 展开区此前用"`promptViews[key]` 是否存在"同时表达两件事:**是否有缓存**和
|
|
7
|
-
* **是否展开**。一个变量两个含义,于是两处都错:
|
|
8
|
-
*
|
|
9
|
-
* 1. 命中缓存时直接 `delete`,注释承诺的"只拉一次"变成"每次收起都丢掉缓存",
|
|
10
|
-
* 再点就是一次新的 RPC;
|
|
11
|
-
* 2. 收起发生在请求返回**之前**时,条目被删掉,但异步回调**无条件**把结果写回,
|
|
12
|
-
* 界面在用户已经收起之后又自己冒出来(反弹)。
|
|
13
|
-
*
|
|
14
|
-
* 修法是显式三态(`loading` / `ready` / `error`)+ 两个纯函数:
|
|
15
|
-
* - {@link togglePromptView}:**看有无条目**决定"发起加载"还是"收起",绝不二义;
|
|
16
|
-
* - {@link applyPromptResult}:只在**该键仍处于 loading**时写回结果 —— 用户已收起
|
|
17
|
-
* (键被删)或已经拿到别的结果时,迟到的响应一律丢弃,不再靠 `alive` ref 兜底
|
|
18
|
-
* (那个只在组件卸载时变化,挡不住"收起")。
|
|
19
|
-
*
|
|
20
|
-
* 状态转移与 React 无关,因此可以脱离渲染直接单测。
|
|
21
|
-
*/
|
|
22
|
-
/** 一个 viewKey 对应的展开状态。`loading` 的条目存在 = 已展开且正在等结果。 */
|
|
23
|
-
export type PromptView = {
|
|
24
|
-
status: 'loading';
|
|
25
|
-
} | {
|
|
26
|
-
status: 'ready';
|
|
27
|
-
persona: string;
|
|
28
|
-
} | {
|
|
29
|
-
status: 'error';
|
|
30
|
-
message: string;
|
|
31
|
-
};
|
|
32
|
-
/** 展开表:键为 `${reviewId}::${expertKey}`。 */
|
|
33
|
-
export type PromptViewTable = Record<string, PromptView>;
|
|
34
|
-
/**
|
|
35
|
-
* 一次点击的结果:新的展开表 + 这次点击**是否需要真的发请求**。
|
|
36
|
-
*
|
|
37
|
-
* `start` 为 false 即"这只是一次收起",调用方不得发 RPC。
|
|
38
|
-
*/
|
|
39
|
-
export interface PromptViewToggle {
|
|
40
|
-
readonly table: PromptViewTable;
|
|
41
|
-
readonly start: boolean;
|
|
42
|
-
}
|
|
43
|
-
/**
|
|
44
|
-
* 点击"查看原提示词":无条目 → 写入 `loading` 并要求发请求;有条目 → 删除该条目
|
|
45
|
-
* (收起),`start` 为 false。
|
|
46
|
-
*
|
|
47
|
-
* 三态一律同样对待:`ready` / `error` / 正在 `loading` 的条目,再点都是收起。
|
|
48
|
-
* 失败后保留 `error` 条目正是为此 —— 用户再点即收起,而不是无限重拉。
|
|
49
|
-
*/
|
|
50
|
-
export declare function togglePromptView(table: PromptViewTable, key: string): PromptViewToggle;
|
|
51
|
-
/**
|
|
52
|
-
* 把一次 RPC 结果写回展开表。
|
|
53
|
-
*
|
|
54
|
-
* **只有该键仍处于 `loading` 时才写回**。用户已收起(键不存在)、或该键已经处于
|
|
55
|
-
* 终态时,返回原表**引用不变** —— 这样 `setState` 不会触发一次无意义的重渲染。
|
|
56
|
-
*/
|
|
57
|
-
export declare function applyPromptResult(table: PromptViewTable, key: string, result: {
|
|
58
|
-
readonly ok: true;
|
|
59
|
-
readonly persona: string;
|
|
60
|
-
} | {
|
|
61
|
-
readonly ok: false;
|
|
62
|
-
readonly message: string;
|
|
63
|
-
}): PromptViewTable;
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 会话身份的**浏览器侧缓存**(设置页保存失败的真实修复)。
|
|
3
|
-
*
|
|
4
|
-
* ## 它修的是什么
|
|
5
|
-
*
|
|
6
|
-
* `crossfire/prefs.set` 在宿主侧要求"必须声称一个身份"(`judgeActor(undefined, session)`)。
|
|
7
|
-
* 而宿主的 slot 契约决定了设置页**结构上拿不到** `sessionId` —— 运行时 slot 目录里:
|
|
8
|
-
*
|
|
9
|
-
* conversation.view → standardProps 含 `sessionId: SessionId`
|
|
10
|
-
* tool.call.toolview → 含
|
|
11
|
-
* settings.section → **不含**(只有 useResource / useWorkspaces / usePanelInfo /
|
|
12
|
-
* useSessions / useSessionStatus / useSessionRetainInfo)
|
|
13
|
-
*
|
|
14
|
-
* 于是卡片(conversation.view / toolcall)保存偏好成功,而设置页的每一次保存都被
|
|
15
|
-
* 拒成"保存失败"—— Bug 的真实形态:**角色预设 / 阵容预设 / 强度 / 预算全部存不进去**。
|
|
16
|
-
*
|
|
17
|
-
* ## 为什么用一个模块级缓存而不是给设置页造假身份
|
|
18
|
-
*
|
|
19
|
-
* 设置页确实是全局的(偏好也是全局的),但它不能在**不知道自己是谁**的情况下写入。
|
|
20
|
-
* 而会话标签页(`conversation.view`)与轨迹卡片(`tool.call.toolview`)都拿到了宿主
|
|
21
|
-
* 注入的真实 `sessionId` —— 用户打开过任意一个,就等于把身份告诉了插件。这里把它记下来,
|
|
22
|
-
* 供设置页在提交时声明。
|
|
23
|
-
*
|
|
24
|
-
* ## 诚实的边界
|
|
25
|
-
*
|
|
26
|
-
* 冷启动后**直接**打开设置页、且本次会话从未渲染过上述任一入口时,缓存是空的,
|
|
27
|
-
* 保存仍会被宿主拒绝(这是刻意的:插件不编造身份)。绕开方式很简单——先看一眼
|
|
28
|
-
* 「针锋」标签页或对话卡片,再回来保存。
|
|
29
|
-
*/
|
|
30
|
-
/**
|
|
31
|
-
* 记下宿主注入的会话 id。
|
|
32
|
-
*
|
|
33
|
-
* 只接受非空字符串;重复值不产生副作用。返回是否**发生了变化**,便于调用方避免
|
|
34
|
-
* 无意义的重渲染。
|
|
35
|
-
*/
|
|
36
|
-
export declare function rememberSessionId(value: unknown): boolean;
|
|
37
|
-
/** 当前已知的会话 id(空串 = 尚未观察到,调用方不得据此编造一个)。 */
|
|
38
|
-
export declare function knownSessionId(): string;
|
|
39
|
-
/** 仅供测试:把缓存恢复到初始状态。 */
|
|
40
|
-
export declare function resetSessionIdCache(): void;
|
|
41
|
-
/**
|
|
42
|
-
* 把一个 payload 补上会话身份(已有 `session` 时不覆盖)。
|
|
43
|
-
*
|
|
44
|
-
* 这是设置页真正用得上的那个函数:它只在**确实知道**身份时才附加字段,否则原样返回,
|
|
45
|
-
* 让宿主按它自己的规则拒绝 —— 而不是伪造一个 `session: ''` 去骗过存在性检查。
|
|
46
|
-
*/
|
|
47
|
-
export declare function withSessionIdentity<T extends Record<string, unknown>>(payload: T): T & {
|
|
48
|
-
session?: string;
|
|
49
|
-
};
|
|
@@ -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 {};
|
|
@@ -1,192 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Wire types shared by the Crossfire browser half: the RPC result envelope and
|
|
3
|
-
* the shapes the settings page consumes.
|
|
4
|
-
*
|
|
5
|
-
* 这些类型**刻意在浏览器侧重新声明**,而不是从 host 的 `src/types.ts` import:
|
|
6
|
-
* `tsconfig.client.json` 只 include `src/client`,跨目录 import 会把 host 模块
|
|
7
|
-
* 拖进客户端类型程序(圆桌会议也采用同一处置)。
|
|
8
|
-
*
|
|
9
|
-
* @module dsh-plugin-crossfire/client/wire
|
|
10
|
-
*/
|
|
11
|
-
/** One RPC response envelope (structurally compatible with the host's RpcResult). */
|
|
12
|
-
export type RpcEnvelope<T> = {
|
|
13
|
-
ok: true;
|
|
14
|
-
value: T;
|
|
15
|
-
error?: never;
|
|
16
|
-
} | {
|
|
17
|
-
ok: false;
|
|
18
|
-
error: {
|
|
19
|
-
code: string;
|
|
20
|
-
message: string;
|
|
21
|
-
details?: unknown;
|
|
22
|
-
};
|
|
23
|
-
value?: never;
|
|
24
|
-
};
|
|
25
|
-
/** A tiny typed RPC caller over the plugin's own web route. */
|
|
26
|
-
export type RpcCaller = <T>(endpoint: string, payload: unknown) => Promise<RpcEnvelope<T>>;
|
|
27
|
-
/** 插件自有 RPC 路由。必须与 host `rpc.ts` 的 `RPC_ROUTE` 逐字一致。 */
|
|
28
|
-
export declare const RPC_ROUTE = "/plugins/dsh-plugin-crossfire/rpc";
|
|
29
|
-
/**
|
|
30
|
-
* POST one RPC call to the plugin's OWN web route.
|
|
31
|
-
*
|
|
32
|
-
* 这条路由与(批 3 起的)快照路由挂在同一个 `webServer` 注册上,因此即使宿主
|
|
33
|
-
* 的通用 connection 通道没有挂载,浏览器也能到达它。不可达或格式错误一律返回
|
|
34
|
-
* 失败包封,而不是让 promise reject —— 调用方不必写 try/catch。
|
|
35
|
-
*/
|
|
36
|
-
export declare function callRpc<T>(endpoint: string, payload: unknown, signal?: AbortSignal): Promise<RpcEnvelope<T>>;
|
|
37
|
-
/** 攻击强度档。 */
|
|
38
|
-
export type Intensity = 'quick' | 'standard' | 'deep';
|
|
39
|
-
/** 用户自建角色预设(与 host `RolePreset` 对应)。 */
|
|
40
|
-
export interface WireRolePreset {
|
|
41
|
-
id: string;
|
|
42
|
-
name: string;
|
|
43
|
-
role: string;
|
|
44
|
-
provider?: string;
|
|
45
|
-
model?: string;
|
|
46
|
-
}
|
|
47
|
-
/** 阵容预设的一位成员(与 host `RoleSquadMember` 对应)。 */
|
|
48
|
-
export interface WireRoleSquadMember {
|
|
49
|
-
key: string;
|
|
50
|
-
role: string;
|
|
51
|
-
provider?: string;
|
|
52
|
-
model?: string;
|
|
53
|
-
}
|
|
54
|
-
/** 用户自建阵容预设(与 host `RoleSquad` 对应)。 */
|
|
55
|
-
export interface WireRoleSquad {
|
|
56
|
-
id: string;
|
|
57
|
-
name: string;
|
|
58
|
-
members: WireRoleSquadMember[];
|
|
59
|
-
}
|
|
60
|
-
/** `crossfire/models.list` 里的一个 provider 及其可选模型(与圆桌同源做法)。 */
|
|
61
|
-
export interface WireProviderOption {
|
|
62
|
-
id: string;
|
|
63
|
-
name: string;
|
|
64
|
-
models: {
|
|
65
|
-
id: string;
|
|
66
|
-
name: string;
|
|
67
|
-
}[];
|
|
68
|
-
}
|
|
69
|
-
/** 偏好形状(与 host `CrossfirePreferences` 对应)。 */
|
|
70
|
-
export interface WirePrefs {
|
|
71
|
-
defaultIntensity: Intensity;
|
|
72
|
-
maxRounds: number;
|
|
73
|
-
maxTokens: number;
|
|
74
|
-
expertMaxTokens: number;
|
|
75
|
-
expertMaxOpinions: number;
|
|
76
|
-
customDimensions: string[];
|
|
77
|
-
rolePresets: WireRolePreset[];
|
|
78
|
-
squads: WireRoleSquad[];
|
|
79
|
-
/** 被用户关掉的解释性说明(键 → true);持久化,刷新不会回来。 */
|
|
80
|
-
dismissedHints: Record<string, boolean>;
|
|
81
|
-
}
|
|
82
|
-
/** 强度档的生效规则(单一来源在 host 侧,客户端只读展示)。 */
|
|
83
|
-
export interface WireIntensitySpec {
|
|
84
|
-
expertsPerDimension: number;
|
|
85
|
-
crossExamination: boolean;
|
|
86
|
-
maxReviewPass: number;
|
|
87
|
-
label: string;
|
|
88
|
-
}
|
|
89
|
-
/** `crossfire/prefs.get` 的返回载荷。 */
|
|
90
|
-
export interface WirePrefsSnapshot {
|
|
91
|
-
prefs: WirePrefs;
|
|
92
|
-
/** 固定维度池。 */
|
|
93
|
-
pool: string[];
|
|
94
|
-
/** 本次实际可用维度:固定池 + 自定义。 */
|
|
95
|
-
dimensions: string[];
|
|
96
|
-
intensities: Record<Intensity, WireIntensitySpec>;
|
|
97
|
-
stateDir: string;
|
|
98
|
-
}
|
|
99
|
-
/** 评审状态机的四态(与 host `ReviewStatus` 对应)。 */
|
|
100
|
-
export type WireReviewStatus = 'draft' | 'attacking' | 'judging' | 'settled';
|
|
101
|
-
/** 缺陷三态。 */
|
|
102
|
-
export type WireDefectStatus = 'pending' | 'endorsed' | 'rejected';
|
|
103
|
-
/** 复审核对结论。 */
|
|
104
|
-
export type WireRecheckVerdict = 'fixed' | 'unfixed' | 'partial';
|
|
105
|
-
/** 一位红队在卡片上的状态。 */
|
|
106
|
-
export interface WireExpert {
|
|
107
|
-
key: string;
|
|
108
|
-
dimension: string;
|
|
109
|
-
status: string;
|
|
110
|
-
defectCount: number;
|
|
111
|
-
/** 提交过几次发言 —— collect 之前唯一有意义的数字(defectCount 那时必然是 0)。 */
|
|
112
|
-
reportCount?: number;
|
|
113
|
-
/**
|
|
114
|
-
* 这位专家实际使用的模型路由。
|
|
115
|
-
*
|
|
116
|
-
* 未显式指定时(继承主持人)host 不写这两个字段 —— 那**不是**缺失信息,而是
|
|
117
|
-
* "跟随主持人"这一事实本身,卡片据此显示"继承"而不是一个猜出来的 provider。
|
|
118
|
-
*/
|
|
119
|
-
provider?: string;
|
|
120
|
-
model?: string;
|
|
121
|
-
/** 用户补充的角色定位(专家列表里可编辑;进 persona 的「你的角色定位」段)。 */
|
|
122
|
-
role?: string;
|
|
123
|
-
note?: string;
|
|
124
|
-
}
|
|
125
|
-
/** 一条缺陷(卡片上的一张子卡)。 */
|
|
126
|
-
export interface WireDefect {
|
|
127
|
-
id: string;
|
|
128
|
-
expertKey: string;
|
|
129
|
-
dimension: string;
|
|
130
|
-
/** true = 落库时维度被归一化过(原始值不可识别)。 */
|
|
131
|
-
dimensionFallback?: boolean;
|
|
132
|
-
content: string;
|
|
133
|
-
quote?: string;
|
|
134
|
-
evidence?: {
|
|
135
|
-
kind: 'repro' | 'argument';
|
|
136
|
-
text: string;
|
|
137
|
-
};
|
|
138
|
-
/** true = 这条缺陷打穿了用户立场里的某条理由。 */
|
|
139
|
-
strikesStance?: boolean;
|
|
140
|
-
status: WireDefectStatus;
|
|
141
|
-
rejectReason?: string;
|
|
142
|
-
recheck?: {
|
|
143
|
-
pass: number;
|
|
144
|
-
verdict?: WireRecheckVerdict;
|
|
145
|
-
note?: string;
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
/** 一场评审的卡片快照(与 host `ReviewSnapshot` 对应)。 */
|
|
149
|
-
export interface WireReview {
|
|
150
|
-
reviewId: string;
|
|
151
|
-
status: WireReviewStatus;
|
|
152
|
-
subjectTitle: string;
|
|
153
|
-
subjectContent: string;
|
|
154
|
-
subjectSource: string;
|
|
155
|
-
stanceText: string;
|
|
156
|
-
stanceOrigin: string;
|
|
157
|
-
dimensions: string[];
|
|
158
|
-
intensity: Intensity;
|
|
159
|
-
intensityLabel: string;
|
|
160
|
-
reviewPass: number;
|
|
161
|
-
maxReviewPass: number;
|
|
162
|
-
experts: WireExpert[];
|
|
163
|
-
defects: WireDefect[];
|
|
164
|
-
counts: {
|
|
165
|
-
total: number;
|
|
166
|
-
pending: number;
|
|
167
|
-
endorsed: number;
|
|
168
|
-
rejected: number;
|
|
169
|
-
strikes: number;
|
|
170
|
-
};
|
|
171
|
-
budget: {
|
|
172
|
-
maxRounds: number;
|
|
173
|
-
maxTokens: number;
|
|
174
|
-
usedRounds: number;
|
|
175
|
-
usedTokens: number;
|
|
176
|
-
};
|
|
177
|
-
startedAt: number;
|
|
178
|
-
updatedAt: number;
|
|
179
|
-
/** 这份评审所在的状态根;回传它可让 host 免去一次工作区遍历。 */
|
|
180
|
-
stateRoot: string;
|
|
181
|
-
}
|
|
182
|
-
/** `crossfire/reviews.list` 的返回载荷。 */
|
|
183
|
-
export interface WireReviewList {
|
|
184
|
-
reviews: WireReview[];
|
|
185
|
-
note?: string;
|
|
186
|
-
}
|
|
187
|
-
/** 快照路由(GET,卡片轮询它)。必须与 host `rpc.ts` 的 `STATE_ROUTE` 一致。 */
|
|
188
|
-
export declare const STATE_ROUTE = "/plugins/dsh-plugin-crossfire/state";
|
|
189
|
-
/** 拉一次评审快照列表。失败一律返回空列表(卡片宁可空着也不该崩)。 */
|
|
190
|
-
export declare function fetchReviews(session: string, signal?: AbortSignal): Promise<WireReviewList>;
|
|
191
|
-
/** 推进状态机的四个步骤(O1 直推)。 */
|
|
192
|
-
export type AdvanceStep = 'attack' | 'collect' | 'finish' | 'recheck';
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 维度归一化:把模型自由输出的维度标签收敛到**本次评审实际采用的维度**里。
|
|
3
|
-
*
|
|
4
|
-
* ## 为什么必须有这一层
|
|
5
|
-
*
|
|
6
|
-
* 抽样 5 份真实圆桌 `review.json`,`dimension` 的实值是:
|
|
7
|
-
*
|
|
8
|
-
* `维度②` ×5 `维度④` ×1 `其他` ×4 空串 ×1
|
|
9
|
-
*
|
|
10
|
-
* 只有 1 份给出了有意义的 `架构/产品/成本`。根因是 LLM 拆分经常失败并退到本地
|
|
11
|
-
* 启发式兜底,而兜底把段首的圆圈数字(①②)直接当成了维度名。用户看到的因此
|
|
12
|
-
* 是一堆编号而不是维度 —— 这直接废掉了"按维度看覆盖面"这件事。
|
|
13
|
-
*
|
|
14
|
-
* 所以 crossfire 的规则是:**落库的 `dimension` 必须是本次采用维度之一**,
|
|
15
|
-
* 越界值归一化到最近的一项并留下 `dimensionFallback` 标记(可见、可统计,
|
|
16
|
-
* 而不是静默污染)。
|
|
17
|
-
*
|
|
18
|
-
* @module dsh-plugin-crossfire/dimensions
|
|
19
|
-
*/
|
|
20
|
-
/** 归一化命中的方式(诊断与测试用)。 */
|
|
21
|
-
export type DimensionMatch = 'exact' | 'normalized' | 'alias' | 'contains' | 'default';
|
|
22
|
-
/** 一次维度归一化的结果。 */
|
|
23
|
-
export interface DimensionResolution {
|
|
24
|
-
/** 归一化后的维度。只要 `allowed` 非空,它一定 ∈ allowed。 */
|
|
25
|
-
dimension: string;
|
|
26
|
-
/** true = 原始值不在 allowed 中,被归一化过(调用方应记 `dimensionFallback`)。 */
|
|
27
|
-
fallback: boolean;
|
|
28
|
-
/** 命中方式。 */
|
|
29
|
-
how: DimensionMatch;
|
|
30
|
-
}
|
|
31
|
-
/** 判断一个值是否是固定池维度。 */
|
|
32
|
-
export declare function isPoolDimension(value: string): boolean;
|
|
33
|
-
/**
|
|
34
|
-
* 把一个自由文本维度收敛到 `allowed` 里。
|
|
35
|
-
*
|
|
36
|
-
* @param raw - 模型/用户给出的原始维度(任意值,非字符串会被当作缺失)。
|
|
37
|
-
* @param allowed - 本次评审实际采用的维度清单(固定池 + 自定义)。
|
|
38
|
-
* @returns 归一化结果;`allowed` 为空时退回固定池。
|
|
39
|
-
*/
|
|
40
|
-
export declare function resolveDimension(raw: unknown, allowed: readonly string[]): DimensionResolution;
|
|
41
|
-
/**
|
|
42
|
-
* 把一组候选维度收敛成"本次实际采用"的清单。
|
|
43
|
-
*
|
|
44
|
-
* 用于 `crossfire_open`:用户给的维度可能重复、越界、或全是垃圾。结果永远非空
|
|
45
|
-
* (最差退回固定池全集),因为"没有维度"会让分发逻辑无处安放。
|
|
46
|
-
*/
|
|
47
|
-
export declare function resolveDimensions(raw: unknown, customAllowed: readonly string[]): string[];
|