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/cli/init.js CHANGED
@@ -8,6 +8,7 @@ import { scanInstances } from '../utils/instance-registry.js';
8
8
  import { saveDefaultsSafe, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
9
9
  import { generateControlAid, resolveControlIssuer } from '../aun/aid/control-aid.js';
10
10
  import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
11
+ import { resolveEcagentConfig } from '../agents/baseagent.js';
11
12
  import { defaultProjectsRoot } from '../utils/project-path.js';
12
13
  import { WEB_CLI_BIN, WEB_PACKAGE_LATEST } from '../product.js';
13
14
  // ==================== Helpers ====================
@@ -23,22 +24,38 @@ export function expandLeadingTildePath(value, homeDir = os.homedir()) {
23
24
  }
24
25
  return value;
25
26
  }
26
- const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
27
+ const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
28
+ const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
27
29
  function isBaseagentAvailable(baseagent) {
28
30
  if (baseagent === 'codex')
29
31
  return isCodexAppServerAvailable();
32
+ if (baseagent === 'ecagent')
33
+ return true;
30
34
  return commandExists(baseagent);
31
35
  }
32
36
  function detectAvailable() {
33
37
  return BASEAGENT_CANDIDATES.filter(isBaseagentAvailable);
34
38
  }
35
39
  function pickDefault(available) {
36
- return (available.includes('claude') ? 'claude' : available[0]);
40
+ return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate));
41
+ }
42
+ function ecagentUnavailableReason() {
43
+ try {
44
+ resolveEcagentConfig({ agents: { ecagent: {} } });
45
+ return undefined;
46
+ }
47
+ catch (error) {
48
+ return error instanceof Error ? error.message : String(error);
49
+ }
37
50
  }
