evolcore 0.0.5 → 0.0.7

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 (323) hide show
  1. package/CHANGELOG.md +49 -0
  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 +88 -8
  21. package/dist/cli/data-command.js +132 -0
  22. package/dist/cli/index.js +11 -3
  23. package/dist/cli/init.js +76 -17
  24. package/dist/cli/net-check.js +9 -20
  25. package/dist/cli/restart-monitor.js +25 -2
  26. package/dist/cli/trigger-command.js +1 -1
  27. package/dist/cli/watch-msg.js +12 -25
  28. package/dist/config/builtin-roles.js +3 -1
  29. package/dist/config/config-field-policy.js +1 -1
  30. package/dist/config/gateway-config.js +21 -4
  31. package/dist/config/resolved-config-op.js +10 -0
  32. package/dist/core/command/command-handler.js +2 -2
  33. package/dist/core/command/menu-handler.js +11 -9
  34. package/dist/core/command/role-menu.js +11 -0
  35. package/dist/core/command/slash-handler.js +18 -14
  36. package/dist/core/data-migration.js +1434 -0
  37. package/dist/core/handoff/store.js +14 -4
  38. package/dist/core/inference/text-inference.js +42 -1
  39. package/dist/core/message/pending-hints.js +1 -1
  40. package/dist/core/message/response-engine.js +406 -167
  41. package/dist/core/message/retry-scheduler.js +71 -0
  42. package/dist/core/model/model-catalog.js +7 -2
  43. package/dist/core/model/model-fallback.js +81 -0
  44. package/dist/core/protected-paths.js +41 -8
  45. package/dist/core/session/adapters/ecagent-session-file-adapter.js +112 -0
  46. package/dist/core/session/session-fs-store.js +283 -59
  47. package/dist/core/session/session-manager.js +19 -21
  48. package/dist/core/session/session-title.js +1 -0
  49. package/dist/core/session/session-turn-coordinator.js +8 -1
  50. package/dist/core/system-channels.js +1 -0
  51. package/dist/index.js +13 -10
  52. package/dist/paths.js +35 -27
  53. package/dist/trigger/feedback.js +16 -0
  54. package/dist/trigger/legacy-session-history.js +19 -0
  55. package/dist/trigger/manager.js +12 -1
  56. package/dist/trigger/parser.js +4 -2
  57. package/dist/trigger/scheduler.js +185 -15
  58. package/dist/trigger/session-lock.js +62 -0
  59. package/dist/trigger/state.js +64 -0
  60. package/dist/trigger/validation.js +32 -2
  61. package/dist/utils/aid-bind.js +1 -1
  62. package/dist/utils/error-utils.js +9 -0
  63. package/dist/utils/instance-registry.js +2 -2
  64. package/dist/utils/welcome.js +2 -3
  65. package/ecagent/LICENSE +21 -0
  66. package/ecagent/NOTICE +11 -0
  67. package/ecagent/README.md +61 -0
  68. package/ecagent/dist/agent-loop.d.ts +24 -0
  69. package/ecagent/dist/agent-loop.d.ts.map +1 -0
  70. package/ecagent/dist/agent-loop.js +547 -0
  71. package/ecagent/dist/agent-loop.js.map +1 -0
  72. package/ecagent/dist/agent.d.ts +127 -0
  73. package/ecagent/dist/agent.d.ts.map +1 -0
  74. package/ecagent/dist/agent.js +386 -0
  75. package/ecagent/dist/agent.js.map +1 -0
  76. package/ecagent/dist/harness/agent-harness.d.ts +95 -0
  77. package/ecagent/dist/harness/agent-harness.d.ts.map +1 -0
  78. package/ecagent/dist/harness/agent-harness.js +926 -0
  79. package/ecagent/dist/harness/agent-harness.js.map +1 -0
  80. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +51 -0
  81. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +1 -0
  82. package/ecagent/dist/harness/compaction/branch-summarization.js +174 -0
  83. package/ecagent/dist/harness/compaction/branch-summarization.js.map +1 -0
  84. package/ecagent/dist/harness/compaction/compaction.d.ts +95 -0
  85. package/ecagent/dist/harness/compaction/compaction.d.ts.map +1 -0
  86. package/ecagent/dist/harness/compaction/compaction.js +528 -0
  87. package/ecagent/dist/harness/compaction/compaction.js.map +1 -0
  88. package/ecagent/dist/harness/compaction/utils.d.ts +25 -0
  89. package/ecagent/dist/harness/compaction/utils.d.ts.map +1 -0
  90. package/ecagent/dist/harness/compaction/utils.js +131 -0
  91. package/ecagent/dist/harness/compaction/utils.js.map +1 -0
  92. package/ecagent/dist/harness/env/nodejs.d.ts +51 -0
  93. package/ecagent/dist/harness/env/nodejs.d.ts.map +1 -0
  94. package/ecagent/dist/harness/env/nodejs.js +513 -0
  95. package/ecagent/dist/harness/env/nodejs.js.map +1 -0
  96. package/ecagent/dist/harness/messages.d.ts +51 -0
  97. package/ecagent/dist/harness/messages.d.ts.map +1 -0
  98. package/ecagent/dist/harness/messages.js +102 -0
  99. package/ecagent/dist/harness/messages.js.map +1 -0
  100. package/ecagent/dist/harness/prompt-templates.d.ts +48 -0
  101. package/ecagent/dist/harness/prompt-templates.d.ts.map +1 -0
  102. package/ecagent/dist/harness/prompt-templates.js +230 -0
  103. package/ecagent/dist/harness/prompt-templates.js.map +1 -0
  104. package/ecagent/dist/harness/session/jsonl-repo.d.ts +26 -0
  105. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +1 -0
  106. package/ecagent/dist/harness/session/jsonl-repo.js +100 -0
  107. package/ecagent/dist/harness/session/jsonl-repo.js.map +1 -0
  108. package/ecagent/dist/harness/session/jsonl-storage.d.ts +34 -0
  109. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +1 -0
  110. package/ecagent/dist/harness/session/jsonl-storage.js +234 -0
  111. package/ecagent/dist/harness/session/jsonl-storage.js.map +1 -0
  112. package/ecagent/dist/harness/session/memory-repo.d.ts +18 -0
  113. package/ecagent/dist/harness/session/memory-repo.d.ts.map +1 -0
  114. package/ecagent/dist/harness/session/memory-repo.js +44 -0
  115. package/ecagent/dist/harness/session/memory-repo.js.map +1 -0
  116. package/ecagent/dist/harness/session/memory-storage.d.ts +25 -0
  117. package/ecagent/dist/harness/session/memory-storage.d.ts.map +1 -0
  118. package/ecagent/dist/harness/session/memory-storage.js +111 -0
  119. package/ecagent/dist/harness/session/memory-storage.js.map +1 -0
  120. package/ecagent/dist/harness/session/repo-utils.d.ts +11 -0
  121. package/ecagent/dist/harness/session/repo-utils.d.ts.map +1 -0
  122. package/ecagent/dist/harness/session/repo-utils.js +39 -0
  123. package/ecagent/dist/harness/session/repo-utils.js.map +1 -0
  124. package/ecagent/dist/harness/session/session.d.ts +47 -0
  125. package/ecagent/dist/harness/session/session.d.ts.map +1 -0
  126. package/ecagent/dist/harness/session/session.js +245 -0
  127. package/ecagent/dist/harness/session/session.js.map +1 -0
  128. package/ecagent/dist/harness/session/uuid.d.ts +2 -0
  129. package/ecagent/dist/harness/session/uuid.d.ts.map +1 -0
  130. package/ecagent/dist/harness/session/uuid.js +50 -0
  131. package/ecagent/dist/harness/session/uuid.js.map +1 -0
  132. package/ecagent/dist/harness/skills.d.ts +44 -0
  133. package/ecagent/dist/harness/skills.d.ts.map +1 -0
  134. package/ecagent/dist/harness/skills.js +311 -0
  135. package/ecagent/dist/harness/skills.js.map +1 -0
  136. package/ecagent/dist/harness/system-prompt.d.ts +3 -0
  137. package/ecagent/dist/harness/system-prompt.d.ts.map +1 -0
  138. package/ecagent/dist/harness/system-prompt.js +30 -0
  139. package/ecagent/dist/harness/system-prompt.js.map +1 -0
  140. package/ecagent/dist/harness/types.d.ts +619 -0
  141. package/ecagent/dist/harness/types.d.ts.map +1 -0
  142. package/ecagent/dist/harness/types.js +81 -0
  143. package/ecagent/dist/harness/types.js.map +1 -0
  144. package/ecagent/dist/harness/utils/shell-output.d.ts +14 -0
  145. package/ecagent/dist/harness/utils/shell-output.d.ts.map +1 -0
  146. package/ecagent/dist/harness/utils/shell-output.js +126 -0
  147. package/ecagent/dist/harness/utils/shell-output.js.map +1 -0
  148. package/ecagent/dist/harness/utils/truncate.d.ts +70 -0
  149. package/ecagent/dist/harness/utils/truncate.d.ts.map +1 -0
  150. package/ecagent/dist/harness/utils/truncate.js +290 -0
  151. package/ecagent/dist/harness/utils/truncate.js.map +1 -0
  152. package/ecagent/dist/index.d.ts +22 -0
  153. package/ecagent/dist/index.d.ts.map +1 -0
  154. package/ecagent/dist/index.js +27 -0
  155. package/ecagent/dist/index.js.map +1 -0
  156. package/ecagent/dist/node.d.ts +3 -0
  157. package/ecagent/dist/node.d.ts.map +1 -0
  158. package/ecagent/dist/node.js +3 -0
  159. package/ecagent/dist/node.js.map +1 -0
  160. package/ecagent/dist/proxy.d.ts +69 -0
  161. package/ecagent/dist/proxy.d.ts.map +1 -0
  162. package/ecagent/dist/proxy.js +278 -0
  163. package/ecagent/dist/proxy.js.map +1 -0
  164. package/ecagent/dist/runtime/api/lazy.d.ts +15 -0
  165. package/ecagent/dist/runtime/api/lazy.d.ts.map +1 -0
  166. package/ecagent/dist/runtime/api/lazy.js +59 -0
  167. package/ecagent/dist/runtime/api/lazy.js.map +1 -0
  168. package/ecagent/dist/runtime/api/pi-messages.d.ts +97 -0
  169. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +1 -0
  170. package/ecagent/dist/runtime/api/pi-messages.js +307 -0
  171. package/ecagent/dist/runtime/api/pi-messages.js.map +1 -0
  172. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts +3 -0
  173. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +1 -0
  174. package/ecagent/dist/runtime/api/pi-messages.lazy.js +3 -0
  175. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +1 -0
  176. package/ecagent/dist/runtime/auth/context.d.ts +7 -0
  177. package/ecagent/dist/runtime/auth/context.d.ts.map +1 -0
  178. package/ecagent/dist/runtime/auth/context.js +42 -0
  179. package/ecagent/dist/runtime/auth/context.js.map +1 -0
  180. package/ecagent/dist/runtime/auth/credential-store.d.ts +16 -0
  181. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +1 -0
  182. package/ecagent/dist/runtime/auth/credential-store.js +39 -0
  183. package/ecagent/dist/runtime/auth/credential-store.js.map +1 -0
  184. package/ecagent/dist/runtime/auth/helpers.d.ts +20 -0
  185. package/ecagent/dist/runtime/auth/helpers.d.ts.map +1 -0
  186. package/ecagent/dist/runtime/auth/helpers.js +46 -0
  187. package/ecagent/dist/runtime/auth/helpers.js.map +1 -0
  188. package/ecagent/dist/runtime/auth/resolve.d.ts +26 -0
  189. package/ecagent/dist/runtime/auth/resolve.d.ts.map +1 -0
  190. package/ecagent/dist/runtime/auth/resolve.js +100 -0
  191. package/ecagent/dist/runtime/auth/resolve.js.map +1 -0
  192. package/ecagent/dist/runtime/auth/types.d.ts +180 -0
  193. package/ecagent/dist/runtime/auth/types.d.ts.map +1 -0
  194. package/ecagent/dist/runtime/auth/types.js +2 -0
  195. package/ecagent/dist/runtime/auth/types.js.map +1 -0
  196. package/ecagent/dist/runtime/compat.d.ts +28 -0
  197. package/ecagent/dist/runtime/compat.d.ts.map +1 -0
  198. package/ecagent/dist/runtime/compat.js +86 -0
  199. package/ecagent/dist/runtime/compat.js.map +1 -0
  200. package/ecagent/dist/runtime/index.d.ts +20 -0
  201. package/ecagent/dist/runtime/index.d.ts.map +1 -0
  202. package/ecagent/dist/runtime/index.js +17 -0
  203. package/ecagent/dist/runtime/index.js.map +1 -0
  204. package/ecagent/dist/runtime/models.d.ts +142 -0
  205. package/ecagent/dist/runtime/models.d.ts.map +1 -0
  206. package/ecagent/dist/runtime/models.js +245 -0
  207. package/ecagent/dist/runtime/models.js.map +1 -0
  208. package/ecagent/dist/runtime/oauth.d.ts +2 -0
  209. package/ecagent/dist/runtime/oauth.d.ts.map +1 -0
  210. package/ecagent/dist/runtime/oauth.js +2 -0
  211. package/ecagent/dist/runtime/oauth.js.map +1 -0
  212. package/ecagent/dist/runtime/providers/faux.d.ts +97 -0
  213. package/ecagent/dist/runtime/providers/faux.d.ts.map +1 -0
  214. package/ecagent/dist/runtime/providers/faux.js +395 -0
  215. package/ecagent/dist/runtime/providers/faux.js.map +1 -0
  216. package/ecagent/dist/runtime/types.d.ts +607 -0
  217. package/ecagent/dist/runtime/types.d.ts.map +1 -0
  218. package/ecagent/dist/runtime/types.js +2 -0
  219. package/ecagent/dist/runtime/types.js.map +1 -0
  220. package/ecagent/dist/runtime/utils/diagnostics.d.ts +19 -0
  221. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +1 -0
  222. package/ecagent/dist/runtime/utils/diagnostics.js +25 -0
  223. package/ecagent/dist/runtime/utils/diagnostics.js.map +1 -0
  224. package/ecagent/dist/runtime/utils/event-stream.d.ts +21 -0
  225. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +1 -0
  226. package/ecagent/dist/runtime/utils/event-stream.js +77 -0
  227. package/ecagent/dist/runtime/utils/event-stream.js.map +1 -0
  228. package/ecagent/dist/runtime/utils/headers.d.ts +4 -0
  229. package/ecagent/dist/runtime/utils/headers.d.ts.map +1 -0
  230. package/ecagent/dist/runtime/utils/headers.js +18 -0
  231. package/ecagent/dist/runtime/utils/headers.js.map +1 -0
  232. package/ecagent/dist/runtime/utils/json-parse.d.ts +16 -0
  233. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +1 -0
  234. package/ecagent/dist/runtime/utils/json-parse.js +113 -0
  235. package/ecagent/dist/runtime/utils/json-parse.js.map +1 -0
  236. package/ecagent/dist/runtime/utils/oauth/types.d.ts +64 -0
  237. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +1 -0
  238. package/ecagent/dist/runtime/utils/oauth/types.js +2 -0
  239. package/ecagent/dist/runtime/utils/oauth/types.js.map +1 -0
  240. package/ecagent/dist/runtime/utils/overflow.d.ts +58 -0
  241. package/ecagent/dist/runtime/utils/overflow.d.ts.map +1 -0
  242. package/ecagent/dist/runtime/utils/overflow.js +158 -0
  243. package/ecagent/dist/runtime/utils/overflow.js.map +1 -0
  244. package/ecagent/dist/runtime/utils/provider-env.d.ts +7 -0
  245. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +1 -0
  246. package/ecagent/dist/runtime/utils/provider-env.js +44 -0
  247. package/ecagent/dist/runtime/utils/provider-env.js.map +1 -0
  248. package/ecagent/dist/runtime/utils/retry.d.ts +12 -0
  249. package/ecagent/dist/runtime/utils/retry.d.ts.map +1 -0
  250. package/ecagent/dist/runtime/utils/retry.js +90 -0
  251. package/ecagent/dist/runtime/utils/retry.js.map +1 -0
  252. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts +17 -0
  253. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +1 -0
  254. package/ecagent/dist/runtime/utils/typebox-helpers.js +21 -0
  255. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +1 -0
  256. package/ecagent/dist/runtime/utils/validation.d.ts +18 -0
  257. package/ecagent/dist/runtime/utils/validation.d.ts.map +1 -0
  258. package/ecagent/dist/runtime/utils/validation.js +269 -0
  259. package/ecagent/dist/runtime/utils/validation.js.map +1 -0
  260. package/ecagent/dist/types.d.ts +401 -0
  261. package/ecagent/dist/types.d.ts.map +1 -0
  262. package/ecagent/dist/types.js +2 -0
  263. package/ecagent/dist/types.js.map +1 -0
  264. package/ecagent/package.json +93 -0
  265. package/ecagent/src/agent-loop.ts +792 -0
  266. package/ecagent/src/agent.ts +575 -0
  267. package/ecagent/src/harness/agent-harness.ts +1029 -0
  268. package/ecagent/src/harness/compaction/branch-summarization.ts +261 -0
  269. package/ecagent/src/harness/compaction/compaction.ts +753 -0
  270. package/ecagent/src/harness/compaction/utils.ts +144 -0
  271. package/ecagent/src/harness/env/nodejs.ts +569 -0
  272. package/ecagent/src/harness/messages.ts +164 -0
  273. package/ecagent/src/harness/prompt-templates.ts +267 -0
  274. package/ecagent/src/harness/session/jsonl-repo.ts +179 -0
  275. package/ecagent/src/harness/session/jsonl-storage.ts +314 -0
  276. package/ecagent/src/harness/session/memory-repo.ts +50 -0
  277. package/ecagent/src/harness/session/memory-storage.ts +133 -0
  278. package/ecagent/src/harness/session/repo-utils.ts +51 -0
  279. package/ecagent/src/harness/session/session.ts +338 -0
  280. package/ecagent/src/harness/session/uuid.ts +54 -0
  281. package/ecagent/src/harness/skills.ts +375 -0
  282. package/ecagent/src/harness/system-prompt.ts +34 -0
  283. package/ecagent/src/harness/types.ts +838 -0
  284. package/ecagent/src/harness/utils/shell-output.ts +135 -0
  285. package/ecagent/src/harness/utils/truncate.ts +344 -0
  286. package/ecagent/src/index.ts +46 -0
  287. package/ecagent/src/node.ts +2 -0
  288. package/ecagent/src/proxy.ts +367 -0
  289. package/ecagent/src/runtime/api/lazy.ts +70 -0
  290. package/ecagent/src/runtime/api/pi-messages.lazy.ts +4 -0
  291. package/ecagent/src/runtime/api/pi-messages.ts +436 -0
  292. package/ecagent/src/runtime/auth/context.ts +45 -0
  293. package/ecagent/src/runtime/auth/credential-store.ts +47 -0
  294. package/ecagent/src/runtime/auth/helpers.ts +46 -0
  295. package/ecagent/src/runtime/auth/resolve.ts +141 -0
  296. package/ecagent/src/runtime/auth/types.ts +182 -0
  297. package/ecagent/src/runtime/compat.ts +158 -0
  298. package/ecagent/src/runtime/index.ts +31 -0
  299. package/ecagent/src/runtime/models.ts +452 -0
  300. package/ecagent/src/runtime/oauth.ts +1 -0
  301. package/ecagent/src/runtime/providers/faux.ts +538 -0
  302. package/ecagent/src/runtime/types.ts +718 -0
  303. package/ecagent/src/runtime/utils/diagnostics.ts +45 -0
  304. package/ecagent/src/runtime/utils/event-stream.ts +88 -0
  305. package/ecagent/src/runtime/utils/headers.ts +18 -0
  306. package/ecagent/src/runtime/utils/json-parse.ts +124 -0
  307. package/ecagent/src/runtime/utils/oauth/types.ts +79 -0
  308. package/ecagent/src/runtime/utils/overflow.ts +165 -0
  309. package/ecagent/src/runtime/utils/provider-env.ts +52 -0
  310. package/ecagent/src/runtime/utils/retry.ts +101 -0
  311. package/ecagent/src/runtime/utils/typebox-helpers.ts +24 -0
  312. package/ecagent/src/runtime/utils/validation.ts +310 -0
  313. package/ecagent/src/types.ts +430 -0
  314. package/ecagent/tsconfig.build.json +32 -0
  315. package/kits/schemas/_meta.json +7 -4
  316. package/kits/schemas/agent-config.schema.10.json +370 -0
  317. package/kits/schemas/defaults.schema.4.json +93 -0
  318. package/kits/schemas/relation-config.schema.8.json +69 -0
  319. package/kits/templates/roles/admin.json +1 -1
  320. package/package.json +22 -6
  321. package/skills/eclink/SKILL.md +70 -0
  322. package/skills/eclink/agents/openai.yaml +4 -0
  323. package/assets/.env.template +0 -4
