@weavecode/weave 0.1.5 → 0.1.6

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 (292) hide show
  1. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/encodeurl/README.md +14 -0
  2. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/encodeurl/index.js +5 -10
  3. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/encodeurl/package.json +10 -6
  4. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/CHANGELOG.md +42 -0
  5. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/README.md +38 -10
  6. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/dist/qs.js +18 -18
  7. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/lib/parse.js +16 -4
  8. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/lib/utils.js +13 -2
  9. package/node_modules/@modelcontextprotocol/sdk/node_modules/express/node_modules/qs/package.json +1 -1
  10. package/node_modules/@modelcontextprotocol/sdk/node_modules/express-rate-limit/dist/index.cjs +6 -2
  11. package/node_modules/@modelcontextprotocol/sdk/node_modules/express-rate-limit/dist/index.mjs +6 -2
  12. package/node_modules/@modelcontextprotocol/sdk/node_modules/express-rate-limit/package.json +10 -10
  13. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/decrypt.d.ts +3 -0
  14. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/unsecured.d.ts +2 -0
  15. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/jwt/verify.d.ts +3 -0
  16. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/types.d.ts +8 -0
  17. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/types/util/decode_jwt.d.ts +1 -0
  18. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/dist/webapi/jwks/remote.js +1 -1
  19. package/node_modules/@modelcontextprotocol/sdk/node_modules/jose/package.json +1 -1
  20. package/node_modules/@weavecode/cli/dist/cli.js +4 -4
  21. package/node_modules/@weavecode/cli/dist/commands/daemon.d.ts +2 -0
  22. package/node_modules/@weavecode/cli/dist/commands/daemon.js +2 -2
  23. package/node_modules/@weavecode/cli/dist/commands/dashboard.js +2 -2
  24. package/node_modules/@weavecode/cli/dist/commands/doctor.js +1 -1
  25. package/node_modules/@weavecode/cli/dist/commands/init.js +1 -1
  26. package/node_modules/@weavecode/cli/dist/commands/msg.js +2 -2
  27. package/node_modules/@weavecode/cli/dist/commands/status.js +2 -2
  28. package/node_modules/@weavecode/cli/dist/i18n/dict/en.js +4 -4
  29. package/node_modules/@weavecode/cli/dist/i18n/dict/zh.d.ts +5 -1
  30. package/node_modules/@weavecode/cli/dist/i18n/dict/zh.js +4 -4
  31. package/node_modules/@weavecode/cli/dist/statusline/ansi.js +1 -1
  32. package/node_modules/@weavecode/cli/package.json +5 -5
  33. package/node_modules/@weavecode/core/dist/harness.d.ts +14 -0
  34. package/node_modules/@weavecode/core/dist/harness.js +1 -1
  35. package/node_modules/@weavecode/core/dist/index.d.ts +1 -1
  36. package/node_modules/@weavecode/core/dist/index.js +1 -1
  37. package/node_modules/@weavecode/core/package.json +1 -1
  38. package/node_modules/@weavecode/harness/dist/claude/assets.js +1 -1
  39. package/node_modules/@weavecode/harness/dist/claude/commands.d.ts +3 -0
  40. package/node_modules/@weavecode/harness/dist/claude/commands.js +2 -3
  41. package/node_modules/@weavecode/harness/dist/claude/index.d.ts +1 -0
  42. package/node_modules/@weavecode/harness/dist/claude/index.js +1 -1
  43. package/node_modules/@weavecode/harness/dist/claude/narration.d.ts +0 -11
  44. package/node_modules/@weavecode/harness/dist/claude/narration.js +1 -1
  45. package/node_modules/@weavecode/harness/dist/claude/questions.js +2 -2
  46. package/node_modules/@weavecode/harness/dist/claude/rewind.d.ts +8 -0
  47. package/node_modules/@weavecode/harness/dist/claude/rewind.js +1 -0
  48. package/node_modules/@weavecode/harness/dist/claude/statusline/generator.js +31 -10
  49. package/node_modules/@weavecode/harness/dist/claude/transcript-cursor.d.ts +26 -0
  50. package/node_modules/@weavecode/harness/dist/claude/transcript-cursor.js +2 -0
  51. package/node_modules/@weavecode/harness/dist/codex/commands.js +4 -4
  52. package/node_modules/@weavecode/harness/dist/driver.d.ts +49 -1
  53. package/node_modules/@weavecode/harness/dist/driver.js +1 -1
  54. package/node_modules/@weavecode/harness/dist/zcode/assets.js +1 -1
  55. package/node_modules/@weavecode/harness/dist/zcode/capture.js +3 -3
  56. package/node_modules/@weavecode/harness/dist/zcode/mcp.js +2 -2
  57. package/node_modules/@weavecode/harness/package.json +2 -2
  58. package/node_modules/@weavecode/server/dist/config/dotted.js +1 -1
  59. package/node_modules/@weavecode/server/dist/config/profiles.d.ts +19 -13
  60. package/node_modules/@weavecode/server/dist/config/profiles.js +2 -2
  61. package/node_modules/@weavecode/server/dist/daemon/ai-config.js +2 -2
  62. package/node_modules/@weavecode/server/dist/daemon/ai-context.js +6 -6
  63. package/node_modules/@weavecode/server/dist/daemon/ai-routes.js +4 -4
  64. package/node_modules/@weavecode/server/dist/daemon/api-log.js +3 -3
  65. package/node_modules/@weavecode/server/dist/daemon/auth-routes.js +1 -1
  66. package/node_modules/@weavecode/server/dist/daemon/auto-start.js +6 -6
  67. package/node_modules/@weavecode/server/dist/daemon/capture-routes.js +1 -1
  68. package/node_modules/@weavecode/server/dist/daemon/config-routes.js +1 -1
  69. package/node_modules/@weavecode/server/dist/daemon/cron.js +1 -1
  70. package/node_modules/@weavecode/server/dist/daemon/dashboard-auth.js +2 -2
  71. package/node_modules/@weavecode/server/dist/daemon/dashboard-pusher.js +1 -1
  72. package/node_modules/@weavecode/server/dist/daemon/data-bus.js +1 -1
  73. package/node_modules/@weavecode/server/dist/daemon/decision-config.js +2 -2
  74. package/node_modules/@weavecode/server/dist/daemon/dev-entry.js +1 -1
  75. package/node_modules/@weavecode/server/dist/daemon/entry.js +7 -1
  76. package/node_modules/@weavecode/server/dist/daemon/fs-browser.js +1 -1
  77. package/node_modules/@weavecode/server/dist/daemon/hook-pipeline.js +1 -1
  78. package/node_modules/@weavecode/server/dist/daemon/hooks-routes.js +1 -1
  79. package/node_modules/@weavecode/server/dist/daemon/http.js +1 -1
  80. package/node_modules/@weavecode/server/dist/daemon/liveness.js +1 -1
  81. package/node_modules/@weavecode/server/dist/daemon/mcp-orchestration.js +1 -1
  82. package/node_modules/@weavecode/server/dist/daemon/narration-watcher.js +2 -2
  83. package/node_modules/@weavecode/server/dist/daemon/opener/backend-ghostty.js +1 -1
  84. package/node_modules/@weavecode/server/dist/daemon/opener/backend-iterm.js +1 -1
  85. package/node_modules/@weavecode/server/dist/daemon/opener/backend-konsole.js +2 -2
  86. package/node_modules/@weavecode/server/dist/daemon/opener/backend-terminal-app.js +1 -1
  87. package/node_modules/@weavecode/server/dist/daemon/opener/backend-wezterm.js +1 -1
  88. package/node_modules/@weavecode/server/dist/daemon/opener/backend-wt.js +1 -1
  89. package/node_modules/@weavecode/server/dist/daemon/opener/backend.js +1 -1
  90. package/node_modules/@weavecode/server/dist/daemon/opener/pty-terminals.js +4 -4
  91. package/node_modules/@weavecode/server/dist/daemon/opener/shared.js +1 -1
  92. package/node_modules/@weavecode/server/dist/daemon/opener/wezterm-mux.js +1 -1
  93. package/node_modules/@weavecode/server/dist/daemon/opener/wt.js +3 -3
  94. package/node_modules/@weavecode/server/dist/daemon/opener.d.ts +2 -0
  95. package/node_modules/@weavecode/server/dist/daemon/opener.js +1 -1
  96. package/node_modules/@weavecode/server/dist/daemon/port.js +1 -1
  97. package/node_modules/@weavecode/server/dist/daemon/project-files.js +2 -2
  98. package/node_modules/@weavecode/server/dist/daemon/project-harnesses.js +2 -2
  99. package/node_modules/@weavecode/server/dist/daemon/project-updates-refresher.js +1 -1
  100. package/node_modules/@weavecode/server/dist/daemon/prompt-images.js +1 -1
  101. package/node_modules/@weavecode/server/dist/daemon/routes/hooks-admin.js +1 -1
  102. package/node_modules/@weavecode/server/dist/daemon/routes/market.js +1 -1
  103. package/node_modules/@weavecode/server/dist/daemon/routes/orchestration.js +1 -1
  104. package/node_modules/@weavecode/server/dist/daemon/routes/path-refs.js +1 -1
  105. package/node_modules/@weavecode/server/dist/daemon/routes/project-files.js +1 -1
  106. package/node_modules/@weavecode/server/dist/daemon/routes/projects.js +1 -1
  107. package/node_modules/@weavecode/server/dist/daemon/routes/search.js +1 -1
  108. package/node_modules/@weavecode/server/dist/daemon/routes/session-control.js +1 -1
  109. package/node_modules/@weavecode/server/dist/daemon/routes/session-insights.js +1 -1
  110. package/node_modules/@weavecode/server/dist/daemon/routes/settings-statusline.js +1 -1
  111. package/node_modules/@weavecode/server/dist/daemon/routes/uploads.js +1 -1
  112. package/node_modules/@weavecode/server/dist/daemon/routes/visualizations.d.ts +3 -0
  113. package/node_modules/@weavecode/server/dist/daemon/routes/visualizations.js +1 -0
  114. package/node_modules/@weavecode/server/dist/daemon/routes/watches.js +1 -1
  115. package/node_modules/@weavecode/server/dist/daemon/server.js +8 -8
  116. package/node_modules/@weavecode/server/dist/daemon/session-open.js +1 -1
  117. package/node_modules/@weavecode/server/dist/daemon/session-probe.d.ts +5 -4
  118. package/node_modules/@weavecode/server/dist/daemon/session-probe.js +1 -1
  119. package/node_modules/@weavecode/server/dist/daemon/settings.js +2 -2
  120. package/node_modules/@weavecode/server/dist/daemon/snapshot-refresher.js +1 -1
  121. package/node_modules/@weavecode/server/dist/daemon/sse-hub.js +1 -1
  122. package/node_modules/@weavecode/server/dist/daemon/start.js +1 -1
  123. package/node_modules/@weavecode/server/dist/daemon/state.js +2 -2
  124. package/node_modules/@weavecode/server/dist/daemon/static.js +1 -1
  125. package/node_modules/@weavecode/server/dist/daemon/terminal-topology-refresher.js +1 -1
  126. package/node_modules/@weavecode/server/dist/daemon/tls.js +1 -1
  127. package/node_modules/@weavecode/server/dist/daemon/toolkit-routes.js +1 -1
  128. package/node_modules/@weavecode/server/dist/daemon/upload-sessions.js +1 -1
  129. package/node_modules/@weavecode/server/dist/db/db.js +6 -2
  130. package/node_modules/@weavecode/server/dist/db/layout.js +1 -1
  131. package/node_modules/@weavecode/server/dist/hooks/answer-keys.js +1 -1
  132. package/node_modules/@weavecode/server/dist/hooks/bus.d.ts +3 -1
  133. package/node_modules/@weavecode/server/dist/hooks/bus.js +1 -1
  134. package/node_modules/@weavecode/server/dist/hooks/dashboard-rate.js +1 -1
  135. package/node_modules/@weavecode/server/dist/hooks/decision-judge.js +1 -1
  136. package/node_modules/@weavecode/server/dist/hooks/delivery/ack-gate.d.ts +12 -6
  137. package/node_modules/@weavecode/server/dist/hooks/delivery/ack-gate.js +1 -1
  138. package/node_modules/@weavecode/server/dist/hooks/delivery/channels.d.ts +3 -3
  139. package/node_modules/@weavecode/server/dist/hooks/delivery/channels.js +1 -1
  140. package/node_modules/@weavecode/server/dist/hooks/delivery/keys.d.ts +19 -9
  141. package/node_modules/@weavecode/server/dist/hooks/delivery/keys.js +1 -1
  142. package/node_modules/@weavecode/server/dist/hooks/delivery/queue.js +1 -1
  143. package/node_modules/@weavecode/server/dist/hooks/delivery/relay.d.ts +2 -2
  144. package/node_modules/@weavecode/server/dist/hooks/delivery/relay.js +1 -1
  145. package/node_modules/@weavecode/server/dist/hooks/delivery/send.js +2 -2
  146. package/node_modules/@weavecode/server/dist/hooks/delivery/views.d.ts +3 -1
  147. package/node_modules/@weavecode/server/dist/hooks/delivery/views.js +1 -1
  148. package/node_modules/@weavecode/server/dist/hooks/dispatcher.js +6 -6
  149. package/node_modules/@weavecode/server/dist/hooks/edge-extract.js +1 -1
  150. package/node_modules/@weavecode/server/dist/hooks/handlers/webhook.js +1 -1
  151. package/node_modules/@weavecode/server/dist/hooks/headless-runner.js +2 -2
  152. package/node_modules/@weavecode/server/dist/hooks/inbox-socket.js +2 -2
  153. package/node_modules/@weavecode/server/dist/hooks/inbox-worker.js +3 -3
  154. package/node_modules/@weavecode/server/dist/hooks/match.js +1 -1
  155. package/node_modules/@weavecode/server/dist/hooks/message-deliverer.d.ts +45 -38
  156. package/node_modules/@weavecode/server/dist/hooks/message-deliverer.js +5 -3
  157. package/node_modules/@weavecode/server/dist/hooks/mirror.js +4 -4
  158. package/node_modules/@weavecode/server/dist/hooks/orchestration-engine.js +7 -7
  159. package/node_modules/@weavecode/server/dist/hooks/orchestration-graph.js +1 -1
  160. package/node_modules/@weavecode/server/dist/hooks/plugin-installer.js +3 -3
  161. package/node_modules/@weavecode/server/dist/hooks/plugin-loader.js +1 -1
  162. package/node_modules/@weavecode/server/dist/hooks/plugin-project-state.js +1 -1
  163. package/node_modules/@weavecode/server/dist/hooks/prompt-flusher.js +1 -1
  164. package/node_modules/@weavecode/server/dist/hooks/promptbox.js +8 -8
  165. package/node_modules/@weavecode/server/dist/hooks/push-notify.js +2 -2
  166. package/node_modules/@weavecode/server/dist/hooks/question-tracker.js +1 -1
  167. package/node_modules/@weavecode/server/dist/hooks/registry.js +1 -1
  168. package/node_modules/@weavecode/server/dist/hooks/resume-runner.js +1 -1
  169. package/node_modules/@weavecode/server/dist/hooks/session-purge.js +1 -1
  170. package/node_modules/@weavecode/server/dist/hooks/side-questions.js +1 -1
  171. package/node_modules/@weavecode/server/dist/hooks/synthetic-sessions.js +1 -1
  172. package/node_modules/@weavecode/server/dist/hooks/toolkit-scan.js +1 -1
  173. package/node_modules/@weavecode/server/dist/hooks/toolkit.js +5 -5
  174. package/node_modules/@weavecode/server/dist/hooks/watch-engine.js +1 -1
  175. package/node_modules/@weavecode/server/dist/install/apply-update.js +1 -1
  176. package/node_modules/@weavecode/server/dist/install/capabilities.js +1 -1
  177. package/node_modules/@weavecode/server/dist/install/codex-toml.js +1 -1
  178. package/node_modules/@weavecode/server/dist/install/file-assets.js +1 -1
  179. package/node_modules/@weavecode/server/dist/install/fingerprint.js +1 -1
  180. package/node_modules/@weavecode/server/dist/install/global-config.js +1 -1
  181. package/node_modules/@weavecode/server/dist/install/global-file-assets.js +1 -1
  182. package/node_modules/@weavecode/server/dist/install/init-files.js +2 -2
  183. package/node_modules/@weavecode/server/dist/install/install-ledger.js +1 -1
  184. package/node_modules/@weavecode/server/dist/install/installer.js +1 -1
  185. package/node_modules/@weavecode/server/dist/install/library-index.js +1 -1
  186. package/node_modules/@weavecode/server/dist/install/mcp-rows.js +1 -1
  187. package/node_modules/@weavecode/server/dist/install/plugin-install.js +1 -1
  188. package/node_modules/@weavecode/server/dist/install/plugin-rows.js +1 -1
  189. package/node_modules/@weavecode/server/dist/install/plugin-templates.js +1 -1
  190. package/node_modules/@weavecode/server/dist/install/project-config.js +1 -1
  191. package/node_modules/@weavecode/server/dist/install/reader.js +1 -1
  192. package/node_modules/@weavecode/server/dist/install/scan-cache.js +1 -1
  193. package/node_modules/@weavecode/server/dist/install/scanner.js +1 -1
  194. package/node_modules/@weavecode/server/dist/install/settings-mcp.js +2 -2
  195. package/node_modules/@weavecode/server/dist/install/updates.js +1 -1
  196. package/node_modules/@weavecode/server/dist/market/builtin.js +1 -1
  197. package/node_modules/@weavecode/server/dist/market/identity.js +1 -1
  198. package/node_modules/@weavecode/server/dist/market/import.js +2 -2
  199. package/node_modules/@weavecode/server/dist/market/refresh.js +1 -1
  200. package/node_modules/@weavecode/server/dist/market/resolve.js +1 -1
  201. package/node_modules/@weavecode/server/dist/market/updates.js +1 -1
  202. package/node_modules/@weavecode/server/dist/registry.js +2 -2
  203. package/node_modules/@weavecode/server/dist/repositories/hook-dispatches.js +5 -5
  204. package/node_modules/@weavecode/server/dist/repositories/hook-events.d.ts +1 -1
  205. package/node_modules/@weavecode/server/dist/repositories/hook-events.js +5 -5
  206. package/node_modules/@weavecode/server/dist/repositories/hook-rules.js +4 -4
  207. package/node_modules/@weavecode/server/dist/repositories/libraries.js +1 -1
  208. package/node_modules/@weavecode/server/dist/repositories/orchestrations.js +7 -7
  209. package/node_modules/@weavecode/server/dist/repositories/plugin-states.js +1 -1
  210. package/node_modules/@weavecode/server/dist/repositories/projects.js +2 -2
  211. package/node_modules/@weavecode/server/dist/repositories/push-subscriptions.js +2 -2
  212. package/node_modules/@weavecode/server/dist/repositories/session-watches.js +4 -4
  213. package/node_modules/@weavecode/server/dist/repositories/sessions.d.ts +11 -1
  214. package/node_modules/@weavecode/server/dist/repositories/sessions.js +4 -2
  215. package/node_modules/@weavecode/server/dist/repositories/uploads.d.ts +14 -4
  216. package/node_modules/@weavecode/server/dist/repositories/uploads.js +11 -11
  217. package/node_modules/@weavecode/server/dist/statusline/cache-sweeper.js +1 -1
  218. package/node_modules/@weavecode/server/dist/statusline/generator.js +1 -1
  219. package/node_modules/@weavecode/server/dist/statusline/manager.js +3 -3
  220. package/node_modules/@weavecode/server/dist/surfaces.d.ts +3 -1
  221. package/node_modules/@weavecode/server/dist/surfaces.js +1 -1
  222. package/node_modules/@weavecode/server/dist/watch/cache.js +1 -1
  223. package/node_modules/@weavecode/server/dist/watch/classifier.js +1 -1
  224. package/node_modules/@weavecode/server/dist/watch/plugin-mods.js +1 -1
  225. package/node_modules/@weavecode/server/dist/watch/sync-scheduler.js +1 -1
  226. package/node_modules/@weavecode/server/dist/watch/watch-service.js +1 -1
  227. package/node_modules/@weavecode/server/package.json +4 -4
  228. package/node_modules/@weavecode/server/web/dist/assets/{abnfDiagram-WEYOIA3C-C-f9WfD3.js → abnfDiagram-WEYOIA3C-CbIFQh0R.js} +1 -1
  229. package/node_modules/@weavecode/server/web/dist/assets/{arc-D62qulAD.js → arc-DH8-rYAa.js} +1 -1
  230. package/node_modules/@weavecode/server/web/dist/assets/{architectureDiagram-ZHVYKT53-B6ZJRpcL.js → architectureDiagram-ZHVYKT53-qnv6S0sc.js} +1 -1
  231. package/node_modules/@weavecode/server/web/dist/assets/{blockDiagram-RJAE7QA3-DOtDFeiT.js → blockDiagram-RJAE7QA3-pT5eO-cB.js} +1 -1
  232. package/node_modules/@weavecode/server/web/dist/assets/{c4Diagram-XDPPPE54-DWn3ng8X.js → c4Diagram-XDPPPE54-DHDBDMb1.js} +1 -1
  233. package/node_modules/@weavecode/server/web/dist/assets/channel-BHPeeuzN.js +1 -0
  234. package/node_modules/@weavecode/server/web/dist/assets/{chunk-2E7WHVMD-B5IIc9cn.js → chunk-2E7WHVMD-CT951PH5.js} +1 -1
  235. package/node_modules/@weavecode/server/web/dist/assets/{chunk-2Q5K7J3B-BUU9Zvt8.js → chunk-2Q5K7J3B-E5AD7_ZY.js} +1 -1
  236. package/node_modules/@weavecode/server/web/dist/assets/{chunk-4EMNKR2T-CqEGIghP.js → chunk-4EMNKR2T-DWz9qkcy.js} +1 -1
  237. package/node_modules/@weavecode/server/web/dist/assets/{chunk-55XCU76K-D36o_E2b.js → chunk-55XCU76K-D-SgWD8w.js} +1 -1
  238. package/node_modules/@weavecode/server/web/dist/assets/{chunk-5VM5RSS4-sxws8ERh.js → chunk-5VM5RSS4-ci6YzbO5.js} +1 -1
  239. package/node_modules/@weavecode/server/web/dist/assets/{chunk-JWPE2WC7-CJo_rEEb.js → chunk-JWPE2WC7-CY4mr2f4.js} +1 -1
  240. package/node_modules/@weavecode/server/web/dist/assets/{chunk-VQ5W62QI-9UlLCpsb.js → chunk-VQ5W62QI-CON8zyg8.js} +1 -1
  241. package/node_modules/@weavecode/server/web/dist/assets/{chunk-XXDRQBXY-CX9CPbWb.js → chunk-XXDRQBXY-DXXXZ4pW.js} +1 -1
  242. package/node_modules/@weavecode/server/web/dist/assets/{chunk-ZCHMQSOR-DwEfdZcF.js → chunk-ZCHMQSOR-CukvPvkM.js} +2 -2
  243. package/node_modules/@weavecode/server/web/dist/assets/{classDiagram-v2-4MYWR3UA-CFCmIyt8.js → classDiagram-v2-4MYWR3UA-GiWZ9xtF.js} +1 -1
  244. package/node_modules/@weavecode/server/web/dist/assets/{cose-bilkent-JH36ORCC-qWXeXJTn.js → cose-bilkent-JH36ORCC-D7aJ-akq.js} +1 -1
  245. package/node_modules/@weavecode/server/web/dist/assets/{cynefin-EF2NZ3EQ-CXabWQxz.js → cynefin-EF2NZ3EQ-9B__MaRM.js} +1 -1
  246. package/node_modules/@weavecode/server/web/dist/assets/{cynefinDiagram-WF24UDRP-Bzpj8Qbj.js → cynefinDiagram-WF24UDRP-B2ujZh2k.js} +1 -1
  247. package/node_modules/@weavecode/server/web/dist/assets/{dagre-NK4GIZVX-CXsL8t30.js → dagre-NK4GIZVX-nHaB6IWf.js} +1 -1
  248. package/node_modules/@weavecode/server/web/dist/assets/{diagram-CBK3MWTC-xfR-zlAD.js → diagram-CBK3MWTC-xKfjDvgr.js} +1 -1
  249. package/node_modules/@weavecode/server/web/dist/assets/{diagram-I5XIIV2D-BjMqEsXr.js → diagram-I5XIIV2D-BHT2-5mY.js} +1 -1
  250. package/node_modules/@weavecode/server/web/dist/assets/{diagram-ODIL2UCW-QZpf8KhT.js → diagram-ODIL2UCW-CckywF4W.js} +1 -1
  251. package/node_modules/@weavecode/server/web/dist/assets/{diagram-PFBEJUXE-Cw1ESWft.js → diagram-PFBEJUXE-Dpjlc7Wh.js} +1 -1
  252. package/node_modules/@weavecode/server/web/dist/assets/{diagram-RJA3I4VV-CAy2RYYZ.js → diagram-RJA3I4VV-M6WznOaW.js} +1 -1
  253. package/node_modules/@weavecode/server/web/dist/assets/{diagram-TYUNFSNR-B09WciWt.js → diagram-TYUNFSNR-DgRJ3zL1.js} +1 -1
  254. package/node_modules/@weavecode/server/web/dist/assets/{ebnfDiagram-V4P3TTPT-DuLmHG0U.js → ebnfDiagram-V4P3TTPT-BzzVN__a.js} +1 -1
  255. package/node_modules/@weavecode/server/web/dist/assets/{elk-QONTBCTQ--zzx0EVP.js → elk-QONTBCTQ-Ch6Gs9G9.js} +1 -1
  256. package/node_modules/@weavecode/server/web/dist/assets/{erDiagram-KYQIGQAA-BPDjG5vB.js → erDiagram-KYQIGQAA-COxT-Pxb.js} +1 -1
  257. package/node_modules/@weavecode/server/web/dist/assets/{flowDiagram-IUGLLWBU-DtE2M9a_.js → flowDiagram-IUGLLWBU-CJVWnTBa.js} +1 -1
  258. package/node_modules/@weavecode/server/web/dist/assets/{ganttDiagram-R7GPNTJY-B-4AaWVa.js → ganttDiagram-R7GPNTJY-BkRpWPUl.js} +1 -1
  259. package/node_modules/@weavecode/server/web/dist/assets/{gitGraphDiagram-PDYL7XHY-CYj5FGYx.js → gitGraphDiagram-PDYL7XHY-Ba8BJcvZ.js} +1 -1
  260. package/node_modules/@weavecode/server/web/dist/assets/index-CtifxEiF.css +11 -0
  261. package/node_modules/@weavecode/server/web/dist/assets/index-FwH9INgS.js +1090 -0
  262. package/node_modules/@weavecode/server/web/dist/assets/{infoDiagram-MVDRKAX5-DHYSDj-3.js → infoDiagram-MVDRKAX5-HTQ5FUNK.js} +1 -1
  263. package/node_modules/@weavecode/server/web/dist/assets/{ishikawaDiagram-HPEHV2GE-CumAEBoU.js → ishikawaDiagram-HPEHV2GE-BNjEDIZy.js} +1 -1
  264. package/node_modules/@weavecode/server/web/dist/assets/{journeyDiagram-BOSHW7O7-ES3O3X05.js → journeyDiagram-BOSHW7O7-Cao6h91a.js} +1 -1
  265. package/node_modules/@weavecode/server/web/dist/assets/{kanban-definition-FNSA4HC5-BMIrf0d9.js → kanban-definition-FNSA4HC5-CXIx2CHQ.js} +1 -1
  266. package/node_modules/@weavecode/server/web/dist/assets/{linear-DEZ5FO1r.js → linear-65WmlvQb.js} +1 -1
  267. package/node_modules/@weavecode/server/web/dist/assets/{mermaid.core-D7gm2szA.js → mermaid.core-D39fi_iR.js} +4 -4
  268. package/node_modules/@weavecode/server/web/dist/assets/{mindmap-definition-6OY57UG3-BBQP8lyN.js → mindmap-definition-6OY57UG3-DHqpz_AK.js} +1 -1
  269. package/node_modules/@weavecode/server/web/dist/assets/{pegDiagram-ECIUJNBX-DzkKsqmg.js → pegDiagram-ECIUJNBX-Cpy1nsER.js} +1 -1
  270. package/node_modules/@weavecode/server/web/dist/assets/{pieDiagram-GNMPY74T-zR3q7Qs1.js → pieDiagram-GNMPY74T-B1IyNqzr.js} +1 -1
  271. package/node_modules/@weavecode/server/web/dist/assets/{quadrantDiagram-D57K4MC3-B4xuIsMW.js → quadrantDiagram-D57K4MC3-zSM6o7mB.js} +1 -1
  272. package/node_modules/@weavecode/server/web/dist/assets/{railroadDiagram-Y72NRBL7-D2I52m9D.js → railroadDiagram-Y72NRBL7-oM4XJIwA.js} +1 -1
  273. package/node_modules/@weavecode/server/web/dist/assets/{requirementDiagram-EQYSW5RA-aJ1KfKj2.js → requirementDiagram-EQYSW5RA-CbCLBjHf.js} +1 -1
  274. package/node_modules/@weavecode/server/web/dist/assets/{sankeyDiagram-SY24JHO5-CEq16Ecf.js → sankeyDiagram-SY24JHO5-BucVk6EP.js} +1 -1
  275. package/node_modules/@weavecode/server/web/dist/assets/{sequenceDiagram-BLEHNHQU-Bc66KG9f.js → sequenceDiagram-BLEHNHQU-Dg8CYEnT.js} +1 -1
  276. package/node_modules/@weavecode/server/web/dist/assets/{sizeCapture-INFHLROL-Ddizw_UG.js → sizeCapture-INFHLROL-C51zb7Oc.js} +1 -1
  277. package/node_modules/@weavecode/server/web/dist/assets/{stateDiagram-v2-A6IESKWZ-C1eVtEX9.js → stateDiagram-v2-A6IESKWZ-BvK2l3is.js} +1 -1
  278. package/node_modules/@weavecode/server/web/dist/assets/{swimlanes-B3ENRKFQ-CCd_KlpZ.js → swimlanes-B3ENRKFQ-Ds7QOfXW.js} +1 -1
  279. package/node_modules/@weavecode/server/web/dist/assets/swimlanesDiagram-AYQRV4AK-ByBF02UJ.js +8 -0
  280. package/node_modules/@weavecode/server/web/dist/assets/{timeline-definition-JXYKGLGB-DqpBhIII.js → timeline-definition-JXYKGLGB-CadU4Qq4.js} +1 -1
  281. package/node_modules/@weavecode/server/web/dist/assets/{usecaseDiagram-WPOLBNNX-bX_jNjSt.js → usecaseDiagram-WPOLBNNX-BYuZwTDZ.js} +1 -1
  282. package/node_modules/@weavecode/server/web/dist/assets/{vennDiagram-Q3Y3IDCW-D07CUHl3.js → vennDiagram-Q3Y3IDCW-C7zgxe07.js} +1 -1
  283. package/node_modules/@weavecode/server/web/dist/assets/{wardleyDiagram-IVAIDKWA-CZ5ovcWa.js → wardleyDiagram-IVAIDKWA-DA4Otf4s.js} +1 -1
  284. package/node_modules/@weavecode/server/web/dist/assets/{xychartDiagram-4URNHDU4-CBTh52hU.js → xychartDiagram-4URNHDU4-Bs5YGwMU.js} +1 -1
  285. package/node_modules/@weavecode/server/web/dist/index.html +2 -2
  286. package/node_modules/@weavecode/server/web/dist/sw.js +14 -12
  287. package/node_modules/@weavecode/templates/package.json +2 -2
  288. package/package.json +6 -6
  289. package/node_modules/@weavecode/server/web/dist/assets/channel-CjSr3Zfg.js +0 -1
  290. package/node_modules/@weavecode/server/web/dist/assets/index-BGdTLG4q.js +0 -1031
  291. package/node_modules/@weavecode/server/web/dist/assets/index-CEpJpR7P.css +0 -11
  292. package/node_modules/@weavecode/server/web/dist/assets/swimlanesDiagram-AYQRV4AK-yIifcHaR.js +0 -8
