@eddyskywalker/dsh-chatgpt-subscription 0.9.8 → 0.10.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/CHANGELOG.md +27 -0
- package/README.md +41 -1
- package/lib/client.js +29 -4
- package/lib/client.js.map +1 -1
- package/lib/index.js +76 -6
- package/lib/types/client/claude/locales.d.ts +2 -2
- package/lib/types/client/command-code/locales.d.ts +2 -2
- package/lib/types/client/kimi-code/locales.d.ts +2 -2
- package/lib/types/client/styles.d.ts +19 -0
- package/lib/types/client/styles.d.ts.map +1 -1
- package/lib/types/host/agent-preset.d.ts.map +1 -1
- package/lib/types/host/claude/mapper.d.ts +11 -2
- package/lib/types/host/claude/mapper.d.ts.map +1 -1
- package/lib/types/host/subagent-model-authorization.d.ts +7 -0
- package/lib/types/host/subagent-model-authorization.d.ts.map +1 -1
- package/package.json +1 -1
- package/presets/dispatch/agent.cordis.yml +13 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,33 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
- **适配 DSH 的 Agent Teams:`dispatch` 自适应协作模式,守卫把 `spawn_teammate` 纳入 inherit 模式**。
|
|
6
|
+
- **根因(实测,非推测)**:Agent Teams bundle 在**每个会话自己的作用域**里注册了一套与内置**同名**的协调工具,而工具注册表按作用域链解析、**近层遮蔽远层**(`packages/core/tools/src/index.ts:1185-1208`;`tool-agent-team` 的安装点是 `agent.ctx`,见其 `src/index.ts:402-421`)。因此在装了该 bundle 的部署上,内置 `tool-subagent-control` 的 `send_message` / `list_agents` / `interrupt_agent` 被 teammate 版顶掉;`roster.membership()` 对任何无 `parentSession` 的顶层会话都返回「自己是 lead」,于是**每个顶层会话都被替换**。本机 web profile 的 `dsh.profile.bundles` 恰好包含该 bundle。
|
|
7
|
+
- **现场证据**:`list_agents({})` 只返回 `[{ target: "lead", role: "lead", status: "running" }]`,当轮真正派出的 continuable 子代理不在其中;`send_message({ target: "<该子代理 session id>" })` 抛 `active teammate "…" not found`(文案出自 `agent-team/src/roster.ts:52`)。后果是「派得出去、回访不了」:R5 等证据与 R6 带证据重派在这类部署上不可用。
|
|
8
|
+
- **守卫改动(按既有结论落地)**:`DEFAULT_SUBAGENT_INHERIT_TOOLS` 由 `['subagent_fork']` 改为 `['subagent_fork', 'spawn_teammate']`。理由:Agent Teams 经 `ctx.subagents.startContinuable` 创建成员时**完全不传 `agentOptions``**,成员必然继承 Lead 路由,且工具没有任何路由参数——「工具不能选」不等于「这个选择被授权」,所以它属于 inherit 语义(同 `subagent_fork`),而非 explicit。**绝不能**把它加进 `subagentModelTools`:那里要求成对 `provider`+`model`,该工具没有这两个字段,会让每次 teammate 创建被硬拒且模型无法自救(为此在守卫测试里加了专门断言)。
|
|
9
|
+
- **persona 改动(自适应,保持精简)**:新增 **R0.5 协作模式侦测**(判据:`spawn_teammate`/`wait_agent` 是否存在;一句「分界不是能不能派,而是派完能不能追问」)与 **R-T teammate 模式**(协调走成员词汇;`spawn_teammate` 无路由参数、不写 route 行;选型与任务板落点),R8 补一行 teammate 边界,反模式补 2 条;【角色识别】扩为「子代理或 teammate」。全部按精简原则写:R0.5 由 7 行压到 4 行、R-T 由 7 条压到 4 条、反模式由 3 条并为 2 条,并删去可由工具描述本身传达的细节(`wait_agent` 的语义、`scope` 辅助判据等)。
|
|
10
|
+
- **验证**:`npx tsc -b --force` 与 `npx tsc -p test/tsconfig.json` **均 0 错误**;`npm run build` 成功且 `lib/index.js` 内确认带有 R0.5/R-T 文本;全量 `npx vitest run` **1615 passed / 7 skipped**(112 文件,1 skipped);新增守卫测试(teammate 归 inherit、越权拒绝文案是 inherit 版而非 explicit 版、授权路由放行、`[]` 退出、显式命名路由被 override 分支拒绝)与 preset 测试(两种形态 parity 逐字节 + R0.5/R-T/反模式文本钉住);**变异验证**:只改 YAML / 只改 TS / 两份同时改同一措辞(parity 仍成立时行为测试单独失败)三种情况均被捕获。
|
|
11
|
+
- **已记录的上游限制**:`spawn_teammate` 无法指定 `provider`/`model`——三层已核对(工具 schema 无此参数;`SpawnTeammateRequest.provider` 是 subagent 后端名而非 LLM provider 且无 `model`;底层 `ContinuableStartSpec.request.agentOptions` 本可携带,但 Team 服务构造 request 时只放 `prompt`+`parent` 整个省略)。插件侧无干净绕法(pre-execute 明确排除输入改写;`agentTeams` 服务内部丢弃后包装不到;自定义 provider 拿不到已在 continuation manager 解析完的路由;`selectForNextRequest` 只作用于下一个请求且与 teammate 立即开跑构成竞态)。README 新增「已知上游限制」一节,并写明**上游接出 `agentOptions` 后**应把 `spawn_teammate` 从 `subagentModelInheritTools` 移到 `subagentModelTools`。
|
|
12
|
+
- **未验证项(如实说明)**:本次是**提示词 + 守卫默认值 + 测试**改动,验证的是「两种形态一致 + 守卫判定符合预期」,**不是**「真实会话里模型一定先侦测再动手」。另需注意一个**已实测的副作用**:本机 web profile 的 `agent-default-model` 是 `workbuddy-subscription/deepseek-v4.1-flash`,而 `subagent-model-selection-settings.allowedModels` 只勾了 `command-code/deepseek/deepseek-v4.1-flash`——两条路由不同,因此在该 profile 下开启允许清单的新会话里,`spawn_teammate` 与 `subagent_fork` 都会被视为「继承路由不在清单内」而被拒绝。要恢复 Agent Teams,需把当前会话路由切到清单内,或把该路由补进勾选清单;拒绝文案已包含这条恢复路径。
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
- **修掉「设置 UI 部分情况下丢失样式表」**(用户报告:设置页偶尔整体裸渲染,重启应用才恢复)。ChatGPT 页样式表(`.dsh-codex-*`)是全部六份客户端样式表里**唯一在卸载时删除元素**的:`installStyles()` 检测到同 id 标签已存在时返回**空 disposer**(放弃所有权),而 cordis 的插件 fiber 重启(改配置、依赖服务重启、HMR 热重载)允许新 fiber 的 `apply()` 在旧 fiber 异步排空 disposer **之前**运行(`runtime.fibers` 明确支持同插件多 fiber 并存)。这个顺序一旦发生:新 fiber 看到旧标签 → 返回空 disposer;旧 fiber 随后 `element.remove()` 删掉唯一一份;此后每次重启都命中同一去重分支,样式表**永不恢复**——正是「部分情况下」才触发的竞态。其余五份样式表(antigravity / claude / kimi / workbuddy / pool)的安装器本来就是「存在即跳过 + 空 disposer」的纯增量模式,天然不受影响;本修复把主样式表改成同一模式:**标签归文档所有(missing 则创建、存在则刷新 `textContent`、disposer 恒为空)**。刷新内容顺带修掉 HMR 场景的另一个小缺陷——重建 bundle 后旧标签的旧 CSS 一直残留(dev 环境改 CSS 不生效)。
|
|
16
|
+
- **验证**:`npm run typecheck` 0 错误;新增 `test/client-styles.test.ts`(7 条)锁定单标签不变量、disposer 不删节点、外部删除后可重建、旧 bundle 内容刷新,以及**真实 Cordis** 上的两条生命周期用例(fiber 重启 + 旧 fiber 迟到销毁、历史「fiber 持有删除」形状的恢复);**红绿验证**——把修复 `git stash` 后在同一测试上复跑,恰好那 2 条生命周期用例失败、恢复修复后 7 条全绿,证明测试真的覆盖该路径。合并 `upstream/master`(0.9.8 之后共 16 个提交,含 zhipu 线路换代为 claude)后复跑:`npx tsc -b --force` + `tsc -p test/tsconfig.json` 0 错误、`npm run build` 通过、`npx vitest run` **110 文件通过 / 1 跳过(112)、1600 条通过 / 9 跳过**;唯一失败的 `test/quota-ui.test.tsx` 2 条是**存量**问题与本修复无关(用例固定 UTC 边界日期 `2030-01-01`,在 UTC+8 机器上落到 `2029-12-31 19:00`,带不带本修复、合并前后同样失败)。
|
|
17
|
+
- **遗留说明**:其余安装器「存在即跳过」不去刷新 `textContent`,HMR 改它们的 CSS 仍需整页刷新才生效——纯 dev 体验问题,与丢失无关,未在本次改动。
|
|
18
|
+
- **修复用户实测缺陷:Claude 线路凡是有内容的回复必定失败(`Assistant stream chunk must be losslessly JSON-serializable`)**。现象是**每一轮**只要模型输出了文字/思考/工具调用,整轮就以这个错误告终,UI 显示"本轮运行失败";而传输失败、凭据失败这类**没有内容**的轮次反而正常,所以看起来像偶发。**这是本插件引入的缺陷,与用户环境无关。**
|
|
19
|
+
- **根因:`claude/mapper.ts` 把 `undefined` 写进了 finish 块的 `replayState.blocks`**。DSH 在会话日志接受每个 stream chunk 之前会做**无损 JSON 校验**,只要包含 JSON 无法承载的值(`undefined`、函数、symbol、非有限数、`-0`、数组空洞等)就**整块拒收**——代价是用户**整轮**对话,而不是一条元数据。三处写坏值:
|
|
20
|
+
- `closeTextBlock`:`state.replayBlocks[open.index] = undefined`(预留给文本块;文本本来就靠 DSH 自己持有的 block 回放,无需逐字条目);
|
|
21
|
+
- `content_block_start` 的 `tool_use` 分支:`state.replayBlocks[index] = undefined`(先占位、等 `content_block_stop` 填入);
|
|
22
|
+
- `closeToolCall`:`input: parsed`,而 `safeJsonParse` 在**缺参数或 JSON 解析失败**时返回 `undefined`。
|
|
23
|
+
- **`JSON.parse` 本身也不是安全来源**(这一条超出了原始报告):它接受线上真会出现的 `-0` 与越界字面量,`{"n":-0}` 解析出负零、`{"n":1e999}` 解析出 `Infinity`,两者都不能无损往返。所以只把 `undefined` 换成 `null` 并不够。
|
|
24
|
+
- **修法(四件事)**:① 两处占位改写成 `null`(合法的 JSON 值,语义正是"此处无逐字条目");② 新增 `jsonSafeValue()`,把值收敛进可无损往返的子集——有限数字通过、`-0` 归一为 `0`、数组变稠密、普通对象只保留可枚举字符串键并丢弃无 JSON 形式的字段(函数/symbol/bigint/undefined)、带环则判为无形式;③ `input` 经 `jsonSafeValue(parsed) ?? {}` 规范化;④ `closeStream` 出口再整体过一遍 `jsonSafeValue`,**未来若有新槽位忘记初始化,代价是一条回放条目而不是用户整轮**。
|
|
25
|
+
- **类型收紧,让同类缺陷编不过**:`ClaudeStreamState.replayBlocks` 由 `unknown[]` 改为 `(Record<string, unknown> | null)[]`。原先那两处 `= undefined` 从此是**编译错误**——这是本次唯一能在编译期拦截该类缺陷的手段。
|
|
26
|
+
- **回放语义不变**:读取端 `replayBlockFor` 用 `isRecord(entry) ? entry : undefined`,对它而言 `null` 与 `undefined` **无法区分**,因此带签名的 thinking 块依旧逐字回放(这是多轮工具调用的前提),文本块照旧由 DSH 自己的 block 重建。已用 `buildClaudeRequestBody`(真实读取路径,而非内部函数)专门断言这一点。
|
|
27
|
+
- **独立变异验证(不只是"跑绿了")**:把三处一起还原成原缺陷 → 新增测试 **5 条失败**;只还原出口守卫(三处仍是 `null`)→ **恰好 1 条**(守卫那条)失败,证明它防的是"未来漏写"而非重复覆盖;全部修复 → 7 条全绿。三次篡改均被捕获。
|
|
28
|
+
- **验证**:`npx tsc -b --force`(host + client)与 `npx tsc -p test/tsconfig.json`(测试树)**均 0 错误**;`npm run build` 成功;`test/claude-mapper.test.ts` **70 项通过**(原 63 + 新 7);全量 `npx vitest run` **1599 通过 / 3 失败 / 9 跳过**,3 个失败全部是 `test/web-provider-lifecycle.test.ts` 的 `URL hostname "example.com" resolves to a non-public IP address`——**与本次改动无关**:本机 DNS 把公网域名解析到 `198.18.0.0/15`(透明代理的 fake-IP),该文件在**未改动的干净工作树上单跑同样 3 失败**(已用 `git stash` 实证)。
|
|
29
|
+
- **未验证项(如实说明)**:本次**没有向真实 Anthropic 端点发过请求**。结论依托两点:一是缺陷链在本地用**真实插件代码 + 从 `app.asar` 抽出的 DSH 真实校验函数**复现(修复前 8 种流式形态 7 种被拒,修复后 8/8 通过),二是用用户会话日志里**两次真实失败轮次**的原始数据回放,修复后 `2/2` 产出被接受的 chunk。
|
|
30
|
+
- **影响面**:只有 `claude-subscription` 线路受影响(`antigravity` 的 `replayBlocks` 只经 `push()` 写入、从不留空位,实测无此问题);该文件其余适配器未受影响。
|
|
31
|
+
|
|
5
32
|
- **修复用户实测缺陷:调度模式一旦进入 R8 兜底就"卡死",任务已经成功完成,下一个任务仍不再派发子代理**。
|
|
6
33
|
- **根因不是状态泄漏,而是提示词缺了两件事**。全仓库检索确认:插件**没有任何**失败计数器、熔断标志或持久化状态(`src/` 里出现"连续失败"字样的唯一位置就是 R8 那句话本身),委派守卫 `subagent-model-authorization` 是**纯函数**(读设置/会话快照→判定→返回理由或 `undefined`),DSH 内核的子代理失败也是**一次性**的(失败即 `throw` 成 `isError`,不重试、不入状态)。所以"兜底状态"只存在于**对话历史**里:旧 R8 把「子代理连续失败」列为无阈值、无失效条件、无解除规则的许可,模型写下"进入兜底"之后,后续每轮都会把它当成仍然成立的既有结论——R0 要求每次重新分诊,R8 没有对应要求,R6 的"不通过就重新派发"也没有兜底之后的**恢复**路径。
|
|
7
34
|
- **修法:把兜底从"会话级口头结论"改成"任务级、有阈值、可解除"的规则**,四条同批落地:① R8 首句明确**任务级、非会话级**,只覆盖触发它的那一个任务;② 触发条件收紧为「同一任务书按 R6 **重新派发两次**仍不通过」(R6 同步补上这个上限),取代不可判定的"连续失败";③ 新增**解除规则**——下一个任务一律回到 R0 重新分诊,条件已解除就必须恢复派发,不得沿用或预设兜底;④ 新增两条反模式(把一次兜底沿用成整个会话默认;拿工具硬拒当当兜底理由而不修触发条件),并针对两类**会话内不会自愈**的硬拒绝给出修条件路径(`active child limit` → 等子代理结算;`subagent model selection` → 按错误里列出的授权路由写明 `provider`/`model`)。
|
package/README.md
CHANGED
|
@@ -245,6 +245,43 @@ DSH 模型选择器应显示 **“Codex(ChatGPT 订阅)”**。GPT-6 系列
|
|
|
245
245
|
|
|
246
246
|
> 路径解析不写死相对路径:`src/host/preset-sync.ts` 从模块位置向上查找最近的 `package.json` 作为包根,因此 `src/` 布局、打包后的 `lib/` 布局,以及通过 pnpm symlink / Windows junction 安装都能正确解析。注意 `fs.cpSync({ recursive: true })` 在 Node 22 + Windows 上遇到含非 ASCII 的源路径会直接崩进程(nodejs/node#54476),所以复制是逐条目实现的。
|
|
247
247
|
|
|
248
|
+
### 协作模式自适应(teammate / subagent)
|
|
249
|
+
|
|
250
|
+
DSH 的 Agent Teams(bundle `@deepseek-ai/dsh-experimental-agent-team-profile`)会在**每个会话自己的作用域**里注册一套与内置**同名**的协调工具。工具注册表按作用域链解析、**近的层遮蔽远的层**,所以一旦该 bundle 被组合进来,会话里的 `send_message`、`list_agents`、`interrupt_agent` 就都换成了 teammate 版:
|
|
251
|
+
|
|
252
|
+
- `list_agents()` 只列 Team 成员(Lead 自己显示为 `lead`),**看不到 `subagent` 派出去的子代理**;
|
|
253
|
+
- `send_message` 的 `target` 只接受成员名,把子代理的 session id 传进去会抛 `active teammate "…" not found`(实测);
|
|
254
|
+
- 于是「派得出去、回访不了」——`subagent` 的孩子只能等完成通知,无法追问。
|
|
255
|
+
|
|
256
|
+
`dispatch` preset 现在**每个任务开始时先侦测一次协作模式**(persona 的 R0.5),再按模式选择协调词汇(R-T):
|
|
257
|
+
|
|
258
|
+
| 侦测为 | 判据 | 协调词汇 | 派发 |
|
|
259
|
+
|---|---|---|---|
|
|
260
|
+
| teammate | `spawn_teammate` / `wait_agent` 存在 | `list_agents()`、`send_message({ target: <成员名> })`、`wait_agent()`、`team_task_*` | `spawn_teammate`(无路由参数,继承主代理路由)或 `subagent`(按 R2 显式路由) |
|
|
261
|
+
| subagent | 二者都不存在 | `list_agents()`、`send_message({ agent_id })`、`interrupt_agent` | `subagent` / `subagent_fork` |
|
|
262
|
+
|
|
263
|
+
两种模式下 R0 分诊、R1 职责边界、R3 自治、R6 验收都照常执行;**模型路由守卫(`subagentModelAuthorization`)不变**——它默认把 `spawn_teammate` 与 `subagent_fork` 同为 **inherit 模式**(见下),校验的是成员实际继承到的路由,而不是要求它给出并不存在的路由参数。
|
|
264
|
+
|
|
265
|
+
> **不要**把 `spawn_teammate` 加进 `subagentModelTools`。该列表是 **explicit** 模式:要求每次调用成对给出 `provider` + `model`,而 `spawn_teammate` 的参数里根本没有这两个字段(DSH 设计如此:成员一律继承 Lead 的路由),结果是每一次 teammate 创建都会被硬拒,且模型无法通过补参数自救。它的正确归属是 `subagentModelInheritTools`(默认已包含)。
|
|
266
|
+
|
|
267
|
+
### 已知上游限制:teammate 不能选模型
|
|
268
|
+
|
|
269
|
+
`spawn_teammate` 目前**无法**指定 `provider`/`model`,这不是插件的问题,而是 Agent Teams 有意不做 per-teammate 路由。三层都已核对:
|
|
270
|
+
|
|
271
|
+
| 层 | 能否带路由 | 证据 |
|
|
272
|
+
|---|---|---|
|
|
273
|
+
| 工具入参 | ❌ | `spawn_teammate` 的 schema 只有 `name`/`description`/`prompt`/`context` |
|
|
274
|
+
| Team 服务 | ❌ | `SpawnTeammateRequest.provider` 是 **subagent 后端名**(`spawn`/`fork`),不是 LLM provider;且无 `model` 字段 |
|
|
275
|
+
| 底层 API | ✅ | `ContinuableStartSpec.request` 可带 `agentOptions`(含 provider/model/reasoningEffort),但 Team 服务构造 request 时**只放 prompt 与 parent**,整个省略 |
|
|
276
|
+
|
|
277
|
+
因此成员必然跑在 Lead 自己的路由上——这正是守卫把它归 inherit 模式的原因,也是**不应**把它加进 explicit 的 `subagentModelTools` 的原因。
|
|
278
|
+
|
|
279
|
+
**插件侧无法绕过**,四条路都已验证死:pre-execute 明确排除输入改写(参数已被记录与呈现,`PreToolDecision` 只有 allow/deny/cancel/ask);`agentTeams` 服务在内部丢弃后包装不到;注册自定义 subagent provider 也拿不到路由(`agentOptions` 在 continuation manager 就已解析完,provider 只返回 `seed`);唯一能改子会话路由的 `selectForNextRequest` 只作用于「下一个请求」,而 teammate 创建后立刻开跑,抢不进去。
|
|
280
|
+
|
|
281
|
+
**上游正路**:在 `packages/experimental/agent-team/src/roster.ts` 构造 `startContinuable` 的 `request` 处补上 `agentOptions`,并给 `SpawnTeammateRequest` 加字段、给工具 schema 加参数、创建前接 `assertAllowedModelSelection`。**该改动落地后**,`spawn_teammate` 就应从 `subagentModelInheritTools` 移到 `subagentModelTools`(explicit)——否则它会变成「能选模型却没人管」。
|
|
282
|
+
|
|
283
|
+
> 实践提醒:inherit 模式校验的是**当前会话路由**。若当前会话跑在允许清单之外的路由上(例如默认模型与勾选清单不是同一条),teammate 与 `subagent_fork` 都会被拒绝——拒绝文案会指出恢复路径(改用显式路由的 `subagent`,或先把会话切到清单内的模型)。`subagent` 的显式路由不受影响。
|
|
284
|
+
|
|
248
285
|
### 配置项
|
|
249
286
|
|
|
250
287
|
| 字段 | 默认 | 含义 |
|
|
@@ -266,6 +303,8 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
|
|
|
266
303
|
- **子代理永远和主代理同一个模型**的历史行为由此消失:模型必须先用 `list_subagent_models` 查出已授权路由,再按拒绝理由里列出的路由(例如 `antigravity/gemini-3.8-flash`)重试;该工具也只展示已授权路由;
|
|
267
304
|
- 未记录允许列表的会话(例如恢复的旧会话、未启用该设置的会话)保持 DSH 原有行为(继承父级模型)。
|
|
268
305
|
|
|
306
|
+
**inherit 模式(`subagentModelInheritTools`,默认 `['subagent_fork', 'spawn_teammate']`)**:有些委派工具**按设计**就没有路由参数,子代理必然跑在调用者自己的路由上——`subagent_fork`(复用对话与 KV Cache)与 Agent Teams 的 `spawn_teammate`(成员一律继承 Lead 的路由)都是这一类。「工具不能选」不等于「这个选择被授权」,所以守卫改为校验它**实际继承到的路由**:允许清单内有该路由就放行,没有就拒绝,并说明该工具无法改路由、要么改用能显式指定路由的 `subagent`、要么先把本会话切到清单内的模型。设为 `[]` 可关闭 inherit 模式的全部校验。
|
|
307
|
+
|
|
269
308
|
守卫装在 DSH 工具注册表的调度入口上,因此 **`run_code`(代码模式 / PTC)里通过 SDK 调用的 `tools["subagent"]` 同样被拦截**:该子调用走的是与直接调用相同的 `prepare → guard → dispatch` 流水线,拒绝理由以 `ToolCallError` 抛回程序。也就是说模型无法靠把委派写进代码里绕过白名单(`test/subagent-model-authorization-ptc.test.ts` 用真实 `ToolRuntime` + 假 code runtime 验证了放行、缺省拒绝与越权拒绝三条路径)。
|
|
270
309
|
|
|
271
310
|
配置项(插件行 `config`,全部可省略):
|
|
@@ -273,7 +312,8 @@ DSH 设置页的「Subagent」卡片会把勾选的模型写成会话级的允
|
|
|
273
312
|
| 字段 | 默认 | 含义 |
|
|
274
313
|
|---|---|---|
|
|
275
314
|
| `subagentModelAuthorization` | `true` | 是否启用上述授权守卫;设为 `false` 回到 DSH 原有行为 |
|
|
276
|
-
| `subagentModelTools` | `['subagent']` |
|
|
315
|
+
| `subagentModelTools` | `['subagent']` | 需要授权的委派工具名(explicit:必须成对给出 `provider`+`model`);preset 里自定义了 `toolName` 时在此列出 |
|
|
316
|
+
| `subagentModelInheritTools` | `['subagent_fork', 'spawn_teammate']` | 按设计继承调用者路由的委派工具(校验实际继承到的路由);设 `[]` 关闭 |
|
|
277
317
|
| `subagentModelScope` | `session` | `session` 只约束记录了允许列表的会话;`preference` 额外用当前设置卡列表约束未记录的会话 |
|
|
278
318
|
|
|
279
319
|
改动只在设置卡片里保存过的勾选生效:设置改动只影响之后新建的会话(DSH 的会话快照语义),已运行的会话继续使用它自己记录的那份列表。
|
package/lib/client.js
CHANGED
|
@@ -7668,7 +7668,8 @@ window.__ModuleLoader__.load({
|
|
|
7668
7668
|
};
|
|
7669
7669
|
//#endregion
|
|
7670
7670
|
//#region src/client/styles.ts
|
|
7671
|
-
const
|
|
7671
|
+
const PLUGIN_ID = "@eddyskywalker/dsh-chatgpt-subscription";
|
|
7672
|
+
const STYLE_ID$4 = `${PLUGIN_ID}/main`;
|
|
7672
7673
|
const CSS = `
|
|
7673
7674
|
.dsh-codex-page{box-sizing:border-box;color:var(--dsw-alias-label-primary);display:flex;flex-direction:column;gap:20px;max-width:780px;min-width:0;padding:2px 0 30px}
|
|
7674
7675
|
.dsh-codex-page *{box-sizing:border-box}
|
|
@@ -7776,14 +7777,38 @@ window.__ModuleLoader__.load({
|
|
|
7776
7777
|
@media(max-width:560px){.dsh-codex-row,.dsh-codex-pref-row,.dsh-codex-quota-fact,.dsh-codex-reset-credits{align-items:flex-start;flex-direction:column;gap:3px}.dsh-codex-value,.dsh-codex-quota-fact strong{text-align:left}.dsh-codex-actions{justify-content:flex-start}.dsh-codex-grouphead{align-items:flex-start;flex-direction:column;gap:0;padding:12px 0}.dsh-codex-meter-meta{align-items:flex-start;flex-direction:column;gap:2px}.dsh-codex-errorbar{align-items:flex-start;flex-direction:column}.dsh-codex-segments{width:100%}.dsh-codex-select{max-width:none;width:100%}.dsh-codex-context-row{align-items:flex-start;flex-direction:column;gap:5px}.dsh-codex-segments label{flex:1}.dsh-codex-segments span{text-align:center}.dsh-codex-image-tool{max-width:100%;margin-left:0}}
|
|
7777
7778
|
@media(prefers-reduced-motion:reduce){.dsh-codex-meter>span{transition:none}.dsh-codex-skeleton span{animation:none}}
|
|
7778
7779
|
`;
|
|
7780
|
+
/**
|
|
7781
|
+
* Install the ChatGPT tab's stylesheet as a document-owned, never-removed
|
|
7782
|
+
* `<style>` element.
|
|
7783
|
+
*
|
|
7784
|
+
* The other client stylesheets this plugin installs are additive: their
|
|
7785
|
+
* installers no-op when the element already exists and their disposers do
|
|
7786
|
+
* nothing, so a restart can never take the styles away. This one used to
|
|
7787
|
+
* create *and remove* its element, and the dedupe handoff was racy: on a
|
|
7788
|
+
* plugin fiber restart (config change, dependency reload, HMR) the new fiber
|
|
7789
|
+
* ran `apply()` before the old fiber's async unload removed the element —
|
|
7790
|
+
* dedupe saw the old tag, returned a no-op disposer, and the old unload then
|
|
7791
|
+
* deleted the only copy. Every later restart kept returning the no-op, so the
|
|
7792
|
+
* stylesheet never came back and the settings page rendered unstyled.
|
|
7793
|
+
*
|
|
7794
|
+
* Fix: treat the tag as owned by the document rather than by one fiber —
|
|
7795
|
+
* create it when missing, refresh `textContent` when a rebuilt bundle ships
|
|
7796
|
+
* different CSS (HMR), and return a no-op disposer unconditionally, matching
|
|
7797
|
+
* the additive installers.
|
|
7798
|
+
*/
|
|
7779
7799
|
function installStyles() {
|
|
7780
|
-
if (document
|
|
7800
|
+
if (typeof document === "undefined") return () => void 0;
|
|
7801
|
+
const existing = document.querySelector(`style[data-plugin-css="${STYLE_ID$4}"]`);
|
|
7802
|
+
if (existing !== null) {
|
|
7803
|
+
if (existing.textContent !== CSS) existing.textContent = CSS;
|
|
7804
|
+
return () => void 0;
|
|
7805
|
+
}
|
|
7781
7806
|
const element = document.createElement("style");
|
|
7782
|
-
element.dataset.plugin =
|
|
7807
|
+
element.dataset.plugin = PLUGIN_ID;
|
|
7783
7808
|
element.dataset.pluginCss = STYLE_ID$4;
|
|
7784
7809
|
element.textContent = CSS;
|
|
7785
7810
|
document.head.appendChild(element);
|
|
7786
|
-
return () =>
|
|
7811
|
+
return () => void 0;
|
|
7787
7812
|
}
|
|
7788
7813
|
//#endregion
|
|
7789
7814
|
//#region src/client/antigravity/styles.ts
|