berry-agent 0.1.0-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (401) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/dist/.api-emit.stamp +1 -0
  4. package/dist/.build-meta.json +6 -0
  5. package/dist/agent/events.js +1 -0
  6. package/dist/agent/index.js +2 -0
  7. package/dist/agent/loop.js +139 -0
  8. package/dist/agent/queue.js +119 -0
  9. package/dist/agent/stream.js +62 -0
  10. package/dist/agent/tools-batch.js +221 -0
  11. package/dist/agent/types.js +8 -0
  12. package/dist/api/berry-agent-llm.d.ts +18 -0
  13. package/dist/api/berry-agent.d.ts +9 -0
  14. package/dist/api/surface.json +1632 -0
  15. package/dist/api/tsconfig.paths.json +12 -0
  16. package/dist/api/typebox-compile.d.ts +2 -0
  17. package/dist/api/typebox-value.d.ts +2 -0
  18. package/dist/api/typebox.d.ts +2 -0
  19. package/dist/browser/cdp.js +256 -0
  20. package/dist/browser/codes.js +31 -0
  21. package/dist/browser/discover.js +107 -0
  22. package/dist/browser/engine.js +169 -0
  23. package/dist/browser/index.js +17 -0
  24. package/dist/browser/install.js +342 -0
  25. package/dist/browser/page.js +321 -0
  26. package/dist/browser/service.js +158 -0
  27. package/dist/browser/tools.js +198 -0
  28. package/dist/browser/types.js +81 -0
  29. package/dist/channels/ask-queue.js +82 -0
  30. package/dist/channels/commands.js +129 -0
  31. package/dist/channels/engine/cell.js +143 -0
  32. package/dist/channels/engine/diff.js +143 -0
  33. package/dist/channels/engine/engine.js +416 -0
  34. package/dist/channels/engine/index.js +8 -0
  35. package/dist/channels/engine/input-keys.js +86 -0
  36. package/dist/channels/engine/input.js +596 -0
  37. package/dist/channels/engine/memory-io.js +71 -0
  38. package/dist/channels/engine/process-io.js +38 -0
  39. package/dist/channels/engine/types.js +17 -0
  40. package/dist/channels/engine/width.js +122 -0
  41. package/dist/channels/index.js +15 -0
  42. package/dist/channels/registry.js +56 -0
  43. package/dist/channels/sdk/admit.js +29 -0
  44. package/dist/channels/sdk/backend.js +102 -0
  45. package/dist/channels/sdk/cursor.js +56 -0
  46. package/dist/channels/sdk/index.js +20 -0
  47. package/dist/channels/sdk/jsonl.js +208 -0
  48. package/dist/channels/sdk/protocol.js +24 -0
  49. package/dist/channels/sdk/schema.js +91 -0
  50. package/dist/channels/sdk/wire-core.js +446 -0
  51. package/dist/channels/service.js +169 -0
  52. package/dist/channels/tui/autocomplete/autocomplete.js +42 -0
  53. package/dist/channels/tui/autocomplete/file-mentions.js +106 -0
  54. package/dist/channels/tui/autocomplete/popup.js +148 -0
  55. package/dist/channels/tui/autocomplete/provider.js +7 -0
  56. package/dist/channels/tui/autocomplete/token.js +68 -0
  57. package/dist/channels/tui/backend/ansi-rows.js +211 -0
  58. package/dist/channels/tui/backend/main-screen.js +207 -0
  59. package/dist/channels/tui/backend/osc.js +91 -0
  60. package/dist/channels/tui/backend/transcript.js +230 -0
  61. package/dist/channels/tui/backend/tui-backend.js +915 -0
  62. package/dist/channels/tui/editor/editor-model.js +543 -0
  63. package/dist/channels/tui/editor/editor-view.js +140 -0
  64. package/dist/channels/tui/editor/editor.js +275 -0
  65. package/dist/channels/tui/editor/undo-stack.js +35 -0
  66. package/dist/channels/tui/editor/visual-lines.js +126 -0
  67. package/dist/channels/tui/editor/word-nav.js +106 -0
  68. package/dist/channels/tui/history/history-viewer.js +403 -0
  69. package/dist/channels/tui/index.js +3 -0
  70. package/dist/channels/tui/layout.js +167 -0
  71. package/dist/channels/tui/markdown/blocks.js +111 -0
  72. package/dist/channels/tui/markdown/inline.js +75 -0
  73. package/dist/channels/tui/markdown/markdown.js +182 -0
  74. package/dist/channels/tui/memory/memory-viewer.js +540 -0
  75. package/dist/channels/tui/overlay/alt-screen.js +105 -0
  76. package/dist/channels/tui/overlay/overlay.js +57 -0
  77. package/dist/channels/tui/overlay/select-confirm.js +156 -0
  78. package/dist/channels/tui/panels/todo-panel.js +54 -0
  79. package/dist/channels/tui/panels/tool-progress-panel.js +93 -0
  80. package/dist/channels/tui/scroll/scroll-view.js +254 -0
  81. package/dist/channels/tui/status/status-line.js +76 -0
  82. package/dist/channels/tui/text.js +38 -0
  83. package/dist/channels/tui/theme.js +41 -0
  84. package/dist/channels/types.js +11 -0
  85. package/dist/channels/ui-core.js +209 -0
  86. package/dist/checkpoint/capture.js +45 -0
  87. package/dist/checkpoint/codes.js +33 -0
  88. package/dist/checkpoint/command.js +82 -0
  89. package/dist/checkpoint/gate.js +48 -0
  90. package/dist/checkpoint/index.js +20 -0
  91. package/dist/checkpoint/restore.js +172 -0
  92. package/dist/checkpoint/store.js +247 -0
  93. package/dist/checkpoint/types.js +4 -0
  94. package/dist/checkpoint/walk.js +146 -0
  95. package/dist/compaction/ccr-tools.js +112 -0
  96. package/dist/compaction/ccr.js +84 -0
  97. package/dist/compaction/codes.js +21 -0
  98. package/dist/compaction/index.js +18 -0
  99. package/dist/compaction/policy.js +200 -0
  100. package/dist/compaction/service.js +422 -0
  101. package/dist/compaction/slots.js +121 -0
  102. package/dist/compaction/types.js +10 -0
  103. package/dist/context/codes.js +40 -0
  104. package/dist/context/events.js +160 -0
  105. package/dist/context/index.js +13 -0
  106. package/dist/context/logger.js +136 -0
  107. package/dist/context/scope.js +182 -0
  108. package/dist/context/workspace.js +104 -0
  109. package/dist/contracts/agent-events.d.ts +73 -0
  110. package/dist/contracts/agent-events.js +16 -0
  111. package/dist/contracts/api.d.ts +249 -0
  112. package/dist/contracts/api.js +299 -0
  113. package/dist/contracts/approval.d.ts +37 -0
  114. package/dist/contracts/approval.js +9 -0
  115. package/dist/contracts/env-ref.d.ts +41 -0
  116. package/dist/contracts/env-ref.js +32 -0
  117. package/dist/contracts/errors.d.ts +56 -0
  118. package/dist/contracts/errors.js +266 -0
  119. package/dist/contracts/events.d.ts +70 -0
  120. package/dist/contracts/events.js +329 -0
  121. package/dist/contracts/index.d.ts +32 -0
  122. package/dist/contracts/index.js +26 -0
  123. package/dist/contracts/llm.d.ts +275 -0
  124. package/dist/contracts/llm.js +12 -0
  125. package/dist/contracts/messages.d.ts +63 -0
  126. package/dist/contracts/messages.js +65 -0
  127. package/dist/contracts/redact.d.ts +46 -0
  128. package/dist/contracts/redact.js +223 -0
  129. package/dist/contracts/tools.d.ts +197 -0
  130. package/dist/contracts/tools.js +33 -0
  131. package/dist/contracts/types.d.ts +302 -0
  132. package/dist/contracts/types.js +50 -0
  133. package/dist/contracts/ui.d.ts +122 -0
  134. package/dist/contracts/ui.js +15 -0
  135. package/dist/conversation/agent-service.js +61 -0
  136. package/dist/conversation/approval-wiring.js +82 -0
  137. package/dist/conversation/backoff.js +38 -0
  138. package/dist/conversation/codes.js +47 -0
  139. package/dist/conversation/control-tools.js +102 -0
  140. package/dist/conversation/control.js +210 -0
  141. package/dist/conversation/driver.js +922 -0
  142. package/dist/conversation/index.js +12 -0
  143. package/dist/conversation/model-visible.js +169 -0
  144. package/dist/conversation/open-tools.js +126 -0
  145. package/dist/conversation/reseed.js +132 -0
  146. package/dist/conversation/sessions.js +215 -0
  147. package/dist/conversation/todo.js +203 -0
  148. package/dist/conversation/types.js +35 -0
  149. package/dist/conversation/wiring.js +204 -0
  150. package/dist/credentials/codes.js +59 -0
  151. package/dist/credentials/commands.js +186 -0
  152. package/dist/credentials/env-ref.js +34 -0
  153. package/dist/credentials/index.js +28 -0
  154. package/dist/credentials/migration.js +18 -0
  155. package/dist/credentials/oauth.js +266 -0
  156. package/dist/credentials/refresh.js +152 -0
  157. package/dist/credentials/secrets.js +118 -0
  158. package/dist/credentials/types.js +30 -0
  159. package/dist/exec/bash.js +279 -0
  160. package/dist/exec/codes.js +37 -0
  161. package/dist/exec/env.js +99 -0
  162. package/dist/exec/environment.js +39 -0
  163. package/dist/exec/git-guard.js +623 -0
  164. package/dist/exec/index.js +24 -0
  165. package/dist/exec/registry.js +120 -0
  166. package/dist/exec/spawn.js +281 -0
  167. package/dist/exec/tail.js +77 -0
  168. package/dist/exec/types.js +5 -0
  169. package/dist/goal/codes.js +33 -0
  170. package/dist/goal/command.js +175 -0
  171. package/dist/goal/fold.js +121 -0
  172. package/dist/goal/gates.js +122 -0
  173. package/dist/goal/index.js +22 -0
  174. package/dist/goal/migration.js +48 -0
  175. package/dist/goal/service.js +501 -0
  176. package/dist/goal/todo-tool.js +174 -0
  177. package/dist/goal/types.js +1 -0
  178. package/dist/goal/update-tool.js +46 -0
  179. package/dist/host/approval-cmd.js +276 -0
  180. package/dist/host/assembly.js +1146 -0
  181. package/dist/host/boot-failures.js +104 -0
  182. package/dist/host/budget-advisory.js +74 -0
  183. package/dist/host/budget-broadcast.js +50 -0
  184. package/dist/host/builtins.js +54 -0
  185. package/dist/host/cli.js +544 -0
  186. package/dist/host/codes.js +120 -0
  187. package/dist/host/config-schema.js +195 -0
  188. package/dist/host/conversation-stack.js +654 -0
  189. package/dist/host/core-plugins.js +1685 -0
  190. package/dist/host/credentials-cmd.js +42 -0
  191. package/dist/host/disclosure.js +30 -0
  192. package/dist/host/dispatch.js +105 -0
  193. package/dist/host/doors-cmd.js +154 -0
  194. package/dist/host/dump-config.js +44 -0
  195. package/dist/host/hook-dispatch-guard.js +27 -0
  196. package/dist/host/import-gate.js +153 -0
  197. package/dist/host/index.js +54 -0
  198. package/dist/host/issue-session.js +241 -0
  199. package/dist/host/loader.js +372 -0
  200. package/dist/host/main.js +161 -0
  201. package/dist/host/manifest.js +381 -0
  202. package/dist/host/mcp-entry.js +53 -0
  203. package/dist/host/plugin-boot.js +952 -0
  204. package/dist/host/plugin-context.js +672 -0
  205. package/dist/host/plugin-install.js +480 -0
  206. package/dist/host/plugin-reload.js +124 -0
  207. package/dist/host/plugin-store.js +462 -0
  208. package/dist/host/plugin-tools.js +359 -0
  209. package/dist/host/plugin-uninstall.js +257 -0
  210. package/dist/host/plugins-cmd.js +302 -0
  211. package/dist/host/plugins-command.js +130 -0
  212. package/dist/host/plugins-config.js +195 -0
  213. package/dist/host/prompt-sections.js +133 -0
  214. package/dist/host/run-entry.js +544 -0
  215. package/dist/host/runtime.js +209 -0
  216. package/dist/host/scheduler-tick.js +294 -0
  217. package/dist/host/serve-daemon.js +501 -0
  218. package/dist/host/serve-entry.js +279 -0
  219. package/dist/host/session-anchor.js +41 -0
  220. package/dist/host/sessions-cmd.js +219 -0
  221. package/dist/host/sessions-face.js +47 -0
  222. package/dist/host/settings-store.js +112 -0
  223. package/dist/host/signals.js +69 -0
  224. package/dist/host/single-instance.js +105 -0
  225. package/dist/host/subagent-factory.js +291 -0
  226. package/dist/host/testkit/harness.js +283 -0
  227. package/dist/host/testkit/index.js +14 -0
  228. package/dist/host/testkit/matrix.js +302 -0
  229. package/dist/host/tool-policy-store.js +177 -0
  230. package/dist/host/triggers.js +212 -0
  231. package/dist/host/tui-entry.js +216 -0
  232. package/dist/host/webui-bridge.js +165 -0
  233. package/dist/issue/codes.js +38 -0
  234. package/dist/issue/filter.js +146 -0
  235. package/dist/issue/github.js +116 -0
  236. package/dist/issue/index.js +17 -0
  237. package/dist/issue/mount.js +70 -0
  238. package/dist/issue/poll.js +58 -0
  239. package/dist/issue/service.js +341 -0
  240. package/dist/issue/tools.js +70 -0
  241. package/dist/issue/types.js +34 -0
  242. package/dist/issue/webhook.js +126 -0
  243. package/dist/llm/codes.js +77 -0
  244. package/dist/llm/complete.js +196 -0
  245. package/dist/llm/events.js +15 -0
  246. package/dist/llm/index.js +24 -0
  247. package/dist/llm/inflight.js +65 -0
  248. package/dist/llm/model-id.js +58 -0
  249. package/dist/llm/provider-face.d.ts +32 -0
  250. package/dist/llm/provider-face.js +34 -0
  251. package/dist/llm/recovery.js +146 -0
  252. package/dist/llm/runtime.js +46 -0
  253. package/dist/llm/stream-fn.js +147 -0
  254. package/dist/lsp/codes.js +31 -0
  255. package/dist/lsp/connection.js +147 -0
  256. package/dist/lsp/frame.js +110 -0
  257. package/dist/lsp/index.js +16 -0
  258. package/dist/lsp/inject.js +58 -0
  259. package/dist/lsp/instance.js +264 -0
  260. package/dist/lsp/service.js +522 -0
  261. package/dist/lsp/tools.js +53 -0
  262. package/dist/lsp/types.js +201 -0
  263. package/dist/mcp/bridge.js +222 -0
  264. package/dist/mcp/codes.js +21 -0
  265. package/dist/mcp/index.js +19 -0
  266. package/dist/mcp/jsonrpc.js +230 -0
  267. package/dist/mcp/service.js +125 -0
  268. package/dist/mcp/tools.js +145 -0
  269. package/dist/mcp/types.js +108 -0
  270. package/dist/memory/cite.js +131 -0
  271. package/dist/memory/codes.js +50 -0
  272. package/dist/memory/command.js +71 -0
  273. package/dist/memory/consolidate.js +219 -0
  274. package/dist/memory/cycle.js +109 -0
  275. package/dist/memory/dao.js +878 -0
  276. package/dist/memory/diff.js +238 -0
  277. package/dist/memory/extract.js +181 -0
  278. package/dist/memory/fts.js +58 -0
  279. package/dist/memory/index.js +42 -0
  280. package/dist/memory/inject.js +354 -0
  281. package/dist/memory/merge.js +178 -0
  282. package/dist/memory/migration.js +132 -0
  283. package/dist/memory/pollution.js +59 -0
  284. package/dist/memory/port.js +385 -0
  285. package/dist/memory/review.js +201 -0
  286. package/dist/memory/scan.js +89 -0
  287. package/dist/memory/tools.js +508 -0
  288. package/dist/memory/types.js +140 -0
  289. package/dist/obs/codes.js +38 -0
  290. package/dist/obs/db.js +115 -0
  291. package/dist/obs/index.js +20 -0
  292. package/dist/obs/rollup.js +61 -0
  293. package/dist/obs/service.js +363 -0
  294. package/dist/obs/session-tools.js +282 -0
  295. package/dist/obs/session-view.js +225 -0
  296. package/dist/obs/tool.js +97 -0
  297. package/dist/obs/types.js +1 -0
  298. package/dist/persist/audit.js +63 -0
  299. package/dist/persist/aux.js +39 -0
  300. package/dist/persist/codes.js +35 -0
  301. package/dist/persist/index.js +30 -0
  302. package/dist/persist/load-history.js +50 -0
  303. package/dist/persist/migrations.js +37 -0
  304. package/dist/persist/paths.js +108 -0
  305. package/dist/persist/persistence.js +229 -0
  306. package/dist/persist/schema.js +99 -0
  307. package/dist/persist/secret-box.js +95 -0
  308. package/dist/persist/store.js +706 -0
  309. package/dist/persist/write-behind.js +238 -0
  310. package/dist/safety/approval.js +171 -0
  311. package/dist/safety/bwrap.js +123 -0
  312. package/dist/safety/codes.js +57 -0
  313. package/dist/safety/danger.js +505 -0
  314. package/dist/safety/gate.js +226 -0
  315. package/dist/safety/index.js +24 -0
  316. package/dist/safety/presets.js +73 -0
  317. package/dist/safety/roots.js +139 -0
  318. package/dist/safety/sandbox.js +214 -0
  319. package/dist/safety/seatbelt.js +88 -0
  320. package/dist/safety/sensitive.js +34 -0
  321. package/dist/safety/tool-policy.js +203 -0
  322. package/dist/safety/types.js +13 -0
  323. package/dist/scheduler/codes.js +54 -0
  324. package/dist/scheduler/cron-backend.js +135 -0
  325. package/dist/scheduler/engine.js +280 -0
  326. package/dist/scheduler/gates.js +60 -0
  327. package/dist/scheduler/index.js +25 -0
  328. package/dist/scheduler/migration.js +22 -0
  329. package/dist/scheduler/runner.js +171 -0
  330. package/dist/scheduler/schedule.js +193 -0
  331. package/dist/scheduler/service.js +283 -0
  332. package/dist/scheduler/tick.js +139 -0
  333. package/dist/scheduler/types.js +1 -0
  334. package/dist/sdk/http.js +865 -0
  335. package/dist/sdk/index.js +22 -0
  336. package/dist/sdk/mcp.js +289 -0
  337. package/dist/sdk/plugin-route-registry.js +126 -0
  338. package/dist/sdk/plugin-routes.js +149 -0
  339. package/dist/sdk/security.js +116 -0
  340. package/dist/sdk/types.js +32 -0
  341. package/dist/session/budget.js +159 -0
  342. package/dist/session/codes.js +40 -0
  343. package/dist/session/derive.js +180 -0
  344. package/dist/session/event-data.js +1 -0
  345. package/dist/session/fork.js +72 -0
  346. package/dist/session/import-gates.js +148 -0
  347. package/dist/session/index.js +18 -0
  348. package/dist/session/recover.js +103 -0
  349. package/dist/session/session.js +279 -0
  350. package/dist/session/snapshot.js +107 -0
  351. package/dist/skills/agents.js +242 -0
  352. package/dist/skills/codes.js +51 -0
  353. package/dist/skills/discovery.js +266 -0
  354. package/dist/skills/frontmatter.js +215 -0
  355. package/dist/skills/index.js +28 -0
  356. package/dist/skills/load.js +118 -0
  357. package/dist/skills/manage.js +201 -0
  358. package/dist/skills/registry.js +138 -0
  359. package/dist/skills/render.js +124 -0
  360. package/dist/skills/sections.js +171 -0
  361. package/dist/skills/types.js +21 -0
  362. package/dist/subagent/codes.js +50 -0
  363. package/dist/subagent/declarative.js +55 -0
  364. package/dist/subagent/index.js +19 -0
  365. package/dist/subagent/notify.js +51 -0
  366. package/dist/subagent/provide.js +7 -0
  367. package/dist/subagent/registry.js +149 -0
  368. package/dist/subagent/service.js +338 -0
  369. package/dist/subagent/surface.js +44 -0
  370. package/dist/subagent/tool.js +153 -0
  371. package/dist/subagent/types.js +25 -0
  372. package/dist/tools/apply-patch.js +164 -0
  373. package/dist/tools/codes.js +109 -0
  374. package/dist/tools/fs.js +501 -0
  375. package/dist/tools/index.js +17 -0
  376. package/dist/tools/observed.js +99 -0
  377. package/dist/tools/pipeline.js +176 -0
  378. package/dist/tools/protected-read.js +54 -0
  379. package/dist/tools/registry.js +250 -0
  380. package/dist/tools/search.js +451 -0
  381. package/dist/tools/worktree.js +327 -0
  382. package/dist/web/codes.js +37 -0
  383. package/dist/web/gate.js +43 -0
  384. package/dist/web/hygiene.js +195 -0
  385. package/dist/web/index.js +16 -0
  386. package/dist/web/service.js +207 -0
  387. package/dist/web/ssrf-guard.js +44 -0
  388. package/dist/web/tool.js +76 -0
  389. package/dist/web/types.js +10 -0
  390. package/dist/webui/assets/index-B-t2O8gh.js +22 -0
  391. package/dist/webui/assets/index-DOlgSZ9Q.css +2 -0
  392. package/dist/webui/index.html +14 -0
  393. package/dist/webui/index.js +14 -0
  394. package/dist/webui/server.js +549 -0
  395. package/dist/webui/types.js +38 -0
  396. package/examples/README.md +26 -0
  397. package/examples/minimal-code-plugin/entry.js +43 -0
  398. package/examples/minimal-code-plugin/package.json +12 -0
  399. package/examples/pure-skill-pack/package.json +14 -0
  400. package/examples/pure-skill-pack/skills/markdown-table/SKILL.md +32 -0
  401. package/package.json +86 -0
