@xinvxueyuan/cordis-plugin-secret 0.4.3 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +66 -14
- package/lib/client/entry.js +1171 -93
- package/lib/index.js +52 -0
- package/lib/naming.d.ts +65 -0
- package/lib/naming.js +170 -0
- package/lib/privacy.d.ts +145 -0
- package/lib/privacy.js +333 -0
- package/lib/service.d.ts +58 -0
- package/lib/service.js +105 -2
- package/package.json +2 -2
- package/src/client/entry.ts +1309 -134
- package/src/index.ts +74 -0
- package/src/naming.ts +202 -0
- package/src/privacy.ts +361 -0
- package/src/service.ts +134 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
- **模型侧的改写由注记承担,且注记会落盘(按 source 去重)**:`agent/pre-step` **不改**用户消息正文(harness 会让改写落盘,见「人类主动附加密钥(反方向)→ 模型侧到底看到什么」),而是追加一条只含变量名的注记,其中**逐变量逐字**写出「正文里的 `@DSH_SECRET_*` 即该变量,模型侧写作 `[secret DSH_SECRET_*]`;它不是文件路径」。注记是 durable 的 `user/message`(因此在对话流里是一行注入说明),且**按自身 source 去重**:模型可见 surface 上已有同一条就不再追加,重复引入同一标记不会堆叠。
|
|
45
45
|
- **会在会话日志里留下变量名标记**:人类发送的消息本身(含 `@DSH_SECRET_OPENAI` 这种**变量名**)作为普通 `user/message` 事件持久化。变量名不是密钥材料,但它会长期留在日志里。
|
|
46
46
|
- **会挂 10 条 `/api` 路由**(**9 个精确路径**:`/api/secret.manage` 一条路径两种方法,**单次注册**、按 `request.method` 分派——真实注册表按精确路径建键、与 `methods` 无关,所以同一条路径注册两次会在装配时抛 `exact Fetch route ... is already registered` 并让整个插件条目不激活;0.4.0 正是这样在实机上不激活,0.4.1 修掉):`/api/secret.pending`(GET)、`/api/secret.attached`(GET)、`/api/secret.attach`(POST)、`/api/secret.release`(POST)、`/api/secret.answer`(POST),以及 0.3.0 新增的三条——`/api/secret.history`(GET,本会话历史流水)、`/api/secret.available`(GET,`@` 菜单的可用密钥)、`/api/secret.adopt`(POST,把凭据库里的持久记录登记到本会话),以及 0.4.0 新增的 `manage`(GET 管理面列表 / POST 执行一个管理动作)。全部位于 `ctx.connection` 的信任栅栏内(本机 / 可信 Host、同源标记、签名浏览器 Cookie)。值只出现在 `attach`、`answer` 与(仅 `action:"value"` 时)`manage` 的请求体里;其余路由都**不回传值**,`adopt` 的取值也由宿主自己经 `credentials.resolve` 完成,值不跨线。
|
|
47
|
-
- **会注册客户端座位与一个引用源**:`conversation.chat.node`(key `secret-request`)、`tool.call.toolview`(key `secret_request` 的 `null` 占位)、`conversation.input.left`(id `secret-attach-toggle`)、`conversation.input.overlay`(id `secret-attach-capsule`),外加一个名为 `secret` 的 `InputTriggerSource` 与 locale 命名空间 `secretAttach`。0.3.0 起再增加三处**增量**注册:`conversation.chat.node`(key `sr-chip`,消息旁的旁挂胶囊)、`sidebarRightTabs` 的 `secret-attach-detail` 类型,以及它的**正文座位** `sidebar.right.pane.tab`(key `@xinvxueyuan/cordis-plugin-secret/secret-attach-detail`)。该页签的**标题不另注册座位**:注册表走 fallback 取类型自己声明的 `title(address)`,即**变量名**(`src/client/entry.ts:3555`),所以用户看到的结果是对的。`tool.call.toolview` 只替换本插件自己那次调用的泛型工具行,不触碰别的工具;其余都是增量座位。0.4.0 再增加两处**增量**注册:`conversation.chat.node`(key `sr-manage`,Agent 侧的管理确认卡)与 `tool.call.toolview`(key `secret_manage` 的 `null` 占位);同样只影响本插件自己那次调用,且管理面的三个新面都在**既有胶囊组件**里,不新增座位、不新增引用源,`dsh.client.inject`
|
|
47
|
+
- **会注册客户端座位与一个引用源**:`conversation.chat.node`(key `secret-request`)、`tool.call.toolview`(key `secret_request` 的 `null` 占位)、`conversation.input.left`(id `secret-attach-toggle`)、`conversation.input.overlay`(id `secret-attach-capsule`),外加一个名为 `secret` 的 `InputTriggerSource` 与 locale 命名空间 `secretAttach`。0.3.0 起再增加三处**增量**注册:`conversation.chat.node`(key `sr-chip`,消息旁的旁挂胶囊)、`sidebarRightTabs` 的 `secret-attach-detail` 类型,以及它的**正文座位** `sidebar.right.pane.tab`(key `@xinvxueyuan/cordis-plugin-secret/secret-attach-detail`)。该页签的**标题不另注册座位**:注册表走 fallback 取类型自己声明的 `title(address)`,即**变量名**(`src/client/entry.ts:3555`),所以用户看到的结果是对的。`tool.call.toolview` 只替换本插件自己那次调用的泛型工具行,不触碰别的工具;其余都是增量座位。0.4.0 再增加两处**增量**注册:`conversation.chat.node`(key `sr-manage`,Agent 侧的管理确认卡)与 `tool.call.toolview`(key `secret_manage` 的 `null` 占位);同样只影响本插件自己那次调用,且管理面的三个新面都在**既有胶囊组件**里,不新增座位、不新增引用源,`dsh.client.inject` 也不变。**0.5.0 再增加一处增量注册**:`conversation.input.right`(id `secret-paste-composer`,输入区的「粘贴」动作,见「0.5.0 的四项」第 2 条);仍然只增座位、不新增引用源,`dsh.client.inject` 不变。
|
|
48
48
|
- **不做的事**:插件自身不 spawn 子进程、不读写仓库文件、不发起网络请求(除被 Harness 自己的 API 通道承载的上面列出的 10 条同源路由外),也没有任何遥测。
|
|
49
49
|
|
|
50
50
|
## 安装
|
|
@@ -210,7 +210,7 @@ Harness 的公开面 `InputActions` **故意不含**引用插入(`Command-styl
|
|
|
210
210
|
|
|
211
211
|
### 自己复测(活体,约 3 分钟)
|
|
212
212
|
|
|
213
|
-
> **先重启。** dsh 在进程启动时加载插件的 `lib/` 构建,所以**发布 0.2.1 之后必须重启 `dsh web`** 才会加载修复后的产物;重启前的活体复测仍会复现 0.2.0 的现象(`staged` 不变 `bound`、shell 里没有变量),那是预期,不是修复失败。同理,**发布 0.3.0 之后也必须重启 `dsh web`** 才会加载四项增强的产物——重启前看不到消息旁的旁挂胶囊、信息框里的「历史记录」区、草稿移除后的自动撤销,`@` 菜单也不会有新分组,这些都属预期(下面是这四项各自的复测方式)。**发布 0.4.0 之后同样必须重启 `dsh web`** 才会加载管理面——重启前 `secret_manage` 工具不会出现在工具列表里,信息框里也没有五条管理路径,历史里不会有 `updated`/`scope-changed`/`unbound`/`deleted` 四类事件;活体上仍是 `0.3.0` 的四个方向,这属预期,不是发布失败。**0.4.0 是个例外**:它虽然在 GitHub Release 与 GitHub Packages 上发了,但在真实运行时**整个插件条目不激活**(`dsh: warning: 1 entry did not activate`,见下面「0.4.0 的发布级缺陷与 0.4.1 修复」),所以重启也换不来可用的管理面;**必须重启到 0.4.1**。**发布 0.4.2 之后同样必须重启 `dsh web`** 才会加载密钥输入的抑制属性——重启前浏览器仍会照旧给那四个字段自动填充建议、密码管理器扩展仍会捕获(这就是 0.4.2
|
|
213
|
+
> **先重启。** dsh 在进程启动时加载插件的 `lib/` 构建,所以**发布 0.2.1 之后必须重启 `dsh web`** 才会加载修复后的产物;重启前的活体复测仍会复现 0.2.0 的现象(`staged` 不变 `bound`、shell 里没有变量),那是预期,不是修复失败。同理,**发布 0.3.0 之后也必须重启 `dsh web`** 才会加载四项增强的产物——重启前看不到消息旁的旁挂胶囊、信息框里的「历史记录」区、草稿移除后的自动撤销,`@` 菜单也不会有新分组,这些都属预期(下面是这四项各自的复测方式)。**发布 0.4.0 之后同样必须重启 `dsh web`** 才会加载管理面——重启前 `secret_manage` 工具不会出现在工具列表里,信息框里也没有五条管理路径,历史里不会有 `updated`/`scope-changed`/`unbound`/`deleted` 四类事件;活体上仍是 `0.3.0` 的四个方向,这属预期,不是发布失败。**0.4.0 是个例外**:它虽然在 GitHub Release 与 GitHub Packages 上发了,但在真实运行时**整个插件条目不激活**(`dsh: warning: 1 entry did not activate`,见下面「0.4.0 的发布级缺陷与 0.4.1 修复」),所以重启也换不来可用的管理面;**必须重启到 0.4.1**。**发布 0.4.2 之后同样必须重启 `dsh web`** 才会加载密钥输入的抑制属性——重启前浏览器仍会照旧给那四个字段自动填充建议、密码管理器扩展仍会捕获(这就是 0.4.2 修的那件事),这属预期,不是修复失败。**发布 0.4.3 之后也必须重启 `dsh web`** 才会加载两个标识符字段(凭据键、标题)的抑制与隐式关联改动——重启前这两个字段仍会弹浏览器的历史值/自动填充建议(这正是 0.4.3 修的那件事),这也属预期。
|
|
214
214
|
|
|
215
215
|
1. 在输入区点「附加密钥」按钮 → 胶囊里填名称(如 `openai`)与值,作用域保持默认「仅本次会话」→ 插入 → 输入框出现 `@DSH_SECRET_OPENAI` 胶囊。
|
|
216
216
|
2. **发送前**:在同一浏览器(同源、带签名 cookie)打开 `GET /api/secret.attached?sessionId=$DSH_SESSION_ID` → 该条的 `state` 必须是 `"staged"`,且此刻 shell 里没有这个变量(未发送永不注入)。
|
|
@@ -325,6 +325,41 @@ Invoke-RestMethod "http://127.0.0.1:<port>/api/secret.available?sessionId=$env:D
|
|
|
325
325
|
if ($env:DSH_SECRET_OPENAI) { "present length=$($env:DSH_SECRET_OPENAI.Length)" } else { "absent" }
|
|
326
326
|
```
|
|
327
327
|
|
|
328
|
+
### 0.5.0 的四项(本轮,将随 0.5.0 发布)
|
|
329
|
+
|
|
330
|
+
> **版本说明**:截至本轮开发段,`package.json` 仍是 **0.4.3**,**0.5.0 尚未 bump、也未发布**;本节的「0.5.0」指本轮这些特性**将随 0.5.0 发布**。发布事实与 registry 现状只在「npm 发布」一节按已发布版本写。
|
|
331
|
+
> 本节示例里的密钥值一律是**假值**(形如 `sk-EXAMPLE-…`),不是任何真实凭据。
|
|
332
|
+
|
|
333
|
+
#### 1. 密钥键改为可选 + 附加时的会话外 AI 命名(R1)
|
|
334
|
+
|
|
335
|
+
- **契约放宽**:`secret_attach` 的 `name` 由必填变为**可选**。空或缺失 ⇒ 由 Host 自动命名(旧客户端与直接调用同样得 200);**非空但非法**的键照旧走原来的错误分支被拒(`ATTACH_KEY_RE`)。
|
|
336
|
+
- **命名是「会话外」调用**:经 `llm.stream({ purpose: 'session-title', … })`,**不传 `sessionId`、不传 `tools`、不 append 任何会话事件**;提示词只带标题与**值形态**(长度 / 字符集 / 类别三类 token,**绝不含明文**)。
|
|
337
|
+
- **有界且不阻塞发送**:deadline **1500ms**;超时、模型答案不合法、profile 里没有 llm,一律回落到**本地兜底键**(标题 slug → 形态默认 → 冲突时加 `-2…-99` 去重)。有「模型挂起也不拖住 attach」的单测(`elapsed < 500ms`)。
|
|
338
|
+
- **llm 不在硬依赖里**:用 `ctx.get('llm')` **可选获取**(不进 `inject` 硬依赖),所以没有 llm 的 profile **插件仍激活、attach 仍成功**(有正对照测试)。
|
|
339
|
+
- **未实现(如实)**:定案里「**晚到结果润色显示标题**」这一条**没有实现**。原因:键在插入胶囊(即报文标记)**之前**就已定型,不存在一个「不改报文」的安全作用点可以回填标题;因此这是**不做**,而不是"待办"。
|
|
340
|
+
|
|
341
|
+
#### 2. 输入框右侧「粘贴」动作 + 值输入框粘贴命中即转胶囊(R2 / R3)
|
|
342
|
+
|
|
343
|
+
- **覆盖面(我方控件,共 7 个可编辑字段)**:附加面板的标题 / 凭据键 / 密钥内容、管理面的改值、管理确认卡的「新密钥内容」、索要卡片的值 / 「其他指示」多行框,各有一个 suffix **「粘贴」**按钮:`type="button"`、`aria-label` 与可见文本同为「粘贴」、`mousedown` 被 `preventDefault`(**不抢焦点**),点击的**最后一步把焦点交还该字段**。剪贴板不可用 / 被拒 / 为空时给固定**双语**文案并引导手动粘贴,**不静默失败、不抛未捕获异常**。
|
|
344
|
+
- **写入与校验同路**:粘贴文本 `trim()` 后调用**该字段自己 `onChange` 用的同一个 setter**(不 `dispatchEvent`、不直接改 DOM),因此既有校验照旧生效——例如粘进凭据键的非法值仍在提交时走 `ATTACH_KEY_RE` → `badKey`,而不是绕过校验。
|
|
345
|
+
- **R3(仅值输入框,且为真登记)**:只有**胶囊的「密钥内容」**这一个值输入框在粘贴时做**机械判定**(`src/privacy.ts` 的规则集:厂商前缀 / JWT / PEM / 长且字符集集中 / 信息熵阈值,外加 URL·路径·邮箱·裸域名·CJK·多行·标识符等排除项;纯函数、不调模型);命中即走该表单**同一条 attach 注册路径**(真登记:同一校验、同一 `POST /api/secret.attach`),成功后字段清空并按既有行为切到详情面;未命中则按普通文本写入(原生粘贴路径**不接管**)。
|
|
346
|
+
- **边界(如实)**:改值面、管理确认卡(`secret_manage`)与索要卡片(`/api/secret.answer`)**不做**自动登记——对它们做会变成「假登记」。`@DSH_SECRET_*` / `[secret …]` / `dsh-resource://` 这类**引用文本**在分类器**第一步**就被排除,绝不会被当成明文再登记一次。任何失败路径(剪贴板读不到、登记被拒、网络不可达)都把粘进来的文本**放回字段**,不丢内容。
|
|
347
|
+
- **composer 侧(R2 的输入区半边)**:在官方座位 `conversation.input.right` 放一个「粘贴」动作,点击经 `InputActions.insertText(text, captureInsertion())` **插入到光标处**,走编辑器自身的 onChange/校验。**能力边界(如实)**:① 只能 `captureInsertion` / `insertText`,**没有写受控 value 的能力**;② 只能在官方座位放自己的按钮,**第三方插件自建的输入框不在覆盖范围内**;③ 无 caret 或编辑器拒绝插入时给手动粘贴提示,不抛错、不抢焦点。
|
|
348
|
+
|
|
349
|
+
#### 3. 胶囊 ✕ = 本会话解绑(R4)
|
|
350
|
+
|
|
351
|
+
- **语义**:✕ = **解绑**(`staged` → release、`bound` → manage `unbind`),即**从本会话移除**;**凭据库里的持久记录永不触碰**。其文案与 `aria-label` 全文**不含「删除」二字**(真删仍只在危险面、需 `confirm:true`)。
|
|
352
|
+
- **会话级后果(诚实结果)**:解绑是会话级的,所以同一变量的**所有**消息旁胶囊都会消失——这由「胶囊状态从实时有效性推导」保证,是**设计结果而非缺陷**。
|
|
353
|
+
- **bound 的 ✕ 不弹二次确认**:宿主 `src/service.ts:1050-1053` 明写该路由上「人的点击本身就是确认」;需要二次确认的只有真删那一档。
|
|
354
|
+
- **显隐**:默认隐藏,`:hover` / `:focus-within` 显示,`@media (hover:none)` 下常显;`aria-label` 形如 `移除 @DSH_SECRET_X`(**不含明文**)。
|
|
355
|
+
- **历史缺陷与修复(如实留痕)**:**0.4.x 的胶囊行在真实页面会把原始 i18n key 渲染成文案与 `aria-label`**(行内翻译绑到了 chat namespace,取不到本插件的键)——也就是**那一版该行的可访问名是坏的**、读屏会念出 key 而不是「移除 …」。**0.5.0 用 `rowT` 探测回落修好**:取不到本插件的键就回落自带字面表,不再渲染 key。
|
|
356
|
+
|
|
357
|
+
#### 4. 输入区选区「转为密钥」(R5)
|
|
358
|
+
|
|
359
|
+
- **行为以按下时刻为准**:按下时用 `captureInsertion()` 的 `start !== end` 判断"有选区";选中文本经 `useInput` 的 `draft` + `occurrences` 由 detect→clipboard 换算得到。**任何不一致都退回原「附密钥」行为**(不猜、不登记、不改写草稿)。
|
|
360
|
+
- **默认作用域 = `session`**:附加面板与转换路径**共用同一个常量** `DEFAULT_ATTACH_SCOPE`(`session`),即**仅本会话内存有效**;空键 / 空标题交给 R1 的自动命名。
|
|
361
|
+
- **实时文案是契约外手段(重要)**:公开 API 只能**按下时**读取选区、**没有选区变化通知**,所以按钮的实时文案依赖**契约外的 DOM `selectionchange` 监听**。失效模式、退回行为与「需真人确认」见「边界与已知限制 → 已知限制」里那一条;**行为永不依赖它**。
|
|
362
|
+
|
|
328
363
|
### 已知限制(如实记录)
|
|
329
364
|
|
|
330
365
|
- **对话框那条消息上的胶囊是 `data-ref-chip="file"`**,`openFile('DSH_SECRET_OPENAI')` 仍是它唯一的点击路径。**0.3.0 起的现状**:插件已注册 `sidebarRightTabs` 的 `secret-attach-detail` 类型**接管这次打开**,所以点它会开出**右侧栏的详情页**(见「0.3.0 的四项增强 → 原胶囊的接管是长度竞争」);0.2.x 的行为是打开一个不存在的文件(无害但不正确;不泄露任何东西——变量名本来就在那里可见),该行为作为历史如实保留。**注意这条接管是"当前 pattern 最长"的长度竞争结果,不是契约。**原因(点击目标为何无法从插件侧直接改写):用户气泡的正文由 `dsh-client-ui-primitives` 的 `projectUserText` 独家渲染,它对 `@token` 形状**硬编码**为文件引用并挂 `openFile`;ui-chat 的消息管线没有可注册的引用种类(能接住点击的 `openReference` 只作用于**草稿编辑器**,不作用于转录气泡)。
|
|
@@ -408,7 +443,7 @@ if ($env:DSH_SECRET_OPENAI) { "present length=$($env:DSH_SECRET_OPENAI.Length)"
|
|
|
408
443
|
|
|
409
444
|
**不可恢复性与副作用(逐字告知)**:引用空间的值被原子重写掉(`writeFileAtomic` + 0600),**没有任何副本**,也无法从会话日志反推(日志里只有变量名);记录空间的标记一并删除,因此**其它会话再也 adopt 不到它**。想再用同一变量,只能重新走 `secret_request` 或重新附加,**重新输入新值**。删除后刷新页面,「管理」面(每次都向 Host 现问)里该行消失,历史里多一条 `deleted`(仅本进程内可见)。若某个会话此前已把它绑到某条消息上,那个会话的内存副本仍在,直到消息回退或会话结束。
|
|
410
445
|
|
|
411
|
-
### 怎么复测 0.4.
|
|
446
|
+
### 怎么复测 0.4.3(先重启 `dsh web`)
|
|
412
447
|
|
|
413
448
|
0. **重启后先看启动日志**(这是 0.4.0 缺陷的判据,也是本版修复的判据):`dsh` 的启动输出里**不应再有** `warning: 1 entry did not activate`,也不应再出现 `secret (@xinvxueyuan/cordis-plugin-secret): Error: connection: exact Fetch route "/api/secret.manage" is already registered`;工具列表里应当有 `secret_manage`,管理面可用。(0.4.0 在实机上正是这条 warning;`lib/routes.js` 里 `/api/secret.manage` 现在只注册一次、`methods` 是 `['GET','POST']`,可以离线核对。)
|
|
414
449
|
|
|
@@ -416,7 +451,7 @@ if ($env:DSH_SECRET_OPENAI) { "present length=$($env:DSH_SECRET_OPENAI.Length)"
|
|
|
416
451
|
```sh
|
|
417
452
|
npm run typecheck && npm test
|
|
418
453
|
```
|
|
419
|
-
共 8 个测试文件 /
|
|
454
|
+
共 8 个测试文件 / **171 个用例**(这两个数字来自 0.5.0 开发段本人亲跑的 `npm test` 原始输出:`tests 171 / pass 171 / fail 0`,自然退出;见「开发」一节)。管理面在 `test/register.test.ts`:两个工具的参数集(逐字断言没有 `value`)、`list`/`unbind`/`delete`/`scope`/`value` 五个动作、五条人类侧路径、降级两个按钮(文案与线上动作都不同)、真删两档(缺 `confirm` 零副作用)、列表两个方向的 `origin` 与委派子代理的可达性注意(含根代理的正对照);**0.4.1 起还有实施真实注册表规则的严格注册表**——它断言 9 条精确路径各注册一次、`/api/secret.manage` 一行两方法,并在修复前的形态上以**同一条** `already registered` 错误失败(这就是 0.4.0 漏网的那个门);**0.4.2 起新增两条密钥输入抑制用例**(`test/client-card.test.ts` / `test/client-attach.test.ts`)——它们逐面枚举渲染出的字段并断言那一套抑制属性与只读守卫,在修复前的形态上逐条列出缺口(正对照);**0.4.3 起再加一条标识符字段用例**——断言凭据键/标题用 `autocomplete="off"`、无稳定 `id`/`name`、隐式 label 关联 + 同文本 `aria-label`、带只读守卫,并在 0.4.2 的形态上失败(正对照)。四类新历史事件在 `test/history.test.ts`。`npm test` 若挂住,按仓库红线处理:**先查泄漏**(本插件自己的 teardown/定时器),临时排障才用 `node --test --test-force-exit`,**不得按进程名清场**。
|
|
420
455
|
2. **活体(需真人;管理面每次都向 Host 现问,改完刷新即可再核对)**:按上面「人类侧信息框(五条路径)」逐条点一遍——列举(两个分区读得清)、改值(掩码框)、改作用域(两个降级按钮各点一次,核对其后果不同)、解绑(凭据库不动)、真删(两次点击)。断言点是上面「需真人确认」列出的那些:不可用的动作不出现;改值后活体 shell 里真的是新值;`scope → persistent` 真的写库;真删后该行从库侧分区消失、会话侧作用域如实变「仅本次会话」、历史多一条 `deleted`、其它三条外来记录一字未动;子代理调 `list` 不挂起、调四个写动作得到结构化 `DELEGATED_CALLER`(无对话框)。
|
|
421
456
|
3. **浏览器侧(0.4.2 起,只能真人做)**:在真实浏览器里打开四个密钥输入面**与两个标识符输入(凭据键、标题)**,逐条核「需真人确认」里那几项——是否还弹自动填充建议(标识符字段尤其要看历史值下拉是否消失)、Chromium 是否还给"生成强密码"建议、是否还弹"保存密码?"、已装的密码管理器扩展是否仍捕获、以及**聚焦之后能否正常键入**(只读守卫的代价)。
|
|
422
457
|
4. **旧版对照**:`git stash` 或 checkout `v0.3.0` 后 `git diff v0.3.0 -- src test` 可看到本轮的全部改动面;`.credentials.yaml` 的基线(大小与 SHA256)应在复测前后逐字节不变。
|
|
@@ -455,6 +490,11 @@ if ($env:DSH_SECRET_OPENAI) { "present length=$($env:DSH_SECRET_OPENAI.Length)"
|
|
|
455
490
|
|
|
456
491
|
### 已知限制(如实记录)
|
|
457
492
|
|
|
493
|
+
- **粘贴动作(R2)的覆盖边界(如实)**:composer 侧只能把文本 `insertText` 到光标处,**没有受控 value 写入能力**;**第三方插件自建的输入框不在覆盖范围内**——我们只能在官方座位放自己的按钮。我方那 7 个可编辑字段则是「与 `onChange` 同路」的受控写入(走同一个 setter)。
|
|
494
|
+
- **粘贴自动转胶囊(R3)只在值输入框、且只有真登记**:只覆盖胶囊的「密钥内容」;改值面 / 管理确认卡 / 索要卡片**都不做**自动登记,`@DSH_SECRET_*` 等引用文本被分类器第一步排除(引用是名字,不是材质)。
|
|
495
|
+
- **`selectionchange` 实时文案是契约外手段(R5 的 C)——需真人确认**:公开面只能**按需**读取选区(pull 式),**选区变化没有通知**;因此那条按钮的实时文案依赖对 DOM `selectionchange` 的监听。**失效模式**:① 非浏览器环境(没有 `document`)**不注册**,行为退回「按下时切换文案」;② 回调抛错被吞掉,同样退回;③ 没有可编辑元素聚焦时回答「无信息」(不猜);④ 选区变化不生成本插件的事件,文案可能**滞后一个事件循环**。**关键(不得当成已验证)**:注册这一层在本套件里**未经执行验证**(测试用的 React stub 把 `useEffect` 置为 no-op,用例只断言到 props 层),**也从未在活体 DOM 上验证过** ⇒ **需真人确认**;并且**任何行为都不依赖 C**:C 只影响文案的实时性,不影响按下时刻的判定、登记与退回。
|
|
496
|
+
- **测试缝的完整新增键(全部 test-only,无生产路径读取)**:`__cordisSecretAttach` 在 0.5.0 新增 `ATTACH_CSS`、8 个 privacy 键(`PRIVACY_RULES` / `PRIVACY_EXCLUSIONS` / `PRIVACY_THRESHOLDS` / `classifyPastedText` / `entropyBitsPerChar` / `distinctCharCount` / `isReferenceText` / `hasCjk`)、`ATTACH_EN`、`selectedSpan`、`selectedTextIn`、`liveSelectionCue`、`DEFAULT_ATTACH_SCOPE`;全部是常量或纯函数,**无状态、不含任何值**。
|
|
497
|
+
- **0.5.0 起需真人确认的项(一律不得写成已验证)**:① 浏览器里**是否真的不再弹自动填充建议 / 密码管理器是否仍捕获**(0.4.2 / 0.4.3 的抑制属性只能机械断言,现场行为仍要人看);② **剪贴板读取的授权提示**(`navigator.clipboard.readText()` 是否被拒、提示是否可理解);③ **R5 的实时文案**(契约外 `selectionchange`,见上一条);④ composer「粘贴」插入后的**编辑器焦点与光标位置**观感。
|
|
458
498
|
- **转录气泡里的胶囊由 shipped 代码渲染,插件改不了那次点击的目标**:`@DSH_SECRET_*` 在用户消息气泡里被 `dsh-client-ui-primitives` 的 `projectUserText` 渲染成 `data-ref-chip="file"`,点击走它硬编码的 `openFile('DSH_SECRET_OPENAI')`。**0.3.0 起**:该 `openFile` 解析出的地址(`dsh-resource://file/session/<id>/DSH_SECRET_X`)已被本插件的右栏查看器接管,因此点击打开的是**我们的详情页**;0.2.x 的行为(打开一个同名文件)作为历史如实保留,且**接管随时可能被同档更长的 pattern 抢走**(见「0.3.0 的四项增强 → 原胶囊的接管是长度竞争」)。**编辑器内(草稿)的胶囊不受影响**:那里是插件自己注册的 `secret` 引用源,点击打开只读详情胶囊。同一条 shipped 规则也适用于 `agent/pre-step` 追加的那行注记(它正文里同样含 `@DSH_SECRET_*`,因此也会显示为 file chip)。逐行证据与"为什么不存在既是 chip 又不可点的 `@` 形态"见「人类主动附加密钥(反方向)→ 已知限制」。
|
|
459
499
|
- **工具结果里的 `@DSH_SECRET_*` 没有注记解释**(值无关,不是泄漏):注记只为**本步引入的、载有标记的用户消息**追加,因此同一形状的标记出现在工具结果里时原样进入模型上下文且无注记覆盖,模型可能按"`@` = 文件路径"去解读它(会失败)。它不会因此获得任何值,也不影响绑定、授权或 shell 注入。详见「人类主动附加密钥(反方向)→ 已知限制」。
|
|
460
500
|
- **`ctx.authorization` 只承载 `persistent`**:该 seam 的契约要求"本次尝试期间提交并观察到一条凭据记录"(否则 `NOT_COMMITTED`),而 `session` 授权按定义不得落盘。因此 `session` 请求走同一套对话框、但不进该 seam;`persistent` 请求完整走 `registerFlow` + `begin`。这是 seam 契约决定的取舍,不是省事。
|
|
@@ -521,6 +561,12 @@ npm test # node --test 八个文件:
|
|
|
521
561
|
npm run build # 产出 lib/(Host 半 + 浏览器产物 ./client)
|
|
522
562
|
```
|
|
523
563
|
|
|
564
|
+
**0.5.0 给测试面加的东西(如实记录)**:
|
|
565
|
+
|
|
566
|
+
- **新用例面**:`test/unit.test.ts` 增加 R1 的命名纯函数(形态 token 化 / 提示词不含明文 / 本地兜底键与去重 / 模型答案清洗 / 模型挂起不阻塞,deadline 内必答)与 R3 的分类器(规则、排除项、阈值,命中与不命中两侧,含 31/32 这类边界);`test/register.test.ts` 增加 R1 的接线(无 llm 时激活且 attach 成功=正对照、模型键被采用且**不带 sessionId / 不含明文 / 不 append 事件**、答案不可用则回落、无 route 时不调模型);`test/client-attach.test.ts` / `test/client-card.test.ts` 增加 R1 空键与非法键、R2 的七处「粘贴」动作与 composer 座位、R3 的两条粘贴路径与三条失败分支、R4 的 ✕ 解绑与显隐、R5 的选区转换与退回。
|
|
567
|
+
- **测试基础设施的一处修正(如实记录)**:渲染器从「所有组件共用一份 state 数组」改成**每个组件各自独立的 state**(对齐真实 React 语义;此前共用一个数组会让不同组件的 `useState` 槽互相污染)。
|
|
568
|
+
- **测试 fetch 助手的另一处修正(如实记录)**:从「按前缀匹配」改成**按 `pathname` 精确路由**——前缀匹配会**把 `/api/secret.attached` 误配到 `/api/secret.attach`**(前者以前缀包含后者),这个历史教训留在用例注释里。
|
|
569
|
+
|
|
524
570
|
## 设计要点
|
|
525
571
|
|
|
526
572
|
- **附加方向的两段式生命周期**:值先在客户端本地 state,再经一次 POST 进 Host 内存的暂存表;只有携带标记的用户消息成为 durable 事件时才提升为 grant,`anchorSeq` 取该事件的 seq。这样"未发送就永不注入",且锚点不依赖任何"回头扫日志"的启发式。
|
|
@@ -576,7 +622,7 @@ npm stage approve <stage-id> # 需要 2FA
|
|
|
576
622
|
判断该版本是否已存在于目标 registry,已存在就跳过发布(并在 Step Summary 写明"该版本已存在,跳过发布"),
|
|
577
623
|
因此对已发布版本重推 tag 不会产生必然失败的公开红叉。其它检查错误(网络、鉴权、registry 故障)仍会让 job 失败。
|
|
578
624
|
|
|
579
|
-
**registry 现状(截至本文)**:npmjs 上已上线 `0.0.0-stage`、`0.1.0`、`0.2.0`、`0.2.1`、`0.3.0`、`0.4.1`、`0.4.2`(`dist-tags.latest = 0.4.2`,2026-10-08T12:09:17Z 上线)。三个 0.4.x 的 stage 队列历史与**现状**逐个写清,避免批错版本:`0.4.0` 的 stage id `dd9ef8e8-866b-498d-a8f5-8ee33e32f62f`(shasum `22dfadfc613695018adfd5ee09eb8b71b1ba9edc`)**从未被批准、从未上线**(该制品有启动缺陷,见「0.4.0 的发布级缺陷与 0.4.1 修复」),建议显式丢弃:`npm stage reject dd9ef8e8-866b-498d-a8f5-8ee33e32f62f`;`0.4.1` 的 stage id `22abf7a3-606b-4ee4-a1f9-5e84b557d6d0`(shasum `9b64a832abad45123ec96b3c2eec398c4b7377dd`,provenance `logIndex=3142251804`)**已由用户 2FA 批准上线**(2026-10-08T09:30:00Z),现已被 0.4.2 取代;`0.4.2` 的 stage id `a4056fb7-0a1a-4740-91d3-856665498b5e`(shasum `60e61cc5395e2f279a8b9a0a769165ecf4b3c403`、integrity `sha512-S34f977fgdNiR…HDV7DobbX5K8A==`、provenance `logIndex=3146400254`;由 tag `v0.4.2` 推送触发:Release run `37764853236` / Publish run `37764853223`,tag 对象 `b5a20422edde9b3951ba433945c72c9a430c3e0f`(GitHub 显示 verified),Release <https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.2>)**已由用户 2FA 批准上线**(2026-10-08T12:09:17Z),即当前的 `dist-tags.latest
|
|
625
|
+
**registry 现状(截至本文)**:npmjs 上已上线 `0.0.0-stage`、`0.1.0`、`0.2.0`、`0.2.1`、`0.3.0`、`0.4.1`、`0.4.2`(`dist-tags.latest = 0.4.2`,2026-10-08T12:09:17Z 上线)。三个 0.4.x 的 stage 队列历史与**现状**逐个写清,避免批错版本:`0.4.0` 的 stage id `dd9ef8e8-866b-498d-a8f5-8ee33e32f62f`(shasum `22dfadfc613695018adfd5ee09eb8b71b1ba9edc`)**从未被批准、从未上线**(该制品有启动缺陷,见「0.4.0 的发布级缺陷与 0.4.1 修复」),建议显式丢弃:`npm stage reject dd9ef8e8-866b-498d-a8f5-8ee33e32f62f`;`0.4.1` 的 stage id `22abf7a3-606b-4ee4-a1f9-5e84b557d6d0`(shasum `9b64a832abad45123ec96b3c2eec398c4b7377dd`,provenance `logIndex=3142251804`)**已由用户 2FA 批准上线**(2026-10-08T09:30:00Z),现已被 0.4.2 取代;`0.4.2` 的 stage id `a4056fb7-0a1a-4740-91d3-856665498b5e`(shasum `60e61cc5395e2f279a8b9a0a769165ecf4b3c403`、integrity `sha512-S34f977fgdNiR…HDV7DobbX5K8A==`、provenance `logIndex=3146400254`;由 tag `v0.4.2` 推送触发:Release run `37764853236` / Publish run `37764853223`,tag 对象 `b5a20422edde9b3951ba433945c72c9a430c3e0f`(GitHub 显示 verified),Release <https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.2>)**已由用户 2FA 批准上线**(2026-10-08T12:09:17Z),即当前的 `dist-tags.latest`;`0.4.3` 的 stage id `9cc6b07d-c766-480c-aaf7-ae27bf92a909`(shasum `ad704fadf5a40a1f25f75a1f335fb4492db3f96d`、integrity `sha512-Y0uUC18gdQvCo…lsva4nx5iVumg==`、provenance `logIndex=3150146502`;由 tag `v0.4.3` 推送触发:Release run `37798652085` / Publish run `37798652062`,tag 对象 `ac5e96deca20b506852c98b96820a06990275478`、GitHub 显示 verified,Release <https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.3>)**已放入队列、尚未批准**——**此刻队列里唯一的待批准版本就是 `0.4.3`**(批准用 `npm stage approve 9cc6b07d-c766-480c-aaf7-ae27bf92a909`;已上线的版本不能再 `npm stage reject`)。本机 npm CLI 12.2.0 的 `npm stage` 确实有 `reject` 子命令,帮助文本:「Reject a staged package, removing it from the registry」,可用 `--otp <otp>`——**批准与丢弃都需要登录 + 2FA,只能由人来做**;本发布流程不登录 npm、不读取或处理任何用户令牌、不代做任何需要 2FA 的动作。GitHub Packages 上 `0.1.0`/`0.2.0`/`0.2.1`/`0.3.0`/`0.4.1`/`0.4.2`/`0.4.3` 都在(`0.4.3` 的包版本 id `1356811507`;`0.4.2` 的是 `1355352850`;`0.4.1` 的是 `1354170442`;上一个可用版本 `0.3.0` 的是 `1346087280`),另有**不可用**的 `0.4.0`(包版本 id `1351737947`)——GitHub Packages 由 CI 直接发布、不经人工批准,所以撤不回,只能靠 Release notes 的警告与本文说明。
|
|
580
626
|
|
|
581
627
|
同一份包也会发布到 **GitHub Packages**(`npm.pkg.github.com`,`github-packages` job,用内置 `GITHUB_TOKEN`),
|
|
582
628
|
使包在仓库页面上可见、可被 `@xinvxueyuan:registry=https://npm.pkg.github.com` 的消费者安装。
|
|
@@ -606,7 +652,13 @@ npm stage approve <stage-id> # 需要 2FA
|
|
|
606
652
|
|
|
607
653
|
### GitHub Release 与签名
|
|
608
654
|
|
|
609
|
-
> **已发生的事实**:`v0.4.
|
|
655
|
+
> **已发生的事实**:`v0.4.3` 的 Release 是 https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.3
|
|
656
|
+
> (2026-10-08 发布,`draft: false`;`release.yml` 运行 `37798652085` 成功,`publish.yml` 运行 `37798652062` 成功),附件三件:`xinvxueyuan-cordis-plugin-secret-0.4.3.tgz`(275823 B,sha256 `a9f46d191c19923370999d99826362c8585ce13d996fa628fa2d47c51d92a895`,与 `SHA256SUMS` 里记的一致,也与本机 `core.autocrlf=false` 干净克隆里 `npm pack` 的产物逐字节一致)、`SHA256SUMS`(109 B,sha256 `101ae23dfe5a518ac7fd90d8d31081763c885833d29fe90bf2c925394f0c8c00`)、`SHA256SUMS.asc`(887 B,sha256 `4db2af4f46d7a11d49692a198136a9d97c4043fac1d9de4674d61d4ceab745ba`;RSA 4096 `6C6FD9B2…72B7B35A` 对 **CI 那份 `SHA256SUMS`** 的分离签名,本机 `gpg --verify` 通过);
|
|
657
|
+
> tgz 与 SHA256SUMS 都由 `gh attestation verify` 可验(一份 attestation,subject 同时列出两者),且**证明绑定在 tag 上**(builder id / 签名证书 SAN = `.../release.yml@refs/tags/v0.4.3`,`externalParameters.workflow.ref` = `refs/tags/v0.4.3`,`resolvedDependencies` = `git+https://github.com/xinvxueyuan/cordis-plugin-secret@refs/tags/v0.4.3` @ commit `05f0b142af5b2c0aff0c6668b43d985ad5270e61`)——即 tag→commit 是证明的一部分,而不是只绑定到 `refs/heads/main`(`--format json` 全文对 `refs/heads/` **零命中**)。tag 对象为 annotated + GPG 签名
|
|
658
|
+
> (`git cat-file -t v0.4.3` → `tag`,tag 对象 sha `ac5e96deca20b506852c98b96820a06990275478`;GitHub API 的 `verification.verified` → `true`,`reason` → `valid`,`verified_at` → `2026-10-08T15:12:27Z`)。
|
|
659
|
+
> 上一个版本 `v0.4.2` 的 Release 是 https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.2
|
|
660
|
+
> (2026-10-08 发布,`draft: false`;同形三附件),`xinvxueyuan-cordis-plugin-secret-0.4.2.tgz` 271615 B、sha256 `90574cef477c64b152a6d7062e920f62dce01892e185e3d8bb8f11d4d40a51b9`(与 `SHA256SUMS` 一致),tag 对象 `b5a20422edde9b3951ba433945c72c9a430c3e0f`(GitHub 显示 verified);该版本**已由用户 2FA 批准上线**(`dist-tags.latest`)。
|
|
661
|
+
> 再上一个版本 `v0.4.1` 的 Release 是 https://github.com/xinvxueyuan/cordis-plugin-secret/releases/tag/v0.4.1
|
|
610
662
|
> (2026-10-08 发布,`draft: false`;`release.yml` 运行 `37735146290` 成功,`publish.yml` 运行 `37735146256` 成功),附件三件:`xinvxueyuan-cordis-plugin-secret-0.4.1.tgz`(262988 B,sha256 `46286a2eefebb0809696377ebfecf7569c8aa723287cbe7a8e2c7bee979a76f8`,与 `SHA256SUMS` 里记的一致,也与本机 `core.autocrlf=false` 干净克隆里 `npm pack` 的产物逐字节一致)、`SHA256SUMS`(109 B,sha256 `ddd3b78232803958fefd699bf7488d208db97462a8985eef891a766598f643dd`)、`SHA256SUMS.asc`(887 B,sha256 `3ded718895f81d5b03b8b6a1037f2b1138d87d10dd9f58523b88a67bc3934ec1`;RSA 4096 `6C6FD9B2…72B7B35A` 对 **CI 那份 `SHA256SUMS`** 的分离签名,本机 `gpg --verify` 通过);
|
|
611
663
|
> tgz 与 SHA256SUMS 都由 `gh attestation verify` 可验(一份 attestation,subject 同时列出两者),且**证明绑定在 tag 上**(builder id / 签名证书 SAN = `.../release.yml@refs/tags/v0.4.1`,`externalParameters.workflow.ref` = `refs/tags/v0.4.1`,`resolvedDependencies` = `git+https://github.com/xinvxueyuan/cordis-plugin-secret@refs/tags/v0.4.1` @ commit `744f5a675d3641cbf6a7dd0121e88b5fa333f4bf`)——即 tag→commit 是证明的一部分,而不是只绑定到 `refs/heads/main`(`--format json` 全文对 `refs/heads/` **零命中**)。tag 对象为 annotated + GPG 签名
|
|
612
664
|
> (`git cat-file -t v0.4.1` → `tag`,tag 对象 sha `43da6f8a85797be40356691db38079de50fa0844`;GitHub API 的 `verification.verified` → `true`,`reason` → `valid`,`verified_at` → `2026-10-08T06:00:40Z`)。
|
|
@@ -631,25 +683,25 @@ npm stage approve <stage-id> # 需要 2FA
|
|
|
631
683
|
| 构建来源证明 | `release.yml` 调用 `actions/attest-build-provenance`(pin 到 commit SHA),为 **tgz 与 SHA256SUMS 两者**生成 Sigstore 签名的 SLSA 构建来源证明,可用 `gh attestation verify` 校验。 |
|
|
632
684
|
| npm 侧 | `release.yml` **完全不执行任何 npm publish**;npm 发布只由上面的 `publish.yml` staged publishing 负责。 |
|
|
633
685
|
|
|
634
|
-
维护者操作顺序(`v0.2.0`、`v0.2.1`、`v0.3.0`、`v0.4.0` 与 `v0.4.
|
|
686
|
+
维护者操作顺序(`v0.2.0`、`v0.2.1`、`v0.3.0`、`v0.4.0`、`v0.4.1`、`v0.4.2` 与 `v0.4.3` 都已按此执行):
|
|
635
687
|
|
|
636
688
|
```sh
|
|
637
689
|
# 1) 本机确认工作区干净、package.json 的 version 已就位(版本号由发布者手工提升)
|
|
638
690
|
git status --porcelain
|
|
639
691
|
|
|
640
692
|
# 2) 创建 annotated + GPG 签名 tag(私钥仅在本机使用;本机需能完成 GPG 签名)
|
|
641
|
-
git tag -s v0.4.
|
|
693
|
+
git tag -s v0.4.3 -m "v0.4.3"
|
|
642
694
|
|
|
643
695
|
# 3) 只推 tag —— release.yml 会构建产物、生成来源证明并创建 Release(不要用 workflow_dispatch:那样证明会绑到 refs/heads/main)
|
|
644
|
-
git push origin v0.4.
|
|
696
|
+
git push origin v0.4.3
|
|
645
697
|
|
|
646
698
|
# 4) 取 Release 上 CI 生成的那份 SHA256SUMS,做分离签名并附回 Release(私钥不进 CI)
|
|
647
|
-
gh release download v0.4.
|
|
699
|
+
gh release download v0.4.3 --pattern SHA256SUMS --clobber
|
|
648
700
|
gpg --armor --detach-sign SHA256SUMS # 生成 SHA256SUMS.asc
|
|
649
|
-
gh release upload v0.4.
|
|
701
|
+
gh release upload v0.4.3 SHA256SUMS.asc --clobber
|
|
650
702
|
```
|
|
651
703
|
|
|
652
|
-
> **为什么第 4 步要下 CI 的 `SHA256SUMS` 而不是签本机 `npm pack` 那份**:本机是 Windows 且 `core.autocrlf=true`,工作树里 `LICENSE-MIT`/`LICENSE-APACHE` 被 checkout 成 CRLF,本机打出的 tgz 与 CI(Linux checkout,LF)打出的只差这两个文件的行尾——`0.4.0`:本机 260311 B / `0a6fd66c…` 对 CI 260287 B / `4612be9c…`;`0.4.1`:本机 263015 B / `4b3c3a7d…` 对 CI 262988 B / `46286a2e…`。在 `core.autocrlf=false` 的干净克隆里本机 `npm pack` 复现出的正是 CI 的那份(`docs/` 之类不进包,`lib/**` 与 `src/**` 逐字节相同)。签错那份会让 `gpg --verify SHA256SUMS.asc SHA256SUMS` 在下载来的附件上对不上。
|
|
704
|
+
> **为什么第 4 步要下 CI 的 `SHA256SUMS` 而不是签本机 `npm pack` 那份**:本机是 Windows 且 `core.autocrlf=true`,工作树里 `LICENSE-MIT`/`LICENSE-APACHE` 被 checkout 成 CRLF,本机打出的 tgz 与 CI(Linux checkout,LF)打出的只差这两个文件的行尾——`0.4.0`:本机 260311 B / `0a6fd66c…` 对 CI 260287 B / `4612be9c…`;`0.4.1`:本机 263015 B / `4b3c3a7d…` 对 CI 262988 B / `46286a2e…`;`0.4.3`:本机 275850 B / `565707f0…` 对 CI 275823 B / `a9f46d19…`。在 `core.autocrlf=false` 的干净克隆里本机 `npm pack` 复现出的正是 CI 的那份(`docs/` 之类不进包,`lib/**` 与 `src/**` 逐字节相同)。签错那份会让 `gpg --verify SHA256SUMS.asc SHA256SUMS` 在下载来的附件上对不上。
|
|
653
705
|
|
|
654
706
|
校验方式:
|
|
655
707
|
|
|
@@ -661,9 +713,9 @@ sha256sum -c SHA256SUMS
|
|
|
661
713
|
gpg --verify SHA256SUMS.asc SHA256SUMS
|
|
662
714
|
|
|
663
715
|
# 校验构建来源证明(需要 gh CLI)
|
|
664
|
-
gh attestation verify xinvxueyuan-cordis-plugin-secret-0.4.
|
|
716
|
+
gh attestation verify xinvxueyuan-cordis-plugin-secret-0.4.3.tgz --repo xinvxueyuan/cordis-plugin-secret
|
|
665
717
|
gh attestation verify SHA256SUMS --repo xinvxueyuan/cordis-plugin-secret
|
|
666
|
-
gh attestation verify xinvxueyuan-cordis-plugin-secret-0.4.
|
|
718
|
+
gh attestation verify xinvxueyuan-cordis-plugin-secret-0.4.3.tgz --repo xinvxueyuan/cordis-plugin-secret --format json # 查 externalParameters.workflow.ref 是否为 refs/tags/v0.4.3
|
|
667
719
|
```
|
|
668
720
|
|
|
669
721
|
补充说明:
|