evolcore 0.0.1 → 0.0.2

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 (371) hide show
  1. package/CHANGELOG.md +813 -0
  2. package/LICENSE +21 -0
  3. package/MIGRATION-0.5.0.md +378 -0
  4. package/README.md +318 -14
  5. package/ROLE_ACCESS_CONTROL.md +174 -0
  6. package/assets/.env.template +4 -0
  7. package/assets/brand/evolcore/README.md +19 -0
  8. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  9. package/assets/brand/evolcore/evolcore-app-icon.svg +13 -0
  10. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  11. package/assets/brand/evolcore/evolcore-brand-board.svg +126 -0
  12. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  13. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  14. package/assets/brand/evolcore/evolcore-logo-reverse.svg +14 -0
  15. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  16. package/assets/brand/evolcore/evolcore-logo.svg +14 -0
  17. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  18. package/assets/brand/evolcore/evolcore-mark.svg +10 -0
  19. package/bin/ec-safe-output.js +161 -0
  20. package/bin/ec.js +29 -0
  21. package/dist/agents/baseagent.js +163 -0
  22. package/dist/agents/claude-runner.js +2385 -0
  23. package/dist/agents/codex-app-server-client.js +448 -0
  24. package/dist/agents/codex-runner.js +2639 -0
  25. package/dist/agents/gemini-runner.js +666 -0
  26. package/dist/agents/runner-types.js +75 -0
  27. package/dist/aun/aid/agentmd.js +216 -0
  28. package/dist/aun/aid/client.js +132 -0
  29. package/dist/aun/aid/control-aid.js +91 -0
  30. package/dist/aun/aid/identity.js +518 -0
  31. package/dist/aun/aid/index.js +4 -0
  32. package/dist/aun/aid/store.js +74 -0
  33. package/dist/aun/aid/types.js +1 -0
  34. package/dist/aun/aid/validation.js +21 -0
  35. package/dist/aun/group-identity.js +10 -0
  36. package/dist/aun/msg/group-index.js +6 -0
  37. package/dist/aun/msg/group.js +1231 -0
  38. package/dist/aun/msg/history.js +123 -0
  39. package/dist/aun/msg/index.js +5 -0
  40. package/dist/aun/msg/p2p.js +393 -0
  41. package/dist/aun/msg/payload-type.js +27 -0
  42. package/dist/aun/msg/upload.js +137 -0
  43. package/dist/aun/outbox.js +160 -0
  44. package/dist/aun/rpc/caller.js +42 -0
  45. package/dist/aun/rpc/connection.js +25 -0
  46. package/dist/aun/rpc/index.js +2 -0
  47. package/dist/aun/service-proxy.js +225 -0
  48. package/dist/aun/storage/download.js +29 -0
  49. package/dist/aun/storage/index.js +3 -0
  50. package/dist/aun/storage/manage.js +10 -0
  51. package/dist/aun/storage/upload.js +68 -0
  52. package/dist/channels/aun.js +4147 -0
  53. package/dist/channels/daemon.js +422 -0
  54. package/dist/channels/dingtalk.js +649 -0
  55. package/dist/channels/feishu.js +1789 -0
  56. package/dist/channels/qqbot.js +409 -0
  57. package/dist/channels/wechat.js +817 -0
  58. package/dist/channels/wecom.js +565 -0
  59. package/dist/cli/agent-command.js +641 -0
  60. package/dist/cli/agent.js +1059 -0
  61. package/dist/cli/aun-commands.js +1948 -0
  62. package/dist/cli/bench.js +1228 -0
  63. package/dist/cli/cli-argv.js +66 -0
  64. package/dist/cli/code-stats.js +329 -0
  65. package/dist/cli/command-log.js +82 -0
  66. package/dist/cli/config-selector.js +69 -0
  67. package/dist/cli/config.js +261 -0
  68. package/dist/cli/ctl-command.js +62 -0
  69. package/dist/cli/daemon-commands.js +2887 -0
  70. package/dist/cli/fs-command.js +1447 -0
  71. package/dist/cli/handoff-command.js +302 -0
  72. package/dist/cli/help.js +31 -0
  73. package/dist/cli/index.js +358 -0
  74. package/dist/cli/init-channel.js +1377 -0
  75. package/dist/cli/init.js +553 -0
  76. package/dist/cli/link-rules.js +240 -0
  77. package/dist/cli/model.js +590 -0
  78. package/dist/cli/net-check.js +723 -0
  79. package/dist/cli/queue-command.js +126 -0
  80. package/dist/cli/response.js +345 -0
  81. package/dist/cli/restart-monitor.js +456 -0
  82. package/dist/cli/stats.js +607 -0
  83. package/dist/cli/task-context.js +80 -0
  84. package/dist/cli/trigger-command.js +505 -0
  85. package/dist/cli/version.js +88 -0
  86. package/dist/cli/watch-logs.js +33 -0
  87. package/dist/cli/watch-msg.js +673 -0
  88. package/dist/config/boot-log.js +266 -0
  89. package/dist/config/builtin-role-templates.js +30 -0
  90. package/dist/config/builtin-roles.js +85 -0
  91. package/dist/config/config-batch-get.js +11 -0
  92. package/dist/config/config-field-policy.js +261 -0
  93. package/dist/config/config-manager.js +916 -0
  94. package/dist/config/config-operation-service.js +384 -0
  95. package/dist/config/contact-book.js +371 -0
  96. package/dist/config/gateway-config.js +858 -0
  97. package/dist/config/lifecycle.js +17 -0
  98. package/dist/config/mention-mode.js +27 -0
  99. package/dist/config/merge.js +161 -0
  100. package/dist/config/owner-policy.js +4 -0
  101. package/dist/config/peer-role-resolver.js +139 -0
  102. package/dist/config/resolved-config-op.js +483 -0
  103. package/dist/config/role-config-v4-startup.js +32 -0
  104. package/dist/config/role-config-v5-startup.js +27 -0
  105. package/dist/config/role-schema.js +105 -0
  106. package/dist/config/role-service.js +159 -0
  107. package/dist/config/role-store.js +204 -0
  108. package/dist/config/roles.js +55 -0
  109. package/dist/config/schema-registry.js +154 -0
  110. package/dist/config/snapshot.js +598 -0
  111. package/dist/config-store.js +503 -0
  112. package/dist/core/auth/agent-delegation.js +111 -0
  113. package/dist/core/auth/auth-gateway.js +166 -0
  114. package/dist/core/auth/authenticated-actor.js +6 -0
  115. package/dist/core/auth/authorization-audit.js +110 -0
  116. package/dist/core/auth/operation-authorizer.js +718 -0
  117. package/dist/core/auth/operation-catalog.js +675 -0
  118. package/dist/core/baseagent-loader.js +54 -0
  119. package/dist/core/bootstrap-service.js +175 -0
  120. package/dist/core/capability/capability-manager.js +316 -0
  121. package/dist/core/capability/providers/claude-capability-provider.js +176 -0
  122. package/dist/core/capability/providers/codex-capability-provider.js +148 -0
  123. package/dist/core/capability/providers/gemini-capability-provider.js +10 -0
  124. package/dist/core/capability/types.js +27 -0
  125. package/dist/core/causation/audit.js +103 -0
  126. package/dist/core/causation/aun-association.js +111 -0
  127. package/dist/core/causation/context.js +93 -0
  128. package/dist/core/causation/index.js +4 -0
  129. package/dist/core/causation/types.js +2 -0
  130. package/dist/core/channel-loader.js +277 -0
  131. package/dist/core/command/agent-control.js +616 -0
  132. package/dist/core/command/cli-intent-parser.js +225 -0
  133. package/dist/core/command/command-handler.js +1638 -0
  134. package/dist/core/command/menu-handler.js +3354 -0
  135. package/dist/core/command/menu-protocol.js +247 -0
  136. package/dist/core/command/role-menu.js +1524 -0
  137. package/dist/core/command/slash-gate.js +148 -0
  138. package/dist/core/command/slash-handler.js +3056 -0
  139. package/dist/core/daemon-file-cache.js +216 -0
  140. package/dist/core/event-bus.js +32 -0
  141. package/dist/core/event-catalog.js +740 -0
  142. package/dist/core/evolagent-registry.js +546 -0
  143. package/dist/core/evolagent.js +331 -0
  144. package/dist/core/handoff/dispatcher.js +229 -0
  145. package/dist/core/handoff/mutex.js +46 -0
  146. package/dist/core/handoff/runtime.js +324 -0
  147. package/dist/core/handoff/store.js +537 -0
  148. package/dist/core/handoff/types.js +12 -0
  149. package/dist/core/inference/text-inference.js +173 -0
  150. package/dist/core/interaction-registration.js +10 -0
  151. package/dist/core/interaction-router.js +278 -0
  152. package/dist/core/message/create-status.js +67 -0
  153. package/dist/core/message/im-renderer.js +657 -0
  154. package/dist/core/message/items-formatter.js +76 -0
  155. package/dist/core/message/logical-queue-bridge.js +123 -0
  156. package/dist/core/message/message-bridge.js +803 -0
  157. package/dist/core/message/message-cache.js +56 -0
  158. package/dist/core/message/message-log.js +339 -0
  159. package/dist/core/message/message-processor.js +4 -0
  160. package/dist/core/message/message-queue.js +1436 -0
  161. package/dist/core/message/message-utils.js +70 -0
  162. package/dist/core/message/peer-mode.js +105 -0
  163. package/dist/core/message/pending-hints.js +232 -0
  164. package/dist/core/message/response-depth.js +33 -0
  165. package/dist/core/message/response-engine.js +3776 -0
  166. package/dist/core/message/response-snapshot.js +83 -0
  167. package/dist/core/message/stream-debouncer.js +130 -0
  168. package/dist/core/message/stream-idle-monitor.js +124 -0
  169. package/dist/core/model/config-scope.js +162 -0
  170. package/dist/core/model/field-scope.js +78 -0
  171. package/dist/core/model/model-catalog.js +227 -0
  172. package/dist/core/model/model-diagnostics.js +182 -0
  173. package/dist/core/model/model-permission.js +90 -0
  174. package/dist/core/permission/approval-gateway.js +1017 -0
  175. package/dist/core/permission/ec-command-parser.js +347 -0
  176. package/dist/core/permission/execution-sandbox.js +16 -0
  177. package/dist/core/permission/index.js +6 -0
  178. package/dist/core/permission/mode.js +24 -0
  179. package/dist/core/permission/sandbox-runtime.js +265 -0
  180. package/dist/core/permission/tool-policy.js +987 -0
  181. package/dist/core/permission/unix-socket-policy.js +99 -0
  182. package/dist/core/protected-paths.js +330 -0
  183. package/dist/core/relation/peer-identity.js +222 -0
  184. package/dist/core/relation/peer-key.js +1 -0
  185. package/dist/core/role/runtime-policy.js +141 -0
  186. package/dist/core/session/adapters/claude-session-file-adapter.js +218 -0
  187. package/dist/core/session/adapters/codex-session-file-adapter.js +333 -0
  188. package/dist/core/session/adapters/gemini-session-file-adapter.js +181 -0
  189. package/dist/core/session/session-file-adapter.js +7 -0
  190. package/dist/core/session/session-file-health.js +45 -0
  191. package/dist/core/session/session-fs-store.js +232 -0
  192. package/dist/core/session/session-key.js +24 -0
  193. package/dist/core/session/session-manager.js +1587 -0
  194. package/dist/core/session/session-mapper.js +100 -0
  195. package/dist/core/session/session-renew.js +314 -0
  196. package/dist/core/session/session-title.js +128 -0
  197. package/dist/core/session/session-turn-coordinator.js +205 -0
  198. package/dist/core/session/session-turns.js +67 -0
  199. package/dist/core/system-channels.js +29 -0
  200. package/dist/eck/baseagent-caps.js +18 -0
  201. package/dist/eck/detect.js +47 -0
  202. package/dist/eck/group-rules-sync.js +345 -0
  203. package/dist/eck/init.js +77 -0
  204. package/dist/eck/kit-renderer.js +359 -0
  205. package/dist/eck/manifest-engine.js +446 -0
  206. package/dist/eck/message-renderer.js +199 -0
  207. package/dist/eck/rules-loader.js +28 -0
  208. package/dist/index.js +2870 -4
  209. package/dist/ipc.js +748 -0
  210. package/dist/paths.js +262 -0
  211. package/dist/product.js +18 -0
  212. package/dist/response-system/context-builder.js +71 -0
  213. package/dist/response-system/coordinator.js +117 -0
  214. package/dist/response-system/decision-executor.js +86 -0
  215. package/dist/response-system/engines/v1/index.js +21 -0
  216. package/dist/response-system/engines/v1/interactive-flow.js +27 -0
  217. package/dist/response-system/engines/v1/proactive-flow.js +137 -0
  218. package/dist/response-system/engines/v1/types.js +1 -0
  219. package/dist/response-system/extensions.js +41 -0
  220. package/dist/response-system/index.js +6 -0
  221. package/dist/response-system/modes/index.js +7 -0
  222. package/dist/response-system/modes/single-session/index.js +72 -0
  223. package/dist/response-system/queues/fifo-queue.js +44 -0
  224. package/dist/response-system/queues/index.js +6 -0
  225. package/dist/response-system/queues/lifo-queue.js +42 -0
  226. package/dist/response-system/queues/priority-queue.js +63 -0
  227. package/dist/response-system/registry.js +97 -0
  228. package/dist/response-system/resolver.js +37 -0
  229. package/dist/response-system/selector.js +23 -0
  230. package/dist/response-system/types.js +7 -0
  231. package/dist/stats/billing.js +151 -0
  232. package/dist/stats/budget.js +93 -0
  233. package/dist/stats/db.js +403 -0
  234. package/dist/stats/eck-vars.js +89 -0
  235. package/dist/stats/index.js +11 -0
  236. package/dist/stats/normalizer.js +80 -0
  237. package/dist/stats/price-resolver.js +138 -0
  238. package/dist/stats/query.js +763 -0
  239. package/dist/stats/role-budget.js +168 -0
  240. package/dist/stats/writer.js +151 -0
  241. package/dist/trigger/anomaly-store.js +258 -0
  242. package/dist/trigger/audit.js +152 -0
  243. package/dist/trigger/event-source.js +119 -0
  244. package/dist/trigger/feedback.js +685 -0
  245. package/dist/trigger/history.js +290 -0
  246. package/dist/trigger/manager.js +291 -0
  247. package/dist/trigger/parser.js +520 -0
  248. package/dist/trigger/patch.js +148 -0
  249. package/dist/trigger/scheduler.js +1600 -0
  250. package/dist/trigger/script-executor.js +155 -0
  251. package/dist/trigger/state.js +145 -0
  252. package/dist/trigger/types.js +1 -0
  253. package/dist/trigger/validation.js +621 -0
  254. package/dist/types.js +12 -0
  255. package/dist/utils/aid-bind.js +299 -0
  256. package/dist/utils/atomic-write.js +95 -0
  257. package/dist/utils/avatar-upload.js +123 -0
  258. package/dist/utils/cross-platform.js +297 -0
  259. package/dist/utils/ecweb-utils.js +73 -0
  260. package/dist/utils/error-dict.json +153 -0
  261. package/dist/utils/error-utils.js +349 -0
  262. package/dist/utils/instance-registry.js +437 -0
  263. package/dist/utils/locale.js +21 -0
  264. package/dist/utils/log-writer.js +224 -0
  265. package/dist/utils/logger.js +89 -0
  266. package/dist/utils/markdown-to-plain-text.js +20 -0
  267. package/dist/utils/media-cache.js +271 -0
  268. package/dist/utils/model-prices.jsonl +17 -0
  269. package/dist/utils/npm-ops.js +210 -0
  270. package/dist/utils/process-introspect.js +133 -0
  271. package/dist/utils/process-tree-stats.js +271 -0
  272. package/dist/utils/project-path.js +74 -0
  273. package/dist/utils/stats.js +410 -0
  274. package/dist/utils/tool-summary.js +284 -0
  275. package/dist/utils/welcome.js +268 -0
  276. package/kits/docs/GUIDE.md +20 -0
  277. package/kits/docs/INDEX.md +65 -0
  278. package/kits/docs/aun/CHEATSHEET.md +19 -0
  279. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -0
  280. package/kits/docs/channels/aun.md +65 -0
  281. package/kits/docs/channels/feishu.md +56 -0
  282. package/kits/docs/context-assembly.md +366 -0
  283. package/kits/docs/eck_templates/GUIDE.template.md +22 -0
  284. package/kits/docs/eck_templates/INDEX.template.md +28 -0
  285. package/kits/docs/eck_templates/path-registry.template.md +33 -0
  286. package/kits/docs/eck_templates/runtime.template.md +19 -0
  287. package/kits/docs/evolcore/INDEX.md +66 -0
  288. package/kits/docs/evolcore/agent.md +69 -0
  289. package/kits/docs/evolcore/aid.md +49 -0
  290. package/kits/docs/evolcore/config.md +149 -0
  291. package/kits/docs/evolcore/ctl.md +46 -0
  292. package/kits/docs/evolcore/event.md +216 -0
  293. package/kits/docs/evolcore/fs-architecture.md +1215 -0
  294. package/kits/docs/evolcore/fs.md +101 -0
  295. package/kits/docs/evolcore/group-fs.md +17 -0
  296. package/kits/docs/evolcore/group-rules.md +226 -0
  297. package/kits/docs/evolcore/group.md +141 -0
  298. package/kits/docs/evolcore/model.md +47 -0
  299. package/kits/docs/evolcore/msg.md +130 -0
  300. package/kits/docs/evolcore/response.md +80 -0
  301. package/kits/docs/evolcore/rpc.md +35 -0
  302. package/kits/docs/evolcore/self-summary.md +29 -0
  303. package/kits/docs/evolcore/stats.md +70 -0
  304. package/kits/docs/evolcore/storage.md +49 -0
  305. package/kits/docs/evolcore/trigger.md +524 -0
  306. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -0
  307. package/kits/docs/identity/PATH_OPS.md +16 -0
  308. package/kits/docs/identity/ROLE_DETAIL.md +23 -0
  309. package/kits/docs/identity/identity-tools.md +26 -0
  310. package/kits/docs/path-registry.md +43 -0
  311. package/kits/docs/prompt-loading-architecture.md +266 -0
  312. package/kits/docs/venues/aun-group.md +45 -0
  313. package/kits/docs/venues/aun-private.md +10 -0
  314. package/kits/docs/venues/client-desktop.md +10 -0
  315. package/kits/docs/venues/client-mobile.md +10 -0
  316. package/kits/docs/venues/feishu-group.md +13 -0
  317. package/kits/docs/venues/feishu-private.md +9 -0
  318. package/kits/docs/venues/group.md +25 -0
  319. package/kits/docs/venues/private.md +10 -0
  320. package/kits/eck_manifest.auxiliary.json +43 -0
  321. package/kits/eck_manifest.json +191 -0
  322. package/kits/eck_message_manifest.json +63 -0
  323. package/kits/migrations/README-role-config-v4.md +32 -0
  324. package/kits/migrations/migrate-role-config-v4.mjs +623 -0
  325. package/kits/migrations/migrate-role-config-v5.mjs +346 -0
  326. package/kits/migrations/rename-config-file.mjs +99 -0
  327. package/kits/rules/01-overview.md +142 -0
  328. package/kits/rules/02-navigation.md +76 -0
  329. package/kits/rules/03-identity.md +34 -0
  330. package/kits/rules/04-relation.md +59 -0
  331. package/kits/rules/05-venue.md +44 -0
  332. package/kits/rules/06-channel.md +59 -0
  333. package/kits/schemas/_meta.json +29 -0
  334. package/kits/schemas/agent-config.schema.1.json +177 -0
  335. package/kits/schemas/agent-config.schema.2.json +239 -0
  336. package/kits/schemas/agent-config.schema.3.json +119 -0
  337. package/kits/schemas/agent-config.schema.4.json +208 -0
  338. package/kits/schemas/agent-config.schema.5.json +326 -0
  339. package/kits/schemas/contact-book.schema.1.json +36 -0
  340. package/kits/schemas/daemon.schema.1.json +90 -0
  341. package/kits/schemas/defaults.schema.1.json +81 -0
  342. package/kits/schemas/menu-exec-schema-commands.md +208 -0
  343. package/kits/schemas/migrations/README.md +28 -0
  344. package/kits/schemas/relation-config.schema.1.json +158 -0
  345. package/kits/schemas/relation-config.schema.2.json +73 -0
  346. package/kits/schemas/relation-config.schema.3.json +50 -0
  347. package/kits/schemas/relation-config.schema.4.json +47 -0
  348. package/kits/schemas/role-config.schema.1.json +200 -0
  349. package/kits/schemas/role-registry.schema.1.json +35 -0
  350. package/kits/schemas/single-session.schema.1.json +31 -0
  351. package/kits/templates/bootstrap-welcome.md +15 -0
  352. package/kits/templates/message-fragments/handoff-request-to-target.md +13 -0
  353. package/kits/templates/message-fragments/handoff-response-to-origin.md +10 -0
  354. package/kits/templates/message-fragments/inject-default.md +2 -0
  355. package/kits/templates/message-fragments/item.md +2 -0
  356. package/kits/templates/roles/admin.json +8 -0
  357. package/kits/templates/roles/member.json +40 -0
  358. package/kits/templates/roles/owner.json +8 -0
  359. package/kits/templates/roles/visitor.json +39 -0
  360. package/kits/templates/system-fragments/baseagent.md +14 -0
  361. package/kits/templates/system-fragments/bootstrap.md +16 -0
  362. package/kits/templates/system-fragments/channel.md +48 -0
  363. package/kits/templates/system-fragments/commands.md +26 -0
  364. package/kits/templates/system-fragments/identity.md +11 -0
  365. package/kits/templates/system-fragments/relation.md +19 -0
  366. package/kits/templates/system-fragments/session.md +53 -0
  367. package/kits/templates/system-fragments/venue.md +31 -0
  368. package/package.json +50 -15
  369. package/dist/index.d.ts +0 -7
  370. package/dist/index.d.ts.map +0 -1
  371. package/dist/index.js.map +0 -1