38
51
  function buildDefaults(chosen, available, projectsDefaultPath) {
39
52
  const baseagents = {};
40
- for (const b of available)
53
+ for (const b of available) {
54
+ // ecagent is bundled but opt-in; do not declare it merely because it is available.
55
+ if (b === 'ecagent' && chosen !== 'ecagent')
56
+ continue;
41
57
  baseagents[b] = {};
58
+ }
42
59
  return {
43
60
  $schema_version: 1,
44
61
  active_baseagent: chosen,
@@ -104,7 +121,7 @@ export async function cmdInit(options) {
104
121
  // 云部署非交互式路径:当带 --owner 或 --format json 时走结构化分支,与交互式 tail 完全隔离。
105
122
  // 预留方案 B:当 daemon 支持 Control Plane 无 agent 启动时,可在此分支跳过 agent new 预创建。
106
123
  if (options?.nonInteractive && (options.owner || options.format === 'json' || options.projectpath || options.ecweb)) {
107
- return cmdInitNonInteractive({
124
+ await cmdInitNonInteractive({
108
125
  owner: options.owner,
109
126
  baseagent: options.baseagent,
110
127
  projectpath: options.projectpath,
@@ -112,6 +129,7 @@ export async function cmdInit(options) {
112
129
  force: options.force,
113
130
  format: options.format,
114
131
  });
132
+ return true;
115
133
  }
116
134
  const p = resolvePaths();
117
135
  ensureDataDirs();
@@ -120,17 +138,17 @@ export async function cmdInit(options) {
120
138
  if (aliveMains.length > 0) {
121
139
  const pids = aliveMains.map(m => m.record.pid).join(', ');
122
140
  console.log(`❌ EvolCore 正在运行 (PID: ${pids}),请先执行 ec stop`);
123
- return;
141
+ return false;
124
142
  }
125
143
  // ── 2. 探测 baseagent ──
126
144
  const available = detectAvailable();
127
- if (available.length === 0) {
145
+ if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
128
146
  console.log('❌ 未检测到可用 baseagent。请安装至少一款:');
129
147
  console.log(' - claude CLI');
130
148
  console.log(' - gemini CLI');
131
149
  console.log(' - codex CLI with app-server');
132
150
  console.log('\n安装后重新运行 ec init');
133
- return;
151
+ return false;
134
152
  }
135
153
  console.log('检测到可用的 baseagent:');
136
154
  for (const b of available)
@@ -152,19 +170,31 @@ export async function cmdInit(options) {
152
170
  if (options.baseagent) {
153
171
  if (!BASEAGENT_CANDIDATES.includes(options.baseagent)) {
154
172
  console.log(`❌ 无效 baseagent: ${options.baseagent}(可选: ${BASEAGENT_CANDIDATES.join('/')})`);
155
- return; // 硬错误:不落 tail
173
+ return false; // 硬错误:不落 tail
156
174
  }
157
175
  if (!available.includes(options.baseagent)) {
158
176
  const reason = options.baseagent === 'codex'
159
177
  ? getCodexAppServerAvailability().reason
160
178
  : undefined;
161
179
  console.log(`❌ ${options.baseagent} 当前环境不可用${reason ? `:${reason}` : `(可用: ${available.join('/')})`}`);
162
- return; // 硬错误:不落 tail
180
+ return false; // 硬错误:不落 tail
181
+ }
182
+ if (options.baseagent === 'ecagent') {
183
+ const reason = ecagentUnavailableReason();
184
+ if (reason) {
185
+ console.log(`❌ ecagent 当前环境不可用:${reason}`);
186
+ return false;
187
+ }
163
188
  }
164
189
  chosen = options.baseagent;
165
190
  }
166
191
  else {
167
- chosen = pickDefault(available);
192
+ const defaultBaseagent = pickDefault(available);
193
+ if (!defaultBaseagent) {
194
+ console.log('❌ ecagent 为可选 baseagent,请显式使用 --baseagent ecagent,并配置 API key');
195
+ return false;
196
+ }
197
+ chosen = defaultBaseagent;
168
198
  }
169
199
  writeDefaults(chosen, available);
170
200
  console.log(`✓ 已${defaultsExisted ? '覆盖' : '创建'}: ${defaultsPath}`);
@@ -177,19 +207,24 @@ export async function cmdInit(options) {
177
207
  await runInteractive();
178
208
  }
179
209
  // ── 共享 tail(单一出口):提示创建 agent + 生成控制 AID ──
180
- await initTail({ invokedByStart: options?.invokedByStart });
210
+ return initTail({ invokedByStart: options?.invokedByStart });
181
211
  // ── 内部函数 ──
182
212
  async function runInteractive() {
183
213
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
184
214
  async function askBaseagent() {
185
215
  const defaultBa = pickDefault(available);
186
- if (available.length === 1) {
216
+ if (available.length === 1 && defaultBa) {
187
217
  console.log(` baseagent: ${defaultBa}`);
188
218
  return defaultBa;
189
219
  }
190
220
  let chosen = null;
191
221
  while (chosen === null) {
192
- const input = (await ask(rl, `默认 baseagent (${available.join('/')}) [${defaultBa}]: `)).trim() || defaultBa;
222
+ const defaultHint = defaultBa ? ` [${defaultBa}]` : '';
223
+ const input = (await ask(rl, `默认 baseagent (${available.join('/')})${defaultHint}: `)).trim() || defaultBa;
224
+ if (!input) {
225
+ console.log(' ecagent 为可选项,请显式输入 ecagent');
226
+ continue;
227
+ }
193
228
  if (!BASEAGENT_CANDIDATES.includes(input)) {
194
229
  console.log(` 无效选择,可选: ${BASEAGENT_CANDIDATES.join('/')}`);
195
230
  continue;
@@ -198,6 +233,13 @@ export async function cmdInit(options) {
198
233
  console.log(` ${input} 当前环境不可用(可用: ${available.join('/')})`);
199
234
  continue;
200
235
  }
236
+ if (input === 'ecagent') {
237
+ const reason = ecagentUnavailableReason();
238
+ if (reason) {
239
+ console.log(` ecagent 当前环境不可用:${reason}`);
240
+ continue;
241
+ }
242
+ }
201
243
  chosen = input;
202
244
  }
203
245
  return chosen;
@@ -275,6 +317,7 @@ export async function initTail(options = {}) {
275
317
  }
276
318
  // 控制 AID:daemon 进程身份。缺失则生成并写回 daemon.json(幂等:已存在则跳过)。
277
319
  const daemonConfig = loadDaemonConfig();
320
+ let controlAidReady = !!daemonConfig.aid;
278
321
  if (daemonConfig.aid) {
279
322
  const targetIssuer = resolveControlIssuer();
280
323
  const currentIssuer = daemonConfig.aid.split('.').slice(1).join('.');
@@ -288,10 +331,12 @@ export async function initTail(options = {}) {
288
331
  try {
289
332
  const { aid } = await generateControlAid();
290
333
  saveDaemonConfig({ ...daemonConfig, $schema_version: daemonConfig.$schema_version ?? 1, aid });
334
+ controlAidReady = true;
291
335
  console.log(`✓ 已生成控制 AID: ${aid}`);
292
336
  }
293
337
  catch (e) {
294
338
  console.error(`⚠️ 控制 AID 生成失败(Gateway 不可达?联网后重跑 ec init 补全): ${e?.message || e}`);
339
+ controlAidReady = false;
295
340
  }
296
341
  }
297
342
  // 交互式询问:使用单一 readline 实例避免 stdin 状态混乱
@@ -387,7 +432,11 @@ export async function initTail(options = {}) {
387
432
  }
388
433
  // ec start 会接着启动 daemon,并在其 ready 后给出可用的 Agent 创建入口。
389
434
  if (options.invokedByStart)
390
- return;
435
+ return controlAidReady;
436
+ if (!controlAidReady) {
437
+ console.error('\n❌ EvolCore 初始化未完成:控制 AID 未配置。联网后重新运行 ec init。');
438
+ return false;
439
+ }
391
440
  // 初始化完成总结
392
441
  console.log('\n✓ EvolCore 初始化完成');
393
442
  const finalCfg = loadDaemonConfig();
@@ -405,6 +454,7 @@ export async function initTail(options = {}) {
405
454
  console.log(`\n✓ 已有 ${finalAgents.length} 个 agent,可直接运行:`);
406
455
  console.log(' ec start');
407
456
  }
457
+ return controlAidReady;
408
458
  }
409
459
  /**
410
460
  * Present instance selection menu when existing instances are found.
@@ -500,8 +550,8 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
500
550
  }
501
551
  // ── 3. baseagent 探测 + 校验 ──
502
552
  const available = dependencies.availableBaseagents ?? detectAvailable();
503
- if (available.length === 0) {
504
- fail('BASEAGENT_UNAVAILABLE', 'no baseagent CLI detected (install claude/codex/gemini)', EXIT_USAGE, format);
553
+ if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
554
+ fail('BASEAGENT_UNAVAILABLE', 'no usable baseagent detected (install claude/codex/gemini or configure bundled ecagent)', EXIT_USAGE, format);
505
555
  }
506
556
  let chosenBaseagent;
507
557
  if (opts.baseagent) {
@@ -512,10 +562,19 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
512
562
  const reason = opts.baseagent === 'codex' ? getCodexAppServerAvailability().reason : undefined;
513
563
  fail('BASEAGENT_UNAVAILABLE', `${opts.baseagent} not available${reason ? `: ${reason}` : ''}`, EXIT_USAGE, format);
514
564
  }
565
+ if (opts.baseagent === 'ecagent') {
566
+ const reason = ecagentUnavailableReason();
567
+ if (reason)
568
+ fail('BASEAGENT_UNAVAILABLE', reason, EXIT_USAGE, format);
569
+ }
515
570
  chosenBaseagent = opts.baseagent;
516
571
  }
517
572
  else {
518
- chosenBaseagent = pickDefault(available);
573
+ const defaultBaseagent = pickDefault(available);
574
+ if (!defaultBaseagent) {
575
+ fail('BASEAGENT_UNAVAILABLE', 'ecagent is optional; pass --baseagent ecagent after configuring its API key', EXIT_USAGE, format);
576
+ }
577
+ chosenBaseagent = defaultBaseagent;
519
578
  }
520
579
  // ── 4. projectpath 校验 + 创建 ──
521
580
  let projectsDefaultPath;
@@ -307,31 +307,20 @@ async function runCheck(aid, formatJson, kickTest) {
307
307
  // 统计每个 AID 的总消息活跃度(遍历所有本地 AID 的 sessions)
308
308
  const { resolvePaths } = await import('../paths.js');
309
309
  const p = resolvePaths();
310
- const sessAunDir = path.join(p.sessionsDir, 'aun');
311
- const encode = (s) => s.replace(/[/%\\:*?"<>|]/g, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
312
- const decode = (s) => s.replace(/%([0-9A-Fa-f]{2})/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));
310
+ const { scanChatDirs } = await import('../core/session/session-fs-store.js');
313
311
  function getGlobalMessageCounts() {
314
312
  const counts = new Map();
315
313
  try {
316
- const localDirs = fs.readdirSync(sessAunDir, { withFileTypes: true });
317
- for (const localDir of localDirs) {
318
- if (!localDir.isDirectory())
314
+ for (const chat of scanChatDirs(p.sessionsDir)) {
315
+ if (chat.channelType !== 'aun')
319
316
  continue;
320
- const localAid = decode(localDir.name);
321
- const peerDirs = fs.readdirSync(path.join(sessAunDir, localDir.name), { withFileTypes: true });
322
- for (const peerDir of peerDirs) {
323
- if (!peerDir.isDirectory())
324
- continue;
325
- const peerId = decode(peerDir.name);
326
- const msgFile = path.join(sessAunDir, localDir.name, peerDir.name, 'messages.jsonl');
327
- try {
328
- const content = fs.readFileSync(msgFile, 'utf-8');
329
- const lineCount = content.split('\n').filter(l => l.trim()).length;
330
- counts.set(peerId, (counts.get(peerId) ?? 0) + lineCount);
331
- counts.set(localAid, (counts.get(localAid) ?? 0) + lineCount);
332
- }
333
- catch { }
317
+ const msgFile = path.join(chat.dirPath, 'messages.jsonl');
318
+ try {
319
+ const lineCount = fs.readFileSync(msgFile, 'utf-8').split('\n').filter(l => l.trim()).length;
320
+ counts.set(chat.channelId, (counts.get(chat.channelId) ?? 0) + lineCount);
321
+ counts.set(chat.selfAID, (counts.get(chat.selfAID) ?? 0) + lineCount);
334
322
  }
323
+ catch { }
335
324
  }
336
325
  }
337
326
  catch { }
@@ -2,7 +2,7 @@ import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { spawn, execFile } from 'child_process';
4
4
  import { promisify } from 'util';
5
- import { resolvePaths, getPackageRoot } from '../paths.js';
5
+ import { resolvePaths, getPackageRoot, daemonControlDir } from '../paths.js';
6
6
  import { loadDaemonConfig } from '../config-store.js';
7
7
  import * as platform from '../utils/cross-platform.js';
8
8
  import { EventBus } from '../core/event-bus.js';
@@ -14,6 +14,8 @@ import { scanInstances, cleanupInstances, writeRestartMonitor, removeRestartMoni
14
14
  import { WEB_PACKAGE_NAME } from '../product.js';
15
15
  import { shouldSuppressRealRestart } from '../utils/restart-safety.js';
16
16
  import { rotateStdoutLog } from '../utils/log-writer.js';
17
+ import { inspectDataMigrationRequirement } from '../core/data-migration.js';
18
+ import { serviceProxyNeedsEcweb, startEcwebIfEnabled } from './daemon-commands.js';
17
19
  const execFileAsync = promisify(execFile);
18
20
  // 清理 Claude Code 环境变量,防止 SDK 认为是嵌套会话
19
21
  function cleanEnv() {
@@ -72,6 +74,14 @@ export async function cmdRestartMonitor() {
72
74
  return s.mains.some(m => m.alive);
73
75
  };
74
76
  log('Restart monitor started');
77
+ const migrationRequirement = inspectDataMigrationRequirement(p.root);
78
+ if (migrationRequirement.required) {
79
+ const message = `Pending user-data migration (${migrationRequirement.operationCount} operations); leaving the current service running. Run ec data migrate --dry-run, then ec data migrate --apply.`;
80
+ log(message);
81
+ console.error(`❌ ${message}`);
82
+ process.exitCode = 2;
83
+ return;
84
+ }
75
85
  // Test daemons live in one temporary HOME per test, so the current HOME's
76
86
  // instance registry cannot see them. Their strict environment markers make
77
87
  // them safe to reap globally on every real restart.
@@ -84,7 +94,7 @@ export async function cmdRestartMonitor() {
84
94
  }
85
95
  }
86
96
  // restart-pending.json 只留给新主进程发送发起会话内的“重启成功”回执。
87
- const pendingFile = path.join(p.dataDir, 'restart-pending.json');
97
+ const pendingFile = path.join(daemonControlDir(), 'restart-pending.json');
88
98
  // 等待所有活 main 进程退出(可能不止一个)
89
99
  const oldStatus = scanInstances();
90
100
  const aliveMains = oldStatus.mains.filter(m => m.alive);
@@ -168,9 +178,22 @@ export async function cmdRestartMonitor() {
168
178
  await notifyDaemonOwners(p, `⚠️ ${WEB_PACKAGE_NAME} 升级失败,使用当前版本继续\n${ecwebUpgrade.error ?? ''}`.trim(), log);
169
179
  break;
170
180
  }
181
+ // source=ecweb 的 Service Proxy 只在 daemon 启动后的短窗口内发现实例。
182
+ // restart-monitor 直接 spawn daemon,必须在此先启动独立的 ec-web,不能绕过
183
+ // cmdStart 的 ECWeb 生命周期逻辑。
184
+ const ecwebStartedBeforeDaemon = serviceProxyNeedsEcweb(loadDaemonConfig())
185
+ ? await startEcwebIfEnabled(p)
186
+ : false;
187
+ if (ecwebStartedBeforeDaemon)
188
+ log('ECWeb started before daemon');
171
189
  // 启动并检测 ready signal
172
190
  let started = await spawnAndWaitReady(p, log, READY_TIMEOUT);
173
191
  if (started) {
192
+ // 没有 Service Proxy 时可以等 daemon ready 后再启动;有代理时已在上方启动,
193
+ // 避免重新拉起造成实例记录和反向隧道短暂失配。
194
+ if (!ecwebStartedBeforeDaemon && await startEcwebIfEnabled(p)) {
195
+ log('ECWeb started after daemon');
196
+ }
174
197
  log('✓ Service restarted successfully');
175
198
  archiveSelfHealLog(p, log);
176
199
  // 发起会话内的“重启成功”通知由新进程自行发送,此处只负责失败/自愈告警。
@@ -110,7 +110,7 @@ Create 参数模式支持:
110
110
  --tz <时区> (仅 cron)
111
111
  --target-channel <channelKey> --target-channel-id <ID> (AUN 可简写为 aun)
112
112
  --target-session <main|thread> [--target-thread-id <threadId>]
113
- --trigger-thread <per-run|by-trigger>
113
+ --trigger-thread <by-run|by-trigger> (兼容旧值 per-run)
114
114
  --name <名称>
115
115
 
116
116
  通用输出参数:
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { resolvePaths } from '../paths.js';
4
- import { decodeDirSegment, readAllJsonlLines } from '../core/session/session-fs-store.js';
4
+ import { chatDirPath, readAllJsonlLines, scanChatDirs } from '../core/session/session-fs-store.js';
5
5
  import { isTransientProtocolMessage } from '../core/message/message-log.js';
6
6
  // ==================== ANSI ====================
7
7
  const isTTY = !!process.stdout.isTTY;
@@ -135,36 +135,26 @@ function shortAid(aid) {
135
135
  // ==================== Data Layer ====================
136
136
  export function getSessionsAunDir() {
137
137
  const p = resolvePaths();
138
- return path.join(p.sessionsDir, 'aun');
138
+ return p.sessionsDir;
139
139
  }
140
140
  export function listLocalAids(aunDir) {
141
- try {
142
- return fs.readdirSync(aunDir, { withFileTypes: true })
143
- .filter(e => e.isDirectory())
144
- .map(e => decodeDirSegment(e.name));
145
- }
146
- catch {
147
- return [];
148
- }
141
+ return [...new Set(scanChatDirs(aunDir)
142
+ .filter(chat => chat.channelType === 'aun')
143
+ .map(chat => chat.selfAID))].sort();
149
144
  }
150
145
  export function listPeers(aunDir, localAid) {
151
- const aidDir = path.join(aunDir, encodeSegment(localAid));
152
- try {
153
- return fs.readdirSync(aidDir, { withFileTypes: true })
154
- .filter(e => e.isDirectory() && !e.name.startsWith('_'))
155
- .map(e => decodeDirSegment(e.name));
156
- }
157
- catch {
158
- return [];
159
- }
146
+ return scanChatDirs(aunDir)
147
+ .filter(chat => chat.channelType === 'aun' && chat.selfAID === localAid)
148
+ .map(chat => chat.channelId)
149
+ .sort();
160
150
  }
161
151
  export function readMessages(aunDir, localAid, peerId) {
162
- const msgPath = path.join(aunDir, encodeSegment(localAid), encodeSegment(peerId), 'messages.jsonl');
152
+ const msgPath = path.join(chatDirPath(aunDir, 'aun', peerId, localAid), 'messages.jsonl');
163
153
  return readAllJsonlLines(msgPath)
164
154
  .filter(m => !isTransientProtocolMessage(m));
165
155
  }
166
156
  function readPeerName(aunDir, localAid, peerId) {
167
- const activePath = path.join(aunDir, encodeSegment(localAid), encodeSegment(peerId), 'active.json');
157
+ const activePath = path.join(chatDirPath(aunDir, 'aun', peerId, localAid), 'active.json');
168
158
  try {
169
159
  const data = JSON.parse(fs.readFileSync(activePath, 'utf-8'));
170
160
  return data?.metadata?.peerName || null;
@@ -173,9 +163,6 @@ function readPeerName(aunDir, localAid, peerId) {
173
163
  return null;
174
164
  }
175
165
  }
176
- function encodeSegment(s) {
177
- return s.replace(/[/%\\:*?"<>|]/g, ch => '%' + ch.charCodeAt(0).toString(16).toUpperCase().padStart(2, '0'));
178
- }
179
166
  export function loadAidInfo(aunDir, aid) {
180
167
  const peers = listPeers(aunDir, aid);
181
168
  let totalIn = 0, totalOut = 0;
@@ -457,7 +444,7 @@ export async function cmdWatchMsg() {
457
444
  watcher.close();
458
445
  watcher = null;
459
446
  }
460
- const aidDir = path.join(aunDir, encodeSegment(aid));
447
+ const aidDir = path.dirname(path.dirname(chatDirPath(aunDir, 'aun', '__watch__', aid)));
461
448
  try {
462
449
  watcher = fs.watch(aidDir, { recursive: true }, (_, filename) => {
463
450
  if (filename && filename.endsWith('messages.jsonl')) {
@@ -40,7 +40,9 @@ export function getManagementCommandPermissions(role) {
40
40
  'config.unset': { allow: true, scopes: ['agent', 'relation'] },
41
41
  'role.policy.write': { allow: true, dangerous: true, scopes: ['agent'], constraints: { requireAgentAdmin: true } },
42
42
  '*': { allow: true },
43
- 'agent.reload': { allow: true, dangerous: true, scopes: ['agent'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
43
+ // Reload is valid from an Agent's own channel and the daemon control plane.
44
+ // The control-channel gate still requires a daemon owner for cross-Agent reloads.
45
+ 'agent.reload': { allow: true, dangerous: true, scopes: ['agent', 'control'], constraints: { targetCurrentAgentOnly: true, requireAgentAdmin: true } },
44
46
  'dangerous:*': { allow: true, dangerous: true, constraints: { requireDaemonOwner: true } },
45
47
  };
46
48
  }
@@ -25,7 +25,7 @@ const BASEAGENT_BEHAVIOR_FIELDS = new Set([
25
25
  'mode',
26
26
  'useVertex',
27
27
  ]);
28
- const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'hermes']);
28
+ const SUPPORTED_BASEAGENTS = new Set(['claude', 'codex', 'gemini', 'ecagent', 'hermes']);
29
29
  const SENSITIVE_TOP_FIELDS = new Set([
30
30
  '$schema_version',
31
31
  'aid',
@@ -15,16 +15,17 @@ import fs from 'fs';
15
15
  import path from 'path';
16
16
  import { resolvePaths } from '../paths.js';
17
17
  import { saveDefaultsSafe, saveAgent } from '../config-store.js';
18
- import { resolveAnthropicConfig, resolveOpenaiConfig } from '../agents/baseagent.js';
18
+ import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../agents/baseagent.js';
19
19
  import { resolvePriceRow } from '../stats/billing.js';
20
20
  import { ipcQuery } from '../ipc.js';
21
21
  import { logger } from '../utils/logger.js';
22
22
  /** 已知 baseagent 类型(网关可管理范围)。 */
23
- const GATEWAY_TYPES = ['claude', 'codex', 'gemini'];
23
+ const GATEWAY_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
24
24
  const DISPLAY_NAMES = {
25
25
  claude: 'Claude',
26
26
  codex: 'Codex (OpenAI)',
27
27
  gemini: 'Gemini',
28
+ ecagent: 'ecagent (EvolCore)',
28
29
  };
29
30
  // ── 掩码 ──
30
31
  const ENV_PREFIX = '$ENV:';
@@ -362,7 +363,7 @@ function buildPatch(type, patch) {
362
363
  else
363
364
  return { error: 'apiKey 仅接受 "$ENV:NAME" 环境变量引用,不接受明文' };
364
365
  }
365
- if (type === 'claude' || type === 'codex') {
366
+ if (type === 'claude' || type === 'codex' || type === 'ecagent') {
366
367
  if (patch.effort !== undefined)
367
368
  f.effort = String(patch.effort).trim() || undefined;
368
369
  }
@@ -383,6 +384,16 @@ function buildPatch(type, patch) {
383
384
  if (patch.location !== undefined)
384
385
  f.location = String(patch.location).trim() || undefined;
385
386
  }
387
+ if (type === 'ecagent') {
388
+ for (const key of ['timeoutMs', 'maxRetries', 'maxRetryDelayMs']) {
389
+ if (patch[key] !== undefined) {
390
+ const value = Number(patch[key]);
391
+ if (!Number.isFinite(value) || value < 0)
392
+ return { error: `${key} 必须是非负数字` };
393
+ f[key] = value;
394
+ }
395
+ }
396
+ }
386
397
  return { fields: f };
387
398
  }
388
399
  /** 触发 reload:defaults 改动影响全部 agent(resync),单 agent 改动只 reload 它。 */
@@ -485,6 +496,10 @@ function resolveGatewayCreds(scope, type) {
485
496
  const r = resolveAnthropicConfig(synth, expanded);
486
497
  return { baseUrl: r.baseUrl, apiKey: r.apiKey };
487
498
  }
499
+ else if (type === 'ecagent') {
500
+ const r = resolveEcagentConfig(synth, expanded);
501
+ return { baseUrl: r.baseUrl, apiKey: r.apiKey };
502
+ }
488
503
  return { error: 'gemini 暂不支持 HTTP 探测(CLI 后端)', code: 'NOT_SUPPORTED' };
489
504
  }
490
505
  catch (e) {
@@ -507,7 +522,9 @@ export async function gatewayTest(args) {
507
522
  try {
508
523
  const controller = new AbortController();
509
524
  const timer = setTimeout(() => controller.abort(), 5000);
510
- const resp = await fetch(`${baseUrl.replace(/\/+$/, '')}/v1/models`, {
525
+ const base = baseUrl.replace(/\/+$/, '');
526
+ const modelsUrl = base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`;
527
+ const resp = await fetch(modelsUrl, {
511
528
  headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
512
529
  signal: controller.signal,
513
530
  });
@@ -110,6 +110,16 @@ function resolveFieldCommand(argv, subcommand, options = {}) {
110
110
  reason: 'Agent role policies are managed through the role policy editor',
111
111
  };
112
112
  }
113
+ const canContainFallbackModels = field === 'baseagents'
114
+ || /^baseagents\.[^.]+$/.test(field)
115
+ || /^baseagents\.[^.]+\.fallbackModels(?:\.|$)/.test(field);
116
+ if (subcommand !== 'get' && canContainFallbackModels) {
117
+ return {
118
+ ok: false,
119
+ code: 'DEFAULTS_FILE_ONLY',
120
+ reason: 'fallbackModels is configured only by editing agents/defaults.json',
121
+ };
122
+ }
113
123
  const fieldRule = resolveConfigFieldRule(field);
114
124
  if (subcommand !== 'get' && fieldRule.class === 'safe-readonly-object') {
115
125
  return {
@@ -803,10 +803,10 @@ export class CommandHandler {
803
803
  };
804
804
  if (s.agentSessionId) {
805
805
  item.agentSessionId = s.agentSessionId;
806
- const fileInfo = this.sessionManager.getSessionFileInfo(s.projectPath, s.agentSessionId, s.baseagent);
806
+ const fileInfo = this.sessionManager.getSessionFileInfo(s.projectPath, s.agentSessionId, s.baseagent, s.selfAID);
807
807
  if (fileInfo.turns)
808
808
  item.turns = fileInfo.turns;
809
- const firstMsg = this.sessionManager.readSessionFirstMessage(s.projectPath, s.agentSessionId, s.baseagent);
809
+ const firstMsg = this.sessionManager.readSessionFirstMessage(s.projectPath, s.agentSessionId, s.baseagent, s.selfAID);
810
810
  if (firstMsg)
811
811
  item.preview = firstMsg.length > 80 ? firstMsg.slice(0, 80) + '...' : firstMsg;
812
812
  }
@@ -4,7 +4,7 @@ import { modelMatches } from '../model/model-catalog.js';
4
4
  import { constrainResolvedModelForRole, filterModelsForRole, validateModelSelectionForRole } from '../model/model-permission.js';
5
5
  import { hasModelSwitcher } from '../../agents/runner-types.js';
6
6
  import { getCodexEfforts } from '../../agents/codex-runner.js';
7
- import { resolvePaths, getPackageRoot } from '../../paths.js';
7
+ import { resolvePaths, getPackageRoot, daemonControlDir } from '../../paths.js';
8
8
  import { buildEnvelope } from '../message/message-utils.js';
9
9
  import path from 'path';
10
10
  import fs from 'fs';
@@ -1038,6 +1038,8 @@ function buildRoleMenuContext(owner, channel, subject) {
1038
1038
  return {
1039
1039
  self,
1040
1040
  actorRole: subject.role,
1041
+ actorRoleSource: subject.roleSource,
1042
+ actorAllowAccess: subject.allowAccess,
1041
1043
  actorAid: subject.actorId,
1042
1044
  chatType: subject.chatType,
1043
1045
  aunAvailable,
@@ -1537,10 +1539,10 @@ export async function getSubMenuItems(cmd, channel, channelId, userId, args, ove
1537
1539
  };
1538
1540
  if (s.agentSessionId) {
1539
1541
  item.agentSessionId = s.agentSessionId;
1540
- const fileInfo = this.sessionManager.getSessionFileInfo(s.projectPath, s.agentSessionId, s.baseagent);
1542
+ const fileInfo = this.sessionManager.getSessionFileInfo(s.projectPath, s.agentSessionId, s.baseagent, s.selfAID);
1541
1543
  if (fileInfo.turns)
1542
1544
  item.turns = fileInfo.turns;
1543
- const firstMsg = this.sessionManager.readSessionFirstMessage(s.projectPath, s.agentSessionId, s.baseagent);
1545
+ const firstMsg = this.sessionManager.readSessionFirstMessage(s.projectPath, s.agentSessionId, s.baseagent, s.selfAID);
1544
1546
  if (firstMsg)
1545
1547
  item.preview = firstMsg.length > 80 ? firstMsg.slice(0, 80) + '…' : firstMsg;
1546
1548
  }
@@ -1903,7 +1905,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1903
1905
  }
1904
1906
  let turns = 0;
1905
1907
  if (session.agentSessionId) {
1906
- const fileInfo = this.sessionManager.getSessionFileInfo(session.projectPath, session.agentSessionId, session.baseagent);
1908
+ const fileInfo = this.sessionManager.getSessionFileInfo(session.projectPath, session.agentSessionId, session.baseagent, session.selfAID);
1907
1909
  turns = fileInfo.turns;
1908
1910
  }
1909
1911
  const data = {
@@ -1950,7 +1952,7 @@ export async function execMenuQuery(cmd, channel, channelId, userId, args, expli
1950
1952
  }
1951
1953
  let turns = 0;
1952
1954
  if (topic.agentSessionId) {
1953
- turns = this.sessionManager.getSessionFileInfo(topic.projectPath, topic.agentSessionId, topic.baseagent).turns;
1955
+ turns = this.sessionManager.getSessionFileInfo(topic.projectPath, topic.agentSessionId, topic.baseagent, topic.selfAID).turns;
1954
1956
  }
1955
1957
  const data = {
1956
1958
  threadId: topic.threadId,
@@ -2878,7 +2880,7 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
2878
2880
  return { error: `无效 targetSession: ${targetSession}`, code: 'INVALID_ARGS' };
2879
2881
  }
2880
2882
  const triggerThread = args.triggerThread;
2881
- if (triggerThread !== undefined && triggerThread !== 'per_run' && triggerThread !== 'by_trigger') {
2883
+ if (triggerThread !== undefined && triggerThread !== 'per_run' && triggerThread !== 'by_run' && triggerThread !== 'by_trigger') {
2882
2884
  return { error: `无效 triggerThread: ${triggerThread}`, code: 'INVALID_ARGS' };
2883
2885
  }
2884
2886
  const parsed = {
@@ -3269,9 +3271,9 @@ export async function execMenuAction(cmd, action, args, channel, channelId, user
3269
3271
  const suppressRealRestart = shouldSuppressRealRestart();
3270
3272
  if (!suppressRealRestart) {
3271
3273
  const restartInfo = { channel, channelId, timestamp: Date.now() };
3272
- const dataDir = resolvePaths().dataDir;
3273
- fs.mkdirSync(dataDir, { recursive: true });
3274
- fs.writeFileSync(path.join(dataDir, 'restart-pending.json'), JSON.stringify(restartInfo));
3274
+ const controlDir = daemonControlDir();
3275
+ fs.mkdirSync(controlDir, { recursive: true });
3276
+ fs.writeFileSync(path.join(controlDir, 'restart-pending.json'), JSON.stringify(restartInfo));
3275
3277
  const { spawn } = await import('child_process');
3276
3278
  spawn('node', [path.join(getPackageRoot(), 'dist', 'cli', 'index.js'), 'restart-monitor'], {
3277
3279
  detached: true,
@@ -785,6 +785,7 @@ async function targetSnapshot(context, args, policyRevision) {
785
785
  target,
786
786
  relationRevision: relation?.revision ?? null,
787
787
  ...targetRoleView(self, target, relation?.config ?? null),
788
+ ...(target.kind === 'group' ? { requesterRole: requesterRoleResult(context) } : {}),
788
789
  relationConfig: relationConfigView(context, target, relation?.config ?? null),
789
790
  };
790
791
  }
@@ -1157,6 +1158,16 @@ function relationConfigView(context, target, config) {
1157
1158
  function roleResult(roleId, source, self) {
1158
1159
  return { roleId, source: roleId ? source : 'none', allowAccess: roleId ? checkRoleAccess(roleId, self) : false };
1159
1160
  }
1161
+ function requesterRoleResult(context) {
1162
+ const roleId = isManagementRole(context.actorRole) ? context.actorRole : null;
1163
+ return {
1164
+ roleId,
1165
+ source: roleId ? context.actorRoleSource ?? 'authenticated' : 'none',
1166
+ allowAccess: roleId
1167
+ ? context.actorAllowAccess ?? checkRoleAccess(roleId, context.self)
1168
+ : false,
1169
+ };
1170
+ }
1160
1171
  function readAgent(self) {
1161
1172
  const agent = read(ConfigTarget.Agent, { self }, { cache: false });
1162
1173
  if (!agent)