evolcore 0.0.4 → 0.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (332) hide show
  1. package/CHANGELOG.md +65 -3
  2. package/README.md +4 -12
  3. package/dist/agents/baseagent.js +39 -0
  4. package/dist/agents/claude-runner.js +10 -2
  5. package/dist/agents/codex-runner.js +37 -8
  6. package/dist/agents/ecagent-runner.js +1159 -0
  7. package/dist/agents/gemini-runner.js +4 -1
  8. package/dist/agents/runner-types.js +20 -4
  9. package/dist/aun/msg/history.js +32 -9
  10. package/dist/aun/outbox.js +4 -9
  11. package/dist/channels/aun.js +32 -6
  12. package/dist/channels/daemon.js +7 -4
  13. package/dist/channels/dingtalk.js +3 -5
  14. package/dist/channels/feishu.js +16 -108
  15. package/dist/channels/wechat.js +4 -4
  16. package/dist/channels/wecom.js +4 -6
  17. package/dist/cli/agent-command.js +1 -1
  18. package/dist/cli/agent.js +42 -11
  19. package/dist/cli/aun-commands.js +50 -3
  20. package/dist/cli/daemon-commands.js +127 -21
  21. package/dist/cli/data-command.js +132 -0
  22. package/dist/cli/index.js +11 -3
  23. package/dist/cli/init.js +116 -37
  24. package/dist/cli/net-check.js +9 -20
  25. package/dist/cli/restart-monitor.js +11 -2
  26. package/dist/cli/trigger-command.js +1 -1
  27. package/dist/cli/watch-msg.js +12 -25
  28. package/dist/config/access-policy-domain.js +0 -20
  29. package/dist/config/access-policy.js +5 -29
  30. package/dist/config/builtin-roles.js +3 -1
  31. package/dist/config/config-field-policy.js +1 -1
  32. package/dist/config/gateway-config.js +21 -4
  33. package/dist/config/resolved-config-op.js +10 -0
  34. package/dist/core/auth/operation-catalog.js +1 -1
  35. package/dist/core/command/command-handler.js +2 -2
  36. package/dist/core/command/connect-menu.js +20 -42
  37. package/dist/core/command/menu-handler.js +11 -9
  38. package/dist/core/command/role-menu.js +27 -0
  39. package/dist/core/command/slash-handler.js +18 -14
  40. package/dist/core/data-migration.js +1434 -0
  41. package/dist/core/handoff/store.js +14 -4
  42. package/dist/core/inference/text-inference.js +42 -1
  43. package/dist/core/message/inbound-admission.js +23 -13
  44. package/dist/core/message/pending-hints.js +1 -1
  45. package/dist/core/message/response-engine.js +406 -167
  46. package/dist/core/message/retry-scheduler.js +71 -0
  47. package/dist/core/model/model-catalog.js +7 -2
  48. package/dist/core/model/model-fallback.js +81 -0
  49. package/dist/core/protected-paths.js +41 -8
  50. package/dist/core/session/adapters/ecagent-session-file-adapter.js +112 -0
  51. package/dist/core/session/session-fs-store.js +300 -61
  52. package/dist/core/session/session-manager.js +225 -21
  53. package/dist/core/session/session-title.js +1 -0
  54. package/dist/core/session/session-turn-coordinator.js +8 -1
  55. package/dist/core/system-channels.js +1 -0
  56. package/dist/index.js +10 -9
  57. package/dist/paths.js +35 -27
  58. package/dist/trigger/feedback.js +16 -0
  59. package/dist/trigger/legacy-session-history.js +19 -0
  60. package/dist/trigger/manager.js +12 -1
  61. package/dist/trigger/parser.js +4 -2
  62. package/dist/trigger/scheduler.js +185 -15
  63. package/dist/trigger/session-lock.js +62 -0
  64. package/dist/trigger/state.js +64 -0
  65. package/dist/trigger/validation.js +32 -2
  66. package/dist/utils/aid-bind.js +1 -1
  67. package/dist/utils/cross-platform.js +8 -2
  68. package/dist/utils/error-utils.js +9 -0
  69. package/dist/utils/process-introspect.js +19 -3
  70. package/dist/utils/welcome.js +2 -3
  71. package/ecagent/LICENSE +21 -0
  72. package/ecagent/NOTICE +11 -0
  73. package/ecagent/README.md +61 -0
  74. package/ecagent/dist/agent-loop.d.ts +24 -0
  75. package/ecagent/dist/agent-loop.d.ts.map +1 -0
  76. package/ecagent/dist/agent-loop.js +547 -0
  77. package/ecagent/dist/agent-loop.js.map +1 -0
  78. package/ecagent/dist/agent.d.ts +127 -0
  79. package/ecagent/dist/agent.d.ts.map +1 -0
  80. package/ecagent/dist/agent.js +386 -0
  81. package/ecagent/dist/agent.js.map +1 -0
  82. package/ecagent/dist/harness/agent-harness.d.ts +95 -0
  83. package/ecagent/dist/harness/agent-harness.d.ts.map +1 -0
  84. package/ecagent/dist/harness/agent-harness.js +926 -0
  85. package/ecagent/dist/harness/agent-harness.js.map +1 -0
  86. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +51 -0
  87. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
  88. package/ecagent/dist/harness/compaction/branch-summarization.js +174 -0
  89. package/ecagent/dist/harness/compaction/branch-summarization.js.map +1 -0
  90. package/ecagent/dist/harness/compaction/compaction.d.ts +95 -0
  91. package/ecagent/dist/harness/compaction/compaction.d.ts.map +1 -0
  92. package/ecagent/dist/harness/compaction/compaction.js +528 -0
  93. package/ecagent/dist/harness/compaction/compaction.js.map +1 -0
  94. package/ecagent/dist/harness/compaction/utils.d.ts +25 -0
  95. package/ecagent/dist/harness/compaction/utils.d.ts.map +1 -0
  96. package/ecagent/dist/harness/compaction/utils.js +131 -0
  97. package/ecagent/dist/harness/compaction/utils.js.map +1 -0
  98. package/ecagent/dist/harness/env/nodejs.d.ts +51 -0
  99. package/ecagent/dist/harness/env/nodejs.d.ts.map +1 -0
  100. package/ecagent/dist/harness/env/nodejs.js +513 -0
  101. package/ecagent/dist/harness/env/nodejs.js.map +1 -0
  102. package/ecagent/dist/harness/messages.d.ts +51 -0
  103. package/ecagent/dist/harness/messages.d.ts.map +1 -0
  104. package/ecagent/dist/harness/messages.js +102 -0
  105. package/ecagent/dist/harness/messages.js.map +1 -0
  106. package/ecagent/dist/harness/prompt-templates.d.ts +48 -0
  107. package/ecagent/dist/harness/prompt-templates.d.ts.map +1 -0
  108. package/ecagent/dist/harness/prompt-templates.js +230 -0
  109. package/ecagent/dist/harness/prompt-templates.js.map +1 -0
  110. package/ecagent/dist/harness/session/jsonl-repo.d.ts +26 -0
  111. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +1 -0
  112. package/ecagent/dist/harness/session/jsonl-repo.js +100 -0
  113. package/ecagent/dist/harness/session/jsonl-repo.js.map +1 -0
  114. package/ecagent/dist/harness/session/jsonl-storage.d.ts +34 -0
  115. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +1 -0
  116. package/ecagent/dist/harness/session/jsonl-storage.js +234 -0
  117. package/ecagent/dist/harness/session/jsonl-storage.js.map +1 -0
  118. package/ecagent/dist/harness/session/memory-repo.d.ts +18 -0
  119. package/ecagent/dist/harness/session/memory-repo.d.ts.map +1 -0
  120. package/ecagent/dist/harness/session/memory-repo.js +44 -0
  121. package/ecagent/dist/harness/session/memory-repo.js.map +1 -0
  122. package/ecagent/dist/harness/session/memory-storage.d.ts +25 -0
  123. package/ecagent/dist/harness/session/memory-storage.d.ts.map +1 -0
  124. package/ecagent/dist/harness/session/memory-storage.js +111 -0
  125. package/ecagent/dist/harness/session/memory-storage.js.map +1 -0
  126. package/ecagent/dist/harness/session/repo-utils.d.ts +11 -0
  127. package/ecagent/dist/harness/session/repo-utils.d.ts.map +1 -0
  128. package/ecagent/dist/harness/session/repo-utils.js +39 -0
  129. package/ecagent/dist/harness/session/repo-utils.js.map +1 -0
  130. package/ecagent/dist/harness/session/session.d.ts +47 -0
  131. package/ecagent/dist/harness/session/session.d.ts.map +1 -0
  132. package/ecagent/dist/harness/session/session.js +245 -0
  133. package/ecagent/dist/harness/session/session.js.map +1 -0
  134. package/ecagent/dist/harness/session/uuid.d.ts +2 -0
  135. package/ecagent/dist/harness/session/uuid.d.ts.map +1 -0
  136. package/ecagent/dist/harness/session/uuid.js +50 -0
  137. package/ecagent/dist/harness/session/uuid.js.map +1 -0
  138. package/ecagent/dist/harness/skills.d.ts +44 -0
  139. package/ecagent/dist/harness/skills.d.ts.map +1 -0
  140. package/ecagent/dist/harness/skills.js +311 -0
  141. package/ecagent/dist/harness/skills.js.map +1 -0
  142. package/ecagent/dist/harness/system-prompt.d.ts +3 -0
  143. package/ecagent/dist/harness/system-prompt.d.ts.map +1 -0
  144. package/ecagent/dist/harness/system-prompt.js +30 -0
  145. package/ecagent/dist/harness/system-prompt.js.map +1 -0
  146. package/ecagent/dist/harness/types.d.ts +619 -0
  147. package/ecagent/dist/harness/types.d.ts.map +1 -0
  148. package/ecagent/dist/harness/types.js +81 -0
  149. package/ecagent/dist/harness/types.js.map +1 -0
  150. package/ecagent/dist/harness/utils/shell-output.d.ts +14 -0
  151. package/ecagent/dist/harness/utils/shell-output.d.ts.map +1 -0
  152. package/ecagent/dist/harness/utils/shell-output.js +126 -0
  153. package/ecagent/dist/harness/utils/shell-output.js.map +1 -0
  154. package/ecagent/dist/harness/utils/truncate.d.ts +70 -0
  155. package/ecagent/dist/harness/utils/truncate.d.ts.map +1 -0
  156. package/ecagent/dist/harness/utils/truncate.js +290 -0
  157. package/ecagent/dist/harness/utils/truncate.js.map +1 -0
  158. package/ecagent/dist/index.d.ts +22 -0
  159. package/ecagent/dist/index.d.ts.map +1 -0
  160. package/ecagent/dist/index.js +27 -0
  161. package/ecagent/dist/index.js.map +1 -0
  162. package/ecagent/dist/node.d.ts +3 -0
  163. package/ecagent/dist/node.d.ts.map +1 -0
  164. package/ecagent/dist/node.js +3 -0
  165. package/ecagent/dist/node.js.map +1 -0
  166. package/ecagent/dist/proxy.d.ts +69 -0
  167. package/ecagent/dist/proxy.d.ts.map +1 -0
  168. package/ecagent/dist/proxy.js +278 -0
  169. package/ecagent/dist/proxy.js.map +1 -0
  170. package/ecagent/dist/runtime/api/lazy.d.ts +15 -0
  171. package/ecagent/dist/runtime/api/lazy.d.ts.map +1 -0
  172. package/ecagent/dist/runtime/api/lazy.js +59 -0
  173. package/ecagent/dist/runtime/api/lazy.js.map +1 -0
  174. package/ecagent/dist/runtime/api/pi-messages.d.ts +97 -0
  175. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +1 -0
  176. package/ecagent/dist/runtime/api/pi-messages.js +307 -0
  177. package/ecagent/dist/runtime/api/pi-messages.js.map +1 -0
  178. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts +3 -0
  179. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +1 -0
  180. package/ecagent/dist/runtime/api/pi-messages.lazy.js +3 -0
  181. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +1 -0
  182. package/ecagent/dist/runtime/auth/context.d.ts +7 -0
  183. package/ecagent/dist/runtime/auth/context.d.ts.map +1 -0
  184. package/ecagent/dist/runtime/auth/context.js +42 -0
  185. package/ecagent/dist/runtime/auth/context.js.map +1 -0
  186. package/ecagent/dist/runtime/auth/credential-store.d.ts +16 -0
  187. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +1 -0
  188. package/ecagent/dist/runtime/auth/credential-store.js +39 -0
  189. package/ecagent/dist/runtime/auth/credential-store.js.map +1 -0
  190. package/ecagent/dist/runtime/auth/helpers.d.ts +20 -0
  191. package/ecagent/dist/runtime/auth/helpers.d.ts.map +1 -0
  192. package/ecagent/dist/runtime/auth/helpers.js +46 -0
  193. package/ecagent/dist/runtime/auth/helpers.js.map +1 -0
  194. package/ecagent/dist/runtime/auth/resolve.d.ts +26 -0
  195. package/ecagent/dist/runtime/auth/resolve.d.ts.map +1 -0
  196. package/ecagent/dist/runtime/auth/resolve.js +100 -0
  197. package/ecagent/dist/runtime/auth/resolve.js.map +1 -0
  198. package/ecagent/dist/runtime/auth/types.d.ts +180 -0
  199. package/ecagent/dist/runtime/auth/types.d.ts.map +1 -0
  200. package/ecagent/dist/runtime/auth/types.js +2 -0
  201. package/ecagent/dist/runtime/auth/types.js.map +1 -0
  202. package/ecagent/dist/runtime/compat.d.ts +28 -0
  203. package/ecagent/dist/runtime/compat.d.ts.map +1 -0
  204. package/ecagent/dist/runtime/compat.js +86 -0
  205. package/ecagent/dist/runtime/compat.js.map +1 -0
  206. package/ecagent/dist/runtime/index.d.ts +20 -0
  207. package/ecagent/dist/runtime/index.d.ts.map +1 -0
  208. package/ecagent/dist/runtime/index.js +17 -0
  209. package/ecagent/dist/runtime/index.js.map +1 -0
  210. package/ecagent/dist/runtime/models.d.ts +142 -0
  211. package/ecagent/dist/runtime/models.d.ts.map +1 -0
  212. package/ecagent/dist/runtime/models.js +245 -0
  213. package/ecagent/dist/runtime/models.js.map +1 -0
  214. package/ecagent/dist/runtime/oauth.d.ts +2 -0
  215. package/ecagent/dist/runtime/oauth.d.ts.map +1 -0
  216. package/ecagent/dist/runtime/oauth.js +2 -0
  217. package/ecagent/dist/runtime/oauth.js.map +1 -0
  218. package/ecagent/dist/runtime/providers/faux.d.ts +97 -0
  219. package/ecagent/dist/runtime/providers/faux.d.ts.map +1 -0
  220. package/ecagent/dist/runtime/providers/faux.js +395 -0
  221. package/ecagent/dist/runtime/providers/faux.js.map +1 -0
  222. package/ecagent/dist/runtime/types.d.ts +607 -0
  223. package/ecagent/dist/runtime/types.d.ts.map +1 -0
  224. package/ecagent/dist/runtime/types.js +2 -0
  225. package/ecagent/dist/runtime/types.js.map +1 -0
  226. package/ecagent/dist/runtime/utils/diagnostics.d.ts +19 -0
  227. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +1 -0
  228. package/ecagent/dist/runtime/utils/diagnostics.js +25 -0
  229. package/ecagent/dist/runtime/utils/diagnostics.js.map +1 -0
  230. package/ecagent/dist/runtime/utils/event-stream.d.ts +21 -0
  231. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +1 -0
  232. package/ecagent/dist/runtime/utils/event-stream.js +77 -0
  233. package/ecagent/dist/runtime/utils/event-stream.js.map +1 -0
  234. package/ecagent/dist/runtime/utils/headers.d.ts +4 -0
  235. package/ecagent/dist/runtime/utils/headers.d.ts.map +1 -0
  236. package/ecagent/dist/runtime/utils/headers.js +18 -0
  237. package/ecagent/dist/runtime/utils/headers.js.map +1 -0
  238. package/ecagent/dist/runtime/utils/json-parse.d.ts +16 -0
  239. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +1 -0
  240. package/ecagent/dist/runtime/utils/json-parse.js +113 -0
  241. package/ecagent/dist/runtime/utils/json-parse.js.map +1 -0
  242. package/ecagent/dist/runtime/utils/oauth/types.d.ts +64 -0
  243. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +1 -0
  244. package/ecagent/dist/runtime/utils/oauth/types.js +2 -0
  245. package/ecagent/dist/runtime/utils/oauth/types.js.map +1 -0
  246. package/ecagent/dist/runtime/utils/overflow.d.ts +58 -0
  247. package/ecagent/dist/runtime/utils/overflow.d.ts.map +1 -0
  248. package/ecagent/dist/runtime/utils/overflow.js +158 -0
  249. package/ecagent/dist/runtime/utils/overflow.js.map +1 -0
  250. package/ecagent/dist/runtime/utils/provider-env.d.ts +7 -0
  251. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +1 -0
  252. package/ecagent/dist/runtime/utils/provider-env.js +44 -0
  253. package/ecagent/dist/runtime/utils/provider-env.js.map +1 -0
  254. package/ecagent/dist/runtime/utils/retry.d.ts +12 -0
  255. package/ecagent/dist/runtime/utils/retry.d.ts.map +1 -0
  256. package/ecagent/dist/runtime/utils/retry.js +90 -0
  257. package/ecagent/dist/runtime/utils/retry.js.map +1 -0
  258. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts +17 -0
  259. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +1 -0
  260. package/ecagent/dist/runtime/utils/typebox-helpers.js +21 -0
  261. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +1 -0
  262. package/ecagent/dist/runtime/utils/validation.d.ts +18 -0
  263. package/ecagent/dist/runtime/utils/validation.d.ts.map +1 -0
  264. package/ecagent/dist/runtime/utils/validation.js +269 -0
  265. package/ecagent/dist/runtime/utils/validation.js.map +1 -0
  266. package/ecagent/dist/types.d.ts +401 -0
  267. package/ecagent/dist/types.d.ts.map +1 -0
  268. package/ecagent/dist/types.js +2 -0
  269. package/ecagent/dist/types.js.map +1 -0
  270. package/ecagent/package.json +93 -0
  271. package/ecagent/src/agent-loop.ts +792 -0
  272. package/ecagent/src/agent.ts +575 -0
  273. package/ecagent/src/harness/agent-harness.ts +1029 -0
  274. package/ecagent/src/harness/compaction/branch-summarization.ts +261 -0
  275. package/ecagent/src/harness/compaction/compaction.ts +753 -0
  276. package/ecagent/src/harness/compaction/utils.ts +144 -0
  277. package/ecagent/src/harness/env/nodejs.ts +569 -0
  278. package/ecagent/src/harness/messages.ts +164 -0
  279. package/ecagent/src/harness/prompt-templates.ts +267 -0
  280. package/ecagent/src/harness/session/jsonl-repo.ts +179 -0
  281. package/ecagent/src/harness/session/jsonl-storage.ts +314 -0
  282. package/ecagent/src/harness/session/memory-repo.ts +50 -0
  283. package/ecagent/src/harness/session/memory-storage.ts +133 -0
  284. package/ecagent/src/harness/session/repo-utils.ts +51 -0
  285. package/ecagent/src/harness/session/session.ts +338 -0
  286. package/ecagent/src/harness/session/uuid.ts +54 -0
  287. package/ecagent/src/harness/skills.ts +375 -0
  288. package/ecagent/src/harness/system-prompt.ts +34 -0
  289. package/ecagent/src/harness/types.ts +838 -0
  290. package/ecagent/src/harness/utils/shell-output.ts +135 -0
  291. package/ecagent/src/harness/utils/truncate.ts +344 -0
  292. package/ecagent/src/index.ts +46 -0
  293. package/ecagent/src/node.ts +2 -0
  294. package/ecagent/src/proxy.ts +367 -0
  295. package/ecagent/src/runtime/api/lazy.ts +70 -0
  296. package/ecagent/src/runtime/api/pi-messages.lazy.ts +4 -0
  297. package/ecagent/src/runtime/api/pi-messages.ts +436 -0
  298. package/ecagent/src/runtime/auth/context.ts +45 -0
  299. package/ecagent/src/runtime/auth/credential-store.ts +47 -0
  300. package/ecagent/src/runtime/auth/helpers.ts +46 -0
  301. package/ecagent/src/runtime/auth/resolve.ts +141 -0
  302. package/ecagent/src/runtime/auth/types.ts +182 -0
  303. package/ecagent/src/runtime/compat.ts +158 -0
  304. package/ecagent/src/runtime/index.ts +31 -0
  305. package/ecagent/src/runtime/models.ts +452 -0
  306. package/ecagent/src/runtime/oauth.ts +1 -0
  307. package/ecagent/src/runtime/providers/faux.ts +538 -0
  308. package/ecagent/src/runtime/types.ts +718 -0
  309. package/ecagent/src/runtime/utils/diagnostics.ts +45 -0
  310. package/ecagent/src/runtime/utils/event-stream.ts +88 -0
  311. package/ecagent/src/runtime/utils/headers.ts +18 -0
  312. package/ecagent/src/runtime/utils/json-parse.ts +124 -0
  313. package/ecagent/src/runtime/utils/oauth/types.ts +79 -0
  314. package/ecagent/src/runtime/utils/overflow.ts +165 -0
  315. package/ecagent/src/runtime/utils/provider-env.ts +52 -0
  316. package/ecagent/src/runtime/utils/retry.ts +101 -0
  317. package/ecagent/src/runtime/utils/typebox-helpers.ts +24 -0
  318. package/ecagent/src/runtime/utils/validation.ts +310 -0
  319. package/ecagent/src/types.ts +430 -0
  320. package/ecagent/tsconfig.build.json +32 -0
  321. package/kits/docs/evolcore/config.md +4 -4
  322. package/kits/docs/evolcore/contact.md +2 -1
  323. package/kits/schemas/_meta.json +7 -4
  324. package/kits/schemas/agent-config.schema.10.json +370 -0
  325. package/kits/schemas/agent-config.schema.9.json +2 -12
  326. package/kits/schemas/defaults.schema.4.json +93 -0
  327. package/kits/schemas/relation-config.schema.8.json +69 -0
  328. package/kits/templates/roles/admin.json +1 -1
  329. package/package.json +22 -6
  330. package/skills/eclink/SKILL.md +70 -0
  331. package/skills/eclink/agents/openai.yaml +4 -0
  332. package/assets/.env.template +0 -4