@@ -0,0 +1,1600 @@
1
+ import crypto from 'crypto';
2
+ import fs from 'fs';
3
+ import { CronExpressionParser } from 'cron-parser';
4
+ import { logger } from '../utils/logger.js';
5
+ import { matchTriggerEvent, TriggerEventSource } from './event-source.js';
6
+ import { definitionRevision, parseDurationMs, previewText, renderTemplate, resolveScriptPath, sha256, } from './validation.js';
7
+ import { enterTrigger, formatTriggerPath } from '../core/causation/context.js';
8
+ import { createRootCausation } from '../core/causation/context.js';
9
+ import { recordCausationSpan } from '../core/causation/audit.js';
10
+ import { accumulateTriggerExecutionAnomalies, drainTriggerExecutionAnomalies, mergeTriggerExecutionAnomalies, } from './anomaly-store.js';
11
+ const MAX_TIMER_MS = 2_147_483_647;
12
+ const RUN_ABORT_SETTLE_GRACE_MS = 1_000;
13
+ const RUN_COMPLETION_BARRIER_MS = 1_500;
14
+ export class TriggerRuntimeScheduler {
15
+ manager;
16
+ state;
17
+ audit;
18
+ scriptExecutor;
19
+ feedback;
20
+ daemonChannel;
21
+ runtime;
22
+ eventBus;
23
+ timers = new Map();
24
+ running = new Map();
25
+ runStartLocks = new Map();
26
+ eventSource;
27
+ initialized = false;
28
+ stopping = false;
29
+ constructor(manager, state, audit, scriptExecutor, feedback, daemonChannel, runtime, eventBus) {
30
+ this.manager = manager;
31
+ this.state = state;
32
+ this.audit = audit;
33
+ this.scriptExecutor = scriptExecutor;
34
+ this.feedback = feedback;
35
+ this.daemonChannel = daemonChannel;
36
+ this.runtime = runtime;
37
+ this.eventBus = eventBus;
38
+ if (eventBus) {
39
+ this.eventSource = new TriggerEventSource(eventBus, (triggerId, event) => {
40
+ void this.fireEventTrigger(triggerId, event);
41
+ });
42
+ }
43
+ }
44
+ async init() {
45
+ await this.stop();
46
+ this.stopping = false;
47
+ await this.recoverOpenRuns();
48
+ for (const definition of this.manager.list()) {
49
+ this.schedule(definition);
50
+ }
51
+ this.initialized = true;
52
+ logger.info(`[Trigger] scheduler initialized for ${this.manager.agentAid}`);
53
+ }
54
+ async stop() {
55
+ this.stopping = true;
56
+ for (const timer of this.timers.values())
57
+ clearTimeout(timer);
58
+ this.timers.clear();
59
+ this.eventSource?.stop();
60
+ const settlements = [];
61
+ for (const runs of Array.from(this.running.values())) {
62
+ for (const [runId, runtime] of Array.from(runs.entries())) {
63
+ settlements.push(this.terminateRun(runId, runtime, {
64
+ reason: 'stopped',
65
+ message: 'trigger scheduler stopped',
66
+ }));
67
+ }
68
+ }
69
+ settlements.push(...this.runStartLocks.values());
70
+ this.initialized = false;
71
+ await Promise.all(settlements);
72
+ }
73
+ async waitForIdle(timeoutMs = 5_000) {
74
+ const deadline = Date.now() + timeoutMs;
75
+ while ((this.running.size > 0 || this.runStartLocks.size > 0) && Date.now() < deadline) {
76
+ await new Promise(resolve => setTimeout(resolve, 10));
77
+ }
78
+ return this.running.size === 0 && this.runStartLocks.size === 0;
79
+ }
80
+ list(opts = {}) {
81
+ return this.manager.list(opts);
82
+ }
83
+ listItems(definitions) {
84
+ const latestRuns = this.audit.latest(definitions.map(definition => definition.id));
85
+ return definitions.map(definition => this.buildListItem(definition, latestRuns.get(definition.id)));
86
+ }
87
+ listItem(definition) {
88
+ return this.buildListItem(definition, this.audit.recent(definition.id, 1)[0]);
89
+ }
90
+ buildListItem(definition, lastRun) {
91
+ const schedule = this.state.readSchedule(definition.id);
92
+ return {
93
+ id: definition.id,
94
+ name: definition.name,
95
+ enabled: definition.enabled,
96
+ ...(definition.description ? { description: definition.description } : {}),
97
+ source: summarizeListSource(definition.source),
98
+ execution: { type: definition.execution.type },
99
+ feedback: {
100
+ strategy: definition.feedback.strategy,
101
+ ...(definition.feedback.target ? { target: definition.feedback.target } : {}),
102
+ },
103
+ ...(schedule ? { nextFireAt: schedule.nextFireAt } : {}),
104
+ ...(lastRun ? { lastStatus: lastRun.status, lastFinishedAt: lastRun.finishedAt } : {}),
105
+ updatedAt: definition.updatedAt,
106
+ };
107
+ }
108
+ show(triggerId, opts = {}) {
109
+ const definition = this.manager.require(triggerId);
110
+ const schedule = this.state.readSchedule(triggerId);
111
+ return {
112
+ definition,
113
+ revision: definitionRevision(definition),
114
+ active: this.state.list(triggerId).map(run => ({
115
+ phase: run.phase,
116
+ triggerId: run.triggerId,
117
+ runId: run.runId,
118
+ startedAt: run.startedAt,
119
+ ...(run.deadlineAt !== undefined ? { deadlineAt: run.deadlineAt } : {}),
120
+ eventCount: run.events.length,
121
+ })),
122
+ ...(schedule ? {
123
+ schedule: {
124
+ nextFireAt: schedule.nextFireAt,
125
+ updatedAt: schedule.updatedAt,
126
+ ...(schedule.lastScheduledAt !== undefined ? { lastScheduledAt: schedule.lastScheduledAt } : {}),
127
+ ...(schedule.lastFiredAt !== undefined ? { lastFiredAt: schedule.lastFiredAt } : {}),
128
+ },
129
+ } : {}),
130
+ limitState: this.state.readLimitState(triggerId),
131
+ recentRuns: this.audit.recent(triggerId, 10).map(run => ({
132
+ runId: run.runId,
133
+ status: run.status,
134
+ ...(run.reason ? { reason: run.reason } : {}),
135
+ startedAt: run.startedAt,
136
+ finishedAt: run.finishedAt,
137
+ durationMs: Math.max(0, run.finishedAt - run.startedAt),
138
+ ...(run.error?.code ? { errorCode: run.error.code } : {}),
139
+ })),
140
+ ...(opts.includeScriptPreview === false ? {} : { scriptPreview: this.scriptPreview(definition) }),
141
+ subscription: this.subscriptionInfo(definition),
142
+ };
143
+ }
144
+ history(triggerId, limit = 100) {
145
+ return this.manager.history.events(triggerId, limit);
146
+ }
147
+ scriptPreview(definition) {
148
+ if (definition.execution.type !== 'script' || !definition.execution.script)
149
+ return undefined;
150
+ const script = definition.execution.script;
151
+ const preview = {
152
+ path: script.path,
153
+ runtime: script.runtime,
154
+ };
155
+ try {
156
+ const stat = fs.statSync(resolveScriptPath(this.manager.triggerDir(definition.id), script.path));
157
+ if (!stat.isFile())
158
+ return { ...preview, error: 'script path is not a file' };
159
+ return { ...preview, sizeBytes: stat.size, mtime: stat.mtimeMs };
160
+ }
161
+ catch (err) {
162
+ return { ...preview, error: err?.message || String(err) };
163
+ }
164
+ }
165
+ stats(triggerId) {
166
+ const historyStats = this.audit.stats(triggerId);
167
+ const merged = this.manager.history.hasLegacyArchive(triggerId)
168
+ ? historyStats
169
+ : mergeStats(this.manager.legacyStats(triggerId), historyStats);
170
+ const schedule = this.state.readSchedule(triggerId);
171
+ return accountScheduleMarker(merged, schedule, skipped => this.audit.hasSkippedSchedule(triggerId, skipped));
172
+ }
173
+ create(input, files = [], opts = {}) {
174
+ const definition = this.manager.create(input, files, opts);
175
+ this.state.clearSchedule(definition.id);
176
+ if (!definition.limits)
177
+ this.state.clearLimitState(definition.id);
178
+ if (definition.enabled && this.initialized)
179
+ this.schedule(definition);
180
+ this.publishTriggerDefinitionEvent('trigger:registered', definition);
181
+ return definition;
182
+ }
183
+ update(triggerId, input, files = []) {
184
+ const previous = this.manager.get(triggerId);
185
+ const definition = this.manager.update(triggerId, input, files);
186
+ this.clearTimer(triggerId);
187
+ this.unregisterEvent(triggerId);
188
+ this.state.clearSchedule(triggerId);
189
+ if (!definition.limits) {
190
+ this.state.clearLimitState(triggerId);
191
+ }
192
+ else if (definition.enabled && limitsSignature(previous?.limits) !== limitsSignature(definition.limits)) {
193
+ this.state.resetLimitState(triggerId);
194
+ }
195
+ else if (definition.enabled) {
196
+ this.state.ensureLimitState(triggerId);
197
+ }
198
+ if (definition.enabled && this.initialized)
199
+ this.schedule(definition);
200
+ this.publishTriggerDefinitionEvent('trigger:updated', definition);
201
+ return definition;
202
+ }
203
+ setEnabled(triggerId, enabled) {
204
+ const definition = this.manager.setEnabled(triggerId, enabled);
205
+ this.clearTimer(triggerId);
206
+ this.unregisterEvent(triggerId);
207
+ this.state.clearSchedule(triggerId);
208
+ if (enabled && definition.limits)
209
+ this.state.resetLimitState(triggerId);
210
+ if (!definition.limits)
211
+ this.state.clearLimitState(triggerId);
212
+ if (definition.enabled && this.initialized)
213
+ this.schedule(definition);
214
+ return definition;
215
+ }
216
+ cancel(triggerId) {
217
+ return this.setEnabled(triggerId, false);
218
+ }
219
+ delete(triggerId) {
220
+ const definition = this.manager.require(triggerId);
221
+ this.clearTimer(triggerId);
222
+ this.unregisterEvent(triggerId);
223
+ this.state.clearSchedule(triggerId);
224
+ this.state.clearLimitState(triggerId);
225
+ return this.manager.delete(triggerId);
226
+ }
227
+ async run(triggerId, opts = {}) {
228
+ const definition = this.manager.require(triggerId);
229
+ const firedAt = Date.now();
230
+ let sourcePayload = this.sourcePayload(definition.source);
231
+ if (opts.eventPayload !== undefined) {
232
+ if (definition.source.type !== 'event') {
233
+ throw new Error('simulated event payload is only supported for Event Triggers');
234
+ }
235
+ if (!isRecord(opts.eventPayload)) {
236
+ throw new Error('simulated event payload must be a JSON object');
237
+ }
238
+ const match = matchTriggerEvent(definition.source, opts.eventPayload);
239
+ if (!match.matched) {
240
+ throw new Error(eventTestMatchError(match.reason, definition.source.eventPattern));
241
+ }
242
+ sourcePayload = opts.eventPayload;
243
+ }
244
+ if (!opts.dryRun) {
245
+ const disabledReason = this.limitDisabledReason(definition, firedAt);
246
+ if (disabledReason) {
247
+ const audit = this.disableForLimit(definition, undefined, firedAt, disabledReason);
248
+ return {
249
+ ok: true,
250
+ runId: audit.runId,
251
+ triggerId: definition.id,
252
+ status: 'skipped',
253
+ reason: audit.reason,
254
+ audit,
255
+ };
256
+ }
257
+ }
258
+ return await this.startRun(definition, {
259
+ firedAt,
260
+ payload: sourcePayload,
261
+ dryRun: opts.dryRun,
262
+ });
263
+ }
264
+ schedule(definition) {
265
+ this.clearTimer(definition.id);
266
+ this.unregisterEvent(definition.id);
267
+ if (definition.limits)
268
+ this.state.ensureLimitState(definition.id);
269
+ else
270
+ this.state.clearLimitState(definition.id);
271
+ if (definition.source.type === 'event') {
272
+ this.registerEvent(definition);
273
+ return;
274
+ }
275
+ const scheduledAt = this.resolveScheduledAt(definition);
276
+ if (scheduledAt === null) {
277
+ if (this.isOneShot(definition.source) && definition.reliability.missedPolicy === 'skip') {
278
+ this.writeSkippedAudit(definition, undefined, Date.now(), 'missed_skip');
279
+ this.setEnabled(definition.id, false);
280
+ }
281
+ return;
282
+ }
283
+ const now = Date.now();
284
+ if (this.isPeriodic(definition.source) && scheduledAt < now - 1000 && definition.reliability.missedPolicy === 'skip') {
285
+ this.writeSkippedAudit(definition, scheduledAt, now, 'missed_skip');
286
+ this.advancePeriodicSchedule(definition, scheduledAt, now);
287
+ this.schedule(definition);
288
+ return;
289
+ }
290
+ const rawDelay = Math.max(0, scheduledAt - Date.now());
291
+ const timer = setTimeout(() => {
292
+ this.timers.delete(definition.id);
293
+ if (scheduledAt - Date.now() > 1000) {
294
+ this.schedule(definition);
295
+ return;
296
+ }
297
+ void this.handleScheduledFire(definition.id, scheduledAt);
298
+ }, Math.min(MAX_TIMER_MS, rawDelay));
299
+ timer.unref?.();
300
+ this.timers.set(definition.id, timer);
301
+ }
302
+ async handleScheduledFire(triggerId, scheduledAt) {
303
+ const definition = this.manager.get(triggerId);
304
+ if (!definition?.enabled)
305
+ return;
306
+ const firedRevision = definitionRevision(definition);
307
+ const firedAt = Date.now();
308
+ if (this.isOneShot(definition.source) && scheduledAt < firedAt - 1000 && definition.reliability.missedPolicy === 'skip') {
309
+ this.writeSkippedAudit(definition, scheduledAt, firedAt, 'missed_skip');
310
+ this.setEnabled(definition.id, false);
311
+ return;
312
+ }
313
+ if (this.isPeriodic(definition.source)) {
314
+ if (scheduledAt < firedAt - 1000 && definition.reliability.missedPolicy === 'skip') {
315
+ this.writeSkippedAudit(definition, scheduledAt, firedAt, 'missed_skip');
316
+ this.advancePeriodicSchedule(definition, scheduledAt, firedAt);
317
+ this.schedule(definition);
318
+ return;
319
+ }
320
+ const disabledReason = this.limitDisabledReason(definition, firedAt);
321
+ if (disabledReason) {
322
+ this.disableForLimit(definition, scheduledAt, firedAt, disabledReason);
323
+ return;
324
+ }
325
+ }
326
+ else {
327
+ const disabledReason = this.limitDisabledReason(definition, firedAt);
328
+ if (disabledReason) {
329
+ this.disableForLimit(definition, scheduledAt, firedAt, disabledReason);
330
+ return;
331
+ }
332
+ }
333
+ const runPromise = this.startRun(definition, {
334
+ scheduledAt,
335
+ firedAt,
336
+ payload: this.sourcePayload(definition.source),
337
+ });
338
+ if (this.isPeriodic(definition.source)) {
339
+ this.advancePeriodicSchedule(definition, scheduledAt, firedAt);
340
+ this.schedule(definition);
341
+ await runPromise;
342
+ return;
343
+ }
344
+ await runPromise;
345
+ const fresh = this.manager.get(triggerId);
346
+ if (fresh?.enabled && definitionRevision(fresh) === firedRevision) {
347
+ this.setEnabled(triggerId, false);
348
+ }
349
+ }
350
+ async startRun(definition, payload) {
351
+ const firedAt = payload.firedAt;
352
+ const runId = `run_${firedAt}_${crypto.randomBytes(3).toString('hex')}`;
353
+ if (this.stopping) {
354
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'stopped' };
355
+ }
356
+ const source = this.buildSourceInfo(definition.source, firedAt, payload.scheduledAt, payload.payload);
357
+ const executionAuthorization = this.runtime.authorizeExecution?.(definition);
358
+ if (executionAuthorization && !executionAuthorization.allowed) {
359
+ const reason = executionAuthorization.reason || 'execution_authorization_denied';
360
+ const audit = this.buildAudit({
361
+ definition,
362
+ runId,
363
+ startedAt: firedAt,
364
+ status: 'failed',
365
+ reason: 'authorization_denied',
366
+ source,
367
+ script: null,
368
+ reply: null,
369
+ feedback: null,
370
+ effects: [],
371
+ error: { code: 'authorization_denied', message: reason },
372
+ causation: payload.causation,
373
+ });
374
+ if (!payload.dryRun) {
375
+ this.audit.write(audit);
376
+ this.publishTriggerRunOutcome(definition, audit);
377
+ }
378
+ logger.warn(`[Trigger] execution authorization denied: trigger=${definition.id} reason=${reason}`);
379
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'authorization_denied', audit, error: reason };
380
+ }
381
+ const entered = enterTrigger(payload.causation, definition.id, runId);
382
+ if (!entered.ok) {
383
+ const audit = this.buildAudit({
384
+ definition,
385
+ runId,
386
+ startedAt: firedAt,
387
+ status: 'skipped',
388
+ reason: entered.reason,
389
+ source,
390
+ script: null,
391
+ reply: null,
392
+ feedback: null,
393
+ effects: [],
394
+ error: null,
395
+ causation: payload.causation,
396
+ });
397
+ if (!payload.dryRun)
398
+ this.audit.write(audit);
399
+ logger.warn(`[Trigger] causation blocked: reason=${entered.reason} trace=${payload.causation?.traceId ?? '<none>'} chain=${formatTriggerPath(payload.causation, definition.id)}`);
400
+ return { ok: true, runId, triggerId: definition.id, status: 'skipped', reason: entered.reason, audit };
401
+ }
402
+ const causation = entered.causation;
403
+ const startedAt = Date.now();
404
+ const controller = new AbortController();
405
+ let script = null;
406
+ let processing = null;
407
+ let reply = null;
408
+ let executionAnomalies = [];
409
+ let runningRuntime;
410
+ let incomplete;
411
+ let settleCompletion;
412
+ const completion = new Promise(resolve => { settleCompletion = resolve; });
413
+ const activeRun = {
414
+ phase: 'running',
415
+ triggerId: definition.id,
416
+ runId,
417
+ startedAt,
418
+ events: [{ seq: 0, event: 'run.started', ts: startedAt }],
419
+ };
420
+ if (!payload.dryRun) {
421
+ const releaseStartLock = await this.acquireRunStartLock(definition.id);
422
+ try {
423
+ if (this.stopping) {
424
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'stopped' };
425
+ }
426
+ if (definition.limits) {
427
+ const disabledReason = this.limitDisabledReason(definition, firedAt);
428
+ if (disabledReason) {
429
+ const audit = this.disableForLimit(definition, payload.scheduledAt, firedAt, disabledReason);
430
+ return {
431
+ ok: true,
432
+ runId: audit.runId,
433
+ triggerId: definition.id,
434
+ status: 'skipped',
435
+ reason: audit.reason,
436
+ audit,
437
+ };
438
+ }
439
+ }
440
+ const conflict = await this.checkConcurrency(definition, runId, source);
441
+ if (conflict)
442
+ return conflict;
443
+ if (this.stopping) {
444
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'stopped' };
445
+ }
446
+ recordCausationSpan(causation, 'trigger.run', { status: 'started', refs: { triggerId: definition.id, runId } });
447
+ runningRuntime = {
448
+ run: activeRun,
449
+ definition,
450
+ source,
451
+ causation,
452
+ controller,
453
+ completion,
454
+ anomalies: executionAnomalies,
455
+ };
456
+ this.addRunning(definition.id, runId, runningRuntime);
457
+ this.state.upsert(definition.id, activeRun);
458
+ if (definition.limits)
459
+ this.state.incrementLimitRunCount(definition.id);
460
+ this.publishTriggerFired(definition, runId, firedAt, causation);
461
+ }
462
+ finally {
463
+ releaseStartLock();
464
+ }
465
+ }
466
+ try {
467
+ const execution = await awaitWithAbortSettleGrace(this.runExecution(definition, runId, firedAt, source.payload, controller.signal, payload.dryRun === true, causation, anomalies => {
468
+ executionAnomalies = anomalies;
469
+ if (runningRuntime)
470
+ runningRuntime.anomalies = anomalies;
471
+ }), controller.signal, RUN_ABORT_SETTLE_GRACE_MS, 'execution');
472
+ script = execution.script;
473
+ processing = execution.processing;
474
+ reply = execution.reply ?? null;
475
+ executionAnomalies = execution.anomalies ?? [];
476
+ if (runningRuntime)
477
+ runningRuntime.anomalies = executionAnomalies;
478
+ if (!payload.dryRun && controller.signal.aborted) {
479
+ const reason = triggerCancellationReason(controller.signal);
480
+ if (this.isRunning(definition.id, runId))
481
+ this.finishRun(definition.id, runId);
482
+ return {
483
+ ok: false,
484
+ runId,
485
+ triggerId: definition.id,
486
+ status: 'failed',
487
+ reason,
488
+ };
489
+ }
490
+ if (!payload.dryRun) {
491
+ const executionFailed = execution.reply
492
+ ? shouldRetryExecution(execution.reply)
493
+ : execution.feedbackResult?.status === 'failed';
494
+ this.state.appendEvent(definition.id, runId, {
495
+ event: executionFailed ? 'execution.failed' : 'execution.completed',
496
+ ts: Date.now(),
497
+ mode: definition.execution.type,
498
+ outcome: execution.reply?.outcome ?? execution.feedbackResult?.status,
499
+ ...(executionAnomalies.length
500
+ ? { anomalyCount: executionAnomalies.length }
501
+ : {}),
502
+ ...(script ? { exitCode: script.exitCode } : {}),
503
+ });
504
+ if (!execution.feedbackResult) {
505
+ this.state.setPhase(definition.id, runId, 'feedback-pending', {
506
+ deadlineAt: Date.now() + 30_000,
507
+ });
508
+ this.state.appendEvent(definition.id, runId, {
509
+ event: 'feedback.pending',
510
+ ts: Date.now(),
511
+ branch: execution.branch,
512
+ });
513
+ }
514
+ }
515
+ const feedbackResult = execution.feedbackResult ?? await awaitWithAbortSettleGrace(this.feedback.dispatch({
516
+ trigger: definition,
517
+ runId,
518
+ firedAt,
519
+ branch: execution.branch,
520
+ reply: execution.reply,
521
+ sourcePayload: source.payload,
522
+ dryRun: payload.dryRun,
523
+ causation,
524
+ signal: controller.signal,
525
+ }), controller.signal, RUN_ABORT_SETTLE_GRACE_MS, 'feedback');
526
+ if (!payload.dryRun && controller.signal.aborted) {
527
+ const reason = triggerCancellationReason(controller.signal);
528
+ if (this.isRunning(definition.id, runId))
529
+ this.finishRun(definition.id, runId);
530
+ return {
531
+ ok: false,
532
+ runId,
533
+ triggerId: definition.id,
534
+ status: 'failed',
535
+ reason,
536
+ };
537
+ }
538
+ const audit = this.buildAudit({
539
+ definition,
540
+ runId,
541
+ startedAt,
542
+ status: payload.dryRun
543
+ ? 'dry-run'
544
+ : statusWithExecutionAnomalies(feedbackResult.status, executionAnomalies),
545
+ reason: feedbackResult.reason,
546
+ source,
547
+ processing,
548
+ script,
549
+ reply,
550
+ feedback: feedbackResult.feedback,
551
+ anomalies: executionAnomalies,
552
+ effects: feedbackResult.effects,
553
+ error: feedbackResult.error,
554
+ causation,
555
+ });
556
+ if (!payload.dryRun) {
557
+ this.audit.write(audit);
558
+ this.publishTriggerRunOutcome(definition, audit);
559
+ this.finishRun(definition.id, runId);
560
+ recordCausationSpan(causation, 'trigger.run', { status: audit.status === 'failed' ? 'failed' : 'completed', refs: { triggerId: definition.id, runId }, reason: audit.reason });
561
+ }
562
+ return {
563
+ ok: feedbackResult.status !== 'failed',
564
+ runId,
565
+ triggerId: definition.id,
566
+ status: audit.status,
567
+ reason: audit.reason,
568
+ audit,
569
+ error: audit.error?.message,
570
+ };
571
+ }
572
+ catch (err) {
573
+ if (err instanceof AbortSettleGraceError) {
574
+ incomplete = {
575
+ reason: 'abort_settle_grace_exceeded',
576
+ phase: err.phase,
577
+ timeoutMs: err.graceMs,
578
+ anomalyCapture: 'truncated',
579
+ };
580
+ }
581
+ executionAnomalies = mergeTriggerExecutionAnomalies(executionAnomalies, executionAnomaliesFromError(err));
582
+ const message = err?.message || String(err);
583
+ if (!payload.dryRun && controller.signal.aborted) {
584
+ const reason = triggerCancellationReason(controller.signal);
585
+ if (this.isRunning(definition.id, runId))
586
+ this.finishRun(definition.id, runId);
587
+ return {
588
+ ok: false,
589
+ runId,
590
+ triggerId: definition.id,
591
+ status: 'failed',
592
+ reason,
593
+ error: message,
594
+ };
595
+ }
596
+ executionAnomalies = accumulateTriggerExecutionAnomalies(executionAnomalies, drainTriggerExecutionAnomalies(runId));
597
+ const audit = this.buildAudit({
598
+ definition,
599
+ runId,
600
+ startedAt,
601
+ status: 'failed',
602
+ reason: 'daemon_error',
603
+ source,
604
+ processing,
605
+ script,
606
+ reply,
607
+ feedback: null,
608
+ anomalies: executionAnomalies,
609
+ effects: [],
610
+ error: { code: 'daemon_error', message },
611
+ causation,
612
+ });
613
+ if (!payload.dryRun) {
614
+ this.audit.write(audit);
615
+ this.publishTriggerRunOutcome(definition, audit);
616
+ this.finishRun(definition.id, runId);
617
+ recordCausationSpan(causation, 'trigger.run', { status: 'failed', refs: { triggerId: definition.id, runId }, reason: 'daemon_error' });
618
+ }
619
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'daemon_error', audit, error: message };
620
+ }
621
+ finally {
622
+ if (runningRuntime)
623
+ runningRuntime.anomalies = executionAnomalies;
624
+ settleCompletion?.({ anomalies: executionAnomalies, ...(incomplete ? { incomplete } : {}) });
625
+ }
626
+ }
627
+ async runExecution(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, onAnomalies) {
628
+ const maxAttempts = !dryRun && definition.execution.onError === 'retry'
629
+ ? definition.reliability.retry.maxAttempts
630
+ : 0;
631
+ let attempt = 1;
632
+ let anomalies = [];
633
+ try {
634
+ let execution = await this.runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt);
635
+ anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(runId)));
636
+ onAnomalies?.(anomalies);
637
+ for (let retryCount = 0; shouldRetryExecutionResult(execution) && retryCount < maxAttempts && !signal.aborted; retryCount++) {
638
+ const backoffCompleted = await sleep(definition.reliability.retry.backoffMs, signal);
639
+ if (!backoffCompleted || signal.aborted)
640
+ break;
641
+ attempt += 1;
642
+ execution = await this.runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt);
643
+ anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(runId)));
644
+ onAnomalies?.(anomalies);
645
+ }
646
+ if (execution.reply && anomalies.length) {
647
+ execution = { ...execution, reply: { ...execution.reply, anomalies } };
648
+ }
649
+ return anomalies.length ? { ...execution, anomalies } : execution;
650
+ }
651
+ catch (error) {
652
+ anomalies = accumulateTriggerExecutionAnomalies(anomalies, drainTriggerExecutionAnomalies(runId));
653
+ onAnomalies?.(anomalies);
654
+ throw new TriggerExecutionFailure(error, anomalies);
655
+ }
656
+ }
657
+ async runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt = 1) {
658
+ if (signal.aborted) {
659
+ return {
660
+ script: null,
661
+ processing: { mode: definition.execution.type },
662
+ reply: errorReply(runId, 0, 'cancelled', 'trigger execution cancelled'),
663
+ };
664
+ }
665
+ if (definition.execution.type === 'script') {
666
+ const script = await this.runScriptOnce(definition, runId, firedAt, sourcePayload, signal);
667
+ const processing = {
668
+ mode: 'script',
669
+ scriptPath: definition.execution.script?.path,
670
+ scriptArgs: definition.execution.script?.args ?? [],
671
+ };
672
+ if (script.error || script.exitCode !== 0) {
673
+ const reply = errorReply(runId, script.durationMs, script.error?.code || 'script_error', script.error?.message || 'script failed');
674
+ return {
675
+ branch: 'onFailure',
676
+ script,
677
+ processing,
678
+ reply,
679
+ };
680
+ }
681
+ const reply = replyFromScriptResult(script, runId);
682
+ return {
683
+ branch: branchFromReply(reply),
684
+ script,
685
+ processing,
686
+ reply,
687
+ };
688
+ }
689
+ const prompt = renderTemplate(definition.execution.prompt ?? '', {
690
+ trigger: definition,
691
+ timestamp: firedAt,
692
+ event: sourcePayload,
693
+ source: { type: definition.source.type, payload: sourcePayload },
694
+ });
695
+ const processing = {
696
+ mode: definition.execution.type,
697
+ renderedTextHash: sha256(prompt),
698
+ renderedTextPreview: previewText(prompt),
699
+ };
700
+ if (definition.execution.type === 'target_session') {
701
+ const feedbackResult = await this.feedback.dispatchTargetSession({
702
+ trigger: definition,
703
+ runId,
704
+ attemptId: `${runId}:attempt:${attempt}`,
705
+ firedAt,
706
+ prompt,
707
+ dryRun,
708
+ causation,
709
+ signal,
710
+ });
711
+ return {
712
+ script: null,
713
+ processing,
714
+ reply: null,
715
+ feedbackResult,
716
+ };
717
+ }
718
+ if (dryRun) {
719
+ const reply = {
720
+ outcome: prompt.trim() ? 'success' : 'noop',
721
+ text: prompt,
722
+ files: [],
723
+ meta: {
724
+ runId,
725
+ durationMs: 0,
726
+ toolCallCount: 0,
727
+ },
728
+ };
729
+ return { branch: branchFromReply(reply), script: null, processing, reply };
730
+ }
731
+ try {
732
+ const runtimeBaseagent = this.runtime.getBaseagent?.() || this.runtime.baseagent;
733
+ const originPath = causation.trigger?.path ?? [];
734
+ logger.info(`[Trigger] execution context triggerId=${definition.id}`
735
+ + ` triggerRunId=${runId}`
736
+ + ` originRunId=${originPath[0]?.runId ?? '<none>'}`
737
+ + ` selfAID=${definition.agentAid}`
738
+ + ` baseagent=${runtimeBaseagent}`
739
+ + ` model=${definition.execution.model ?? '<agent-default>'}`
740
+ + ` effort=${definition.execution.effort ?? '<agent-default>'}`);
741
+ const executionSession = await this.daemonChannel.getOrCreateConversationSession(definition, this.runtime.projectPath, runtimeBaseagent, runId, `${runId}:attempt:${attempt}`);
742
+ const daemonReply = await this.daemonChannel.converse(prompt, {
743
+ trigger: definition,
744
+ runId,
745
+ attemptId: `${runId}:attempt:${attempt}`,
746
+ firedAt,
747
+ projectPath: this.runtime.projectPath,
748
+ baseagent: runtimeBaseagent,
749
+ session: executionSession,
750
+ causation,
751
+ signal,
752
+ onStateChange: (state, metadata) => {
753
+ if (state === 'requires_action') {
754
+ this.state.setPhase(definition.id, runId, 'requires-action');
755
+ this.state.appendEvent(definition.id, runId, {
756
+ event: 'execution.requires_action',
757
+ ts: Date.now(),
758
+ requestId: metadata?.requestId,
759
+ expiresAt: metadata?.expiresAt,
760
+ });
761
+ }
762
+ else {
763
+ this.state.setPhase(definition.id, runId, 'running');
764
+ this.state.appendEvent(definition.id, runId, {
765
+ event: 'execution.resumed',
766
+ ts: Date.now(),
767
+ requestId: metadata?.requestId,
768
+ });
769
+ }
770
+ },
771
+ });
772
+ const sentinel = definition.execution.noopSentinel ?? '[[NOOP]]';
773
+ const reply = {
774
+ ...daemonReply,
775
+ outcome: daemonReply.outcome === 'success' && daemonReply.text.trim() === sentinel ? 'noop' : daemonReply.outcome,
776
+ text: daemonReply.text.trim() === sentinel ? '' : daemonReply.text,
777
+ };
778
+ return {
779
+ branch: branchFromReply(reply),
780
+ script: null,
781
+ processing,
782
+ reply,
783
+ };
784
+ }
785
+ catch (err) {
786
+ const reply = errorReply(runId, 0, 'agent_execution_error', err?.message || String(err));
787
+ return {
788
+ branch: 'onFailure',
789
+ script: null,
790
+ processing,
791
+ reply,
792
+ };
793
+ }
794
+ }
795
+ async runScriptOnce(definition, runId, firedAt, sourcePayload, signal) {
796
+ return await this.scriptExecutor.execute({
797
+ trigger: definition,
798
+ triggerDir: this.manager.triggerDir(definition.id),
799
+ runId,
800
+ firedAt,
801
+ sourcePayload,
802
+ signal,
803
+ });
804
+ }
805
+ async checkConcurrency(definition, nextRunId, source) {
806
+ const runs = this.running.get(definition.id);
807
+ if (!runs || runs.size === 0 || definition.reliability.concurrency === 'allow')
808
+ return undefined;
809
+ const first = runs.values().next().value;
810
+ const conflictRunId = first?.run.runId;
811
+ if (definition.reliability.concurrency === 'forbid') {
812
+ const audit = this.writeSkippedAudit(definition, source.scheduledAt, source.firedAt, 'concurrency_forbid', conflictRunId, nextRunId);
813
+ return {
814
+ ok: true,
815
+ runId: nextRunId,
816
+ triggerId: definition.id,
817
+ status: 'skipped',
818
+ reason: 'concurrency_forbid',
819
+ audit,
820
+ ...(conflictRunId ? { conflictRunId } : {}),
821
+ };
822
+ }
823
+ await Promise.all(Array.from(runs.entries()).map(([runId, runtime]) => this.terminateRun(runId, runtime, {
824
+ reason: 'replaced',
825
+ message: `trigger run replaced by ${nextRunId}`,
826
+ })));
827
+ return await this.checkConcurrency(definition, nextRunId, source);
828
+ }
829
+ async recoverOpenRuns() {
830
+ for (const definition of this.manager.list({ all: true })) {
831
+ const activeRuns = this.state.list(definition.id);
832
+ if (activeRuns.length === 0)
833
+ continue;
834
+ for (const run of activeRuns) {
835
+ const status = run.phase === 'feedback-pending' ? 'skipped' : 'failed';
836
+ const audit = this.buildAudit({
837
+ definition,
838
+ runId: run.runId,
839
+ startedAt: run.startedAt,
840
+ status,
841
+ reason: 'daemon_restart',
842
+ source: this.buildSourceInfo(definition.source, run.startedAt, run.startedAt, this.sourcePayload(definition.source)),
843
+ script: null,
844
+ reply: null,
845
+ feedback: null,
846
+ effects: [],
847
+ error: status === 'failed' ? { code: 'daemon_restart', message: 'daemon restarted while run was open' } : null,
848
+ });
849
+ this.audit.write(audit);
850
+ if (status === 'failed') {
851
+ const reply = errorReply(run.runId, Math.max(0, Date.now() - run.startedAt), 'daemon_restart', 'daemon restarted while run was waiting or executing');
852
+ try {
853
+ await this.feedback.dispatch({
854
+ trigger: definition,
855
+ runId: run.runId,
856
+ firedAt: run.startedAt,
857
+ branch: 'onFailure',
858
+ reply,
859
+ sourcePayload: this.sourcePayload(definition.source),
860
+ causation: createRootCausation(),
861
+ });
862
+ }
863
+ catch (error) {
864
+ logger.warn(`[Trigger] daemon restart feedback failed: trigger=${definition.id} run=${run.runId} error=${error instanceof Error ? error.message : String(error)}`);
865
+ }
866
+ }
867
+ }
868
+ this.state.clear(definition.id);
869
+ }
870
+ }
871
+ resolveScheduledAt(definition) {
872
+ const now = Date.now();
873
+ switch (definition.source.type) {
874
+ case 'once':
875
+ return now;
876
+ case 'delay': {
877
+ const at = definition.createdAt + definition.source.afterMs;
878
+ return at <= now && definition.reliability.missedPolicy === 'skip' ? null : Math.max(at, now);
879
+ }
880
+ case 'at': {
881
+ const at = new Date(definition.source.at).getTime();
882
+ return at <= now && definition.reliability.missedPolicy === 'skip' ? null : Math.max(at, now);
883
+ }
884
+ case 'interval':
885
+ case 'cron': {
886
+ const signature = this.sourceSignature(definition.source);
887
+ const state = this.state.readSchedule(definition.id);
888
+ if (state?.sourceSignature === signature) {
889
+ return this.ensureScheduleRunMarker(definition, state).nextFireAt;
890
+ }
891
+ const nextFireAt = this.nextPeriodicFireAt(definition.source, now);
892
+ this.writeScheduleCursor(definition, nextFireAt, signature);
893
+ return nextFireAt;
894
+ }
895
+ case 'event':
896
+ return null;
897
+ }
898
+ }
899
+ advancePeriodicSchedule(definition, scheduledAt, firedAt) {
900
+ if (!this.isPeriodic(definition.source))
901
+ return;
902
+ const missed = scheduledAt < firedAt - 1000;
903
+ const ref = missed ? firedAt : scheduledAt;
904
+ let nextFireAt = this.nextPeriodicFireAt(definition.source, ref);
905
+ const now = Date.now();
906
+ if (nextFireAt <= now && missed) {
907
+ nextFireAt = this.nextPeriodicFireAt(definition.source, now);
908
+ }
909
+ this.writeScheduleCursor(definition, nextFireAt, undefined, { lastScheduledAt: scheduledAt, lastFiredAt: firedAt });
910
+ }
911
+ nextPeriodicFireAt(source, ref) {
912
+ if (source.type === 'interval')
913
+ return ref + source.everyMs;
914
+ return this.nextCronFireAt(source, ref);
915
+ }
916
+ nextCronFireAt(source, ref) {
917
+ const interval = CronExpressionParser.parse(source.expression, {
918
+ currentDate: new Date(ref),
919
+ tz: source.timezone,
920
+ });
921
+ return interval.next().getTime();
922
+ }
923
+ writeScheduleCursor(definition, nextFireAt, signature = this.sourceSignature(definition.source), patch = {}) {
924
+ const previous = this.state.readSchedule(definition.id);
925
+ this.state.writeSchedule(definition.id, {
926
+ nextFireAt,
927
+ updatedAt: Date.now(),
928
+ sourceSignature: signature,
929
+ ...(previous?.sourceSignature === signature && previous.lastScheduledAt !== undefined
930
+ ? { lastScheduledAt: previous.lastScheduledAt }
931
+ : {}),
932
+ ...(previous?.sourceSignature === signature && previous.lastFiredAt !== undefined
933
+ ? { lastFiredAt: previous.lastFiredAt }
934
+ : {}),
935
+ ...patch,
936
+ });
937
+ }
938
+ ensureScheduleRunMarker(definition, schedule) {
939
+ if (schedule.lastScheduledAt !== undefined || !this.isPeriodic(definition.source))
940
+ return schedule;
941
+ const inferred = this.previousPeriodicFireAt(definition.source, schedule.nextFireAt);
942
+ if (inferred === undefined)
943
+ return schedule;
944
+ if (Math.abs(schedule.updatedAt - inferred) > 300_000)
945
+ return schedule;
946
+ const patched = {
947
+ ...schedule,
948
+ lastScheduledAt: inferred,
949
+ lastFiredAt: schedule.updatedAt,
950
+ };
951
+ this.state.writeSchedule(definition.id, patched);
952
+ return patched;
953
+ }
954
+ previousPeriodicFireAt(source, ref) {
955
+ if (source.type === 'interval')
956
+ return ref - source.everyMs;
957
+ try {
958
+ const interval = CronExpressionParser.parse(source.expression, {
959
+ currentDate: new Date(ref),
960
+ tz: source.timezone,
961
+ });
962
+ return interval.prev().getTime();
963
+ }
964
+ catch {
965
+ return undefined;
966
+ }
967
+ }
968
+ sourceSignature(source) {
969
+ return JSON.stringify(source);
970
+ }
971
+ sourcePayload(source) {
972
+ switch (source.type) {
973
+ case 'once': return {};
974
+ case 'delay': return { afterMs: source.afterMs };
975
+ case 'at': return { at: source.at };
976
+ case 'cron': return {
977
+ expression: source.expression,
978
+ timezone: source.timezone ?? runtimeTimezone(),
979
+ };
980
+ case 'interval': return { everyMs: source.everyMs };
981
+ case 'event': return { eventPattern: source.eventPattern };
982
+ }
983
+ }
984
+ buildSourceInfo(source, firedAt, scheduledAt, payload) {
985
+ return {
986
+ type: source.type,
987
+ eventName: source.type === 'event' ? (typeof payload?.type === 'string' ? payload.type : source.eventPattern) : undefined,
988
+ scheduledAt,
989
+ firedAt,
990
+ payload: payload ?? this.sourcePayload(source),
991
+ };
992
+ }
993
+ buildAudit(input) {
994
+ const reply = summarizeReply(input.reply, { omitText: input.script?.result !== undefined });
995
+ return {
996
+ runId: input.runId,
997
+ triggerId: input.definition.id,
998
+ agentAid: input.definition.agentAid,
999
+ startedAt: input.startedAt,
1000
+ finishedAt: Date.now(),
1001
+ status: input.status,
1002
+ reason: input.reason,
1003
+ conflictRunId: input.conflictRunId,
1004
+ definition: {
1005
+ schemaVersion: input.definition.$schema_version,
1006
+ revision: definitionRevision(input.definition),
1007
+ name: input.definition.name,
1008
+ },
1009
+ source: input.source,
1010
+ processing: input.processing ?? null,
1011
+ script: summarizeScript(input.script),
1012
+ reply,
1013
+ feedback: summarizeFeedback(input.feedback, input.reply),
1014
+ ...(input.anomalies?.length ? { anomalies: input.anomalies } : {}),
1015
+ ...(input.incomplete ? { incomplete: input.incomplete } : {}),
1016
+ effects: input.effects,
1017
+ error: input.error,
1018
+ causation: input.causation,
1019
+ };
1020
+ }
1021
+ writeSkippedAudit(definition, scheduledAt, firedAt, reason, conflictRunId, runId = `run_${firedAt}_${crypto.randomBytes(3).toString('hex')}`) {
1022
+ const audit = this.buildAudit({
1023
+ definition,
1024
+ runId,
1025
+ startedAt: firedAt,
1026
+ status: 'skipped',
1027
+ reason,
1028
+ source: this.buildSourceInfo(definition.source, firedAt, scheduledAt, this.sourcePayload(definition.source)),
1029
+ script: null,
1030
+ reply: null,
1031
+ feedback: null,
1032
+ effects: [],
1033
+ error: null,
1034
+ conflictRunId,
1035
+ });
1036
+ this.audit.write(audit);
1037
+ this.publishTriggerRunOutcome(definition, audit);
1038
+ return audit;
1039
+ }
1040
+ limitDisabledReason(definition, firedAt) {
1041
+ if (!definition.limits)
1042
+ return undefined;
1043
+ const limitState = this.state.ensureLimitState(definition.id);
1044
+ if (definition.limits.maxRuns !== undefined && limitState.runCount >= definition.limits.maxRuns) {
1045
+ return 'max_runs';
1046
+ }
1047
+ if (definition.limits.maxDuration !== undefined) {
1048
+ const durationMs = parseDurationMs(definition.limits.maxDuration);
1049
+ if (firedAt - limitState.startedAt >= durationMs)
1050
+ return 'max_duration';
1051
+ }
1052
+ return undefined;
1053
+ }
1054
+ disableForLimit(definition, scheduledAt, firedAt, reason) {
1055
+ this.state.markLimitDisabled(definition.id, reason);
1056
+ this.setEnabled(definition.id, false);
1057
+ return this.writeSkippedAudit(definition, scheduledAt, firedAt, reason === 'max_runs' ? 'max_runs_reached' : 'max_duration_reached');
1058
+ }
1059
+ isOneShot(source) {
1060
+ return source.type === 'once' || source.type === 'delay' || source.type === 'at';
1061
+ }
1062
+ isPeriodic(source) {
1063
+ return source.type === 'cron' || source.type === 'interval';
1064
+ }
1065
+ addRunning(triggerId, runId, runtime) {
1066
+ let runs = this.running.get(triggerId);
1067
+ if (!runs) {
1068
+ runs = new Map();
1069
+ this.running.set(triggerId, runs);
1070
+ }
1071
+ runs.set(runId, runtime);
1072
+ }
1073
+ async acquireRunStartLock(triggerId) {
1074
+ const previous = this.runStartLocks.get(triggerId) ?? Promise.resolve();
1075
+ let releaseCurrent;
1076
+ const current = new Promise(resolve => { releaseCurrent = resolve; });
1077
+ const tail = previous.then(() => current);
1078
+ this.runStartLocks.set(triggerId, tail);
1079
+ await previous;
1080
+ let released = false;
1081
+ return () => {
1082
+ if (released)
1083
+ return;
1084
+ released = true;
1085
+ releaseCurrent();
1086
+ if (this.runStartLocks.get(triggerId) === tail) {
1087
+ this.runStartLocks.delete(triggerId);
1088
+ }
1089
+ };
1090
+ }
1091
+ terminateRun(runId, runtime, termination) {
1092
+ if (runtime.terminationSettlement)
1093
+ return runtime.terminationSettlement;
1094
+ runtime.termination = termination;
1095
+ if (!runtime.controller.signal.aborted) {
1096
+ runtime.controller.abort(new Error(termination.message));
1097
+ }
1098
+ runtime.terminationSettlement = this.settleTerminatedRun(runId, runtime, termination);
1099
+ return runtime.terminationSettlement;
1100
+ }
1101
+ async settleTerminatedRun(runId, runtime, termination) {
1102
+ const completion = await settleWithin(runtime.completion, RUN_COMPLETION_BARRIER_MS);
1103
+ const completedAnomalies = mergeTriggerExecutionAnomalies(runtime.anomalies, completion.settled ? completion.value.anomalies : undefined);
1104
+ const anomalies = accumulateTriggerExecutionAnomalies(completedAnomalies, drainTriggerExecutionAnomalies(runId));
1105
+ if (!completion.settled) {
1106
+ logger.warn(`[Trigger] run completion barrier timed out: trigger=${runtime.definition.id} run=${runId} reason=${termination.reason} timeoutMs=${RUN_COMPLETION_BARRIER_MS}`);
1107
+ }
1108
+ if (!runtime.terminalAuditWritten) {
1109
+ runtime.terminalAuditWritten = true;
1110
+ const incomplete = completion.settled
1111
+ ? completion.value.incomplete
1112
+ : {
1113
+ reason: 'completion_barrier_timeout',
1114
+ phase: 'run',
1115
+ timeoutMs: RUN_COMPLETION_BARRIER_MS,
1116
+ anomalyCapture: 'truncated',
1117
+ };
1118
+ const message = incomplete?.reason === 'completion_barrier_timeout'
1119
+ ? `${termination.message}; completion barrier timed out after ${incomplete.timeoutMs}ms; anomaly capture is truncated`
1120
+ : incomplete
1121
+ ? `${termination.message}; ${incomplete.phase} operation did not settle within ${incomplete.timeoutMs}ms abort grace; anomaly capture is truncated`
1122
+ : termination.message;
1123
+ const audit = this.buildAudit({
1124
+ definition: runtime.definition,
1125
+ runId,
1126
+ startedAt: runtime.run.startedAt,
1127
+ status: 'failed',
1128
+ reason: termination.reason,
1129
+ source: runtime.source,
1130
+ script: null,
1131
+ reply: null,
1132
+ feedback: null,
1133
+ anomalies,
1134
+ incomplete,
1135
+ effects: [],
1136
+ error: { code: termination.reason, message },
1137
+ causation: runtime.causation,
1138
+ });
1139
+ this.audit.write(audit);
1140
+ this.publishTriggerRunOutcome(runtime.definition, audit);
1141
+ recordCausationSpan(runtime.causation, 'trigger.run', {
1142
+ status: 'failed',
1143
+ refs: { triggerId: runtime.definition.id, runId },
1144
+ reason: termination.reason,
1145
+ });
1146
+ }
1147
+ this.finishRun(runtime.definition.id, runId);
1148
+ }
1149
+ finishRun(triggerId, runId) {
1150
+ const runs = this.running.get(triggerId);
1151
+ runs?.delete(runId);
1152
+ if (runs && runs.size === 0)
1153
+ this.running.delete(triggerId);
1154
+ this.state.remove(triggerId, runId);
1155
+ }
1156
+ isRunning(triggerId, runId) {
1157
+ return this.running.get(triggerId)?.has(runId) === true;
1158
+ }
1159
+ clearTimer(triggerId) {
1160
+ const timer = this.timers.get(triggerId);
1161
+ if (timer)
1162
+ clearTimeout(timer);
1163
+ this.timers.delete(triggerId);
1164
+ }
1165
+ registerEvent(definition) {
1166
+ if (!this.eventSource) {
1167
+ logger.warn(`[Trigger] event source disabled; skip ${definition.id}`);
1168
+ return;
1169
+ }
1170
+ this.eventSource.register(definition);
1171
+ }
1172
+ unregisterEvent(triggerId) {
1173
+ this.eventSource?.unregister(triggerId);
1174
+ }
1175
+ subscriptionInfo(definition) {
1176
+ if (definition.source.type !== 'event')
1177
+ return { status: 'not-event' };
1178
+ if (!definition.enabled)
1179
+ return { status: 'inactive', warning: 'trigger is disabled' };
1180
+ if (!this.eventSource)
1181
+ return { status: 'event-bus-unavailable', warning: 'event bus is not attached to this scheduler' };
1182
+ if (this.eventSource.has(definition.id))
1183
+ return { status: 'active' };
1184
+ return { status: 'inactive', warning: 'event subscription is not registered' };
1185
+ }
1186
+ async fireEventTrigger(triggerId, event) {
1187
+ const definition = this.manager.get(triggerId);
1188
+ if (!definition?.enabled || definition.source.type !== 'event')
1189
+ return;
1190
+ const disabledReason = this.limitDisabledReason(definition, event.firedAt);
1191
+ if (disabledReason) {
1192
+ this.disableForLimit(definition, undefined, event.firedAt, disabledReason);
1193
+ return;
1194
+ }
1195
+ await this.startRun(definition, {
1196
+ firedAt: event.firedAt,
1197
+ payload: event.payload,
1198
+ causation: event.causation,
1199
+ });
1200
+ }
1201
+ publishTriggerDefinitionEvent(type, definition) {
1202
+ const target = primaryTarget(definition);
1203
+ this.eventBus?.publish({
1204
+ type,
1205
+ triggerId: definition.id,
1206
+ name: definition.name,
1207
+ peerId: definition.origin?.peerId,
1208
+ targetChannel: target?.channelKey,
1209
+ targetChannelId: target?.channelId,
1210
+ scheduleType: definition.source.type,
1211
+ scheduleValue: this.sourceScheduleValue(definition.source),
1212
+ timestamp: Date.now(),
1213
+ });
1214
+ }
1215
+ publishTriggerFired(definition, runId, firedAt, causation) {
1216
+ const target = primaryTarget(definition);
1217
+ this.eventBus?.publish({
1218
+ type: 'trigger:fired',
1219
+ triggerId: definition.id,
1220
+ name: definition.name,
1221
+ runId,
1222
+ originTriggerId: definition.id,
1223
+ fireTime: firedAt,
1224
+ targetChannel: target?.channelKey,
1225
+ targetChannelId: target?.channelId,
1226
+ scheduleType: definition.source.type,
1227
+ timestamp: Date.now(),
1228
+ causation,
1229
+ });
1230
+ }
1231
+ publishTriggerRunOutcome(definition, audit) {
1232
+ if (!this.eventBus || audit.status === 'dry-run')
1233
+ return;
1234
+ const target = primaryTarget(definition);
1235
+ const targetChannel = target?.channelKey ?? 'daemon';
1236
+ const targetChannelId = target?.channelId ?? definition.agentAid;
1237
+ const base = {
1238
+ triggerId: definition.id,
1239
+ name: definition.name,
1240
+ runId: audit.runId,
1241
+ originTriggerId: definition.id,
1242
+ targetChannel,
1243
+ targetChannelId,
1244
+ fireTime: audit.source.firedAt,
1245
+ causation: audit.causation,
1246
+ };
1247
+ if (audit.status === 'completed' || audit.status === 'degraded' || audit.status === 'noop') {
1248
+ this.eventBus.publish({
1249
+ type: 'trigger:completed',
1250
+ ...base,
1251
+ messageId: this.triggerOutcomeMessageId(audit),
1252
+ durationMs: Math.max(0, audit.finishedAt - audit.startedAt),
1253
+ });
1254
+ return;
1255
+ }
1256
+ if (audit.status === 'failed') {
1257
+ this.eventBus.publish({
1258
+ type: 'trigger:failed',
1259
+ ...base,
1260
+ messageId: this.triggerOutcomeMessageId(audit),
1261
+ error: audit.error?.message ?? audit.reason ?? 'trigger failed',
1262
+ phase: 'execute',
1263
+ });
1264
+ return;
1265
+ }
1266
+ if (audit.status === 'skipped') {
1267
+ this.eventBus.publish({
1268
+ type: 'trigger:skipped',
1269
+ ...base,
1270
+ reason: audit.reason ?? 'skipped',
1271
+ });
1272
+ }
1273
+ }
1274
+ triggerOutcomeMessageId(audit) {
1275
+ return audit.effects.find(effect => effect.messageId)?.messageId ?? audit.runId;
1276
+ }
1277
+ sourceScheduleValue(source) {
1278
+ switch (source.type) {
1279
+ case 'cron': return source.expression;
1280
+ case 'interval': return `${source.everyMs}ms`;
1281
+ case 'delay': return `${source.afterMs}ms`;
1282
+ case 'at': return String(source.at);
1283
+ case 'once': return 'once';
1284
+ case 'event': return source.eventPattern;
1285
+ }
1286
+ }
1287
+ }
1288
+ function replyFromScriptResult(script, runId) {
1289
+ const result = script.result ?? {};
1290
+ const outcomeRaw = typeof result.outcome === 'string' ? result.outcome : undefined;
1291
+ const outcome = normalizeReplyOutcome(outcomeRaw);
1292
+ const text = result.text === undefined || result.text === null ? '' : String(result.text);
1293
+ if (!outcome) {
1294
+ return invalidScriptReply(script, runId, 'script_reply_invalid', 'script stdout must be a TriggerReply object with outcome: success | noop | error | interrupted | timeout');
1295
+ }
1296
+ const files = Array.isArray(result.files)
1297
+ ? result.files
1298
+ .filter((file) => isRecord(file) && typeof file.path === 'string')
1299
+ .map(file => ({
1300
+ path: String(file.path),
1301
+ name: typeof file.name === 'string' ? file.name : undefined,
1302
+ }))
1303
+ : [];
1304
+ const error = isRecord(result.error)
1305
+ ? {
1306
+ reason: typeof result.error.reason === 'string' ? result.error.reason : undefined,
1307
+ text: typeof result.error.text === 'string' ? result.error.text : text || 'script returned error',
1308
+ }
1309
+ : outcome === 'error'
1310
+ ? { text: text || 'script returned error' }
1311
+ : undefined;
1312
+ return {
1313
+ outcome,
1314
+ text,
1315
+ files,
1316
+ error,
1317
+ meta: {
1318
+ runId,
1319
+ durationMs: script.durationMs,
1320
+ toolCallCount: 0,
1321
+ },
1322
+ };
1323
+ }
1324
+ function normalizeReplyOutcome(value) {
1325
+ if (value === 'success' || value === 'noop' || value === 'error' || value === 'interrupted' || value === 'timeout') {
1326
+ return value;
1327
+ }
1328
+ return undefined;
1329
+ }
1330
+ function invalidScriptReply(script, runId, reason, text) {
1331
+ return {
1332
+ outcome: 'error',
1333
+ text: '',
1334
+ files: [],
1335
+ error: { reason, text },
1336
+ meta: {
1337
+ runId,
1338
+ durationMs: script.durationMs,
1339
+ toolCallCount: 0,
1340
+ },
1341
+ };
1342
+ }
1343
+ function branchFromReply(reply) {
1344
+ if (reply.outcome === 'success')
1345
+ return 'onReply';
1346
+ if (reply.outcome === 'noop')
1347
+ return 'onNoop';
1348
+ return 'onFailure';
1349
+ }
1350
+ function shouldRetryExecution(reply) {
1351
+ return reply.outcome === 'error' || reply.outcome === 'timeout' || reply.outcome === 'interrupted';
1352
+ }
1353
+ function triggerCancellationReason(signal) {
1354
+ const reason = signal.reason instanceof Error
1355
+ ? signal.reason.message
1356
+ : typeof signal.reason === 'string'
1357
+ ? signal.reason
1358
+ : '';
1359
+ if (reason.includes('replaced'))
1360
+ return 'replaced';
1361
+ if (reason.includes('stopped'))
1362
+ return 'stopped';
1363
+ return 'cancelled';
1364
+ }
1365
+ function shouldRetryExecutionResult(execution) {
1366
+ if (execution.reply)
1367
+ return shouldRetryExecution(execution.reply);
1368
+ return execution.feedbackResult?.status === 'failed';
1369
+ }
1370
+ function statusWithExecutionAnomalies(status, anomalies) {
1371
+ return status === 'completed' && anomalies.length ? 'degraded' : status;
1372
+ }
1373
+ function errorReply(runId, durationMs, reason, text) {
1374
+ return {
1375
+ outcome: 'error',
1376
+ text: '',
1377
+ files: [],
1378
+ error: { reason, text },
1379
+ meta: {
1380
+ runId,
1381
+ durationMs,
1382
+ toolCallCount: 0,
1383
+ },
1384
+ };
1385
+ }
1386
+ function summarizeScript(script) {
1387
+ if (!script)
1388
+ return null;
1389
+ const { stdoutPreview, error, ...summary } = script;
1390
+ const summarizedError = error
1391
+ ? {
1392
+ code: error.code,
1393
+ message: error.message,
1394
+ ...(error.stdoutPreview && error.stdoutPreview !== stdoutPreview
1395
+ ? { stdoutPreview: error.stdoutPreview }
1396
+ : {}),
1397
+ ...(error.stderrPreview && error.stderrPreview !== script.stderrPreview
1398
+ ? { stderrPreview: error.stderrPreview }
1399
+ : {}),
1400
+ }
1401
+ : undefined;
1402
+ return {
1403
+ ...summary,
1404
+ ...(script.result === undefined && stdoutPreview ? { stdoutPreview } : {}),
1405
+ ...(summarizedError ? { error: summarizedError } : {}),
1406
+ };
1407
+ }
1408
+ function summarizeFeedback(feedback, reply) {
1409
+ if (!feedback)
1410
+ return feedback;
1411
+ const duplicateReply = !!reply?.text
1412
+ && feedback.renderedTextHash === sha256(reply.text);
1413
+ const emptyOrSilent = feedback.strategy === 'silent'
1414
+ || !feedback.renderedTextPreview;
1415
+ if (!duplicateReply && !emptyOrSilent)
1416
+ return feedback;
1417
+ const { renderedTextHash: _hash, renderedTextPreview: _preview, ...summary } = feedback;
1418
+ return summary;
1419
+ }
1420
+ function summarizeReply(reply, opts = {}) {
1421
+ if (!reply)
1422
+ return null;
1423
+ return {
1424
+ outcome: reply.outcome,
1425
+ ...(!opts.omitText && reply.text
1426
+ ? { textHash: sha256(reply.text), textPreview: previewText(reply.text) }
1427
+ : {}),
1428
+ fileCount: reply.files.length,
1429
+ durationMs: reply.meta.durationMs,
1430
+ numTurns: reply.meta.numTurns,
1431
+ tokenUsage: reply.meta.tokenUsage,
1432
+ toolCallCount: reply.meta.toolCallCount,
1433
+ };
1434
+ }
1435
+ function primaryTarget(definition) {
1436
+ if (definition.feedback.strategy === 'target')
1437
+ return definition.feedback.target;
1438
+ if (!definition.origin)
1439
+ return undefined;
1440
+ return {
1441
+ channelKey: definition.origin.channelKey,
1442
+ channelId: definition.origin.channelId,
1443
+ session: definition.origin.session,
1444
+ threadId: definition.origin.threadId,
1445
+ };
1446
+ }
1447
+ function eventTestMatchError(reason, eventPattern) {
1448
+ switch (reason) {
1449
+ case 'missing_event_type':
1450
+ return 'simulated event payload.type must be a non-empty string';
1451
+ case 'event_pattern_mismatch':
1452
+ return `simulated event payload.type does not match Event Pattern "${eventPattern}"`;
1453
+ case 'event_filter_mismatch':
1454
+ return 'simulated event payload does not satisfy the Event Trigger filter';
1455
+ }
1456
+ }
1457
+ function runtimeTimezone() {
1458
+ try {
1459
+ return Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC';
1460
+ }
1461
+ catch {
1462
+ return 'UTC';
1463
+ }
1464
+ }
1465
+ function isRecord(value) {
1466
+ return !!value && typeof value === 'object' && !Array.isArray(value);
1467
+ }
1468
+ function summarizeListSource(source) {
1469
+ switch (source.type) {
1470
+ case 'once': return { type: 'once' };
1471
+ case 'delay': return { type: 'delay', afterMs: source.afterMs };
1472
+ case 'at': return { type: 'at', at: source.at };
1473
+ case 'cron': return {
1474
+ type: 'cron',
1475
+ expression: source.expression,
1476
+ ...(source.timezone ? { timezone: source.timezone } : {}),
1477
+ };
1478
+ case 'interval': return { type: 'interval', everyMs: source.everyMs };
1479
+ case 'event': return { type: 'event', eventPattern: source.eventPattern };
1480
+ }
1481
+ }
1482
+ function sleep(ms, signal) {
1483
+ if (signal?.aborted)
1484
+ return Promise.resolve(false);
1485
+ return new Promise(resolve => {
1486
+ let settled = false;
1487
+ const finish = (completed) => {
1488
+ if (settled)
1489
+ return;
1490
+ settled = true;
1491
+ clearTimeout(timer);
1492
+ signal?.removeEventListener('abort', onAbort);
1493
+ resolve(completed);
1494
+ };
1495
+ const onAbort = () => finish(false);
1496
+ const timer = setTimeout(() => finish(true), Math.max(0, ms));
1497
+ timer.unref?.();
1498
+ signal?.addEventListener('abort', onAbort, { once: true });
1499
+ });
1500
+ }
1501
+ function awaitWithAbortSettleGrace(operation, signal, graceMs, phase) {
1502
+ return new Promise((resolve, reject) => {
1503
+ let settled = false;
1504
+ let graceTimer;
1505
+ const finish = (callback) => {
1506
+ if (settled)
1507
+ return;
1508
+ settled = true;
1509
+ if (graceTimer)
1510
+ clearTimeout(graceTimer);
1511
+ signal.removeEventListener('abort', onAbort);
1512
+ callback();
1513
+ };
1514
+ const onAbort = () => {
1515
+ if (settled || graceTimer)
1516
+ return;
1517
+ graceTimer = setTimeout(() => {
1518
+ const reason = signal.reason instanceof Error
1519
+ ? signal.reason.message
1520
+ : typeof signal.reason === 'string'
1521
+ ? signal.reason
1522
+ : 'trigger run aborted';
1523
+ finish(() => reject(new AbortSettleGraceError(reason, phase, graceMs)));
1524
+ }, Math.max(0, graceMs));
1525
+ graceTimer.unref?.();
1526
+ };
1527
+ operation.then(value => finish(() => resolve(value)), error => finish(() => reject(error)));
1528
+ signal.addEventListener('abort', onAbort, { once: true });
1529
+ if (signal.aborted)
1530
+ onAbort();
1531
+ });
1532
+ }
1533
+ class AbortSettleGraceError extends Error {
1534
+ phase;
1535
+ graceMs;
1536
+ constructor(reason, phase, graceMs) {
1537
+ super(`${reason}; ${phase} operation did not settle within ${graceMs}ms abort grace`);
1538
+ this.phase = phase;
1539
+ this.graceMs = graceMs;
1540
+ this.name = 'AbortSettleGraceError';
1541
+ }
1542
+ }
1543
+ function settleWithin(promise, timeoutMs) {
1544
+ return new Promise(resolve => {
1545
+ let settled = false;
1546
+ const finish = (result) => {
1547
+ if (settled)
1548
+ return;
1549
+ settled = true;
1550
+ clearTimeout(timer);
1551
+ resolve(result);
1552
+ };
1553
+ const timer = setTimeout(() => finish({ settled: false }), Math.max(0, timeoutMs));
1554
+ timer.unref?.();
1555
+ promise.then(value => finish({ settled: true, value }), () => finish({ settled: false }));
1556
+ });
1557
+ }
1558
+ class TriggerExecutionFailure extends Error {
1559
+ anomalies;
1560
+ constructor(error, anomalies) {
1561
+ super(error instanceof Error ? error.message : String(error), { cause: error });
1562
+ this.anomalies = anomalies;
1563
+ this.name = 'TriggerExecutionFailure';
1564
+ }
1565
+ }
1566
+ function executionAnomaliesFromError(error) {
1567
+ return error instanceof TriggerExecutionFailure ? error.anomalies : undefined;
1568
+ }
1569
+ function limitsSignature(limits) {
1570
+ return JSON.stringify(limits ?? null);
1571
+ }
1572
+ function mergeStats(legacy, current) {
1573
+ if (!legacy)
1574
+ return current;
1575
+ const out = {
1576
+ fireCount: legacy.fireCount + current.fireCount,
1577
+ failCount: legacy.failCount + current.failCount,
1578
+ };
1579
+ const legacyAt = legacy.lastFiredAt ?? -1;
1580
+ const currentAt = current.lastFiredAt ?? -1;
1581
+ if (legacyAt >= currentAt && legacy.lastFiredAt !== undefined) {
1582
+ out.lastFiredAt = legacy.lastFiredAt;
1583
+ out.lastResult = legacy.lastResult;
1584
+ }
1585
+ else if (current.lastFiredAt !== undefined) {
1586
+ out.lastFiredAt = current.lastFiredAt;
1587
+ out.lastResult = current.lastResult;
1588
+ }
1589
+ return out;
1590
+ }
1591
+ function accountScheduleMarker(stats, schedule, hasSkippedSchedule) {
1592
+ if (stats.fireCount > 0 || schedule?.lastScheduledAt === undefined)
1593
+ return stats;
1594
+ if (hasSkippedSchedule(schedule.lastScheduledAt))
1595
+ return stats;
1596
+ return {
1597
+ ...stats,
1598
+ fireCount: 1,
1599
+ };
1600
+ }