@@ -0,0 +1,135 @@
1
+ /**
2
+ * cron 可选后端乙案(04 §12:缺省进程内挂钟〔甲案〕;OS cron 后端 = 宿主
3
+ * 停机期不断保的可选增强)。写用户 crontab 的编舞:
4
+ *
5
+ * - 自有条目行尾标记 `# berry-agent:<名>`(对外声明值位——去品牌化例外);
6
+ * 读改写全程保留他人行(只增删自有标记行);
7
+ * - 人面授权链(authorize 注入——写 crontab 是改用户系统态,授权缺席
8
+ * 即未授权:亮拒 SCHEDULER_CRON_UNAUTHORIZED,不猜默认放行);
9
+ * - win32 诚实拒(crontab 生态不在——SCHEDULER_CRON_UNSUPPORTED,不装能);
10
+ * - schedule 可表达性:cron 分钟粒度——sub-minute 间隔与非 60 倍数分钟
11
+ * 间隔拒(UNSUPPORTED);once 形拒(cron 表达不了单发——单发归进程内
12
+ * 挂钟独辖);every/daily/weekly 可表达形全量映射;
13
+ * - 写失败(crontab 非零退出)SCHEDULER_CRON_WRITE_FAILED。
14
+ *
15
+ * 联动契约(service 消费):register/unregister **同步**亮拒——调用方(行
16
+ * 翻转/删行前的 service 面)先 OS 后行库的序,错在 OS 段即行未动(不半态);
17
+ * 故本件用 spawnSync 直读直写(启停/删行低频动作,短暂阻塞可接受)。
18
+ */
19
+ import { spawnSync } from 'node:child_process';
20
+ import { BaseError } from '../contracts/index.js';
21
+ /** 自有条目标记(行尾注释——读改写的身份判据;对外声明值位去品牌化例外) */
22
+ export function cronMarker(name) {
23
+ return `# berry-agent:${name}`;
24
+ }
25
+ /**
26
+ * schedule → cron 表达式(纯函数,导出供测试)。
27
+ *
28
+ * day 编号两系同构(0=周日……6=周六:Date.getDay 与 cron dow 一致)——直接映射。
29
+ */
30
+ export function scheduleToCron(s) {
31
+ switch (s.kind) {
32
+ case 'every': {
33
+ if (s.seconds % 60 !== 0) {
34
+ throw new BaseError('SCHEDULER_CRON_UNSUPPORTED', `cron 分钟粒度:every:${s.seconds}s 非 60 整除秒不可表达(进程内挂钟可跑——OS 后端不挂)`);
35
+ }
36
+ const minutes = s.seconds / 60;
37
+ if (minutes === 1)
38
+ return '* * * * *';
39
+ if (minutes < 60)
40
+ return `*/${minutes} * * * *`;
41
+ if (minutes % 60 === 0) {
42
+ const hours = minutes / 60;
43
+ return hours === 1 ? '0 * * * *' : `0 */${hours} * * *`;
44
+ }
45
+ throw new BaseError('SCHEDULER_CRON_UNSUPPORTED', `cron 表达不了 every:${s.seconds}s(非整分钟/整小时间隔——进程内挂钟可跑)`);
46
+ }
47
+ case 'daily': {
48
+ const [h, m] = s.time.split(':');
49
+ return `${Number(m)} ${Number(h)} * * *`; // 域序换位 + 去零垫(cron 规范形)
50
+ }
51
+ case 'weekly': {
52
+ const [h, min] = s.time.split(':');
53
+ const dow = [...s.days].sort((a, b) => a - b).join(',');
54
+ return `${Number(min)} ${Number(h)} * * ${dow}`;
55
+ }
56
+ case 'once':
57
+ throw new BaseError('SCHEDULER_CRON_UNSUPPORTED', 'cron 表达不了 once 单发形(单发归进程内挂钟独辖——行停机错过即跳过)');
58
+ }
59
+ }
60
+ /** OS crontab 注册器实装(同步编舞——联动契约见头注) */
61
+ export function createOsCronRegistrar(deps = {}) {
62
+ const platform = deps.platform ?? process.platform;
63
+ const command = deps.command ?? 'berry-agent';
64
+ const exec = deps.execCrontab ?? execCrontabViaSpawnSync;
65
+ /** 平台前置(两动词同判——win32 无 crontab 生态,读写皆不可为) */
66
+ function assertPlatform() {
67
+ if (platform === 'win32') {
68
+ throw new BaseError('SCHEDULER_CRON_UNSUPPORTED', 'win32 无 crontab 生态——OS cron 后端不支持(缺省进程内挂钟形态不受影响)');
69
+ }
70
+ }
71
+ /** 授权前置(写动作判——读 crontab 无害不判;注销摘自有行属收敛不判) */
72
+ function assertAuthorized() {
73
+ if (!deps.authorize || !deps.authorize()) {
74
+ throw new BaseError('SCHEDULER_CRON_UNAUTHORIZED', 'OS cron 后端未获人面授权(写用户 crontab 须显式授权——不猜默认放行)');
75
+ }
76
+ }
77
+ /** 读当前 crontab(无 crontab = 空表——exit 1 且空 stdout 视为常态非错误) */
78
+ function readCrontab() {
79
+ const r = exec(['-l']);
80
+ if (r.code === 0)
81
+ return r.stdout;
82
+ if (r.code === 1 && r.stdout.trim() === '')
83
+ return ''; // 「no crontab for <user>」常态
84
+ throw new BaseError('SCHEDULER_CRON_WRITE_FAILED', `crontab -l 读失败(exit ${r.code}):${r.stderr.trim() || '无 stderr'}`);
85
+ }
86
+ /** 写新 crontab(stdin 注入形;非零退出亮拒) */
87
+ function writeCrontab(content) {
88
+ const r = exec(['-'], content.endsWith('\n') || content === '' ? content : `${content}\n`);
89
+ if (r.code !== 0) {
90
+ throw new BaseError('SCHEDULER_CRON_WRITE_FAILED', `crontab 写入失败(exit ${r.code}):${r.stderr.trim() || '无 stderr'}`);
91
+ }
92
+ }
93
+ /** 自有标记行摘除(他人行原样保留;返回 [新内容, 是否有摘除]) */
94
+ function stripMarkerLines(content, name) {
95
+ const marker = cronMarker(name);
96
+ const kept = [];
97
+ let removed = false;
98
+ for (const line of content.split('\n')) {
99
+ if (line.includes(marker))
100
+ removed = true;
101
+ else
102
+ kept.push(line);
103
+ }
104
+ return [kept.join('\n'), removed];
105
+ }
106
+ return {
107
+ register(row) {
108
+ assertPlatform();
109
+ const cron = scheduleToCron(row.schedule); // 可表达性判先于授权(不授权也先知道形不行)
110
+ assertAuthorized();
111
+ const current = readCrontab();
112
+ const [withoutSelf] = stripMarkerLines(current, row.name);
113
+ const entry = `${cron} ${command} run --read-only --tick ${row.name} ${cronMarker(row.name)}`;
114
+ const next = withoutSelf.trim() === '' ? entry : `${withoutSelf.trimEnd()}\n${entry}`;
115
+ writeCrontab(next);
116
+ },
117
+ unregister(name) {
118
+ assertPlatform();
119
+ const current = readCrontab();
120
+ const [next, removed] = stripMarkerLines(current, name);
121
+ if (!removed)
122
+ return; // 无自有条目——no-op(不重写用户 crontab)
123
+ writeCrontab(next);
124
+ },
125
+ };
126
+ }
127
+ /** 缺省 crontab 执行器(spawnSync;错误折 exit code——-1 = spawn 失败) */
128
+ function execCrontabViaSpawnSync(args, input) {
129
+ const r = spawnSync('crontab', args, input === undefined ? undefined : { input });
130
+ return {
131
+ stdout: r.stdout?.toString() ?? '',
132
+ stderr: r.stderr?.toString() ?? '',
133
+ code: r.error ? -1 : (r.status ?? -1),
134
+ };
135
+ }
@@ -0,0 +1,280 @@
1
+ /**
2
+ * scheduler 引擎(04 §12 缺省甲案:进程内挂钟)。
3
+ *
4
+ * 编舞:轮询求 due 集 → 闸评估(clock 道独辖)→ 执行前抢占(同任务在飞先
5
+ * kill 'preempted' 再起新——同任务不并发双跑)→ runner spawn → 墙钟超时
6
+ * 守卫 → 结算回写(last_fire_at/last_outcome/next_fire_at 推进)。
7
+ *
8
+ * 语义钉死(与 schedule.ts 三钉同源):
9
+ * - 错过不重放:start() 重启补推进——启机时既 due 的行静默 advance 到
10
+ * 下一刻(不补跑、只 warn 留痕);
11
+ * - gated 拦截也推进:闸拦的行 settleGated 记结局 + advance(不动
12
+ * last_fire_at——gated 非真跑,冷却闸判据不被污染);
13
+ * - manual 道不走闸(用户显式意图)也不受并发帽辖(pi-tick manual 同律);
14
+ * 帽只辖 clock sweep 的自动并发。
15
+ *
16
+ * claim-then-advance 接线律(04 §12 无人值守执行链定形注③——u-2 落码):
17
+ * fire 起跑 setActive 记账(activePid = runner 句柄 pid——甲案宿主 pid/
18
+ * 乙案子进程 pid)、settle 清账——「执行前抢占」条款的 durable 对偶(进程
19
+ * 内注册表管进程内防双跑,activePid 管跨进程可见性);start() 僵行清扫
20
+ * (pid 死 + 超墙钟 → 行回 idle 可重发)+ fire 前跨进程在飞判定(乙案
21
+ * 并存窗让位的引擎侧对偶——见 fireRow)。墙钟 kill 语义由 runner 实装
22
+ * 定义(定形注④两形:进程内实装 = interrupt 协作中止;进程实装 = TERM
23
+ * →KILL)——引擎只认 kill 接口零感知。
24
+ *
25
+ * 定时器全接缝注入(TimerSeam——测试假钟驱动;缺省真身 setTimeout)。
26
+ * 停钟不杀在飞:stop 只摘轮询定时器,在飞实例自然收场照常结算回写
27
+ * (宿主退出编舞若需「杀在飞」属 host 装配批 killAll 编舞,不在本件)。
28
+ */
29
+ import { kill as processKill, pid as processPid } from 'node:process';
30
+ import { BaseError } from '../contracts/index.js';
31
+ import { evaluateGates } from './gates.js';
32
+ import { nextFireAt } from './schedule.js';
33
+ /** 墙钟超时缺省(毫秒)——单次 fire 的最长在飞时帽(30min;pi-tick 同量级) */
34
+ export const FIRE_WALL_TIMEOUT_MS = 30 * 60_000;
35
+ /** 轮询下限(毫秒)——due 等待的定时夹取下限(防零毫秒自旋;every:5s 粒度下无害) */
36
+ export const MIN_POLL_MS = 500;
37
+ /** 轮询上限(毫秒)——无近期 due 时的 belt 巡检间隔(行可能被外部 enable,最迟此距补觉) */
38
+ export const MAX_POLL_MS = 60_000;
39
+ /** clock sweep 并发帽缺省(1——单并发保守档:同时至多一个自动任务在飞) */
40
+ export const DEFAULT_MAX_CONCURRENT = 1;
41
+ /** 缺省真身定时器 */
42
+ export function realTimerSeam() {
43
+ return {
44
+ set(ms, fn) {
45
+ return setTimeout(fn, ms);
46
+ },
47
+ clear(handle) {
48
+ clearTimeout(handle);
49
+ },
50
+ };
51
+ }
52
+ /** 引擎实装 */
53
+ export function createSchedulerEngine(deps) {
54
+ const dao = deps.dao;
55
+ const runner = deps.runner;
56
+ const now = deps.now;
57
+ const warn = deps.warn;
58
+ const timers = deps.timers ?? realTimerSeam();
59
+ const wallTimeoutMs = deps.wallTimeoutMs ?? FIRE_WALL_TIMEOUT_MS;
60
+ const maxConcurrent = deps.maxConcurrent ?? DEFAULT_MAX_CONCURRENT;
61
+ const gateFacts = deps.gateFacts ?? (() => ({}));
62
+ const isPidAlive = deps.isPidAlive ?? realIsPidAlive;
63
+ /** 同名在飞注册表(键=行名——抢占判据面) */
64
+ const active = new Map();
65
+ /**
66
+ * 同名起跑预留位(fireRow 入口同步置、收场清)——sweep 判 active ∪ 本位:
67
+ * active.set 落在首个 await(spawn)之后,无预留位时同步连呼 sweep 会
68
+ * 在微任务窗内重复起同名任务(在飞判据穿透)。manual 道不判本位(抢占语义)。
69
+ */
70
+ const reserved = new Set();
71
+ let pollHandle = null;
72
+ let running = false;
73
+ let sweeping = false;
74
+ /** 下一轮轮询定时(夹取 [MIN_POLL_MS, MAX_POLL_MS]——null earliest 落 MAX belt 巡检) */
75
+ function scheduleNextPoll() {
76
+ if (!running)
77
+ return;
78
+ if (pollHandle !== null) {
79
+ timers.clear(pollHandle);
80
+ pollHandle = null;
81
+ }
82
+ const earliest = dao.earliestNextFire();
83
+ let ms = MAX_POLL_MS;
84
+ if (earliest !== null) {
85
+ const delta = Date.parse(earliest) - Date.parse(now());
86
+ ms = Math.min(Math.max(delta, MIN_POLL_MS), MAX_POLL_MS);
87
+ }
88
+ pollHandle = timers.set(ms, () => {
89
+ pollHandle = null;
90
+ doSweep();
91
+ });
92
+ }
93
+ return {
94
+ start() {
95
+ if (running)
96
+ return;
97
+ running = true;
98
+ // 僵行清扫(定形注③——u-2):activePid 非空非己的行,pid 死 + 超起跑
99
+ // 墙钟(activeStartedAt + wallTimeoutMs)→ 清账行回 idle + lastOutcome
100
+ // 记回收(settleGated 形不动 last_fire_at——非真跑)。与关系保守判:
101
+ // 活 + 超钟形(pid 复用误判险)不清、下轮 fire 的 setActive 覆写自愈。
102
+ for (const row of dao.list()) {
103
+ if (row.activePid === null || row.activePid === processPid)
104
+ continue; // 己 pid = 甲案在飞(进程内注册表管辖)
105
+ const startedAtMs = row.activeStartedAt !== null ? Date.parse(row.activeStartedAt) : 0;
106
+ const expired = Date.parse(now()) - startedAtMs > wallTimeoutMs;
107
+ if (!expired || isPidAlive(row.activePid))
108
+ continue;
109
+ const outcome = {
110
+ trigger: 'clock',
111
+ reason: 'killed',
112
+ error: `僵行回收(activePid ${row.activePid} 已死且超墙钟——乙案子进程 kill -9 残账/宿主重启旧账)`,
113
+ finishedAt: now(),
114
+ };
115
+ dao.setActive(row.name, null, null, now());
116
+ dao.settleGated(row.name, outcome, row.nextFireAt, now());
117
+ warn(`[scheduler] 僵行清扫:${row.name}(activePid ${row.activePid} 死 + 超墙钟——行回 idle 可重发)`);
118
+ }
119
+ // 重启补推进:既 due 行静默跳下一刻(错过不重放——warn 留痕不 fire)
120
+ const missed = dao.due(now());
121
+ for (const row of missed) {
122
+ const next = nextFireAt(row.schedule, new Date(now()));
123
+ dao.advanceNextFire(row.name, next, now());
124
+ warn(`[scheduler] 重启补推进:${row.name} 错过到点 ${row.nextFireAt},跳下一刻 ${next}`);
125
+ }
126
+ scheduleNextPoll();
127
+ },
128
+ stop() {
129
+ running = false;
130
+ if (pollHandle !== null) {
131
+ timers.clear(pollHandle);
132
+ pollHandle = null;
133
+ }
134
+ },
135
+ poke() {
136
+ if (!running)
137
+ return;
138
+ scheduleNextPoll();
139
+ },
140
+ sweep: doSweep,
141
+ async fireNow(name, trigger) {
142
+ const row = dao.get(name);
143
+ if (!row)
144
+ throw new BaseError('SCHEDULER_NOT_FOUND', `任务「${name}」不存在(run 幽灵名零行守卫)`);
145
+ return fireRow(row, trigger);
146
+ },
147
+ get inFlightCount() {
148
+ // 观察面诚实律:active.set 落在 spawn 的 await 之后,spawn 已发起但未上
149
+ // 注册表的瞬态窗内单计 active 会给同步观察者假 0(fireRow 已在跑)——
150
+ // 计 active ∪ reserved,「在飞 = 起跑中或已 spawn」。
151
+ return new Set([...active.keys(), ...reserved]).size;
152
+ },
153
+ };
154
+ /**
155
+ * 单行触发共核(clock/manual/cron 三道同编舞):
156
+ * 抢占同任务旧实例 → 闸评估(clock/cron 道;manual 直通)→ spawn →
157
+ * 墙钟守卫 → 结算回写(gated 走 settleGated 不动 last_fire_at)。
158
+ */
159
+ async function fireRow(row, trigger) {
160
+ const name = row.name;
161
+ reserved.add(name); // 同步占位(见 reserved 注释——防 sweep 微任务窗重触)
162
+ try {
163
+ // 执行前抢占:同任务旧实例在飞先杀再起(kill 幂等 + 等收场——同任务不并发双跑)
164
+ const prev = active.get(name);
165
+ if (prev) {
166
+ prev.kill('preempted');
167
+ await prev.settled;
168
+ }
169
+ // 闸评估:manual 道不走(用户显式意图);clock/cron 道序定五门
170
+ if (trigger !== 'manual') {
171
+ const block = evaluateGates(gateFacts(row), { now: now() });
172
+ if (block) {
173
+ const firedAt = now();
174
+ const next = nextFireAt(row.schedule, new Date(firedAt));
175
+ const outcome = {
176
+ trigger,
177
+ reason: 'gated',
178
+ gate: block.gate,
179
+ error: block.reason,
180
+ finishedAt: firedAt,
181
+ };
182
+ dao.settleGated(name, outcome, next, now());
183
+ return outcome;
184
+ }
185
+ }
186
+ const firedAt = now();
187
+ // 跨进程在飞判定(定形注③乙案并存窗的引擎侧对偶——u-2):行 activePid
188
+ // 非空非己且活体未超墙钟 = 乙案子进程(或他宿主)真在飞 → 本轮让位
189
+ // (gated 记结局 + 推进,不 kill 不双跑——跨进程 kill 有 pid 复用误杀
190
+ // 险,「执行前抢占」条款对跨进程实例不越权;子进程侧对偶 = run-entry
191
+ // --tick 让位律,双向对称)。死/超钟形不拦(settle 段覆写自愈)。
192
+ if (row.activePid !== null && row.activePid !== processPid && isPidAlive(row.activePid)) {
193
+ const startedAtMs = row.activeStartedAt !== null ? Date.parse(row.activeStartedAt) : 0;
194
+ const expired = Date.parse(now()) - startedAtMs > wallTimeoutMs;
195
+ if (!expired) {
196
+ const next = nextFireAt(row.schedule, new Date(firedAt));
197
+ const outcome = {
198
+ trigger,
199
+ reason: 'gated',
200
+ error: `跨进程实例在飞(activePid ${row.activePid})——本轮让位不双跑`,
201
+ finishedAt: firedAt,
202
+ };
203
+ dao.settleGated(name, outcome, next, now());
204
+ return outcome;
205
+ }
206
+ }
207
+ const handle = await runner.spawn({ row, trigger, wallTimeoutMs });
208
+ active.set(name, handle);
209
+ // claim-then-advance 记账(定形注③):fire 起跑落 activePid(runner 句柄
210
+ // pid——甲案宿主 pid/乙案子进程 pid)+ 起跑墙钟;settle 清(对偶位)
211
+ if (handle.pid !== null)
212
+ dao.setActive(name, handle.pid, now(), now());
213
+ // 墙钟超时守卫(kill('timeout')——TERM→宽限→KILL 由 runner 实装升级)
214
+ const wallHandle = timers.set(wallTimeoutMs, () => handle.kill('timeout'));
215
+ let outcome;
216
+ try {
217
+ outcome = await handle.settled;
218
+ }
219
+ finally {
220
+ timers.clear(wallHandle);
221
+ active.delete(name);
222
+ dao.setActive(name, null, null, now()); // durable 在飞占用面清账(claim 对偶)
223
+ }
224
+ // next 从结算时刻取下一刻(every 形锚 now——错过不重放同律);
225
+ // runner 内零跑判定(gated——wake 未落地/分派处理器缺席)同走 settleGated
226
+ // 不动 last_fire_at(与闸拦同律:非真跑不污染冷却闸判据)
227
+ const next = nextFireAt(row.schedule, new Date(now()));
228
+ if (outcome.reason === 'gated') {
229
+ dao.settleGated(name, outcome, next, now());
230
+ }
231
+ else {
232
+ dao.settleFire(name, firedAt, outcome, next, now());
233
+ }
234
+ return outcome;
235
+ }
236
+ finally {
237
+ reserved.delete(name);
238
+ }
239
+ }
240
+ /** 巡检体(定时回调与公开 sweep 直呼同体——函数声明提升供 scheduleNextPoll 前向引用) */
241
+ function doSweep() {
242
+ if (!running || sweeping)
243
+ return; // 重入闸:定时回调与手动直呼不叠跑
244
+ sweeping = true;
245
+ try {
246
+ const due = dao.due(now());
247
+ // 帽初值同观察面:起跑预留位(spawn 的 await 前瞬态)也占帽——同帽语义
248
+ let launched = new Set([...active.keys(), ...reserved]).size;
249
+ for (const row of due) {
250
+ // 在飞/起跑预留不重触:settle 前 next_fire_at 仍 due,若重触会自踏
251
+ // 活锁(重复抢占自家实例)——抢占只留给 manual 新实例道。在飞期间
252
+ // 持续 poll 是廉价 belt 巡检(settle 即推进)。
253
+ if (active.has(row.name) || reserved.has(row.name))
254
+ continue;
255
+ if (launched >= maxConcurrent)
256
+ break; // 帽满留 due——下轮 sweep 补觉(定时夹取防自旋)
257
+ launched += 1;
258
+ void fireRow(row, 'clock');
259
+ }
260
+ }
261
+ finally {
262
+ sweeping = false;
263
+ }
264
+ scheduleNextPoll();
265
+ }
266
+ }
267
+ /**
268
+ * pid 活死判真身(0 信号探活——不投递信号只验存在性):ESRCH = 进程不存在
269
+ * (死);EPERM = 存在但非己属(活——他用户/系统进程照算活体)。run-entry
270
+ * --tick 让位律与引擎清扫/判定共用单源(u-2 定形注③)。
271
+ */
272
+ export function realIsPidAlive(pid) {
273
+ try {
274
+ processKill(pid, 0);
275
+ return true;
276
+ }
277
+ catch (err) {
278
+ return err.code === 'EPERM';
279
+ }
280
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * DiscoveryGates 触发前置条件门(04 §12 /tick 瘦身条「照搬」——蓝本
3
+ * pilotdeck always-on DiscoveryGates 的纯闸评估序:**序定 first-failing-wins**,
4
+ * 单 Agent 场景裁多 project 位〔project_disabled/project_missing/
5
+ * dormant_no_signal/lock_busy 四门系 pilotdeck 多项目形态私有〕留五门)。
6
+ *
7
+ * 闸序(序即语义——前面的门先判,先拦先报):
8
+ * 1. job_disabled 行启停位关(引擎 due 集已滤——belt 位,闸面仍判)
9
+ * 2. agent_busy 宿主前台在飞(单活跃机——定时任务不与用户对话抢跑)
10
+ * 3. recent_user_msg 用户静默窗内(刚说过话的窗口期不打扰)
11
+ * 4. cooldown 同任务上次触发后的冷却窗内(防高频任务空转连发)
12
+ * 5. daily_budget 当日后台预算 canAfford 闸(04 §5——缺预算跳本轮下周期再试)
13
+ *
14
+ * 判据全注入(GateFacts 由装配层从宿主面收集;评估器零 IO 零时钟)——
15
+ * 缺席事实 = 闸放行(探针未装配的门不咬——fail-open 属实:这些门是打扰
16
+ * 礼仪与预算面,非安全边界;安全边界在 runner 的 --read-only 与审批
17
+ * fail-closed)。
18
+ */
19
+ /** 闸 id 序(评估序即语义序——勿重排:便宜的门在前、需要事实的门在后) */
20
+ export const GATE_ORDER = ['job_disabled', 'agent_busy', 'recent_user_msg', 'cooldown', 'daily_budget'];
21
+ /** 用户静默窗(毫秒)——窗内近期用户消息即拦(不打扰正在交互的用户) */
22
+ export const USER_QUIET_WINDOW_MS = 5 * 60_000;
23
+ /** 同任务冷却窗(毫秒)——上次触发后此窗内不再连发 */
24
+ export const JOB_COOLDOWN_MS = 60_000;
25
+ /**
26
+ * 序定闸评估:依 GATE_ORDER 逐门判、先拦先报。
27
+ *
28
+ * manual 触发不走本评估(/tick run = 用户显式意图——04 §12 未辖手动道,
29
+ * pi-tick manual 同律不设闸);本函数只辖挂钟/cron 到点道。
30
+ */
31
+ export function evaluateGates(facts, ctx) {
32
+ const nowMs = Date.parse(ctx.now);
33
+ // 1. job_disabled——行关即拦(belt:due 集已滤 enabled,闸面独立判一次)
34
+ if (facts.enabled === false) {
35
+ return { gate: 'job_disabled', reason: '任务行处于停用态' };
36
+ }
37
+ // 2. agent_busy——前台在飞即拦(本轮跳过,advance 到下一刻)
38
+ if (facts.agentBusy === true) {
39
+ return { gate: 'agent_busy', reason: '宿主前台对话在飞' };
40
+ }
41
+ // 3. recent_user_msg——静默窗内有用户消息即拦
42
+ if (facts.lastUserMessageAt) {
43
+ const at = Date.parse(facts.lastUserMessageAt);
44
+ if (!Number.isNaN(at) && nowMs - at < USER_QUIET_WINDOW_MS) {
45
+ return { gate: 'recent_user_msg', reason: `静默窗(${USER_QUIET_WINDOW_MS / 1000}s)内有用户消息` };
46
+ }
47
+ }
48
+ // 4. cooldown——同任务冷却窗内即拦
49
+ if (facts.lastFireAt) {
50
+ const at = Date.parse(facts.lastFireAt);
51
+ if (!Number.isNaN(at) && nowMs - at < JOB_COOLDOWN_MS) {
52
+ return { gate: 'cooldown', reason: `上次触发后冷却窗(${JOB_COOLDOWN_MS / 1000}s)内` };
53
+ }
54
+ }
55
+ // 5. daily_budget——canAfford 假即拦(跳本轮,下周期再试)
56
+ if (facts.canAfford === false) {
57
+ return { gate: 'daily_budget', reason: '当日后台预算不可负担(canAfford 假)' };
58
+ }
59
+ return null;
60
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * scheduler 件公开面(02 §4.1 席 22 [L3, deps contracts+context+persist]:
3
+ * jobs 表 + 进程内挂钟 + /tick + DiscoveryGates + cron 可选后端;04 §12)。
4
+ *
5
+ * 装载态集成归批 12 装载面后装配批:ctx.schedule 消费面(03 §10.7)、
6
+ * channels /tick 注册、--tick CLI 编舞、真 bin spawn 接线、GateFacts 从宿主
7
+ * 面收集(agentBusy/lastUserMessageAt/canAfford)——本件纯逻辑 + 全接缝注入。
8
+ */
9
+ import './codes.js';
10
+ // schedule 串词法/校验/推刻(纯函数)
11
+ export { formatSchedule, MIN_INTERVAL_SECONDS, nextFireAt, parseSchedule, resolveRelativeOnce, } from './schedule.js';
12
+ // 迁移
13
+ export { SCHEDULER_MIGRATION } from './migration.js';
14
+ // DiscoveryGates 触发前置条件门
15
+ export { evaluateGates, GATE_ORDER, JOB_COOLDOWN_MS, USER_QUIET_WINDOW_MS, } from './gates.js';
16
+ // jobs 表 DAO + 六动词服务面 + GoalJobsFace 第五槽
17
+ export { createSchedulerService, JobsDao, } from './service.js';
18
+ // runner(执行接缝 + 进程实装)
19
+ export { createProcessRunnerFactory, } from './runner.js';
20
+ // 进程内挂钟引擎
21
+ export { createSchedulerEngine, DEFAULT_MAX_CONCURRENT, FIRE_WALL_TIMEOUT_MS, MAX_POLL_MS, MIN_POLL_MS, realIsPidAlive, realTimerSeam, } from './engine.js';
22
+ // cron 可选后端(OS 注册器乙案)
23
+ export { createOsCronRegistrar, cronMarker, scheduleToCron, } from './cron-backend.js';
24
+ // /tick 命令处理器(纯程序面——argv → 人读文本)
25
+ export { describeOutcome, runTickCommand, TICK_USAGE } from './tick.js';
@@ -0,0 +1,22 @@
1
+ export const SCHEDULER_MIGRATION = {
2
+ version: 2,
3
+ name: 'scheduler-jobs',
4
+ sql: `
5
+ CREATE TABLE jobs (
6
+ name TEXT PRIMARY KEY,
7
+ prompt TEXT NOT NULL,
8
+ cwd TEXT,
9
+ schedule TEXT NOT NULL,
10
+ enabled INTEGER NOT NULL DEFAULT 0,
11
+ builtin INTEGER NOT NULL DEFAULT 0,
12
+ created_at TEXT NOT NULL,
13
+ updated_at TEXT NOT NULL,
14
+ next_fire_at TEXT,
15
+ last_fire_at TEXT,
16
+ last_outcome TEXT,
17
+ active_pid INTEGER,
18
+ active_started_at TEXT
19
+ );
20
+ CREATE INDEX idx_jobs_next_fire ON jobs (next_fire_at) WHERE enabled = 1 AND next_fire_at IS NOT NULL;
21
+ `,
22
+ };