@@ -1,5 +1,5 @@
1
1
  import { type Harness } from '@weavecode/core';
2
- import { type MessagingSurface } from '@weavecode/harness';
2
+ import { type CommandsSurface, type MessagingSurface } from '@weavecode/harness';
3
3
  import type { HookEventRepository } from '../repositories/hook-events.js';
4
4
  import type { ProjectRepository } from '../repositories/projects.js';
5
5
  import type { SessionRepository } from '../repositories/sessions.js';
@@ -143,6 +143,9 @@ export interface MessageDelivererDeps {
143
143
  hooksDir?: string;
144
144
  /** 能力面访问器(测试注入);缺省 surfaces.ts 模块级 registry。 */
145
145
  surfaceFor?: (harness: Harness) => MessagingSurface;
146
+ /** 命令面访问器(测试注入);缺省 surfaces.ts 模块级 registry——命令形态的
147
+ * 触发符表由它答(`/` 斜杠命令、`!` bash 模式),投递闸按它分流回执窗口。 */
148
+ commandsFor?: (harness: Harness) => CommandsSurface | null;
146
149
  /** 层② 终端 send-text 通道(装配侧按 TerminalBackend 能力注入;缺省无兜底)。 */
147
150
  terminal?: PromptTerminal;
148
151
  /** 冲洗节流:同一会话两次投递之间的最小间隔(毫秒);缺省 1000。Claude Code 对
@@ -159,6 +162,8 @@ export interface MessageDelivererDeps {
159
162
  ackStallReleaseMs?: number;
160
163
  /** 斜杠命令等回执的短窗口(毫秒);缺省 5s——过了仍无 UPS 即按内建命令收。 */
161
164
  commandAckGraceMs?: number;
165
+ /** 中断后读屏确认的等待(毫秒);缺省 1.2s——没看到「已中断」就补一记 Ctrl+C。 */
166
+ interruptVerifyMs?: number;
162
167
  /** 该会话此刻是否挂着阻塞式模态框(questions 槽;QuestionTracker 是唯一实现)。
163
168
  * 模态框在场时层② 的文本与回车会被它吃掉:投出去只换来一条「已发出却没回执」
164
169
  * 的假送达,回车还可能替用户按下某个选项。缺省 = 不判(测试与无 tracker 装配)。 */
@@ -206,11 +211,6 @@ export type AnswerResult = {
206
211
  status: 400 | 404 | 409;
207
212
  error: string;
208
213
  };
209
- /**
210
- * 层② 定位来源:both = pane env 优先、标题+cwd 回落(投递路径的常规口径);
211
- * locator = 只认 env;heuristic = 只按标题+cwd(中断补打用,见 interrupt)。
212
- */
213
- export type PaneTargetSource = 'both' | 'locator' | 'heuristic';
214
214
  /** 通道现况:ready = 现在就能投;unknown = 判不动(终端只剩标题/cwd 回落,被动面看不出来)。 */
215
215
  interface ChannelState<R extends string> {
216
216
  status: 'ready' | 'unavailable' | 'unknown';
@@ -223,7 +223,7 @@ export interface SessionDeliveryDiagnosis {
223
223
  inbox: ChannelState<'no-mechanism' | 'no-credentials' | 'auth-token-required'>;
224
224
  queue: ChannelState<'no-mechanism'>;
225
225
  relay: ChannelState<'no-mechanism' | 'no-peer-name'>;
226
- terminal: ChannelState<'no-backend' | 'no-pane-env'>;
226
+ terminal: ChannelState<'no-backend' | 'no-pane-env' | 'pane-env-conflict'>;
227
227
  /** 该会话有没有上报过 pane 定位 env。 */
228
228
  paneEnvReported: boolean;
229
229
  /** 队列里压着的行数。 */
@@ -231,21 +231,6 @@ export interface SessionDeliveryDiagnosis {
231
231
  /** 暂停闸是否已置(未回执超窗 / 投递失败)。 */
232
232
  stalled: boolean;
233
233
  }
234
- /**
235
- * 作答键序白名单(安全闸):逐个**原子**验形态——作答面不能变成任意键序注入
236
- * 通道(键序若打进已关的模态框会漏进输入框执行)。原子要么是宿主的键(选项
237
- * 序号 / Tab / 回车 / 下移 / 预览面的 `n` 备注键),要么是自定义输入行的文本
238
- * (无控制字符——CR/LF/ESC 混进来就成了任意键序注入,不只是打字)。原子表本身
239
- * 由 harness 规格拼装(选项 keys / textFocus / textTail),这里只验形态。
240
- */
241
- /**
242
- * 斜杠命令(宿主内建命令与 skill/插件命令都算):判据跟宿主一致——**trim 后以
243
- * `/` 开头**(命令只在最前面生效)。两者的回执行为不同:内建命令(`/compact`、
244
- * `/model`、`/effort`…)由宿主内部执行、**不发 UserPromptSubmit**,而 skill/插件
245
- * 命令会把敲的那行原样报一条 UPS(实测 0.6s)。故不能一律当「无回执」收,见
246
- * `armCommandAck` 的短窗口。
247
- */
248
- export declare function isSlashCommand(text: string): boolean;
249
234
  export declare class MessageDeliverer {
250
235
  private readonly deps;
251
236
  private readonly headless;
@@ -271,6 +256,12 @@ export declare class MessageDeliverer {
271
256
  * sessionTerminals() 报完即焚;会话再发事件(resume)会重新 notePaneEnv。
272
257
  */
273
258
  private readonly revokedPaneEnvs;
259
+ /**
260
+ * sessionId → 占着同一 pane 定位的另一个会话(fork 继承同一份 env):有值 =
261
+ * 这个会话的终端定位不可信,投递/击键一律如实拒绝,绝不打进别人的终端。
262
+ * 只在内存:重启后由下一个事件重新裁决(窗口内可能漏一次,可接受)。
263
+ */
264
+ private readonly paneEnvConflicts;
274
265
  /** 上次落盘的 pane env(hook 每次事件都上报,值没变就不重写盘)。 */
275
266
  private persistedPaneEnvs;
276
267
  /**
@@ -369,8 +360,16 @@ export declare class MessageDeliverer {
369
360
  /**
370
361
  * hook 旁路上报的会话 pane 定位 env(**每个 hook 事件覆盖刷新**:会话换 pane /
371
362
  * resume 到新窗口后,下一次事件就把定位改过来,与 inbox 凭据同一自愈口径)。
363
+ *
364
+ * **同一份 env 只归一个会话**:claude 的 fork 会让新会话继承旧会话的 pane env
365
+ * (实测 pane 0 同时挂在 256de1f1 与 fork 出的 f59a061a 名下),照着投递就会把
366
+ * 消息打进别人终端、而目标永远收不到。裁决 = 先到先得:前持有者还有活动就
367
+ * 不让位(本会话记成冲突,投递/击键如实 409);前持有者静默超过
368
+ * `PANE_CLAIM_HANDOVER_MS` 才让位(那才是真的退出了)。
372
369
  */
373
370
  notePaneEnv(sessionId: string, env: Record<string, string>): void;
371
+ /** 该会话的终端定位是否被另一个会话占着(fork 继承同一 pane);有值 = 不可投递。 */
372
+ paneEnvConflictOf(sessionId: string): string | null;
374
373
  /**
375
374
  * 记一条起点候选(weave 起会话的 spawn 回执)。只为 paneEnvSource='launch'
376
375
  * 的 harness 而来(调用侧闸):'hook' 的 harness env 精确,起点只会引入过期源。
@@ -445,21 +444,29 @@ export declare class MessageDeliverer {
445
444
  */
446
445
  answer(sessionId: string, atoms: readonly string[]): Promise<AnswerResult>;
447
446
  /**
448
- * 中断目标会话正在跑的回合(dashboard 停止按钮):往 pane 打**一记 ESC**。
449
- * 键序固定死在服务端(**不当参数收**)——这条通道一放开就是任意击键注入;
450
- * 且只打一记:claude 里 ESC 一次是中断,连打两次是开历史回退菜单。
447
+ * 中断目标会话正在跑的回合(dashboard 停止按钮):按 `INTERRUPT_KEYS` 的梯子
448
+ * 逐记试,每记之后读屏,看到「已停」就收手。
449
+ *
450
+ * **为什么梯子第一记是 Kitty 编码的 Esc**(2026-10-30 活体实测定案):claude 的
451
+ * TUI 开着 Kitty 键盘协议,手按 Esc 送出的其实是 `ESC[27u`,而裸 `0x1b` 会被
452
+ * 当成一个未完成的转义序列丢掉——这就是「TUI 里手按能停、weave 注入不停」的
453
+ * 真因。同一个正在跑的工具上逐键单测:裸 ESC 观察 8 秒无效、裸 Ctrl+C 观察
454
+ * 12 秒无效,`ESC[27u` 与 `ESC[99;5u` 各自 1 秒内落 `Interrupted`。
451
455
  *
452
- * `source` / `avoidPaneId` 只给 /interrupt 的**补打**用(缺省 = 老口径:locator
453
- * 优先、标题+cwd 回落)。宿主没反应时可指定 'heuristic' 只按标题+cwd 重打一记——
454
- * pane env 指着旧 pane(会话换了窗口)时,第一记落进的是别人的输入行,宿主
455
- * 那边毫无动静,而错误在投递侧根本看不出来(send-text 照样 exit 0)。
456
- * `avoidPaneId` 命中即**不打**(`sent:false`):落到同一个 pane 的第二记只会
457
- * 在 claude 里开出历史回退菜单。
458
- */
459
- interrupt(sessionId: string, source?: PaneTargetSource, avoidPaneId?: string): Promise<{
456
+ * 后续两记是兜底:裸 ESC(没开该协议的宿主)、Ctrl+B(实测在工具执行窗口里
457
+ * 也能直接落 `Interrupted`,纯控制字节,任何协议下都无歧义)。不再发第二记
458
+ * 裸 ESC——claude 里两记 Esc 是历史回退菜单。读屏拿不到(无读屏能力的后端)
459
+ * 就不往下打,不凭猜继续注入。键序固定死在服务端,不当参数收。
460
+ *
461
+ * 结果**只认正面证据**:屏上写着 `Interrupted` 或状态行落到 `· done` 才算停;
462
+ * 否则 `confirmed:false`,UI 把「没停」如实说出来。「没挂 esc to interrupt」
463
+ * 当判停会漏判(工具间隙那一拍屏上本来就没有它),故不作判据。
464
+ */
465
+ interrupt(sessionId: string): Promise<{
460
466
  ok: true;
461
467
  paneId: string;
462
468
  sent: boolean;
469
+ confirmed: boolean;
463
470
  } | Extract<AnswerResult, {
464
471
  ok: false;
465
472
  }>>;
@@ -482,8 +489,9 @@ export declare class MessageDeliverer {
482
489
  */
483
490
  pressEnter(sessionId: string): Promise<AnswerResult>;
484
491
  /**
485
- * 手动硬中断(Ctrl+C 键帽):往 pane 打一记 Ctrl+C。与 ESC 不同——它是宿主
486
- * TUI 的硬中断(杀当前行/退出输入态),不落转录标记,「停没停」判不了,只报送出。
492
+ * 手动硬中断(Ctrl+C 键帽):往 pane 打一记 Ctrl+C。与 ESC 同源——宿主开着
493
+ * Kitty 键盘协议,真实按键是 `ESC[99;5u`,裸 `0x03` 不被认(2026-10-30 实测:
494
+ * 手按 Ctrl+C 能停、注入裸 0x03 不能)。不落转录标记,只报送出。
487
495
  */
488
496
  pressCtrlC(sessionId: string): Promise<AnswerResult>;
489
497
  /**
@@ -573,6 +581,8 @@ export declare class MessageDeliverer {
573
581
  forgetProject(projectId: number): number;
574
582
  /** harness 归属 = 最新事件 source(open-terminal 同口径)。 */
575
583
  private surfaceOfSession;
584
+ /** 这段输入命中哪种命令形态(触发符表归能力面);null = 普通 prompt。 */
585
+ private commandModeOf;
576
586
  /**
577
587
  * mode → 实际通道。prompt = 层① 原生 || 层② 终端通道(都没有才 409);
578
588
  * auto 的忙/闲基准 = turnOpenOf,忙 → inject(无通道才拒绝,绝不无头抢占),
@@ -584,9 +594,6 @@ export declare class MessageDeliverer {
584
594
  * 与标题是否漂移无关,也覆盖从没上报过标题的会话;后端已做存在性校验)→
585
595
  * ② 回落「标题(剥 spinner 前缀,字符集含 ◐ 等输入等待态)+ 项目 cwd」启发式
586
596
  * (无定位能力的终端 / 老 hook),歧义拒绝不打。
587
- *
588
- * `source='heuristic'` 跳过 ①:只给中断的补打用——① 命中的 pane 活着但已经
589
- * 不是这个会话(换过窗口),宿主对那一记毫无反应,此时只剩「按标题认回来」一条路。
590
597
  */
591
598
  private terminalPaneFor;
592
599
  /** 层② 通道现取(装配期注入;与 settings 的 preset 无关,只有「没装」才是 null)。 */
@@ -1,3 +1,5 @@
1
- import{randomUUID as R}from"node:crypto";import{chmodSync as y,mkdirSync as w,readFileSync as v,renameSync as O,writeFileSync as b}from"node:fs";import{dirname as I,join as g}from"node:path";import{isHarness as E}from"@weavecode/core";import{turnOutputOf as B}from"@weavecode/harness";import{isImageMediaType as T}from"../repositories/uploads.js";import{messagingSurfaceFor as L}from"../surfaces.js";import{pidAliveOf as F}from"../daemon/claude-liveness.js";import{combinedLivenessOf as A}from"../daemon/liveness.js";import{HeadlessRunner as N}from"./headless-runner.js";import{hooksRoot as Q}from"./mirror.js";import{INBOX_TARGETS_FILE as G,InboxSocketRegistry as W,deliverViaInboxSocket as J}from"./inbox-socket.js";import{AckGate as H}from"./delivery/ack-gate.js";import{ANSWER_ATOM as D,ANSWER_ATOM_GAP_MS as U,ANSWER_TEXT as V,CTRL_C as $,ENTER as X,ESC as z,MODEL_NAME as Z,SHIFT_TAB as Y,effortCommand as ee,effortLevelsOf as te,locateTerminalPane as se,modelCommand as ie,sessionKeysTarget as ne}from"./delivery/keys.js";import{hasImages as re,settleDelivered as P,spawnOnce as ae,wireTextOf as oe}from"./delivery/channels.js";import{deliverHeadless as le,deliverInject as ue,resolveChannel as he}from"./delivery/send.js";import{appendQueueEntry as de,failOffline as ce,failQueuedOffline as me,sessionJudgedDead as fe}from"./delivery/queue.js";import{runRelay as pe}from"./delivery/relay.js";import{listInflightCounts as ge,listVisible as ke,sessionTerminals as Se}from"./delivery/views.js";import{readPromptbox as k,removePromptboxBySession as ye,removePromptboxEntries as f,scanQueuedSessionIds as we}from"./promptbox.js";const ve=1e3,Oe=6e4,be=3e4,Ie=9e4,Ee=5e3,Te=8,Ae=250,De=5,Pe=200;export function isSlashCommand(m){return/^\/[A-Za-z][A-Za-z0-9:_-]*(\s|$)/.test(m.trim())}const x="pane-envs.json",_="pending-origins.json",M=15*6e4;function xe(m){try{const e=JSON.parse(v(m,"utf8"));if(!Array.isArray(e))return[];const t=[];for(const s of e){if(typeof s!="object"||s===null)continue;const i=s;if(!E(i.harness)||typeof i.projectId!="number"||typeof i.env!="object"||i.env===null||typeof i.at!="string")continue;const n={};for(const[r,l]of Object.entries(i.env))typeof l=="string"&&l&&(n[r]=l);Object.keys(n).length!==0&&t.push({harness:i.harness,projectId:i.projectId,env:n,at:i.at})}return t.filter(s=>Date.now()-Date.parse(s.at)<M)}catch{return[]}}function _e(m){try{const e=JSON.parse(v(m,"utf8"));if(typeof e!="object"||e===null)return{};const t={};for(const[s,i]of Object.entries(e)){if(typeof i!="object"||i===null)continue;const n={};for(const[r,l]of Object.entries(i))typeof l=="string"&&l&&(n[r]=l);Object.keys(n).length>0&&(t[s]=n)}return t}catch{return{}}}const p=m=>new Promise(e=>setTimeout(e,m)),j=300,C=1500,Me=1e3,je=12e4;export class MessageDeliverer{deps;headless;hooksDir;sideAnswerStore;memory=new Map;queuedSessions;inboxSockets;paneEnvs;revokedPaneEnvs=new Set;persistedPaneEnvs;pendingOrigins;persistedPendingOrigins;flushScheduled=new Set;flushing=new Map;stalled=new Map;awaitingAck=new Map;dismissedIds=new Set;gate;constructor(e){this.deps=e,this.headless=e.headless??new N({spawnImpl:e.spawnImpl,killImpl:e.killImpl}),this.hooksDir=e.hooksDir??Q(),this.sideAnswerStore=e.sideAnswerStore,this.inboxSockets=new W(g(this.hooksDir,G));const t=_e(g(this.hooksDir,x));this.paneEnvs=new Map(Object.entries(t)),this.persistedPaneEnvs=JSON.stringify(t),this.pendingOrigins=xe(g(this.hooksDir,_)),this.persistedPendingOrigins=JSON.stringify(this.pendingOrigins),this.queuedSessions=we(this.hooksDir),this.gate=new H({awaitingAck:this.awaitingAck,stalled:this.stalled,memory:this.memory},{ackStallMs:e.ackStallMs??be,ackStallReleaseMs:e.ackStallReleaseMs??Ie,commandAckGraceMs:e.commandAckGraceMs??Ee},{emit:s=>this.bus(s)}),this.gate.onAutoRelease=s=>this.scheduleFlushContinuation(s)}async send(e){const t=this.deps.sessions.getBySessionId(e.toSessionId);if(!t)return{ok:!1,status:404,error:"Session not found"};const s=this.surfaceOfSession(e.toSessionId);if(!s)return{ok:!1,status:400,error:"Session has no hook events to determine its harness"};const i=e.attachments?[...e.attachments]:[];if(i.length>0){if(!s.attach)return{ok:!1,status:409,error:"This harness cannot receive attachments"};if(i.some(h=>T(h.mediaType))&&!s.attach.kinds.includes("image"))return{ok:!1,status:409,error:"This harness cannot receive images"};if(i.some(h=>!T(h.mediaType))&&!s.attach.kinds.includes("file"))return{ok:!1,status:409,error:"This harness cannot receive file attachments"}}const n=this.resolveChannel(e.mode,e.toSessionId,s);if(typeof n!="string")return{ok:!1,status:409,error:n.reason};if(t.projectId==null)return{ok:!1,status:400,error:"Session is not bound to a project"};const r=this.deps.projects.getById(t.projectId);if(!r)return{ok:!1,status:404,error:"Project not found"};if(n==="headless"&&this.headless.isBusy(e.toSessionId))return{ok:!1,status:409,error:"Session already has a running run"};const l={fromLabel:e.fromKind==="session"?`session ${e.fromSessionId??"unknown"}`:e.fromKind,at:new Date().toISOString(),mode:n},a={id:R(),sessionId:e.toSessionId,text:e.text,...i.length>0?{attachments:i}:{},mode:e.mode,oneWay:e.oneWay===!0,...e.forceTerminal===!0?{forceTerminal:!0}:{},channel:n,status:"pending",at:l.at},o=n==="prompt"&&this.blockedAhead(a.sessionId);o&&(this.queueEntry(a,r.token),this.scheduleFlushContinuation(a.sessionId));const c=o?{ok:!0,messageId:a.id,status:"pending",message:a}:n==="inject"?this.deliverInject(a,r.token,l,s):n==="headless"?this.deliverHeadless(a,r.path,l,s):await this.deliverPrompt(a,t,r,s);return this.bus({kind:"message.created",messageId:a.id,sessionId:a.sessionId,mode:a.mode,at:a.at}),c}getMessage(e){return this.memory.get(e)??null}listVisible(e){return ke(this.deps,this.hooksDir,this.memory,e,this.deps.visibleTtlMs??Oe)}hasQueued(e){return this.queuedSessions.has(e)}listInflightCounts(){return ge(this.hooksDir,this.memory)}hasTerminalChannel(){return this.terminalChannel()!=null}scheduleFlushContinuation(e){if(this.flushScheduled.has(e))return;this.flushScheduled.add(e),setTimeout(()=>{this.flushScheduled.delete(e),this.flushSession(e)},this.deps.flushGapMs??ve).unref?.()}ackGateOpen(e){return this.gate.open(e)}blockedAhead(e){return this.ackGateOpen(e)?this.queuedSessions.has(e)||this.flushing.has(e):!0}noteInboxSocket(e,t){this.inboxSockets.set(e,t)}async awaitInboxReady(e,t){const s=this.surfaceOfSession(e)?.promptInbox??null;if(!s)return!1;const i=()=>{const r=this.inboxSockets.get(e);return!!r&&(!s.requiresAuthOn(process.platform)||!!r.token)},n=Date.now()+t;for(;!i();){if(Date.now()>=n)return!1;await new Promise(r=>setTimeout(r,Ae))}return!0}lastTurnOutput(e){const t=this.deps.hookEvents.listBySession(e,De);for(let s=t.length-1;s>=0;s--){const i=t[s];let n=null;try{n=JSON.parse(i.payload)}catch{continue}const r=B(n?.raw);if(r)return{output:r,at:i.createdAt}}return null}notePaneEnv(e,t){this.paneEnvs.set(e,t),this.revokedPaneEnvs.delete(e),this.persistPaneEnvs()}notePendingOrigin(e){this.pendingOrigins=this.pendingOrigins.filter(t=>Date.now()-Date.parse(t.at)<M),this.pendingOrigins.push({...e,at:new Date().toISOString()}),this.persistPendingOrigins()}claimPendingOrigin(e,t,s){const i=this.pendingOrigins.findIndex(r=>r.harness===t&&r.projectId===s);if(i<0)return;const[n]=this.pendingOrigins.splice(i,1);this.notePaneEnv(e,n.env),this.persistPendingOrigins()}persistPaneEnvs(){const e=JSON.stringify(Object.fromEntries(this.paneEnvs));if(e===this.persistedPaneEnvs)return;const t=g(this.hooksDir,x);try{w(I(t),{recursive:!0});const s=`${t}.tmp`;b(s,`${e}
2
- `,{encoding:"utf8",mode:384}),O(s,t),y(t,384),this.persistedPaneEnvs=e}catch{}}persistPendingOrigins(){const e=JSON.stringify(this.pendingOrigins);if(e===this.persistedPendingOrigins)return;const t=g(this.hooksDir,_);try{w(I(t),{recursive:!0});const s=`${t}.tmp`;b(s,`${e}
3
- `,{encoding:"utf8",mode:384}),O(s,t),y(t,384),this.persistedPendingOrigins=e}catch{}}sessionExited(e){this.inboxSockets.forget(e),this.paneEnvs.delete(e),this.revokedPaneEnvs.add(e),this.persistPaneEnvs(),this.awaitingAck.delete(e);const t=this.deps.sessions.getBySessionId(e),s=t?.projectId==null?null:this.deps.projects.getById(t.projectId);if(!s)return;const i=k(this.hooksDir,s.token).filter(n=>n.sessionId===e);this.failQueuedOffline(e,i.filter(n=>!this.dismissedIds.has(n.id)))}async sessionTerminals(){return Se(this.deps,this.terminalChannel(),this.paneEnvs,()=>this.persistPaneEnvs(),this.revokedPaneEnvs)}diagnose(e){const t=this.surfaceOfSession(e),s=this.deps.sessions.livenessRowOf(e),i=s?F(s,this.deps.livenessOf?.()??A()):null,n=t?.promptInbox??null,r=this.inboxSockets.get(e),l=n?r?n.requiresAuthOn(process.platform)&&!r.token?{status:"unavailable",reason:"auth-token-required"}:{status:"ready",reason:null}:{status:"unavailable",reason:"no-credentials"}:{status:"unavailable",reason:"no-mechanism"},a=t?.queuedPrompt?{status:"ready",reason:null}:{status:"unavailable",reason:"no-mechanism"},o=t?.prompt?.resolve(e)??null,c=t?.prompt?o?{status:"ready",reason:null}:{status:"unavailable",reason:"no-peer-name"}:{status:"unavailable",reason:"no-mechanism"},h=this.paneEnvs.has(e),u=this.terminalChannel()?h?{status:"ready",reason:null}:{status:"unknown",reason:"no-pane-env"}:{status:"unavailable",reason:"no-backend"},d=this.deps.sessions.getBySessionId(e),S=d?.projectId==null?null:this.deps.projects.getById(d.projectId),q=S?k(this.hooksDir,S.token).filter(K=>K.sessionId===e).length:0;return{alive:i,inbox:l,queue:a,relay:c,terminal:u,paneEnvReported:h,pending:q,stalled:this.gate.isStalled(e)}}onSessionPrompt(e,t,s){const i=this.gate.acknowledge(e,t,s,{ackMinText:Te});return this.gate.hitLast&&this.queuedSessions.has(e)&&this.scheduleFlushContinuation(e),i}noteActivity(e){this.gate.noteActivity(e)&&this.queuedSessions.has(e)&&this.scheduleFlushContinuation(e)}armAckWatch(e){this.gate.armAckWatch(e)}armCommandAck(e){this.gate.armCommandAck(e,t=>{this.queuedSessions.has(t)&&this.scheduleFlushContinuation(t)})}stall(e,t,s){this.gate.stall(e,t,s)}async answer(e,t){if(t.length===0||!D.test(t[0])||t.some(i=>!D.test(i)&&!V.test(i)))return{ok:!1,status:400,error:"Invalid answer keys"};const s=await this.sessionKeysTarget(e);if(!s.ok)return s;try{for(const[i,n]of t.entries())i>0&&await p(U),await s.terminal.sendKeys(s.paneId,n);return{ok:!0}}catch(i){return{ok:!1,status:409,error:i instanceof Error?i.message:String(i)}}}async interrupt(e,t="both",s){const i=await this.sessionKeysTarget(e,"\u51FB\u952E",t);if(!i.ok)return i;if(s!=null&&i.paneId===s)return{ok:!0,paneId:i.paneId,sent:!1};try{return await i.terminal.sendKeys(i.paneId,z),{ok:!0,paneId:i.paneId,sent:!0}}catch(n){return{ok:!1,status:409,error:n instanceof Error?n.message:String(n)}}}async closePane(e){const t=await this.sessionKeysTarget(e,"\u5173\u95ED");if(!t.ok)return t;try{return await t.terminal.closePane(t.paneId),{ok:!0}}catch(s){return{ok:!1,status:409,error:s instanceof Error?s.message:String(s)}}}async cycleMode(e){const t=await this.sessionKeysTarget(e);if(!t.ok)return t;try{return await t.terminal.sendKeys(t.paneId,Y),{ok:!0}}catch(s){return{ok:!1,status:409,error:s instanceof Error?s.message:String(s)}}}async pressEnter(e){const t=await this.sessionKeysTarget(e);if(!t.ok)return t;try{return await t.terminal.sendKeys(t.paneId,X),{ok:!0}}catch(s){return{ok:!1,status:409,error:s instanceof Error?s.message:String(s)}}}async pressCtrlC(e){const t=await this.sessionKeysTarget(e);if(!t.ok)return t;try{return await t.terminal.sendKeys(t.paneId,$),{ok:!0}}catch(s){return{ok:!1,status:409,error:s instanceof Error?s.message:String(s)}}}async setModel(e,t){const s=t.trim();if(!Z.test(s))return{ok:!1,status:400,error:"\u6A21\u578B\u540D\u5F62\u6001\u4E0D\u5408\u6CD5\uFF08\u4E0D\u542B\u7A7A\u767D\u4E0E\u63A7\u5236\u5B57\u7B26\uFF0C\u2264128 \u5B57\u7B26\uFF09"};const i=await this.sessionKeysTarget(e);if(!i.ok)return i;try{return await i.terminal.sendText(i.paneId,ie(s)),{ok:!0}}catch(n){return{ok:!1,status:409,error:n instanceof Error?n.message:String(n)}}}async setEffort(e,t){const s=t.trim(),i=await this.sessionKeysTarget(e);if(!i.ok)return i;const n=te(this.deps.hookEvents,e);if(!n.includes(s))return{ok:!1,status:400,error:`\u601D\u8003\u5F3A\u5EA6\u6863\u4F4D\u4E0D\u5408\u6CD5\uFF08\u53EF\u9009\uFF1A${n.join(" / ")||"\u65E0"}\uFF09`};try{return await i.terminal.sendText(i.paneId,ee(s)),{ok:!0}}catch(r){return{ok:!1,status:409,error:r instanceof Error?r.message:String(r)}}}async askSideQuestion(e,t){if(!this.deps.sessions.getBySessionId(e))return{ok:!1,status:404,error:"Session not found"};const s=this.surfaceOfSession(e),i=s?.sideQuestion??null;if(!i)return{ok:!1,status:409,error:s?.unsupportedReasons?.sideQuestion??"\u8FD9\u4E2A harness \u6CA1\u6709\u65C1\u8DEF\u4F1A\u8BDD\u901A\u9053"};const n=i.validate(t);if(!n.ok)return{ok:!1,status:400,error:n.error};if(i.prerequisite&&this.deps.sessions.hasRunOf(e)!==!0)return{ok:!1,status:409,error:i.prerequisite};const r=await this.sessionKeysTarget(e,"\u53D1\u8D77\u65C1\u8DEF\u4F1A\u8BDD");if(!r.ok)return r;try{return i.screen?(this.pollScreenAnswer(e,r.terminal,r.paneId,i,n.text),{ok:!0,question:n.text}):(await r.terminal.sendText(r.paneId,i.command(n.text)),{ok:!0,question:n.text})}catch(l){return{ok:!1,status:409,error:l instanceof Error?l.message:String(l)}}}pollScreenAnswer(e,t,s,i,n){const r=i.screen,l=async()=>await t.getPaneText?.(s).catch(()=>null)??null;(async()=>{const a=await l()??"";await t.sendKeys(s,r.openKeys).catch(()=>{}),await p(j),await t.sendKeys(s,r.submitKey).catch(()=>{}),await p(C);let o=await l();if((!o||!r.panelOpen(o))&&(await t.sendKeys(s,r.submitKey).catch(()=>{}),await p(C),o=await l(),!o||!r.panelOpen(o)))return;await t.sendText(s,n).catch(()=>{}),await p(j),await t.sendKeys(s,r.submitKey).catch(()=>{});const c=Date.now()+je;let h=null;for(;Date.now()<c;){if(await p(Me),o=await l(),o==null)return;const d=r.extractAnswer(n,a,o);if(d.done){h=d.answer;break}}h!=null&&(this.sideAnswerStore?.answerById(e,n,h),this.bus({kind:"sideQuestion.answered",sessionId:e,at:new Date().toISOString()}));const u=await l();u&&r.panelOpen(u)&&await t.sendKeys(s,r.closeKey).catch(()=>{})})()}async sessionKeysTarget(e,t="\u51FB\u952E",s="both"){return ne(this.keysDeps(),e,t,s)}busSafe(){return{emit:e=>this.bus(e)}}channelDeps(){return{setMemory:e=>this.memory.set(e.id,e),deleteMemory:e=>this.memory.delete(e),awaitAck:(e,t)=>this.gate.await(e,t),isSlashCommand,armAckWatch:e=>this.gate.armAckWatch(e),armCommandAck:e=>this.gate.armCommandAck(e,t=>{this.queuedSessions.has(t)&&this.scheduleFlushContinuation(t)}),emit:e=>this.bus(e),...this.deps.spawnImpl?{spawnImpl:this.deps.spawnImpl}:{}}}wireTextOfEntry(e){return oe(e,t=>this.surfaceOfSession(t))}hasImagesEntry(e){return re(e)}keysDeps(){return{sessions:this.deps.sessions,projects:this.deps.projects,hookEvents:this.deps.hookEvents,terminalChannel:()=>this.terminalChannel(),terminalPaneFor:(e,t,s)=>this.terminalPaneFor(e,t,s)}}async resend(e){const t=this.memory.get(e);if(!t)return{ok:!1,status:404,error:"Message not found"};if(!(t.channel==="prompt"&&(t.status==="failed"||t.status==="delivered"&&!t.acked)))return{ok:!1,status:409,error:"Message is not resendable"};const i=this.deps.sessions.getBySessionId(t.sessionId);if(!i)return{ok:!1,status:404,error:"Session not found"};if(i.projectId==null)return{ok:!1,status:400,error:"Session is not bound to a project"};const n=this.deps.projects.getById(i.projectId);if(!n)return{ok:!1,status:404,error:"Project not found"};const r=this.surfaceOfSession(t.sessionId);if(!r)return{ok:!1,status:400,error:"Session has no hook events to determine its harness"};if(this.gate.clearStall(t.sessionId,e),t.status==="delivered"&&t.via==="terminal"&&this.terminalChannel()){const o=await this.terminalPaneFor(i,n);if(o)return this.memory.set(e,{...t,status:"delivering",stalled:!1,settledAt:null}),this.runTerminalSubmit(e,o.id),this.bus({kind:"message.created",messageId:e,sessionId:t.sessionId,mode:t.mode,at:new Date().toISOString()}),{ok:!0,messageId:e,status:"delivering",message:this.memory.get(e)}}this.memory.delete(e);const l={...t,status:"pending",stalled:!1,acked:!1,settledAt:null,reason:void 0,error:null},a=await this.deliverPrompt(l,i,n,r);return this.bus({kind:"message.created",messageId:l.id,sessionId:l.sessionId,mode:l.mode,at:new Date().toISOString()}),a}dismiss(e,t){const s=this.memory.get(t);if(s){if(s.status==="running")return{ok:!1,status:409,error:"Message is still in flight"};s.status==="delivering"&&this.gate.headOf(s.sessionId)===t&&this.flushing.delete(s.sessionId),this.memory.delete(t),this.gate.clearStall(s.sessionId,t),this.gate.release(s.sessionId,t),this.queuedSessions.has(s.sessionId)&&this.scheduleFlushContinuation(s.sessionId)}else if(!this.dropQueued(e,t))return{ok:!1,status:404,error:"Message not found"};return this.bus({kind:"message.dismissed",messageId:t,sessionId:e,at:new Date().toISOString()}),{ok:!0}}dropQueued(e,t){const s=this.deps.sessions.getBySessionId(e);if(s?.projectId==null)return!1;const i=this.deps.projects.getById(s.projectId);if(!i)return!1;const n=k(this.hooksDir,i.token);if(!n.some(r=>r.id===t&&r.sessionId===e))return!1;for(f(this.hooksDir,i.token,new Set([t])),this.dismissedIds.add(t);this.dismissedIds.size>Pe;){const r=this.dismissedIds.values().next().value;if(r===void 0)break;this.dismissedIds.delete(r)}return n.some(r=>r.sessionId===e&&r.id!==t)||this.queuedSessions.delete(e),!0}async flushSession(e){if(this.stalled.has(e)||this.flushing.has(e)||!this.queuedSessions.has(e))return;const t=Symbol(e);this.flushing.set(e,t);try{await this.flushSessionInner(e)}catch{}finally{this.flushing.get(e)===t&&this.flushing.delete(e)}}async flushSessionInner(e){if(!this.ackGateOpen(e))return;const t=this.deps.sessions.getBySessionId(e);if(t?.projectId==null)return;const s=this.deps.projects.getById(t.projectId);if(!s)return;const i=k(this.hooksDir,s.token).filter(a=>a.sessionId===e);if(i.length===0){this.queuedSessions.delete(e);return}if(this.sessionJudgedDead(e)){this.failQueuedOffline(e,i.filter(a=>!this.dismissedIds.has(a.id)));return}const n=this.surfaceOfSession(e),r=n?.prompt??null,l=this.modalBlocked(e);for(const a of i){if(this.stalled.has(e))return;if(this.dismissedIds.has(a.id))continue;const o={id:a.id,sessionId:e,text:a.text,...a.attachments&&a.attachments.length>0?{attachments:a.attachments}:{},mode:"prompt",oneWay:a.oneWay===!0,...a.forceTerminal===!0?{forceTerminal:!0}:{},via:"socket",channel:"prompt",status:"pending",at:a.at};if(!l&&this.hasImagesEntry(o)&&this.terminalChannel()){const u=await this.terminalPaneFor(t,s);if(u){f(this.hooksDir,s.token,new Set([a.id])),this.memory.set(o.id,{...o,via:"terminal",status:"delivering"}),this.awaitingAck.set(e,o.id),await this.runTerminalPrompt(o.id,u.id,this.wireTextOfEntry(o)),this.settleQueueIndex(e,s.token);return}}if(n&&await this.tryQueuedDeliver(o,n)){f(this.hooksDir,s.token,new Set([a.id])),this.settleQueueIndex(e,s.token);return}if(n&&await this.tryInboxDeliver(o,n)){f(this.hooksDir,s.token,new Set([a.id])),this.settleQueueIndex(e,s.token);return}const c=r?.resolve(e)??null,h=o.forceTerminal===!0?["terminal"]:o.oneWay===!0?["terminal","relay"]:["relay","terminal"];for(const u of h){if(u==="relay"&&r&&c){if(this.memory.set(o.id,{...o,status:"delivering",via:"relay"}),!this.runRelay(o,r,c.name)){this.memory.delete(o.id);return}this.awaitingAck.set(e,o.id),f(this.hooksDir,s.token,new Set([a.id]));break}if(u==="terminal"&&!l&&this.terminalChannel()){const d=await this.terminalPaneFor(t,s);if(d){f(this.hooksDir,s.token,new Set([a.id])),this.memory.set(o.id,{...o,via:"terminal",status:"delivering"}),this.awaitingAck.set(e,o.id),await this.runTerminalPrompt(o.id,d.id,this.wireTextOfEntry(o));break}}}if(this.awaitingAck.get(e)===o.id){this.settleQueueIndex(e,s.token);return}return}this.settleQueueIndex(e,s.token)}settleQueueIndex(e,t){k(this.hooksDir,t).every(s=>s.sessionId!==e)&&this.queuedSessions.delete(e)}async flushAll(){for(const e of[...this.queuedSessions])await this.flushSession(e)}forgetSession(e){let t=0;for(const[s,i]of this.memory)i.sessionId===e&&(this.memory.delete(s),t++);return t+=ye(this.hooksDir,e),this.queuedSessions.delete(e),this.gate.clearStall(e),this.gate.release(e),this.inboxSockets.forget(e),this.paneEnvs.delete(e),this.revokedPaneEnvs.add(e),this.persistPaneEnvs(),t}forgetProject(e){const t=this.pendingOrigins.length;this.pendingOrigins=this.pendingOrigins.filter(i=>i.projectId!==e),this.pendingOrigins.length!==t&&this.persistPendingOrigins();let s=0;for(const[i,n]of this.memory)this.deps.sessions.getBySessionId(n.sessionId)?.projectId===e&&(this.memory.delete(i),this.inboxSockets.forget(n.sessionId),this.paneEnvs.delete(n.sessionId),this.gate.clearStall(n.sessionId),this.gate.release(n.sessionId),s++);for(const i of[...this.queuedSessions])this.deps.sessions.getBySessionId(i)?.projectId===e&&(s+=this.forgetSession(i));return this.persistPaneEnvs(),s}surfaceOfSession(e){const t=this.deps.hookEvents.listBySession(e,1),s=t[t.length-1]?.source;return E(s)?this.deps.surfaceFor?.(s)??L(s):null}resolveChannel(e,t,s){return he(this.deps.sessions,()=>this.terminalChannel()!=null,e,t,s)}async terminalPaneFor(e,t,s="both"){return se(this.terminalChannel(),this.paneEnvs,e,t,s)}terminalChannel(){const e=this.deps.terminal;return e&&e.available()?e:null}modalBlocked(e){return this.deps.blockedByQuestion?.(e)===!0}async deliverPrompt(e,t,s,i){if(this.sessionJudgedDead(e.sessionId))return{ok:!0,messageId:e.id,status:"failed",message:this.failOffline(e)};const n=this.modalBlocked(e.sessionId);if(!n&&this.hasImagesEntry(e)&&this.terminalChannel()){const u=await this.terminalPaneFor(t,s);if(u)return this.memory.set(e.id,{...e,status:"delivering",via:"terminal"}),this.awaitingAck.set(e.sessionId,e.id),this.runTerminalPrompt(e.id,u.id,this.wireTextOfEntry(e)),{ok:!0,messageId:e.id,status:"delivering",message:this.memory.get(e.id)}}const r=await this.tryQueuedDeliver(e,i);if(r)return{ok:!0,messageId:e.id,status:"delivered",message:r};const l=await this.tryInboxDeliver(e,i);if(l)return{ok:!0,messageId:e.id,status:"delivered",message:l};const a=i.prompt,o=a?.resolve(e.sessionId)??null,c=e.forceTerminal===!0?["terminal"]:e.oneWay===!0?["terminal","relay"]:["relay","terminal"];for(const u of c){if(u==="relay"&&a&&o){if(this.memory.set(e.id,{...e,status:"delivering",via:"relay"}),this.runRelay(e,a,o.name))return this.awaitingAck.set(e.sessionId,e.id),{ok:!0,messageId:e.id,status:"delivering",message:this.memory.get(e.id)};this.memory.delete(e.id)}if(u==="terminal"&&!n&&this.terminalChannel()){const d=await this.terminalPaneFor(t,s);if(d)return this.memory.set(e.id,{...e,status:"delivering",via:"terminal"}),this.awaitingAck.set(e.sessionId,e.id),this.runTerminalPrompt(e.id,d.id,this.wireTextOfEntry(e)),{ok:!0,messageId:e.id,status:"delivering",message:this.memory.get(e.id)}}}if(this.sessionJudgedDead(e.sessionId))return{ok:!0,messageId:e.id,status:"failed",message:this.failOffline(e)};this.queueEntry(e,s.token);const h={...e,status:"pending"};return{ok:!0,messageId:e.id,status:"pending",message:h}}async tryQueuedDeliver(e,t){const s=t.queuedPrompt;if(!s||this.hasImagesEntry(e))return null;const{bin:i,args:n}=s.command(e.sessionId,this.wireTextOfEntry(e));this.memory.set(e.id,{...e,status:"delivering",via:"queue"});try{await ae(i,n,s.timeoutMs??3e4,this.deps.spawnImpl)}catch{return this.memory.delete(e.id),null}return P(this.channelDeps(),{...e,via:"queue"},"queue")}async tryInboxDeliver(e,t){const s=t.promptInbox,i=this.inboxSockets.get(e.sessionId);if(!s||!i||e.forceTerminal===!0)return null;this.memory.set(e.id,{...e,status:"delivering",via:"socket"});try{await J(s,i,this.wireTextOfEntry(e))}catch{return this.memory.delete(e.id),null}return P(this.channelDeps(),e,"socket")}queueEntry(e,t){de(this.hooksDir,t,e),this.queuedSessions.add(e.sessionId)}sessionJudgedDead(e,t=this.deps.livenessOf?.()??A()){return fe(this.deps.sessions,e,t)}failOffline(e){return ce(this.memory,t=>this.bus(t),e)}failQueuedOffline(e,t){me(this.deps,this.hooksDir,this.memory,s=>this.bus(s),e,t),this.queuedSessions.delete(e)}relayDeps(){return{headless:this.headless,getMemory:e=>this.memory.get(e)??null,setMemory:e=>this.memory.set(e.id,e),awaitAck:(e,t)=>this.gate.await(e,t),stall:(e,t,s)=>this.gate.stall(e,t,s),isSlashCommand,armAckWatch:e=>this.gate.armAckWatch(e),armCommandAck:(e,t)=>this.gate.armCommandAck(e,s=>{t(s),this.queuedSessions.has(s)&&this.scheduleFlushContinuation(s)}),onReleased:e=>{this.queuedSessions.has(e)&&this.scheduleFlushContinuation(e)},emit:e=>this.bus(e),...this.deps.bin?{bin:this.deps.bin}:{},...this.deps.relayTimeoutMs===void 0?{}:{relayTimeoutMs:this.deps.relayTimeoutMs}}}runRelay(e,t,s){return pe(this.relayDeps(),e,t,s,this.wireTextOfEntry(e))}async runTerminalPrompt(e,t,s){await this.runTerminal(e,i=>i.sendText(t,s))}async runTerminalSubmit(e,t){await this.runTerminal(e,s=>s.sendKeys(t,"\r"))}async runTerminal(e,t){const s=this.memory.get(e);if(!s)return;const i=this.terminalChannel();if(i)try{await t(i);const n=this.memory.get(e);if(!n)return;const r={...n,status:"delivered",settledAt:new Date().toISOString()};this.memory.set(e,r),isSlashCommand(n.text)?this.armCommandAck(e):this.armAckWatch(r),this.bus({kind:"message.delivered",messageId:e,sessionId:s.sessionId,at:new Date().toISOString()})}catch(n){const r=this.memory.get(e);if(!r)return;if(r.acked===!0){const a=new Date().toISOString();this.memory.set(e,{...r,status:"delivered",settledAt:a}),this.bus({kind:"message.delivered",messageId:e,sessionId:r.sessionId,at:a});return}const l={...r,status:"failed",error:n instanceof Error?n.message:String(n),settledAt:new Date().toISOString()};this.memory.set(e,l),this.stall(s.sessionId,l,"failed"),this.bus({kind:"message.finished",messageId:e,sessionId:s.sessionId,status:"failed",at:new Date().toISOString()})}}deliverInject(e,t,s,i){return ue({hooksDir:this.hooksDir,memory:this.memory,bus:this.busSafe()},e,t,s,i,this.wireTextOfEntry(e))}deliverHeadless(e,t,s,i){return le({memory:this.memory,headless:this.headless,bus:this.busSafe(),...this.deps.bin?{bin:this.deps.bin}:{},...this.deps.timeoutMs===void 0?{}:{timeoutMs:this.deps.timeoutMs}},e,t,s,i,this.wireTextOfEntry(e))}bus(e){try{this.deps.bus?.emit(e)}catch{}}}
1
+ import{randomUUID as K}from"node:crypto";import{chmodSync as S,mkdirSync as v,readFileSync as w,renameSync as O,writeFileSync as E}from"node:fs";import{dirname as b,join as g}from"node:path";import{isHarness as k}from"@weavecode/core";import{commandModeAt as Q,turnOutputOf as R}from"@weavecode/harness";import{isImageMediaType as D}from"../repositories/uploads.js";import{commandsSurfaceFor as N,messagingSurfaceFor as W}from"../surfaces.js";import{pidAliveOf as _}from"../daemon/claude-liveness.js";import{combinedLivenessOf as T}from"../daemon/liveness.js";import{HeadlessRunner as J}from"./headless-runner.js";import{hooksRoot as G}from"./mirror.js";import{INBOX_TARGETS_FILE as L,InboxSocketRegistry as H,deliverViaInboxSocket as $}from"./inbox-socket.js";import{AckGate as V}from"./delivery/ack-gate.js";import{ANSWER_ATOM as A,ANSWER_ATOM_GAP_MS as Y,ANSWER_TEXT as U,CTRL_B as X,CTRL_C_KITTY as z,ENTER as Z,ESC as ss,ESC_KITTY as es,MODEL_NAME as ts,SHIFT_TAB as is,effortCommand as ns,effortLevelsOf as rs,locateTerminalPane as as,modelCommand as os,sessionKeysTarget as ds}from"./delivery/keys.js";import{hasImages as us,settleDelivered as P,spawnOnce as hs,wireTextOf as ls}from"./delivery/channels.js";import{deliverHeadless as cs,deliverInject as ms,resolveChannel as fs}from"./delivery/send.js";import{appendQueueEntry as ps,failOffline as gs,failQueuedOffline as ys,sessionJudgedDead as ks}from"./delivery/queue.js";import{runRelay as Is}from"./delivery/relay.js";import{listInflightCounts as Ss,listVisible as vs,sessionTerminals as ws}from"./delivery/views.js";import{readPromptbox as y,removePromptboxBySession as Os,removePromptboxEntries as p,scanQueuedSessionIds as Es}from"./promptbox.js";const bs=1e3,Ds=6e4,Ts=3e4,As=9e4,Ps=5e3,xs=8,Cs=12e4,js=1200,Ms=/interrupted/i,Fs=/· done/,Bs=12,qs=[es,ss,X],Ks=600,Qs=250,Rs=5,Ns=200,x="pane-envs.json",C="pending-origins.json",j=15*6e4;function Ws(f){try{const s=JSON.parse(w(f,"utf8"));if(!Array.isArray(s))return[];const e=[];for(const t of s){if(typeof t!="object"||t===null)continue;const i=t;if(!k(i.harness)||typeof i.projectId!="number"||typeof i.env!="object"||i.env===null||typeof i.at!="string")continue;const n={};for(const[r,d]of Object.entries(i.env))typeof d=="string"&&d&&(n[r]=d);Object.keys(n).length!==0&&e.push({harness:i.harness,projectId:i.projectId,env:n,at:i.at})}return e.filter(t=>Date.now()-Date.parse(t.at)<j)}catch{return[]}}function _s(f){try{const s=JSON.parse(w(f,"utf8"));if(typeof s!="object"||s===null)return{};const e={};for(const[t,i]of Object.entries(s)){if(typeof i!="object"||i===null)continue;const n={};for(const[r,d]of Object.entries(i))typeof d=="string"&&d&&(n[r]=d);Object.keys(n).length>0&&(e[t]=n)}return e}catch{return{}}}const m=f=>new Promise(s=>setTimeout(s,f)),M=300,F=1500,Js=1e3,Gs=12e4;export class MessageDeliverer{deps;headless;hooksDir;sideAnswerStore;memory=new Map;queuedSessions;inboxSockets;paneEnvs;revokedPaneEnvs=new Set;paneEnvConflicts=new Map;persistedPaneEnvs;pendingOrigins;persistedPendingOrigins;flushScheduled=new Set;flushing=new Map;stalled=new Map;awaitingAck=new Map;dismissedIds=new Set;gate;constructor(s){this.deps=s,this.headless=s.headless??new J({spawnImpl:s.spawnImpl,killImpl:s.killImpl}),this.hooksDir=s.hooksDir??G(),this.sideAnswerStore=s.sideAnswerStore,this.inboxSockets=new H(g(this.hooksDir,L));const e=_s(g(this.hooksDir,x));this.paneEnvs=new Map(Object.entries(e)),this.persistedPaneEnvs=JSON.stringify(e),this.pendingOrigins=Ws(g(this.hooksDir,C)),this.persistedPendingOrigins=JSON.stringify(this.pendingOrigins),this.queuedSessions=Es(this.hooksDir),this.gate=new V({awaitingAck:this.awaitingAck,stalled:this.stalled,memory:this.memory},{ackStallMs:s.ackStallMs??Ts,ackStallReleaseMs:s.ackStallReleaseMs??As,commandAckGraceMs:s.commandAckGraceMs??Ps},{emit:t=>this.bus(t)},(t,i)=>this.commandModeOf(t,i)!==null),this.gate.onAutoRelease=t=>this.scheduleFlushContinuation(t)}async send(s){const e=this.deps.sessions.getBySessionId(s.toSessionId);if(!e)return{ok:!1,status:404,error:"Session not found"};const t=this.surfaceOfSession(s.toSessionId);if(!t)return{ok:!1,status:400,error:"Session has no hook events to determine its harness"};const i=s.attachments?[...s.attachments]:[];if(i.length>0){if(!t.attach)return{ok:!1,status:409,error:"This harness cannot receive attachments"};if(i.some(l=>D(l.mediaType))&&!t.attach.kinds.includes("image"))return{ok:!1,status:409,error:"This harness cannot receive images"};if(i.some(l=>!D(l.mediaType))&&!t.attach.kinds.includes("file"))return{ok:!1,status:409,error:"This harness cannot receive file attachments"}}const n=this.resolveChannel(s.mode,s.toSessionId,t);if(typeof n!="string")return{ok:!1,status:409,error:n.reason};if(e.projectId==null)return{ok:!1,status:400,error:"Session is not bound to a project"};const r=this.deps.projects.getById(e.projectId);if(!r)return{ok:!1,status:404,error:"Project not found"};if(n==="headless"&&this.headless.isBusy(s.toSessionId))return{ok:!1,status:409,error:"Session already has a running run"};const d={fromLabel:s.fromKind==="session"?`session ${s.fromSessionId??"unknown"}`:s.fromKind,at:new Date().toISOString(),mode:n},a={id:K(),sessionId:s.toSessionId,text:s.text,...i.length>0?{attachments:i}:{},mode:s.mode,oneWay:s.oneWay===!0,...s.forceTerminal===!0?{forceTerminal:!0}:{},channel:n,status:"pending",at:d.at},o=n==="prompt"&&this.blockedAhead(a.sessionId);o&&(this.queueEntry(a,r.token),this.scheduleFlushContinuation(a.sessionId));const h=o?{ok:!0,messageId:a.id,status:"pending",message:a}:n==="inject"?this.deliverInject(a,r.token,d,t):n==="headless"?this.deliverHeadless(a,r.path,d,t):await this.deliverPrompt(a,e,r,t);return this.bus({kind:"message.created",messageId:a.id,sessionId:a.sessionId,mode:a.mode,at:a.at}),h}getMessage(s){return this.memory.get(s)??null}listVisible(s){return vs(this.deps,this.hooksDir,this.memory,s,this.deps.visibleTtlMs??Ds,e=>this.commandModeOf(s,e)!==null)}hasQueued(s){return this.queuedSessions.has(s)}listInflightCounts(){return Ss(this.hooksDir,this.memory)}hasTerminalChannel(){return this.terminalChannel()!=null}scheduleFlushContinuation(s){this.flushScheduled.has(s)||(this.flushScheduled.add(s),setTimeout(()=>{this.flushScheduled.delete(s),this.flushSession(s)},this.deps.flushGapMs??bs).unref?.())}ackGateOpen(s){return this.gate.open(s)}blockedAhead(s){return this.ackGateOpen(s)?this.queuedSessions.has(s)||this.flushing.has(s):!0}noteInboxSocket(s,e){this.inboxSockets.set(s,e)}async awaitInboxReady(s,e){const t=this.surfaceOfSession(s)?.promptInbox??null;if(!t)return!1;const i=()=>{const r=this.inboxSockets.get(s);return!!r&&(!t.requiresAuthOn(process.platform)||!!r.token)},n=Date.now()+e;for(;!i();){if(Date.now()>=n)return!1;await new Promise(r=>setTimeout(r,Qs))}return!0}lastTurnOutput(s){const e=this.deps.hookEvents.listBySession(s,Rs);for(let t=e.length-1;t>=0;t--){const i=e[t];let n=null;try{n=JSON.parse(i.payload)}catch{continue}const r=R(n?.raw);if(r)return{output:r,at:i.createdAt}}return null}notePaneEnv(s,e){const t=JSON.stringify(e);for(const[i,n]of this.paneEnvs){if(i===s||JSON.stringify(n)!==t)continue;const r=this.deps.sessions.getBySessionId(i)?.lastSeenAt;if((r?Date.now()-Date.parse(r):Number.POSITIVE_INFINITY)<Cs){this.paneEnvs.delete(s),this.paneEnvConflicts.set(s,i),this.persistPaneEnvs();return}this.paneEnvs.delete(i),this.revokedPaneEnvs.add(i)}this.paneEnvConflicts.delete(s),this.paneEnvs.set(s,e),this.revokedPaneEnvs.delete(s),this.persistPaneEnvs()}paneEnvConflictOf(s){return this.paneEnvConflicts.get(s)??null}notePendingOrigin(s){this.pendingOrigins=this.pendingOrigins.filter(e=>Date.now()-Date.parse(e.at)<j),this.pendingOrigins.push({...s,at:new Date().toISOString()}),this.persistPendingOrigins()}claimPendingOrigin(s,e,t){const i=this.pendingOrigins.findIndex(r=>r.harness===e&&r.projectId===t);if(i<0)return;const[n]=this.pendingOrigins.splice(i,1);this.notePaneEnv(s,n.env),this.persistPendingOrigins()}persistPaneEnvs(){const s=JSON.stringify(Object.fromEntries(this.paneEnvs));if(s===this.persistedPaneEnvs)return;const e=g(this.hooksDir,x);try{v(b(e),{recursive:!0});const t=`${e}.tmp`;E(t,`${s}
2
+ `,{encoding:"utf8",mode:384}),O(t,e),S(e,384),this.persistedPaneEnvs=s}catch{}}persistPendingOrigins(){const s=JSON.stringify(this.pendingOrigins);if(s===this.persistedPendingOrigins)return;const e=g(this.hooksDir,C);try{v(b(e),{recursive:!0});const t=`${e}.tmp`;E(t,`${s}
3
+ `,{encoding:"utf8",mode:384}),O(t,e),S(e,384),this.persistedPendingOrigins=s}catch{}}sessionExited(s){this.inboxSockets.forget(s),this.paneEnvs.delete(s),this.revokedPaneEnvs.add(s),this.persistPaneEnvs(),this.awaitingAck.delete(s);const e=this.deps.sessions.getBySessionId(s),t=e?.projectId==null?null:this.deps.projects.getById(e.projectId);if(!t)return;const i=y(this.hooksDir,t.token).filter(n=>n.sessionId===s);this.failQueuedOffline(s,i.filter(n=>!this.dismissedIds.has(n.id)))}async sessionTerminals(){return ws(this.deps,this.terminalChannel(),this.paneEnvs,()=>this.persistPaneEnvs(),this.revokedPaneEnvs)}diagnose(s){const e=this.surfaceOfSession(s),t=this.deps.sessions.livenessRowOf(s),i=t?_(t,this.deps.livenessOf?.()??T()):null,n=e?.promptInbox??null,r=this.inboxSockets.get(s),d=n?r?n.requiresAuthOn(process.platform)&&!r.token?{status:"unavailable",reason:"auth-token-required"}:{status:"ready",reason:null}:{status:"unavailable",reason:"no-credentials"}:{status:"unavailable",reason:"no-mechanism"},a=e?.queuedPrompt?{status:"ready",reason:null}:{status:"unavailable",reason:"no-mechanism"},o=e?.prompt?.resolve(s)??null,h=e?.prompt?o?{status:"ready",reason:null}:{status:"unavailable",reason:"no-peer-name"}:{status:"unavailable",reason:"no-mechanism"},l=this.paneEnvs.has(s),u=this.terminalChannel()?l?{status:"ready",reason:null}:this.paneEnvConflicts.has(s)?{status:"unavailable",reason:"pane-env-conflict"}:{status:"unknown",reason:"no-pane-env"}:{status:"unavailable",reason:"no-backend"},c=this.deps.sessions.getBySessionId(s),I=c?.projectId==null?null:this.deps.projects.getById(c.projectId),B=I?y(this.hooksDir,I.token).filter(q=>q.sessionId===s).length:0;return{alive:i,inbox:d,queue:a,relay:h,terminal:u,paneEnvReported:l,pending:B,stalled:this.gate.isStalled(s)}}onSessionPrompt(s,e,t){const i=this.gate.acknowledge(s,e,t,{ackMinText:xs});return this.gate.hitLast&&this.queuedSessions.has(s)&&this.scheduleFlushContinuation(s),i}noteActivity(s){this.gate.noteActivity(s)&&this.queuedSessions.has(s)&&this.scheduleFlushContinuation(s)}armAckWatch(s){this.gate.armAckWatch(s)}armCommandAck(s){this.gate.armCommandAck(s,e=>{this.queuedSessions.has(e)&&this.scheduleFlushContinuation(e)})}stall(s,e,t){this.gate.stall(s,e,t)}async answer(s,e){if(e.length===0||!A.test(e[0])||e.some(i=>!A.test(i)&&!U.test(i)))return{ok:!1,status:400,error:"Invalid answer keys"};const t=await this.sessionKeysTarget(s);if(!t.ok)return t;try{for(const[i,n]of e.entries())i>0&&await m(Y),await t.terminal.sendKeys(t.paneId,n);return{ok:!0}}catch(i){return{ok:!1,status:409,error:i instanceof Error?i.message:String(i)}}}async interrupt(s){const e=await this.sessionKeysTarget(s,"\u51FB\u952E");if(!e.ok)return e;const t=async()=>{try{return await e.terminal.getPaneText?.(e.paneId)??null}catch{return null}},i=d=>{const a=d.split(`
4
+ `).slice(-Bs).join(`
5
+ `);return Ms.test(a)||Fs.test(a)},n=this.deps.interruptVerifyMs??js;let r=!1;for(const[d,a]of qs.entries()){try{await e.terminal.sendKeys(e.paneId,a)}catch(h){if(d===0)return{ok:!1,status:409,error:h instanceof Error?h.message:String(h)};break}await m(d===0?n:Ks);const o=await t();if(o===null)return{ok:!0,paneId:e.paneId,sent:!0,confirmed:!0};if(i(o)){r=!0;break}}return{ok:!0,paneId:e.paneId,sent:!0,confirmed:r}}async closePane(s){const e=await this.sessionKeysTarget(s,"\u5173\u95ED");if(!e.ok)return e;try{return await e.terminal.closePane(e.paneId),{ok:!0}}catch(t){return{ok:!1,status:409,error:t instanceof Error?t.message:String(t)}}}async cycleMode(s){const e=await this.sessionKeysTarget(s);if(!e.ok)return e;try{return await e.terminal.sendKeys(e.paneId,is),{ok:!0}}catch(t){return{ok:!1,status:409,error:t instanceof Error?t.message:String(t)}}}async pressEnter(s){const e=await this.sessionKeysTarget(s);if(!e.ok)return e;try{return await e.terminal.sendKeys(e.paneId,Z),{ok:!0}}catch(t){return{ok:!1,status:409,error:t instanceof Error?t.message:String(t)}}}async pressCtrlC(s){const e=await this.sessionKeysTarget(s);if(!e.ok)return e;try{return await e.terminal.sendKeys(e.paneId,z),{ok:!0}}catch(t){return{ok:!1,status:409,error:t instanceof Error?t.message:String(t)}}}async setModel(s,e){const t=e.trim();if(!ts.test(t))return{ok:!1,status:400,error:"\u6A21\u578B\u540D\u5F62\u6001\u4E0D\u5408\u6CD5\uFF08\u4E0D\u542B\u7A7A\u767D\u4E0E\u63A7\u5236\u5B57\u7B26\uFF0C\u2264128 \u5B57\u7B26\uFF09"};const i=await this.sessionKeysTarget(s);if(!i.ok)return i;try{return await i.terminal.sendText(i.paneId,os(t)),{ok:!0}}catch(n){return{ok:!1,status:409,error:n instanceof Error?n.message:String(n)}}}async setEffort(s,e){const t=e.trim(),i=await this.sessionKeysTarget(s);if(!i.ok)return i;const n=rs(this.deps.hookEvents,s);if(!n.includes(t))return{ok:!1,status:400,error:`\u601D\u8003\u5F3A\u5EA6\u6863\u4F4D\u4E0D\u5408\u6CD5\uFF08\u53EF\u9009\uFF1A${n.join(" / ")||"\u65E0"}\uFF09`};try{return await i.terminal.sendText(i.paneId,ns(t)),{ok:!0}}catch(r){return{ok:!1,status:409,error:r instanceof Error?r.message:String(r)}}}async askSideQuestion(s,e){if(!this.deps.sessions.getBySessionId(s))return{ok:!1,status:404,error:"Session not found"};const t=this.surfaceOfSession(s),i=t?.sideQuestion??null;if(!i)return{ok:!1,status:409,error:t?.unsupportedReasons?.sideQuestion??"\u8FD9\u4E2A harness \u6CA1\u6709\u65C1\u8DEF\u4F1A\u8BDD\u901A\u9053"};const n=i.validate(e);if(!n.ok)return{ok:!1,status:400,error:n.error};if(i.prerequisite&&this.deps.sessions.hasRunOf(s)!==!0)return{ok:!1,status:409,error:i.prerequisite};const r=await this.sessionKeysTarget(s,"\u53D1\u8D77\u65C1\u8DEF\u4F1A\u8BDD");if(!r.ok)return r;try{return i.screen?(this.pollScreenAnswer(s,r.terminal,r.paneId,i,n.text),{ok:!0,question:n.text}):(await r.terminal.sendText(r.paneId,i.command(n.text)),{ok:!0,question:n.text})}catch(d){return{ok:!1,status:409,error:d instanceof Error?d.message:String(d)}}}pollScreenAnswer(s,e,t,i,n){const r=i.screen,d=async()=>await e.getPaneText?.(t).catch(()=>null)??null;(async()=>{const a=await d()??"";await e.sendKeys(t,r.openKeys).catch(()=>{}),await m(M),await e.sendKeys(t,r.submitKey).catch(()=>{}),await m(F);let o=await d();if((!o||!r.panelOpen(o))&&(await e.sendKeys(t,r.submitKey).catch(()=>{}),await m(F),o=await d(),!o||!r.panelOpen(o)))return;await e.sendText(t,n).catch(()=>{}),await m(M),await e.sendKeys(t,r.submitKey).catch(()=>{});const h=Date.now()+Gs;let l=null;for(;Date.now()<h;){if(await m(Js),o=await d(),o==null)return;const c=r.extractAnswer(n,a,o);if(c.done){l=c.answer;break}}l!=null&&(this.sideAnswerStore?.answerById(s,n,l),this.bus({kind:"sideQuestion.answered",sessionId:s,at:new Date().toISOString()}));const u=await d();u&&r.panelOpen(u)&&await e.sendKeys(t,r.closeKey).catch(()=>{})})()}async sessionKeysTarget(s,e="\u51FB\u952E"){return ds(this.keysDeps(),s,e)}busSafe(){return{emit:s=>this.bus(s)}}channelDeps(){return{setMemory:s=>this.memory.set(s.id,s),deleteMemory:s=>this.memory.delete(s),awaitAck:(s,e)=>this.gate.await(s,e),commandModeOf:(s,e)=>this.commandModeOf(e,s),armAckWatch:s=>this.gate.armAckWatch(s),armCommandAck:s=>this.gate.armCommandAck(s,e=>{this.queuedSessions.has(e)&&this.scheduleFlushContinuation(e)}),emit:s=>this.bus(s),...this.deps.spawnImpl?{spawnImpl:this.deps.spawnImpl}:{}}}wireTextOfEntry(s){return ls(s,e=>this.surfaceOfSession(e))}hasImagesEntry(s){return us(s)}keysDeps(){return{sessions:this.deps.sessions,projects:this.deps.projects,hookEvents:this.deps.hookEvents,terminalChannel:()=>this.terminalChannel(),terminalPaneFor:(s,e)=>this.terminalPaneFor(s,e),paneEnvConflictOf:s=>this.paneEnvConflictOf(s)}}async resend(s){const e=this.memory.get(s);if(!e)return{ok:!1,status:404,error:"Message not found"};if(!(e.channel==="prompt"&&(e.status==="failed"||e.status==="delivered"&&!e.acked)))return{ok:!1,status:409,error:"Message is not resendable"};const t=this.deps.sessions.getBySessionId(e.sessionId);if(!t)return{ok:!1,status:404,error:"Session not found"};if(t.projectId==null)return{ok:!1,status:400,error:"Session is not bound to a project"};const i=this.deps.projects.getById(t.projectId);if(!i)return{ok:!1,status:404,error:"Project not found"};const n=this.surfaceOfSession(e.sessionId);if(!n)return{ok:!1,status:400,error:"Session has no hook events to determine its harness"};if(this.gate.clearStall(e.sessionId,s),e.status==="delivered"&&e.via==="terminal"&&this.terminalChannel()){const a=await this.terminalPaneFor(t,i);if(a)return this.memory.set(s,{...e,status:"delivering",stalled:!1,settledAt:null}),this.runTerminalSubmit(s,a.id),this.bus({kind:"message.created",messageId:s,sessionId:e.sessionId,mode:e.mode,at:new Date().toISOString()}),{ok:!0,messageId:s,status:"delivering",message:this.memory.get(s)}}this.memory.delete(s);const r={...e,status:"pending",stalled:!1,acked:!1,settledAt:null,reason:void 0,error:null},d=await this.deliverPrompt(r,t,i,n);return this.bus({kind:"message.created",messageId:r.id,sessionId:r.sessionId,mode:r.mode,at:new Date().toISOString()}),d}dismiss(s,e){const t=this.memory.get(e);if(t){if(t.status==="running")return{ok:!1,status:409,error:"Message is still in flight"};t.status==="delivering"&&this.gate.headOf(t.sessionId)===e&&this.flushing.delete(t.sessionId),this.memory.delete(e),this.gate.clearStall(t.sessionId,e),this.gate.release(t.sessionId,e),this.queuedSessions.has(t.sessionId)&&this.scheduleFlushContinuation(t.sessionId)}else if(!this.dropQueued(s,e))return{ok:!1,status:404,error:"Message not found"};return this.bus({kind:"message.dismissed",messageId:e,sessionId:s,at:new Date().toISOString()}),{ok:!0}}dropQueued(s,e){const t=this.deps.sessions.getBySessionId(s);if(t?.projectId==null)return!1;const i=this.deps.projects.getById(t.projectId);if(!i)return!1;const n=y(this.hooksDir,i.token);if(!n.some(r=>r.id===e&&r.sessionId===s))return!1;for(p(this.hooksDir,i.token,new Set([e])),this.dismissedIds.add(e);this.dismissedIds.size>Ns;){const r=this.dismissedIds.values().next().value;if(r===void 0)break;this.dismissedIds.delete(r)}return n.some(r=>r.sessionId===s&&r.id!==e)||this.queuedSessions.delete(s),!0}async flushSession(s){if(this.stalled.has(s)||this.flushing.has(s)||!this.queuedSessions.has(s))return;const e=Symbol(s);this.flushing.set(s,e);try{await this.flushSessionInner(s)}catch{}finally{this.flushing.get(s)===e&&this.flushing.delete(s)}}async flushSessionInner(s){if(!this.ackGateOpen(s))return;const e=this.deps.sessions.getBySessionId(s);if(e?.projectId==null)return;const t=this.deps.projects.getById(e.projectId);if(!t)return;const i=y(this.hooksDir,t.token).filter(a=>a.sessionId===s);if(i.length===0){this.queuedSessions.delete(s);return}if(this.sessionJudgedDead(s)){this.failQueuedOffline(s,i.filter(a=>!this.dismissedIds.has(a.id)));return}const n=this.surfaceOfSession(s),r=n?.prompt??null,d=this.modalBlocked(s);for(const a of i){if(this.stalled.has(s))return;if(this.dismissedIds.has(a.id))continue;const o={id:a.id,sessionId:s,text:a.text,...a.attachments&&a.attachments.length>0?{attachments:a.attachments}:{},mode:"prompt",oneWay:a.oneWay===!0,...a.forceTerminal===!0?{forceTerminal:!0}:{},via:"socket",channel:"prompt",status:"pending",at:a.at};if(!d&&this.hasImagesEntry(o)&&this.terminalChannel()){const u=await this.terminalPaneFor(e,t);if(u){p(this.hooksDir,t.token,new Set([a.id])),this.memory.set(o.id,{...o,via:"terminal",status:"delivering"}),this.awaitingAck.set(s,o.id),await this.runTerminalPrompt(o.id,u.id,this.wireTextOfEntry(o)),this.settleQueueIndex(s,t.token);return}}if(n&&await this.tryQueuedDeliver(o,n)){p(this.hooksDir,t.token,new Set([a.id])),this.settleQueueIndex(s,t.token);return}if(n&&await this.tryInboxDeliver(o,n)){p(this.hooksDir,t.token,new Set([a.id])),this.settleQueueIndex(s,t.token);return}const h=r?.resolve(s)??null,l=o.forceTerminal===!0?["terminal"]:o.oneWay===!0?["terminal","relay"]:["relay","terminal"];for(const u of l){if(u==="relay"&&r&&h){if(this.memory.set(o.id,{...o,status:"delivering",via:"relay"}),!this.runRelay(o,r,h.name)){this.memory.delete(o.id);return}this.awaitingAck.set(s,o.id),p(this.hooksDir,t.token,new Set([a.id]));break}if(u==="terminal"&&!d&&this.terminalChannel()){const c=await this.terminalPaneFor(e,t);if(c){p(this.hooksDir,t.token,new Set([a.id])),this.memory.set(o.id,{...o,via:"terminal",status:"delivering"}),this.awaitingAck.set(s,o.id),await this.runTerminalPrompt(o.id,c.id,this.wireTextOfEntry(o));break}}}if(this.awaitingAck.get(s)===o.id){this.settleQueueIndex(s,t.token);return}return}this.settleQueueIndex(s,t.token)}settleQueueIndex(s,e){y(this.hooksDir,e).every(t=>t.sessionId!==s)&&this.queuedSessions.delete(s)}async flushAll(){for(const s of[...this.queuedSessions])await this.flushSession(s)}forgetSession(s){let e=0;for(const[t,i]of this.memory)i.sessionId===s&&(this.memory.delete(t),e++);return e+=Os(this.hooksDir,s),this.queuedSessions.delete(s),this.gate.clearStall(s),this.gate.release(s),this.inboxSockets.forget(s),this.paneEnvs.delete(s),this.paneEnvConflicts.delete(s),this.revokedPaneEnvs.add(s),this.persistPaneEnvs(),e}forgetProject(s){const e=this.pendingOrigins.length;this.pendingOrigins=this.pendingOrigins.filter(i=>i.projectId!==s),this.pendingOrigins.length!==e&&this.persistPendingOrigins();let t=0;for(const[i,n]of this.memory)this.deps.sessions.getBySessionId(n.sessionId)?.projectId===s&&(this.memory.delete(i),this.inboxSockets.forget(n.sessionId),this.paneEnvs.delete(n.sessionId),this.gate.clearStall(n.sessionId),this.gate.release(n.sessionId),t++);for(const i of[...this.queuedSessions])this.deps.sessions.getBySessionId(i)?.projectId===s&&(t+=this.forgetSession(i));return this.persistPaneEnvs(),t}surfaceOfSession(s){const e=this.deps.hookEvents.listBySession(s,1),t=e[e.length-1]?.source;return k(t)?this.deps.surfaceFor?.(t)??W(t):null}commandModeOf(s,e){const t=this.deps.hookEvents.listBySession(s,1),i=t[t.length-1]?.source;if(!k(i))return null;const n=this.deps.commandsFor?.(i)??N(i);return n?Q(e,n.inputModes):null}resolveChannel(s,e,t){return fs(this.deps.sessions,()=>this.terminalChannel()!=null,s,e,t)}async terminalPaneFor(s,e){return this.paneEnvConflicts.has(s.sessionId)?null:as(this.terminalChannel(),this.paneEnvs,s,e)}terminalChannel(){const s=this.deps.terminal;return s&&s.available()?s:null}modalBlocked(s){return this.deps.blockedByQuestion?.(s)===!0}async deliverPrompt(s,e,t,i){if(this.sessionJudgedDead(s.sessionId))return{ok:!0,messageId:s.id,status:"failed",message:this.failOffline(s)};const n=this.modalBlocked(s.sessionId);if(!n&&this.hasImagesEntry(s)&&this.terminalChannel()){const u=await this.terminalPaneFor(e,t);if(u)return this.memory.set(s.id,{...s,status:"delivering",via:"terminal"}),this.awaitingAck.set(s.sessionId,s.id),this.runTerminalPrompt(s.id,u.id,this.wireTextOfEntry(s)),{ok:!0,messageId:s.id,status:"delivering",message:this.memory.get(s.id)}}const r=await this.tryQueuedDeliver(s,i);if(r)return{ok:!0,messageId:s.id,status:"delivered",message:r};const d=await this.tryInboxDeliver(s,i);if(d)return{ok:!0,messageId:s.id,status:"delivered",message:d};const a=i.prompt,o=a?.resolve(s.sessionId)??null,h=s.forceTerminal===!0?["terminal"]:s.oneWay===!0?["terminal","relay"]:["relay","terminal"];for(const u of h){if(u==="relay"&&a&&o){if(this.memory.set(s.id,{...s,status:"delivering",via:"relay"}),this.runRelay(s,a,o.name))return this.awaitingAck.set(s.sessionId,s.id),{ok:!0,messageId:s.id,status:"delivering",message:this.memory.get(s.id)};this.memory.delete(s.id)}if(u==="terminal"&&!n&&this.terminalChannel()){const c=await this.terminalPaneFor(e,t);if(c)return this.memory.set(s.id,{...s,status:"delivering",via:"terminal"}),this.awaitingAck.set(s.sessionId,s.id),this.runTerminalPrompt(s.id,c.id,this.wireTextOfEntry(s)),{ok:!0,messageId:s.id,status:"delivering",message:this.memory.get(s.id)}}}if(this.sessionJudgedDead(s.sessionId))return{ok:!0,messageId:s.id,status:"failed",message:this.failOffline(s)};this.queueEntry(s,t.token);const l={...s,status:"pending"};return{ok:!0,messageId:s.id,status:"pending",message:l}}async tryQueuedDeliver(s,e){const t=e.queuedPrompt;if(!t||this.hasImagesEntry(s))return null;const{bin:i,args:n}=t.command(s.sessionId,this.wireTextOfEntry(s));this.memory.set(s.id,{...s,status:"delivering",via:"queue"});try{await hs(i,n,t.timeoutMs??3e4,this.deps.spawnImpl)}catch{return this.memory.delete(s.id),null}return P(this.channelDeps(),{...s,via:"queue"},"queue")}async tryInboxDeliver(s,e){const t=e.promptInbox,i=this.inboxSockets.get(s.sessionId);if(!t||!i||s.forceTerminal===!0)return null;this.memory.set(s.id,{...s,status:"delivering",via:"socket"});try{await $(t,i,this.wireTextOfEntry(s))}catch{return this.memory.delete(s.id),null}return P(this.channelDeps(),s,"socket")}queueEntry(s,e){ps(this.hooksDir,e,s),this.queuedSessions.add(s.sessionId)}sessionJudgedDead(s,e=this.deps.livenessOf?.()??T()){return ks(this.deps.sessions,s,e)}failOffline(s){return gs(this.memory,e=>this.bus(e),s)}failQueuedOffline(s,e){ys(this.deps,this.hooksDir,this.memory,t=>this.bus(t),s,e),this.queuedSessions.delete(s)}relayDeps(){return{headless:this.headless,getMemory:s=>this.memory.get(s)??null,setMemory:s=>this.memory.set(s.id,s),awaitAck:(s,e)=>this.gate.await(s,e),stall:(s,e,t)=>this.gate.stall(s,e,t),commandModeOf:(s,e)=>this.commandModeOf(e,s),armAckWatch:s=>this.gate.armAckWatch(s),armCommandAck:(s,e)=>this.gate.armCommandAck(s,t=>{e(t),this.queuedSessions.has(t)&&this.scheduleFlushContinuation(t)}),onReleased:s=>{this.queuedSessions.has(s)&&this.scheduleFlushContinuation(s)},emit:s=>this.bus(s),...this.deps.bin?{bin:this.deps.bin}:{},...this.deps.relayTimeoutMs===void 0?{}:{relayTimeoutMs:this.deps.relayTimeoutMs}}}runRelay(s,e,t){return Is(this.relayDeps(),s,e,t,this.wireTextOfEntry(s))}async runTerminalPrompt(s,e,t){await this.runTerminal(s,i=>i.sendText(e,t))}async runTerminalSubmit(s,e){await this.runTerminal(s,t=>t.sendKeys(e,"\r"))}async runTerminal(s,e){const t=this.memory.get(s);if(!t)return;const i=this.terminalChannel();if(i)try{await e(i);const n=this.memory.get(s);if(!n)return;const r={...n,status:"delivered",settledAt:new Date().toISOString()};this.memory.set(s,r),this.commandModeOf(n.sessionId,n.text)?this.armCommandAck(s):this.armAckWatch(r),this.bus({kind:"message.delivered",messageId:s,sessionId:t.sessionId,at:new Date().toISOString()})}catch(n){const r=this.memory.get(s);if(!r)return;if(r.acked===!0){const a=new Date().toISOString();this.memory.set(s,{...r,status:"delivered",settledAt:a}),this.bus({kind:"message.delivered",messageId:s,sessionId:r.sessionId,at:a});return}const d={...r,status:"failed",error:n instanceof Error?n.message:String(n),settledAt:new Date().toISOString()};this.memory.set(s,d),this.stall(t.sessionId,d,"failed"),this.bus({kind:"message.finished",messageId:s,sessionId:t.sessionId,status:"failed",at:new Date().toISOString()})}}deliverInject(s,e,t,i){return ms({hooksDir:this.hooksDir,memory:this.memory,bus:this.busSafe()},s,e,t,i,this.wireTextOfEntry(s))}deliverHeadless(s,e,t,i){return cs({memory:this.memory,headless:this.headless,bus:this.busSafe(),...this.deps.bin?{bin:this.deps.bin}:{},...this.deps.timeoutMs===void 0?{}:{timeoutMs:this.deps.timeoutMs}},s,e,t,i,this.wireTextOfEntry(s))}bus(s){try{this.deps.bus?.emit(s)}catch{}}}
@@ -1,5 +1,5 @@
1
- import{createHash as l}from"node:crypto";import{mkdirSync as a,readFileSync as h,renameSync as f,writeFileSync as p}from"node:fs";import{join as n}from"node:path";import{hookSourceHarness as u}from"@weavecode/core";import{getDataDir as m,getHooksDir as y}from"../db/paths.js";import{projectLabel as j}from"./match.js";export const MIRROR_VERSION=1,OUTBOX_MAX_LINES=200;export function hookShardId(i){return l("sha256").update(i).digest("hex").slice(0,16)}export function hooksRoot(i){return y(i)}export function mirrorPathOf(i,r){return n(i,"mirror",`${r}.json`)}export function outboxPathOf(i,r){return n(i,"outbox",`${r}.jsonl`)}export function buildMirrorDoc(i,r,e){const o=r.filter(t=>!(t.source.kind!=="hook-event"||t.handler.type!=="inject"||!t.enabled||t.projectId!==null&&t.projectId!==i.id||t.filter.crossProjectOnly)).sort((t,s)=>s.priority-t.priority).map(t=>({id:t.id,name:t.name,harness:u(t.source)??"claude",events:[t.source.kind==="hook-event"?t.source.event:""],tools:t.filter.toolNames?.length?[...t.filter.toolNames]:null,globs:t.filter.pathGlobs?.length?[...t.filter.pathGlobs]:null,template:t.handler.type==="inject"?t.handler.template:""}));return{version:MIRROR_VERSION,project:j(i.path),pendingTitles:e,rules:o}}export class MirrorWriter{deps;dataDir;constructor(r){this.deps=r,this.dataDir=r.dataDir??m()}syncAll(){for(const r of this.deps.projects.list())this.syncProject(r.id)}syncProject(r){const e=this.deps.projects.getById(r);if(!e||e.archivedAt)return;const o={};for(const t of this.deps.sessions.list(r))t.pendingTitle&&(o[t.sessionId]=t.pendingTitle);this.write(e.token,buildMirrorDoc(e,this.deps.rules.list(),o))}write(r,e){try{const o=mirrorPathOf(hooksRoot(this.dataDir),hookShardId(r));a(n(hooksRoot(this.dataDir),"mirror"),{recursive:!0});const t=`${o}.tmp`;p(t,JSON.stringify(e)),f(t,o)}catch{}}attachToBus(r){r.subscribe(e=>{if(e.kind!=="session.renamed"||!e.sessionId)return;const o=this.deps.sessions.getBySessionId(e.sessionId);o?.projectId!=null&&this.syncProject(o.projectId)})}}export function appendInbox(i,r,e){const o=n(i,"inbox");a(o,{recursive:!0}),p(n(o,`${hookShardId(r)}.jsonl`),JSON.stringify({v:1,token:r,report:e})+`
2
- `,{flag:"a"})}export function appendOutbox(i,r,e){if(e.length!==0)try{const o=outboxPathOf(i,hookShardId(r));a(n(i,"outbox"),{recursive:!0});let t=[];try{t=h(o,"utf8").split(`
3
- `).filter(c=>c.trim())}catch{t=[]}for(const c of e)t.push(JSON.stringify({...c,at:new Date().toISOString()}));const s=t.slice(-OUTBOX_MAX_LINES),d=`${o}.tmp`;p(d,s.length?s.join(`
1
+ import{createHash as h}from"node:crypto";import{mkdirSync as a,readFileSync as u,renameSync as d,writeFileSync as l}from"node:fs";import{join as i}from"node:path";import{hookSourceHarness as f}from"@weavecode/core";import{getDataDir as m,getHooksDir as y}from"../db/paths.js";import{projectLabel as j}from"./match.js";export const MIRROR_VERSION=1,OUTBOX_MAX_LINES=200;export function hookShardId(s){return h("sha256").update(s).digest("hex").slice(0,16)}export function hooksRoot(s){return y(s)}export function mirrorPathOf(s,e){return i(s,"mirror",`${e}.json`)}export function outboxPathOf(s,e){return i(s,"outbox",`${e}.jsonl`)}export function buildMirrorDoc(s,e,o){const r=e.filter(t=>!(t.source.kind!=="hook-event"||t.handler.type!=="inject"||!t.enabled||t.projectId!==null&&t.projectId!==s.id||t.filter.crossProjectOnly)).sort((t,n)=>n.priority-t.priority).map(t=>({id:t.id,name:t.name,harness:f(t.source)??"claude",events:[t.source.kind==="hook-event"?t.source.event:""],tools:t.filter.toolNames?.length?[...t.filter.toolNames]:null,globs:t.filter.pathGlobs?.length?[...t.filter.pathGlobs]:null,template:t.handler.type==="inject"?t.handler.template:""}));return{version:MIRROR_VERSION,project:j(s.path),pendingTitles:o,rules:r}}export class MirrorWriter{deps;dataDir;constructor(e){this.deps=e,this.dataDir=e.dataDir??m()}syncAll(){for(const e of this.deps.projects.list())this.syncProject(e.id)}syncProject(e){const o=this.deps.projects.getById(e);if(!o||o.archivedAt)return;const r={};for(const t of this.deps.sessions.list(e))t.pendingTitle&&(r[t.sessionId]=t.pendingTitle);this.write(o.token,buildMirrorDoc(o,this.deps.rules.list(),r))}write(e,o){try{const r=mirrorPathOf(hooksRoot(this.dataDir),hookShardId(e));a(i(hooksRoot(this.dataDir),"mirror"),{recursive:!0});const t=`${r}.tmp`;l(t,JSON.stringify(o)),d(t,r)}catch{}}attachToBus(e){e.subscribe(o=>{if(o.kind!=="session.renamed"||!o.sessionId)return;const r=this.deps.sessions.getBySessionId(o.sessionId);r?.projectId!=null&&this.syncProject(r.projectId)})}}export function appendInbox(s,e,o){const r=i(s,"inbox");a(r,{recursive:!0}),l(i(r,`${hookShardId(e)}.jsonl`),JSON.stringify({v:1,token:e,report:o})+`
2
+ `,{flag:"a"})}export function appendOutbox(s,e,o){if(o.length!==0)try{const r=outboxPathOf(s,hookShardId(e));a(i(s,"outbox"),{recursive:!0});let t=[];try{t=u(r,"utf8").split(`
3
+ `).filter(c=>c.trim())}catch{t=[]}for(const c of o)t.push(JSON.stringify({...c,at:new Date().toISOString()}));const n=t.slice(-OUTBOX_MAX_LINES),p=`${r}.tmp`;l(p,n.length?n.join(`
4
4
  `)+`
5
- `:""),f(d,o)}catch{}}
5
+ `:""),d(p,r)}catch{}}
@@ -1,11 +1,11 @@
1
- import{randomUUID as S}from"node:crypto";import{graphDirections as m,pendingCountsOf as v}from"../repositories/orchestrations.js";import{messagingSurfaceFor as _}from"../surfaces.js";import{matchesMinute as b,minuteKey as E,parseCron as A}from"../daemon/cron.js";import{pidAliveOf as y}from"../daemon/claude-liveness.js";import{combinedLivenessOf as w}from"../daemon/liveness.js";import{postJson as C}from"./handlers/webhook.js";import{renderTemplate as R}from"./match.js";import{ORCH_MAX_DELIVERIES as I,ORCH_MAX_FORKS as p,validateOrchestration as k}from"./orchestration-graph.js";const N=5e3;function $(l,e){const t=l.trim();return t?`${t}
1
+ import{randomUUID as D}from"node:crypto";import{graphDirections as m,pendingCountsOf as v}from"../repositories/orchestrations.js";import{messagingSurfaceFor as S}from"../surfaces.js";import{matchesMinute as C,minuteKey as $,parseCron as F}from"../daemon/cron.js";import{pidAliveOf as y}from"../daemon/claude-liveness.js";import{combinedLivenessOf as I}from"../daemon/liveness.js";import{postJson as E}from"./handlers/webhook.js";import{renderTemplate as B}from"./match.js";import{ORCH_MAX_DELIVERIES as w,ORCH_MAX_FORKS as p,validateOrchestration as k}from"./orchestration-graph.js";const A=5e3;function j(h,e){const t=h.trim();return t?`${t}
2
2
 
3
- ${e}`:e}const O=4e3,g=32e3;function T(l){if(l.length<=g)return{text:l,truncated:!1};const e=g/2;return{text:`${l.slice(0,e)}
4
- \u2026[weave: ${l.length-g} chars truncated]\u2026
5
- ${l.slice(-e)}`,truncated:!0}}function x(l,e){return R(l,{upstream:e.upstream,summary:e.summary,session:e.session})}const M=/\{(upstream|summary|session)\}/,F=new Set(["yes","y","true","\u662F","\u5BF9\u7684","\u5BF9","\u9700\u8981","\u53D1"]),j=new Set(["no","n","false","\u5426","\u4E0D\u5BF9","\u4E0D\u9700\u8981","\u4E0D\u53D1"]);export function parseConditionAnswer(l){const t=((l??"").trim().toLowerCase().split(/\s+/)[0]??"").replace(/[.。!!??]/g,"");return F.has(t)?!0:j.has(t)?!1:null}export function createForkConditionJudge(l){const e=t=>t===null?{ok:!1,source:"fork",reason:"condition could not be evaluated (no clear yes/no answer) \u2014 delivery blocked"}:t?{ok:!0,source:"fork"}:{ok:!1,source:"fork",reason:"condition answered no \u2014 delivery skipped"};return{async judge({sessionId:t,condition:n}){if(!l.supports(t))return{ok:!1,source:"fork",reason:"condition not evaluable \u2014 harness has no context sub-agent face"};const s=await l.extract({sessionId:t,prompt:`${n}
3
+ ${e}`:e}const x=4e3,g=32e3;function P(h){if(h.length<=g)return{text:h,truncated:!1};const e=g/2;return{text:`${h.slice(0,e)}
4
+ \u2026[weave: ${h.length-g} chars truncated]\u2026
5
+ ${h.slice(-e)}`,truncated:!0}}function O(h,e){return B(h,{upstream:e.upstream,summary:e.summary,session:e.session})}const M=/\{(upstream|summary|session)\}/,R=new Set(["yes","y","true","\u662F","\u5BF9\u7684","\u5BF9","\u9700\u8981","\u53D1"]),J=new Set(["no","n","false","\u5426","\u4E0D\u5BF9","\u4E0D\u9700\u8981","\u4E0D\u53D1"]);export function parseConditionAnswer(h){const e=((h??"").trim().toLowerCase().split(/\s+/)[0]??"").replace(/[.。!!??]/g,"");return R.has(e)?!0:J.has(e)?!1:null}export function createForkConditionJudge(h){const e=t=>t===null?{ok:!1,source:"fork",reason:"condition could not be evaluated (no clear yes/no answer) \u2014 delivery blocked"}:t?{ok:!0,source:"fork"}:{ok:!1,source:"fork",reason:"condition answered no \u2014 delivery skipped"};return{async judge({sessionId:t,condition:n}){if(!h.supports(t))return{ok:!1,source:"fork",reason:"condition not evaluable \u2014 harness has no context sub-agent face"};const s=await h.extract({sessionId:t,prompt:`${n}
6
6
 
7
- Based on the conversation context above, answer with exactly one word: yes or no.`});return e(parseConditionAnswer(s))},async judgeBatch({sessionId:t,items:n}){if(!l.supports(t))return n.map(()=>({ok:!1,source:"fork",reason:"condition not evaluable \u2014 harness has no context sub-agent face"}));const s=n.map((o,d)=>`${d+1}. key=${o.key}: ${o.condition}`).join(`
8
- `),r=await l.extract({sessionId:t,prompt:`For EACH item below, decide yes/no based on the conversation context.
7
+ Based on the conversation context above, answer with exactly one word: yes or no.`});return e(parseConditionAnswer(s))},async judgeBatch({sessionId:t,items:n}){if(!h.supports(t))return n.map(()=>({ok:!1,source:"fork",reason:"condition not evaluable \u2014 harness has no context sub-agent face"}));const s=n.map((d,u)=>`${u+1}. key=${d.key}: ${d.condition}`).join(`
8
+ `),r=await h.extract({sessionId:t,prompt:`For EACH item below, decide yes/no based on the conversation context.
9
9
  ${s}
10
10
 
11
- Respond with ONLY a JSON object mapping each key to true or false, e.g. {"k1":true,"k2":false}. No other text.`}),i=B(r,n.map(o=>o.key));return i?n.map(o=>{const d=i.get(o.key);return e(d===void 0?null:d)}):n.map(()=>e(null))}}}function B(l,e){if(!l)return null;const t=l.match(/\{[\s\S]*\}/);if(!t)return null;try{const n=JSON.parse(t[0]),s=new Map;for(const r of e){const i=n[r];if(typeof i!="boolean")return null;s.set(r,i)}return s}catch{return null}}export function matchWhen(l,e){const t=(e??"").toLowerCase();return l.split(/[,,\n;;]/).map(n=>n.trim().toLowerCase()).filter(n=>n.length>0).some(n=>t.includes(n))}const P=5e3,U=1e4,J=6e4,L=2*36e5,W=1500,Y=9e4;export class OrchestrationEngine{deps;runtime=new Map;opening=new Map;startedByOrchestration=new Map;forkJudge;constructor(e){this.deps=e,this.forkJudge=createForkConditionJudge({supports:t=>e.supportsExtract?.(t)??!1,extract:t=>e.extract?.(t)??Promise.resolve(null)})}async start(e,t,n,s){const r=this.deps.orchestrations.getById(e);if(!r)return{ok:!1,status:404,error:"Orchestration not found"};const i=k(r);if(!i.ok)return{ok:!1,status:409,error:i.error};const{entries:o}=m(r.nodes,r.edges),d=s?o.filter(f=>s.includes(f.id)):o.filter(f=>f.kind==="session");if(d.length===0)return{ok:!1,status:409,error:"No matching entry node"};const u=this.activeOf(e);if(u)return await Promise.allSettled(d.map(f=>f.kind==="session"?this.deliver(u,{from:"",to:f.id,edgeId:"",text:n?.trim()||r.startText.trim()||r.name}):this.fireEdges(u,f.id,n??""))),this.maybeDone(u),{ok:!0,runId:u.id};const c={id:this.deps.runs.insert({orchestrationId:r.id,orchestrationName:r.name,projectId:r.projectId,trigger:t}).id,orchestrationId:r.id,trigger:t,name:r.name,nodes:r.nodes,edges:r.edges,state:{pending:{},events:[]},deliveries:0,forks:0,inflight:0,running:!0,finishing:!1};return this.runtime.set(c.id,c),this.emitRun(c,"running"),await Promise.allSettled(d.map(f=>f.kind==="session"?this.deliver(c,{from:"",to:f.id,edgeId:"",text:n?.trim()||r.startText.trim()||r.name}):this.fireEdges(c,f.id,n??""))),this.maybeDone(c),{ok:!0,runId:c.id}}cancel(e){const t=this.runtime.get(e);return t?this.finish(t,"cancelled"):!1}onEvent(e){try{if(!(this.deps.surfaceFor?.(e.source)??_(e.source)).idleSignals.includes(e.eventType))return;const n=this.hasPendingAnywhere(e.sessionId);for(const s of[...this.runtime.values()]){const r=s.state.pending[e.sessionId];!s.running||!r||r.length===0||(r.shift(),this.persist(s),this.fireEdges(s,e.sessionId))}if(!n){const s=Date.now(),r=this.startedByOrchestration.get(e.sessionId);r!==void 0&&s-r<J?this.startedByOrchestration.delete(e.sessionId):(this.startedByOrchestration.delete(e.sessionId),this.maybeAutoFire(e.sessionId))}}catch{}}hasPendingAnywhere(e){return[...this.runtime.values()].some(t=>(t.state.pending[e]?.length??0)>0)}maybeAutoFire(e){const t=Date.now();for(const n of this.entryIndex().get(e)??[]){if(!n.enabled||this.activeOf(n.id))continue;const s=`${n.id}:${e}`;if(t-(this.lastAutoFireAt.get(s)??0)<P)continue;const r=n.nodes.find(i=>i.id===e&&i.kind==="session");!r||!n.edges.some(i=>i.from===r.id)||(this.lastAutoFireAt.set(s,t),this.fireFromEntry(n,r.id))}}async fireFromEntry(e,t){if(!k(e).ok)return;const r={id:this.deps.runs.insert({orchestrationId:e.id,orchestrationName:e.name,projectId:e.projectId,trigger:"manual"}).id,orchestrationId:e.id,trigger:"manual",name:e.name,nodes:e.nodes,edges:e.edges,state:{pending:{},events:[]},deliveries:0,forks:0,inflight:0,running:!0,finishing:!1};this.runtime.set(r.id,r),this.emitRun(r,"running"),await this.fireEdges(r,t),this.maybeDone(r)}entryIndexCache=null;lastAutoFireAt=new Map;entryIndex(){if(this.entryIndexCache&&Date.now()-this.entryIndexCache.at<U)return this.entryIndexCache.map;const e=new Map;for(const t of this.deps.orchestrations.list()){if(!t.enabled)continue;const n=new Set(m(t.nodes,t.edges).entries.map(s=>s.id));for(const s of t.nodes){if(s.kind!=="session"||!n.has(s.id)&&!s.autoFire)continue;const r=e.get(s.id)??[];r.push(t),e.set(s.id,r)}}return this.entryIndexCache={at:Date.now(),map:e},e}cronTick(e=new Date){let t=0;try{const n=E(e);for(const s of this.deps.orchestrations.list()){if(!s.enabled||!s.cron||s.lastCronAt===n)continue;const r=A(s.cron);if(!r||!b(r,e))continue;const{entries:i}=m(s.nodes,s.edges);i.some(o=>o.kind==="session")&&(this.deps.orchestrations.setCronFired(s.id,n),!this.activeOf(s.id)&&(this.start(s.id,"cron"),t++))}}catch{}return t}rearm(){try{for(const e of this.deps.runs.listRunning()){const t=this.deps.orchestrations.getById(e.orchestrationId);if(!(t?k(t):{ok:!1}).ok){this.deps.runs.finish(e.id,"interrupted","daemon restarted \u2014 definition gone");continue}const s={id:e.id,orchestrationId:e.orchestrationId,trigger:e.trigger,name:e.orchestrationName,nodes:t.nodes,edges:t.edges,state:e.state,deliveries:e.deliveries,forks:0,inflight:0,running:!0,finishing:!1};this.runtime.set(s.id,s),this.emitRun(s,"running")}}catch{}try{this.deps.runs.prune()}catch{}}sweepTerminated(){let e=0;try{const t=this.deps.livenessOf?.()??w(),n=new Map(this.deps.sessions.listWithCounts().map(r=>[r.sessionId,r])),s=Date.now();for(const r of[...this.runtime.values()])if(r.running)for(const[i,o]of Object.entries(r.state.pending)){if(o.length===0)continue;const d=n.get(i);if(d&&y(d,t)!==!1){const u=Date.parse(o[0].at);Number.isFinite(u)&&s-u>L&&(this.staleOut(r,i,o[0].at),e++);continue}this.reviveOrDrop(r,i),e++}}catch{return e}return e}staleOut(e,t,n){const s=((Date.now()-Date.parse(n))/36e5).toFixed(1);this.dropPending(e,t,`pending stale ${s}h (oldest ${n}) \u2014 target alive but never turned; run failed to release the single-run gate`),this.finish(e,"failed",`pending stale on ${t} (${s}h) \u2014 run aborted`)}ensureOpenOnce(e){const t=this.opening.get(e);if(t)return t;const n=(this.deps.ensureOpen?.(e)??Promise.resolve({ok:!0})).finally(()=>this.opening.delete(e));return this.opening.set(e,n),n}async reviveOrDrop(e,t){const n=await this.ensureOpen(t);n.ok||!e.running||(this.dropPending(e,t,`pending dropped \u2014 session is not running: ${n.error}`),this.maybeDone(e))}async fireEdges(e,t,n=""){const s=e.edges.filter(a=>a.from===t);if(s.length===0){this.maybeDone(e);return}const r=n||(this.deps.deliverer.lastTurnOutput?.(t)?.output??null),i=r?r.length>O?`${r.slice(0,O)}\u2026`:r:null,o=r?T(r):{text:null,truncated:!1},d=this.deps.sessions.getBySessionId(t)?.title||t,u=new Map;if(!n){const a=s.filter(c=>c.condition?.trim()&&!(c.when?.trim()&&!matchWhen(c.when,o.text)));if(a.length>0){const c=await this.evaluateConditionsBatch(e,t,a,o.text,o.truncated);for(const[f,h]of c)u.set(f,h)}}await Promise.allSettled(s.map(async a=>{if(n){const h=x(a.text,{upstream:n,summary:n,session:t}),D=a.text.includes("{upstream}")?h:$(h,n);return this.deliver(e,{from:t,to:a.to,edgeId:a.id,text:D})}let c=null;if(a.extract?.trim()&&this.takeFork(e)&&(c=this.deps.supportsExtract?.(t)?await this.deps.extract?.({sessionId:t,prompt:a.extract.trim()})??null:null),a.when?.trim()&&!matchWhen(a.when,o.text)){this.record(e,{from:t,to:a.to,edgeId:a.id,ok:!1,stage:"condition",error:"pre-filter no match \u2014 upstream output lacks all keywords"});return}if(a.condition?.trim()){const h=u.get(a.id);if(h!==!0){h===void 0&&this.record(e,{from:t,to:a.to,edgeId:a.id,ok:!1,stage:"condition",error:`fork budget exhausted (${p}) \u2014 condition not evaluated`});return}}const f=x(a.text,{upstream:i,summary:c??i,session:d});if(M.test(f)){this.record(e,{from:t,to:a.to,edgeId:a.id,ok:!1,error:"unresolved template variable \u2014 no upstream output and no extract result"});return}return this.deliver(e,{from:t,to:a.to,edgeId:a.id,text:f})})),this.maybeDone(e)}async evaluateConditionsBatch(e,t,n,s,r){const i=new Map,o=this.deps.conditionJudge??this.forkJudge;if(!this.takeFork(e)){for(const d of n)this.record(e,{from:t,to:d.to,edgeId:d.id,ok:!1,stage:"condition",error:`fork budget exhausted (${p}) \u2014 condition not evaluated`});return i}if(o.judgeBatch&&n.length>1)try{const d=await o.judgeBatch({sessionId:t,items:n.map(u=>({key:u.id,condition:u.condition.trim()})),upstream:s});return n.forEach((u,a)=>{const c=d[a];c&&(i.set(u.id,c.ok),this.record(e,{from:t,to:u.to,edgeId:u.id,ok:c.ok,stage:"condition",...c.ok?{}:{error:c.reason??"condition not satisfied \u2014 delivery blocked"},...c.source?{source:c.source}:{},...this.conditionDetail(c.detail,r)}))}),i}catch{}for(const d of n)i.set(d.id,await this.evaluateCondition(e,t,d,s,r));return i}conditionDetail(e,t){const n=[e,t?`state truncated to ${g} chars`:null].filter(Boolean);return n.length>0?{detail:n.join(" \u2014 ")}:{}}async evaluateCondition(e,t,n,s,r=!1){const i=a=>(this.record(e,{from:t,to:n.to,edgeId:n.id,ok:!1,stage:"condition",error:a,...o?{source:o}:{},...this.conditionDetail(d,r)}),!1);let o,d,u;try{u=await(this.deps.conditionJudge??this.forkJudge).judge({sessionId:t,condition:n.condition.trim(),upstream:s}),o=u.source,d=u.detail}catch(a){return i(`condition judge failed \u2014 delivery blocked: ${a instanceof Error?a.message:String(a)}`)}return u.ok?(this.record(e,{from:t,to:n.to,edgeId:n.id,ok:!0,stage:"condition",...o?{source:o}:{},...this.conditionDetail(d,r)}),!0):i(u.reason??"condition not satisfied \u2014 delivery blocked")}async deliver(e,t){const n=await this.deliverOnce(e,t);return n||!e.running?n:(await new Promise(s=>setTimeout(s,this.deps.retryDelayMs??W)),e.running?this.deliverOnce(e,t):!1)}async deliverOnce(e,t){e.inflight++;try{if(!e.running)return!1;const n=e.nodes.find(i=>i.id===t.to);if(!n)return this.record(e,{...t,ok:!1,error:"target node is not in the snapshot"}),!1;if(n.kind==="webhook"){const i=await(this.deps.postJsonImpl??C)(n.url??"",n.secret,{orchestrationName:e.name,runId:e.id,from:t.from,text:t.text,at:new Date().toISOString()});return e.running?(e.deliveries++,this.record(e,{...t,ok:i.ok,...i.ok?{}:{error:i.error}}),this.checkBudget(e),i.ok):!1}if(!t.text.trim())return this.record(e,{...t,ok:!1,error:"edge has no message"}),!1;if(this.deps.deferWhenUserPresent&&this.userPresent(t.to)){const i=e.state.pending[t.to]??[];return i.push({messageId:`defer-${S()}`,at:new Date().toISOString()}),e.state.pending[t.to]=i,this.record(e,{from:t.from,to:t.to,edgeId:t.edgeId,ok:!0,stage:"defer"}),!0}if(this.aliveOf(t.to)===!1){const i=Date.now(),o=await this.ensureOpenOnce(t.to);if(!e.running)return!1;if(this.record(e,{from:"",to:t.to,edgeId:t.edgeId,ok:o.ok,stage:"ensure",ms:Date.now()-i,...o.ok?{}:{error:o.error}}),!o.ok)return this.record(e,{...t,ok:!1,error:`target session is not open: ${o.error}`}),!1;if(this.startedByOrchestration.set(t.to,Date.now()),await this.deps.deliverer.awaitInboxReady?.(t.to,N),!e.running)return!1}const s=await this.deps.deliverer.send({toSessionId:t.to,text:t.text,mode:"prompt",fromKind:"dashboard"});if(!e.running)return!1;if(!s.ok)return this.record(e,{...t,ok:!1,error:s.error}),!1;if(s.status==="failed"){const i=s.message;return this.record(e,{...t,ok:!1,error:i.error??(i.reason==="offline"?"target session is offline":"delivery failed")}),!1}const r=e.state.pending[t.to]??[];return r.push({messageId:s.messageId,at:new Date().toISOString()}),e.state.pending[t.to]=r,e.deliveries++,this.record(e,{...t,ok:!0}),this.checkBudget(e),!0}catch(n){return this.record(e,{...t,ok:!1,error:n instanceof Error?n.message:String(n)}),!1}finally{e.inflight--}}checkBudget(e){e.deliveries>=(this.deps.maxDeliveries??I)&&this.finish(e,"failed",`delivery budget exceeded (${I})`)}takeFork(e){return e.forks>=p?!1:(e.forks++,!0)}record(e,t){if(!e.running)return;const n={at:new Date().toISOString(),from:t.from,to:t.to,edgeId:t.edgeId,ok:t.ok,...t.error?{error:t.error}:{},...t.stage?{stage:t.stage}:{},...t.ms===void 0?{}:{ms:t.ms},...t.source?{source:t.source}:{},...t.detail?{detail:t.detail}:{}};e.state.events.push(n),e.state.events.length>200&&e.state.events.splice(0,e.state.events.length-200),this.persist(e),this.deps.bus.emit({kind:"orchestration.delivery",orchestrationId:e.orchestrationId,runId:e.id,edgeId:t.edgeId,from:t.from,to:t.to,ok:t.ok,...t.error?{error:t.error}:{},deliveries:e.deliveries,pendingCounts:v(e.state),at:n.at})}maybeDone(e){if(!e.running||e.finishing||e.inflight>0||Object.values(e.state.pending).some(s=>s.length>0))return;e.finishing=!0;const t=e.state.events.some(s=>s.ok&&s.stage===void 0);if(!t&&e.state.events.length>0&&e.state.events.every(s=>s.stage==="condition")){this.finish(e,"done");return}const n=[...e.state.events].reverse().find(s=>!s.ok)?.error??null;this.finish(e,t?"done":"failed",t?null:n??"no delivery succeeded")}finish(e,t,n=null){if(!e.running||(e.running=!1,!this.deps.runs.finish(e.id,t,n,e.state)))return!1;this.runtime.delete(e.id),this.emitRun(e,t);try{this.deps.runs.prune()}catch{}return!0}persist(e){this.deps.runs.updateState(e.id,e.state,e.deliveries),this.emitRun(e,"running")}emitRun(e,t){this.deps.bus.emit({kind:"orchestration.run",orchestrationId:e.orchestrationId,runId:e.id,status:t,trigger:e.trigger,deliveries:e.deliveries,pendingCounts:v(e.state),at:new Date().toISOString()})}dropPending(e,t,n){delete e.state.pending[t],this.record(e,{from:"",to:t,edgeId:"",ok:!1,error:n})}activeOf(e){return[...this.runtime.values()].find(t=>t.running&&t.orchestrationId===e)}aliveOf(e){const t=this.deps.sessions.listWithCounts().find(s=>s.sessionId===e);if(!t)return!1;const n=this.deps.livenessOf?.()??w();return y(t,n)}userPresent(e){if(this.deps.sessions.turnOpenOf(e))return!1;const t=this.deps.sessions.listWithCounts().find(s=>s.sessionId===e);if(!t)return!1;const n=Date.parse(t.lastSeenAt);return Number.isFinite(n)&&Date.now()-n<Y}async ensureOpen(e){if(!this.deps.ensureOpen)return{ok:!1,error:"terminal auto-open unavailable"};try{return await this.deps.ensureOpen(e)}catch(t){return{ok:!1,error:t instanceof Error?t.message:String(t)}}}}
11
+ Respond with ONLY a JSON object mapping each key to true or false, e.g. {"k1":true,"k2":false}. No other text.`}),i=N(r,n.map(d=>d.key));return i?n.map(d=>{const u=i.get(d.key);return e(u===void 0?null:u)}):n.map(()=>e(null))}}}function N(h,e){if(!h)return null;const t=h.match(/\{[\s\S]*\}/);if(!t)return null;try{const n=JSON.parse(t[0]),s=new Map;for(const r of e){const i=n[r];if(typeof i!="boolean")return null;s.set(r,i)}return s}catch{return null}}export function matchWhen(h,e){const t=(e??"").toLowerCase();return h.split(/[,,\n;;]/).map(n=>n.trim().toLowerCase()).filter(n=>n.length>0).some(n=>t.includes(n))}const T=5e3,L=1e4,W=6e4,_=2*36e5,U=1500,H=9e4;export class OrchestrationEngine{deps;runtime=new Map;opening=new Map;startedByOrchestration=new Map;forkJudge;constructor(e){this.deps=e,this.forkJudge=createForkConditionJudge({supports:t=>e.supportsExtract?.(t)??!1,extract:t=>e.extract?.(t)??Promise.resolve(null)})}async start(e,t,n,s){const r=this.deps.orchestrations.getById(e);if(!r)return{ok:!1,status:404,error:"Orchestration not found"};const i=k(r);if(!i.ok)return{ok:!1,status:409,error:i.error};const{entries:d}=m(r.nodes,r.edges),u=s?d.filter(a=>s.includes(a.id)):d.filter(a=>a.kind==="session");if(u.length===0)return{ok:!1,status:409,error:"No matching entry node"};const c=this.activeOf(e);if(c)return await Promise.allSettled(u.map(a=>a.kind==="session"?this.deliver(c,{from:"",to:a.id,edgeId:"",text:n?.trim()||r.startText.trim()||r.name}):this.fireEdges(c,a.id,n??""))),this.maybeDone(c),{ok:!0,runId:c.id};const o={id:this.deps.runs.insert({orchestrationId:r.id,orchestrationName:r.name,projectId:r.projectId,trigger:t}).id,orchestrationId:r.id,trigger:t,name:r.name,nodes:r.nodes,edges:r.edges,state:{pending:{},events:[]},deliveries:0,forks:0,inflight:0,running:!0,finishing:!1};return this.runtime.set(o.id,o),this.emitRun(o,"running"),await Promise.allSettled(u.map(a=>a.kind==="session"?this.deliver(o,{from:"",to:a.id,edgeId:"",text:n?.trim()||r.startText.trim()||r.name}):this.fireEdges(o,a.id,n??""))),this.maybeDone(o),{ok:!0,runId:o.id}}cancel(e){const t=this.runtime.get(e);return t?this.finish(t,"cancelled"):!1}onEvent(e){try{if(!(this.deps.surfaceFor?.(e.source)??S(e.source)).idleSignals.includes(e.eventType))return;const t=this.hasPendingAnywhere(e.sessionId);for(const n of[...this.runtime.values()]){const s=n.state.pending[e.sessionId];!n.running||!s||s.length===0||(s.shift(),this.persist(n),this.fireEdges(n,e.sessionId))}if(!t){const n=Date.now(),s=this.startedByOrchestration.get(e.sessionId);s!==void 0&&n-s<W?this.startedByOrchestration.delete(e.sessionId):(this.startedByOrchestration.delete(e.sessionId),this.maybeAutoFire(e.sessionId))}}catch{}}hasPendingAnywhere(e){return[...this.runtime.values()].some(t=>(t.state.pending[e]?.length??0)>0)}maybeAutoFire(e){const t=Date.now();for(const n of this.entryIndex().get(e)??[]){if(!n.enabled||this.activeOf(n.id))continue;const s=`${n.id}:${e}`;if(t-(this.lastAutoFireAt.get(s)??0)<T)continue;const r=n.nodes.find(i=>i.id===e&&i.kind==="session");!r||!n.edges.some(i=>i.from===r.id)||(this.lastAutoFireAt.set(s,t),this.fireFromEntry(n,r.id))}}async fireFromEntry(e,t){if(!k(e).ok)return;const n={id:this.deps.runs.insert({orchestrationId:e.id,orchestrationName:e.name,projectId:e.projectId,trigger:"manual"}).id,orchestrationId:e.id,trigger:"manual",name:e.name,nodes:e.nodes,edges:e.edges,state:{pending:{},events:[]},deliveries:0,forks:0,inflight:0,running:!0,finishing:!1};this.runtime.set(n.id,n),this.emitRun(n,"running"),await this.fireEdges(n,t),this.maybeDone(n)}entryIndexCache=null;lastAutoFireAt=new Map;entryIndex(){if(this.entryIndexCache&&Date.now()-this.entryIndexCache.at<L)return this.entryIndexCache.map;const e=new Map;for(const t of this.deps.orchestrations.list()){if(!t.enabled)continue;const n=new Set(m(t.nodes,t.edges).entries.map(s=>s.id));for(const s of t.nodes){if(s.kind!=="session"||!n.has(s.id)&&!s.autoFire)continue;const r=e.get(s.id)??[];r.push(t),e.set(s.id,r)}}return this.entryIndexCache={at:Date.now(),map:e},e}cronTick(e=new Date){let t=0;try{const n=$(e);for(const s of this.deps.orchestrations.list()){if(!s.enabled||!s.cron||s.lastCronAt===n)continue;const r=F(s.cron);if(!r||!C(r,e))continue;const{entries:i}=m(s.nodes,s.edges);i.some(d=>d.kind==="session")&&(this.deps.orchestrations.setCronFired(s.id,n),!this.activeOf(s.id)&&(this.start(s.id,"cron"),t++))}}catch{}return t}rearm(){try{for(const e of this.deps.runs.listRunning()){const t=this.deps.orchestrations.getById(e.orchestrationId);if(!(t?k(t):{ok:!1}).ok){this.deps.runs.finish(e.id,"interrupted","daemon restarted \u2014 definition gone");continue}const n={id:e.id,orchestrationId:e.orchestrationId,trigger:e.trigger,name:e.orchestrationName,nodes:t.nodes,edges:t.edges,state:e.state,deliveries:e.deliveries,forks:0,inflight:0,running:!0,finishing:!1};this.runtime.set(n.id,n),this.emitRun(n,"running")}}catch{}try{this.deps.runs.prune()}catch{}}sweepTerminated(){let e=0;try{const t=this.deps.livenessOf?.()??I(),n=new Map(this.deps.sessions.listWithCounts().map(r=>[r.sessionId,r])),s=Date.now();for(const r of[...this.runtime.values()])if(r.running)for(const[i,d]of Object.entries(r.state.pending)){if(d.length===0)continue;const u=n.get(i);if(u&&y(u,t)!==!1){const c=Date.parse(d[0].at);Number.isFinite(c)&&s-c>_&&(this.staleOut(r,i,d[0].at),e++);continue}this.reviveOrDrop(r,i),e++}}catch{return e}return e}staleOut(e,t,n){const s=((Date.now()-Date.parse(n))/36e5).toFixed(1);this.dropPending(e,t,`pending stale ${s}h (oldest ${n}) \u2014 target alive but never turned; run failed to release the single-run gate`),this.finish(e,"failed",`pending stale on ${t} (${s}h) \u2014 run aborted`)}ensureOpenOnce(e){const t=this.opening.get(e);if(t)return t;const n=(this.deps.ensureOpen?.(e)??Promise.resolve({ok:!0})).finally(()=>this.opening.delete(e));return this.opening.set(e,n),n}async reviveOrDrop(e,t){const n=await this.ensureOpen(t);n.ok||!e.running||(this.dropPending(e,t,`pending dropped \u2014 session is not running: ${n.error}`),this.maybeDone(e))}async fireEdges(e,t,n=""){const s=e.edges.filter(o=>o.from===t);if(s.length===0){this.maybeDone(e);return}const r=n||(this.deps.deliverer.lastTurnOutput?.(t)?.output??null),i=r?r.length>x?`${r.slice(0,x)}\u2026`:r:null,d=r?P(r):{text:null,truncated:!1},u=this.deps.sessions.getBySessionId(t)?.title||t,c=new Map;if(!n){const o=s.filter(a=>a.condition?.trim()&&!(a.when?.trim()&&!matchWhen(a.when,d.text)));if(o.length>0){const a=await this.evaluateConditionsBatch(e,t,o,d.text,d.truncated);for(const[f,l]of a)c.set(f,l)}}await Promise.allSettled(s.map(async o=>{if(n){const l=O(o.text,{upstream:n,summary:n,session:t}),b=o.text.includes("{upstream}")?l:j(l,n);return this.deliver(e,{from:t,to:o.to,edgeId:o.id,text:b})}let a=null;if(o.extract?.trim()&&this.takeFork(e)&&(a=this.deps.supportsExtract?.(t)?await this.deps.extract?.({sessionId:t,prompt:o.extract.trim()})??null:null),o.when?.trim()&&!matchWhen(o.when,d.text)){this.record(e,{from:t,to:o.to,edgeId:o.id,ok:!1,stage:"condition",error:"pre-filter no match \u2014 upstream output lacks all keywords"});return}if(o.condition?.trim()){const l=c.get(o.id);if(l!==!0){l===void 0&&this.record(e,{from:t,to:o.to,edgeId:o.id,ok:!1,stage:"condition",error:`fork budget exhausted (${p}) \u2014 condition not evaluated`});return}}const f=O(o.text,{upstream:i,summary:a??i,session:u});if(M.test(f)){this.record(e,{from:t,to:o.to,edgeId:o.id,ok:!1,error:"unresolved template variable \u2014 no upstream output and no extract result"});return}return this.deliver(e,{from:t,to:o.to,edgeId:o.id,text:f})})),this.maybeDone(e)}async evaluateConditionsBatch(e,t,n,s,r){const i=new Map,d=this.deps.conditionJudge??this.forkJudge;if(!this.takeFork(e)){for(const u of n)this.record(e,{from:t,to:u.to,edgeId:u.id,ok:!1,stage:"condition",error:`fork budget exhausted (${p}) \u2014 condition not evaluated`});return i}if(d.judgeBatch&&n.length>1)try{const u=await d.judgeBatch({sessionId:t,items:n.map(c=>({key:c.id,condition:c.condition.trim()})),upstream:s});return n.forEach((c,o)=>{const a=u[o];a&&(i.set(c.id,a.ok),this.record(e,{from:t,to:c.to,edgeId:c.id,ok:a.ok,stage:"condition",...a.ok?{}:{error:a.reason??"condition not satisfied \u2014 delivery blocked"},...a.source?{source:a.source}:{},...this.conditionDetail(a.detail,r)}))}),i}catch{}for(const u of n)i.set(u.id,await this.evaluateCondition(e,t,u,s,r));return i}conditionDetail(e,t){const n=[e,t?`state truncated to ${g} chars`:null].filter(Boolean);return n.length>0?{detail:n.join(" \u2014 ")}:{}}async evaluateCondition(e,t,n,s,r=!1){const i=o=>(this.record(e,{from:t,to:n.to,edgeId:n.id,ok:!1,stage:"condition",error:o,...d?{source:d}:{},...this.conditionDetail(u,r)}),!1);let d,u,c;try{c=await(this.deps.conditionJudge??this.forkJudge).judge({sessionId:t,condition:n.condition.trim(),upstream:s}),d=c.source,u=c.detail}catch(o){return i(`condition judge failed \u2014 delivery blocked: ${o instanceof Error?o.message:String(o)}`)}return c.ok?(this.record(e,{from:t,to:n.to,edgeId:n.id,ok:!0,stage:"condition",...d?{source:d}:{},...this.conditionDetail(u,r)}),!0):i(c.reason??"condition not satisfied \u2014 delivery blocked")}async deliver(e,t){const n=await this.deliverOnce(e,t);return n||!e.running?n:(await new Promise(s=>setTimeout(s,this.deps.retryDelayMs??U)),e.running?this.deliverOnce(e,t):!1)}async deliverOnce(e,t){e.inflight++;try{if(!e.running)return!1;const n=e.nodes.find(i=>i.id===t.to);if(!n)return this.record(e,{...t,ok:!1,error:"target node is not in the snapshot"}),!1;if(n.kind==="webhook"){const i=await(this.deps.postJsonImpl??E)(n.url??"",n.secret,{orchestrationName:e.name,runId:e.id,from:t.from,text:t.text,at:new Date().toISOString()});return e.running?(e.deliveries++,this.record(e,{...t,ok:i.ok,...i.ok?{}:{error:i.error}}),this.checkBudget(e),i.ok):!1}if(!t.text.trim())return this.record(e,{...t,ok:!1,error:"edge has no message"}),!1;if(this.deps.deferWhenUserPresent&&this.userPresent(t.to)){const i=e.state.pending[t.to]??[];return i.push({messageId:`defer-${D()}`,at:new Date().toISOString()}),e.state.pending[t.to]=i,this.record(e,{from:t.from,to:t.to,edgeId:t.edgeId,ok:!0,stage:"defer"}),!0}if(this.aliveOf(t.to)===!1){const i=Date.now(),d=await this.ensureOpenOnce(t.to);if(!e.running)return!1;if(this.record(e,{from:"",to:t.to,edgeId:t.edgeId,ok:d.ok,stage:"ensure",ms:Date.now()-i,...d.ok?{}:{error:d.error}}),!d.ok)return this.record(e,{...t,ok:!1,error:`target session is not open: ${d.error}`}),!1;if(this.startedByOrchestration.set(t.to,Date.now()),await this.deps.deliverer.awaitInboxReady?.(t.to,A),!e.running)return!1}const s=await this.deps.deliverer.send({toSessionId:t.to,text:t.text,mode:"prompt",fromKind:"dashboard"});if(!e.running)return!1;if(!s.ok)return this.record(e,{...t,ok:!1,error:s.error}),!1;if(s.status==="failed"){const i=s.message;return this.record(e,{...t,ok:!1,error:i.error??(i.reason==="offline"?"target session is offline":"delivery failed")}),!1}const r=e.state.pending[t.to]??[];return r.push({messageId:s.messageId,at:new Date().toISOString()}),e.state.pending[t.to]=r,e.deliveries++,this.record(e,{...t,ok:!0}),this.checkBudget(e),!0}catch(n){return this.record(e,{...t,ok:!1,error:n instanceof Error?n.message:String(n)}),!1}finally{e.inflight--}}checkBudget(e){e.deliveries>=(this.deps.maxDeliveries??w)&&this.finish(e,"failed",`delivery budget exceeded (${w})`)}takeFork(e){return e.forks>=p?!1:(e.forks++,!0)}record(e,t){if(!e.running)return;const n={at:new Date().toISOString(),from:t.from,to:t.to,edgeId:t.edgeId,ok:t.ok,...t.error?{error:t.error}:{},...t.stage?{stage:t.stage}:{},...t.ms===void 0?{}:{ms:t.ms},...t.source?{source:t.source}:{},...t.detail?{detail:t.detail}:{}};e.state.events.push(n),e.state.events.length>200&&e.state.events.splice(0,e.state.events.length-200),this.persist(e),this.deps.bus.emit({kind:"orchestration.delivery",orchestrationId:e.orchestrationId,runId:e.id,edgeId:t.edgeId,from:t.from,to:t.to,ok:t.ok,...t.error?{error:t.error}:{},deliveries:e.deliveries,pendingCounts:v(e.state),at:n.at})}maybeDone(e){if(!e.running||e.finishing||e.inflight>0||Object.values(e.state.pending).some(s=>s.length>0))return;e.finishing=!0;const t=e.state.events.some(s=>s.ok&&s.stage===void 0);if(!t&&e.state.events.length>0&&e.state.events.every(s=>s.stage==="condition")){this.finish(e,"done");return}const n=[...e.state.events].reverse().find(s=>!s.ok)?.error??null;this.finish(e,t?"done":"failed",t?null:n??"no delivery succeeded")}finish(e,t,n=null){if(!e.running||(e.running=!1,!this.deps.runs.finish(e.id,t,n,e.state)))return!1;this.runtime.delete(e.id),this.emitRun(e,t);try{this.deps.runs.prune()}catch{}return!0}persist(e){this.deps.runs.updateState(e.id,e.state,e.deliveries),this.emitRun(e,"running")}emitRun(e,t){this.deps.bus.emit({kind:"orchestration.run",orchestrationId:e.orchestrationId,runId:e.id,status:t,trigger:e.trigger,deliveries:e.deliveries,pendingCounts:v(e.state),at:new Date().toISOString()})}dropPending(e,t,n){delete e.state.pending[t],this.record(e,{from:"",to:t,edgeId:"",ok:!1,error:n})}activeOf(e){return[...this.runtime.values()].find(t=>t.running&&t.orchestrationId===e)}aliveOf(e){const t=this.deps.sessions.listWithCounts().find(s=>s.sessionId===e);if(!t)return!1;const n=this.deps.livenessOf?.()??I();return y(t,n)}userPresent(e){if(this.deps.sessions.turnOpenOf(e))return!1;const t=this.deps.sessions.listWithCounts().find(s=>s.sessionId===e);if(!t)return!1;const n=Date.parse(t.lastSeenAt);return Number.isFinite(n)&&Date.now()-n<H}async ensureOpen(e){if(!this.deps.ensureOpen)return{ok:!1,error:"terminal auto-open unavailable"};try{return await this.deps.ensureOpen(e)}catch(t){return{ok:!1,error:t instanceof Error?t.message:String(t)}}}}
@@ -1 +1 @@
1
- import{graphDirections as p}from"../repositories/orchestrations.js";export const ORCH_MAX_NODES=24,ORCH_MAX_EDGES=64,ORCH_MAX_DELIVERIES=200,ORCH_MAX_FORKS=50;export function validateOrchestration(d){const{nodes:n,edges:t}=d;if(n.length===0)return{ok:!1,error:"At least one node is required"};if(n.length>ORCH_MAX_NODES)return{ok:!1,error:`At most ${ORCH_MAX_NODES} nodes per orchestration`};const s=new Set;for(const e of n){if(s.has(e.id))return{ok:!1,error:`Duplicate node ${e.id}`};if(s.add(e.id),e.kind==="webhook"&&!e.id.startsWith("w:"))return{ok:!1,error:`Webhook node id must start with "w:": ${e.id}`};if(e.kind==="trigger"&&!e.id.startsWith("t:"))return{ok:!1,error:`Trigger node id must start with "t:": ${e.id}`}}if(t.length>ORCH_MAX_EDGES)return{ok:!1,error:`At most ${ORCH_MAX_EDGES} edges per orchestration`};const i=new Set,a=new Set;for(const e of t){if(i.has(e.id))return{ok:!1,error:`Duplicate edge id ${e.id}`};if(i.add(e.id),!s.has(e.from)||!s.has(e.to))return{ok:!1,error:`Edge ${e.id} references a node outside the node set`};if(e.from===e.to)return{ok:!1,error:`Self-loop on ${e.from}`};const h=`${e.from}->${e.to}`;if(a.has(h))return{ok:!1,error:`Duplicate edge ${e.from}\u2192${e.to}`};a.add(h)}const{entries:o}=p(n,t),r=new Set(t.map(e=>e.to)),f=new Set(t.map(e=>e.from)),c=new Set(o.filter(e=>e.kind==="webhook").map(e=>e.id)),l=t.find(e=>!e.text.trim()&&!c.has(e.from)&&!e.extract?.trim());if(l)return{ok:!1,error:`Edge ${l.from}\u2192${l.to} has no message`};for(const e of t)if(c.has(e.from)&&(e.condition?.trim()||e.when?.trim()))return{ok:!1,error:`Edge ${e.from}\u2192${e.to} cannot have a condition/pre-filter (webhook entries have no session to ask)`};for(const e of n)if(e.kind==="webhook"&&e.autoFire)return{ok:!1,error:`Webhook node ${e.id} cannot have autoFire`};for(const e of n)if(e.kind==="webhook"){if(r.has(e.id)&&f.has(e.id))return{ok:!1,error:`Webhook node ${e.id} cannot have both inbound and outbound edges`};if(!r.has(e.id)&&!f.has(e.id))return{ok:!1,error:`Webhook node ${e.id} is not connected`}}for(const e of n)if(e.kind==="trigger"){if(r.has(e.id))return{ok:!1,error:`Trigger node ${e.id} cannot have inbound edges`};if(!f.has(e.id))return{ok:!1,error:`Trigger node ${e.id} is not connected`}}for(const e of n){if(e.kind!=="webhook"||!r.has(e.id))continue;const h=e.url??"";let u=null;try{u=new URL(h)}catch{u=null}if(!u||u.protocol!=="http:"&&u.protocol!=="https:")return{ok:!1,error:`Webhook node ${e.id} needs a valid http(s) URL`}}const k=findCycle(n,t);return k?{ok:!1,error:`Cycle detected: ${k.join(" \u2192 ")}`}:{ok:!0}}export function findCycle(d,n){const t=new Map;for(const o of d)t.set(o.id,[]);for(const o of n){const r=t.get(o.from);r&&r.push(o.to)}const s=new Map,i=[],a=o=>{s.set(o,1),i.push(o);for(const r of t.get(o)??[]){const f=s.get(r)??0;if(f===1)return[...i.slice(i.indexOf(r)),r];if(f===0){const c=a(r);if(c)return c}}return i.pop(),s.set(o,2),null};for(const o of d)if((s.get(o.id)??0)===0){const r=a(o.id);if(r)return r}return null}export function ranksOf(d,n){const t=new Map(d.map(o=>[o.id,0])),s=new Map(d.map(o=>[o.id,[]]));for(const o of n)s.get(o.from)?.push(o.to),t.set(o.to,(t.get(o.to)??0)+1);const i=new Map(d.map(o=>[o.id,0])),a=d.filter(o=>(t.get(o.id)??0)===0).map(o=>o.id);for(let o=0;o<a.length;o++){const r=a[o];for(const f of s.get(r)??[]){i.set(f,Math.max(i.get(f)??0,(i.get(r)??0)+1));const c=(t.get(f)??0)-1;t.set(f,c),c===0&&a.push(f)}}return i}
1
+ import{graphDirections as g}from"../repositories/orchestrations.js";export const ORCH_MAX_NODES=24,ORCH_MAX_EDGES=64,ORCH_MAX_DELIVERIES=200,ORCH_MAX_FORKS=50;export function validateOrchestration(f){const{nodes:n,edges:r}=f;if(n.length===0)return{ok:!1,error:"At least one node is required"};if(n.length>ORCH_MAX_NODES)return{ok:!1,error:`At most ${ORCH_MAX_NODES} nodes per orchestration`};const i=new Set;for(const o of n){if(i.has(o.id))return{ok:!1,error:`Duplicate node ${o.id}`};if(i.add(o.id),o.kind==="webhook"&&!o.id.startsWith("w:"))return{ok:!1,error:`Webhook node id must start with "w:": ${o.id}`};if(o.kind==="trigger"&&!o.id.startsWith("t:"))return{ok:!1,error:`Trigger node id must start with "t:": ${o.id}`}}if(r.length>ORCH_MAX_EDGES)return{ok:!1,error:`At most ${ORCH_MAX_EDGES} edges per orchestration`};const s=new Set,a=new Set;for(const o of r){if(s.has(o.id))return{ok:!1,error:`Duplicate edge id ${o.id}`};if(s.add(o.id),!i.has(o.from)||!i.has(o.to))return{ok:!1,error:`Edge ${o.id} references a node outside the node set`};if(o.from===o.to)return{ok:!1,error:`Self-loop on ${o.from}`};const u=`${o.from}->${o.to}`;if(a.has(u))return{ok:!1,error:`Duplicate edge ${o.from}\u2192${o.to}`};a.add(u)}const{entries:e}=g(n,r),t=new Set(r.map(o=>o.to)),d=new Set(r.map(o=>o.from)),c=new Set(e.filter(o=>o.kind==="webhook").map(o=>o.id)),k=r.find(o=>!o.text.trim()&&!c.has(o.from)&&!o.extract?.trim());if(k)return{ok:!1,error:`Edge ${k.from}\u2192${k.to} has no message`};for(const o of r)if(c.has(o.from)&&(o.condition?.trim()||o.when?.trim()))return{ok:!1,error:`Edge ${o.from}\u2192${o.to} cannot have a condition/pre-filter (webhook entries have no session to ask)`};for(const o of n)if(o.kind==="webhook"&&o.autoFire)return{ok:!1,error:`Webhook node ${o.id} cannot have autoFire`};for(const o of n)if(o.kind==="webhook"){if(t.has(o.id)&&d.has(o.id))return{ok:!1,error:`Webhook node ${o.id} cannot have both inbound and outbound edges`};if(!t.has(o.id)&&!d.has(o.id))return{ok:!1,error:`Webhook node ${o.id} is not connected`}}for(const o of n)if(o.kind==="trigger"){if(t.has(o.id))return{ok:!1,error:`Trigger node ${o.id} cannot have inbound edges`};if(!d.has(o.id))return{ok:!1,error:`Trigger node ${o.id} is not connected`}}for(const o of n){if(o.kind!=="webhook"||!t.has(o.id))continue;const u=o.url??"";let h=null;try{h=new URL(u)}catch{h=null}if(!h||h.protocol!=="http:"&&h.protocol!=="https:")return{ok:!1,error:`Webhook node ${o.id} needs a valid http(s) URL`}}const p=findCycle(n,r);return p?{ok:!1,error:`Cycle detected: ${p.join(" \u2192 ")}`}:{ok:!0}}export function findCycle(f,n){const r=new Map;for(const e of f)r.set(e.id,[]);for(const e of n){const t=r.get(e.from);t&&t.push(e.to)}const i=new Map,s=[],a=e=>{i.set(e,1),s.push(e);for(const t of r.get(e)??[]){const d=i.get(t)??0;if(d===1)return[...s.slice(s.indexOf(t)),t];if(d===0){const c=a(t);if(c)return c}}return s.pop(),i.set(e,2),null};for(const e of f)if((i.get(e.id)??0)===0){const t=a(e.id);if(t)return t}return null}export function ranksOf(f,n){const r=new Map(f.map(e=>[e.id,0])),i=new Map(f.map(e=>[e.id,[]]));for(const e of n)i.get(e.from)?.push(e.to),r.set(e.to,(r.get(e.to)??0)+1);const s=new Map(f.map(e=>[e.id,0])),a=f.filter(e=>(r.get(e.id)??0)===0).map(e=>e.id);for(let e=0;e<a.length;e++){const t=a[e];for(const d of i.get(t)??[]){s.set(d,Math.max(s.get(d)??0,(s.get(t)??0)+1));const c=(r.get(d)??0)-1;r.set(d,c),c===0&&a.push(d)}}return s}
@@ -1,3 +1,3 @@
1
- import{cp as j,mkdir as p,readFile as g,readdir as _,rm as m,writeFile as P}from"node:fs/promises";import{existsSync as v,readdirSync as D,rmSync as F}from"node:fs";import s from"node:path";import{spawn as J}from"node:child_process";import{WEAVE_PLUGIN_MANIFEST as S,WeavePluginManifestSchema as W}from"@weavecode/core";import{getPluginsDir as b,getTmpDir as A}from"../db/paths.js";export function globalPluginRoot(){return b()}export function pruneInstallTmp(){const t=A();let r;try{r=D(t)}catch{return}for(const e of r)if(e.startsWith("install-"))try{F(s.join(t,e),{recursive:!0,force:!0})}catch{}}function E(t){return s.join(t??globalPluginRoot(),"installed.json")}async function N(t){try{const r=JSON.parse(await g(E(t),"utf8"));return Array.isArray(r)?r:[]}catch{return[]}}async function O(t,r){const e=r??globalPluginRoot();await p(e,{recursive:!0}),await P(E(r),JSON.stringify(t,null,2)+`
2
- `,"utf8")}async function x(t){try{const r=JSON.parse(await g(s.join(t,S),"utf8"));return W.safeParse(r).success?r:null}catch{return null}}async function k(t){const r=[],e=await x(t);if(e)return[{dir:t,manifest:e}];let o;try{o=await _(t)}catch{return r}for(const a of o){const n=s.join(t,a),i=await x(n);i&&r.push({dir:n,manifest:i})}return r}function I(t,r,e,o=6e4){return new Promise((a,n)=>{const i=J(t,r,{cwd:e,windowsHide:!0,stdio:["ignore","pipe","pipe"]});let c="";const l=setTimeout(()=>{i.kill(),n(new Error(`${t} ${r.join(" ")} \u8D85\u65F6\uFF08${o}ms\uFF09`))},o);i.stderr?.on("data",u=>c+=u.toString()),i.on("error",u=>{clearTimeout(l),n(u)}),i.on("close",u=>{clearTimeout(l),u===0?a():n(new Error(`${t} \u9000\u51FA\u7801 ${u}: ${c.slice(0,500)}`))})})}export function isGitSource(t){return/^(https?:\/\/|git@|ssh:\/\/|git:\/\/)/.test(t)||t.endsWith(".git")}export async function installPlugin(t,r){const e=[];let o,a;if(isGitSource(t)){e.push(`\u4ECE ${t} \u5B89\u88C5\u7684\u63D2\u4EF6\u672A\u7ECF\u6C99\u7BB1\u9694\u79BB\uFF0C\u8BF7\u786E\u8BA4\u6765\u6E90\u53EF\u4FE1`);const n=s.join(A(),`install-${Date.now()}`);await p(n,{recursive:!0}),await I("git",["clone","--depth","1",t,s.join(n,"repo")]),o=s.join(n,"repo"),a=()=>m(n,{recursive:!0,force:!0})}else if(o=s.resolve(t),!v(o))throw new Error(`\u672C\u5730\u8DEF\u5F84\u4E0D\u5B58\u5728: ${o}`);try{const n=await k(o);if(n.length===0)throw new Error(`\u672A\u627E\u5230 ${S}\uFF08src \u6216\u5176\u4E00\u7EA7\u5B50\u76EE\u5F55\uFF09`);const i=r?.root??globalPluginRoot();await p(i,{recursive:!0});const c=await N(r?.root),l=[];for(const{dir:u,manifest:y}of n){const f=String(y.id),h=s.join(i,f);s.resolve(h).startsWith(s.resolve(i)+s.sep)&&(await m(h,{recursive:!0,force:!0}),await j(u,h,{recursive:!0,filter:w=>!w.includes("node_modules")&&!w.includes(`${s.sep}.git`)}));const d={id:f,name:String(y.name??f),version:String(y.version??"0.0.0"),source:t,dir:f,installedAt:new Date().toISOString()},$=c.findIndex(w=>w.id===f);$>=0?c[$]=d:c.push(d),l.push({id:f,name:d.name,version:d.version,dir:f})}return await O(c,r?.root),{installed:l,warnings:e}}finally{a&&await a()}}export async function uninstallPlugin(t,r){const e=r?.root??globalPluginRoot(),o=await N(r?.root),a=o.find(i=>i.id===t),n=a?s.join(e,a.dir):s.join(e,t);return v(n)?(await m(n,{recursive:!0,force:!0}),a&&await O(o.filter(i=>i.id!==t),r?.root),!0):!1}export async function exportPlugin(t,r,e){const o=s.join(e?.root??globalPluginRoot(),t);if(!v(o))throw new Error(`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728: ${o}`);if(!await x(o))throw new Error(`\u63D2\u4EF6\u7F3A\u5C11\u5408\u6CD5 ${S}: ${t}`);const n=s.join(s.resolve(r),t);return await m(n,{recursive:!0,force:!0}),await j(o,n,{recursive:!0,filter:i=>!i.includes("node_modules")&&!i.includes(`${s.sep}.git`)}),n}function T(t){return s.join(t??globalPluginRoot(),"sources.json")}export function parseSources(t){if(Array.isArray(t))return{indexes:t.filter(r=>typeof r=="string")};if(t&&typeof t=="object"){const r=t.indexes;return{indexes:Array.isArray(r)?r.filter(e=>typeof e=="string"):[]}}return{indexes:[]}}export async function readSourcesFile(t){try{return parseSources(JSON.parse(await g(T(t),"utf8")))}catch{return{indexes:[]}}}export async function writeSourcesFile(t,r){const e=r??globalPluginRoot();await p(e,{recursive:!0}),await P(T(r),JSON.stringify(t,null,2)+`
3
- `,"utf8")}export async function searchPlugins(t){const{indexes:r}=await readSourcesFile(),e=[];for(const o of r)try{const a=await fetch(o,{signal:AbortSignal.timeout(1e4)});if(!a.ok)continue;const n=await a.json();for(const i of n.plugins??[]){const c=`${i.id} ${i.name} ${i.description??""}`.toLowerCase();(!t||c.includes(t.toLowerCase()))&&e.push(i)}}catch{}return e}
1
+ import{cp as x,mkdir as p,readFile as E,readdir as C,rm as m,writeFile as j}from"node:fs/promises";import{existsSync as F,readdirSync as N,rmSync as O}from"node:fs";import s from"node:path";import{spawn as T}from"node:child_process";import{WEAVE_PLUGIN_MANIFEST as h,WeavePluginManifestSchema as I}from"@weavecode/core";import{getPluginsDir as J,getTmpDir as $}from"../db/paths.js";export function globalPluginRoot(){return J()}export function pruneInstallTmp(){const r=$();let t;try{t=N(r)}catch{return}for(const n of t)if(n.startsWith("install-"))try{O(s.join(r,n),{recursive:!0,force:!0})}catch{}}function A(r){return s.join(r??globalPluginRoot(),"installed.json")}async function D(r){try{const t=JSON.parse(await E(A(r),"utf8"));return Array.isArray(t)?t:[]}catch{return[]}}async function P(r,t){const n=t??globalPluginRoot();await p(n,{recursive:!0}),await j(A(t),JSON.stringify(r,null,2)+`
2
+ `,"utf8")}async function S(r){try{const t=JSON.parse(await E(s.join(r,h),"utf8"));return I.safeParse(t).success?t:null}catch{return null}}async function W(r){const t=[],n=await S(r);if(n)return[{dir:r,manifest:n}];let i;try{i=await C(r)}catch{return t}for(const u of i){const e=s.join(r,u),o=await S(e);o&&t.push({dir:e,manifest:o})}return t}function _(r,t,n,i=6e4){return new Promise((u,e)=>{const o=T(r,t,{cwd:n,windowsHide:!0,stdio:["ignore","pipe","pipe"]});let a="";const l=setTimeout(()=>{o.kill(),e(new Error(`${r} ${t.join(" ")} \u8D85\u65F6\uFF08${i}ms\uFF09`))},i);o.stderr?.on("data",c=>a+=c.toString()),o.on("error",c=>{clearTimeout(l),e(c)}),o.on("close",c=>{clearTimeout(l),c===0?u():e(new Error(`${r} \u9000\u51FA\u7801 ${c}: ${a.slice(0,500)}`))})})}export function isGitSource(r){return/^(https?:\/\/|git@|ssh:\/\/|git:\/\/)/.test(r)||r.endsWith(".git")}export async function installPlugin(r,t){const n=[];let i,u;if(isGitSource(r)){n.push(`\u4ECE ${r} \u5B89\u88C5\u7684\u63D2\u4EF6\u672A\u7ECF\u6C99\u7BB1\u9694\u79BB\uFF0C\u8BF7\u786E\u8BA4\u6765\u6E90\u53EF\u4FE1`);const e=s.join($(),`install-${Date.now()}`);await p(e,{recursive:!0}),await _("git",["clone","--depth","1",r,s.join(e,"repo")]),i=s.join(e,"repo"),u=()=>m(e,{recursive:!0,force:!0})}else if(i=s.resolve(r),!F(i))throw new Error(`\u672C\u5730\u8DEF\u5F84\u4E0D\u5B58\u5728: ${i}`);try{const e=await W(i);if(e.length===0)throw new Error(`\u672A\u627E\u5230 ${h}\uFF08src \u6216\u5176\u4E00\u7EA7\u5B50\u76EE\u5F55\uFF09`);const o=t?.root??globalPluginRoot();await p(o,{recursive:!0});const a=await D(t?.root),l=[];for(const{dir:c,manifest:y}of e){const f=String(y.id),g=s.join(o,f);s.resolve(g).startsWith(s.resolve(o)+s.sep)&&(await m(g,{recursive:!0,force:!0}),await x(c,g,{recursive:!0,filter:w=>!w.includes("node_modules")&&!w.includes(`${s.sep}.git`)}));const d={id:f,name:String(y.name??f),version:String(y.version??"0.0.0"),source:r,dir:f,installedAt:new Date().toISOString()},v=a.findIndex(w=>w.id===f);v>=0?a[v]=d:a.push(d),l.push({id:f,name:d.name,version:d.version,dir:f})}return await P(a,t?.root),{installed:l,warnings:n}}finally{u&&await u()}}export async function uninstallPlugin(r,t){const n=t?.root??globalPluginRoot(),i=await D(t?.root),u=i.find(o=>o.id===r),e=u?s.join(n,u.dir):s.join(n,r);return F(e)?(await m(e,{recursive:!0,force:!0}),u&&await P(i.filter(o=>o.id!==r),t?.root),!0):!1}export async function exportPlugin(r,t,n){const i=s.join(n?.root??globalPluginRoot(),r);if(!F(i))throw new Error(`\u63D2\u4EF6\u76EE\u5F55\u4E0D\u5B58\u5728: ${i}`);if(!await S(i))throw new Error(`\u63D2\u4EF6\u7F3A\u5C11\u5408\u6CD5 ${h}: ${r}`);const u=s.join(s.resolve(t),r);return await m(u,{recursive:!0,force:!0}),await x(i,u,{recursive:!0,filter:e=>!e.includes("node_modules")&&!e.includes(`${s.sep}.git`)}),u}function B(r){return s.join(r??globalPluginRoot(),"sources.json")}export function parseSources(r){if(Array.isArray(r))return{indexes:r.filter(t=>typeof t=="string")};if(r&&typeof r=="object"){const t=r.indexes;return{indexes:Array.isArray(t)?t.filter(n=>typeof n=="string"):[]}}return{indexes:[]}}export async function readSourcesFile(r){try{return parseSources(JSON.parse(await E(B(r),"utf8")))}catch{return{indexes:[]}}}export async function writeSourcesFile(r,t){const n=t??globalPluginRoot();await p(n,{recursive:!0}),await j(B(t),JSON.stringify(r,null,2)+`
3
+ `,"utf8")}export async function searchPlugins(r){const{indexes:t}=await readSourcesFile(),n=[];for(const i of t)try{const u=await fetch(i,{signal:AbortSignal.timeout(1e4)});if(!u.ok)continue;const e=await u.json();for(const o of e.plugins??[]){const a=`${o.id} ${o.name} ${o.description??""}`.toLowerCase();(!r||a.includes(r.toLowerCase()))&&n.push(o)}}catch{}return n}
@@ -1 +1 @@
1
- import{existsSync as D}from"node:fs";import{readFile as S,readdir as I}from"node:fs/promises";import d from"node:path";import{pathToFileURL as P}from"node:url";import{watch as A}from"chokidar";import{builtinRulesDir as M}from"../market/builtin.js";import{HARNESS_EVENT_UNION as W,HookRuleSchema as O,PluginRuleEntrySchema as x,WEAVE_PLUGIN_MANIFEST as T,WeavePluginManifestSchema as H}from"@weavecode/core";import{getDataRoot as L,getPluginsDir as N}from"../db/paths.js";import{marketLibraryRoots as C}from"../market/resolve.js";import{validateHandler as U}from"./registry.js";import{builtinToolkitRoot as _,discoverToolkitSources as B,discoverWeavePackDirs as F}from"./toolkit-scan.js";export function createPluginLogger(l){return{debug:e=>console.debug(`[plugin:${l}] ${e}`),info:e=>console.info(`[plugin:${l}] ${e}`),warn:e=>console.warn(`[plugin:${l}] ${e}`),error:e=>console.error(`[plugin:${l}] ${e}`)}}const j="@file:";function q(){return d.resolve(_())}export class WeavePluginLoader{deps;handlers=new Map;configs=new Map;plugins=new Map;subscribers=new Map;roots=new Map;watchStop;constructor(e){this.deps=e}list(){return[...this.plugins.values()]}getHandler(e,s){return this.handlers.get(`${e}:${s}`)}getConfig(e){return this.configs.get(e)??{}}async loadRoot(e,s,o){const n=d.resolve(e);this.roots.set(n,{scope:s,projectId:o});let i;try{i=await I(n)}catch{return[]}const t=this.priorIdsUnder(n),c=[];for(const f of i){const u=d.join(n,f);if(this.roots.has(d.resolve(u))||d.resolve(u)===q())continue;const g=await this.loadOne(u,s,o,f);this.plugins.set(g.id,g),c.push(g)}return this.finalizeScan(t,c)}async loadToolkitRoot(e,s,o){const n=d.resolve(e);this.roots.set(n,{scope:s,projectId:o,deep:!0});const i=await F(n),t=this.priorIdsUnder(n),c=[];for(const f of i){await this.ensureToolkitDefaultDisabled(f);const u=await this.loadOne(f,s,o,d.basename(f));this.plugins.set(u.id,u),c.push(u)}return this.finalizeScan(t,c)}async ensureToolkitDefaultDisabled(e){try{const s=JSON.parse(await S(d.join(e,T),"utf8"));typeof s.id=="string"&&!this.deps.states.get(s.id)&&this.deps.states.setEnabled(s.id,!1)}catch{}}priorIdsUnder(e){const s=[...this.roots.keys()].filter(o=>o!==e&&o.startsWith(e+d.sep));return new Set([...this.plugins.values()].filter(o=>o.dir.startsWith(e+d.sep)?!s.some(n=>o.dir.startsWith(n+d.sep)):!1).map(o=>o.id))}finalizeScan(e,s){const o=new Set(s.map(i=>i.id));let n=!1;for(const i of e)o.has(i)||(this.deps.rules.removeByPlugin(i),this.unloadHandlers(i),this.configs.delete(i),this.plugins.delete(i),n=!0,this.emitReload({pluginId:i,status:"rejected",ruleCount:0,removed:!0}));(n||s.length>0)&&this.deps.registry.load();for(const i of s)this.emitReload({pluginId:i.id,status:i.status,ruleCount:i.ruleCount,at:new Date().toISOString()});return s}watchRoots(e=1e3){if(this.watchStop)return this.watchStop;const s=new Map,o=[...this.roots.entries()].filter(([n])=>D(n)).map(([n,i])=>{const t=i.deep?6:i.projectId!==null?3:2,c=A(n,{ignoreInitial:!0,depth:t});return c.on("all",()=>{const f=s.get(n);f&&clearTimeout(f),s.set(n,setTimeout(()=>{s.delete(n);const u=this.roots.get(n);if(!u)return;(u.deep?this.loadToolkitRoot(n,u.scope,u.projectId):this.loadRoot(n,u.scope,u.projectId)).catch(()=>{})},e))}),c.on("error",()=>{}),c});return this.watchStop=()=>{for(const n of s.values())clearTimeout(n);s.clear();for(const n of o)n.close();this.watchStop=void 0},this.watchStop}async reloadAll(){for(const[e,s]of[...this.roots])await(s.deep?this.loadToolkitRoot(e,s.scope,s.projectId):this.loadRoot(e,s.scope,s.projectId)).catch(()=>{})}stopWatching(){this.watchStop?.()}async rescanRoots(e,s){await registerPluginRoots(this,{libraries:s},e),this.stopWatching(),await this.reloadAll(),this.watchRoots()}setPluginEnabled(e,s){const o=this.plugins.get(e);if(!o)return!1;this.deps.states.setEnabled(e,s);for(const i of this.deps.rules.listByPlugin(e))this.deps.rules.setEnabled(i.id,s);this.deps.registry.load();const n=s?o.error?"rejected":"active":"disabled";return this.plugins.set(e,{...o,status:n}),this.deps.bus?.emit({kind:"plugin.toggled",pluginId:e,enabled:s,at:new Date().toISOString()}),!0}unloadHandlers(e){for(const s of[...this.handlers.keys()])s.startsWith(`${e}:`)&&this.handlers.delete(s);this.subscribers.delete(e)}notifyEvent(e,s,o){for(const[n,i]of this.subscribers){if(!i.events.has(e))continue;const t=this.configs.get(n)??{};(async()=>{await i.sub.onEvent({eventType:e,payload:s,sessionId:o,config:t,logger:createPluginLogger(n)})})().catch(()=>{})}}emitReload(e){this.deps.bus?.emit({kind:"plugin.reloaded",at:new Date().toISOString(),...e})}async loadOne(e,s,o,n){const i=(r,a,v=n,h="0.0.0")=>{const w=this.plugins.get(r);return w&&w.dir===e&&(this.deps.rules.removeByPlugin(r),this.unloadHandlers(r),this.configs.delete(r)),{id:r,name:v,version:h,dir:e,scope:s,projectId:o,status:"rejected",error:a,ruleCount:0}};let t;try{const r=JSON.parse(await S(d.join(e,T),"utf8")),a=H.safeParse(r);if(!a.success)return i(n,`manifest \u6821\u9A8C\u5931\u8D25: ${a.error.issues[0]?.message??""}`);t=a.data}catch(r){return i(n,`manifest \u8BFB\u53D6\u5931\u8D25: ${r instanceof Error?r.message:String(r)}`)}if(s!=="builtin"&&t.id.startsWith("weave-"))return i(t.id,"id \u4EE5 weave- \u5F00\u5934\u4E3A\u5185\u7F6E\u9884\u8BBE\u547D\u540D\u7A7A\u95F4\u4FDD\u7559",t.name,t.version);const c=this.plugins.get(t.id);if(c&&c.status!=="rejected"&&c.dir!==e)return i(t.id,`\u91CD\u590D\u7684\u63D2\u4EF6 id\uFF08\u5DF2\u7531 ${c.dir} \u6CE8\u518C\uFF09`,t.name,t.version);const u=(t.requires?.events??[]).filter(r=>!W.includes(r));if(u.length>0)return i(t.id,`requires.events \u542B\u672A\u77E5\u4E8B\u4EF6: ${u.join(", ")}`,t.name,t.version);const g=[];for(const r of t.rules)try{const a=JSON.parse(await S(this.resolveWithin(e,r),"utf8"));if(!Array.isArray(a))throw new Error(`${r} \u4E0D\u662F\u6570\u7EC4`);g.push(...a)}catch(a){return i(t.id,`\u89C4\u5219\u6587\u4EF6 ${r}: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}const E=new Map;for(const r of t.handlers)try{const a=this.resolveWithin(e,r.entry),h=(await import(`${P(a).href}?t=${Date.now()}`)).default;if(!h||typeof h.execute!="function")throw new Error(`${r.entry} \u7684 default \u5BFC\u51FA\u7F3A\u5C11 execute \u51FD\u6570`);if(h.type!==r.type)throw new Error(`${r.entry} \u5BFC\u51FA type="${h.type}" \u4E0E manifest \u58F0\u660E "${r.type}" \u4E0D\u7B26`);E.set(`${t.id}:${r.type}`,h)}catch(a){return i(t.id,`handler ${r.type} \u52A0\u8F7D\u5931\u8D25: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}let R;if(t.subscribe){const r=t.subscribe.events.filter(a=>!W.includes(a));if(r.length>0)return i(t.id,`subscribe.events \u542B\u672A\u77E5\u4E8B\u4EF6: ${r.join(", ")}`,t.name,t.version);try{const a=this.resolveWithin(e,t.subscribe.entry),h=(await import(`${P(a).href}?t=${Date.now()}`)).default;if(!h||typeof h.onEvent!="function")throw new Error(`${t.subscribe.entry} \u7684 default \u5BFC\u51FA\u7F3A\u5C11 onEvent \u51FD\u6570`);R={events:new Set(t.subscribe.events),sub:h}}catch(a){return i(t.id,`subscriber \u52A0\u8F7D\u5931\u8D25: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}}const k=[];for(let r=0;r<g.length;r++){const a=g[r],v=typeof a=="object"&&a!==null&&"handler"in a?{...a,handler:z(a.handler,t.id)}:a,h=x.safeParse(v);if(!h.success)return i(t.id,`\u89C4\u5219 #${r} \u6821\u9A8C\u5931\u8D25: ${h.error.issues[0]?.message??""}`,t.name,t.version);const w=h.data;let p=w.handler;if(p.type==="plugin"&&!E.has(`${t.id}:${p.handlerType}`))return i(t.id,`\u89C4\u5219 #${r} \u5F15\u7528\u672A\u58F0\u660E\u7684 handler "${p.handlerType}"`,t.name,t.version);if((p.type==="inject"||p.type==="manual")&&p.template.startsWith(j)){const y=p.template.slice(j.length);try{const b=await S(this.resolveWithin(e,y),"utf8");p={...p,template:b}}catch(b){return i(t.id,`\u89C4\u5219 #${r} \u6A21\u677F ${y}: ${b instanceof Error?b.message:String(b)}`,t.name,t.version)}}let $;try{$=O.parse({...w,handler:p,id:`plugin:${t.id}:${r}`,projectId:o,enabled:w.enabled??!0,priority:w.priority??0,origin:s==="builtin"?"builtin":"plugin",pluginId:t.id}),U($.source,$.handler)}catch(y){return i(t.id,`\u89C4\u5219 #${r}: ${y instanceof Error?y.message:String(y)}`,t.name,t.version)}k.push($)}const m=this.deps.states.get(t.id);try{for(const r of k)this.deps.rules.upsertPluginRule(r)}catch(r){return this.deps.rules.removeByPlugin(t.id),i(t.id,`\u89C4\u5219\u5165\u5E93\u5931\u8D25: ${r instanceof Error?r.message:String(r)}`,t.name,t.version)}if(m&&!m.enabled)for(const r of this.deps.rules.listByPlugin(t.id))this.deps.rules.setEnabled(r.id,!1);for(const[r,a]of E)this.handlers.set(r,a);return this.configs.set(t.id,{...t.config??{},...m?.config??{}}),R&&!(m&&!m.enabled)&&this.subscribers.set(t.id,R),{id:t.id,name:t.name,version:t.version,dir:e,scope:s,projectId:o,status:m&&!m.enabled?"disabled":"active",ruleCount:k.length}}resolveWithin(e,s){const o=d.resolve(e),n=d.resolve(o,s);if(n!==o&&!n.startsWith(o+d.sep))throw new Error(`\u8DEF\u5F84\u8D8A\u51FA\u63D2\u4EF6\u76EE\u5F55: ${s}`);return n}}function z(l,e){if(typeof l!="object"||l===null)return l;const s=l;return s.type!=="plugin"||s.pluginId!==void 0?l:{...s,pluginId:e}}export async function registerPluginRoots(l,e,s){const o=[],n=B();if(e.libraries)for(const t of C(e.libraries))n.push({root:t,provenance:"market"});const i=new Set;for(const t of n){const c=d.resolve(t.root);i.has(c)||(i.add(c),o.push(...await l.loadToolkitRoot(t.root,t.provenance==="builtin"?"builtin":"user",null)))}o.push(...await l.loadRoot(M(L()),"builtin",null)),o.push(...await l.loadRoot(N(),"user",null));for(const t of s)o.push(...await l.loadRoot(d.join(t.path,".weave","plugins"),"project",t.id));return o}export async function loadAllPlugins(l,e){const s=new WeavePluginLoader(l),o=await registerPluginRoots(s,l,e);return{loader:s,loaded:o}}
1
+ import{existsSync as j}from"node:fs";import{readFile as $,readdir as C}from"node:fs/promises";import d from"node:path";import{pathToFileURL as D}from"node:url";import{watch as P}from"chokidar";import{builtinRulesDir as W}from"../market/builtin.js";import{HARNESS_EVENT_UNION as I,HookRuleSchema as B,PluginRuleEntrySchema as T,WEAVE_PLUGIN_MANIFEST as A,WeavePluginManifestSchema as k}from"@weavecode/core";import{getDataRoot as O,getPluginsDir as M}from"../db/paths.js";import{marketLibraryRoots as N}from"../market/resolve.js";import{validateHandler as x}from"./registry.js";import{builtinToolkitRoot as H,discoverToolkitSources as U,discoverWeavePackDirs as L}from"./toolkit-scan.js";export function createPluginLogger(u){return{debug:e=>console.debug(`[plugin:${u}] ${e}`),info:e=>console.info(`[plugin:${u}] ${e}`),warn:e=>console.warn(`[plugin:${u}] ${e}`),error:e=>console.error(`[plugin:${u}] ${e}`)}}const R="@file:";function _(){return d.resolve(H())}export class WeavePluginLoader{deps;handlers=new Map;configs=new Map;plugins=new Map;subscribers=new Map;roots=new Map;watchStop;constructor(e){this.deps=e}list(){return[...this.plugins.values()]}getHandler(e,s){return this.handlers.get(`${e}:${s}`)}getConfig(e){return this.configs.get(e)??{}}async loadRoot(e,s,n){const i=d.resolve(e);this.roots.set(i,{scope:s,projectId:n});let o;try{o=await C(i)}catch{return[]}const t=this.priorIdsUnder(i),c=[];for(const p of o){const l=d.join(i,p);if(this.roots.has(d.resolve(l))||d.resolve(l)===_())continue;const g=await this.loadOne(l,s,n,p);this.plugins.set(g.id,g),c.push(g)}return this.finalizeScan(t,c)}async loadToolkitRoot(e,s,n){const i=d.resolve(e);this.roots.set(i,{scope:s,projectId:n,deep:!0});const o=await L(i),t=this.priorIdsUnder(i),c=[];for(const p of o){await this.ensureToolkitDefaultDisabled(p);const l=await this.loadOne(p,s,n,d.basename(p));this.plugins.set(l.id,l),c.push(l)}return this.finalizeScan(t,c)}async ensureToolkitDefaultDisabled(e){try{const s=JSON.parse(await $(d.join(e,A),"utf8"));typeof s.id=="string"&&!this.deps.states.get(s.id)&&this.deps.states.setEnabled(s.id,!1)}catch{}}priorIdsUnder(e){const s=[...this.roots.keys()].filter(n=>n!==e&&n.startsWith(e+d.sep));return new Set([...this.plugins.values()].filter(n=>n.dir.startsWith(e+d.sep)?!s.some(i=>n.dir.startsWith(i+d.sep)):!1).map(n=>n.id))}finalizeScan(e,s){const n=new Set(s.map(o=>o.id));let i=!1;for(const o of e)n.has(o)||(this.deps.rules.removeByPlugin(o),this.unloadHandlers(o),this.configs.delete(o),this.plugins.delete(o),i=!0,this.emitReload({pluginId:o,status:"rejected",ruleCount:0,removed:!0}));(i||s.length>0)&&this.deps.registry.load();for(const o of s)this.emitReload({pluginId:o.id,status:o.status,ruleCount:o.ruleCount,at:new Date().toISOString()});return s}watchRoots(e=1e3){if(this.watchStop)return this.watchStop;const s=new Map,n=[...this.roots.entries()].filter(([i])=>j(i)).map(([i,o])=>{const t=o.deep?6:o.projectId!==null?3:2,c=P(i,{ignoreInitial:!0,depth:t});return c.on("all",()=>{const p=s.get(i);p&&clearTimeout(p),s.set(i,setTimeout(()=>{s.delete(i);const l=this.roots.get(i);l&&(l.deep?this.loadToolkitRoot(i,l.scope,l.projectId):this.loadRoot(i,l.scope,l.projectId)).catch(()=>{})},e))}),c.on("error",()=>{}),c});return this.watchStop=()=>{for(const i of s.values())clearTimeout(i);s.clear();for(const i of n)i.close();this.watchStop=void 0},this.watchStop}async reloadAll(){for(const[e,s]of[...this.roots])await(s.deep?this.loadToolkitRoot(e,s.scope,s.projectId):this.loadRoot(e,s.scope,s.projectId)).catch(()=>{})}stopWatching(){this.watchStop?.()}async rescanRoots(e,s){await registerPluginRoots(this,{libraries:s},e),this.stopWatching(),await this.reloadAll(),this.watchRoots()}setPluginEnabled(e,s){const n=this.plugins.get(e);if(!n)return!1;this.deps.states.setEnabled(e,s);for(const o of this.deps.rules.listByPlugin(e))this.deps.rules.setEnabled(o.id,s);this.deps.registry.load();const i=s?n.error?"rejected":"active":"disabled";return this.plugins.set(e,{...n,status:i}),this.deps.bus?.emit({kind:"plugin.toggled",pluginId:e,enabled:s,at:new Date().toISOString()}),!0}unloadHandlers(e){for(const s of[...this.handlers.keys()])s.startsWith(`${e}:`)&&this.handlers.delete(s);this.subscribers.delete(e)}notifyEvent(e,s,n){for(const[i,o]of this.subscribers){if(!o.events.has(e))continue;const t=this.configs.get(i)??{};(async()=>{await o.sub.onEvent({eventType:e,payload:s,sessionId:n,config:t,logger:createPluginLogger(i)})})().catch(()=>{})}}emitReload(e){this.deps.bus?.emit({kind:"plugin.reloaded",at:new Date().toISOString(),...e})}async loadOne(e,s,n,i){const o=(r,a,h=i,y="0.0.0")=>{const w=this.plugins.get(r);return w&&w.dir===e&&(this.deps.rules.removeByPlugin(r),this.unloadHandlers(r),this.configs.delete(r)),{id:r,name:h,version:y,dir:e,scope:s,projectId:n,status:"rejected",error:a,ruleCount:0}};let t;try{const r=JSON.parse(await $(d.join(e,A),"utf8")),a=k.safeParse(r);if(!a.success)return o(i,`manifest \u6821\u9A8C\u5931\u8D25: ${a.error.issues[0]?.message??""}`);t=a.data}catch(r){return o(i,`manifest \u8BFB\u53D6\u5931\u8D25: ${r instanceof Error?r.message:String(r)}`)}if(s!=="builtin"&&t.id.startsWith("weave-"))return o(t.id,"id \u4EE5 weave- \u5F00\u5934\u4E3A\u5185\u7F6E\u9884\u8BBE\u547D\u540D\u7A7A\u95F4\u4FDD\u7559",t.name,t.version);const c=this.plugins.get(t.id);if(c&&c.status!=="rejected"&&c.dir!==e)return o(t.id,`\u91CD\u590D\u7684\u63D2\u4EF6 id\uFF08\u5DF2\u7531 ${c.dir} \u6CE8\u518C\uFF09`,t.name,t.version);const p=(t.requires?.events??[]).filter(r=>!I.includes(r));if(p.length>0)return o(t.id,`requires.events \u542B\u672A\u77E5\u4E8B\u4EF6: ${p.join(", ")}`,t.name,t.version);const l=[];for(const r of t.rules)try{const a=JSON.parse(await $(this.resolveWithin(e,r),"utf8"));if(!Array.isArray(a))throw new Error(`${r} \u4E0D\u662F\u6570\u7EC4`);l.push(...a)}catch(a){return o(t.id,`\u89C4\u5219\u6587\u4EF6 ${r}: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}const g=new Map;for(const r of t.handlers)try{const a=this.resolveWithin(e,r.entry),h=(await import(`${D(a).href}?t=${Date.now()}`)).default;if(!h||typeof h.execute!="function")throw new Error(`${r.entry} \u7684 default \u5BFC\u51FA\u7F3A\u5C11 execute \u51FD\u6570`);if(h.type!==r.type)throw new Error(`${r.entry} \u5BFC\u51FA type="${h.type}" \u4E0E manifest \u58F0\u660E "${r.type}" \u4E0D\u7B26`);g.set(`${t.id}:${r.type}`,h)}catch(a){return o(t.id,`handler ${r.type} \u52A0\u8F7D\u5931\u8D25: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}let F;if(t.subscribe){const r=t.subscribe.events.filter(a=>!I.includes(a));if(r.length>0)return o(t.id,`subscribe.events \u542B\u672A\u77E5\u4E8B\u4EF6: ${r.join(", ")}`,t.name,t.version);try{const a=this.resolveWithin(e,t.subscribe.entry),h=(await import(`${D(a).href}?t=${Date.now()}`)).default;if(!h||typeof h.onEvent!="function")throw new Error(`${t.subscribe.entry} \u7684 default \u5BFC\u51FA\u7F3A\u5C11 onEvent \u51FD\u6570`);F={events:new Set(t.subscribe.events),sub:h}}catch(a){return o(t.id,`subscriber \u52A0\u8F7D\u5931\u8D25: ${a instanceof Error?a.message:String(a)}`,t.name,t.version)}}const S=[];for(let r=0;r<l.length;r++){const a=l[r],h=typeof a=="object"&&a!==null&&"handler"in a?{...a,handler:z(a.handler,t.id)}:a,y=T.safeParse(h);if(!y.success)return o(t.id,`\u89C4\u5219 #${r} \u6821\u9A8C\u5931\u8D25: ${y.error.issues[0]?.message??""}`,t.name,t.version);const w=y.data;let f=w.handler;if(f.type==="plugin"&&!g.has(`${t.id}:${f.handlerType}`))return o(t.id,`\u89C4\u5219 #${r} \u5F15\u7528\u672A\u58F0\u660E\u7684 handler "${f.handlerType}"`,t.name,t.version);if((f.type==="inject"||f.type==="manual")&&f.template.startsWith(R)){const v=f.template.slice(R.length);try{const b=await $(this.resolveWithin(e,v),"utf8");f={...f,template:b}}catch(b){return o(t.id,`\u89C4\u5219 #${r} \u6A21\u677F ${v}: ${b instanceof Error?b.message:String(b)}`,t.name,t.version)}}let E;try{E=B.parse({...w,handler:f,id:`plugin:${t.id}:${r}`,projectId:n,enabled:w.enabled??!0,priority:w.priority??0,origin:s==="builtin"?"builtin":"plugin",pluginId:t.id}),x(E.source,E.handler)}catch(v){return o(t.id,`\u89C4\u5219 #${r}: ${v instanceof Error?v.message:String(v)}`,t.name,t.version)}S.push(E)}const m=this.deps.states.get(t.id);try{for(const r of S)this.deps.rules.upsertPluginRule(r)}catch(r){return this.deps.rules.removeByPlugin(t.id),o(t.id,`\u89C4\u5219\u5165\u5E93\u5931\u8D25: ${r instanceof Error?r.message:String(r)}`,t.name,t.version)}if(m&&!m.enabled)for(const r of this.deps.rules.listByPlugin(t.id))this.deps.rules.setEnabled(r.id,!1);for(const[r,a]of g)this.handlers.set(r,a);return this.configs.set(t.id,{...t.config??{},...m?.config??{}}),F&&!(m&&!m.enabled)&&this.subscribers.set(t.id,F),{id:t.id,name:t.name,version:t.version,dir:e,scope:s,projectId:n,status:m&&!m.enabled?"disabled":"active",ruleCount:S.length}}resolveWithin(e,s){const n=d.resolve(e),i=d.resolve(n,s);if(i!==n&&!i.startsWith(n+d.sep))throw new Error(`\u8DEF\u5F84\u8D8A\u51FA\u63D2\u4EF6\u76EE\u5F55: ${s}`);return i}}function z(u,e){if(typeof u!="object"||u===null)return u;const s=u;return s.type!=="plugin"||s.pluginId!==void 0?u:{...s,pluginId:e}}export async function registerPluginRoots(u,e,s){const n=[],i=U();if(e.libraries)for(const t of N(e.libraries))i.push({root:t,provenance:"market"});const o=new Set;for(const t of i){const c=d.resolve(t.root);o.has(c)||(o.add(c),n.push(...await u.loadToolkitRoot(t.root,t.provenance==="builtin"?"builtin":"user",null)))}n.push(...await u.loadRoot(W(O()),"builtin",null)),n.push(...await u.loadRoot(M(),"user",null));for(const t of s)n.push(...await u.loadRoot(d.join(t.path,".weave","plugins"),"project",t.id));return n}export async function loadAllPlugins(u,e){const s=new WeavePluginLoader(u),n=await registerPluginRoots(s,u,e);return{loader:s,loaded:n}}
@@ -1 +1 @@
1
- export function pluginsStateOf(s){const e={syncedAt:null,enabled:{}};if(s==null)return e;try{const t=JSON.parse(s);if(!t||typeof t!="object"||Array.isArray(t))return e;const n=t.plugins;if(!n||typeof n!="object"||Array.isArray(n))return e;const r=n,a=r.enabled,c={};if(a&&typeof a=="object"&&!Array.isArray(a))for(const[l,o]of Object.entries(a))typeof o=="boolean"&&(c[l]=o);return{syncedAt:typeof r.syncedAt=="string"?r.syncedAt:null,enabled:c}}catch{return e}}export function effective(s,e,t){const n=pluginsStateOf(s.meta);return Object.hasOwn(n.enabled,e)?n.enabled[e]:n.syncedAt!==null?!1:t.states.get(e)?.enabled??t.defaultEnabled??!0}export function setProjectEnabled(s,e,t,n){const r=pluginsStateOf(s.meta);n.projects.setMeta(s.id,{plugins:{syncedAt:r.syncedAt,enabled:{...r.enabled,[e]:t}}})}export function syncPlugins(s,e,t){const r={...pluginsStateOf(s.meta).enabled};for(const c of e)Object.hasOwn(r,c)||(r[c]=!1);const a=t.now??new Date().toISOString();return t.projects.setMeta(s.id,{plugins:{syncedAt:a,enabled:r}}),a}
1
+ export function pluginsStateOf(r){const t={syncedAt:null,enabled:{}};if(r==null)return t;try{const n=JSON.parse(r);if(!n||typeof n!="object"||Array.isArray(n))return t;const e=n.plugins;if(!e||typeof e!="object"||Array.isArray(e))return t;const o=e,c=o.enabled,s={};if(c&&typeof c=="object"&&!Array.isArray(c))for(const[l,a]of Object.entries(c))typeof a=="boolean"&&(s[l]=a);return{syncedAt:typeof o.syncedAt=="string"?o.syncedAt:null,enabled:s}}catch{return t}}export function effective(r,t,n){const e=pluginsStateOf(r.meta);return Object.hasOwn(e.enabled,t)?e.enabled[t]:e.syncedAt!==null?!1:n.states.get(t)?.enabled??n.defaultEnabled??!0}export function setProjectEnabled(r,t,n,e){const o=pluginsStateOf(r.meta);e.projects.setMeta(r.id,{plugins:{syncedAt:o.syncedAt,enabled:{...o.enabled,[t]:n}}})}export function syncPlugins(r,t,n){const e={...pluginsStateOf(r.meta).enabled};for(const c of t)Object.hasOwn(e,c)||(e[c]=!1);const o=n.now??new Date().toISOString();return n.projects.setMeta(r.id,{plugins:{syncedAt:o,enabled:e}}),o}