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,520 @@
1
+ import { CronExpressionParser } from 'cron-parser';
2
+ const TRIGGER_UPDATE_FLAGS = new Set([
3
+ 'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
4
+ 'prompt', 'name',
5
+ 'model', 'effort', 'permission',
6
+ 'max-runs', 'max-duration', 'concurrency', 'missed-policy',
7
+ ]);
8
+ // Note: unquoted multi-word values (e.g. --prompt=hello world) are not supported.
9
+ // The second word would be treated as an unknown token. Always quote multi-word values:
10
+ // --prompt "hello world" or --prompt='hello world'
11
+ function parseFlags(args) {
12
+ const flags = new Map();
13
+ const re = /--(\w[\w-]*)(?:=("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+)|(?:\s+(?!--)("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|\S+))?)?/g;
14
+ let m;
15
+ while ((m = re.exec(args)) !== null) {
16
+ const key = m[1];
17
+ const val = m[2] ?? m[3];
18
+ flags.set(key, val === undefined ? true : val.replace(/^["']|["']$/g, ''));
19
+ }
20
+ return flags;
21
+ }
22
+ export function parseDuration(s) {
23
+ const m = /^([1-9]\d*)([dhms])$/.exec(s.trim().toLowerCase());
24
+ if (!m)
25
+ return null;
26
+ const amount = Number(m[1]);
27
+ const unit = m[2];
28
+ const multiplier = unit === 'd' ? 86_400_000 : unit === 'h' ? 3_600_000 : unit === 'm' ? 60_000 : 1_000;
29
+ return amount * multiplier;
30
+ }
31
+ export function parseIsoDate(s) {
32
+ const d = new Date(s);
33
+ return isNaN(d.getTime()) ? null : d.getTime();
34
+ }
35
+ export function validateCronExpr(expr) {
36
+ try {
37
+ CronExpressionParser.parse(expr);
38
+ return true;
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ }
44
+ function parsePositiveIntegerFlag(flags, name, label) {
45
+ if (!flags.has(name))
46
+ return { ok: true };
47
+ const raw = flags.get(name);
48
+ if (!raw || raw === true)
49
+ return { ok: false, error: `${label} 不能为空` };
50
+ if (!/^[1-9]\d*$/.test(raw))
51
+ return { ok: false, error: `${label} 必须是正整数` };
52
+ return { ok: true, value: Number(raw) };
53
+ }
54
+ function parseDurationFlag(flags, name, label) {
55
+ if (!flags.has(name))
56
+ return { ok: true };
57
+ const raw = flags.get(name);
58
+ if (!raw || raw === true)
59
+ return { ok: false, error: `${label} 不能为空` };
60
+ const value = parseDuration(raw);
61
+ if (value === null)
62
+ return { ok: false, error: `${label} 支持格式:30s、15m、2h、1d` };
63
+ return { ok: true, value };
64
+ }
65
+ function parseLimitDurationFlag(flags, name, label) {
66
+ if (!flags.has(name))
67
+ return { ok: true };
68
+ const raw = flags.get(name);
69
+ if (!raw || raw === true)
70
+ return { ok: false, error: `${label} 不能为空` };
71
+ if (!/^[1-9]\d*(s|m|h|d)$/.test(raw))
72
+ return { ok: false, error: `${label} 支持格式:30s、15m、2h、1d` };
73
+ return { ok: true, value: raw };
74
+ }
75
+ const PERMISSION_MODES = new Set([
76
+ 'readonly',
77
+ 'auto',
78
+ 'request',
79
+ 'bypass',
80
+ ]);
81
+ const TRIGGER_EFFORTS = new Set(['low', 'medium', 'high', 'xhigh', 'max']);
82
+ function parsePermissionModeFlag(flags, opts = {}) {
83
+ if (!flags.has('permission'))
84
+ return { ok: true };
85
+ const raw = flags.get('permission');
86
+ if (!raw || raw === true)
87
+ return { ok: false, error: '--permission 不能为空' };
88
+ if (raw === 'inherit') {
89
+ if (opts.allowInherit)
90
+ return { ok: true, value: null };
91
+ return { ok: false, error: '--permission 创建时只能接受 readonly、auto、request 或 bypass' };
92
+ }
93
+ if (!PERMISSION_MODES.has(raw)) {
94
+ return { ok: false, error: '--permission 只接受 readonly、auto、request 或 bypass' };
95
+ }
96
+ return { ok: true, value: raw };
97
+ }
98
+ function parseModelFlag(flags) {
99
+ if (!flags.has('model'))
100
+ return { ok: true };
101
+ const raw = flags.get('model');
102
+ if (!raw || raw === true)
103
+ return { ok: false, error: '--model 不能为空' };
104
+ return { ok: true, value: raw };
105
+ }
106
+ function parseEffortFlag(flags) {
107
+ if (!flags.has('effort'))
108
+ return { ok: true };
109
+ const raw = flags.get('effort');
110
+ if (!raw || raw === true)
111
+ return { ok: false, error: '--effort 不能为空' };
112
+ if (!TRIGGER_EFFORTS.has(raw)) {
113
+ return { ok: false, error: '--effort 只接受 low、medium、high、xhigh 或 max' };
114
+ }
115
+ return { ok: true, value: raw };
116
+ }
117
+ function rejectDeprecatedFlags(flags) {
118
+ if (flags.has('mode'))
119
+ return '--mode 已废弃,请使用 --exec script|trigger-session|target-session';
120
+ if (flags.has('session'))
121
+ return '--session 已废弃,请使用 --feedback/--target-session 或 --trigger-thread';
122
+ if (flags.has('thread'))
123
+ return '--thread 已废弃,请使用 --target-thread-id';
124
+ if (flags.has('script'))
125
+ return '--script 已废弃,请使用 --exec script --script-path';
126
+ if (flags.has('runtime'))
127
+ return '--runtime 已废弃,请使用 --script-runtime';
128
+ if (flags.has('channel') || flags.has('channelid'))
129
+ return '--channel/--channelid 已废弃,请使用 --target-channel/--target-channel-id';
130
+ if (flags.has('chatmode'))
131
+ return '--chatmode 不支持;trigger chatmode 由运行时按 peerType 强制决定';
132
+ if (flags.has('activity') || flags.has('show-activities') || flags.has('showActivities')) {
133
+ return '--activity/--show-activities 不支持;trigger 中间输出由运行时按 peerType 强制决定';
134
+ }
135
+ return undefined;
136
+ }
137
+ function parseExecutionType(flags) {
138
+ if (!flags.has('exec'))
139
+ return { ok: true };
140
+ const raw = flags.get('exec');
141
+ if (!raw || raw === true)
142
+ return { ok: false, error: '--exec 不能为空' };
143
+ if (raw === 'script')
144
+ return { ok: true, value: 'script' };
145
+ if (raw === 'trigger-session' || raw === 'trigger_session')
146
+ return { ok: true, value: 'trigger_session' };
147
+ if (raw === 'target-session' || raw === 'target_session')
148
+ return { ok: true, value: 'target_session' };
149
+ return { ok: false, error: '--exec 只接受 script、trigger-session 或 target-session' };
150
+ }
151
+ function parseFeedbackStrategy(flags) {
152
+ if (!flags.has('feedback'))
153
+ return { ok: true };
154
+ const raw = flags.get('feedback');
155
+ if (!raw || raw === true)
156
+ return { ok: false, error: '--feedback 不能为空' };
157
+ if (raw === 'origin' || raw === 'target' || raw === 'silent')
158
+ return { ok: true, value: raw };
159
+ return { ok: false, error: '--feedback 只接受 origin、target 或 silent' };
160
+ }
161
+ function parseTriggerThread(flags) {
162
+ if (!flags.has('trigger-thread'))
163
+ return { ok: true };
164
+ const raw = flags.get('trigger-thread');
165
+ if (!raw || raw === true)
166
+ return { ok: false, error: '--trigger-thread 不能为空' };
167
+ if (raw === 'per-run')
168
+ return { ok: true, value: 'per_run' };
169
+ if (raw === 'by-trigger')
170
+ return { ok: true, value: 'by_trigger' };
171
+ if (raw === 'per_run' || raw === 'by_trigger')
172
+ return { ok: true, value: raw };
173
+ return { ok: false, error: '--trigger-thread 只接受 per-run 或 by-trigger' };
174
+ }
175
+ function parseTargetSession(flags) {
176
+ if (!flags.has('target-session'))
177
+ return { ok: true };
178
+ const raw = flags.get('target-session');
179
+ if (!raw || raw === true)
180
+ return { ok: false, error: '--target-session 不能为空' };
181
+ if (raw === 'main' || raw === 'thread')
182
+ return { ok: true, value: raw };
183
+ return { ok: false, error: '--target-session 只接受 main 或 thread' };
184
+ }
185
+ function parseSourceFlags(flags, opts = {}) {
186
+ const hasOnce = flags.has('once');
187
+ const hasDelay = flags.has('delay');
188
+ const hasAt = flags.has('at');
189
+ const hasCron = flags.has('cron');
190
+ const hasEvery = flags.has('every');
191
+ const hasEvent = flags.has('event');
192
+ const count = [hasOnce, hasDelay, hasAt, hasCron, hasEvery, hasEvent].filter(Boolean).length;
193
+ if (!opts.update && count === 0) {
194
+ return { ok: false, error: '必须指定触发参数:--once | --delay <时长> | --at <ISO时间> | --cron <表达式> | --every <时长> | --event <事件模式>' };
195
+ }
196
+ if (count > 1)
197
+ return { ok: false, error: '--once、--delay、--at、--cron、--every、--event 互斥,只能指定一个' };
198
+ if (count === 0)
199
+ return { ok: true };
200
+ if (hasOnce)
201
+ return { ok: true, scheduleType: 'once', scheduleValue: '' };
202
+ if (hasDelay) {
203
+ const raw = flags.get('delay');
204
+ if (!raw || raw === true)
205
+ return { ok: false, error: '--delay 不能为空' };
206
+ const ms = parseDuration(raw);
207
+ if (ms === null)
208
+ return { ok: false, error: `无法解析 --delay "${raw}",支持格式:30s、15m、2h、1d` };
209
+ return { ok: true, scheduleType: 'delay', scheduleValue: String(ms) };
210
+ }
211
+ if (hasAt) {
212
+ const raw = flags.get('at');
213
+ if (!raw || raw === true)
214
+ return { ok: false, error: '--at 不能为空' };
215
+ const ts = parseIsoDate(raw);
216
+ if (ts === null)
217
+ return { ok: false, error: `无法解析 --at "${raw}",请使用 ISO 格式,如 2026-05-15T09:00` };
218
+ if (!opts.update && ts <= Date.now())
219
+ return { ok: false, error: `--at 时间已过期:${raw}` };
220
+ return { ok: true, scheduleType: 'at', scheduleValue: new Date(ts).toISOString() };
221
+ }
222
+ if (hasCron) {
223
+ const raw = flags.get('cron');
224
+ if (!raw || raw === true)
225
+ return { ok: false, error: '--cron 不能为空' };
226
+ if (!validateCronExpr(raw)) {
227
+ const truncated = /^[\d*/,-]+$/.test(raw) && /--cron\s+\S+\s+[*\d]/.test(String(raw));
228
+ const hint = truncated ? '(看起来 cron 表达式被空格截断了,请用引号包裹)' : '(需 5 段:分 时 日 月 周,如 */15 * * * *)';
229
+ return { ok: false, error: `无效的 cron 表达式:"${raw}" ${hint}` };
230
+ }
231
+ const timezone = flags.has('tz') ? flags.get('tz') : undefined;
232
+ if (timezone === true)
233
+ return { ok: false, error: '--tz 不能为空' };
234
+ return { ok: true, scheduleType: 'cron', scheduleValue: raw, timezone: timezone };
235
+ }
236
+ if (hasEvery) {
237
+ const raw = flags.get('every');
238
+ if (!raw || raw === true)
239
+ return { ok: false, error: '--every 不能为空' };
240
+ const ms = parseDuration(raw);
241
+ if (ms === null)
242
+ return { ok: false, error: `无法解析 --every "${raw}",支持格式:30s、15m、2h、1d` };
243
+ return { ok: true, scheduleType: 'interval', scheduleValue: String(ms) };
244
+ }
245
+ const raw = flags.get('event');
246
+ if (!raw || raw === true)
247
+ return { ok: false, error: '--event 不能为空' };
248
+ return { ok: true, scheduleType: 'event', scheduleValue: raw };
249
+ }
250
+ function commonParsed(flags, opts = {}) {
251
+ const deprecated = rejectDeprecatedFlags(flags);
252
+ if (deprecated)
253
+ return { ok: false, error: deprecated };
254
+ const source = parseSourceFlags(flags, opts);
255
+ if (!source.ok)
256
+ return source;
257
+ if (flags.has('tz') && source.scheduleType !== 'cron' && (!opts.update || source.scheduleType !== undefined)) {
258
+ return { ok: false, error: '--tz 只能和 --cron 一起使用' };
259
+ }
260
+ const execution = parseExecutionType(flags);
261
+ if (!execution.ok)
262
+ return execution;
263
+ const feedback = parseFeedbackStrategy(flags);
264
+ if (!feedback.ok)
265
+ return feedback;
266
+ const triggerThread = parseTriggerThread(flags);
267
+ if (!triggerThread.ok)
268
+ return triggerThread;
269
+ const targetSession = parseTargetSession(flags);
270
+ if (!targetSession.ok)
271
+ return targetSession;
272
+ const scriptTimeout = parseDurationFlag(flags, 'script-timeout', '--script-timeout');
273
+ if (!scriptTimeout.ok)
274
+ return scriptTimeout;
275
+ const maxRuns = parsePositiveIntegerFlag(flags, 'max-runs', '--max-runs');
276
+ if (!maxRuns.ok)
277
+ return maxRuns;
278
+ const maxDuration = parseLimitDurationFlag(flags, 'max-duration', '--max-duration');
279
+ if (!maxDuration.ok)
280
+ return maxDuration;
281
+ const model = parseModelFlag(flags);
282
+ if (!model.ok)
283
+ return model;
284
+ const effort = parseEffortFlag(flags);
285
+ if (!effort.ok)
286
+ return effort;
287
+ const permissionMode = parsePermissionModeFlag(flags, { allowInherit: opts.update });
288
+ if (!permissionMode.ok)
289
+ return permissionMode;
290
+ let scriptArgs;
291
+ if (flags.has('script-args')) {
292
+ const raw = flags.get('script-args');
293
+ if (!raw || raw === true)
294
+ return { ok: false, error: '--script-args 不能为空' };
295
+ try {
296
+ scriptArgs = JSON.parse(raw);
297
+ }
298
+ catch {
299
+ return { ok: false, error: '--script-args 必须是合法的 JSON' };
300
+ }
301
+ }
302
+ const prompt = flags.get('prompt');
303
+ if (prompt === true)
304
+ return { ok: false, error: '--prompt 不能为空' };
305
+ if (typeof prompt === 'string' && prompt.length > 4096)
306
+ return { ok: false, error: '--prompt 超过 4096 字符限制' };
307
+ const scriptPath = flags.get('script-path');
308
+ if (scriptPath === true)
309
+ return { ok: false, error: '--script-path 不能为空' };
310
+ const scriptRuntime = flags.get('script-runtime');
311
+ if (scriptRuntime === true)
312
+ return { ok: false, error: '--script-runtime 不能为空' };
313
+ if (scriptRuntime !== undefined && scriptRuntime !== 'node' && scriptRuntime !== 'python' && scriptRuntime !== 'bash') {
314
+ return { ok: false, error: '--script-runtime 只接受 node、python 或 bash' };
315
+ }
316
+ const targetChannel = flags.get('target-channel');
317
+ if (targetChannel === true)
318
+ return { ok: false, error: '--target-channel 不能为空' };
319
+ const targetChannelId = flags.get('target-channel-id');
320
+ if (targetChannelId === true)
321
+ return { ok: false, error: '--target-channel-id 不能为空' };
322
+ const targetThreadId = flags.get('target-thread-id');
323
+ if (targetThreadId === true)
324
+ return { ok: false, error: '--target-thread-id 不能为空' };
325
+ const hasTargetField = targetChannel !== undefined || targetChannelId !== undefined || targetSession.value !== undefined || targetThreadId !== undefined;
326
+ if ((targetChannel === undefined) !== (targetChannelId === undefined)) {
327
+ return { ok: false, error: '--target-channel 与 --target-channel-id 必须同时指定或同时省略' };
328
+ }
329
+ const inferredExecution = execution.value ?? (scriptPath ? 'script' : 'target_session');
330
+ if (inferredExecution === 'script') {
331
+ if (!scriptPath && !opts.update)
332
+ return { ok: false, error: '--exec script 需要 --script-path' };
333
+ if (scriptPath && !scriptRuntime && !opts.update)
334
+ return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
335
+ }
336
+ else if (!prompt && !opts.update) {
337
+ return { ok: false, error: '--prompt 为必填项' };
338
+ }
339
+ const targetSessionValue = targetSession.value ?? (targetThreadId ? 'thread' : 'main');
340
+ const feedbackStrategy = feedback.value ?? (hasTargetField ? 'target' : 'origin');
341
+ return {
342
+ ok: true,
343
+ value: {
344
+ ...source,
345
+ executionType: execution.value,
346
+ inferredExecution,
347
+ feedbackStrategy,
348
+ targetChannel: targetChannel,
349
+ targetChannelId: targetChannelId,
350
+ targetSession: targetSessionValue,
351
+ targetThreadId: targetThreadId,
352
+ agentId: flags.has('agent') ? flags.get('agent') : undefined,
353
+ name: flags.has('name') ? flags.get('name') : undefined,
354
+ prompt: prompt,
355
+ scriptPath: scriptPath,
356
+ scriptRuntime: scriptRuntime,
357
+ scriptArgs,
358
+ scriptTimeoutMs: scriptTimeout.value,
359
+ triggerThread: triggerThread.value,
360
+ maxRuns: maxRuns.value,
361
+ maxDuration: maxDuration.value,
362
+ model: model.value,
363
+ effort: effort.value,
364
+ permissionMode: permissionMode.value,
365
+ },
366
+ };
367
+ }
368
+ export function parseTriggerSet(args) {
369
+ const flags = parseFlags(args);
370
+ const parsed = commonParsed(flags);
371
+ if (!parsed.ok)
372
+ return parsed;
373
+ const value = parsed.value;
374
+ return {
375
+ ok: true,
376
+ value: {
377
+ scheduleType: value.scheduleType,
378
+ scheduleValue: value.scheduleValue ?? '',
379
+ timezone: value.timezone,
380
+ executionType: value.inferredExecution,
381
+ feedbackStrategy: value.feedbackStrategy,
382
+ targetChannel: value.targetChannel,
383
+ targetChannelId: value.targetChannelId,
384
+ targetSession: value.targetSession,
385
+ targetThreadId: value.targetThreadId,
386
+ agentId: value.agentId,
387
+ name: value.name,
388
+ prompt: value.prompt,
389
+ scriptPath: value.scriptPath,
390
+ scriptRuntime: value.scriptRuntime,
391
+ scriptArgs: value.scriptArgs,
392
+ scriptTimeoutMs: value.scriptTimeoutMs,
393
+ triggerThread: value.triggerThread,
394
+ maxRuns: value.maxRuns,
395
+ maxDuration: value.maxDuration,
396
+ model: value.model,
397
+ effort: value.effort,
398
+ permissionMode: value.permissionMode,
399
+ },
400
+ };
401
+ }
402
+ export function parseTriggerUpdate(args) {
403
+ const trimmed = args.trim();
404
+ if (!trimmed)
405
+ return { ok: false, error: '用法:/trigger update <名称|ID> [--参数...]' };
406
+ let nameOrId;
407
+ let rest;
408
+ if (trimmed.startsWith('"') || trimmed.startsWith("'")) {
409
+ const quote = trimmed[0];
410
+ const end = trimmed.indexOf(quote, 1);
411
+ if (end === -1)
412
+ return { ok: false, error: '名称引号未闭合' };
413
+ nameOrId = trimmed.slice(1, end);
414
+ rest = trimmed.slice(end + 1).trim();
415
+ }
416
+ else {
417
+ const spaceIdx = trimmed.indexOf(' ');
418
+ if (spaceIdx === -1)
419
+ return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay、--event 等)' };
420
+ nameOrId = trimmed.slice(0, spaceIdx);
421
+ rest = trimmed.slice(spaceIdx + 1).trim();
422
+ }
423
+ if (!rest)
424
+ return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay、--event 等)' };
425
+ const flags = parseFlags(rest);
426
+ return parseTriggerUpdateFlags(nameOrId, flags);
427
+ }
428
+ export function parseTriggerUpdateArgv(nameOrId, args) {
429
+ const flags = new Map();
430
+ for (let i = 0; i < args.length; i++) {
431
+ const arg = args[i];
432
+ if (!arg.startsWith('--'))
433
+ return { ok: false, error: `无法识别 update 参数: ${arg}` };
434
+ const equals = arg.indexOf('=');
435
+ if (equals > 2) {
436
+ flags.set(arg.slice(2, equals), arg.slice(equals + 1));
437
+ continue;
438
+ }
439
+ const key = arg.slice(2);
440
+ const next = args[i + 1];
441
+ if (next !== undefined && !next.startsWith('--')) {
442
+ flags.set(key, next);
443
+ i += 1;
444
+ }
445
+ else {
446
+ flags.set(key, true);
447
+ }
448
+ }
449
+ return parseTriggerUpdateFlags(nameOrId, flags);
450
+ }
451
+ function parseTriggerUpdateFlags(nameOrId, flags) {
452
+ if (flags.size === 0)
453
+ return { ok: false, error: '至少需要指定一个修改参数(如 --prompt、--delay、--event 等)' };
454
+ for (const flag of flags.keys()) {
455
+ if (!TRIGGER_UPDATE_FLAGS.has(flag))
456
+ return { ok: false, error: `update 不支持参数 --${flag}` };
457
+ }
458
+ const parsed = commonParsed(flags, { update: true });
459
+ if (!parsed.ok)
460
+ return parsed;
461
+ const value = parsed.value;
462
+ const update = {};
463
+ if (value.scheduleType !== undefined)
464
+ update.scheduleType = value.scheduleType;
465
+ if (value.scheduleValue !== undefined)
466
+ update.scheduleValue = value.scheduleValue;
467
+ if (value.timezone !== undefined)
468
+ update.timezone = value.timezone;
469
+ if (flags.has('tz') && update.timezone === undefined) {
470
+ const timezone = optionalFlagString(flags, 'tz');
471
+ if ('error' in timezone)
472
+ return { ok: false, error: timezone.error };
473
+ if (timezone.value !== undefined)
474
+ update.timezone = timezone.value;
475
+ }
476
+ if (value.name !== undefined)
477
+ update.name = value.name;
478
+ if (value.prompt !== undefined)
479
+ update.prompt = value.prompt;
480
+ if (value.model !== undefined)
481
+ update.model = value.model;
482
+ if (value.effort !== undefined)
483
+ update.effort = value.effort;
484
+ if (value.permissionMode !== undefined)
485
+ update.permissionMode = value.permissionMode;
486
+ if (value.maxRuns !== undefined)
487
+ update.maxRuns = value.maxRuns;
488
+ if (value.maxDuration !== undefined)
489
+ update.maxDuration = value.maxDuration;
490
+ const concurrency = optionalFlagString(flags, 'concurrency');
491
+ if ('error' in concurrency)
492
+ return { ok: false, error: concurrency.error };
493
+ if (concurrency.value !== undefined) {
494
+ if (concurrency.value !== 'forbid' && concurrency.value !== 'replace' && concurrency.value !== 'allow') {
495
+ return { ok: false, error: '--concurrency 只接受 forbid、replace 或 allow' };
496
+ }
497
+ update.concurrency = concurrency.value;
498
+ }
499
+ const missedPolicy = optionalFlagString(flags, 'missed-policy');
500
+ if ('error' in missedPolicy)
501
+ return { ok: false, error: missedPolicy.error };
502
+ if (missedPolicy.value !== undefined) {
503
+ const normalized = missedPolicy.value.replace('-', '_');
504
+ if (normalized !== 'skip' && normalized !== 'run_once' && normalized !== 'run_all') {
505
+ return { ok: false, error: '--missed-policy 只接受 skip、run-once 或 run-all' };
506
+ }
507
+ update.missedPolicy = normalized;
508
+ }
509
+ if (Object.keys(update).length === 0)
510
+ return { ok: false, error: '没有可更新的 trigger 参数' };
511
+ return { ok: true, nameOrId, value: update };
512
+ }
513
+ function optionalFlagString(flags, name) {
514
+ if (!flags.has(name))
515
+ return {};
516
+ const raw = flags.get(name);
517
+ if (raw === true)
518
+ return { error: `--${name} 不能为空` };
519
+ return { value: raw };
520
+ }
@@ -0,0 +1,148 @@
1
+ import { normalizeTriggerDefinition, parseDurationMs, TRIGGER_SCHEMA_VERSION } from './validation.js';
2
+ const PATCH_KEYS = new Set([
3
+ 'name',
4
+ 'scheduleType',
5
+ 'scheduleValue',
6
+ 'timezone',
7
+ 'prompt',
8
+ 'model',
9
+ 'effort',
10
+ 'permissionMode',
11
+ 'maxRuns',
12
+ 'maxDuration',
13
+ 'concurrency',
14
+ 'missedPolicy',
15
+ ]);
16
+ export function applyTriggerPatch(definition, input) {
17
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
18
+ throw new Error('trigger patch must be an object');
19
+ }
20
+ const patch = input;
21
+ const keys = Object.keys(patch);
22
+ if (keys.length === 0)
23
+ throw new Error('trigger patch must contain at least one field');
24
+ for (const key of keys) {
25
+ if (!PATCH_KEYS.has(key))
26
+ throw new Error(`trigger update does not support field: ${key}`);
27
+ }
28
+ const updated = JSON.parse(JSON.stringify(definition));
29
+ if (patch.name !== undefined)
30
+ updated.name = String(patch.name);
31
+ applySourcePatch(updated, patch);
32
+ if (patch.prompt !== undefined) {
33
+ if (updated.execution.type === 'script')
34
+ updated.feedback.onReply = { template: String(patch.prompt) };
35
+ else
36
+ updated.execution.prompt = String(patch.prompt);
37
+ }
38
+ if (patch.model !== undefined)
39
+ setOptional(updated.execution, 'model', patch.model);
40
+ if (patch.effort !== undefined)
41
+ setOptional(updated.execution, 'effort', patch.effort);
42
+ if (patch.permissionMode !== undefined)
43
+ setOptional(updated.execution, 'permissionMode', patch.permissionMode);
44
+ if (patch.concurrency !== undefined)
45
+ updated.reliability.concurrency = patch.concurrency;
46
+ if (patch.missedPolicy !== undefined)
47
+ updated.reliability.missedPolicy = patch.missedPolicy;
48
+ if (patch.maxRuns !== undefined || patch.maxDuration !== undefined) {
49
+ const limits = updated.limits ? { ...updated.limits } : {};
50
+ if (patch.maxRuns !== undefined) {
51
+ if (patch.maxRuns === null)
52
+ delete limits.maxRuns;
53
+ else
54
+ limits.maxRuns = patch.maxRuns;
55
+ }
56
+ if (patch.maxDuration !== undefined) {
57
+ if (patch.maxDuration === null || patch.maxDuration === '')
58
+ delete limits.maxDuration;
59
+ else
60
+ limits.maxDuration = patch.maxDuration;
61
+ }
62
+ updated.limits = Object.keys(limits).length > 0 ? limits : undefined;
63
+ }
64
+ return normalizeTriggerDefinition({
65
+ ...updated,
66
+ id: definition.id,
67
+ agentAid: definition.agentAid,
68
+ createdAt: definition.createdAt,
69
+ updatedAt: definition.updatedAt,
70
+ ...(definition.origin ? { origin: definition.origin } : {}),
71
+ });
72
+ }
73
+ /**
74
+ * Replaces the editable Trigger V3.1 definition while keeping lifecycle and
75
+ * ownership fields under server control. This is the ECWeb structured-edit
76
+ * path; the compact CLI patch path above remains intentionally limited.
77
+ */
78
+ export function replaceEditableTriggerDefinition(existing, input) {
79
+ if (!input || typeof input !== 'object' || Array.isArray(input)) {
80
+ throw new Error('trigger definition update must be an object');
81
+ }
82
+ const editable = input;
83
+ return normalizeTriggerDefinition({
84
+ ...editable,
85
+ $schema_version: TRIGGER_SCHEMA_VERSION,
86
+ id: existing.id,
87
+ agentAid: existing.agentAid,
88
+ enabled: existing.enabled,
89
+ createdAt: existing.createdAt,
90
+ updatedAt: existing.updatedAt,
91
+ origin: existing.origin,
92
+ });
93
+ }
94
+ function applySourcePatch(updated, patch) {
95
+ const previous = updated.source;
96
+ if (patch.scheduleType !== undefined || patch.scheduleValue !== undefined) {
97
+ const current = scheduleView(previous);
98
+ const type = patch.scheduleType ?? current.type;
99
+ const value = patch.scheduleValue ?? current.value;
100
+ const next = sourceFromSchedule(type, value);
101
+ if (next.type === 'cron' && previous.type === 'cron' && patch.timezone === undefined) {
102
+ next.timezone = previous.timezone;
103
+ }
104
+ if (next.type === 'event' && previous.type === 'event')
105
+ next.filter = previous.filter;
106
+ updated.source = next;
107
+ }
108
+ if (patch.timezone !== undefined) {
109
+ if (updated.source.type !== 'cron')
110
+ throw new Error('timezone is only valid for a cron trigger');
111
+ if (patch.timezone === null || patch.timezone === '')
112
+ delete updated.source.timezone;
113
+ else
114
+ updated.source.timezone = String(patch.timezone);
115
+ }
116
+ }
117
+ function sourceFromSchedule(type, value) {
118
+ switch (type) {
119
+ case 'once': return { type: 'once' };
120
+ case 'delay': return { type: 'delay', afterMs: durationMs(value) };
121
+ case 'at': return { type: 'at', at: value };
122
+ case 'cron': return { type: 'cron', expression: value };
123
+ case 'interval': return { type: 'interval', everyMs: durationMs(value) };
124
+ case 'event': return { type: 'event', eventPattern: value };
125
+ }
126
+ }
127
+ function scheduleView(source) {
128
+ switch (source.type) {
129
+ case 'once': return { type: 'once', value: '' };
130
+ case 'delay': return { type: 'delay', value: String(source.afterMs) };
131
+ case 'at': return { type: 'at', value: source.at };
132
+ case 'cron': return { type: 'cron', value: source.expression };
133
+ case 'interval': return { type: 'interval', value: String(source.everyMs) };
134
+ case 'event': return { type: 'event', value: source.eventPattern };
135
+ }
136
+ }
137
+ function durationMs(value) {
138
+ const numeric = Number(value);
139
+ if (Number.isFinite(numeric) && numeric > 0)
140
+ return numeric;
141
+ return parseDurationMs(value);
142
+ }
143
+ function setOptional(object, key, value) {
144
+ if (value === null || value === '')
145
+ delete object[key];
146
+ else
147
+ object[key] = value;
148
+ }