@kiritoko1029/dsh-multi-agent 0.9.3-stage.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -2,6 +2,31 @@
2
2
 
3
3
  本项目的更新日志遵循 [Keep a Changelog] 风格,版本编号遵循 [语义化版本]。历史条目按现有说明与验证记录整理;无法确认发布日期的版本不标注日期。
4
4
 
5
+ ## [0.9.3] - 2026-11-14
6
+
7
+ ### 新增
8
+
9
+ - GitHub Release 工作流新增 `publish-npm` 后续任务:新版本标签完成 GitHub Release 后,下载并校验同一份 TGZ 资产,自动发布到 npm 并附加 provenance。
10
+ - 使用 npm Trusted Publishing:npm 11.20.0 通过 GitHub OIDC 获取短期发布凭证,不配置或存储 `NPM_TOKEN` 等长期令牌。
11
+ - 新增发布校验脚本,绑定标签、checkout 与 TGZ 清单、仓库、Release 构建 SHA-256、侧车和 npm SHA-512 integrity;支持重复运行跳过与发布后有界验证。
12
+ - Release 任务增加完整测试门禁,测试通过后才创建 Release 并进入 npm 发布任务。
13
+
14
+ ### 变更
15
+
16
+ - 包名改为 `@kiritoko1029/dsh-multi-agent`,移除 `private: true`,显式设置公共 npm registry 与 `access: public`;后续 Release TGZ 使用 scoped 包名生成的资产文件名。
17
+ - Cordis patch、Client module id 与 locale namespace 同步使用 scoped 包名;包名也是 DSH 插件安装身份,升级时不要同时启用新旧两个包。
18
+ - 为配置 Trusted Publisher 创建 `0.9.3-stage.0` 预发布引导版本;它不是稳定插件发行版。现有 0.9.2 及更早版本不追溯发布,从 0.9.3 开始由发布工作流自动发布稳定版。
19
+
20
+ ### 修复
21
+
22
+ - 默认 `main-session` 策略现在读取主会话有效权限预设;仅在 `never + danger-full-access`(不再询问 + 完全权限)同时成立时继承完全权限,不再把 Host 的策略自动拒绝误当成子代理应拒绝。
23
+ - 继承时 Codex 使用原生 `danger-full-access` 线程,agy 追加 `--dangerously-skip-permissions`,Kimi 自动允许 ACP 权限申请。其他权限组合、显式人工审批等策略及服务检测失败仍走原审批链,用户主动拒绝保持拒绝。
24
+
25
+ ### 测试
26
+
27
+ - 新增发布资产身份、OIDC、integrity、幂等恢复及主会话权限继承测试;完整回归共 400 项,399 通过、0 失败、1 项原有条件跳过。
28
+ - `npm run check`、`npm run build`、`npm run release:pack` 与 `git diff --check` 通过;真实 rc.1 / rc.2 双版本契约矩阵仍需固定官方源码,未在本轮执行。
29
+
5
30
  ## [0.9.2] - 2026-11-14
6
31
 
7
32
  ### 新增
@@ -194,12 +219,14 @@
194
219
 
195
220
  ## 链接
196
221
 
222
+ - [0.9.3 发布页][0.9.3]
197
223
  - [0.9.2 发布页][0.9.2]
198
224
  - [0.9.1 发布页][0.9.1]
199
225
  - [0.9.0 发布页][0.9.0]
200
226
  - [Keep a Changelog]
201
227
  - [语义化版本]
202
228
 
229
+ [0.9.3]: https://github.com/kiritoko1029/dsh-multi-agent/releases/tag/v0.9.3
203
230
  [0.9.2]: https://github.com/kiritoko1029/dsh-multi-agent/releases/tag/v0.9.2
204
231
  [0.9.1]: https://github.com/kiritoko1029/dsh-multi-agent/releases/tag/v0.9.1
205
232
  [0.9.0]: https://github.com/kiritoko1029/dsh-multi-agent/releases/tag/v0.9.0
package/INSTALL.zh.md CHANGED
@@ -1,6 +1,8 @@
1
- # DSH 多 Agent 派发 0.9.2 — 安装与安全说明
1
+ # DSH 多 Agent 派发 0.9.3 — 安装与安全说明
2
2
 