package/dist/paths.js CHANGED
@@ -25,15 +25,19 @@ export function _resetRoot() {
25
25
  }
26
26
  export function resolvePaths() {
27
27
  const root = resolveRoot();
28
+ const agentsDir = path.join(root, 'agents');
28
29
  return {
29
30
  root,
30
- sessionsDir: path.join(root, 'data', 'sessions'),
31
+ // Session storage is partitioned by owning Agent. Keep the historical
32
+ // location separately so the explicit data migration can inventory it.
33
+ sessionsDir: agentsDir,
34
+ legacySessionsDir: path.join(root, 'data', 'sessions'),
31
35
  instanceDir: path.join(root, 'data', 'instance'),
32
36
  outboxDir: path.join(root, 'data', 'outbox'),
33
37
  dataDir: path.join(root, 'data'),
34
38
  claudeModelCache: path.join(root, 'data', 'claude-model-cache.json'),
35
39
  logs: path.join(root, 'logs'),
36
- agentsDir: path.join(root, 'agents'),
40
+ agentsDir,
37
41
  lineStats: path.join(root, 'logs', 'line-stats.log'),
38
42
  readySignal: path.join(root, 'data', 'instance', 'ready.signal'),
39
43
  controlToken: path.join(root, 'data', 'instance', 'control.token'),
@@ -126,8 +130,36 @@ export function agentDataDir(aid) {
126
130
  export function agentDataCacheDir(aid) {
127
131
  return path.join(agentDataDir(aid), 'cache');
128
132
  }
133
+ export function agentHandoffDir(aid) {
134
+ return path.join(agentDataDir(aid), 'handoff');
135
+ }
136
+ export function agentOutboxPath(aid) {
137
+ return path.join(agentDataDir(aid), 'outbox.jsonl');
138
+ }
139
+ /** Directory-safe channel instance key used by connector runtime state. */
140
+ function encodePathKey(value) {
141
+ return encodeURIComponent(value);
142
+ }
143
+ export function daemonDataDir() {
144
+ return path.join(resolveRoot(), 'data', 'daemon');
145
+ }
146
+ export function daemonControlDir() {
147
+ return path.join(daemonDataDir(), 'control');
148
+ }
149
+ export function daemonQueuePath() {
150
+ return path.join(daemonDataDir(), 'message-queue.json');
151
+ }
152
+ export function channelStateDir(channelKey) {
153
+ if (!channelKey || channelKey === '.' || channelKey === '..') {
154
+ throw new Error('channelKey is required');
155
+ }
156
+ return path.join(resolveRoot(), 'data', 'channel-state', encodePathKey(channelKey));
157
+ }
158
+ export function channelStatePath(channelKey, ...parts) {
159
+ return path.join(channelStateDir(channelKey), ...parts);
160
+ }
129
161
  export function agentTriggersDir(aid) {
130
- return path.join(resolveRoot(), 'data', 'triggers', aid);
162
+ return path.join(agentDir(aid), 'triggers');
131
163
  }
132
164
  function resolveInstanceSocketPath(root) {
133
165
  if (isWindows) {
@@ -147,7 +179,6 @@ export function ensureDataDirs() {
147
179
  fs.mkdirSync(p.outboxDir, { recursive: true });
148
180
  fs.mkdirSync(p.eckDir, { recursive: true });
149
181
  fs.mkdirSync(aidsDir(), { recursive: true });
150
- seedConfigTemplates();
151
182
  migrateFromAun();
152
183
  }
153
184
  const MIGRATION_MARKER = '.migrated-from-aun';
@@ -211,29 +242,6 @@ function migrateFromAun() {
211
242
  }
212
243
  catch { }
213
244
  }
214
- /**
215
- * 首次运行时从 assets/ 模板拷贝 .env 到 $EVOLCORE_HOME。
216
- * 已存在则跳过,不覆盖用户修改。
217
- */
218
- function seedConfigTemplates() {
219
- const root = resolveRoot();
220
- const assetsDir = path.join(getPackageRoot(), 'assets');
221
- const templates = [
222
- { src: '.env.template', dst: '.env' },
223
- ];
224
- for (const { src, dst } of templates) {
225
- const dstPath = path.join(root, dst);
226
- if (fs.existsSync(dstPath))
227
- continue;
228
- const srcPath = path.join(assetsDir, src);
229
- if (!fs.existsSync(srcPath))
230
- continue;
231
- try {
232
- fs.copyFileSync(srcPath, dstPath);
233
- }
234
- catch { /* best-effort */ }
235
- }
236
- }
237
245
  // ── kits 路径(始终从包内读取,不复制到 EVOLCORE_HOME)──
238
246
  export function kitsDir() {
239
247
  return path.join(getPackageRoot(), 'kits');
@@ -69,6 +69,7 @@ export class TriggerFeedbackDispatcher {
69
69
  const deadlineTimer = setTimeout(() => abortDelivery('timeout', new Error(`target session timed out after ${TARGET_SESSION_DEADLINE_MS}ms`)), TARGET_SESSION_DEADLINE_MS);
70
70
  deadlineTimer.unref?.();
71
71
  let outcome;
72
+ let releaseSessionLock;
72
73
  try {
73
74
  if (delivery.signal.aborted) {
74
75
  const aborted = targetSessionCancellationError(abortKind, delivery.signal.reason, false);
@@ -128,6 +129,20 @@ export class TriggerFeedbackDispatcher {
128
129
  }
129
130
  return this.failed(feedback, [], 'session_error', err?.message || String(err));
130
131
  }
132
+ if (input.onSessionResolved) {
133
+ const access = await input.onSessionResolved(session);
134
+ if (access.kind === 'conflict') {
135
+ return {
136
+ status: 'skipped',
137
+ reason: 'concurrency_forbid',
138
+ conflictRunId: access.conflictRunId,
139
+ feedback,
140
+ effects: [],
141
+ error: null,
142
+ };
143
+ }
144
+ releaseSessionLock = access.release;
145
+ }
131
146
  const message = {
132
147
  channel: target.channelKey,
133
148
  channelType,
@@ -241,6 +256,7 @@ export class TriggerFeedbackDispatcher {
241
256
  }
242
257
  }
243
258
  finally {
259
+ releaseSessionLock?.();
244
260
  clearTimeout(deadlineTimer);
245
261
  input.signal?.removeEventListener('abort', onInputAbort);
246
262
  outcome?.cancel();
@@ -0,0 +1,19 @@
1
+ const HISTORY_CHANNELS = new Set(['aun', 'feishu', 'wechat', 'dingtalk', 'qqbot', 'wecom']);
2
+ /**
3
+ * Turns legacy raw session-file references into the controlled history CLI.
4
+ * The replacement deliberately keeps the explicit peer/channel scope but
5
+ * never carries a filesystem location into a Trigger prompt.
6
+ */
7
+ export function rewriteLegacyTriggerSessionPaths(prompt, triggerAid) {
8
+ const legacyPath = /data\/sessions\/([A-Za-z0-9_-]+)\/([^/\s`"'<>]+)(?:\/([^/\s`"'<>]+))?\/messages\.jsonl/g;
9
+ return prompt.replace(legacyPath, (match, channel, first, second) => {
10
+ if (!HISTORY_CHANNELS.has(channel))
11
+ return match;
12
+ const isAun = channel === 'aun';
13
+ const target = isAun ? second : first;
14
+ const selfAid = triggerAid ?? (isAun ? first : undefined);
15
+ if (!selfAid || !target)
16
+ return match;
17
+ return `ec msg history ${selfAid} ${target} --channel ${channel} --session all --direction in --format json`;
18
+ });
19
+ }
@@ -4,6 +4,7 @@ import { agentTriggersDir } from '../paths.js';
4
4
  import { definitionRevision, normalizeTriggerDefinition, resolveScriptPath, safeRelativePath, } from './validation.js';
5
5
  import { atomicWriteJson } from '../core/session/session-fs-store.js';
6
6
  import { TriggerHistoryStore } from './history.js';
7
+ import { rewriteLegacyTriggerSessionPaths } from './legacy-session-history.js';
7
8
  export class TriggerDefinitionManager {
8
9
  agentAid;
9
10
  rootDir;
@@ -34,7 +35,17 @@ export class TriggerDefinitionManager {
34
35
  if (!fs.existsSync(file))
35
36
  return undefined;
36
37
  const raw = JSON.parse(fs.readFileSync(file, 'utf-8'));
37
- return normalizeTriggerDefinition(raw);
38
+ const definition = normalizeTriggerDefinition(raw);
39
+ if (typeof definition.execution.prompt === 'string') {
40
+ const rewritten = rewriteLegacyTriggerSessionPaths(definition.execution.prompt, definition.agentAid);
41
+ if (rewritten !== definition.execution.prompt) {
42
+ definition.execution = { ...definition.execution, prompt: rewritten };
43
+ definition.updatedAt = Date.now();
44
+ this.writeDefinition(definition);
45
+ this.history.recordDefinition('trigger.updated', definition, { reason: 'legacy-session-history-paths' });
46
+ }
47
+ }
48
+ return definition;
38
49
  }
39
50
  require(triggerId) {
40
51
  const def = this.get(triggerId);
@@ -210,11 +210,13 @@ function parseTriggerThread(flags) {
210
210
  return { ok: false, error: '--trigger-thread 不能为空' };
211
211
  if (raw === 'per-run')
212
212
  return { ok: true, value: 'per_run' };
213
+ if (raw === 'by-run')
214
+ return { ok: true, value: 'by_run' };
213
215
  if (raw === 'by-trigger')
214
216
  return { ok: true, value: 'by_trigger' };
215
- if (raw === 'per_run' || raw === 'by_trigger')
217
+ if (raw === 'per_run' || raw === 'by_run' || raw === 'by_trigger')
216
218
  return { ok: true, value: raw };
217
- return { ok: false, error: '--trigger-thread 只接受 per-run 或 by-trigger' };
219
+ return { ok: false, error: '--trigger-thread 只接受 by-run 或 by-trigger(兼容 per-run)' };
218
220
  }
219
221
  function parseTargetSession(flags) {
220
222
  if (!flags.has('target-session'))
@@ -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
+ }