@mogragsigma/dsh-dual-host-plugin 0.33.4 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/README.md +70 -99
  2. package/bundle/README.md +70 -99
  3. package/bundle/SETUP.md +87 -0
  4. package/bundle/approval-chat.js +2 -2
  5. package/bundle/approval-feishu.js +4 -1
  6. package/bundle/approval-ledger.js +30 -13
  7. package/bundle/approval-workbench.js +2 -0
  8. package/bundle/artifact-media.js +2 -2
  9. package/bundle/compatibility.json +43 -3
  10. package/bundle/configuration-v2.json +15 -0
  11. package/bundle/delivery-state.js +2 -0
  12. package/bundle/development-gate.js +2 -0
  13. package/bundle/distribution-lock.json +2 -2
  14. package/bundle/gateway-maintenance.js +1 -0
  15. package/bundle/global-rules/AGENTS.md +24 -20
  16. package/bundle/index.js +135 -25
  17. package/bundle/intervention-state.js +1 -1
  18. package/bundle/link-contract.js +9 -4
  19. package/bundle/managed-closeout.js +45 -0
  20. package/bundle/managed-cloud-policy.js +19 -0
  21. package/bundle/managed-command.js +71 -0
  22. package/bundle/managed-development-contract.js +43 -0
  23. package/bundle/managed-development.js +86 -0
  24. package/bundle/managed-endpoint.js +282 -0
  25. package/bundle/managed-execution.js +207 -0
  26. package/bundle/managed-policy.js +20 -0
  27. package/bundle/managed-runtime.js +186 -0
  28. package/bundle/managed-search.js +57 -0
  29. package/bundle/managed-tools.js +17 -0
  30. package/bundle/managed-triggers.js +85 -0
  31. package/bundle/native-context.js +4 -2
  32. package/bundle/native-entry.js +6 -2
  33. package/bundle/native-human-context.js +29 -0
  34. package/bundle/native-model-choice.js +12 -0
  35. package/bundle/native-rules/continuity.md +13 -0
  36. package/bundle/native-rules/execution.md +36 -0
  37. package/bundle/native-tools.js +52 -19
  38. package/bundle/notification-artifacts.js +26 -10
  39. package/bundle/notification-endpoint.js +1 -1
  40. package/bundle/notification-feishu.js +2 -2
  41. package/bundle/notification-media-endpoint.js +4 -3
  42. package/bundle/notification-observations.js +45 -0
  43. package/bundle/onboarding-cloud-package.js +1 -1
  44. package/bundle/onboarding-cloud.js +1 -1
  45. package/bundle/onboarding-file-contract.js +3 -2
  46. package/bundle/onboarding-file-transfer.js +4 -4
  47. package/bundle/onboarding-native-probe.js +1 -1
  48. package/bundle/onboarding-runtime-activation.js +1 -1
  49. package/bundle/onboarding-runtime-extension.js +2 -2
  50. package/bundle/package-lock.json +2 -2
  51. package/bundle/package.json +6 -4
  52. package/bundle/peer-file-review.js +1 -1
  53. package/bundle/peer-files.js +8 -4
  54. package/bundle/recovery.js +1 -1
  55. package/bundle/repository-gitee.js +12 -0
  56. package/bundle/repository-managed.js +1 -0
  57. package/bundle/runtime-im-lock.json +3409 -2849
  58. package/bundle/runtime-im-package.json +5 -5
  59. package/bundle/runtime-lock.json +3409 -2849
  60. package/bundle/runtime-package.json +5 -5
  61. package/bundle/schedule-ledger.js +35 -0
  62. package/bundle/search-contract.js +11 -1
  63. package/bundle/skills/task-approval/SKILL.md +20 -1
  64. package/bundle/skills/task-development/SKILL.md +30 -13
  65. package/bundle/skills/task-direct-link/SKILL.md +39 -13
  66. package/bundle/skills/task-file-transfer/SKILL.md +2 -2
  67. package/bundle/skills/task-notify/SKILL.md +2 -0
  68. package/bundle/skills/task-repository/SKILL.md +10 -1
  69. package/bundle/skills/task-research/SKILL.md +17 -2
  70. package/bundle/source-assessment.js +38 -0
  71. package/bundle/source-gitee.js +52 -0
  72. package/bundle/source-intake.js +137 -0
  73. package/bundle/source-policy-envelope.js +29 -0
  74. package/bundle/source-routing-tools.js +12 -0
  75. package/bundle/source-routing.js +218 -0
  76. package/bundle/source-search.js +2 -1
  77. package/bundle/store.js +6 -5
  78. package/bundle/task-artifacts.js +51 -12
  79. package/bundle/task-board.js +8 -4
  80. package/bundle/task-catalog.js +4 -3
  81. package/bundle/task-context.js +2 -2
  82. package/bundle/task-continuity-tools.js +14 -0
  83. package/bundle/task-continuity.js +248 -0
  84. package/bundle/task-envelope.js +31 -20
  85. package/bundle/task-notifications.js +6 -2
  86. package/bundle/task-observer-model.js +25 -0
  87. package/bundle/task-observer.js +120 -0
  88. package/bundle/task-results.js +1 -1
  89. package/bundle/task-scheduler.js +3 -3
  90. package/bundle/task-trigger-time.js +17 -12
  91. package/bundle/task-triggers.js +5 -30
  92. package/bundle/task-worker.js +10 -2
  93. package/bundle/task-workflows.js +2 -1
  94. package/bundle/workbench-client.js +165 -30
  95. package/bundle/workbench-decisions.js +22 -5
  96. package/bundle/workbench-host.js +5 -3
  97. package/bundle/worker-tools.js +8 -1
  98. package/compatibility.json +43 -3
  99. package/package.json +3 -3