3
- 本项目不是 DSH 官方插件。源码与安装包发布在 [GitHub 仓库](https://github.com/kiritoko1029/dsh-multi-agent),未发布到 npm;请从 [Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载 `dsh-multi-agent-0.9.2.tgz` 及其 `.sha256` 校验文件。
3
+ 本项目不是 DSH 官方插件。源码与安装包发布在 [GitHub 仓库](https://github.com/kiritoko1029/dsh-multi-agent);0.9.3 起使用 scoped npm 包 `@kiritoko1029/dsh-multi-agent`,请从 [Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载 `kiritoko1029-dsh-multi-agent-0.9.3.tgz` 及其 `.sha256` 校验文件。npm 发布不改变 DSH 安装方式:仍按本文使用插件管理器,不要在 profile 目录手动执行 npm / pnpm 安装。0.9.2 及更早版本不追溯发布到 npm。
4
+
5
+ 0.9.3 的 Release 资产名为 `kiritoko1029-dsh-multi-agent-0.9.3.tgz`。包名同时也是插件安装身份;从旧 `dsh-multi-agent` 升级时应先备份配置并按插件管理器提示处理旧包,不要让新旧两个包同时启用。既有 0.9.2 资产名和安装方式不变。
4
6
 
5
7
  ## 一、准备环境
6
8
 
@@ -21,14 +23,14 @@
21
23
 
22
24
  1. 记录或备份当前插件设置(模型、CLI 路径、路由规则、权限策略)。
23
25
  2. 在 DSH 插件管理器中卸载旧的 `@local/dsh-multi-agent` bundle;不要清空整个 profile,也不要删除整个 `.dsh`。
24
- 3. 安装 Releases 中的 `dsh-multi-agent-0.9.2.tgz`,按管理器提示应用或重启 DSH。
26
+ 3. 安装 Releases 中的 `kiritoko1029-dsh-multi-agent-0.9.3.tgz`,按管理器提示应用或重启 DSH。
25
27
  4. 重新核对顶栏“多 Agent 派发”面板:包名变化后旧设置键不会自动迁移,请按第 1 步的记录重新填写,再确认每个 Agent 的权限策略符合预期。
26
28
 
27
29
  若 0.8.6 曾导致标准 / PTC / 创造模式失败,0.9.0 已包含 0.8.7 的宿主依赖隔离修复,升级后完全重启 DSH 即可;不要为恢复而删除整个 `.dsh`。旧版 profile 若保留 `disabled: !!js ...` 条件覆盖,会继续绕过新版默认,升级时只清理这三个 Provider 的旧动态 disabled 覆盖,保留模型、命令、权限和其他插件设置,无需删除整份 `cordis.patch.yml`。
28
30
 
29
31
  ## 二、首次安装(推荐)
30
32
 
31
- 从 Releases 页面下载 `dsh-multi-agent-0.9.2.tgz`,需要时一并下载 `.sha256` 校验文件:在 macOS / Linux 上执行 `shasum -a 256 -c dsh-multi-agent-0.9.2.tgz.sha256` 即可核对传输完整性(该校验值不是数字签名,也不能独立证明来源可信)。TGZ 本身就是插件安装包,不需要先解开。
33
+ 从 Releases 页面下载 `kiritoko1029-dsh-multi-agent-0.9.3.tgz`,需要时一并下载 `.sha256` 校验文件:在 macOS / Linux 上执行 `shasum -a 256 -c kiritoko1029-dsh-multi-agent-0.9.3.tgz.sha256` 即可核对传输完整性(该校验值不是数字签名,也不能独立证明来源可信)。TGZ 本身就是插件安装包,不需要先解开。
32
34
 
33
35
  把 TGZ 文件或它在 DSH 所在电脑上的绝对路径交给 DSH,并发送:
34
36
 
@@ -72,10 +74,11 @@ Windows 只接受 DSH 托管进程边界可直接启动的原生程序。`.cmd`
72
74
 
73
75
  - **0.9.0 起,Kimi / agy / Codex 新安装默认 `permission: main-session`(主会话审批)。** 子代理的权限申请会送到派发它的主会话:主会话轮次进行中出现原生审批面板;轮次已结束则弹出交互式审批卡片。没有人作答或服务不可用时一律拒绝(fail-closed)。
74
76
  - 五种策略按 Agent 在顶栏“多 Agent 派发”面板独立配置:`main-session`(主会话审批)、`human`(人工审批卡片)、`allow`(允许所有)、`reject`(拒绝所有,0.8.x 的 Kimi/agy 默认,旧保存值继续有效)、`subagent`(子代理控制,含 `auto` / `full-access` / `ask-request` 三个等级;agy 的 `full-access` 会追加其原生 `--dangerously-skip-permissions` 参数)。
77
+ - **当前版本的完全权限继承例外:** 默认 `main-session` 策略会读取主会话有效权限;只有恰好为“不再询问 + 完全权限”(`never + danger-full-access`)时才自动继承,避免 Host 将自动拒绝传给子代理。Codex 使用原生完全权限线程,agy 使用原生跳过权限参数,Kimi 自动允许其 ACP 权限申请。其他组合仍走原审批链,用户主动拒绝继续拒绝;显式选择 `human` 等其他策略时不触发继承。
75
78
  - 批准操作可能允许写文件、执行命令及访问 CLI 自身可访问的内容。这些策略**都不是操作系统级沙箱**,也不保证 CLI 的所有行为都会请求权限。敏感工作应使用独立工作目录、容器或其它隔离方式。
76
- - Codex 在主会话审批 / 人工审批 / 允许所有策略下以 on-request 线程运行(workspace-write 沙箱、无自动审查器),真实升级请求才会到达送审;拒绝所有与子代理控制 `auto` 沿用其 `permissionMode`(默认 `approve-for-me` 自动审查)。
79
+ - Codex 在主会话审批 / 人工审批 / 允许所有策略下以 on-request 线程运行(workspace-write 沙箱、无自动审查器),真实升级请求才会到达送审;继承上述完全权限组合时改用原生 `danger-full-access` 线程;拒绝所有与子代理控制 `auto` 沿用其 `permissionMode`(默认 `approve-for-me` 自动审查)。
77
80
  - DSH、Codex、Kimi、agy 使用各自的模型配置、登录和计费。多个 Agent 并行可能同时消耗额度。
78
- - 父 DSH 的权限设置不等于所有外部 CLI 自动继承相同权限。安装本插件不代表授予所有后续操作无限权限。
81
+ - 除上述默认策略下的明确完全权限组合外,父 DSH 的权限设置不等于所有外部 CLI 自动继承相同权限。安装本插件本身不代表授予所有后续操作无限权限;只有主会话有效组合和 Agent 策略同时满足上述条件才继承。
79
82
 
80
83
  本包不包含分享者的账号、密钥、会话历史、任务报告、备份或 node_modules。首次安装仍会从配置的包仓库下载 npm 依赖;它不是离线全依赖包。
81
84
 
package/PUBLISHING.md ADDED
@@ -0,0 +1,60 @@
1
+ # 发布维护
2
+
3
+ 本文只面向仓库维护者。DSH 用户的安装与配置请阅读 [README.md](README.md) 和 [INSTALL.zh.md](INSTALL.zh.md),版本变更只记录在 [CHANGELOG.md](CHANGELOG.md)。
4
+
5
+ ## npm Trusted Publisher
6
+
7
+ npm 包为 [`@kiritoko1029/dsh-multi-agent`](https://www.npmjs.com/package/@kiritoko1029/dsh-multi-agent)。在 npm 包的 **Settings → Trusted publishing** 中配置 GitHub Actions:
8
+
9
+ - Repository owner:`kiritoko1029`
10
+ - Repository name:`dsh-multi-agent`
11
+ - Workflow filename:`release.yml`
12
+ - Environment:留空;工作流当前未使用 GitHub Environment
13
+
14
+ 发布工作流通过 GitHub OIDC 换取 npm 短期凭证,不配置 `NPM_TOKEN`,也不在仓库、workflow 日志或插件配置中保存长期 npm 令牌。`publish-npm` 任务显式安装 npm `11.20.0`,满足 Trusted Publishing 所需的 npm 11.5.1+,并使用包含后续修复的 11.x 版本。
15
+
16
+ `0.9.3-stage.0` 仅用于建立 npm 包与 Trusted Publisher 的引导关系。它是预发布版本,不是稳定插件发行版,也不应作为稳定安装来源。
17
+
18
+ ## 正常发布步骤
19
+
20
+ 1. 更新 `package.json` 的版本和 `CHANGELOG.md` 的对应版本段落。
21
+ 2. 运行语法检查、完整测试、Client 构建和 `release:pack`,检查生成的 TGZ 与 SHA-256。
22
+ 3. 提交并推送 `main`,确认 CI 成功后创建并推送对应的 `vX.Y.Z` 标签。
23
+ 4. 等待 `.github/workflows/release.yml` 中的 `release` 与 `publish-npm` 两个任务完成,并检查 GitHub Release 和 npm registry 中的版本。
24
+
25
+ 普通 `main` 提交、本地安装、手动 `npm pack` 都不会发布 npm。只有版本标签会触发该工作流。
26
+
27
+ ## 工作流与产物校验
28
+
29
+ `release` 任务依次执行依赖安装、语法检查、完整测试、标签版本核对、Client 构建和 TGZ / SHA-256 构建,随后创建 GitHub Release。`scripts/build-release.mjs` 同时把本次构建产物的 SHA-256 写入 GitHub Actions step output,由 `release` job output 传给 npm 任务。
30
+
31
+ `publish-npm` 任务使用最小权限 `contents: read` 与 `id-token: write`,从刚创建的 GitHub Release 下载 TGZ 和校验侧车。`scripts/publish-npm.mjs` 在发布前验证:
32
+
33
+ - 标签版本、checkout 的 `package.json` 与 TGZ 内 `package/package.json` 的名称和版本一致;
34
+ - 包不是 private,目标 registry 为 `https://registry.npmjs.org/`,访问级别为 public;
35
+ - checkout 与 TGZ 的 repository 均属于触发工作流的 GitHub 仓库;
36
+ - `dist` 中只有预期 TGZ 和同名 `.sha256`,TGZ 的实际 SHA-256 与 Release 构建 job output 一致;
37
+ - 侧车只包含该精确 TGZ 的一条有效 SHA-256 记录;
38
+ - 本地 TGZ 计算出用于发布前后比对的 SHA-512 `dist.integrity`。
39
+
40
+ 校验通过后,脚本只发布这个精确文件,不使用 TGZ 通配符:
41
+
42
+ ```sh
43
+ npm publish <精确TGZ> --access public --provenance --registry=https://registry.npmjs.org/
44
+ ```
45
+
46
+ ## 重试与故障恢复
47
+
48
+ - 发布前发现同版本且 integrity 相同:视为已完成,跳过重复发布。
49
+ - 发布前发现同版本但 integrity 不同:拒绝发布,不能覆盖 npm 中已有内容。
50
+ - `npm publish` 返回错误:继续执行有界 registry 查询;若版本实际已接收且 integrity 一致,则恢复成功,避免重跑时重复发布。
51
+ - 发布后 registry 暂时不可见或查询失败:在限定次数内重试,不把网络错误当成“包不存在”。
52
+ - 发布后 integrity 不同或最终无法确认:任务失败,不报告发布成功。
53
+
54
+ Trusted Publisher、OIDC 或仓库绑定配置错误时,GitHub Release 可能已经创建,但 npm 发布会失败。修正配置后在原工作流运行中选择 **Re-run failed jobs**,不要重复创建同一标签或版本,也不要删除 Release 来掩盖失败。
55
+
56
+ ## 分发边界
57
+
58
+ 0.9.3 起包名为 `@kiritoko1029/dsh-multi-agent`,`npm pack` 生成的 Release 资产名为 `kiritoko1029-dsh-multi-agent-<version>.tgz`。既有 0.9.2 的 `dsh-multi-agent-0.9.2.tgz` 资产名不变。
59
+
60
+ 0.9.2 及更早版本保持原有 GitHub Release 分发,不追溯发布到 npm。npm 发布不改变 DSH 安装方式:用户仍通过插件管理器安装 TGZ,不在 profile 目录手动执行 npm / pnpm 安装。
package/README.md CHANGED
@@ -2,69 +2,29 @@
2
2
 
3
3
  把独立任务按自然语言规则派发给 **DSH 自身、Codex、Kimi Code 或 Antigravity(agy)**,并通过 DSH 原生子代理完成通知把结果送回主会话。
4
4
 
5
- ## 0.9.2:顶栏多 Agent 设置面板
5
+ ## 安装(DSH 用户)
6
6
 
7
- - 会话顶栏右侧新增“多 Agent 派发”小按钮,点击后在宽敞浮层中完成全部配置,不再进入 DSH 设置页。
8
- - 面板按“全部”“模型与思考强度”“派发策略与规则”“运行参数”分组,保留 DSH、Codex、Kimi Code 与 Antigravity(agy)的模型、路由、权限和运行配置。
9
- - 保留四个 Config namespace 的独立 revision / CAS 保存与部分失败处理;支持未保存关闭确认、Escape、遮罩关闭、焦点圈定和中英文界面。
10
- - 原 DSH 设置页入口已移除;已有配置值保持不变,无需手工迁移。
7
+ 1. 确认 DSH 版本为 `0.2.2-rc.1`、`0.2.2-rc.2` 或 `0.2.2-rc.3`,不要通过版本豁免强行安装。
8
+ 2. 从 [GitHub Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载稳定版的 TGZ 安装包,可同时下载同名 `.sha256` 文件核对传输完整性。TGZ 不需要解压。
9
+ 3. 把 TGZ 文件或它在 DSH 所在电脑上的绝对路径发给 DSH,并发送:
11
10
 
12
- ### 0.9.0:子代理权限申请(主会话审批)
11
+ > 请通过插件管理器安装这个多 Agent 派发插件 TGZ。先检查 DSH 版本和安装包,不要手动修改 profile,不要开启版本豁免,也不要替我把权限策略改成 allow 或 full-access。安装后请核验状态,并告诉我是否需要重启。
13
12
 
14
- 子代理的权限申请不再一律拒绝。每个 Agent(Kimi、agy、Codex)都有独立的"子代理权限请求策略",默认 **主会话审批**:
13
+ 4. 按插件管理器返回结果应用或重启 DSH。刷新页面后,点击会话顶栏右侧的 **“多 Agent 派发”**,配置模型、路由规则、CLI 路径与权限策略。
15
14
 
16
- - **主会话审批(默认)**:权限申请送入派发它的主会话审批链,Web 端在主会话对话中出现原生审批面板;主会话轮次已结束、审批链无可用应答器或审批服务异常时,自动降级为交互式审批卡片,两条通道都无人应答时拒绝(fail-closed),任务不会被静默卡死。主会话审批策略为 `never` 时按其语义拒绝。
17
- - **人工审批**:每次权限申请都弹出交互式审批卡片(允许一次 / 始终允许 / 拒绝),不经过主会话审批链。
18
- - **允许所有**:自动批准全部申请。
19
- - **拒绝所有**:自动拒绝全部申请(0.8.x 分享版的 Kimi/agy 默认行为,旧保存值继续有效)。
20
- - **子代理控制**:走子代理自己的权限体系,按等级适配各后端:
21
- - `auto`:子代理自动处理。Codex 沿用下方"Codex 权限模式"的线程参数(默认 `approve-for-me` 自动审查;审查升级仍无人值守时拒绝;设为 `never` 或 bypass 时按该模式运行);ACP 子代理按其内部规则过滤后提出的申请自动接受(仅单次授权)。
22
- - `full access`:完全访问。Codex 使用 bypass 线程模式;agy 追加原生 `--dangerously-skip-permissions`;Kimi 的 acp 子命令没有公开的跳过参数,按自动允许全部申请实现(文档如实标注)。
23
- - `ask request`:逐次询问。Codex 以 on-request 线程运行(无自动审查器),每次升级连同 ACP 的每次申请都送主会话审批链。
15
+ 完整升级、CLI 和安全说明见 [安装与安全说明](INSTALL.zh.md)。从旧 `dsh-multi-agent` 升级到 scoped 包时,先备份配置并按插件管理器提示处理旧包;包名也是插件安装身份,不要让新旧两个包同时启用。不要在 profile 目录手动执行 `npm install` / `pnpm install`;npm 包与 GitHub Release 提供的是同一种 TGZ 安装产物,不是需要全局安装的 CLI。
24
16
 
25
- Codex 在主会话审批 / 人工审批 / 允许所有策略下使用新的 ask-request 线程参数(`approvalPolicy: on-request` + `sandbox: workspace-write`、无自动审查器),因此真实的权限事件会到达本插件并按策略送审;批准时命令/文件申请返回 `accept`,权限升级回显所申请的权限。Kimi 与 agy 的 ACP `session/request_permission` 同样按策略决定:一次授权只选 `allow_once`(子代理未提供时拒绝,绝不扩大授权),持续授权仅出现在用户选择"始终允许"或 `full-access` 等级且子代理提供 `allow_always` 时;拒绝优先回选子代理提供的 `reject_once` 选项,未提供才按取消返回。审批卡片会解析出具体操作——命令(`command:`)、目标路径(`path:`)和内容预览(`content:`),并兼容 Kimi(动作写在 `content` 文本块)与 agy(`kind` / `locations` / `diff` / `rawInput`)两种载荷;缺少 `kind` 时按工具名推断类别,中文任务显示中文类别(如“子代理权限请求(写入文件)”)。权限申请与结果会以活动条目显示在任务面板;所有未决路径 fail-closed(拒绝),运行取消后迟到的批准一律作废。
17
+ ### 使用边界
26
18
 
27
- **升级说明:** 从 0.8.x 升级后,未改动过策略的安装默认从 `reject`(Kimi/agy)或自动审查(Codex `approve-for-me`)变为主会话审批——这是收紧交互而非放权(不再自动放行的同时也不会自动拒绝,改由主会话决定);如需保持旧行为,在设置中把策略改为 `reject` 即可。已保存的 `allow` / `reject` 值不受影响。
28
-
29
- ### 0.8.8:原生任务面板与 Windows 兼容
30
-
31
- 任务面板改用 DSH 自带的右侧栏页面注册、开始页入口和文件预览,不再要求安装第三方 Better Sidebar。任务列表、活动流水、历史、续接和报告查看保留;原生导航参数传递父会话与子任务,后台会话不会误开到当前页面。
32
-
33
- ### 0.8.7:宿主依赖隔离与 Windows 兼容
34
-
35
- 0.8.6 的 Windows 适配仍把 rc.1 Host Provider 包作为普通依赖,可能在全新 profile 中遮蔽 DSH 自带实现,导致 `subagents` 及标准/PTC/创造模式无法启动。0.8.7 移除了这些生产依赖:ACP 使用纯 SDK,Codex 使用纯协议传输与原生 Codex 包,所需进程辅助代码保留在插件内部。Cordis 仅作为宿主 peer,不再随插件重复安装。
36
-
37
- ### 0.8.6:CLI 自动检测与手动覆盖
38
-
39
- 统一设置页为 Codex、Kimi Code、Antigravity 提供 CLI 路径输入。留空使用自动检测;手动填写优先使用用户配置,清空已保存的覆盖可恢复自动检测。
40
-
41
- - Codex 按“手动路径 → 检测到的原生 CLI → 依赖包内的原生 Codex”选择,模型目录与实际 app-server 使用同一命令;仅在兼容回退中使用官方 JavaScript wrapper,并显式设置 Electron 的 Node 模式。
42
- - Kimi / agy 手动路径同时用于模型目录与 ACP 任务,不再被独立的自动检测门禁误拦;目录查询继承对应 Provider 的环境覆盖。
43
- - 保留 0.8.4 的 Windows 原生 CLI 检测、Provider 启动等待和 Codex 局部 helper 修复。
44
-
45
- Windows 下请填写实际原生程序(通常为 `.exe`)的绝对路径。当前没有 Windows 实机端到端验收,模型目录读取仍需在目标环境验证。
46
-
47
- 本版修复 Windows 外部 CLI 可用性检测,并消除三个 Provider 的启动时序竞态:
48
-
49
- - 支持原生 `.exe` / `.com`、Windows PATH 和官方安装目录;不把 `.cmd` / `.bat` 当作 DSH 可直接启动的原生程序。
50
- - Codex 检查 `%LOCALAPPDATA%\Programs\OpenAI\Codex\bin`;Kimi 检查当前用户的 `.kimi-code\bin`;agy 检查 `%LOCALAPPDATA%\agy\bin`。
51
- - 三个 Provider 通过 Cordis 注入等待可用性服务,不再在服务未就绪时用动态 `disabled` 表达式跳过挂载。
52
- - 保留 0.8.3 的 Codex 局部 helper 导入修复;不改模型授权、用户路由或权限默认。
53
- - `.cmd` / `.bat` / JavaScript / PowerShell shim 会在 Windows 启动前给出明确错误,不进入无 shell 的 `CreateProcess`;重复 `Path` / `PATH` 键和带引号的 PATH 目录按确定规则处理。
54
- - ACP 目录探测同时观察进程退出,启动失败立即返回并回收子进程;续接工作区按目录身份比较,不再仅比较路径字符串。
55
- - 任务 Markdown 链接支持 Windows 盘符、反斜杠 UNC 与 `file:///C:/...`,旧完成通知可识别盘符报告路径;危险协议和 `//host/...` 仍按原安全规则拒绝。
56
-
57
- 当前 macOS 上完成了模拟 Windows 路径、真实 Loader 生命周期和完整回归验证。实际 Windows CLI 的模型目录读取仍应在接收者电脑验收,不能把模拟路径测试描述为 Windows 端到端通过。
58
-
59
- 这是 `dsh-multi-agent` 0.9.2 的发布说明。源码与安装包发布在 [GitHub 仓库](https://github.com/kiritoko1029/dsh-multi-agent),安装包从 [Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载,未发布到 npm。首次安装请先阅读 [安装与安全说明](INSTALL.zh.md),版本变更见 [更新日志](CHANGELOG.md)。
60
-
61
- - Kimi / agy 命令从接收者自己的用户目录与 PATH 检测,不含打包者的本机路径。
62
- - Kimi / agy 新安装默认 `permission: main-session`:权限申请送主会话审批;0.8.x 保存的 `allow` / `reject` 值继续有效。批准操作可能允许写文件与执行命令,这不是操作系统级只读沙箱。
19
+ - 需要哪个外部 Agent,就自行安装并登录对应 CLI;插件不提供账号或模型额度。只用 DSH 子代理时不需要外部 CLI。
20
+ - Kimi / agy 新安装默认 `permission: main-session`:权限申请送主会话审批;批准操作可能允许写文件与执行命令,这不是操作系统级只读沙箱。
63
21
  - 不包含账号、密钥、会话、任务报告、备份或 node_modules;安装依赖需要联网。
64
22
  - 任务面板使用 DSH 原生右侧栏与文件预览,无需安装第三方侧栏扩展。
65
23
  - macOS 是当前验证平台;Linux / Windows 未作实机安装和 CLI 执行验收,不承诺跨平台即装即用。
66
24
 
67
- ## DSH 版本兼容(继承 0.8.1)
25
+ 版本变更只记录在 [更新日志](CHANGELOG.md),不在 README 重复维护。0.9.3 起稳定版同步到 [npm 包 `@kiritoko1029/dsh-multi-agent`](https://www.npmjs.com/package/@kiritoko1029/dsh-multi-agent);历史版本不追溯发布。
26
+
27
+ ## DSH 版本兼容
68
28
 
69
29
  同一份插件面向 **DSH `0.2.2-rc.1` / `0.2.2-rc.2` / `0.2.2-rc.3`**,无需手工切换模式。兼容层根据注入的 Host 服务能力选择接口,而非根据插件目录中依赖包的版本推测 Host;不识别或混合的接口组合会拒绝启动,不在派发失败后换参数格式重试,避免重复创建任务。
70
30
 
@@ -107,7 +67,7 @@ DSH Subagent Manager
107
67
  - 外部子代理每轮仍由独立 managed activation 执行:完成后可用 `continue_agent` 恢复同一 Codex thread / Kimi 或 agy session 并开始下一轮。运行中暂不追加指令。首次派发仍需自包含;续接会保留子会话自己的上下文,不继承主会话全文。
108
68
  - 工具只对 root/main Agent 开放。DSH 内部子代理不能继续调用它,从而不会绕过原生派发深度,也不会触发无效的子代理用户问答。
109
69
  - 父 Session 保留 external child 的归属与终态。已接收的子任务由 DSH 管理,路由组件重载不再立即取消它们;每个任务原有的超时计时和完成回传继续有效。Host 进程退出/崩溃后的续跑不在此保证内。
110
- - 0.4.2 修复了从 `run_code` 派发后任务立即中止的问题,并补齐交接竞争处理:调用方取消信号只控制启动阶段,接收成功后解除监听,避免 PTC 正常结束时的清理误杀后台任务。是否已接收由 DSH 管理器裁决;返回成功的 activation 不再因交接瞬间的调用方取消而被销毁。任务超时独立计时,不会被启动取消状态屏蔽;接收后的父子中断遵循 DSH 原生管理机制。
70
+ - 从 `run_code` 派发时,交接竞争按以下规则处理:调用方取消信号只控制启动阶段,接收成功后解除监听,避免 PTC 正常结束时的清理误杀后台任务。是否已接收由 DSH 管理器裁决;返回成功的 activation 不再因交接瞬间的调用方取消而被销毁。任务超时独立计时,不会被启动取消状态屏蔽;接收后的父子中断遵循 DSH 原生管理机制。
111
71
 
112
72
  ## 普通派发与原生例外
113
73
 
@@ -179,7 +139,7 @@ DSH Subagent Manager
179
139
 
180
140
  - `model`:位于顶栏“多 Agent 派发”面板顶部的默认 Codex 模型;通过可用性检测到的本机当前 Codex CLI 执行 `debug models`(不使用 `--bundled` 静态快照),读取与交互 `/models` 一致的公开模型(`visibility: list`),可下拉选择或手动输入;留空使用 Codex 原生配置,后续每次启动都会读取最新值。
181
141
  - `reasoningEffort`:默认思考强度;建议取自 Codex 目录的 `supported_reasoning_levels`,执行时明确发送到 app-server 的 `turn/start.effort`。留空时不覆盖原生默认值。
182
- - `permissionPolicy`:子代理权限请求策略,默认 `main-session`。主会话审批 / 人工审批 / 允许所有时线程使用 ask-request 参数(无自动审查器),真实升级请求按策略决定;拒绝所有与子代理控制的 `auto` 沿用下方 `permissionMode`。
142
+ - `permissionPolicy`:子代理权限请求策略,默认 `main-session`。主会话审批 / 人工审批 / 允许所有时线程使用 ask-request 参数(无自动审查器),真实升级请求按策略决定;主会话有效权限为“不再询问 + 完全权限”(`never + danger-full-access`)时,改用 Codex 原生完全权限线程并自动允许其申请,避免策略自动拒绝。拒绝所有与子代理控制的 `auto` 沿用下方 `permissionMode`。
183
143
  - `subagentPermissionMode`:子代理控制等级(`auto` / `full-access` / `ask-request`),仅策略为 `subagent` 时生效,映射到 Codex 原生线程模式。
184
144
  - `permissionMode`:Codex 原生线程权限模式,默认 `approve-for-me`(自动审查 + workspace-write);策略为拒绝所有或子代理控制 auto 时直接作为线程模式使用。
185
145
  - `env`:Codex 子进程环境覆盖。
@@ -192,23 +152,23 @@ DSH Subagent Manager
192
152
  - `reasoningEffort`:默认思考强度;建议取自模型的 `supportEfforts`。模型设置成功后,按会话最新返回的 `thinking` 选项验证并设置强度;不支持的值会在发送任务前报错,不会静默忽略。
193
153
  - `command` / `args`:默认 `<本机 kimi 路径> acp`。
194
154
  - `permission`:子代理权限请求策略,默认 `main-session`(主会话审批)。
195
- - `main-session`:申请送主会话审批链;主会话轮次已结束时降级为交互卡片。
155
+ - `main-session`:申请送主会话审批链;主会话轮次已结束时降级为交互卡片。若主会话有效权限恰好为“不再询问 + 完全权限”(`never + danger-full-access`),子代理继承该组合并自动允许申请;其他组合仍按原审批链处理,用户主动拒绝不会被改写为允许。权限状态在每次任务启动时读取,本次启动后保持不变。
196
156
  - `human`:每次申请弹交互式审批卡片。
197
157
  - `allow`:自动选择 ACP 返回的首个 `allow_once` / `allow_always` 选项。
198
- - `reject`:拒绝子代理发出的权限请求(0.8.x 默认),可能导致报告写入或任务执行失败。
158
+ - `reject`:拒绝子代理发出的权限请求,可能导致报告写入或任务执行失败。
199
159
  - `subagent`:子代理控制,配合 `subagentPermissionMode` 等级(`auto` 接受其内部规则过滤后的申请;`full-access` 自动允许全部——kimi acp 没有公开的跳过参数;`ask-request` 逐次送主会话审批)。
200
160
  - `subagentPermissionMode`:子代理控制等级,仅 `permission: subagent` 时生效。
201
161
  - `env`:显式子进程环境覆盖。不要在这里填写不再需要的密钥;子 Agent 原生认证通常已经足够。
202
162
  - `disposeEofGraceMs` / `disposeGraceMs`:进程退出与强制终止的宽限时间。
203
163
 
204
- ### Antigravity(agy)ACP(0.7.0)
164
+ ### Antigravity(agy)ACP
205
165
 
206
166
  在顶栏“多 Agent 派发”面板配置 **Antigravity(agy)**。它有独立的模型、思考强度和运行参数,不会覆盖 Kimi 的 ACP 配置。
207
167
 
208
168
  - `command` / `args`:使用本机自动检测到的 agy 路径和 `["acp"]`;可在顶栏面板中修改命令,特殊安装位置需同步设置 `AGY_COMMAND`,供 DSH 启动时检测。
209
169
  - `model` / `reasoningEffort`:留空沿用原生默认值;指定后通过 ACP 会话配置设置。思考强度使用 agy 的 `thought_level`,与 Kimi 的 `thinking` 分开处理。不要把普通 CLI 的 `--model` / `--effort` 直接追加到 `agy acp` 参数。
210
170
  - 模型建议来自 `GET /multi-agent/model-catalog/agy`:通过短暂 ACP 会话读取实际模型族及各模型支持的强度,15 秒超时并回收进程,不执行任务 prompt。不要把 `agy models` 中带强度后缀的组合 ID 当作模型族填写。可选模型以当前账号和 ACP 返回值为准。
211
- - `permission`:子代理权限请求策略,默认 `main-session`(主会话审批),选项与 Kimi 相同(`main-session` / `human` / `allow` / `reject` / `subagent`)。
171
+ - `permission`:子代理权限请求策略,默认 `main-session`(主会话审批),选项与 Kimi 相同(`main-session` / `human` / `allow` / `reject` / `subagent`)。继承主会话“不再询问 + 完全权限”时,agy 会追加原生 `--dangerously-skip-permissions`;Kimi 没有公开跳过参数,通过自动允许其 ACP 权限申请实现。
212
172
  - `subagentPermissionMode`:子代理控制等级。agy 与 Kimi 的差异在 `full-access`:agy 会追加原生 `--dangerously-skip-permissions` 启动参数,彻底交由其内部权限体系;其余等级语义一致。
213
173
  - `env` 与退出宽限参数:语义与 Kimi ACP 相同;已有登录无需额外填写密钥。
214
174
  - 可在路由规则、默认 Agent 中选择 `agy`,或直接调用 `dispatch_agent` 并指定 `agent: "agy"`。结果、工具活动和报告进入同一任务面板。
@@ -232,7 +192,7 @@ DSH Subagent Manager
232
192
  - DSH 子代理不依赖外部 CLI,始终作为本机兜底。模型选择仍遵守当前 Session 的模型授权和能力校验。
233
193
  - 新安装外部 CLI 后需要重载插件或重启 DSH,让 Loader 重新计算组件可用性。
234
194
 
235
- **安全边界:** Kimi 和 agy 非交互任务可以写文件、执行 Shell。任何批准类策略(主会话审批中点了允许、人工审批、允许所有、子代理控制)都可能批准这些操作,且都不是操作系统沙箱;请只在受信任工作区使用。需要隔离时使用独立 worktree、容器或操作系统沙箱,不要仅靠提示词声明“只读”。
195
+ **安全边界:** Kimi 和 agy 非交互任务可以写文件、执行 Shell。任何批准类策略(主会话审批中点了允许、人工审批、允许所有、子代理控制)都可能批准这些操作,且都不是操作系统沙箱。主会话选择“不再询问 + 完全权限”是有意的完全授权:使用默认 `main-session` 策略时,Codex / Kimi / agy 会继承该权限,不再逐项询问;如需逐项审批,请把主会话审批改回“询问”,或为对应 Agent 选择人工审批等显式策略。请只在受信任工作区使用;需要隔离时使用独立 worktree、容器或操作系统沙箱,不要仅靠提示词声明“只读”。
236
196
 
237
197
  ## 主 Agent 调用契约
238
198
 
@@ -307,13 +267,13 @@ Report path: <absolute-report-path>
307
267
 
308
268
  最终答案通过后续 DSH completion notice 回到主会话。只有等待结果时,主 Agent 应结束当前 turn,不要轮询或睡眠。
309
269
 
310
- ## 任务标签面板与完成卡片(0.5.1)
270
+ ## 任务标签面板与完成卡片
311
271
 
312
272
  派发新的 Codex/Kimi/agy 任务后,当前主会话的原生右侧栏会自动打开 **Agent 任务** 面板;也可以从开始页的 Agent 任务入口打开。界面使用 DSH 原生页面与文件预览,原生 Team 任务面板仍保留。后台会话的新任务不会误开到当前页面。
313
273
 
314
274
  - 每个子任务独占一个标签页,按创建时间倒序排列,最新任务在前;标签及任务标题区显示本地日期和时间,不显示“创建时间”文字前缀,耗时仍保留。同一 Agent 的多次派发分别展示,支持键盘切换,完成通知可定位对应标签。
315
- - 0.7.0 起,子任务正文按连续消息流完整展示,不再逐条使用卡片、消息标签或摘要/全文两层展示。相邻工具活动按发生顺序分组,穿插在正文之间,默认折叠为紧凑列表;工具输出可进一步展开,展开时不强制跟随滚动。与最后一条正文完全相同的最终结果不重复展示。
316
- - 0.7.1 修复连续正文显示 Markdown 源码的问题:消息和最终结果统一渲染标题、强调、列表、引用、表格、代码块及链接。文件链接点击后使用当前任务工作区和父会话的文件查看入口;代码与原始 HTML 按文本显示,危险链接不会执行,图片不自动加载。Markdown 解析器随 Client bundle 本地打包,不访问 CDN。
275
+ - 子任务正文按连续消息流完整展示,不逐条使用卡片、消息标签或摘要/全文两层展示。相邻工具活动按发生顺序分组,穿插在正文之间,默认折叠为紧凑列表;工具输出可进一步展开,展开时不强制跟随滚动。与最后一条正文完全相同的最终结果不重复展示。
276
+ - 消息和最终结果统一渲染标题、强调、列表、引用、表格、代码块及链接。文件链接点击后使用当前任务工作区和父会话的文件查看入口;代码与原始 HTML 按文本显示,危险链接不会执行,图片不自动加载。Markdown 解析器随 Client bundle 本地打包,不访问 CDN。
317
277
  - 新派发的任务标题沿用主会话语言;子任务的进度消息、最终回复和报告也遵守传入的语言约束。保留代码、路径和专有名词,尊重明确指定的交付物语言。已有英文标题和报告不自动翻译。
318
278
  - 每个子任务独立记录阅读位置及自动跟随状态。在消息区底部时,新消息、已有消息追加内容或最终输出更新会自动跟随;向上滚动暂停,回到底部恢复,也可点击向下箭头回到最新。不显示底部“正在跟随”或“已暂停跟随”状态条。
319
279
  - 切换标签后保留各任务阅读位置;刷新任务列表和新任务出现不会强制切走当前标签。消息到来时不会为了保持固定条数而主动移除正在阅读的内容。
@@ -323,11 +283,11 @@ Report path: <absolute-report-path>
323
283
  - 关闭/隐藏面板会停止列表刷新;只有当前选中的可见子任务读取详细活动,没有可显示的当前会话时不强行打开。
324
284
  - 主模型可用 `agent_tasks` 查询自己派发的任务状态与活动,不能查询其他主会话。
325
285
 
326
- 0.6.0 起,任务摘要、归档状态、输出与真实外部会话引用保存在 DSH 原生存储域 `multi_agent_task_history`,不保存凭据。进程重启时,未结束的记录标为已中断并归档,等待明确续接,不会自动重跑。活动流水仍仅保存在当前 Host 内存中:每任务最多 500 项活动、256K 字符活动正文,单条正文最多 16K 字符;恢复后的历史可查看摘要、报告和续接能力,不能补录之前的流水。最多保留 200 条任务记录,运行中的任务不因达到数量限制被淘汰;超过上限时最早终态记录会被淘汰。
286
+ 任务摘要、归档状态、输出与真实外部会话引用保存在 DSH 原生存储域 `multi_agent_task_history`,不保存凭据。进程重启时,未结束的记录标为已中断并归档,等待明确续接,不会自动重跑。活动流水仍仅保存在当前 Host 内存中:每任务最多 500 项活动、256K 字符活动正文,单条正文最多 16K 字符;恢复后的历史可查看摘要、报告和续接能力,不能补录之前的流水。最多保留 200 条任务记录,运行中的任务不因达到数量限制被淘汰;超过上限时最早终态记录会被淘汰。
327
287
 
328
288
  更新时使用保持启用状态的 profile 增量协调,不再采用关闭整个 bundle 后重开的方式。路由组件卸载不取消已接收任务,原有 deadline 继续执行;Host 重启仍会终止进程。本地 Client 源码改动后先运行 `node scripts/build-client.mjs` 生成合并入口,再协调加载。
329
289
 
330
- ## 模型与 token 用量(0.8.0)
290
+ ## 模型与 token 用量
331
291
 
332
292
  任务详情标题区显示模型、思考强度及 token 用量,历史详情使用同一信息区,不给每条消息增加卡片。
333
293
 
@@ -339,15 +299,15 @@ Report path: <absolute-report-path>
339
299
  - DSH 从自身子会话事件记录模型、思考强度及真实 usage,按事件序号去重。Codex 使用 `thread/tokenUsage/updated.tokenUsage.total` 的线程累计快照,标为“会话 Token”,重复快照不累加;不将 `last` 猜成整轮用量。
340
300
  - Kimi/agy 从 ACP 会话配置读取模型和思考强度;只有 prompt 响应包含 `usage` 才显示消耗。`usage_update.used/size` 表示上下文窗口占用,不计作消耗;未提供实验性 usage 的 CLI 仍会显示“—”。
341
301
 
342
- ## 历史归档、清理与续接(0.6.0)
302
+ ## 历史归档、清理与续接
343
303
 
344
- - 已完成、已中止和错误任务自动归档。0.6.1 起,顶部“历史任务”入口打开独立的历史视图,列表独立滚动,不与当前任务消息区上下叠放;查看历史详情时收起列表,并可返回先前活动任务、恢复阅读位置。正在查看的任务结束时保留详情,不会自动打开历史列表。历史提供查看、恢复到活动标签和清理;恢复标签本身不会启动模型。
304
+ - 已完成、已中止和错误任务自动归档。顶部“历史任务”入口打开独立的历史视图,列表独立滚动,不与当前任务消息区上下叠放;查看历史详情时收起列表,并可返回先前活动任务、恢复阅读位置。正在查看的任务结束时保留详情,不会自动打开历史列表。历史提供查看、恢复到活动标签和清理;恢复标签本身不会启动模型。
345
305
  - 清理只从面板列表隐藏终态任务并释放活动流水,保留磁盘报告和底层会话。主 Agent 可按仍保留的逻辑 childId 查询、恢复或续接;清理不等同于删除会话,历史上限淘汰的记录不保证仍可通过本插件访问。
346
306
  - 面板“继续指挥”或主 Agent 的 `continue_agent({ childId, message, expected_output?, language? })` 调用同一业务入口。续接开始后回到活动区,下一轮结束再次归档。只允许本父会话的终态任务,拒绝重复并发续接。
347
307
  - DSH:通过原生 `sendMessage` 唤起同一子会话。Codex:新任务使用持久 thread,后续 `thread/resume` 复用它。Kimi:按能力优先 `session/resume`,兼容 `session/load`。agy:使用 `session/load` 确认保存的历史存在;两者均屏蔽旧消息回放。
348
308
  - 外部续接会创建新的 managed activation,但面板保持同一个逻辑任务,通过内部别名关联各轮;不能把新 conversation 当成旧会话复用。缺失原始 ID、远端历史已被删除、能力不支持或工作区不匹配时直接报错,不静默重新派发。
349
309
  - 升级前的 Codex 临时线程不落盘、Kimi 原始 ID 未记录;这些旧任务无法通过本插件可靠恢复。新持久记录才提供外部续接能力。外部恢复沿用原工作区和模型,当前审批策略仍逐轮执行。
350
- - 0.6.0 修复历史存储单元名称不合规范与初始化拒绝未及时捕获造成的 Host 崩溃。初始化失败时显示明确不可用错误并阻止任务操作;不删除或重置损坏数据,卸载失败也不会形成未处理拒绝。
310
+ - 历史存储初始化失败时显示明确不可用错误并阻止任务操作;不删除或重置损坏数据,卸载失败也不会形成未处理拒绝。
351
311
  - 报告和完成通知保留,超时与取消不回滚已发生的文件修改或外部副作用;跨 Host 同时续接的分布式锁、运行中外部 steer 不在当前范围。
352
312
 
353
313
  ## 开发
@@ -383,6 +343,10 @@ node --test test/*.test.mjs test/client.test.js
383
343
 
384
344
  脚本会校验使用文件的固定 SHA-256,分别执行两版官方的 AgentRegistry、startActivation 和外部启动管理器方法,检查错误形状对照、参数保持、精确身份、取消、provider 变更和不重试。无参数时查看 `node scripts/verify-runtime-contracts.mjs --help`。脚本不自动下载、不安装依赖、不调用模型。
385
345
 
386
- 验证边界:这是“真实上游方法 + 明确的资源/Session/provider 替身”的隔离契约验证,不是完整 Cordis Host 或 rc.2 桌面端到端验收。0.8.1 开发验证已包含完整回归和两版源码矩阵;真实 CLI、原生页面注册与桌面渲染仍以对应运行环境中的实际操作为准。
346
+ 验证边界:这是“真实上游方法 + 明确的资源/Session/provider 替身”的隔离契约验证,不是完整 Cordis Host 或 rc.2 桌面端到端验收。开发验证包含完整回归和两版源码矩阵;真实 CLI、原生页面注册与桌面渲染仍以对应运行环境中的实际操作为准。
387
347
 
388
348
  本地链接包更新后需要由 Plugin Manager 协调重新加载。以返回的 `application` 和 `nextStep` 为准:`applied` 后仍需验证实际任务;`restart-required` 表示新代码需重启 DSH 才会加载,不能将磁盘版本更新说成正在运行的新版本。若返回 `failed to import`,先排查 Host 导入错误。前端源码修改后需重建 Client bundle;浏览器页面刷新本身不会重启 Host。
349
+
350
+ ### 发布维护
351
+
352
+ 维护者的 GitHub Release、OIDC Trusted Publishing 与 npm 发布流程见 [PUBLISHING.md](PUBLISHING.md)。普通 `main` 提交、本地安装和手动 `npm pack` 都不会触发 npm 发布。
package/SHARE_NOTES.md CHANGED
@@ -31,18 +31,18 @@
31
31
  ## 本次整理
32
32
 
33
33
  - 删除打包者专用的 Kimi、agy 绝对路径,使用接收者本机的检测结果。
34
- - Kimi / agy 新安装默认 `main-session`(主会话审批);0.8.x 保存的 `allow` / `reject` 值继续有效。批准操作不是操作系统级沙箱,风险说明见 INSTALL.zh.md。
34
+ - Kimi / agy 新安装默认 `main-session`(主会话审批);0.8.x 保存的 `allow` / `reject` 值继续有效。主会话有效权限为“不再询问 + 完全权限”时,默认策略会让 Codex / Kimi / agy 继承完全权限;其他组合仍按审批链处理。批准操作不是操作系统级沙箱,风险说明见 INSTALL.zh.md。
35
35
  - 补充首次安装、前置依赖、CLI 检测、费用与安全说明。
36
36
  - 保留已构建 Client、所有 Host 入口、语言资源、图标和第三方声明。
37
37
  - 不捆绑 node_modules、用户配置、账户凭据、会话、任务报告、备份、开发测试或本机日志。
38
38
 
39
39
  ## 安装包性质
40
40
 
41
- 这是 npm 格式 TGZ,可交给 DSH 插件管理器从本地绝对路径安装。安装依赖仍需联网;无需接收者自行构建前端。`private: true` 仅阻止误发布 npm,不阻止本地安装。
41
+ 这是 npm 格式 TGZ,可交给 DSH 插件管理器从本地绝对路径安装。安装依赖仍需联网;无需接收者自行构建前端。包清单已允许公共 npm 发布,但只有推送新版本标签并通过 GitHub Release 工作流才会发布,本地安装不会触发 npm publish。
42
42
 
43
- 安装包从 [GitHub Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载:`dsh-multi-agent-0.9.2.tgz` 及同名 `.sha256`。SHA-256 文件用于核对传输完整性,不是数字签名,也不能独立证明来源可信。
43
+ 安装包从 [GitHub Releases](https://github.com/kiritoko1029/dsh-multi-agent/releases) 下载:`kiritoko1029-dsh-multi-agent-0.9.3.tgz` 及同名 `.sha256`。SHA-256 文件用于核对传输完整性,不是数字签名,也不能独立证明来源可信。
44
44
 
45
- 该包由独立工作区构建;TGZ 不含打包者 profile、用户配置或凭据。源码与安装包发布到 GitHub 仓库与 Releases;不执行 npm publish,也不提交插件市场。
45
+ 该包由独立工作区构建;TGZ 不含打包者 profile、用户配置或凭据。源码与安装包发布到 GitHub 仓库与 Releases;0.9.3 起由 GitHub Actions 通过 OIDC Trusted Publishing 同步到 npm 包 `@kiritoko1029/dsh-multi-agent`,对应资产名为 `kiritoko1029-dsh-multi-agent-<version>.tgz`。0.9.2 及更早版本不追溯发布,也不提交插件市场。
46
46
 
47
47
  ## 支持边界
48
48
 
package/acp-provider.js CHANGED
@@ -4,7 +4,7 @@ import { PROTOCOL_VERSION, client, methods, ndJsonStream } from '@agentclientpro
4
4
  import z from '@deepseek-ai/schemastery'
5
5
  import { currentTaskTrace } from './task-monitor.js'
6
6
  import { currentValue, dynamicAcpConfig } from './lib/config.js'
7
- import { createPermissionAsker, normalizePermissionPolicy, normalizeSubagentPermissionMode, PERMISSION_POLICIES, SUBAGENT_PERMISSION_MODES } from './lib/permission-policy.js'
7
+ import { createPermissionAsker, normalizePermissionPolicy, normalizeSubagentPermissionMode, PERMISSION_POLICIES, resolveMainSessionFullAccess, SUBAGENT_PERMISSION_MODES } from './lib/permission-policy.js'
8
8
  import { kimiCatalogFromOutput, registerModelCatalogRoute, runCatalogCommand } from './lib/model-catalog-route.js'
9
9
  import { sameDirectory } from './lib/path-compat.js'
10
10
  import { assertDirectCommand, commandEnvironment, hasEnvironmentKey } from './lib/process-command.js'
@@ -693,6 +693,8 @@ export class AcpProvider {
693
693
  }
694
694
  const permissionPolicy = normalizePermissionPolicy(currentValue(this.config.permission))
695
695
  const subagentPermissionMode = normalizeSubagentPermissionMode(currentValue(this.config.subagentPermissionMode))
696
+ const mainSessionFullAccess = permissionPolicy === 'main-session'
697
+ && resolveMainSessionFullAccess(this.ctx, request.parent)
696
698
  const askPermission = createPermissionAsker({
697
699
  ctx: this.ctx,
698
700
  parent: request.parent,
@@ -702,13 +704,15 @@ export class AcpProvider {
702
704
  agentLabel: this.dialect.label,
703
705
  language: trace?.record?.language,
704
706
  signal: request.signal,
707
+ mainSessionFullAccess,
705
708
  })
706
709
  // The agy CLI exposes a native full-access bypass; kimi acp documents no
707
710
  // startup flag, so its full-access level is realized by granting every
708
- // ask instead.
711
+ // ask instead. The same backend treatment applies to an explicitly
712
+ // configured subagent full access and an inherited native full preset.
709
713
  const bypassArgs = this.dialect.bypassFlag !== undefined
710
- && permissionPolicy === 'subagent'
711
- && subagentPermissionMode === 'full-access'
714
+ && (mainSessionFullAccess
715
+ || (permissionPolicy === 'subagent' && subagentPermissionMode === 'full-access'))
712
716
  && !(this.config.args ?? []).includes(this.dialect.bypassFlag)
713
717
  ? [this.dialect.bypassFlag]
714
718
  : []
package/client-bundle.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // Generated by scripts/build-client.mjs; edit client.js or tasks-client.js.
2
2
  // Third-party licenses: THIRD_PARTY_NOTICES.md.
3
3
  window.__ModuleLoader__.load({
4
- id: 'dsh-multi-agent',
4
+ id: "@kiritoko1029/dsh-multi-agent",
5
5
  factory(require) {
6
6
  const markdownIt = (() => {
7
7
  const module = { exports: {} };
@@ -16,7 +16,7 @@ window.__ModuleLoader__.load({
16
16
  const React = require('react')
17
17
  const h = React.createElement
18
18
  const { useState, useEffect, useRef } = React
19
- const NS = 'dsh-multi-agent'
19
+ const NS = '@kiritoko1029/dsh-multi-agent'
20
20
  const AGENT_OPTIONS = [
21
21
  { value: 'codex', label: 'Codex' },
22
22
  { value: 'kimi-code', label: 'Kimi Code' },
@@ -65,7 +65,7 @@ window.__ModuleLoader__.load({
65
65
  'codex.model': 'Default Codex model (blank uses native configuration)',
66
66
  'codex.command': 'Codex CLI path (blank uses auto-detection)',
67
67
  'codex.permissionPolicy': 'Sub-agent permission requests',
68
- 'codex.permissionPolicy.main-session': 'Ask the main session (default)',
68
+ 'codex.permissionPolicy.main-session': 'Main session / inherit full access (default)',
69
69
  'codex.permissionPolicy.human': 'Ask me interactively',
70
70
  'codex.permissionPolicy.allow': 'Allow all requests',
71
71
  'codex.permissionPolicy.reject': 'Deny all requests',
@@ -88,7 +88,7 @@ window.__ModuleLoader__.load({
88
88
  'acp.model': 'Default Kimi model alias (blank uses native configuration)',
89
89
  'acp.args': 'ACP arguments (JSON string array)',
90
90
  'acp.permission': 'Sub-agent permission requests',
91
- 'acp.permission.main-session': 'Ask the main session (default)',
91
+ 'acp.permission.main-session': 'Main session / inherit full access (default)',
92
92
  'acp.permission.human': 'Ask me interactively',
93
93
  'acp.permission.allow': 'Allow all requests',
94
94
  'acp.permission.reject': 'Deny all requests',
@@ -110,7 +110,7 @@ window.__ModuleLoader__.load({
110
110
  'agy.model': 'Default Antigravity model (blank uses native configuration)',
111
111
  'agy.args': 'ACP arguments (JSON string array)',
112
112
  'agy.permission': 'Sub-agent permission requests',
113
- 'agy.permission.main-session': 'Ask the main session (default)',
113
+ 'agy.permission.main-session': 'Main session / inherit full access (default)',
114
114
  'agy.permission.human': 'Ask me interactively',
115
115
  'agy.permission.allow': 'Allow all requests',
116
116
  'agy.permission.reject': 'Deny all requests',
@@ -208,7 +208,7 @@ window.__ModuleLoader__.load({
208
208
  'codex.model': '默认 Codex 模型(留空使用原生配置)',
209
209
  'codex.command': 'Codex CLI 路径(留空自动检测)',
210
210
  'codex.permissionPolicy': '子代理权限请求策略',
211
- 'codex.permissionPolicy.main-session': '主会话审批(默认)',
211
+ 'codex.permissionPolicy.main-session': '主会话审批 / 继承完全权限(默认)',
212
212
  'codex.permissionPolicy.human': '人工审批',
213
213
  'codex.permissionPolicy.allow': '允许所有请求',
214
214
  'codex.permissionPolicy.reject': '拒绝所有请求',
@@ -231,7 +231,7 @@ window.__ModuleLoader__.load({
231
231
  'acp.model': '默认 Kimi 模型别名(留空使用原生配置)',
232
232
  'acp.args': 'ACP 启动参数(JSON 字符串数组)',
233
233
  'acp.permission': '子代理权限请求策略',
234
- 'acp.permission.main-session': '主会话审批(默认)',
234
+ 'acp.permission.main-session': '主会话审批 / 继承完全权限(默认)',
235
235
  'acp.permission.human': '人工审批',
236
236
  'acp.permission.allow': '允许所有请求',
237
237
  'acp.permission.reject': '拒绝所有请求',
@@ -253,7 +253,7 @@ window.__ModuleLoader__.load({
253
253
  'agy.model': '默认 Antigravity 模型(留空使用原生配置)',
254
254
  'agy.args': 'ACP 启动参数(JSON 字符串数组)',
255
255
  'agy.permission': '子代理权限请求策略',
256
- 'agy.permission.main-session': '主会话审批(默认)',
256
+ 'agy.permission.main-session': '主会话审批 / 继承完全权限(默认)',
257
257
  'agy.permission.human': '人工审批',
258
258
  'agy.permission.allow': '允许所有请求',
259
259
  'agy.permission.reject': '拒绝所有请求',
package/client.js CHANGED
@@ -1,10 +1,10 @@
1
1
  window.__ModuleLoader__.load({
2
- id: 'dsh-multi-agent',
2
+ id: '@kiritoko1029/dsh-multi-agent',
3
3
  factory(require) {
4
4
  const React = require('react')
5
5
  const h = React.createElement
6
6
  const { useState, useEffect, useRef } = React
7
- const NS = 'dsh-multi-agent'
7
+ const NS = '@kiritoko1029/dsh-multi-agent'
8
8
  const AGENT_OPTIONS = [
9
9
  { value: 'codex', label: 'Codex' },
10
10
  { value: 'kimi-code', label: 'Kimi Code' },
@@ -53,7 +53,7 @@ window.__ModuleLoader__.load({
53
53
  'codex.model': 'Default Codex model (blank uses native configuration)',
54
54
  'codex.command': 'Codex CLI path (blank uses auto-detection)',
55
55
  'codex.permissionPolicy': 'Sub-agent permission requests',
56
- 'codex.permissionPolicy.main-session': 'Ask the main session (default)',
56
+ 'codex.permissionPolicy.main-session': 'Main session / inherit full access (default)',
57
57
  'codex.permissionPolicy.human': 'Ask me interactively',
58
58
  'codex.permissionPolicy.allow': 'Allow all requests',
59
59
  'codex.permissionPolicy.reject': 'Deny all requests',
@@ -76,7 +76,7 @@ window.__ModuleLoader__.load({
76
76
  'acp.model': 'Default Kimi model alias (blank uses native configuration)',
77
77
  'acp.args': 'ACP arguments (JSON string array)',
78
78
  'acp.permission': 'Sub-agent permission requests',
79
- 'acp.permission.main-session': 'Ask the main session (default)',
79
+ 'acp.permission.main-session': 'Main session / inherit full access (default)',
80
80
  'acp.permission.human': 'Ask me interactively',
81
81
  'acp.permission.allow': 'Allow all requests',
82
82
  'acp.permission.reject': 'Deny all requests',
@@ -98,7 +98,7 @@ window.__ModuleLoader__.load({
98
98
  'agy.model': 'Default Antigravity model (blank uses native configuration)',
99
99
  'agy.args': 'ACP arguments (JSON string array)',
100
100
  'agy.permission': 'Sub-agent permission requests',
101
- 'agy.permission.main-session': 'Ask the main session (default)',
101
+ 'agy.permission.main-session': 'Main session / inherit full access (default)',
102
102
  'agy.permission.human': 'Ask me interactively',
103
103
  'agy.permission.allow': 'Allow all requests',
104
104
  'agy.permission.reject': 'Deny all requests',
@@ -196,7 +196,7 @@ window.__ModuleLoader__.load({
196
196
  'codex.model': '默认 Codex 模型(留空使用原生配置)',
197
197
  'codex.command': 'Codex CLI 路径(留空自动检测)',
198
198
  'codex.permissionPolicy': '子代理权限请求策略',
199
- 'codex.permissionPolicy.main-session': '主会话审批(默认)',
199
+ 'codex.permissionPolicy.main-session': '主会话审批 / 继承完全权限(默认)',
200
200
  'codex.permissionPolicy.human': '人工审批',
201
201
  'codex.permissionPolicy.allow': '允许所有请求',
202
202
  'codex.permissionPolicy.reject': '拒绝所有请求',
@@ -219,7 +219,7 @@ window.__ModuleLoader__.load({
219
219
  'acp.model': '默认 Kimi 模型别名(留空使用原生配置)',
220
220
  'acp.args': 'ACP 启动参数(JSON 字符串数组)',
221
221
  'acp.permission': '子代理权限请求策略',
222
- 'acp.permission.main-session': '主会话审批(默认)',
222
+ 'acp.permission.main-session': '主会话审批 / 继承完全权限(默认)',
223
223
  'acp.permission.human': '人工审批',
224
224
  'acp.permission.allow': '允许所有请求',
225
225
  'acp.permission.reject': '拒绝所有请求',
@@ -241,7 +241,7 @@ window.__ModuleLoader__.load({
241
241
  'agy.model': '默认 Antigravity 模型(留空使用原生配置)',
242
242
  'agy.args': 'ACP 启动参数(JSON 字符串数组)',
243
243
  'agy.permission': '子代理权限请求策略',
244
- 'agy.permission.main-session': '主会话审批(默认)',
244
+ 'agy.permission.main-session': '主会话审批 / 继承完全权限(默认)',
245
245
  'agy.permission.human': '人工审批',
246
246
  'agy.permission.allow': '允许所有请求',
247
247
  'agy.permission.reject': '拒绝所有请求',
package/cordis.patch.yml CHANGED
@@ -1,15 +1,15 @@
1
1
  - insert:
2
2
  - id: multi-agent-availability
3
- name: 'dsh-multi-agent/availability'
3
+ name: '@kiritoko1029/dsh-multi-agent/availability'
4
4
  - id: multi-agent-task-monitor
5
- name: 'dsh-multi-agent/task-monitor'
5
+ name: '@kiritoko1029/dsh-multi-agent/task-monitor'
6
6
  - id: multi-agent-delegation-policy
7
- name: 'dsh-multi-agent/delegation-policy'
7
+ name: '@kiritoko1029/dsh-multi-agent/delegation-policy'
8
8
  - id: multi-agent-router
9
- name: 'dsh-multi-agent'
9
+ name: '@kiritoko1029/dsh-multi-agent'
10
10
  config: {}
11
11
  - id: kimi-code-acp
12
- name: 'dsh-multi-agent/acp-provider'
12
+ name: '@kiritoko1029/dsh-multi-agent/acp-provider'
13
13
  config:
14
14
  providerName: kimi-code
15
15
  command: !!js "ctx.get('multiAgentAvailability')?.kimiCommand"
@@ -18,7 +18,7 @@
18
18
  subagentPermissionMode: auto
19
19
  inject: [multiAgentAvailability]
20
20
  - id: agy-acp
21
- name: 'dsh-multi-agent/agy-provider'
21
+ name: '@kiritoko1029/dsh-multi-agent/agy-provider'
22
22
  config:
23
23
  providerName: agy
24
24
  command: !!js "ctx.get('multiAgentAvailability')?.agyCommand"
@@ -27,7 +27,7 @@
27
27
  subagentPermissionMode: auto
28
28
  inject: [multiAgentAvailability]
29
29
  - id: codex-provider
30
- name: 'dsh-multi-agent/codex-provider'
30
+ name: '@kiritoko1029/dsh-multi-agent/codex-provider'
31
31
  config:
32
32
  providerName: codex-router
33
33
  permissionPolicy: main-session
package/lib/codex-run.js CHANGED
@@ -3,7 +3,7 @@ import { writeFileSync } from "node:fs";
3
3
  import { currentTaskTrace } from "../task-monitor.js";
4
4
  import { codexWrapperArgv } from "./codex-command.js";
5
5
  import { JsonRpcLineTransport } from "./json-rpc-line-transport.js";
6
- import { createPermissionAsker, normalizePermissionPolicy, normalizeSubagentPermissionMode } from "./permission-policy.js";
6
+ import { createPermissionAsker, normalizePermissionPolicy, normalizeSubagentPermissionMode, resolveMainSessionFullAccess } from "./permission-policy.js";
7
7
  import { sameDirectory } from "./path-compat.js";
8
8
  import { assertDirectCommand } from "./process-command.js";
9
9
  import { NO_START_CAPABILITIES, assertPositiveFinite, assertUsableCwd, settleRunResult, subprocessRunHandle } from "./provider-process-contract.js";
@@ -68,9 +68,11 @@ const THREAD_PERMISSION_PARAMS = {
68
68
  * @param permissionMode - configured Codex native mode.
69
69
  * @param permissionPolicy - plugin permission policy (normalized).
70
70
  * @param subagentMode - sub-agent control level (normalized).
71
+ * @param mainSessionFullAccess - run-start snapshot of the native full preset.
71
72
  * @returns the thread permission mode for {@link THREAD_PERMISSION_PARAMS}.
72
73
  */
73
- function effectiveCodexThreadMode(permissionMode, permissionPolicy, subagentMode) {
74
+ function effectiveCodexThreadMode(permissionMode, permissionPolicy, subagentMode, mainSessionFullAccess = false) {
75
+ if (permissionPolicy === "main-session" && mainSessionFullAccess) return "dangerously-bypass-approvals-and-sandbox";
74
76
  if (permissionPolicy === "main-session" || permissionPolicy === "human" || permissionPolicy === "allow") return "ask-request";
75
77
  if (permissionPolicy === "subagent") {
76
78
  if (subagentMode === "full-access") return "dangerously-bypass-approvals-and-sandbox";
@@ -864,6 +866,7 @@ async function startCodexRun(request, spec) {
864
866
  spec.permissionMode,
865
867
  normalizePermissionPolicy(spec.permissionPolicy),
866
868
  normalizeSubagentPermissionMode(spec.subagentPermissionMode),
869
+ spec.mainSessionFullAccess === true,
867
870
  ), spec.model, { ask: spec.askPermission });
868
871
  const onStderr = (chunk) => {
869
872
  const bytes = typeof chunk === "string" ? Buffer.from(chunk) : chunk;
@@ -1054,6 +1057,8 @@ export class CodexProvider {
1054
1057
  }
1055
1058
  const permissionPolicy = normalizePermissionPolicy(this.config.permissionPolicy);
1056
1059
  const subagentPermissionMode = normalizeSubagentPermissionMode(this.config.subagentPermissionMode);
1060
+ const mainSessionFullAccess = permissionPolicy === "main-session"
1061
+ && resolveMainSessionFullAccess(this.ctx, request.parent);
1057
1062
  const askPermission = createPermissionAsker({
1058
1063
  ctx: this.ctx,
1059
1064
  parent: request.parent,
@@ -1063,6 +1068,7 @@ export class CodexProvider {
1063
1068
  agentLabel: "Codex",
1064
1069
  language: trace?.record?.language,
1065
1070
  signal: request.signal,
1071
+ mainSessionFullAccess,
1066
1072
  });
1067
1073
  return startCodexRun(request, {
1068
1074
  cwd,
@@ -1072,6 +1078,7 @@ export class CodexProvider {
1072
1078
  permissionMode: this.config.permissionMode,
1073
1079
  permissionPolicy,
1074
1080
  subagentPermissionMode,
1081
+ mainSessionFullAccess,
1075
1082
  askPermission,
1076
1083
  env: this.config.env,
1077
1084
  disposeGraceMs: this.config.disposeGraceMs,
@@ -8,7 +8,9 @@
8
8
  * - `main-session` (default): ask the delegating session's native approval
9
9
  * chain (`ctx.approval`); when that chain cannot take the question (for
10
10
  * example the parent turn already ended), fall back to the interactive
11
- * human card so the task is never silently blocked.
11
+ * human card so the task is never silently blocked. An effective native
12
+ * `never` + `danger-full-access` preset is inherited as full access instead
13
+ * of turning the chain's automatic rejection into a child denial.
12
14
  * - `human`: always show the interactive approval card (`ctx.userQuestions`).
13
15
  * - `allow`: grant every request.
14
16
  * - `reject`: deny every request (the 0.8.x share-release default for ACP).
@@ -43,6 +45,51 @@ export function normalizeSubagentPermissionMode(value) {
43
45
  return SUBAGENT_PERMISSION_MODES.includes(value) ? value : 'auto'
44
46
  }
45
47
 
48
+ function serviceOf(ctx, name) {
49
+ try {
50
+ return typeof ctx?.get === 'function' ? ctx.get(name) : undefined
51
+ } catch {
52
+ return undefined
53
+ }
54
+ }
55
+
56
+ /**
57
+ * Resolve the delegating Session's effective permission preset without
58
+ * inferring user intent from an ApprovalOutcome. Only the exact native
59
+ * `never` + `danger-full-access` combination inherits full access. Explicit
60
+ * human rejections under every other combination remain rejections.
61
+ *
62
+ * The preset service is the authoritative effective-knob view. Older hosts
63
+ * without it fall back to the approval and sandbox services; missing or
64
+ * failing services fail closed and preserve the normal approval chain.
65
+ */
66
+ export function resolveMainSessionFullAccess(ctx, parent) {
67
+ const session = parent?.session
68
+ if (session === undefined || session === null) return false
69
+ const presets = serviceOf(ctx, 'permissionPresets')
70
+ if (presets !== undefined && presets !== null) {
71
+ try {
72
+ const current = presets.current(session)
73
+ if (current !== 'custom') {
74
+ const preset = presets.resolve(current)
75
+ return preset?.approval === 'never' && preset?.sandbox === 'danger-full-access'
76
+ }
77
+ } catch {
78
+ // Fall through to the individual effective-knob services.
79
+ }
80
+ }
81
+ const approval = serviceOf(ctx, 'approval')
82
+ const sandboxPolicy = serviceOf(ctx, 'sandboxPolicy')
83
+ if (approval === undefined || approval === null || sandboxPolicy === undefined || sandboxPolicy === null) return false
84
+ try {
85
+ const approvalPolicy = approval.overrideOf(session) ?? approval.defaultPolicy
86
+ const sandbox = sandboxPolicy.resolve({ session })?.mode
87
+ return approvalPolicy === 'never' && sandbox === 'danger-full-access'
88
+ } catch {
89
+ return false
90
+ }
91
+ }
92
+
46
93
  const ALLOW = 'allow'
47
94
  const DENY = 'deny'
48
95
 
@@ -145,32 +192,29 @@ function decisionLabels(language) {
145
192
  * @param {string} [options.language] Task language tag.
146
193
  * @param {AbortSignal} [options.signal] Run cancellation; aborting withdraws
147
194
  * pending questions and denies.
195
+ * @param {boolean} [options.mainSessionFullAccess] Run-start snapshot shared
196
+ * with the backend launcher; when omitted it is resolved from Host services.
148
197
  * @param {(note: string) => void} [options.onDecision] Optional observer for
149
198
  * activity/diagnostic recording.
150
199
  * @returns {(request: {kindLabel: string, title?: string, detail?: string, canAlways?: boolean}) => Promise<{outcome: 'allow'|'deny', scope: 'once'|'always', source: string}>}
151
200
  */
152
201
  export function createPermissionAsker({
153
202
  ctx, parent, policy, subagentMode, backend, agentLabel, language, signal, onDecision,
203
+ mainSessionFullAccess,
154
204
  }) {
155
205
  const normalizedPolicy = normalizePermissionPolicy(policy)
156
206
  const normalizedMode = normalizeSubagentPermissionMode(subagentMode)
207
+ const inheritFullAccess = normalizedPolicy === 'main-session'
208
+ && (mainSessionFullAccess ?? resolveMainSessionFullAccess(ctx, parent))
157
209
  const note = (message) => {
158
210
  try { onDecision?.(message) } catch { /* observer failures never block a decision */ }
159
211
  }
160
212
 
161
- function serviceOf(ctx, name) {
162
- try {
163
- return typeof ctx?.get === 'function' ? ctx.get(name) : undefined
164
- } catch {
165
- return undefined
213
+ function askMainSessionError(error) {
214
+ // The native chain refuses out-of-turn asks and service hiccups; keep the
215
+ // task moving by degrading to the interactive question.
216
+ return { fallback: true, note: `main-session approval unavailable (${truncate(error?.message, 160)}); falling back to an interactive question` }
166
217
  }
167
- }
168
-
169
- function askMainSessionError(error) {
170
- // The native chain refuses out-of-turn asks and service hiccups; keep the
171
- // task moving by degrading to the interactive question.
172
- return { fallback: true, note: `main-session approval unavailable (${truncate(error?.message, 160)}); falling back to an interactive question` }
173
- }
174
218
 
175
219
  async function askMainSession(request) {
176
220
  const approval = serviceOf(ctx, 'approval')
@@ -238,6 +282,7 @@ function askMainSessionError(error) {
238
282
  if (signal?.aborted) return { outcome: DENY, scope: 'once', source: 'cancelled' }
239
283
  if (normalizedPolicy === 'allow') return { outcome: ALLOW, scope: 'once', source: 'policy-allow' }
240
284
  if (normalizedPolicy === 'reject') return { outcome: DENY, scope: 'once', source: 'policy-reject' }
285
+ if (inheritFullAccess) return { outcome: ALLOW, scope: 'always', source: 'main-session-full-access' }
241
286
  if (normalizedPolicy === 'subagent') {
242
287
  if (normalizedMode === 'full-access') return { outcome: ALLOW, scope: 'always', source: 'subagent-full-access' }
243
288
  if (normalizedMode === 'auto') {
package/package.json CHANGED
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "name": "@kiritoko1029/dsh-multi-agent",
3
- "version": "0.9.3-stage.0",
3
+ "version": "0.9.3",
4
+ "publishConfig": {
5
+ "access": "public",
6
+ "registry": "https://registry.npmjs.org/"
7
+ },
4
8
  "engines": {
5
9
  "dsh": "0.2.2-rc.1 || 0.2.2-rc.2 || 0.2.2-rc.3"
6
10
  },
@@ -68,6 +72,7 @@
68
72
  "README.md",
69
73
  "CHANGELOG.md",
70
74
  "INSTALL.zh.md",
75
+ "PUBLISHING.md",
71
76
  "SHARE_NOTES.md",
72
77
  "THIRD_PARTY_NOTICES.md",
73
78
  "delegation-policy.js",
@@ -114,9 +119,5 @@
114
119
  "@deepseek-ai/dsh-client-ui-sidebar-documentpreview"
115
120
  ]
116
121
  }
117
- },
118
- "publishConfig": {
119
- "access": "public",
120
- "registry": "https://registry.npmjs.org/"
121
122
  }
122
123
  }
@@ -3,6 +3,8 @@ import vm from 'node:vm'
3
3
  import { createRequire } from 'node:module'
4
4
 
5
5
  const require = createRequire(import.meta.url)
6
+ const packageName = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')).name
7
+ if (!packageName) throw new Error('package.json name is required for the Client module id')
6
8
 
7
9
  // Capture declarations only; never execute either Client factory during build.
8
10
  function factoryOf(file) {
@@ -22,5 +24,5 @@ const tasks = factoryOf('tasks-client.js')
22
24
  // Embed the dependency's self-contained browser build in a private CommonJS
23
25
  // scope. This adds no browser runtime imports, global writes, eval, or CDN load.
24
26
  const markdown = readFileSync(require.resolve('markdown-it/dist/markdown-it.min.js'), 'utf8')
25
- const output = `// Generated by scripts/build-client.mjs; edit client.js or tasks-client.js.\n// Third-party licenses: THIRD_PARTY_NOTICES.md.\nwindow.__ModuleLoader__.load({\n id: 'dsh-multi-agent',\n factory(require) {\n const markdownIt = (() => {\n const module = { exports: {} };\n const exports = module.exports;\n${markdown}\n return module.exports;\n })();\n const taskRequire = (id) => id === 'markdown-it' ? markdownIt : require(id);\n const settings = (${settings})(require);\n const tasks = (${tasks})(taskRequire);\n return {\n inject: settings.inject,\n apply(ctx) {\n settings.apply(ctx);\n ctx.inject(tasks.inject, (child) => tasks.apply(child));\n },\n };\n },\n});\n`
27
+ const output = `// Generated by scripts/build-client.mjs; edit client.js or tasks-client.js.\n// Third-party licenses: THIRD_PARTY_NOTICES.md.\nwindow.__ModuleLoader__.load({\n id: ${JSON.stringify(packageName)},\n factory(require) {\n const markdownIt = (() => {\n const module = { exports: {} };\n const exports = module.exports;\n${markdown}\n return module.exports;\n })();\n const taskRequire = (id) => id === 'markdown-it' ? markdownIt : require(id);\n const settings = (${settings})(require);\n const tasks = (${tasks})(taskRequire);\n return {\n inject: settings.inject,\n apply(ctx) {\n settings.apply(ctx);\n ctx.inject(tasks.inject, (child) => tasks.apply(child));\n },\n };\n },\n});\n`
26
28
  writeFileSync(new URL('../client-bundle.js', import.meta.url), output)
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process'
2
2
  import { createHash } from 'node:crypto'
3
- import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
3
+ import { appendFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs'
4
4
  import { join } from 'node:path'
5
5
  import { fileURLToPath } from 'node:url'
6
6
 
@@ -30,6 +30,7 @@ if (!existsSync(artifact)) {
30
30
 
31
31
  const digest = createHash('sha256').update(readFileSync(artifact)).digest('hex')
32
32
  writeFileSync(`${artifact}.sha256`, `${digest} ${expected}\n`)
33
+ if (process.env.GITHUB_OUTPUT) appendFileSync(process.env.GITHUB_OUTPUT, `sha256=${digest}\n`)
33
34
 
34
35
  console.log(`\nArtifact: dist/${expected}`)
35
36
  console.log(`SHA-256: ${digest}`)
@@ -0,0 +1,191 @@
1
+ import { spawnSync } from 'node:child_process'
2
+ import { createHash } from 'node:crypto'
3
+ import { readFileSync, readdirSync } from 'node:fs'
4
+ import { resolve, join } from 'node:path'
5
+ import { fileURLToPath } from 'node:url'
6
+
7
+ export const NPM_REGISTRY = 'https://registry.npmjs.org/'
8
+
9
+ function assert(condition, message) {
10
+ if (!condition) throw new Error(message)
11
+ }
12
+
13
+ export function packageAssetName(manifest) {
14
+ const slug = manifest.name.replace(/^@/, '').replace(/\//g, '-')
15
+ return `${slug}-${manifest.version}.tgz`
16
+ }
17
+
18
+ function readArchiveManifest(assetPath) {
19
+ const result = spawnSync('tar', ['-xOf', assetPath, 'package/package.json'], {
20
+ encoding: 'utf8',
21
+ maxBuffer: 1024 * 1024,
22
+ })
23
+ if (result.error) throw result.error
24
+ assert(result.status === 0, `cannot read package/package.json from ${assetPath}`)
25
+ return JSON.parse(result.stdout)
26
+ }
27
+
28
+ function assertPublicManifest(manifest, label, repository) {
29
+ assert(manifest.private !== true, `${label} is private`)
30
+ assert(manifest.publishConfig?.access === 'public', `${label} must set publishConfig.access=public`)
31
+ assert(manifest.publishConfig?.registry === NPM_REGISTRY, `${label} must target ${NPM_REGISTRY}`)
32
+ assert(
33
+ manifest.repository?.url === `git+https://github.com/${repository}.git`,
34
+ `${label} repository must match ${repository}`,
35
+ )
36
+ }
37
+
38
+ export function verifyReleaseAsset({
39
+ rootDir = process.cwd(),
40
+ distDir = join(process.cwd(), 'dist'),
41
+ tag = process.env.GITHUB_REF_NAME,
42
+ repository = process.env.GITHUB_REPOSITORY,
43
+ expectedSha256 = process.env.EXPECTED_TGZ_SHA256,
44
+ readManifest = readArchiveManifest,
45
+ } = {}) {
46
+ const rootManifest = JSON.parse(readFileSync(join(rootDir, 'package.json'), 'utf8'))
47
+ assert(typeof tag === 'string' && tag.startsWith('v'), 'release tag must start with v')
48
+ assert(tag.slice(1) === rootManifest.version, `tag ${tag} does not match package.json version ${rootManifest.version}`)
49
+ assert(typeof repository === 'string' && repository.length > 0, 'GITHUB_REPOSITORY is required')
50
+ assertPublicManifest(rootManifest, 'checkout package.json', repository)
51
+
52
+ const assetName = packageAssetName(rootManifest)
53
+ const entries = readdirSync(distDir, { withFileTypes: true })
54
+ const tgzFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.tgz')).map((entry) => entry.name)
55
+ const shaFiles = entries.filter((entry) => entry.isFile() && entry.name.endsWith('.sha256')).map((entry) => entry.name)
56
+ assert(tgzFiles.length === 1 && tgzFiles[0] === assetName, `dist must contain only ${assetName}`)
57
+ assert(shaFiles.length === 1 && shaFiles[0] === `${assetName}.sha256`, `dist must contain only ${assetName}.sha256`)
58
+
59
+ const assetPath = join(distDir, assetName)
60
+ const contents = readFileSync(assetPath)
61
+ const actualSha256 = createHash('sha256').update(contents).digest('hex')
62
+ assert(typeof expectedSha256 === 'string' && /^[0-9a-f]{64}$/.test(expectedSha256), 'release job TGZ SHA-256 output is required')
63
+ assert(actualSha256 === expectedSha256, 'downloaded TGZ does not match the release job build digest')
64
+
65
+ const escapedName = assetName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
66
+ const sidecar = readFileSync(`${assetPath}.sha256`, 'utf8')
67
+ const match = sidecar.match(new RegExp(`^([0-9a-f]{64}) ${escapedName}\r?\n?$`))
68
+ assert(match, `sidecar must contain exactly one SHA-256 record for ${assetName}`)
69
+ assert(match[1] === actualSha256, 'sidecar SHA-256 does not match the downloaded TGZ')
70
+
71
+ const archiveManifest = readManifest(assetPath)
72
+ assert(archiveManifest.name === rootManifest.name, 'TGZ package name does not match checkout package.json')
73
+ assert(archiveManifest.version === rootManifest.version, 'TGZ package version does not match the release tag')
74
+ assertPublicManifest(archiveManifest, 'TGZ package/package.json', repository)
75
+ assert(
76
+ archiveManifest.repository?.url === rootManifest.repository?.url,
77
+ 'TGZ repository does not match checkout package.json',
78
+ )
79
+
80
+ return {
81
+ name: rootManifest.name,
82
+ version: rootManifest.version,
83
+ assetPath,
84
+ sha256: actualSha256,
85
+ integrity: `sha512-${createHash('sha512').update(contents).digest('base64')}`,
86
+ }
87
+ }
88
+
89
+ export function queryNpmIntegrity(name, version, run = spawnSync) {
90
+ const result = run('npm', [
91
+ 'view', `${name}@${version}`, 'dist.integrity', '--json', `--registry=${NPM_REGISTRY}`,
92
+ ], { encoding: 'utf8', env: process.env, maxBuffer: 1024 * 1024 })
93
+ if (result.error) throw result.error
94
+ if (result.status === 0) {
95
+ let integrity
96
+ try {
97
+ integrity = JSON.parse(result.stdout)
98
+ } catch {
99
+ throw new Error('npm view returned invalid JSON')
100
+ }
101
+ assert(typeof integrity === 'string' && integrity.startsWith('sha512-'), `npm ${name}@${version} has no usable dist.integrity`)
102
+ return { found: true, integrity }
103
+ }
104
+ if (/\bE404\b/.test(result.stderr || '')) return { found: false }
105
+ throw new Error(`npm view failed with exit code ${result.status}; the package was not treated as absent`)
106
+ }
107
+
108
+ function publishPackage(assetPath) {
109
+ // npm 11.5.1+ exchanges GitHub's OIDC identity for short-lived registry
110
+ // credentials. No NODE_AUTH_TOKEN or other long-lived npm token is used.
111
+ const result = spawnSync('npm', [
112
+ 'publish', assetPath, '--access', 'public', '--provenance', `--registry=${NPM_REGISTRY}`,
113
+ ], { stdio: 'inherit', env: process.env })
114
+ if (result.error) throw result.error
115
+ assert(result.status === 0, `npm publish failed with exit code ${result.status}`)
116
+ }
117
+
118
+ const wait = (milliseconds) => new Promise((resolveWait) => setTimeout(resolveWait, milliseconds))
119
+
120
+ export async function publishVerifiedPackage(verified, {
121
+ view = queryNpmIntegrity,
122
+ publish = publishPackage,
123
+ sleep = wait,
124
+ logger = console,
125
+ prePublishAttempts = 3,
126
+ verifyAttempts = 12,
127
+ retryDelayMs = 5000,
128
+ } = {}) {
129
+ const { name, version, assetPath, integrity } = verified
130
+
131
+ async function lookup(attempts, retryMissing) {
132
+ let lastError
133
+ for (let attempt = 1; attempt <= attempts; attempt += 1) {
134
+ try {
135
+ const result = await view(name, version)
136
+ if (result?.found) return result
137
+ lastError = new Error(`npm ${name}@${version} is not visible`)
138
+ if (!retryMissing) return null
139
+ } catch (error) {
140
+ lastError = error
141
+ }
142
+ if (attempt < attempts) await sleep(retryDelayMs)
143
+ }
144
+ throw lastError || new Error(`cannot verify npm ${name}@${version}`)
145
+ }
146
+
147
+ const existing = await lookup(prePublishAttempts, false)
148
+ if (existing) {
149
+ assert(existing.integrity === integrity, `npm ${name}@${version} already exists with different content; refusing to overwrite`)
150
+ logger.log(`npm ${name}@${version} already exists with matching dist.integrity; skipping duplicate publish`)
151
+ return { published: false, alreadyExists: true }
152
+ }
153
+
154
+ let publishError = null
155
+ try {
156
+ await publish(assetPath)
157
+ } catch (error) {
158
+ publishError = error
159
+ logger.warn(`npm publish returned an error; checking whether the registry accepted ${name}@${version} before failing`)
160
+ }
161
+
162
+ try {
163
+ const published = await lookup(verifyAttempts, true)
164
+ assert(published.integrity === integrity, `published npm ${name}@${version} has different dist.integrity`)
165
+ } catch (verificationError) {
166
+ if (publishError) {
167
+ throw new Error(`${publishError.message}; subsequent registry verification failed: ${verificationError.message}`)
168
+ }
169
+ throw verificationError
170
+ }
171
+
172
+ return { published: !publishError, recoveredAfterPublishError: Boolean(publishError) }
173
+ }
174
+
175
+ async function main() {
176
+ assert(process.env.GITHUB_ACTIONS === 'true', 'Trusted Publishing must run in GitHub Actions')
177
+ assert(
178
+ process.env.ACTIONS_ID_TOKEN_REQUEST_URL && process.env.ACTIONS_ID_TOKEN_REQUEST_TOKEN,
179
+ 'GitHub OIDC credentials are unavailable; publish-npm requires id-token: write',
180
+ )
181
+ const verified = verifyReleaseAsset()
182
+ const result = await publishVerifiedPackage(verified)
183
+ console.log(`Verified npm ${verified.name}@${verified.version}: ${JSON.stringify(result)}`)
184
+ }
185
+
186
+ if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
187
+ main().catch((error) => {
188
+ console.error(`::error::${error.message}`)
189
+ process.exitCode = 1
190
+ })
191
+ }