@@ -3,7 +3,8 @@ import fs from 'fs';
3
3
  import { CronExpressionParser } from 'cron-parser';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import { matchTriggerEvent, TriggerEventSource } from './event-source.js';
6
- import { definitionRevision, parseDurationMs, previewText, renderTemplate, resolveScriptPath, sha256, } from './validation.js';
6
+ import { definitionRevision, parseDurationMs, previewText, renderTemplate, resolveScriptPath, sha256, validateConcurrencyCombination, } from './validation.js';
7
+ import { TargetSessionLockRegistry } from './session-lock.js';
7
8
  import { enterTrigger, formatTriggerPath } from '../core/causation/context.js';
8
9
  import { createRootCausation } from '../core/causation/context.js';
9
10
  import { recordCausationSpan } from '../core/causation/audit.js';
@@ -21,6 +22,7 @@ export class TriggerRuntimeScheduler {
21
22
  daemonChannel;
22
23
  runtime;
23
24
  eventBus;
25
+ targetSessionLocks;
24
26
  timers = new Map();
25
27
  running = new Map();
26
28
  runStartLocks = new Map();
@@ -28,7 +30,7 @@ export class TriggerRuntimeScheduler {
28
30
  eventSource;
29
31
  initialized = false;
30
32
  stopping = false;
31
- constructor(manager, state, audit, scriptExecutor, feedback, daemonChannel, runtime, eventBus) {
33
+ constructor(manager, state, audit, scriptExecutor, feedback, daemonChannel, runtime, eventBus, targetSessionLocks = new TargetSessionLockRegistry()) {
32
34
  this.manager = manager;
33
35
  this.state = state;
34
36
  this.audit = audit;
@@ -37,6 +39,7 @@ export class TriggerRuntimeScheduler {
37
39
  this.daemonChannel = daemonChannel;
38
40
  this.runtime = runtime;
39
41
  this.eventBus = eventBus;
42
+ this.targetSessionLocks = targetSessionLocks;
40
43
  if (eventBus) {
41
44
  this.eventSource = new TriggerEventSource(eventBus, (triggerId, event) => {
42
45
  void this.fireEventTrigger(triggerId, event);
@@ -391,6 +394,31 @@ export class TriggerRuntimeScheduler {
391
394
  return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'stopped' };
392
395
  }
393
396
  const source = this.buildSourceInfo(definition.source, firedAt, payload.scheduledAt, payload.payload);
397
+ try {
398
+ validateConcurrencyCombination(definition.execution, definition.reliability);
399
+ }
400
+ catch (error) {
401
+ const message = error instanceof Error ? error.message : String(error);
402
+ const audit = this.buildAudit({
403
+ definition,
404
+ runId,
405
+ startedAt: firedAt,
406
+ status: 'failed',
407
+ reason: 'invalid_execution_configuration',
408
+ source,
409
+ script: null,
410
+ reply: null,
411
+ feedback: null,
412
+ effects: [],
413
+ error: { code: 'invalid_execution_configuration', message },
414
+ causation: payload.causation,
415
+ });
416
+ if (!payload.dryRun) {
417
+ this.audit.write(audit);
418
+ this.publishTriggerRunOutcome(definition, audit);
419
+ }
420
+ return { ok: false, runId, triggerId: definition.id, status: 'failed', reason: 'invalid_execution_configuration', audit, error: message };
421
+ }
394
422
  const executionAuthorization = this.runtime.authorizeExecution?.(definition);
395
423
  if (executionAuthorization && !executionAuthorization.allowed) {
396
424
  const reason = executionAuthorization.reason || 'execution_authorization_denied';
@@ -455,6 +483,7 @@ export class TriggerRuntimeScheduler {
455
483
  definitionRevision: definitionRevision(definition),
456
484
  source,
457
485
  resumeCount: 0,
486
+ attempts: [],
458
487
  events: [{ seq: 0, event: 'run.started', ts: startedAt }],
459
488
  };
460
489
  if (!payload.dryRun) {
@@ -597,6 +626,7 @@ export class TriggerRuntimeScheduler {
597
626
  anomalies: executionAnomalies,
598
627
  effects: feedbackResult.effects,
599
628
  error: feedbackResult.error,
629
+ conflictRunId: feedbackResult.conflictRunId,
600
630
  causation,
601
631
  });
602
632
  if (!payload.dryRun) {
@@ -680,7 +710,7 @@ export class TriggerRuntimeScheduler {
680
710
  let attempt = 1;
681
711
  let anomalies = [];
682
712
  try {
683
- let execution = await this.runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt);
713
+ let execution = await this.runRecordedExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt);
684
714
  anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(runId)));
685
715
  onAnomalies?.(anomalies);
686
716
  for (let retryCount = 0; shouldRetryExecutionResult(execution) && retryCount < maxAttempts && !signal.aborted; retryCount++) {
@@ -688,7 +718,7 @@ export class TriggerRuntimeScheduler {
688
718
  if (!backoffCompleted || signal.aborted)
689
719
  break;
690
720
  attempt += 1;
691
- execution = await this.runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt);
721
+ execution = await this.runRecordedExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt, retryContextFromExecution(attempt - 1, `${runId}:attempt:${attempt - 1}`, execution));
692
722
  anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(runId)));
693
723
  onAnomalies?.(anomalies);
694
724
  }
@@ -703,7 +733,34 @@ export class TriggerRuntimeScheduler {
703
733
  throw new TriggerExecutionFailure(error, anomalies);
704
734
  }
705
735
  }
706
- async runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt = 1) {
736
+ async runRecordedExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt, previousAttempt) {
737
+ const attemptId = `${runId}:attempt:${attempt}`;
738
+ if (!dryRun) {
739
+ this.state.startExecutionAttempt?.(definition.id, runId, {
740
+ attempt,
741
+ attemptId,
742
+ startedAt: Date.now(),
743
+ });
744
+ }
745
+ try {
746
+ const execution = await this.runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt, previousAttempt);
747
+ if (!dryRun) {
748
+ const summary = attemptSummaryFromExecution(attempt, attemptId, execution);
749
+ this.state.completeExecutionAttempt?.(definition.id, runId, attemptId, summary);
750
+ }
751
+ return execution;
752
+ }
753
+ catch (error) {
754
+ if (!dryRun) {
755
+ this.state.completeExecutionAttempt?.(definition.id, runId, attemptId, {
756
+ status: 'failed',
757
+ error: errorSummary(error, 'execution_exception'),
758
+ });
759
+ }
760
+ throw error;
761
+ }
762
+ }
763
+ async runExecutionAttempt(definition, runId, firedAt, sourcePayload, signal, dryRun, causation, attempt = 1, previousAttempt) {
707
764
  if (signal.aborted) {
708
765
  return {
709
766
  script: null,
@@ -735,12 +792,15 @@ export class TriggerRuntimeScheduler {
735
792
  reply,
736
793
  };
737
794
  }
738
- const prompt = renderTemplate(definition.execution.prompt ?? '', {
795
+ const originalPrompt = renderTemplate(definition.execution.prompt ?? '', {
739
796
  trigger: definition,
740
797
  timestamp: firedAt,
741
798
  event: sourcePayload,
742
799
  source: { type: definition.source.type, payload: sourcePayload },
743
800
  });
801
+ const prompt = previousAttempt
802
+ ? renderRetryContinuationPrompt(originalPrompt, previousAttempt)
803
+ : originalPrompt;
744
804
  const processing = {
745
805
  mode: definition.execution.type,
746
806
  renderedTextHash: sha256(prompt),
@@ -756,6 +816,20 @@ export class TriggerRuntimeScheduler {
756
816
  dryRun,
757
817
  causation,
758
818
  signal,
819
+ onSessionResolved: async (session) => {
820
+ this.state.startExecutionAttempt?.(definition.id, runId, {
821
+ attempt,
822
+ attemptId: `${runId}:attempt:${attempt}`,
823
+ startedAt: Date.now(),
824
+ executionSessionId: session.id,
825
+ });
826
+ // runExecution is also used by focused unit tests without a live
827
+ // scheduler runtime. Production calls always have a RunningRun.
828
+ if (!this.running.get(definition.id)?.has(runId)) {
829
+ return { kind: 'acquired', release: () => undefined };
830
+ }
831
+ return await this.acquireTargetSessionLock(definition, runId, session.id);
832
+ },
759
833
  });
760
834
  return {
761
835
  script: null,
@@ -792,19 +866,13 @@ export class TriggerRuntimeScheduler {
792
866
  const attemptId = `${runId}:attempt:${attempt}`;
793
867
  const executionDeadlineAt = this.daemonExecutionDeadlineAt(Date.now());
794
868
  const executionSession = await this.daemonChannel.getOrCreateConversationSession(definition, this.runtime.projectPath, runtimeBaseagent, runId, attemptId);
795
- const activeRun = this.state.setPhase?.(definition.id, runId, 'running', {
869
+ const activeRun = this.state.startExecutionAttempt?.(definition.id, runId, {
796
870
  attempt,
797
871
  attemptId,
872
+ startedAt: Date.now(),
798
873
  executionSessionId: executionSession.id,
799
874
  executionDeadlineAt,
800
875
  });
801
- this.state.appendEvent?.(definition.id, runId, {
802
- event: 'execution.attempt.started',
803
- ts: Date.now(),
804
- attempt,
805
- attemptId,
806
- executionSessionId: executionSession.id,
807
- });
808
876
  const daemonReply = await this.daemonChannel.converse(prompt, {
809
877
  trigger: definition,
810
878
  runId,
@@ -869,6 +937,26 @@ export class TriggerRuntimeScheduler {
869
937
  signal,
870
938
  });
871
939
  }
940
+ async acquireTargetSessionLock(definition, runId, sessionId) {
941
+ const concurrency = definition.reliability.concurrency;
942
+ if (concurrency === 'allow') {
943
+ throw new Error('target_session concurrency=allow must be rejected before session dispatch');
944
+ }
945
+ const runtime = this.running.get(definition.id)?.get(runId);
946
+ if (!runtime) {
947
+ throw new Error(`target session lock requested for non-running Trigger run: ${definition.id}/${runId}`);
948
+ }
949
+ return await this.targetSessionLocks.acquire(sessionId, concurrency, {
950
+ triggerId: definition.id,
951
+ runId,
952
+ replace: async () => {
953
+ await this.terminateRun(runId, runtime, {
954
+ reason: 'replaced',
955
+ message: `target session run replaced: trigger=${definition.id} run=${runId}`,
956
+ });
957
+ },
958
+ });
959
+ }
872
960
  async checkConcurrency(definition, nextRunId, source) {
873
961
  const runs = this.running.get(definition.id);
874
962
  if (!runs || runs.size === 0 || definition.reliability.concurrency === 'allow')
@@ -1118,7 +1206,16 @@ export class TriggerRuntimeScheduler {
1118
1206
  async runRecoveredExecution(definition, runtime, session) {
1119
1207
  const { run, source, controller, causation } = runtime;
1120
1208
  let attempt = run.attempt;
1209
+ const attemptId = run.attemptId ?? `${run.runId}:attempt:${attempt}`;
1210
+ this.state.startExecutionAttempt(definition.id, run.runId, {
1211
+ attempt,
1212
+ attemptId,
1213
+ startedAt: run.startedAt,
1214
+ executionSessionId: session.id,
1215
+ executionDeadlineAt: run.executionDeadlineAt,
1216
+ });
1121
1217
  let execution = await this.runRecoveredExecutionAttempt(definition, runtime, session);
1218
+ this.state.completeExecutionAttempt(definition.id, run.runId, attemptId, attemptSummaryFromExecution(attempt, attemptId, execution));
1122
1219
  let anomalies = accumulateTriggerExecutionAnomalies([], mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(run.runId)));
1123
1220
  runtime.anomalies = anomalies;
1124
1221
  const maxAttempt = 1 + (definition.execution.onError === 'retry'
@@ -1129,7 +1226,7 @@ export class TriggerRuntimeScheduler {
1129
1226
  if (!backoffCompleted || controller.signal.aborted)
1130
1227
  break;
1131
1228
  attempt += 1;
1132
- execution = await this.runExecutionAttempt(definition, run.runId, source.firedAt, source.payload, controller.signal, false, causation, attempt);
1229
+ execution = await this.runRecordedExecutionAttempt(definition, run.runId, source.firedAt, source.payload, controller.signal, false, causation, attempt, retryContextFromExecution(attempt - 1, `${run.runId}:attempt:${attempt - 1}`, execution));
1133
1230
  anomalies = accumulateTriggerExecutionAnomalies(anomalies, mergeTriggerExecutionAnomalies(execution.reply?.anomalies, drainTriggerExecutionAnomalies(run.runId)));
1134
1231
  runtime.anomalies = anomalies;
1135
1232
  }
@@ -1356,6 +1453,12 @@ export class TriggerRuntimeScheduler {
1356
1453
  }
1357
1454
  buildAudit(input) {
1358
1455
  const reply = summarizeReply(input.reply, { omitText: input.script?.result !== undefined });
1456
+ const activeRun = this.state.list?.(input.definition.id).find(run => run.runId === input.runId);
1457
+ const attempts = activeRun?.attempts?.map(attempt => ({
1458
+ ...attempt,
1459
+ ...(attempt.error ? { error: { ...attempt.error } } : {}),
1460
+ }));
1461
+ const finalAttempt = attempts?.at(-1);
1359
1462
  return {
1360
1463
  runId: input.runId,
1361
1464
  triggerId: input.definition.id,
@@ -1365,6 +1468,9 @@ export class TriggerRuntimeScheduler {
1365
1468
  status: input.status,
1366
1469
  reason: input.reason,
1367
1470
  conflictRunId: input.conflictRunId,
1471
+ ...(activeRun?.executionSessionId ? { executionSessionId: activeRun.executionSessionId } : {}),
1472
+ ...(attempts?.length ? { attempts } : {}),
1473
+ ...(finalAttempt ? { finalAttempt: finalAttempt.attempt } : {}),
1368
1474
  definition: {
1369
1475
  schemaVersion: input.definition.$schema_version,
1370
1476
  revision: definitionRevision(input.definition),
@@ -1522,6 +1628,10 @@ export class TriggerRuntimeScheduler {
1522
1628
  this.removeRunning(triggerId, runId);
1523
1629
  return;
1524
1630
  }
1631
+ // terminateRun owns the terminal audit and must retain active attempt
1632
+ // snapshots until its completion barrier has settled.
1633
+ if (runtime?.termination)
1634
+ return;
1525
1635
  this.finishRun(triggerId, runId);
1526
1636
  }
1527
1637
  suspendedRuntimeResult(runtime, triggerId, runId) {
@@ -1787,6 +1897,65 @@ function errorReply(runId, durationMs, reason, text) {
1787
1897
  },
1788
1898
  };
1789
1899
  }
1900
+ function attemptSummaryFromExecution(attempt, attemptId, execution) {
1901
+ if (execution.feedbackResult) {
1902
+ const failed = execution.feedbackResult.status !== 'completed';
1903
+ return {
1904
+ status: failed ? 'failed' : 'completed',
1905
+ ...(execution.feedbackResult.error
1906
+ ? { error: execution.feedbackResult.error }
1907
+ : failed
1908
+ ? { error: {
1909
+ code: execution.feedbackResult.reason ?? 'target_session_failed',
1910
+ message: execution.feedbackResult.reason ?? 'target session execution did not start',
1911
+ } }
1912
+ : {}),
1913
+ };
1914
+ }
1915
+ const reply = execution.reply;
1916
+ if (!reply) {
1917
+ return { status: 'failed', error: { code: 'execution_missing_reply', message: 'execution completed without a reply' } };
1918
+ }
1919
+ switch (reply.outcome) {
1920
+ case 'success': return { status: 'completed' };
1921
+ case 'noop': return { status: 'noop' };
1922
+ case 'interrupted': return { status: 'interrupted', error: replyErrorSummary(reply, 'interrupted') };
1923
+ case 'timeout': return { status: 'timeout', error: replyErrorSummary(reply, 'timeout') };
1924
+ case 'error': return { status: 'failed', error: replyErrorSummary(reply, 'execution_error') };
1925
+ }
1926
+ }
1927
+ function retryContextFromExecution(attempt, attemptId, execution) {
1928
+ return {
1929
+ attempt,
1930
+ attemptId,
1931
+ ...attemptSummaryFromExecution(attempt, attemptId, execution),
1932
+ };
1933
+ }
1934
+ function replyErrorSummary(reply, fallbackCode) {
1935
+ return {
1936
+ code: reply.error?.reason || fallbackCode,
1937
+ message: previewText(reply.error?.text || `Trigger attempt ${fallbackCode}`, 600),
1938
+ };
1939
+ }
1940
+ function errorSummary(error, fallbackCode) {
1941
+ const cause = error instanceof TriggerExecutionFailure ? error.cause : error;
1942
+ return {
1943
+ code: fallbackCode,
1944
+ message: previewText(cause instanceof Error ? cause.message : String(cause), 600),
1945
+ };
1946
+ }
1947
+ function renderRetryContinuationPrompt(originalPrompt, previous) {
1948
+ const error = previous.error
1949
+ ? `${previous.error.code}: ${previous.error.message}`
1950
+ : previous.status;
1951
+ return [
1952
+ '这是同一个 Trigger run 的恢复执行,不是新任务。',
1953
+ `上一 attempt 为 ${previous.attempt}(${previous.attemptId}),结果:${error}。`,
1954
+ '请先检查当前会话已有上下文、工具结果和已完成步骤,只继续未完成的工作。',
1955
+ '原始任务:',
1956
+ originalPrompt,
1957
+ ].join('\n');
1958
+ }
1790
1959
  function summarizeScript(script) {
1791
1960
  if (!script)
1792
1961
  return null;
@@ -1833,6 +2002,7 @@ function summarizeReply(reply, opts = {}) {
1833
2002
  durationMs: reply.meta.durationMs,
1834
2003
  numTurns: reply.meta.numTurns,
1835
2004
  tokenUsage: reply.meta.tokenUsage,
2005
+ contextRecoveryAttempts: reply.meta.contextRecoveryAttempts,
1836
2006
  toolCallCount: reply.meta.toolCallCount,
1837
2007
  };
1838
2008
  }
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Process-wide ownership for target sessions. Schedulers are created per
3
+ * agent, so per-scheduler maps cannot protect a session shared by triggers.
4
+ */
5
+ export class TargetSessionLockRegistry {
6
+ owners = new Map();
7
+ mutations = new Map();
8
+ async acquire(sessionId, concurrency, owner) {
9
+ return await this.withMutation(sessionId, async () => {
10
+ const current = this.owners.get(sessionId);
11
+ if (!current)
12
+ return this.claim(sessionId, owner);
13
+ if (concurrency === 'forbid') {
14
+ return {
15
+ kind: 'conflict',
16
+ conflictTriggerId: current.triggerId,
17
+ conflictRunId: current.runId,
18
+ };
19
+ }
20
+ await current.replace();
21
+ const remaining = this.owners.get(sessionId);
22
+ if (remaining) {
23
+ return {
24
+ kind: 'conflict',
25
+ conflictTriggerId: remaining.triggerId,
26
+ conflictRunId: remaining.runId,
27
+ };
28
+ }
29
+ return this.claim(sessionId, owner);
30
+ });
31
+ }
32
+ claim(sessionId, owner) {
33
+ this.owners.set(sessionId, owner);
34
+ let released = false;
35
+ return {
36
+ kind: 'acquired',
37
+ release: () => {
38
+ if (released)
39
+ return;
40
+ released = true;
41
+ if (this.owners.get(sessionId) === owner)
42
+ this.owners.delete(sessionId);
43
+ },
44
+ };
45
+ }
46
+ async withMutation(sessionId, action) {
47
+ const previous = this.mutations.get(sessionId) ?? Promise.resolve();
48
+ let releaseCurrent;
49
+ const current = new Promise(resolve => { releaseCurrent = resolve; });
50
+ const tail = previous.then(() => current);
51
+ this.mutations.set(sessionId, tail);
52
+ await previous;
53
+ try {
54
+ return await action();
55
+ }
56
+ finally {
57
+ releaseCurrent();
58
+ if (this.mutations.get(sessionId) === tail)
59
+ this.mutations.delete(sessionId);
60
+ }
61
+ }
62
+ }
@@ -48,6 +48,70 @@ export class TriggerRunStateStore {
48
48
  this.write(triggerId, active);
49
49
  return run;
50
50
  }
51
+ startExecutionAttempt(triggerId, runId, input) {
52
+ const active = this.read(triggerId);
53
+ const run = active.runs[runId];
54
+ if (!run)
55
+ return undefined;
56
+ const attempts = run.attempts ?? (run.attempts = []);
57
+ const existing = attempts.find(attempt => attempt.attemptId === input.attemptId);
58
+ if (existing) {
59
+ if (input.executionSessionId)
60
+ existing.executionSessionId = input.executionSessionId;
61
+ }
62
+ else {
63
+ attempts.push({
64
+ attempt: input.attempt,
65
+ attemptId: input.attemptId,
66
+ startedAt: input.startedAt,
67
+ status: 'running',
68
+ ...(input.executionSessionId ? { executionSessionId: input.executionSessionId } : {}),
69
+ });
70
+ run.events.push({
71
+ seq: run.events.length,
72
+ event: 'execution.attempt.started',
73
+ ts: input.startedAt,
74
+ attempt: input.attempt,
75
+ attemptId: input.attemptId,
76
+ ...(input.executionSessionId ? { executionSessionId: input.executionSessionId } : {}),
77
+ });
78
+ }
79
+ run.attempt = input.attempt;
80
+ run.attemptId = input.attemptId;
81
+ if (input.executionSessionId)
82
+ run.executionSessionId = input.executionSessionId;
83
+ if (input.executionDeadlineAt !== undefined)
84
+ run.executionDeadlineAt = input.executionDeadlineAt;
85
+ this.write(triggerId, active);
86
+ return run;
87
+ }
88
+ completeExecutionAttempt(triggerId, runId, attemptId, result) {
89
+ const active = this.read(triggerId);
90
+ const run = active.runs[runId];
91
+ if (!run)
92
+ return undefined;
93
+ const attempt = run.attempts?.find(item => item.attemptId === attemptId);
94
+ if (!attempt || attempt.status !== 'running')
95
+ return run;
96
+ const finishedAt = result.finishedAt ?? Date.now();
97
+ attempt.status = result.status;
98
+ attempt.finishedAt = finishedAt;
99
+ if (result.error)
100
+ attempt.error = result.error;
101
+ run.events.push({
102
+ seq: run.events.length,
103
+ event: result.status === 'completed' || result.status === 'noop'
104
+ ? 'execution.attempt.completed'
105
+ : 'execution.attempt.failed',
106
+ ts: finishedAt,
107
+ attempt: attempt.attempt,
108
+ attemptId,
109
+ status: result.status,
110
+ ...(result.error ? { error: result.error } : {}),
111
+ });
112
+ this.write(triggerId, active);
113
+ return run;
114
+ }
51
115
  remove(triggerId, runId) {
52
116
  const active = this.read(triggerId);
53
117
  delete active.runs[runId];
@@ -29,6 +29,35 @@ export function normalizeTriggerDefinition(input, opts = {}) {
29
29
  export function validateTriggerDefinition(definition) {
30
30
  normalizeTriggerDefinition(definition);
31
31
  }
32
+ /**
33
+ * Keep legacy `per_run` definitions byte-stable while giving runtime code one
34
+ * canonical representation for session selection and validation.
35
+ */
36
+ export function normalizeExecutionMode(execution) {
37
+ if (execution.type !== 'trigger_session')
38
+ return { type: execution.type };
39
+ return {
40
+ type: execution.type,
41
+ thread: execution.thread === 'per_run' || execution.thread === 'by_run'
42
+ ? 'by_run'
43
+ : 'by_trigger',
44
+ };
45
+ }
46
+ /** Validate the run-concurrency policy against the session ownership model. */
47
+ export function validateConcurrencyCombination(execution, reliability) {
48
+ // Scripts do not share a model session, so all run-level policies remain valid.
49
+ if (execution.type === 'script')
50
+ return;
51
+ if (reliability.concurrency === 'allow') {
52
+ const mode = normalizeExecutionMode(execution);
53
+ if (mode.type === 'target_session') {
54
+ throw new Error('invalid Trigger concurrency: execution.type=target_session, concurrency=allow; allowed values are forbid or replace');
55
+ }
56
+ if (mode.thread === 'by_trigger') {
57
+ throw new Error('invalid Trigger concurrency: execution.type=trigger_session, thread=by_trigger, concurrency=allow; allowed values are forbid or replace');
58
+ }
59
+ }
60
+ }
32
61
  export function definitionRevision(definition) {
33
62
  const stable = stableStringify(definition);
34
63
  return `sha256:${sha256(stable)}`;
@@ -381,8 +410,8 @@ function normalizeExecutionThread(value) {
381
410
  const thread = optionalString(value);
382
411
  if (thread === undefined)
383
412
  return undefined;
384
- if (thread !== 'per_run' && thread !== 'by_trigger') {
385
- throw new Error('execution.thread must be per_run or by_trigger');
413
+ if (thread !== 'per_run' && thread !== 'by_run' && thread !== 'by_trigger') {
414
+ throw new Error('execution.thread must be per_run, by_run, or by_trigger');
386
415
  }
387
416
  return thread;
388
417
  }
@@ -572,6 +601,7 @@ function validateTriggerSemantics(definition) {
572
601
  if (execution.type !== 'script' && (definition.feedback.onReply || definition.feedback.onNoop || definition.feedback.onFailure)) {
573
602
  throw new Error('feedback.onReply/onNoop/onFailure are only allowed when execution.type=script');
574
603
  }
604
+ validateConcurrencyCombination(execution, definition.reliability);
575
605
  }
576
606
  function generateTriggerId() {
577
607
  return `trig_${Date.now()}_${crypto.randomBytes(4).toString('hex')}`;
@@ -291,7 +291,7 @@ function resolveBaseagentInfo(config) {
291
291
  return { active_baseagent, model, effort };
292
292
  }
293
293
  function pickFirstBaseagent(baseagents) {
294
- for (const name of ['claude', 'codex', 'gemini']) {
294
+ for (const name of ['claude', 'codex', 'gemini', 'ecagent']) {
295
295
  if (baseagents[name] !== undefined)
296
296
  return name;
297
297
  }
@@ -3,6 +3,7 @@ import { fileURLToPath } from 'url';
3
3
  import { execFileSync, execFile, spawn, spawnSync } from 'child_process';
4
4
  import { promisify } from 'util';
5
5
  import fs from 'fs';
6
+ import { getProcessStartTime } from './process-introspect.js';
6
7
  const execFileAsync = promisify(execFile);
7
8
  export const isWindows = process.platform === 'win32';
8
9
  const ENCODE_PATH_MAX = 200;
@@ -133,10 +134,15 @@ export function getProcessInfo(pid) {
133
134
  }
134
135
  }
135
136
  else {
136
- const etimes = execFileSync('ps', ['-p', String(pid), '-o', 'etimes='], { encoding: 'utf-8' }).trim();
137
+ // procps `etimes` can overflow for freshly spawned processes in some
138
+ // container/time-namespace combinations. /proc start time is reliable
139
+ // on Linux and is already used for PID reuse protection elsewhere.
140
+ const startedAt = process.platform === 'linux' ? getProcessStartTime(pid) : null;
141
+ const uptime = startedAt !== null
142
+ ? formatUptime(Math.max(0, Math.floor((Date.now() - startedAt) / 1000)))
143
+ : formatUptime(parseInt(execFileSync('ps', ['-p', String(pid), '-o', 'etimes='], { encoding: 'utf-8' }).trim(), 10));
137
144
  const cpu = execFileSync('ps', ['-p', String(pid), '-o', '%cpu='], { encoding: 'utf-8' }).trim();
138
145
  const mem = execFileSync('ps', ['-p', String(pid), '-o', 'rss='], { encoding: 'utf-8' }).trim();
139
- const uptime = formatUptime(parseInt(etimes, 10));
140
146
  return { uptime, cpu, memory: mem };
141
147
  }
142
148
  }
@@ -54,6 +54,15 @@ const NON_INFRA_TERMINAL_REASONS = new Set([
54
54
  'aborted_streaming',
55
55
  'aborted_tools',
56
56
  ]);
57
+ /**
58
+ * SDK terminal reasons that mean the current turn was stopped before normal
59
+ * completion. In particular, Claude reports a declined/expired
60
+ * AskUserQuestion as `aborted_tools`; that is a user interaction outcome, not
61
+ * an infrastructure or agent execution failure.
62
+ */
63
+ export function isAbortTerminalReason(terminalReason) {
64
+ return terminalReason === 'aborted_streaming' || terminalReason === 'aborted_tools';
65
+ }
57
66
  export function isInfraError(subtype, terminalReason) {
58
67
  // terminalReason 优先级更高(更精确)
59
68
  if (terminalReason && NON_INFRA_TERMINAL_REASONS.has(terminalReason)) {
@@ -8,8 +8,25 @@
8
8
  */
9
9
  import fs from 'fs';
10
10
  import { spawnSync } from 'child_process';
11
- import { isWindows } from './cross-platform.js';
11
+ const isWindows = process.platform === 'win32';
12
12
  const isMacOS = process.platform === 'darwin';
13
+ function readLinuxClockTicks() {
14
+ if (process.platform !== 'linux')
15
+ return 100;
16
+ try {
17
+ const result = spawnSync('getconf', ['CLK_TCK'], {
18
+ encoding: 'utf-8',
19
+ timeout: 1000,
20
+ stdio: ['ignore', 'pipe', 'ignore'],
21
+ });
22
+ const value = Number.parseInt(result.stdout?.trim() || '', 10);
23
+ return value > 0 ? value : 100;
24
+ }
25
+ catch {
26
+ return 100;
27
+ }
28
+ }
29
+ const linuxClockTicks = readLinuxClockTicks();
13
30
  /** 容差:2 秒(覆盖 macOS 秒级精度 + 时钟漂移) */
14
31
  export const START_TIME_TOLERANCE_MS = 2000;
15
32
  /**
@@ -72,8 +89,7 @@ function getStartTimeLinux(pid) {
72
89
  }
73
90
  if (isNaN(btimeSec))
74
91
  return null;
75
- const clkTck = 100;
76
- return (btimeSec + starttimeJiffies / clkTck) * 1000;
92
+ return (btimeSec + starttimeJiffies / linuxClockTicks) * 1000;
77
93
  }
78
94
  // ── macOS ──
79
95
  function getStartTimeMacOS(pid) {
@@ -8,8 +8,7 @@
8
8
  * - 所有 IM 渠道(Feishu/WeChat/DingTalk/QQBot/WeCom):首次交互时发送给交互用户(被动响应)
9
9
  */
10
10
  import fs from 'fs';
11
- import path from 'path';
12
- import { resolvePaths } from '../paths.js';
11
+ import { channelStatePath } from '../paths.js';
13
12
  import { loadAgent, saveAgent } from '../config-store.js';
14
13
  import { withLifecycleForWrite } from '../config/lifecycle.js';
15
14
  import { logger } from './logger.js';
@@ -157,7 +156,7 @@ export class FirstInteractionWelcomeManager {
157
156
  this.agentAid = agentAid;
158
157
  this.channelName = channelName;
159
158
  // 每个 channel instance 独立的已欢迎用户记录
160
- this.greetedUsersFile = path.join(resolvePaths().dataDir, `${channelType}-greeted-${channelName}.jsonl`);
159
+ this.greetedUsersFile = channelStatePath(channelName, 'welcome.jsonl');
161
160
  this.loadGreetedUsers();
162
161
  }
163
162
  /**
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Mario Zechner
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.