@twinsearth/fang-dsh-plugin 0.0.0-stage → 3.3.5

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 (147) hide show
  1. package/CLA.md +79 -0
  2. package/LICENSE-COMMERCIAL +78 -0
  3. package/LICENSE-CORE +661 -0
  4. package/LICENSE-SDK +21 -0
  5. package/LICENSES/cordis-MIT.txt +39 -0
  6. package/NOTICE +95 -0
  7. package/README.md +158 -2
  8. package/client.js +7287 -0
  9. package/cordis.patch.yml +163 -0
  10. package/defaults/umbrella-rules.yaml +113 -0
  11. package/docs/APPROVAL-DRILL.md +42 -0
  12. package/docs/CONTRACT-INTEROP.md +77 -0
  13. package/docs/CRASH-SAFETY.md +69 -0
  14. package/docs/DECISION-RELAY.md +46 -0
  15. package/docs/DSH-ARCHITECTURE-MAP.md +73 -0
  16. package/docs/FORK-SEED.md +64 -0
  17. package/docs/MAIN-CITY-GATE.md +58 -0
  18. package/docs/OVERLAY-MASTER-SWITCH.md +35 -0
  19. package/docs/PACKAGE-RENAME-v3.3.3.md +40 -0
  20. package/docs/PM-DECISION-POLICY.md +61 -0
  21. package/docs/WORKFLOW-UI.md +86 -0
  22. package/icon.svg +8 -0
  23. package/locale/en.json +6 -0
  24. package/locale/zh.json +6 -0
  25. package/package.json +71 -4
  26. package/sdk/README.md +65 -0
  27. package/sdk/examples/status-badge/README.md +16 -0
  28. package/sdk/examples/status-badge/client.js +60 -0
  29. package/sdk/examples/status-badge/cordis.patch.yml +7 -0
  30. package/sdk/examples/status-badge/index.js +12 -0
  31. package/sdk/examples/status-badge/package.json +24 -0
  32. package/sdk/templates/plugin/README.md +36 -0
  33. package/sdk/templates/plugin/client.js +35 -0
  34. package/sdk/templates/plugin/cordis.patch.yml +8 -0
  35. package/sdk/templates/plugin/icon.svg +5 -0
  36. package/sdk/templates/plugin/index.js +23 -0
  37. package/sdk/templates/plugin/locale/en.json +6 -0
  38. package/sdk/templates/plugin/package.json +36 -0
  39. package/src/compose.mjs +67 -0
  40. package/src/contract/VENDOR.json +16 -0
  41. package/src/contract/client.js +93 -0
  42. package/src/contract/constants.js +94 -0
  43. package/src/contract/envelope.js +83 -0
  44. package/src/contract/index.js +20 -0
  45. package/src/contract/negotiate.js +106 -0
  46. package/src/contract/relay.js +223 -0
  47. package/src/contract/server.js +77 -0
  48. package/src/contract/transports.js +319 -0
  49. package/src/index.mjs +11 -0
  50. package/src/kernel/core.mjs +100 -0
  51. package/src/kernel/extensions.mjs +61 -0
  52. package/src/kernel/fsx.mjs +349 -0
  53. package/src/kernel/index.mjs +21 -0
  54. package/src/kernel/ledger.mjs +94 -0
  55. package/src/kernel/paths.mjs +182 -0
  56. package/src/kernel/role.mjs +68 -0
  57. package/src/kernel/rounds.mjs +172 -0
  58. package/src/kernel/schema.mjs +5 -0
  59. package/src/kernel/types.mjs +266 -0
  60. package/src/kernel/vendor/cosmokit.mjs +421 -0
  61. package/src/kernel/vendor/schemastery.mjs +638 -0
  62. package/src/lib/adapter-client.mjs +383 -0
  63. package/src/lib/adapter-protocol.mjs +243 -0
  64. package/src/lib/ai-secret-store.mjs +202 -0
  65. package/src/lib/approval-drill.mjs +111 -0
  66. package/src/lib/boot-guard.mjs +186 -0
  67. package/src/lib/branch-store.mjs +84 -0
  68. package/src/lib/branch.mjs +74 -0
  69. package/src/lib/bridge-client.mjs +133 -0
  70. package/src/lib/broker-adapter.mjs +67 -0
  71. package/src/lib/broker.mjs +45 -0
  72. package/src/lib/business-producers.mjs +109 -0
  73. package/src/lib/command-cache.mjs +209 -0
  74. package/src/lib/compat.mjs +170 -0
  75. package/src/lib/composer-journal.mjs +128 -0
  76. package/src/lib/container/index.mjs +806 -0
  77. package/src/lib/decision-events.mjs +129 -0
  78. package/src/lib/decision-ingest-client.mjs +41 -0
  79. package/src/lib/decision-outbox.mjs +129 -0
  80. package/src/lib/decision-policy.mjs +52 -0
  81. package/src/lib/default-essence.mjs +61 -0
  82. package/src/lib/desktop-launch.mjs +138 -0
  83. package/src/lib/essence.mjs +1211 -0
  84. package/src/lib/fork-client.mjs +410 -0
  85. package/src/lib/fork-config-lock.mjs +166 -0
  86. package/src/lib/fork-seed.mjs +304 -0
  87. package/src/lib/fork-sessions.mjs +183 -0
  88. package/src/lib/fork-supervision-settings.mjs +58 -0
  89. package/src/lib/fork-supervisor.mjs +122 -0
  90. package/src/lib/fs-guard.mjs +28 -0
  91. package/src/lib/http-auth.mjs +162 -0
  92. package/src/lib/idle-audit.mjs +178 -0
  93. package/src/lib/interaction-poke.mjs +61 -0
  94. package/src/lib/main-gate.mjs +582 -0
  95. package/src/lib/mirror-etag.mjs +92 -0
  96. package/src/lib/mirror-official-source.mjs +93 -0
  97. package/src/lib/mirror.mjs +478 -0
  98. package/src/lib/mother-link.mjs +156 -0
  99. package/src/lib/mutex.mjs +31 -0
  100. package/src/lib/permissions.mjs +115 -0
  101. package/src/lib/pnpm-store.mjs +189 -0
  102. package/src/lib/preferences.mjs +212 -0
  103. package/src/lib/producer-contract.mjs +162 -0
  104. package/src/lib/reflow.mjs +41 -0
  105. package/src/lib/remote.mjs +408 -0
  106. package/src/lib/restart-contract.mjs +752 -0
  107. package/src/lib/roots.mjs +30 -0
  108. package/src/lib/rule-registry.mjs +1127 -0
  109. package/src/lib/runtime-capture.mjs +307 -0
  110. package/src/lib/semaphore.mjs +34 -0
  111. package/src/lib/session-bundle.mjs +138 -0
  112. package/src/lib/session-format.mjs +672 -0
  113. package/src/lib/sse-parser.mjs +102 -0
  114. package/src/lib/ssrf-guard.mjs +170 -0
  115. package/src/lib/status-store.mjs +257 -0
  116. package/src/lib/task-handle.mjs +622 -0
  117. package/src/lib/ui-actions.mjs +55 -0
  118. package/src/lib/umbrella.mjs +1661 -0
  119. package/src/lib/upgrade-coordinator.mjs +107 -0
  120. package/src/lib/upgrade-status.mjs +43 -0
  121. package/src/plugins/adapter.mjs +1468 -0
  122. package/src/plugins/approver.mjs +1210 -0
  123. package/src/plugins/auditor.mjs +330 -0
  124. package/src/plugins/buffer-keeper.mjs +522 -0
  125. package/src/plugins/decision-center.mjs +2581 -0
  126. package/src/plugins/forker.mjs +1610 -0
  127. package/src/plugins/healer.mjs +702 -0
  128. package/src/plugins/hot-reloader.mjs +274 -0
  129. package/src/plugins/imports.mjs +247 -0
  130. package/src/plugins/orchestrator.mjs +3979 -0
  131. package/src/plugins/restart-guard.mjs +1022 -0
  132. package/src/plugins/router.mjs +714 -0
  133. package/src/plugins/scorer.mjs +661 -0
  134. package/src/plugins/session-bridge.mjs +166 -0
  135. package/src/plugins/session-catalog.mjs +771 -0
  136. package/src/plugins/session-router.mjs +125 -0
  137. package/src/plugins/session-sync.mjs +171 -0
  138. package/src/plugins/umbrella.mjs +419 -0
  139. package/tools/approval-drill.mjs +21 -0
  140. package/tools/boot-guard.mjs +95 -0
  141. package/tools/decision-ingest.mjs +53 -0
  142. package/tools/desktop-launch.mjs +71 -0
  143. package/tools/fangctl.mjs +425 -0
  144. package/tools/gate/main-city.mjs +204 -0
  145. package/tools/mcp/server.mjs +121 -0
  146. package/tools/mcp/tools.mjs +65 -0
  147. package/tools/recover-queue.mjs +133 -0