package/README.md CHANGED
@@ -1,100 +1,71 @@
1
- # 大肥鱼 bot — DSH 本地与云端协作插件
2
-
3
- 面向 [DeepSeek Harness(DSH)](https://github.com/deepseek-ai/deepseek-harness) 的双端协作插件:
4
- 在本地对话或 IM 中自然语言交办,云端独立 worker 持续处理有界任务,
5
- 在统一工作台查看成果并处理需要你决定的事项。
6
-
7
- - npm 包:`@mogragsigma/dsh-dual-host-plugin`(从仓库根目录打包,入口指向 `bundle/`)
8
- - 兼容范围:`>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0`
9
- - 0.33.4 是宿主兼容修复,不是 2.0 功能发布;新验收覆盖 DSH `0.2.2-rc.1`,其他允许版本不等于逐版实测。
10
- 新建独立运行时模板仍固定 `0.2.1-rc.5`,插件兼容范围不改变安装锁定。
11
- - 许可:自定义源码可见许可(以中文文本为准),允许免费自用,禁止转售与收费对外托管
12
- - 本仓库是产品源码仓库;`bundle/` 为 npm 发布内容,`docs/` 为设计与验收文档
13
-
14
- ## 安装
15
-
16
- ```sh
17
- # 插件市场(推荐):在 DSH 插件市场搜索「大肥鱼 bot」一键安装
18
-
19
- # 命令行
20
- dsh plugin --profile web add @mogragsigma/dsh-dual-host-plugin --allow-build=koffi
21
- ```
22
-
23
- 本包含原生依赖 `koffi`,pnpm 11 默认会拦截依赖的构建脚本;插件市场会提示
24
- 「允许构建脚本并重试」,点击即可完成安装。若所用 pnpm 不支持 `--allow-build`,
25
- 可在目标 profile 的 `pnpm-workspace.yaml` 中加入:
26
-
27
- ```yaml
28
- allowBuilds:
29
- koffi: true
30
- ```
31
-
32
- Desktop 与 Web 请选择各自实际使用的 profile;安装后按当前 Host 的方式重载或重启。
33
-
34
- ## 能力
35
-
36
- - **双端协作**:本地与云端各装同一套自有插件,按角色与配对区分职责;不要求两个机器人同群
37
- - **持久任务**:云端独立 worker、有界批次与预算、检查点与重启恢复;入口不持续占用
38
- - **按任务选模型**:以目标端实际模型目录唯一匹配为准,简称可用,歧义先澄清
39
- - **审批账本**:工作台自然语言意见、已绑定 IM 身份核验、持久账本与权威回执;
40
- 批准、排队、执行、测试、交付、已读分别证明
41
- - **成果与文件**:任务成果查询、受控下载与原执行会话入口;IM/本地/云端三端文件收发
42
- - **六页配置导览**:连接云端、模型服务、聊天与审批、成果与仓库、安装计划、配置与验收
43
- - **全局规则与 Skill**:保护性追加或确认后创建,不覆盖用户原规则;冲突先阻止
44
-
45
- ## 兼容与边界
46
-
47
- - 仅声明已测试的官方 DSH `0.2.1-rc.5`;其他 Host 版本或平台需自行验证
48
- - 不修改 DSH 内核或第三方 IM 插件源码
49
- - 不承诺操作系统级隔离、全平台支持或全部 IM 渠道能力一致
50
- - 开发、commit、push、发布分别授权;源码可见不等于授权分发、部署或商业托管
51
- - 本项目是独立插件,不代表 DeepSeek 或依赖作者的官方产品
52
-
53
- ## 仓库结构
54
-
55
- | 路径 | 内容 |
1
+ # 大肥鱼 bot 2.0 — DSH 本地与云端协作插件
2
+
3
+ > 2.0.1发行说明。功能范围、验收范围、包已发布与实例已加载是不同事实。
4
+ > 本文不记录维护者的私有部署状态;安装或更新前核对包来源、版本、兼容性和目标Host。
5
+
6
+ ## 一、能力与入口
7
+
8
+ - 本地DSH、本人本地IM机器人、本人云端IM机器人三条交办通道,统一云端DSH原生执行。
9
+ - 单用户;IM所有者/白名单沿dsh-im,任务权限与IM权限独立,不新增个人凭证系统。
10
+ - 本地保留普通DSH分析、文件与对话;不接受反向受管worker,云端忙时不回退本地执行。
11
+ - 目标与必要原文归入任务,换会话仍可查询跟进;用户看整体进展与成果,不管理内部子代理。
12
+ - 云端充分使用运行账号下的原生工具,任务明确限制仍有效;无模型次数门禁、统一300秒截止或两小时重批。
13
+ - 3个普通执行名额+最多1个已有任务人工跟进名额;等待不提前创建执行Session。
14
+ - 复用原生后台作业、子代理、长任务、定时/依赖及命名信号;空闲7天归档、后续自动恢复,不自动删除Session。
15
+ - 成果快照、相关检查、约定交付分开呈现;就绪不等于推送、发布或用户已读。
16
+ - 选定Gitee Issue的observe/auto及本人确认的本期新任务总数,默认关闭,不订阅全仓/评论/Webhook。
17
+ - 蓝白叠页工作台用于查询及指示草稿;草稿需通过对话提交,不构成第四条创建入口。
18
+ - 云端大肥鱼页可直接“打开云端工作台”,进入原生DSH;与“查看云端任务”分开,沿用既有连接。
19
+
20
+ ## 二、安装与启用
21
+
22
+ - npm包名 `@mogragsigma/dsh-dual-host-plugin`,仅从仓库根打包;不另发内层bundle子包。
23
+ - [公开安装步骤](bundle/SETUP.md),[原生profile配置模板](bundle/configuration-v2.json)。
24
+ - 宽兼容范围:`>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0`;范围准入不等于逐版实测。
25
+ - 独立新建Host模板锁定DSH `0.2.2-rc.1`,不修改现有Host依赖;可选dsh-im锁定5.9.1。
26
+ - 真实IM证据限选定飞书本人目标,不承诺全平台。koffi构建按原生安装器确认,不私装旧版DSH组件。
27
+ - 规则/Skill安装先预览冲突,不能覆盖用户内容;六页旧只读运行时不是2.0云执行的启用证明。
28
+
29
+ ## 三、范围与迁移
30
+
31
+ 新任务不再走旧IM审批或短批次预算链;历史账本和兼容模块保留,不能据此恢复旧政策。
32
+ 云端自主不代表本地目录自动上传、资料里的指令获得授权,或服务商额度无限。
33
+ Git以执行端仓库为基础,远端可选。Gitee commit/push已有选定真实证据;产品PR合同仍仅GitHub,
34
+ 维护API创建的Gitee PR不是产品原生PR验收。
35
+
36
+ 跨端文件和IM发送需要实际接线与明确目标。新文件配置默认32MiB,可调整至技术上限512MiB,
37
+ 旧配置不自动扩大;不是无限大小或512MiB性能保证,IM仍受平台与网络限制。
38
+ 升级须先排空、校验备份;保留旧记录和新数据,恢复使用经验证的2.0快照及独立目录,
39
+ 不承诺0.33.4旧二进制可读新库。新建安装、已有实例升级和实际加载分别验收。
40
+ 已有实例沿用IM/ECS配置,不用新建模板覆盖原profile;旧任务完整迁移、定时任务无感接续不作本版承诺。
41
+ 不提供完整外置Watchdog、弹性云/任意BYOM或独立持续自动上线平台;不改DSH/第三方源码。
42
+ 任务执行与产品仓库发布是不同授权;安装候选不授权正式部署或产品推送。
43
+
44
+ ## 四、首发验收边界
45
+
46
+ 选定三通道、自然跟进、原生执行、来源策略、文件与UI已有分层证据;并非全平台或任意任务零错误。
47
+ 主验DSH 0.2.2-rc.1和下界0.2.1-rc.5均有隔离实装;选定真实IM为飞书,其他平台未作同等承诺。
48
+ 双端及云端IM大于2MiB文件已验证,本地IM大ZIP在网络超时下按批准边界简化,不能宣称已成功。
49
+ 会话归档为受控七天时钟验证;稳定性为双端超过两小时观察、首批满槽至少15分钟,非持续满载或SLA。
50
+ 窄窗适配以部分可视高度换取无遮挡;不承诺FPS或任意第三方挂件兼容。
51
+
52
+ ## 五、开发与验收
53
+
54
+ | 目录 | 用途 |
56
55
  | --- | --- |
57
- | `bundle/` | 插件包(npm 发布内容):自有插件、全局规则模板、Skill、工作台客户端、许可与声明 |
58
- | `ui-source/` | 工作台与六页导览前端源码(`bundle/` 内为生成结果,不手改) |
59
- | `scripts/` | 构建、模块索引与定向回归脚本 |
60
- | `tests/` | 合成测试;测试依赖声明见 `tests/package.json` |
61
- | `docs/` | 设计、兼容、验收与发行文档 |
62
- | `operator-kit/` | 历史运营工具,仅供维护参考 |
63
-
64
- ## 开发与测试
65
-
66
- - 需要 Node.js 22 与 Python 3;测试使用隔离 HOME/DSH_HOME 与合成服务,不读取日常配置
67
- - 定向回归(先阅读脚本清单,不通配执行历史测试):
68
-
69
- ```sh
70
- python3 scripts/run-release-regression.py
71
- ```
72
-
73
- ## 许可
74
-
75
- 本仓库根目录 `LICENSE` 为中文控制文本,`LICENSE.en.md` 为英文参考译本;
76
- npm 包内 `bundle/LICENSE` 与内容相同。核心规则:
77
-
78
- - 允许任何个人和组织免费安装、运行、研究、备份和修改,含企业内部使用与内部商业工作
79
- - 未经另行书面授权,禁止转售、出租、商业再许可、收费再分发和收费对外托管
80
- - 允许免费再分发;须附许可与声明、标明修改、不得冒充官方版本
81
- - 不是 MIT/Apache-2.0/OSI 开源许可;许可以中文文本为准
82
-
83
- 第三方组件声明见 `bundle/THIRD_PARTY_NOTICES.md`,素材声明见 `bundle/ASSET_NOTICE.md`。
84
-
85
- ## 反馈
86
-
87
- - Issues:本仓库
88
- - Gitee 镜像:<https://gitee.com/wenheng-Zhang/dsh-dual-host-plugin>(可见性以该站实际状态为准)
89
-
90
- 提交问题前请移除 API Key、机器人密钥、用户消息、内网地址、签名及私有任务数据。
91
-
92
- ## 0.33.3 验收合同
93
-
94
- - 根包是唯一 npm 发布入口;内层 `bundle/package.json` 供受控云端归档使用,不单独发布。
95
- - DSH 组件使用 Host 提供的可选 peer 依赖:可选仅表示包管理器不自动安装,功能仍要求 Host 服务。
96
- 本版明确要求 rc.5,不在插件下安装旧版 DSH 组件,也不修改 Host 的依赖。
97
- - `runtime-*-package/lock.json` 是另行确认后创建独立 Host 的固定依赖,不用于修改现有 Host。
98
- - `run-release-regression.py` 是当前具名验收入口;旧 `release-promotion`/`market-v0331`
99
- 与冻结清单保留作历史,不参与当前版本验收。当前合同保留身份、锁冲突、符号链接等拒绝测试。
100
- - UI 从 `ui-source/` 构建,模块索引从源码生成;GitHub 镜像不再另行修改发行代码。
56
+ | bundle | 插件、规则、Skill、安装模板与生成UI |
57
+ | ui-source | UI源码,修改后重新生成 |
58
+ | tests | 当前及历史具名测试 |
59
+ | scripts | 构建、索引、发行验证 |
60
+ | docs/release-2.0 | 当前发行合同和测试清单 |
61
+
62
+ 当前2.0发行集合为 `scripts/run-v2-release-regression.py`,旧run-release-regression保留历史入口。
63
+ 只通过独立HOME/DSH_HOME/DSH_AGENTS_HOME隔离启动器运行;真实Git集合单独授权,不通配旧测试。
64
+ 模型、IM、远端Git及正式Host操作须在当前授权内,测试通过不等于已发布。
65
+
66
+ ## 六、许可与反馈
67
+
68
+ 自定义源码可见许可,以根LICENSE中文为准,LICENSE.en.md为参考译本。
69
+ 允许免费自用、研究、修改和企业内部使用;未经许可禁止转售或收费对外托管,不是MIT/Apache/OSI许可。
70
+ 第三方/素材声明见bundle中的THIRD_PARTY_NOTICES.md与ASSET_NOTICE.md。
71
+ 本项目是独立插件,不代表DeepSeek官方产品;反馈前移除凭据、用户消息与私有任务数据。
package/bundle/README.md CHANGED
@@ -1,100 +1,71 @@
1
- # 大肥鱼 bot — DSH 本地与云端协作插件
2
-
3
- 面向 [DeepSeek Harness(DSH)](https://github.com/deepseek-ai/deepseek-harness) 的双端协作插件:
4
- 在本地对话或 IM 中自然语言交办,云端独立 worker 持续处理有界任务,
5
- 在统一工作台查看成果并处理需要你决定的事项。
6
-
7
- - npm 包:`@mogragsigma/dsh-dual-host-plugin`(从仓库根目录打包,入口指向 `bundle/`)
8
- - 兼容范围:`>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0`
9
- - 0.33.4 是宿主兼容修复,不是 2.0 功能发布;新验收覆盖 DSH `0.2.2-rc.1`,其他允许版本不等于逐版实测。
10
- 新建独立运行时模板仍固定 `0.2.1-rc.5`,插件兼容范围不改变安装锁定。
11
- - 许可:自定义源码可见许可(以中文文本为准),允许免费自用,禁止转售与收费对外托管
12
- - 本仓库是产品源码仓库;`bundle/` 为 npm 发布内容,`docs/` 为设计与验收文档
13
-
14
- ## 安装
15
-
16
- ```sh
17
- # 插件市场(推荐):在 DSH 插件市场搜索「大肥鱼 bot」一键安装
18
-
19
- # 命令行
20
- dsh plugin --profile web add @mogragsigma/dsh-dual-host-plugin --allow-build=koffi
21
- ```
22
-
23
- 本包含原生依赖 `koffi`,pnpm 11 默认会拦截依赖的构建脚本;插件市场会提示
24
- 「允许构建脚本并重试」,点击即可完成安装。若所用 pnpm 不支持 `--allow-build`,
25
- 可在目标 profile 的 `pnpm-workspace.yaml` 中加入:
26
-
27
- ```yaml
28
- allowBuilds:
29
- koffi: true
30
- ```
31
-
32
- Desktop 与 Web 请选择各自实际使用的 profile;安装后按当前 Host 的方式重载或重启。
33
-
34
- ## 能力
35
-
36
- - **双端协作**:本地与云端各装同一套自有插件,按角色与配对区分职责;不要求两个机器人同群
37
- - **持久任务**:云端独立 worker、有界批次与预算、检查点与重启恢复;入口不持续占用
38
- - **按任务选模型**:以目标端实际模型目录唯一匹配为准,简称可用,歧义先澄清
39
- - **审批账本**:工作台自然语言意见、已绑定 IM 身份核验、持久账本与权威回执;
40
- 批准、排队、执行、测试、交付、已读分别证明
41
- - **成果与文件**:任务成果查询、受控下载与原执行会话入口;IM/本地/云端三端文件收发
42
- - **六页配置导览**:连接云端、模型服务、聊天与审批、成果与仓库、安装计划、配置与验收
43
- - **全局规则与 Skill**:保护性追加或确认后创建,不覆盖用户原规则;冲突先阻止
44
-
45
- ## 兼容与边界
46
-
47
- - 仅声明已测试的官方 DSH `0.2.1-rc.5`;其他 Host 版本或平台需自行验证
48
- - 不修改 DSH 内核或第三方 IM 插件源码
49
- - 不承诺操作系统级隔离、全平台支持或全部 IM 渠道能力一致
50
- - 开发、commit、push、发布分别授权;源码可见不等于授权分发、部署或商业托管
51
- - 本项目是独立插件,不代表 DeepSeek 或依赖作者的官方产品
52
-
53
- ## 仓库结构
54
-
55
- | 路径 | 内容 |
1
+ # 大肥鱼 bot 2.0 — DSH 本地与云端协作插件
2
+
3
+ > 2.0.1发行说明。功能范围、验收范围、包已发布与实例已加载是不同事实。
4
+ > 本文不记录维护者的私有部署状态;安装或更新前核对包来源、版本、兼容性和目标Host。
5
+
6
+ ## 一、能力与入口
7
+
8
+ - 本地DSH、本人本地IM机器人、本人云端IM机器人三条交办通道,统一云端DSH原生执行。
9
+ - 单用户;IM所有者/白名单沿dsh-im,任务权限与IM权限独立,不新增个人凭证系统。
10
+ - 本地保留普通DSH分析、文件与对话;不接受反向受管worker,云端忙时不回退本地执行。
11
+ - 目标与必要原文归入任务,换会话仍可查询跟进;用户看整体进展与成果,不管理内部子代理。
12
+ - 云端充分使用运行账号下的原生工具,任务明确限制仍有效;无模型次数门禁、统一300秒截止或两小时重批。
13
+ - 3个普通执行名额+最多1个已有任务人工跟进名额;等待不提前创建执行Session。
14
+ - 复用原生后台作业、子代理、长任务、定时/依赖及命名信号;空闲7天归档、后续自动恢复,不自动删除Session。
15
+ - 成果快照、相关检查、约定交付分开呈现;就绪不等于推送、发布或用户已读。
16
+ - 选定Gitee Issue的observe/auto及本人确认的本期新任务总数,默认关闭,不订阅全仓/评论/Webhook。
17
+ - 蓝白叠页工作台用于查询及指示草稿;草稿需通过对话提交,不构成第四条创建入口。
18
+ - 云端大肥鱼页可直接“打开云端工作台”,进入原生DSH;与“查看云端任务”分开,沿用既有连接。
19
+
20
+ ## 二、安装与启用
21
+
22
+ - npm包名 `@mogragsigma/dsh-dual-host-plugin`,仅从仓库根打包;不另发内层bundle子包。
23
+ - [公开安装步骤](SETUP.md),[原生profile配置模板](configuration-v2.json)。
24
+ - 宽兼容范围:`>=0.2.1-rc.5 <0.2.2-0 || >=0.2.2-rc.1 <0.2.3-0`;范围准入不等于逐版实测。
25
+ - 独立新建Host模板锁定DSH `0.2.2-rc.1`,不修改现有Host依赖;可选dsh-im锁定5.9.1。
26
+ - 真实IM证据限选定飞书本人目标,不承诺全平台。koffi构建按原生安装器确认,不私装旧版DSH组件。
27
+ - 规则/Skill安装先预览冲突,不能覆盖用户内容;六页旧只读运行时不是2.0云执行的启用证明。
28
+
29
+ ## 三、范围与迁移
30
+
31
+ 新任务不再走旧IM审批或短批次预算链;历史账本和兼容模块保留,不能据此恢复旧政策。
32
+ 云端自主不代表本地目录自动上传、资料里的指令获得授权,或服务商额度无限。
33
+ Git以执行端仓库为基础,远端可选。Gitee commit/push已有选定真实证据;产品PR合同仍仅GitHub,
34
+ 维护API创建的Gitee PR不是产品原生PR验收。
35
+
36
+ 跨端文件和IM发送需要实际接线与明确目标。新文件配置默认32MiB,可调整至技术上限512MiB,
37
+ 旧配置不自动扩大;不是无限大小或512MiB性能保证,IM仍受平台与网络限制。
38
+ 升级须先排空、校验备份;保留旧记录和新数据,恢复使用经验证的2.0快照及独立目录,
39
+ 不承诺0.33.4旧二进制可读新库。新建安装、已有实例升级和实际加载分别验收。
40
+ 已有实例沿用IM/ECS配置,不用新建模板覆盖原profile;旧任务完整迁移、定时任务无感接续不作本版承诺。
41
+ 不提供完整外置Watchdog、弹性云/任意BYOM或独立持续自动上线平台;不改DSH/第三方源码。
42
+ 任务执行与产品仓库发布是不同授权;安装候选不授权正式部署或产品推送。
43
+
44
+ ## 四、首发验收边界
45
+
46
+ 选定三通道、自然跟进、原生执行、来源策略、文件与UI已有分层证据;并非全平台或任意任务零错误。
47
+ 主验DSH 0.2.2-rc.1和下界0.2.1-rc.5均有隔离实装;选定真实IM为飞书,其他平台未作同等承诺。
48
+ 双端及云端IM大于2MiB文件已验证,本地IM大ZIP在网络超时下按批准边界简化,不能宣称已成功。
49
+ 会话归档为受控七天时钟验证;稳定性为双端超过两小时观察、首批满槽至少15分钟,非持续满载或SLA。
50
+ 窄窗适配以部分可视高度换取无遮挡;不承诺FPS或任意第三方挂件兼容。
51
+
52
+ ## 五、开发与验收
53
+
54
+ | 目录 | 用途 |
56
55
  | --- | --- |
57
- | `bundle/` | 插件包(npm 发布内容):自有插件、全局规则模板、Skill、工作台客户端、许可与声明 |
58
- | `ui-source/` | 工作台与六页导览前端源码(`bundle/` 内为生成结果,不手改) |
59
- | `scripts/` | 构建、模块索引与定向回归脚本 |
60
- | `tests/` | 合成测试;测试依赖声明见 `tests/package.json` |
61
- | `docs/` | 设计、兼容、验收与发行文档 |
62
- | `operator-kit/` | 历史运营工具,仅供维护参考 |
63
-
64
- ## 开发与测试
65
-
66
- - 需要 Node.js 22 与 Python 3;测试使用隔离 HOME/DSH_HOME 与合成服务,不读取日常配置
67
- - 定向回归(先阅读脚本清单,不通配执行历史测试):
68
-
69
- ```sh
70
- python3 scripts/run-release-regression.py
71
- ```
72
-
73
- ## 许可
74
-
75
- 本仓库根目录 `LICENSE` 为中文控制文本,`LICENSE.en.md` 为英文参考译本;
76
- npm 包内 `bundle/LICENSE` 与内容相同。核心规则:
77
-
78
- - 允许任何个人和组织免费安装、运行、研究、备份和修改,含企业内部使用与内部商业工作
79
- - 未经另行书面授权,禁止转售、出租、商业再许可、收费再分发和收费对外托管
80
- - 允许免费再分发;须附许可与声明、标明修改、不得冒充官方版本
81
- - 不是 MIT/Apache-2.0/OSI 开源许可;许可以中文文本为准
82
-
83
- 第三方组件声明见 `bundle/THIRD_PARTY_NOTICES.md`,素材声明见 `bundle/ASSET_NOTICE.md`。
84
-
85
- ## 反馈
86
-
87
- - Issues:本仓库
88
- - Gitee 镜像:<https://gitee.com/wenheng-Zhang/dsh-dual-host-plugin>(可见性以该站实际状态为准)
89
-
90
- 提交问题前请移除 API Key、机器人密钥、用户消息、内网地址、签名及私有任务数据。
91
-
92
- ## 0.33.3 验收合同
93
-
94
- - 根包是唯一 npm 发布入口;内层 `bundle/package.json` 供受控云端归档使用,不单独发布。
95
- - DSH 组件使用 Host 提供的可选 peer 依赖:可选仅表示包管理器不自动安装,功能仍要求 Host 服务。
96
- 本版明确要求 rc.5,不在插件下安装旧版 DSH 组件,也不修改 Host 的依赖。
97
- - `runtime-*-package/lock.json` 是另行确认后创建独立 Host 的固定依赖,不用于修改现有 Host。
98
- - `run-release-regression.py` 是当前具名验收入口;旧 `release-promotion`/`market-v0331`
99
- 与冻结清单保留作历史,不参与当前版本验收。当前合同保留身份、锁冲突、符号链接等拒绝测试。
100
- - UI 从 `ui-source/` 构建,模块索引从源码生成;GitHub 镜像不再另行修改发行代码。
56
+ | bundle | 插件、规则、Skill、安装模板与生成UI |
57
+ | ui-source | UI源码,修改后重新生成 |
58
+ | tests | 当前及历史具名测试 |
59
+ | scripts | 构建、索引、发行验证 |
60
+ | docs/release-2.0 | 当前发行合同和测试清单 |
61
+
62
+ 当前2.0发行集合为 `scripts/run-v2-release-regression.py`,旧run-release-regression保留历史入口。
63
+ 只通过独立HOME/DSH_HOME/DSH_AGENTS_HOME隔离启动器运行;真实Git集合单独授权,不通配旧测试。
64
+ 模型、IM、远端Git及正式Host操作须在当前授权内,测试通过不等于已发布。
65
+
66
+ ## 六、许可与反馈
67
+
68
+ 自定义源码可见许可,以根LICENSE中文为准,LICENSE.en.md为参考译本。
69
+ 允许免费自用、研究、修改和企业内部使用;未经许可禁止转售或收费对外托管,不是MIT/Apache/OSI许可。
70
+ 第三方/素材声明见bundle中的THIRD_PARTY_NOTICES.md与ASSET_NOTICE.md。
71
+ 本项目是独立插件,不代表DeepSeek官方产品;反馈前移除凭据、用户消息与私有任务数据。
@@ -0,0 +1,87 @@
1
+ ---
2
+ title: "大肥鱼2.0安装与启用"
3
+ description: "根发布包的原生DSH配置路径和首发边界"
4
+ author: "大肥鱼 bot 项目组"
5
+ created: "2026-11-08"
6
+ updated: "2026-11-10"
7
+ version: "2.0.1"
8
+ status: "draft"
9
+ ---
10
+
11
+ # 大肥鱼2.0安装与启用
12
+
13
+ > 2.0.1安装说明;发布状态以包来源回读为准。以下是公开的原生DSH配置路径,不是灰度脚本。
14
+ > 包安装、配置保存、运行时已加载、双端已连接和任务已执行是不同状态。
15
+
16
+ ## 一、安装与版本
17
+
18
+ 只从仓库根包安装。准备期使用已核验的tgz;发布后才能改用registry版本。
19
+ 在每端自己的DSH、HOME、DSH_HOME、DSH_AGENTS_HOME和实际profile中执行:
20
+
21
+ ```sh
22
+ dsh plugin --profile YOUR_PROFILE add /absolute/path/dsh-dual-host-plugin-2.0.1.tgz --allow-build=koffi
23
+ ```
24
+
25
+ 不要使用正式profile做测试;不要向插件内另装DSH依赖。独立新建Host模板锁定0.2.2-rc.1,
26
+ 不代表安装插件会升级现有Host。宽兼容准入与逐版本实测不同,见根兼容声明及验收记录。
27
+
28
+ ## 二、使用原生profile配置启用
29
+
30
+ 随包的 `configuration-v2.json` 提供local/cloud两份原生Cordis patch数组。
31
+ 在实际profile的 `cordis.patch.yml` 中合并对应数组,不覆盖其他插件设置,不重复插入同名工具插件。
32
+ 可用JSON格式保存该YAML文件;保留安装器生成的插件包选择及默认bundle。
33
+
34
+ 1. 两端选定同一个PAIR_NAMESPACE。STATE_DIR分别指向各自独立、私有的状态目录,不能交换或共用数据库。
35
+ 2. TOKEN_FILE是本端私有JSON文件路径,权限0600。云端包含不同的localToken、cloudToken;
36
+ 本地只需同一个localToken。每个令牌至少32个随机字符,不把令牌放进聊天、任务或配置示例仓库。
37
+ 3. 三个示例端口需确认空闲。云端Gateway仅监听127.0.0.1;跨机器用既有SSH转发将本地coordinatorUrl
38
+ 指向云端回环端口,校验SSH主机身份,不改成公网明文监听。转发端口不与本地Gateway端口相同。
39
+ 4. 云端endpoint只在云端配置。本地不配置执行endpoint,不回退为本地worker。
40
+ 5. 如需指定任务默认模型,在managedExecution中增加defaultModel对象,provider/model取云端实际目录。
41
+ 不填写则沿现有DSH默认;显式任务模型可覆盖。API Key仍由DSH原生凭据配置,不填进模板。
42
+ 6. 安装自有规则/Skill沿既有安装入口进行,预览冲突后确认,不能覆盖用户规则。
43
+ 普通聊天保留DSH原生能力,新任务才进入云端受管执行。
44
+ 7. 按当前Host方式正常重载。通过工作台和 `task_identity`、`task_source_capabilities`、`task_results`
45
+ 观察实际服务;在本地对话交办小任务,核实云端进度与成果,不能只凭配置文件存在宣称启用。
46
+
47
+ 无IM也可完成本地DSH交办。可选IM由dsh-im原生配置所有者/白名单和机器人连接;
48
+ 本地机器人进本地DSH、云端机器人进云端DSH,不新增消费者,不从IM权限推导任务权限。
49
+ 旧向导的“只读任务Worker”属于历史受管运行时,不是2.0充分自主云执行;不要同时启动两套Gateway。
50
+ 切换已有实例涉及存量迁移,须按单独升级方案进行,不能直接套用新建配置或另建空库冒充迁移成功。
51
+
52
+ ## 三、来源与主动策略
53
+
54
+ 默认sourceIntake关闭、sources为空;任务运行不以来源接入为前提。
55
+ 如需接入,复制selectedSource示例到云端sources,替换真实仓库URL、不可变repository_id、
56
+ 匹配namespace、scope及最多8个Issue编号。示例42和IEXAMPLE不是可运行的测试凭据。
57
+
58
+ 私有仓库读取沿既有repositoryAccess凭据引用。先按DSH配置启用该来源并核对状态,再通过
59
+ `task_source_status` / `task_source_control` 选择observe;auto必须由本人明确策略、范围、交付和本期新任务总数。
60
+ 不能在安装配置中默认为auto,不代填总数,不重置旧周期。来源作者和内容不是用户授权。
61
+ 接入/来源启停、处理模式、策略撤销分别操作;停止接收不意味着撤销全部既有任务。
62
+ 此路径不订阅全仓、评论或Webhook;缺配置或读取失败应显示未连接/具体错误,不冒称在线。
63
+
64
+ ## 四、文件与故障边界
65
+
66
+ 成果登记、不可变快照及任务内查询由云执行提供;发送还需明确目标与实际通道能力。
67
+ 跨端文件队列和IM发送并非只靠上述基础模板即全部启用,应在现有文件/IM配置中显式配置并验收。
68
+ 本地路径不会自动上传,缺资料需明确提供;不能开放整个电脑或默认复制目录来消除报错。
69
+ 跨端文件新配置默认32MiB,可显式配置,现有缓冲实现技术上限512MiB;这不是该大小性能保证。
70
+ 旧隐式/显式配置不自动扩大。已收妥文件按原大小核验,不因新传输上限降低而误报旧成果不可用。
71
+ 双端与云IM的3,420,279字节ZIP已按哈希核验;本地IM大ZIP受网络超时影响,首发按批准边界简化,
72
+ 已有小文件通过不代表本地大文件通过。平台拒收、缺失和未知发送回执必须如实呈现,不能重复派工代替补交付。
73
+
74
+ 身份/令牌不匹配、缺模型服务、来源未配置、SSH未连接应先修复对应配置,不清库、反复建任务或扩大权限。
75
+ 会话归档由系统处理,不自动删除;停止的任务不因重新连接或升级恢复。
76
+ 任务调用次数不设插件上限,也没有统一300秒或两小时许可重批;服务商限流、技术超时仍然存在。
77
+
78
+ ## 五、已有实例升级与恢复
79
+
80
+ 不要将新建示例直接覆盖已有profile、令牌、规则或状态目录。先检查活动任务并排空Host,
81
+ 确认目录无占用后备份实际配置、数据、规则/Skill及插件版本,校验备份后再走原生安装器。
82
+ 旧任务、失败、成果、原发送回执保留;升级不复活停止任务、不静默开启来源auto、不重置期总数。
83
+
84
+ 2.0前向升级及独立快照恢复已有隔离验证。不要求兼容回退0.33.4二进制,不保证其可读取含2.0新任务的库。
85
+ 需要恢复时使用经验证的2.0版本与经校验快照,先在独立目录核对,保全当前新数据;
86
+ 不能用旧库覆盖新写入,也不能把恢复数据库当作撤销已经发生的外部发送或Git操作。
87
+ 管理员绕过维护流程手动降级不在安全保证范围内;正式部署、恢复或数据删除须当次明确授权。
@@ -36,7 +36,7 @@ export class ChatApproval {
36
36
  this.receipts=new ApprovalReceipts(ledger,this.bindingHash,this.source,this.binding);this.guidance=new ApprovalGuidance(this);
37
37
  }
38
38
  queue(request_id){
39
- const v=this.l.view(request_id);if(v.state!=='pending')return this.delivery(request_id);
39
+ const v=this.l.view(request_id);if(this.l.isNativeTask(v.task_id))throw Error('APPROVAL_AUTHORITY_PATH_MISMATCH');if(v.state!=='pending')return this.delivery(request_id);
40
40
  const text=renderChatProposal(v,this.semantic);if(Buffer.byteLength(text)>18000)throw Error('CHAT_PROPOSAL_TOO_LARGE');
41
41
  this.db.prepare("INSERT OR IGNORE INTO approval_chat_deliveries(request_id,binding_hash,uuid,text,state,created,semantics_version) VALUES (?,?,?,?,'queued',?,?)").run(request_id,this.bindingHash,randomUUID(),text,this.now(),this.semantic?SEMANTICS_VERSION:0);return this.delivery(request_id);
42
42
  }
@@ -46,7 +46,7 @@ export class ChatApproval {
46
46
  async cycle(){
47
47
  if(this.nextPoll>this.now())return;this.nextPoll=this.now()+this.interval;
48
48
  try{
49
- const rows=this.db.prepare("SELECT * FROM approval_chat_deliveries WHERE binding_hash=? AND state IN ('queued','sent') ORDER BY created LIMIT 100").all(this.bindingHash).filter(r=>{if(['pending','approved','held','clarification_required','confirmation_review'].includes(this.l.view(r.request_id).state))return true;this.db.prepare("UPDATE approval_chat_deliveries SET state='inactive' WHERE request_id=?").run(r.request_id);return false;});
49
+ const rows=this.db.prepare("SELECT * FROM approval_chat_deliveries WHERE binding_hash=? AND state IN ('queued','sent') ORDER BY created LIMIT 100").all(this.bindingHash).filter(r=>{if(this.l.isNativeTask(this.l.row(r.request_id).task_id)){this.db.prepare("UPDATE approval_chat_deliveries SET state='inactive' WHERE request_id=?").run(r.request_id);return false;}if(['pending','approved','held','clarification_required','confirmation_review'].includes(this.l.view(r.request_id).state))return true;this.db.prepare("UPDATE approval_chat_deliveries SET state='inactive' WHERE request_id=?").run(r.request_id);return false;});
50
50
  if(!rows.length){this.lastError=null;return;}
51
51
  await this.source.verifyBot();if(this.closed)return;
52
52
  for(const r of rows.filter(r=>r.state==='queued').slice(0,5)){
@@ -9,9 +9,12 @@ export class FeishuApprovalSource {
9
9
  constructor(c,{fetcher=fetch,env=process.env,now=Date.now,credentials}={}){this.binding=chatBinding(c);this.secretRef=c.appSecretEnv;this.credentials=credentials;this.secret=credentials?null:env[c.appSecretEnv];if(!credentials&&(typeof this.secret!=='string'||!this.secret))throw Error('CHAT_APPROVAL_CREDENTIAL_UNAVAILABLE');this.fetcher=fetcher;this.now=now;this.token=null;}
10
10
  async http(path,options={}){
11
11
  let r;try{r=await this.fetcher(BASE+path,{...options,redirect:'error',signal:AbortSignal.timeout(10000)});}catch{throw Error('CHAT_PROVIDER_UNAVAILABLE');}
12
- if(!r.ok)throw Error('CHAT_PROVIDER_HTTP_'+r.status);
12
+ // Permission errors can use HTTP 400/403 as well as 200; inspect only bounded JSON.
13
+ if(!r.ok&&![400,403].includes(r.status))throw Error('CHAT_PROVIDER_HTTP_'+r.status);
13
14
  let raw='',bytes=0;const decoder=new TextDecoder();for await(const part of r.body){bytes+=part.byteLength;raw+=decoder.decode(part,{stream:true});if(bytes>1048576)throw Error('CHAT_PROVIDER_RESPONSE_TOO_LARGE');}
14
15
  raw+=decoder.decode();let d;try{d=JSON.parse(raw);}catch{throw Error('CHAT_PROVIDER_INVALID_RESPONSE');}
16
+ if(d.code===99991672)throw Error('CHAT_PROVIDER_CODE_99991672');
17
+ if(!r.ok)throw Error('CHAT_PROVIDER_HTTP_'+r.status);
15
18
  if(d.code!==0)throw Error('CHAT_PROVIDER_CODE_'+String(Number(d.code)));
16
19
  return d;
17
20
  }
@@ -1,3 +1,4 @@
1
+ import {executionSpec} from './managed-execution.js';
1
2
  import {developmentSpec} from './development-contract.js';
2
3
  import {deliveryReference,deliveryExecution} from './delivery-contract.js';
3
4
  import {createHash,randomUUID} from 'node:crypto';
@@ -8,11 +9,12 @@ export const digest=v=>createHash('sha256').update(typeof v==='string'?v:canonic
8
9
  export const scopes=['develop','commit','push','publish','deploy'];
9
10
  const text=(x,n)=>{if(typeof x!=='string'||!x.trim()||x.length>n)throw Error('INVALID_APPROVAL_PROPOSAL');return x;};
10
11
  function proposal(p){
11
- if(!p||Object.keys(p).some(k=>!['summary','changes','validation','risks','rollback','model_policy','scopes','repository','development','delivery'].includes(k)))throw Error('INVALID_APPROVAL_PROPOSAL');
12
+ if(!p||Object.keys(p).some(k=>!['summary','changes','validation','risks','rollback','model_policy','scopes','repository','development','delivery','execution'].includes(k)))throw Error('INVALID_APPROVAL_PROPOSAL');
12
13
  if(!Array.isArray(p.scopes)||!p.scopes.length||p.scopes.length>5||new Set(p.scopes).size!==p.scopes.length||p.scopes.some(x=>!scopes.includes(x)))throw Error('INVALID_APPROVAL_SCOPE');
13
14
  // Only an opaque reference is accepted. The coordinator supplies the immutable provider-verified snapshot.
14
15
  if(p.repository!==null&&(!p.repository||typeof p.repository!=='object'||Array.isArray(p.repository)||Object.keys(p.repository).some(k=>!['binding_revision','fingerprint'].includes(k))||!Number.isInteger(p.repository.binding_revision)||p.repository.binding_revision<1||!(/^[a-f0-9]{64}$/).test(p.repository.fingerprint??'')))throw Error('REPOSITORY_BINDING_NOT_IMPLEMENTED');
15
16
  const result={summary:text(p.summary,1000),changes:text(p.changes,4000),validation:text(p.validation,2000),risks:text(p.risks,2000),rollback:text(p.rollback,2000),model_policy:validPolicy(p.model_policy),scopes:[...p.scopes].sort(),repository:p.repository};
17
+ if(p.execution!==undefined){if(p.development!==undefined||p.delivery!==undefined||p.scopes.join()!=='develop'||p.repository!==null)throw Error('TASK_EXECUTION_SCOPE_INVALID');result.execution=executionSpec(p.execution);}
16
18
  if(p.development!==undefined){if(p.scopes.length!==1||p.scopes[0]!=='develop'||p.repository===null)throw Error('DEVELOPMENT_SCOPE_DENIED');result.development=developmentSpec(p.development);}
17
19
  if(p.delivery!==undefined){if(p.development!==undefined||p.scopes.length!==1||p.scopes[0]!==(p.delivery?.kind==='push-execution-reference-v1'?'push':'commit')||p.repository===null)throw Error('DELIVERY_SCOPE_DENIED');result.delivery=deliveryReference(p.delivery);}
18
20
  if(canonical(result).length>12000)throw Error('APPROVAL_PROPOSAL_TOO_LARGE');return result;
@@ -37,17 +39,19 @@ export class ApprovalLedger {
37
39
  request(node,a){
38
40
  if(!['local','cloud'].includes(node)||!a||!Number.isInteger(a.expected_revision)||a.expected_revision<1)throw Error('INVALID_APPROVAL_REQUEST');
39
41
  const event=text(a.event_id,256),task_id=text(a.task_id,128),p=proposal(a.proposal),ttl=a.ttl_seconds??3600;
40
- if(!Number.isInteger(ttl)||ttl<60||ttl>86400)throw Error('INVALID_APPROVAL_EXPIRY');
42
+ if(!Number.isInteger(ttl)||ttl<60||ttl>(p.execution?366*86400:86400))throw Error('INVALID_APPROVAL_EXPIRY');
41
43
  const fp=digest({node,task_id,revision:a.expected_revision,proposal:p,ttl});
42
44
  return this.store.tx(()=>{
43
45
  const old=this.db.prepare('SELECT id,fingerprint FROM approval_requests WHERE event_id=?').get(event);
44
46
  if(old){if(old.fingerprint!==fp)throw Error('APPROVAL_EVENT_CONFLICT');return this.view(old.id);}
47
+ if(p.execution&&!this.isNativeTask(task_id))throw Error('TASK_EXECUTION_NATIVE_TASK_REQUIRED');
45
48
  const h=this.catalog.head(task_id);if(h.legacy)throw Error('LEGACY_TASK_READONLY');if(h.spec_revision!==a.expected_revision)throw Error('TASK_REVISION_CONFLICT');
46
49
  if(this.store.task(task_id).blocked)throw Error('TASK_BLOCKED');
47
50
  if(!this.repositories&&p.repository!==null)throw Error('REPOSITORY_BINDING_NOT_IMPLEMENTED');
48
- const binding=this.repositories?.approvalSnapshot(task_id,p.repository)??{revision:0,snapshot:null},resolved={...p,model_policy:newCloudPolicy(p.model_policy),repository:binding.snapshot};
51
+ const binding=this.repositories?.approvalSnapshot(task_id,p.repository)??{revision:0,snapshot:null},resolved={...p,model_policy:p.execution?p.model_policy:newCloudPolicy(p.model_policy),repository:binding.snapshot};
49
52
  if(p.delivery){if(!this.delivery)throw Error('DELIVERY_CAPABILITY_REQUIRED');resolved.delivery=this.delivery.approvalSnapshot(task_id,h.spec_revision,p.delivery,binding.snapshot,resolved.model_policy);}
50
53
  if(canonical(resolved).length>16000)throw Error('APPROVAL_PROPOSAL_TOO_LARGE');
54
+ if(this.isNativeTask(task_id)&&this.db.prepare("SELECT proposal FROM approval_requests WHERE task_id=? AND spec_revision=? AND state IN ('rejected','revoked')").all(task_id,h.spec_revision).some(x=>canonical(JSON.parse(x.proposal))===canonical(resolved)))throw Error('APPROVAL_AMENDMENT_REQUIRED');
51
55
  const previous=this.db.prepare("SELECT proposal FROM approval_requests WHERE task_id=? AND state='changes_requested' ORDER BY created DESC,rowid DESC LIMIT 1").get(task_id);
52
56
  if(previous&&canonical(JSON.parse(previous.proposal))===canonical(resolved))throw Error('APPROVAL_AMENDMENT_REQUIRED');
53
57
  const id='approval-'+randomUUID(),created=this.now(),expires=created+ttl*1000,task_hash=digest(h.goal),proposal_hash=digest({task_id,spec_revision:h.spec_revision,task_hash,proposal:resolved,repository_revision:binding.revision,expires});
@@ -56,6 +60,7 @@ export class ApprovalLedger {
56
60
  return this.view(id);
57
61
  });
58
62
  }
63
+ isNativeTask(id){return parseEnvelope(this.catalog.head(id).goal)?.continuity===1;}
59
64
  row(id){const r=this.db.prepare('SELECT * FROM approval_requests WHERE id=?').get(id);if(!r)throw Error('APPROVAL_NOT_FOUND');return r;}
60
65
  validity(r,{ignoreInterpretation=false,ignoreTaskBlock=false}={}){
61
66
  const h=this.catalog.head(r.task_id);if(r.state==='superseded'||r.state==='revoked'||r.state==='rejected'||r.state==='repository_changed'||r.state==='changes_requested')return r.state;
@@ -65,10 +70,15 @@ export class ApprovalLedger {
65
70
  if(this.repositories&&!this.repositories.matches(r.task_id,r.repository_revision,JSON.parse(r.proposal).repository))return 'repository_changed';
66
71
  const proposal=JSON.parse(r.proposal),delivery=proposal.delivery;
67
72
  if(delivery&&(canonical(proposal.scopes)!==canonical([delivery.kind==='push-execution-v1'?'push':'commit'])||digest({task_id:r.task_id,spec_revision:r.spec_revision,task_hash:r.task_hash,proposal,repository_revision:r.repository_revision,expires:r.expires})!==r.proposal_hash||!this.delivery?.matches(r.task_id,r.spec_revision,delivery)))return 'delivery_changed';
68
- if(!ignoreInterpretation&&this.db.prepare("SELECT 1 FROM approval_chat_evaluations WHERE request_id=? AND state IN ('processing','needs_review')").get(r.id))return 'confirmation_review';
73
+ if(this.isNativeTask(r.task_id)&&this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='task_continuity'").get()&&this.db.prepare('SELECT state FROM task_continuity WHERE task_id=?').get(r.task_id)?.state==='stopped')return 'task_stopped';
74
+ if(!this.isNativeTask(r.task_id)&&!ignoreInterpretation&&this.db.prepare("SELECT 1 FROM approval_chat_evaluations WHERE request_id=? AND state IN ('processing','needs_review')").get(r.id))return 'confirmation_review';
69
75
  if(r.state==='approved'){
70
76
  const d=this.db.prepare('SELECT credential_id,challenge_id,proof_hash FROM approval_decisions WHERE id=?').get(r.decision_id);
71
- if(d?.credential_id?.startsWith('workbench-im:')){
77
+ if(this.isNativeTask(r.task_id)){
78
+ const exists=this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='task_native_decisions'").get();
79
+ const p=exists&&this.db.prepare('SELECT * FROM task_native_decisions WHERE event_id=?').get(d?.challenge_id??'');
80
+ if(d?.credential_id!=='task-native'||!p||p.request_id!==r.id||p.action!=='approve'||p.fingerprint!==d.proof_hash||digest(JSON.parse(p.record))!==p.fingerprint)return 'task_decision_invalid';
81
+ }else if(d?.credential_id?.startsWith('workbench-im:')){
72
82
  const exists=this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='workbench_im_proofs'").get();
73
83
  const p=exists&&this.db.prepare('SELECT binding_hash,proof_hash FROM workbench_im_proofs WHERE challenge_id=?').get(d.challenge_id),b=p&&this.db.prepare('SELECT disabled FROM approval_chat_bindings WHERE id=?').get(p.binding_hash);
74
84
  if(!b||b.disabled||d.credential_id!=='workbench-im:'+p.binding_hash||d.proof_hash!==p.proof_hash)return 'identity_revoked';
@@ -79,9 +89,10 @@ export class ApprovalLedger {
79
89
  }
80
90
  return r.state;
81
91
  }
82
- view(id){const r=this.row(id),revision=this.db.prepare('SELECT title,goal FROM task_revisions WHERE task_id=? AND revision=?').get(r.task_id,r.spec_revision),e=parseEnvelope(revision.goal);return {id:r.id,task_id:r.task_id,title:revision.title,spec_revision:r.spec_revision,task_sources:e?.task_context?.sources?.map(s=>s.text)??[e?.request_text??revision.goal],proposal:JSON.parse(r.proposal),proposal_hash:r.proposal_hash,recorded_state:r.state,state:this.validity(r),created:r.created,expires:r.expires,decision:r.decision_id?this.db.prepare('SELECT action,principal,created FROM approval_decisions WHERE id=?').get(r.decision_id):null,execution:deliveryExecution(JSON.parse(r.proposal))?'requires_delivery_runtime_gate':JSON.parse(r.proposal).development?'requires_runtime_gate':'disabled',repository_revision:r.repository_revision,repository_binding:JSON.parse(r.proposal).repository?'metadata_only':'unbound',feedback:this.feedback(r.id)};}
92
+ view(id){const r=this.row(id),revision=this.db.prepare('SELECT title,goal FROM task_revisions WHERE task_id=? AND revision=?').get(r.task_id,r.spec_revision),e=parseEnvelope(revision.goal);return {id:r.id,task_id:r.task_id,...(this.isNativeTask(r.task_id)?{authorization_mode:'task-native',decision_id:r.decision_id}:{}),title:revision.title,spec_revision:r.spec_revision,task_sources:e?.task_context?.sources?.map(s=>s.text)??[e?.request_text??revision.goal],proposal:JSON.parse(r.proposal),proposal_hash:r.proposal_hash,recorded_state:r.state,state:this.validity(r),created:r.created,expires:r.expires,decision:r.decision_id?this.db.prepare('SELECT action,principal,created FROM approval_decisions WHERE id=?').get(r.decision_id):null,execution:JSON.parse(r.proposal).execution?'requires_managed_execution_gate':deliveryExecution(JSON.parse(r.proposal))?'requires_delivery_runtime_gate':JSON.parse(r.proposal).development?'requires_runtime_gate':'disabled',repository_revision:r.repository_revision,repository_binding:JSON.parse(r.proposal).repository?'metadata_only':'unbound',feedback:this.feedback(r.id)};}
83
93
  list(task_id){if(task_id)this.store.task(task_id);return this.db.prepare('SELECT id FROM approval_requests '+(task_id?'WHERE task_id=? ':'')+'ORDER BY created DESC,id LIMIT 100').all(...(task_id?[task_id]:[])).map(x=>this.view(x.id));}
84
94
  feedback(id){
95
+ const r=this.row(id);if(this.isNativeTask(r.task_id)){const exists=this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='task_native_decisions'").get(),p=exists&&this.db.prepare('SELECT record FROM task_native_decisions WHERE decision_id=?').get(r.decision_id);if(!p)return null;const v=JSON.parse(p.record);return {...v.interpretation,user_text:v.source.text,requires_new_proposal:v.action==='amend',source:'task-native'};}
85
96
  const current=this.db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name='workbench_im_proofs'").get()?this.db.prepare('SELECT p.* FROM workbench_im_proofs p JOIN approval_decisions d ON d.challenge_id=p.challenge_id JOIN approval_requests r ON r.decision_id=d.id WHERE r.id=?').get(id):null;
86
97
  if(current){const i=JSON.parse(current.interpretation);return {...i,user_text:current.user_text,requires_new_proposal:i.intent==='amend',source:'workbench-existing-im'};}
87
98
  const row=this.db.prepare("SELECT p.proof FROM approval_chat_proofs p JOIN approval_decisions d ON d.challenge_id=p.challenge_id WHERE d.request_id=? ORDER BY d.created DESC,d.rowid DESC LIMIT 1").get(id);
@@ -89,17 +100,23 @@ export class ApprovalLedger {
89
100
  if(web){const i=JSON.parse(web.interpretation);return {...i,user_text:web.user_text,requires_new_proposal:i.intent==='amend',source:'workbench'};}
90
101
  if(!row)return null;const p=JSON.parse(row.proof);return p.interpretation?{...p.interpretation,user_text:p.text,requires_new_proposal:p.interpretation.intent==='amend'}:null;
91
102
  }
92
- assertDecision(id,action,{chatEvaluation=false,workbenchEvaluation=false}={}){
93
- const r=this.row(id);if(!['approve','reject','revoke',...((chatEvaluation||workbenchEvaluation)?['hold','amend','clarify']:[])].includes(action))throw Error('INVALID_APPROVAL_ACTION');
103
+ assertDecision(id,action,{chatEvaluation=false,workbenchEvaluation=false,nativeTask=false}={}){
104
+ const r=this.row(id);if(this.isNativeTask(r.task_id)!==nativeTask)throw Error('APPROVAL_AUTHORITY_PATH_MISMATCH');if(!['approve','reject','revoke',...((chatEvaluation||workbenchEvaluation||nativeTask)?['hold','amend','clarify']:[])].includes(action))throw Error('INVALID_APPROVAL_ACTION');
94
105
  const state=this.validity(r,{ignoreInterpretation:chatEvaluation});
95
- if(chatEvaluation||workbenchEvaluation){if(!['pending','approved','held','clarification_required'].includes(state))throw Error('APPROVAL_NOT_PENDING');}
106
+ if(nativeTask&&action==='revoke'&&state!=='approved')throw Error('APPROVAL_NOT_APPROVED');
107
+ if(chatEvaluation||workbenchEvaluation||nativeTask){if(!['pending','approved','held','clarification_required'].includes(state))throw Error('APPROVAL_NOT_PENDING');}
96
108
  else if(action==='revoke'){if(r.state!=='approved')throw Error('APPROVAL_NOT_APPROVED');}
97
109
  else if(!['pending','held','clarification_required'].includes(state))throw Error('APPROVAL_NOT_PENDING');
98
110
  return r;
99
111
  }
100
- // Internal ledger operation. Runtime authority currently comes from a verified IM reply.
112
+ // Internal ledger operation. Legacy IM proofs and native task decisions are disjoint contracts.
101
113
  // Historical credential/proof rows remain readable; retired ceremonies have no live entry.
102
- recordDecision({request_id,action,principal,credential_id,proof_hash,challenge_id,chatEvaluation=false,workbenchEvaluation=false,workbenchIM=false}){
114
+ recordDecision({request_id,action,principal,credential_id,proof_hash,challenge_id,chatEvaluation=false,workbenchEvaluation=false,workbenchIM=false,nativeTask=false}){
115
+ if(this.isNativeTask(this.row(request_id).task_id)!==nativeTask)throw Error('APPROVAL_AUTHORITY_PATH_MISMATCH');
116
+ if(nativeTask){
117
+ const exists=this.db.prepare("SELECT 1 FROM sqlite_master WHERE name='task_native_decisions'").get(),p=exists&&this.db.prepare('SELECT * FROM task_native_decisions WHERE event_id=?').get(challenge_id??'');
118
+ const v=p&&JSON.parse(p.record);if(!p||p.request_id!==request_id||p.action!==action||p.fingerprint!==proof_hash||digest(v)!==proof_hash||credential_id!=='task-native'||principal!=='single-user-'+v.source.role)throw Error('TASK_DECISION_RECORD_REQUIRED');
119
+ }
103
120
  if(workbenchIM){
104
121
  const p=this.db.prepare('SELECT * FROM workbench_im_proofs WHERE challenge_id=?').get(challenge_id),b=p&&this.db.prepare('SELECT disabled FROM approval_chat_bindings WHERE id=?').get(p.binding_hash);
105
122
  if(!p||!b||b.disabled||credential_id!=='workbench-im:'+p.binding_hash||p.request_id!==request_id||p.credential_id!==credential_id||p.principal!==principal||p.action!==action||p.proof_hash!==proof_hash||digest(JSON.parse(p.proof))!==proof_hash)throw Error('APPROVAL_WORKBENCH_PROOF_REQUIRED');
@@ -112,7 +129,7 @@ export class ApprovalLedger {
112
129
  const p=this.db.prepare('SELECT * FROM approval_workbench_proofs WHERE challenge_id=?').get(challenge_id);
113
130
  if(!p||p.request_id!==request_id||p.credential_id!==credential_id||p.principal!==principal||p.action!==action||p.proof_hash!==proof_hash)throw Error('APPROVAL_WORKBENCH_PROOF_REQUIRED');
114
131
  }
115
- const r=this.assertDecision(request_id,action,{chatEvaluation,workbenchEvaluation:workbenchEvaluation||workbenchIM}),id='decision-'+randomUUID();
132
+ const r=this.assertDecision(request_id,action,{chatEvaluation,workbenchEvaluation:workbenchEvaluation||workbenchIM,nativeTask}),id='decision-'+randomUUID();
116
133
  this.db.prepare('INSERT INTO approval_decisions VALUES (?,?,?,?,?,?,?,?)').run(id,r.id,action,principal,credential_id,proof_hash,challenge_id,this.now());
117
134
  this.db.prepare('UPDATE approval_requests SET state=?,decision_id=? WHERE id=?').run({approve:'approved',reject:'rejected',revoke:'revoked',hold:'held',amend:'changes_requested',clarify:'clarification_required'}[action],id,r.id);
118
135
  this.enqueueReceipt(id);
@@ -121,7 +138,7 @@ export class ApprovalLedger {
121
138
  // Part of the decision transaction. A notification is never an authorization input.
122
139
  enqueueReceipt(decision_id,localWorkbenchBinding=null){
123
140
  const d=this.db.prepare('SELECT * FROM approval_decisions WHERE id=?').get(decision_id);
124
- if(!d)return;
141
+ if(!d||this.isNativeTask(this.row(d.request_id).task_id))return;
125
142
  let route=this.db.prepare("SELECT * FROM approval_chat_deliveries WHERE request_id=? AND state IN ('sent','inactive') AND message_id IS NOT NULL AND prompt_version IS NOT NULL").get(d.request_id);
126
143
  // Only the live local-workbench decision transaction may create this fallback.
127
144
  // Historical startup backfill never supplies a binding and cannot reroute old decisions.