@@ -0,0 +1,58 @@
1
+ # 主城门禁(main-city gate)
2
+
3
+ > 目标:任何会影响 DSH 端安全的动作——打包、发布、安装、部署、新建 profile——都必须先通过同一套可复核检查。门禁拿不到证据时 **fail-closed**,不允许「先上再补」。
4
+
5
+ ## 1. 七个入口
6
+
7
+ | target | 何时 | 关键检查 |
8
+ |---|---|---|
9
+ | `source` | 提交/CI/发布前 | 版本、CHANGELOG/AUDIT、vendored contract 逐字节、关键防线文件、restart 默认 observe/不自动重启、build/publish 已接线 |
10
+ | `package` | 打包完成后 | staged package 名称/版本/private/client/bundle;tarball sha256、sidecar、manifest、禁用产物条目、必需文件 |
11
+ | `publish` | npm 发布前 | package 检查 + tag 与版本一致 + registry 同版本重复发布阻断(可显式 `--allow-existing`) |
12
+ | `install` | 干净 profile 安装 | package + profile 路径/捆绑去重/patch 可解析 + 无活跃宿主 + 崩溃循环/safe-mode + 审计链 + 磁盘 |
13
+ | `deploy` | 重启/部署前 | install 检查 + 备份(可 `--repair` 自动建)+ supervisor 证据 |
14
+ | `workflows` | CI/发布工作流变更 | 必需 ci/release、禁止 pull_request_target/write-all/secrets inherit、禁止 PR 字段直接插值 run、必须接 source gate 与 build.mjs |
15
+ | `profile` | 新建/覆盖 profile | 名称白名单、禁止 `desktop`/`plugin`、路径必须在 `$DSH_HOME/profiles`、磁盘、已存在须 `--force` + 自动备份 |
16
+
17
+ ## 2. 输出与退出码
18
+
19
+ - JSON 报告写入 `dist/main-city-gate.json`;
20
+ - `ok=false` 当且仅当存在 `blocker`;`warning` 不阻断但会进入评分;
21
+ - 评分由 `scorer.scoreGateFindings` 折算 A–E:0 warning=A,1–2=B,3+=C,任何 blocker=E;
22
+ - 退出码 0=GREEN,1=RED,2=用法错误。
23
+
24
+ ## 3. 与五个子插件的关系
25
+
26
+ | 子插件 | 在主城门的职责 |
27
+ |---|---|
28
+ | restart-guard | 崩溃循环(15 分钟内 ≥3 次)自动进入安全模式并**永久放弃交接执行权**,直到人工 `clearSafeMode`;门禁把 safe-mode/崩溃循环当 blocker |
29
+ | scorer | `scoreGateFindings` 把 findings 折成准入分;A/B 才放行 |
30
+ | approver | 需要写 profile 时由门禁生成 `.gate-backups/<stamp>/`(审批式快照,不覆盖) |
31
+ | auditor | 部署/安装/发布时校验 `audit.jsonl` 链;门禁结论写链(`main-city-gate`),链坏拒绝追加 |
32
+ | healer | 输出修复建议;修复不确定时保持 fail-closed(`blockingOnUnknown`) |
33
+ | 门禁/CI | build/publish/install/deploy 全部调用同一 CLI;CI hygiene/release guard 先跑 source gate |
34
+
35
+ ## 4. 常用命令
36
+
37
+ ```bash
38
+ node tools/gate/main-city.mjs --target source
39
+ node tools/gate/main-city.mjs --target package --package-dir dist/npm/package --tarball dist/dsh-fang-X.tgz
40
+ node tools/gate/main-city.mjs --target publish --tag vX --check-registry
41
+ node tools/gate/main-city.mjs --target install --home <DSH_HOME> --profile fang-x
42
+ node tools/gate/main-city.mjs --target deploy --home <DSH_HOME> --profile fang-x --repair
43
+ node tools/gate/main-city.mjs --target profile --home <DSH_HOME> --profile demo --create
44
+ ```
45
+
46
+ ## 5. 人工解除崩溃循环安全模式
47
+
48
+ 1. 先读 `<dataDir>/restart/safe-mode.json` 与 `runtime-crash.jsonl`,确认崩溃原因;
49
+ 2. 在运行中的宿主里调用 `ctx.get('fangRestart').clearSafeMode({ by, note })`(或对应面板);
50
+ 3. `clearSafeMode` 会把 crash ledger 改名为 `.cleared-<stamp>`,并写链式审计,下一次启动不会按旧证据重新触发;
51
+ 4. 重新跑 deploy gate;`runtime: no active crash loop` 与 `runtime: restart safe-mode clear` 必须为 PASS。
52
+
53
+ ## 6. 明确不做
54
+
55
+ - 不自动「修复」产物 hash/sidecar(会掩盖损坏),只报修复建议;
56
+ - 不自动清审计链或崩溃记录;
57
+ - 不在门禁里调用网络(`--check-registry` 例外,且只读 registry 的 latest);
58
+ - 不改 DSH 核心文件;写 profile 仅限 `--repair` 备份和 `profile --create`。
@@ -0,0 +1,35 @@
1
+ # 宿主浮窗总开关(v3.3.2)
2
+
3
+ ## 目标
4
+
5
+ 母体(宿主)页面里,瓮城的决策浮标、红色告警、收件箱抽屉、Mother 浮窗、通知/弹层都是浮层。
6
+ 需要一键隐藏/显示,而不是逐个关闭;隐藏后还要能随时恢复。
7
+
8
+ ## 入口
9
+
10
+ | 位置 | 形态 | 说明 |
11
+ |---|---|---|
12
+ | 宿主侧栏底部 | `浮窗` / `浮窗已隐藏` 按钮 | 常驻;其他浮窗全隐藏时仍然可见,作为恢复入口 |
13
+ | 瓮城控制台面板头 | `隐藏浮窗` / `显示浮窗` 按钮 | 在控制台内直接切换 |
14
+ | 偏好 | `localStorage['fang.overlays.hidden']` | `1` 隐藏 / `0` 显示;刷新、重启后保持 |
15
+ | 跨窗口 | `storage` 事件 | 一个窗口切换,其他窗口同步 |
16
+
17
+ ## 行为
18
+
19
+ 1. 切换 `document.documentElement[data-fang-overlays]` 为 `hidden|visible`;
20
+ 2. CSS 统一隐藏以下瓮城浮层(`display:none!important`):
21
+ - `.fang-relay-fab` / `.fang-relay-panel`(会话决策中继)
22
+ - `.fang-red-alert-stack` / `[data-fang-red-alerts]`(红色告警)
23
+ - `.fang-inbox-drawer` / `.fang-inbox-backdrop`(收件箱抽屉)
24
+ - `.mother-palette`(Mother 浮窗)
25
+ - `[data-fang-essence-notice]`(元主落地提示)
26
+ - `.fang-console .overlay`(确认/截图等弹层)
27
+ - `.fang-console .toast`(面板 toast)
28
+ 3. **不销毁 DOM、不关闭状态**:隐藏只是视觉开关;再显示时原样恢复(例如收件箱仍停在原分组/滚动位置)。
29
+ 4. 不隐藏宿主自己的 UI 与瓮城面板本身;开关按钮自身也不在隐藏列表里。
30
+
31
+ ## 明确不做
32
+
33
+ - 不修改宿主的浮窗系统,不注入全局 CSS 变量;
34
+ - 不把「隐藏」当成分身/元主 UI 锁(那是角色收敛,不是浮窗偏好);
35
+ - 不在隐藏时丢弃红色告警:告警仍在 DOM 中,恢复后可见;若需要真正处理,必须点开关显示后再处置。
@@ -0,0 +1,40 @@
1
+ # npm 包改名:@twinsearth/fang-dsh-plugin
2
+
3
+ 生效版本:`v3.3.4`(v3.3.3 因 npm 402 未发布)
4
+
5
+ ## 新身份
6
+
7
+ | 项 | 值 |
8
+ |---|---|
9
+ | npm 包名 | `@twinsearth/fang-dsh-plugin` |
10
+ | 安装 | `dsh plugin --profile desktop add @twinsearth/fang-dsh-plugin@3.3.3` |
11
+ | 本地开发 bundle 名 | `@twinsearth/fang-dsh-plugin`(构建时统一改写为新包名) |
12
+ | 旧 npm 包名 | `@twinsearth/dsh-fang`(继续兼容,不再作为新发布身份) |
13
+
14
+ ## 为什么保留旧名兼容
15
+
16
+ - 已安装旧包的 profile 仍能启动、升级;`FANG_PACKAGE_NAMES` / `FORK_BUNDLE_NAMES` 同时接受新旧名和本地 link。
17
+ - 新 profile、安装 drill、发布门禁、npm registry 查询全部使用新名。
18
+ - 旧包暂不自动 deprecate;如需公告迁移,单独执行 `npm deprecate` 并记录。
19
+
20
+ ## 市场状态(2026-11-14)
21
+
22
+ 当前 DSH `0.2.2-rc.1` 没有「官方插件市场」,文档明确「DSH 不内置任何索引源」。
23
+ 因此本版**只发布 npm,不提交任何市场收录**;官方市场上线后再按官方流程提交。
24
+ 社区 Open Plugin Market(dshopen.org)是第三方索引,本版未提交。
25
+
26
+ ## 验证
27
+
28
+ ```text
29
+ source / workflows / package / publish gate GREEN
30
+ 全量测试见构建输出
31
+ install drill 使用新包名安装并列出新 client.js
32
+ ```
33
+
34
+
35
+ ## 队列子插件默认关闭(同一版本)
36
+
37
+ - `dsh-fang-queue-journal` 的 bundle 行默认 `disabled: true`;
38
+ - 未启用时 host half 不提供 `fangQueueJournal`,journal/queue 路由返回 503;
39
+ - client half 不加载;即使旧缓存残留,也会因能力探测失败而隐藏 UI;
40
+ - 启用方式:在 profile 的 `cordis.patch.yml` 覆盖该行 `disabled: false`(或由后续设置界面提供开关)。
@@ -0,0 +1,61 @@
1
+ # PM 主动开票策略(v3.1.25)
2
+
3
+ > 目的:让「大模型主动开票」真正生效。决策中心链路一直是通的,但 PM 从不调用
4
+ > `ask_user_choice` / `request_human_decision`,于是「开发一整天也看不到一张票」。
5
+ >
6
+ > 策略落在两处(因为模型只能看到第二处):
7
+ > 1. 本文件(人读);
8
+ > 2. **工具 description**(模型读)——由 `src/plugins/decision-center.mjs` 的
9
+ > `PM_DECISION_POLICY` / `PM_PROACTIVE_HINT` 注入,改策略就改那里。
10
+
11
+ ## 一、必须开票(mustAsk)
12
+
13
+ | # | 情形 |
14
+ |---|---|
15
+ | 1 | 要在两个及以上合理方案里做取舍(架构 / 技术选型 / 交互形态),且代价不可忽略 |
16
+ | 2 | 要执行不可逆或难以回滚的动作:删除数据、重置分支、强制推送、覆盖生产配置、发版 |
17
+ | 3 | 要触碰凭据、权限、外部账号、付费资源或对外承诺 |
18
+ | 4 | 需求存在歧义或缺失关键信息,不同理解会导致返工 |
19
+ | 5 | 要越过既定范围(改用户没要求的模块、扩大 mandate、跨仓库操作) |
20
+ | 6 | 发现阻塞缺陷但修复方案会改变对外行为 / 契约 |
21
+ | 7 | 长时间任务(>10 分钟)需要中途改变方向 |
22
+
23
+ ## 二、不要开票(mustNotAsk)
24
+
25
+ - 低风险、可回滚、在既定范围内的实现细节(命名、注释、内部结构、测试补全);
26
+ - 等价于「继续」的常规推进,以及取文件 / 跑测试 / 格式化这类机械操作;
27
+ - 已经有明确约定的既定方案(此前已确认过)。
28
+
29
+ > 判据:开票的成本是「打断一个人的注意力」,只有**决策代价 > 打断代价**时才开票。
30
+
31
+ ## 三、提问规则(rules)
32
+
33
+ 1. 一次只问一个决定点,问题要具体到「选哪个」或「能不能做」;
34
+ 2. 必须给 2–4 个可执行选项,推荐项放第一个并把标签后缀 `(推荐)`;
35
+ 3. `context` 写清:为什么需要人决定、每个选项的代价与风险、不决定的后果;
36
+ 4. `red` 用于**阻塞**(人不在就没法继续);`yellow` / `blue` 用于「记录并继续」,不要滥用 red;
37
+ 5. **绝对不要因为「不确定」而把问题原样抛回给开发者** —— 先把能自己查的查清。
38
+
39
+ ## 四、AI 自动决策的边界(autoDecide)
40
+
41
+ | 项 | 默认 | 说明 |
42
+ |---|---|---|
43
+ | `autoDecide.enabled` | `true` | 关闭后所有票都必须人工回答 |
44
+ | `autoDecide.levels` | `["green"]` | 只对低注意力票自动决策 |
45
+ | `autoDecide.minConfidence` | `0.8` | 建议置信度下限 |
46
+ | `autoDecide.allowUnknownRisk` | `false` | 推荐项风险未知时是否允许自动 |
47
+
48
+ **硬红线**:`red` 与任何 `isBlocking` 的票**永不自动决策**,必须人工显式选择。
49
+ 自动决策会写 `resolvedBy: "ai-auto"` 并记审计 `decision-ai-auto-resolved`。
50
+
51
+ ## 五、如何验证策略生效
52
+
53
+ ```bash
54
+ # 1) 工具描述里应能看到策略(模型读得到)
55
+ node -e "import('./src/plugins/decision-center.mjs').then(m=>console.log(m.PM_DECISION_POLICY.mustAsk.length))"
56
+
57
+ # 2) 运行态:让 PM 遇到一个必然需要人决定的问题,观察是否出现新票
58
+ curl -H "x-fang-auth: <token>" http://127.0.0.1:<port>/fang/api/decisions
59
+
60
+ # 3) 收件箱:0 条时应看到常驻入口(半透明 + 计数 0)
61
+ ```
@@ -0,0 +1,86 @@
1
+ # WORKFLOW-UI — 瓮城 UI 改动与发版固定流程
2
+
3
+ > 状态:生效中(2026-11-10 起)。本文件是**后续会话的持久约束**;与 `docs/CRASH-SAFETY.md`
4
+ > 一起进包、进测试、进发布门禁。违反本流程 = 发布门禁不通过,不是“建议”。
5
+
6
+ ## 0. 铁律
7
+
8
+ 1. **发布必须由人类显式授权。** 禁止自动执行 `release workflow`、`npm publish`、GitHub Release、
9
+ 打 tag、移动 tag。完成修改与测试后先汇报,等确认再发。
10
+ 2. **不是所有修改都值得发版。** 文档/流程/无行为变化的重构可以只合并不发版;
11
+ 版本号是否递增由人类确认。
12
+ 3. **发版后不再动 tag/文档。** 发布后文档状态固化为“以 registry / Release 为准”,
13
+ 避免 v3.1.8 的“移动 tag → 重建 tarball → npm 与 Release 摘要不一致”返工。
14
+
15
+ ## 1. 先判定:纯样式 vs 结构/高度链
16
+
17
+ | 判定 | 处理 | 目标 |
18
+ |---|---|---|
19
+ | 纯样式(颜色、间距、圆角、滚动条外观) | 只改 CSS + 1 个 DOM 契约测试 | ≤10 分钟 |
20
+ | 结构/高度链(宿主容器 flex、`min-height`、滚动容器归属、overlay 层级) | 先画高度链与 DOM 归属,再改;必须加 DOM/CSS 契约测试 | 按预算 |
21
+ | 交互/数据(滚动、拖拽、焦点、异步状态) | 加可自动化的行为测试;必要时做一次浏览器冒烟 | 按预算 |
22
+
23
+ **检查点(改之前先回答)**:目标元素在哪个宿主容器里?宿主对它有 `height` 吗?
24
+ 滚动容器是不是“唯一的”?有没有内容在滚动容器之外(本次 v3.1.9 的根因就是元主卡片在滚动区外)?
25
+
26
+ ## 2. 浏览器冒烟:只在必要时,复用现成 harness
27
+
28
+ - 只有涉及**宿主布局 / 滚动 / 拖拽 / 层叠**时才做真实浏览器冒烟;
29
+ - 复用仓库已有工具/脚本,**不新建一次性 harness**;单次冒烟为上限,不反复迭代调试;
30
+ - 冒烟必须记录:视口尺寸、输入方式(CDP 真实 wheel/drag 要写清楚)、前后指标、截图路径。
31
+
32
+ ## 3. 合并前一次性验证(最多 2 轮)
33
+
34
+ ```bash
35
+ node --test test/<相关>.test.mjs # 单测 / DOM 契约
36
+ node --test test/ # 本地全量(含 jsdom 套件)
37
+ node tools/release/preflight.mjs # 版本/许可/allowlist/client 解析/crash-safety/全量
38
+ ```
39
+
40
+ - 全量 1 次绿灯即可;同一轮失败 → 修一次 → 复跑;**第 2 轮仍红就停下汇报**。
41
+ - PR body 必须附:改动范围、根因、验证命令与结果、已知边界。
42
+
43
+ ## 4. 发版前置检查(硬门禁,缺一不发)
44
+
45
+ 发版前逐条确认,任何一项不满足先修环境、**不先打 tag**:
46
+
47
+ 1. `git ls-remote https://github.com/<repo> HEAD` 可达(runner 与本机都能访问 github.com);
48
+ 2. self-hosted runner 在线且 idle;
49
+ 3. npm token 有效(`NPM_TOKEN` / 本地 secret 文件,可 `whoami` 或 dry-run 校验);
50
+ 4. 目标版本号**未打 tag**、CHANGELOG 有对应 `## vX.Y.Z` 段、`FANG_VERSION` 与 `package.json` 一致;
51
+ 5. `node tools/release/preflight.mjs --e2e` 本地 GREEN,且构建产物通过
52
+ `crash-safety --package`(打包后二次验证)。
53
+
54
+ 发布顺序(只有人类授权后才执行):
55
+ `PR 合并 → 新 main 打注解 tag → release workflow → 校验 tests/build/Release/npm/healthz/final verify → 告知升级与重启方式`。
56
+
57
+ ## 5. 运行中的 profile 升级安全规程(2026-11-10 事故后新增)
58
+
59
+ **禁止对正在运行的 DSH host 直接执行 `dsh plugin add` / 插件管理器改 profile。**
60
+ 2026-11-10 21:33 在运行中的 desktop profile 上执行升级,导致 DSH 崩溃、用户层被重置
61
+ (bundles 变回默认、`cordis.patch.yml` 被模板覆盖);已从 `.bak-*` 备份恢复。
62
+
63
+ 正确步骤:
64
+
65
+ 1. **停止 host**(或走宿主自带的“重启/退出”路径),确认没有进程占用 profile;
66
+ 2. 备份 `package.json`、`cordis.patch.yml`、`pnpm-workspace.yaml`、`pnpm-lock.yaml`
67
+ 到 `profiles/<name>/.repair-<ts>/`(或 `.bak-*`);
68
+ 3. 对**候选包**先冒烟,不先替换:`node tools/boot-guard.mjs --candidate-dir <下载解包目录> --json`;
69
+ 4. 安装/升级(优先 GUI 插件管理器,其次离线安装);
70
+ 5. 装完对 profile 做 dry-run:`node tools/boot-guard.mjs --profile-dir <profile> --json`;
71
+ 6. 启动 host;若启动失败,用 `--apply` 禁用失败行或恢复备份后重启;
72
+ 7. 记录:升级前版本、候选包冒烟结果、装后 dry-run 结果、回滚路径。
73
+
74
+ ## 6. Token / 时间预算
75
+
76
+ - 单 UI 改动:目标 **≤15 分钟**、验证 **≤2 轮**;
77
+ - 超出预算立即停下汇报:**卡在哪、已试什么、是否继续深挖**,由人类决定;
78
+ - 不做与需求无关的“顺手重构”;不新造工具(先找仓库已有工具)。
79
+
80
+ ## 7. 完成定义(Definition of Done)
81
+
82
+ - [ ] 根因写清楚(不是“加了 overflow”);
83
+ - [ ] 测试覆盖该根因(DOM/CSS 契约或行为断言);
84
+ - [ ] 本地全量绿灯 / preflight GREEN;
85
+ - [ ] 已记录边界与回滚路径;
86
+ - [ ] 发布前得到人类显式授权(否则停在“待发布”状态)。
package/icon.svg ADDED
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64" role="img" aria-label="dsh-Fang">
2
+ <rect x="4" y="4" width="56" height="56" rx="12" fill="#0f172a"/>
3
+ <rect x="12" y="12" width="16" height="16" rx="3" fill="#38bdf8"/>
4
+ <rect x="36" y="12" width="16" height="16" rx="3" fill="#1e40af" opacity="0.9"/>
5
+ <rect x="12" y="36" width="16" height="16" rx="3" fill="#1e40af" opacity="0.7"/>
6
+ <rect x="36" y="36" width="16" height="16" rx="3" fill="#64748b" opacity="0.8"/>
7
+ <path d="M28 20h8M20 28v8M44 28v8M28 44h8" stroke="#94a3b8" stroke-width="2" stroke-linecap="round"/>
8
+ </svg>
package/locale/en.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "Fangcheng: DSH's First Virtual Machine!",
4
+ "description": "Dsh-Fangcheng was born from a group of geeks who love tinkering — and have been burned by it more times than we can count. In the era of self-evolving AI, we believe that \"how to safely try something new\" matters more than \"the new thing itself\".\n\nIf you are one of these three, Fangcheng was built for you:\n1. The plugin-building maniac: writing N new plugins a day and changing code hundreds of times. You used to restart DSH after every edit; now you hot-reload inside Fangcheng, and a broken build rolls back instantly.\n2. The multi-tasker: one window for data analysis, one for a crawler, one for long-running tests. Fangcheng lets you open as many as you like without crashing — each in its own absolute isolation.\n3. The security worrier: unwilling to hand your client to third-party plugins, afraid of deleted files or stolen API keys. Fangcheng's review gates and independent sandboxes keep risky code boxed in."
5
+ }
6
+ }
package/locale/zh.json ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "meta": {
3
+ "title": "瓮城:DSH的第一台虚拟机!",
4
+ "description": "Dsh-瓮城,诞生于一群热爱折腾、也被折腾过无数次的极客之手。我们深知在“自进化 AI”时代,“如何安全地尝试新事物”比“新事物本身”更重要!\n\n如果你是以下三类人,瓮城就是为你量身定制的:\n1. 插件开发狂魔:每天要写 N 个新插件、改几百次代码。以前改一次重启一次 DSH;现在在瓮城里随便热更新,坏了直接秒回滚。\n2. 多任务并发狂:一个窗口跑数据分析,一个窗口写爬虫,一个窗口做长周期测试。瓮城让你像开浏览器一样“多开不崩”,并各跑各的绝对隔离。\n3. 安全焦虑症患者:不敢把客户端交给第三方插件,怕乱删文件、怕被盗取 API Key。瓮城的审核系统和独立沙箱,把危险代码安排得明明白白。"
5
+ }
6
+ }
package/package.json CHANGED
@@ -1,6 +1,73 @@
1
1
  {
2
2
  "name": "@twinsearth/fang-dsh-plugin",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "3.3.5",
4
+ "private": false,
5
+ "type": "module",
6
+ "description": "dsh-Fang (瓮城) — mother/fork plugin development environment for DeepSeek Harness: one readonly mother, a disposable child dsh-Web per task.",
7
+ "exports": {
8
+ ".": "./src/index.mjs",
9
+ "./compose": "./src/compose.mjs",
10
+ "./orchestrator": "./src/plugins/orchestrator.mjs",
11
+ "./forker": "./src/plugins/forker.mjs",
12
+ "./scorer": "./src/plugins/scorer.mjs",
13
+ "./umbrella": "./src/plugins/umbrella.mjs",
14
+ "./healer": "./src/plugins/healer.mjs",
15
+ "./approver": "./src/plugins/approver.mjs",
16
+ "./hot-reloader": "./src/plugins/hot-reloader.mjs",
17
+ "./restart-guard": "./src/plugins/restart-guard.mjs",
18
+ "./buffer-keeper": "./src/plugins/buffer-keeper.mjs",
19
+ "./auditor": "./src/plugins/auditor.mjs",
20
+ "./router": "./src/plugins/router.mjs",
21
+ "./adapter": "./src/plugins/adapter.mjs",
22
+ "./session-catalog": "./src/plugins/session-catalog.mjs",
23
+ "./client": {
24
+ "browser": "./client.js",
25
+ "default": "./client.js"
26
+ },
27
+ "./locale/*.json": "./locale/*.json",
28
+ "./icon": "./icon.svg",
29
+ "./session-bridge": "./src/plugins/session-bridge.mjs",
30
+ "./session-sync": "./src/plugins/session-sync.mjs",
31
+ "./session-router": "./src/plugins/session-router.mjs",
32
+ "./decision-center": "./src/plugins/decision-center.mjs",
33
+ "./contract": "./src/contract/index.js"
34
+ },
35
+ "dsh": {
36
+ "bundle": {
37
+ "patch": "./cordis.patch.yml"
38
+ },
39
+ "client": {
40
+ "platform": "web",
41
+ "immediately": true,
42
+ "inject": []
43
+ }
44
+ },
45
+ "scripts": {
46
+ "test": "node --test --test-concurrency=1 \"test/*.test.mjs\""
47
+ },
48
+ "license": "AGPL-3.0-or-later",
49
+ "engines": {
50
+ "node": ">=22"
51
+ },
52
+ "optionalDependencies": {
53
+ "@twinsearth/dsh-fang-queue-journal": "^0.1.7",
54
+ "@twinsearth/dsh-fang-web-entry": "0.1.1"
55
+ },
56
+ "devDependencies": {
57
+ "jsdom": "^31.0.0"
58
+ },
59
+ "publishConfig": {
60
+ "access": "public"
61
+ },
62
+ "repository": {
63
+ "type": "git",
64
+ "url": "git+https://github.com/TwinsEarth/dsh-fang.git"
65
+ },
66
+ "homepage": "https://github.com/TwinsEarth/dsh-fang#readme",
67
+ "bugs": {
68
+ "url": "https://github.com/TwinsEarth/dsh-fang/issues"
69
+ },
70
+ "bin": {
71
+ "fangctl": "./tools/fangctl.mjs"
72
+ }
73
+ }
package/sdk/README.md ADDED
@@ -0,0 +1,65 @@
1
+ # dsh-Fang 生态层(MIT)
2
+
3
+ 这个目录是 dsh-Fang 的**生态层**,使用 [MIT 许可](../LICENSE-SDK):
4
+ 插件 SDK、模板、示例都可以零摩擦复制、修改、二次分发,甚至闭源集成进你的产品。
5
+
6
+ > 核心层(编排器/派生器/红线守卫/Adapter/容器执行器等)在 `src/` 下,使用 **AGPL-3.0**(见
7
+ > `../LICENSE-CORE`);商业层(Relay/企业功能)在 `../relay-server`、`../enterprise`,闭源。
8
+ > 逐目录许可地图见 [`../NOTICE`](../NOTICE)。
9
+
10
+ ---
11
+
12
+ ## 1. 一个 dsh-Fang 插件长什么样
13
+
14
+ Fang 插件就是一个 DSH bundle:一个 npm 包目录,带
15
+
16
+ ```
17
+ my-plugin/
18
+ ├── package.json # name + dsh.bundle.patch(+ 可选 dsh.client 声明浏览器半)
19
+ ├── cordis.patch.yml # 安装时把本插件的 loader 行 insert 进 profile
20
+ ├── index.js # Host 半:export const name/inject/Config/apply
21
+ └── client.js # 可选 Client 半:window.__ModuleLoader__.load({...})
22
+ ```
23
+
24
+ 安装(不需要发布到 npm):
25
+
26
+ ```powershell
27
+ # 在 DSH 会话里对 Agent 说:
28
+ # plugin_manager install_bundle(target="E:\path\to\my-plugin")
29
+ # 或者由你的发布脚本调用 DSH 的 plugin_manager
30
+ ```
31
+
32
+ ## 2. 模板
33
+
34
+ - [`templates/plugin/`](templates/plugin/) — 最小可安装插件(Host + 可选 Client + 图标 + locale)
35
+ - [`examples/status-badge/`](examples/status-badge/) — 读取瓮城 `/fang/api/status` 并在输入框下方显示徽标
36
+ (演示"第三方插件如何安全调用 Fang 的公开 HTTP 面")
37
+
38
+ 复制模板:
39
+
40
+ ```powershell
41
+ Copy-Item -Recurse sdk\templates\plugin my-plugin
42
+ # 改 package.json 里的 name / 行 id / patch 里的 id
43
+ ```
44
+
45
+ ## 3. 与瓮城核心交互的三种合法方式
46
+
47
+ | 方式 | 适用 | 协议边界 |
48
+ |---|---|---|
49
+ | 读 `/fang/api/status`、`/fang/api/tasks`、`/fang/api/sessions` | 面板/徽标/旁路观察 | 只读 HTTP,最安全 |
50
+ | `POST /fang/api/derive`、`/tasks/*`、`/promote` | 自动化工作流 | 写入前请遵守核心的配额与审批语义 |
51
+ | Adapter 协议(仅分身内,`/fang/adapter/*`) | 分身内插件 | 需要分身 token;不要在母体调用 |
52
+
53
+ > 插件与核心之间通过**公开 HTTP API** 交互时,你的插件是独立作品,可以自选许可证
54
+ > (MIT/Apache/闭源均可)——这正是生态层 MIT 的意义。
55
+
56
+ ## 4. 需要遵守的少量规则
57
+
58
+ - 不要从其他插件的 DOM / 样式表里"猜"布局;用 slots 与主题 token(`--dsw-alias-*`)。
59
+ - Client 半不要 `require` 其他 DSH Client 包;React 来自浏览器模块表。
60
+ - 定时器/监听器用 `ctx.effect` 注册清理。
61
+ - 不要写 `$DSH_HOME/profiles/**`;通过 bundle/plugin_manager 安装。
62
+
63
+ ## 5. 贡献
64
+
65
+ 生态层(`sdk/`、`tools/fangctl.mjs`)的贡献同样需要签署 [CLA](../CLA.md)(为了让项目能双许可)。
@@ -0,0 +1,16 @@
1
+ # status-badge(MIT 示例插件)
2
+
3
+ 演示第三方插件如何**只通过公开 HTTP 面**与瓮城交互:
4
+
5
+ - `index.js`:Host 半,提供一个只读服务(证明插件已加载);
6
+ - `client.js`:Client 半,每 10s 拉一次 `GET /fang/api/status`,在输入框下方显示「瓮城 N/M」。
7
+ - 不 import 任何瓮城内部模块 → 它是独立作品,可自由选许可证(本示例为 MIT)。
8
+
9
+ 安装:
10
+
11
+ ```powershell
12
+ # DSH 会话里:
13
+ # plugin_manager install_bundle(target="<repo>\\sdk\\examples\\status-badge")
14
+ ```
15
+
16
+ 失败态:拉不到 `/fang/api/status` 时显示「瓮城离线」,不假装有数据。
@@ -0,0 +1,60 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Example client half: read dsh-Fang's public read-only HTTP surface and show it in the dock.
3
+ // This demonstrates the recommended third-party integration: HTTP only, no host internals.
4
+ window.__ModuleLoader__.load({
5
+ id: '@fang-example/status-badge',
6
+ factory(require) {
7
+ const React = require('react')
8
+ const h = React.createElement
9
+
10
+ function StatusBadge() {
11
+ const [view, setView] = React.useState({ state: 'loading', text: '瓮城 …' })
12
+ React.useEffect(() => {
13
+ let alive = true
14
+ const poll = async () => {
15
+ try {
16
+ const res = await fetch('/fang/api/status', { headers: { accept: 'application/json' } })
17
+ if (!res.ok) throw new Error('status ' + res.status)
18
+ const body = await res.json()
19
+ if (!alive) return
20
+ const ready = body?.tasks?.ready ?? 0
21
+ const max = body?.tasks?.max ?? '?'
22
+ setView({ state: 'ok', text: `瓮城 ${ready}/${max}`, version: body?.version })
23
+ } catch {
24
+ if (alive) setView({ state: 'off', text: '瓮城离线' })
25
+ }
26
+ }
27
+ poll()
28
+ const timer = setInterval(poll, 10000)
29
+ return () => { alive = false; clearInterval(timer) }
30
+ }, [])
31
+
32
+ const color = view.state === 'ok'
33
+ ? 'var(--dsw-alias-state-success, #22c55e)'
34
+ : view.state === 'off'
35
+ ? 'var(--dsw-alias-state-warning, #9ca3af)'
36
+ : 'var(--dsw-alias-state-business-primary, #2563eb)'
37
+ return h('div', {
38
+ title: view.version ? `dsh-Fang ${view.version}` : 'dsh-Fang 状态(第三方插件示例)',
39
+ style: {
40
+ display: 'inline-flex', alignItems: 'center', gap: 6, padding: '2px 8px', borderRadius: 999,
41
+ fontSize: 11, color: 'var(--dsw-alias-label-secondary, #888)',
42
+ border: '1px solid var(--dsw-alias-border, rgba(127,127,127,.3))',
43
+ },
44
+ },
45
+ h('span', { 'aria-hidden': true, style: { width: 6, height: 6, borderRadius: '50%', background: color } }),
46
+ h('span', null, view.text))
47
+ }
48
+
49
+ return {
50
+ inject: ['slots'],
51
+ apply(ctx) {
52
+ return ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
53
+ name: 'conversation.composer.dock',
54
+ id: 'fang-example-status-badge',
55
+ order: 70,
56
+ }, (props) => h(StatusBadge, { ...props, ctx })))
57
+ },
58
+ }
59
+ },
60
+ })
@@ -0,0 +1,7 @@
1
+ # SPDX-License-Identifier: MIT
2
+ # Example plugin row: host half marks the plugin present; the client half talks to /fang/api/status.
3
+ - insert:
4
+ - id: fang-example-status-badge
5
+ name: '@fang-example/status-badge'
6
+ config:
7
+ intervalMs: 10000
@@ -0,0 +1,12 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // Example host half: exposes a tiny service so the plugin is observable, and nothing else.
3
+ export const name = 'fang-example-status-badge'
4
+ export const inject = []
5
+
6
+ export function apply(ctx, config) {
7
+ const cfg = { intervalMs: 10000, ...(config ?? {}) }
8
+ ctx.effect(() => ctx.provide('fangExampleStatusBadge', {
9
+ intervalMs: () => cfg.intervalMs,
10
+ at: new Date().toISOString(),
11
+ }))
12
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@fang-example/status-badge",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "Example MIT plugin: shows dsh-Fang status next to the composer (third-party plugin calling Fang's public HTTP surface).",
7
+ "exports": {
8
+ ".": "./index.js",
9
+ "./client": {
10
+ "browser": "./client.js",
11
+ "default": "./client.js"
12
+ }
13
+ },
14
+ "files": ["index.js", "client.js", "cordis.patch.yml"],
15
+ "license": "MIT",
16
+ "dsh": {
17
+ "bundle": { "patch": "./cordis.patch.yml" },
18
+ "client": {
19
+ "platform": "web",
20
+ "immediately": true,
21
+ "inject": ["@deepseek-ai/dsh-client-connection"]
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,36 @@
1
+ # 插件模板(MIT)
2
+
3
+ 最小可安装的 DSH / dsh-Fang 插件,包含 Host 半(`index.js`)与可选的 Client 半(`client.js`)。
4
+
5
+ ## 用法
6
+
7
+ ```powershell
8
+ Copy-Item -Recurse sdk\templates\plugin E:\my\my-plugin
9
+ cd E:\my\my-plugin
10
+ # 1) 改 package.json:name、dsh.client(不需要 UI 就删掉 dsh.client 与 client.js)
11
+ # 2) 改 cordis.patch.yml:id 与 name 保持一致
12
+ # 3) 在 DSH 会话里安装:
13
+ # plugin_manager install_bundle(target="E:\\my\\my-plugin")
14
+ ```
15
+
16
+ ## 结构
17
+
18
+ | 文件 | 作用 |
19
+ |---|---|
20
+ | `package.json` | `dsh.bundle.patch` 让它成为可安装 bundle;`dsh.client` 声明浏览器半 |
21
+ | `cordis.patch.yml` | 安装时把 `my-plugin` 这一行 insert 进 profile 的 loader 树 |
22
+ | `index.js` | Host 半:`export const name / inject / apply`;用 `ctx.effect(ctx.provide(...))` 提供能力 |
23
+ | `client.js` | Client 半:向 `conversation.composer.dock` 注册一个徽标(可删) |
24
+ | `locale/en.json` | 界面上显示给用户的标题与描述(国际化) |
25
+ | `icon.svg` | 插件图标;不要用上游/他人的图标 |
26
+
27
+ ## 可配置
28
+
29
+ 模板刻意**不带** `Config`,避免模板依赖 schema 库。需要配置时,把 schemastery(MIT)复制到
30
+ `vendor/` 再 `export const Config = z.object({...})`——核心层的做法可直接参考。
31
+
32
+ ## 与瓮城交互
33
+
34
+ 第三方插件调用瓮城只看公开 HTTP 面:`/fang/api/status`、`/fang/api/tasks`、`/fang/api/sessions`、
35
+ `POST /fang/api/derive` 等(见 [`../../README.md`](../../README.md))。用 HTTP 交互时你的插件是独立作品,
36
+ 可以选择自己的许可证。
@@ -0,0 +1,35 @@
1
+ // SPDX-License-Identifier: MIT
2
+ // dsh-Fang / DSH plugin template — optional Client half.
3
+ //
4
+ // Contract: window.__ModuleLoader__.load({ id: <package name>, factory(require) {...} })
5
+ // React comes from the browser module table; do not require other DSH client packages.
6
+ window.__ModuleLoader__.load({
7
+ id: '@your-scope/dsh-plugin-template',
8
+ factory(require) {
9
+ const React = require('react')
10
+ const h = React.createElement
11
+
12
+ function Badge() {
13
+ return h('div', {
14
+ title: 'my-plugin: 输入框下方的一个示例徽标',
15
+ style: {
16
+ display: 'inline-flex', alignItems: 'center', gap: 6,
17
+ padding: '2px 8px', borderRadius: 999, fontSize: 11,
18
+ color: 'var(--dsw-alias-label-secondary, #888)',
19
+ border: '1px solid var(--dsw-alias-border, rgba(127,127,127,.3))',
20
+ },
21
+ }, 'my-plugin')
22
+ }
23
+
24
+ return {
25
+ inject: ['slots'],
26
+ apply(ctx) {
27
+ return ctx.slots.inject('conversation.composer.dock', () => ctx.slots.register({
28
+ name: 'conversation.composer.dock',
29
+ id: 'my-plugin-badge',
30
+ order: 60,
31
+ }, (props) => h(Badge, { ...props, ctx })))
32
+ },
33
+ }
34
+ },
35
+ })