evolcore 0.0.23 → 0.0.25

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 (245) hide show
  1. package/CHANGELOG.md +583 -516
  2. package/LICENSE +21 -21
  3. package/README.md +455 -453
  4. package/bin/codex-managed-acl.ps1 +160 -155
  5. package/bin/codex-managed-hook.mjs +209 -92
  6. package/bin/ec-safe-output.js +327 -245
  7. package/bin/ec.js +29 -29
  8. package/bin/install-codex-managed-hooks.mjs +555 -544
  9. package/dist/agents/baseagent-availability.js +4 -4
  10. package/dist/agents/baseagent.js +29 -1
  11. package/dist/agents/claude-runner.js +152 -14
  12. package/dist/agents/codex/app-server-client.js +18 -8
  13. package/dist/agents/codex/catalog.js +1 -1
  14. package/dist/agents/codex/diagnostics.js +215 -0
  15. package/dist/agents/codex-runner.js +445 -63
  16. package/dist/agents/ecagent-runner.js +31 -16
  17. package/dist/agents/gemini-runner.js +82 -4
  18. package/dist/agents/runner-types.js +1 -1
  19. package/dist/aun/aid/agentmd.js +56 -4
  20. package/dist/aun/aid/index.js +1 -0
  21. package/dist/aun/aid/managed-operation.js +80 -6
  22. package/dist/aun/aid/store.js +41 -0
  23. package/dist/aun/msg/managed-operation.js +6 -1
  24. package/dist/aun/outbox.js +56 -18
  25. package/dist/aun/rpc/client.js +4 -5
  26. package/dist/aun/storage/client.js +4 -1
  27. package/dist/aun/storage/index.js +1 -1
  28. package/dist/channels/aun.js +147 -76
  29. package/dist/channels/daemon.js +7 -1
  30. package/dist/channels/dingtalk.js +1 -0
  31. package/dist/channels/feishu.js +10 -4
  32. package/dist/channels/wecom.js +2 -1
  33. package/dist/cli/agent-command.js +214 -89
  34. package/dist/cli/agent-create.js +576 -0
  35. package/dist/cli/agent.js +129 -676
  36. package/dist/cli/aun-commands.js +419 -313
  37. package/dist/cli/bench.js +20 -20
  38. package/dist/cli/config.js +20 -20
  39. package/dist/cli/contact.js +10 -10
  40. package/dist/cli/ctl-command.js +22 -20
  41. package/dist/cli/daemon-commands.js +163 -32
  42. package/dist/cli/fs-command.js +67 -67
  43. package/dist/cli/handoff-command.js +7 -7
  44. package/dist/cli/index.js +79 -79
  45. package/dist/cli/link-rules.js +21 -21
  46. package/dist/cli/managed-command-guard.js +3 -0
  47. package/dist/cli/menu-client.js +11 -11
  48. package/dist/cli/net-check.js +22 -22
  49. package/dist/cli/queue-command.js +29 -29
  50. package/dist/cli/response.js +30 -30
  51. package/dist/cli/restart-monitor.js +462 -38
  52. package/dist/cli/stats.js +161 -51
  53. package/dist/cli/trigger-command.js +56 -56
  54. package/dist/config/builtin-roles.js +3 -0
  55. package/dist/config/config-manager.js +15 -0
  56. package/dist/config/config-operation-service.js +20 -3
  57. package/dist/config/resolved-config-op.js +39 -1
  58. package/dist/core/auth/auth-gateway.js +3 -2
  59. package/dist/core/auth/authorization-audit.js +72 -19
  60. package/dist/core/auth/operation-authorizer.js +125 -7
  61. package/dist/core/auth/operation-catalog.js +26 -2
  62. package/dist/core/auth/remote-access-store.js +3 -2
  63. package/dist/core/baseagent-loader.js +4 -4
  64. package/dist/core/bootstrap-messages.js +39 -18
  65. package/dist/core/bootstrap-service.js +32 -26
  66. package/dist/core/capability/capability-manager.js +8 -4
  67. package/dist/core/capability/providers/codex-capability-provider.js +1 -1
  68. package/dist/core/command/agent-control.js +60 -26
  69. package/dist/core/command/cli-intent-parser.js +18 -0
  70. package/dist/core/command/command-handler.js +173 -36
  71. package/dist/core/command/menu-catalog.js +4 -1
  72. package/dist/core/command/menu-handler.js +315 -75
  73. package/dist/core/command/menu-protocol.js +7 -2
  74. package/dist/core/command/slash-gate.js +1 -3
  75. package/dist/core/command/slash-handler.js +166 -57
  76. package/dist/core/data-migration.js +5 -15
  77. package/dist/core/event-catalog.js +8 -0
  78. package/dist/core/evolagent.js +3 -3
  79. package/dist/core/handoff/runtime.js +3 -1
  80. package/dist/core/handoff/store.js +7 -1
  81. package/dist/core/message/im-renderer.js +13 -6
  82. package/dist/core/message/message-bridge.js +5 -36
  83. package/dist/core/message/message-utils.js +6 -3
  84. package/dist/core/message/response-engine.js +85 -27
  85. package/dist/core/model/catalog-service.js +3 -1
  86. package/dist/core/model/model-catalog.js +10 -0
  87. package/dist/core/model/model-permission.js +4 -1
  88. package/dist/core/permission/approval-gateway.js +169 -33
  89. package/dist/core/permission/tool-policy.js +56 -10
  90. package/dist/core/session/adapters/codex-session-file-adapter.js +71 -3
  91. package/dist/core/session/session-manager.js +49 -13
  92. package/dist/core/session/session-renew.js +1 -0
  93. package/dist/index.js +327 -134
  94. package/dist/ipc.js +68 -21
  95. package/dist/paths.js +23 -1
  96. package/dist/stats/billing.js +18 -4
  97. package/dist/stats/db.js +159 -159
  98. package/dist/stats/eck-vars.js +9 -9
  99. package/dist/stats/query.js +139 -139
  100. package/dist/stats/role-budget.js +6 -6
  101. package/dist/stats/writer.js +51 -51
  102. package/dist/utils/atomic-write.js +26 -2
  103. package/dist/utils/avatar-upload.js +282 -23
  104. package/dist/utils/cross-platform.js +436 -1
  105. package/dist/utils/error-dict.json +160 -160
  106. package/dist/utils/error-utils.js +11 -4
  107. package/dist/utils/macos-autostart.js +22 -22
  108. package/dist/utils/model-prices.jsonl +23 -20
  109. package/dist/utils/npm-ops.js +24 -1
  110. package/dist/utils/welcome.js +22 -22
  111. package/dist/utils/windows-autostart.js +1 -1
  112. package/ecagent/LICENSE +21 -21
  113. package/ecagent/NOTICE +11 -11
  114. package/ecagent/README.md +61 -61
  115. package/ecagent/dist/harness/compaction/branch-summarization.js +30 -30
  116. package/ecagent/dist/harness/compaction/compaction.js +83 -83
  117. package/ecagent/dist/harness/messages.js +7 -7
  118. package/kits/docs/GUIDE.md +20 -20
  119. package/kits/docs/INDEX.md +66 -66
  120. package/kits/docs/aun/CHEATSHEET.md +19 -19
  121. package/kits/docs/aun/SYNC_PROTOCOL.md +15 -15
  122. package/kits/docs/channels/aun.md +56 -56
  123. package/kits/docs/channels/feishu.md +56 -56
  124. package/kits/docs/context-assembly.md +365 -364
  125. package/kits/docs/eck_templates/GUIDE.template.md +22 -22
  126. package/kits/docs/eck_templates/INDEX.template.md +28 -28
  127. package/kits/docs/eck_templates/path-registry.template.md +33 -33
  128. package/kits/docs/eck_templates/runtime.template.md +19 -19
  129. package/kits/docs/evolcore/INDEX.md +71 -69
  130. package/kits/docs/evolcore/agent-create.md +170 -156
  131. package/kits/docs/evolcore/agent.md +83 -73
  132. package/kits/docs/evolcore/aid.md +64 -62
  133. package/kits/docs/evolcore/codex-windows-sandbox.md +146 -0
  134. package/kits/docs/evolcore/config.md +195 -195
  135. package/kits/docs/evolcore/contact.md +68 -68
  136. package/kits/docs/evolcore/ctl.md +46 -46
  137. package/kits/docs/evolcore/event.md +216 -216
  138. package/kits/docs/evolcore/fs-architecture.md +1215 -1215
  139. package/kits/docs/evolcore/fs.md +110 -110
  140. package/kits/docs/evolcore/group-collaboration.md +243 -243
  141. package/kits/docs/evolcore/group-fs.md +17 -17
  142. package/kits/docs/evolcore/group-rules.md +251 -251
  143. package/kits/docs/evolcore/group.md +152 -152
  144. package/kits/docs/evolcore/menu-client.md +34 -34
  145. package/kits/docs/evolcore/model.md +50 -50
  146. package/kits/docs/evolcore/msg.md +165 -165
  147. package/kits/docs/evolcore/response.md +75 -75
  148. package/kits/docs/evolcore/rpc.md +37 -37
  149. package/kits/docs/evolcore/self-summary.md +29 -29
  150. package/kits/docs/evolcore/stats.md +83 -83
  151. package/kits/docs/evolcore/storage.md +50 -50
  152. package/kits/docs/evolcore/trigger.md +575 -575
  153. package/kits/docs/identity/AID_PROFILE_SPEC.md +26 -26
  154. package/kits/docs/identity/PATH_OPS.md +16 -16
  155. package/kits/docs/identity/ROLE_DETAIL.md +25 -25
  156. package/kits/docs/identity/identity-tools.md +26 -26
  157. package/kits/docs/path-registry.md +62 -62
  158. package/kits/docs/prompt-loading-architecture.md +270 -269
  159. package/kits/docs/venues/aun-group.md +45 -45
  160. package/kits/docs/venues/aun-private.md +10 -10
  161. package/kits/docs/venues/client-desktop.md +10 -10
  162. package/kits/docs/venues/client-mobile.md +10 -10
  163. package/kits/docs/venues/feishu-group.md +13 -13
  164. package/kits/docs/venues/feishu-private.md +9 -9
  165. package/kits/docs/venues/group.md +25 -25
  166. package/kits/docs/venues/private.md +10 -10
  167. package/kits/eck_manifest.auxiliary.json +43 -43
  168. package/kits/eck_manifest.json +195 -183
  169. package/kits/eck_message_manifest.json +74 -74
  170. package/kits/migrations/README-role-config-v4.md +32 -32
  171. package/kits/migrations/migrate-contact-book-v2.mjs +748 -748
  172. package/kits/migrations/migrate-role-config-v4.mjs +614 -614
  173. package/kits/migrations/migrate-role-config-v5.mjs +365 -365
  174. package/kits/migrations/rename-config-file.mjs +99 -99
  175. package/kits/rules/01-overview.md +162 -162
  176. package/kits/rules/02-navigation.md +96 -96
  177. package/kits/rules/03-identity.md +39 -37
  178. package/kits/rules/04-relation.md +75 -75
  179. package/kits/rules/05-venue.md +60 -60
  180. package/kits/rules/06-channel.md +63 -63
  181. package/kits/schemas/_meta.json +53 -53
  182. package/kits/schemas/agent-config.schema.1.json +177 -177
  183. package/kits/schemas/agent-config.schema.10.json +395 -395
  184. package/kits/schemas/agent-config.schema.11.json +421 -421
  185. package/kits/schemas/agent-config.schema.12.json +427 -427
  186. package/kits/schemas/agent-config.schema.2.json +239 -239
  187. package/kits/schemas/agent-config.schema.3.json +119 -119
  188. package/kits/schemas/agent-config.schema.4.json +208 -208
  189. package/kits/schemas/agent-config.schema.5.json +326 -326
  190. package/kits/schemas/agent-config.schema.6.json +322 -322
  191. package/kits/schemas/agent-config.schema.7.json +303 -303
  192. package/kits/schemas/agent-config.schema.8.json +304 -304
  193. package/kits/schemas/agent-config.schema.9.json +364 -364
  194. package/kits/schemas/contact-book.schema.1.json +36 -36
  195. package/kits/schemas/contact-book.schema.2.json +43 -43
  196. package/kits/schemas/contact-book.schema.3.json +67 -67
  197. package/kits/schemas/daemon.schema.1.json +91 -91
  198. package/kits/schemas/daemon.schema.2.json +101 -101
  199. package/kits/schemas/daemon.schema.3.json +123 -123
  200. package/kits/schemas/daemon.schema.4.json +136 -136
  201. package/kits/schemas/daemon.schema.5.json +135 -135
  202. package/kits/schemas/daemon.schema.6.json +131 -131
  203. package/kits/schemas/defaults.schema.1.json +81 -81
  204. package/kits/schemas/defaults.schema.2.json +85 -85
  205. package/kits/schemas/defaults.schema.3.json +73 -73
  206. package/kits/schemas/defaults.schema.4.json +107 -107
  207. package/kits/schemas/defaults.schema.5.json +119 -119
  208. package/kits/schemas/menu-exec-schema-commands.md +208 -208
  209. package/kits/schemas/migrations/README.md +30 -30
  210. package/kits/schemas/relation-config.schema.1.json +158 -158
  211. package/kits/schemas/relation-config.schema.2.json +73 -73
  212. package/kits/schemas/relation-config.schema.3.json +50 -50
  213. package/kits/schemas/relation-config.schema.4.json +47 -47
  214. package/kits/schemas/relation-config.schema.5.json +47 -47
  215. package/kits/schemas/relation-config.schema.6.json +46 -46
  216. package/kits/schemas/relation-config.schema.7.json +59 -59
  217. package/kits/schemas/relation-config.schema.8.json +103 -103
  218. package/kits/schemas/role-config.schema.1.json +201 -201
  219. package/kits/schemas/role-registry.schema.1.json +35 -35
  220. package/kits/schemas/single-session.schema.1.json +31 -31
  221. package/kits/schemas/single-session.schema.2.json +57 -57
  222. package/kits/schemas/single-session.schema.3.json +32 -32
  223. package/kits/templates/bootstrap-welcome.md +17 -17
  224. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -9
  225. package/kits/templates/message-fragments/handoff-request-to-target.md +19 -19
  226. package/kits/templates/message-fragments/handoff-response-to-origin.md +9 -9
  227. package/kits/templates/message-fragments/inject-default.md +2 -2
  228. package/kits/templates/message-fragments/item.md +2 -2
  229. package/kits/templates/roles/admin.json +96 -82
  230. package/kits/templates/roles/member.json +97 -96
  231. package/kits/templates/roles/owner.json +9 -9
  232. package/kits/templates/roles/visitor.json +86 -85
  233. package/kits/templates/system-fragments/baseagent.md +14 -14
  234. package/kits/templates/system-fragments/bootstrap.md +38 -29
  235. package/kits/templates/system-fragments/channel.md +64 -64
  236. package/kits/templates/system-fragments/commands.md +28 -28
  237. package/kits/templates/system-fragments/identity.md +13 -13
  238. package/kits/templates/system-fragments/relation.md +19 -19
  239. package/kits/templates/system-fragments/session.md +59 -59
  240. package/kits/templates/system-fragments/venue.md +31 -31
  241. package/package.json +98 -94
  242. package/AGENTS.md +0 -62
  243. package/dist/utils/codex-cli.js +0 -43
  244. package/skills/eclink/SKILL.md +0 -84
  245. package/skills/eclink/agents/openai.yaml +0 -4
@@ -1,5 +1,6 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import os from 'os';
3
4
  import { randomUUID } from 'crypto';
4
5
  import { logger } from '../../utils/logger.js';
5
6
  import { resolveRoot } from '../../paths.js';
@@ -41,6 +42,14 @@ export function expandManagedTempPath(value, options = {}) {
41
42
  }
42
43
  const WRITE_INTENT_RE = /(?:^|[\s;&|])(?:mkdir|mktemp|touch|tee|cp|mv|install|chmod|chown|ln|rm)(?:[\s;&|]|$)|(?:^|[\s])(?:\d>>?|>>?)\s*/i;
43
44
  const TEMP_TOKEN_RE = /(?:\$TMPDIR|\$\{TMPDIR\}|\/tmp(?:\/|$)|\/var\/tmp(?:\/|$)|\/dev\/shm(?:\/|$))[^\s'";&|<>)]*/g;
45
+ function windowsTempRoots(options) {
46
+ if (process.platform !== 'win32')
47
+ return [];
48
+ return [os.tmpdir(), options.env?.TEMP, options.env?.TMP, process.env.TEMP, process.env.TMP]
49
+ .filter((value) => typeof value === 'string' && path.isAbsolute(value))
50
+ .map(value => resolveProtectedCandidate(value))
51
+ .filter((value, index, values) => values.indexOf(value) === index);
52
+ }
44
53
  /**
45
54
  * Inspect shell write targets that look like temporary paths. Ordinary
46
55
  * project writes are left to the existing workspace/H-class policy; any
@@ -52,19 +61,32 @@ export function checkManagedTempShellCommand(command, options = {}) {
52
61
  const root = resolveManagedTempDir(options.env);
53
62
  const normalized = substituteTmpDir(command, root);
54
63
  const candidates = [...normalized.matchAll(TEMP_TOKEN_RE)].map(match => match[0].replace(/[.,:]+$/, ''));
55
- if (candidates.length === 0)
64
+ // Windows has no portable `/tmp` spelling. Treat absolute paths below the
65
+ // host temporary roots as temporary operands too, so a sibling session
66
+ // directory cannot bypass the managed TMPDIR check.
67
+ if (process.platform === 'win32') {
68
+ const roots = windowsTempRoots(options);
69
+ const windowsCandidates = normalized.match(/[A-Za-z]:[\\/][^\s'";&|<>)]*/g) ?? [];
70
+ for (const candidate of windowsCandidates) {
71
+ const canonical = resolveProtectedCandidate(candidate.replace(/[.,:]+$/, ''), options.cwd);
72
+ if (roots.some(root => isSameOrDescendant(canonical, root)))
73
+ candidates.push(candidate.replace(/[.,:]+$/, ''));
74
+ }
75
+ }
76
+ const uniqueCandidates = [...new Set(candidates)];
77
+ if (uniqueCandidates.length === 0)
56
78
  return { kind: 'none' };
57
79
  if (!root) {
58
- return { kind: 'deny', paths: candidates, reason: 'TMPDIR is unset or not an absolute managed path' };
80
+ return { kind: 'deny', paths: uniqueCandidates, reason: 'TMPDIR is unset or not an absolute managed path' };
59
81
  }
60
- const outside = candidates.filter(candidate => !isManagedTempPath(candidate, {
82
+ const outside = uniqueCandidates.filter(candidate => !isManagedTempPath(candidate, {
61
83
  ...options,
62
84
  env: { ...(options.env ?? process.env), TMPDIR: root },
63
85
  }));
64
86
  if (outside.length > 0) {
65
87
  return { kind: 'deny', paths: outside, reason: 'temporary write target is outside the session-managed TMPDIR' };
66
88
  }
67
- return { kind: 'allow', paths: candidates };
89
+ return { kind: 'allow', paths: uniqueCandidates };
68
90
  }
69
91
  /**
70
92
  * Replace only already-validated temporary path operands before a second
@@ -2161,10 +2183,11 @@ export function evaluateToolPreflight(toolName, input, context) {
2161
2183
  : command;
2162
2184
  const tempCheck = checkManagedTempShellCommand(commandForPolicy, managedTempOptions(context));
2163
2185
  if (tempCheck.kind === 'deny') {
2186
+ const currentTmpDir = resolveManagedTempDir(managedTempOptions(context).env);
2164
2187
  return {
2165
2188
  behavior: 'deny',
2166
2189
  input,
2167
- message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内:${tempCheck.reason}`,
2190
+ message: `🔒 临时文件必须位于当前会话的 $TMPDIR 内(当前值:${currentTmpDir ?? '<不可用>'}):${tempCheck.reason}`,
2168
2191
  policyCode: 'temporary_path_outside_tmpdir',
2169
2192
  };
2170
2193
  }
@@ -2215,14 +2238,20 @@ export function evaluateToolPreflight(toolName, input, context) {
2215
2238
  const ownAid = !!managedAidArgv
2216
2239
  && !!context.selfAid
2217
2240
  && managedAidArgv.at(-1)?.replace(/^@/, '') === context.selfAid.replace(/^@/, '');
2218
- const isSelfManagedAidRead = !!managedAidArgv
2241
+ const isManagedAidPublicRead = !!context.selfAid
2242
+ && !!managedAidArgv
2219
2243
  && managedAidArgv[0] === 'ec'
2220
2244
  && managedAidArgv[1] === 'aid'
2221
- && ownAid
2222
- && ((managedAidArgv.length === 4 && ['show', 'lookup'].includes(managedAidArgv[2]))
2245
+ && ((managedAidArgv.length === 4 && managedAidArgv[2] === 'lookup')
2223
2246
  || (managedAidArgv.length === 5
2224
2247
  && managedAidArgv[2] === 'agentmd'
2225
2248
  && managedAidArgv[3] === 'get'));
2249
+ const isSelfManagedAidShow = !!managedAidArgv
2250
+ && managedAidArgv.length === 4
2251
+ && managedAidArgv[0] === 'ec'
2252
+ && managedAidArgv[1] === 'aid'
2253
+ && managedAidArgv[2] === 'show'
2254
+ && ownAid;
2226
2255
  const isSelfAgentMdPublish = !!managedAidArgv
2227
2256
  && managedAidArgv.length === 5
2228
2257
  && managedAidArgv[0] === 'ec'
@@ -2230,16 +2259,33 @@ export function evaluateToolPreflight(toolName, input, context) {
2230
2259
  && managedAidArgv[2] === 'agentmd'
2231
2260
  && managedAidArgv[3] === 'put'
2232
2261
  && ownAid;
2262
+ const avatarFileIndex = managedAidArgv?.indexOf('--file') ?? -1;
2263
+ const avatarUrlIndex = managedAidArgv?.indexOf('--url') ?? -1;
2264
+ const avatarSourceIndex = avatarFileIndex >= 0 ? avatarFileIndex : avatarUrlIndex;
2265
+ const isSelfAvatarUpload = !!managedAidArgv
2266
+ && (avatarSourceIndex === 4)
2267
+ && managedAidArgv.length === 6
2268
+ && managedAidArgv[0] === 'ec'
2269
+ && managedAidArgv[1] === 'aid'
2270
+ && managedAidArgv[2] === 'avatar'
2271
+ && !!context.selfAid
2272
+ && managedAidArgv[3]?.replace(/^@/, '') === context.selfAid.replace(/^@/, '')
2273
+ && !!managedAidArgv[5]
2274
+ && !managedAidArgv[5].startsWith('-');
2233
2275
  const canPublishSelfAgentMd = isSelfAgentMdPublish
2234
2276
  && ['request', 'bypass'].includes(context.permissionMode ?? '');
2235
- const isAllowedManagedAidCommand = isSelfManagedAidRead || canPublishSelfAgentMd;
2277
+ const canUploadSelfAvatar = isSelfAvatarUpload
2278
+ && ['request', 'bypass'].includes(context.permissionMode ?? '');
2279
+ const isAllowedManagedAidCommand = isManagedAidPublicRead || isSelfManagedAidShow || canPublishSelfAgentMd || canUploadSelfAvatar;
2236
2280
  if (context.sessionId && /^\s*ec\s+aid(?:\s|$)/i.test(policyCommand) && !isAllowedManagedAidCommand) {
2237
2281
  return {
2238
2282
  behavior: 'deny',
2239
2283
  input,
2240
2284
  message: isSelfAgentMdPublish && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
2241
2285
  ? `🔒 ${context.permissionMode} 模式禁止发布 agent.md;请切换到 request 或 bypass 后再执行 \`ec aid agentmd put <当前 self AID>\`。`
2242
- : '🔒 EvolCore 托管会话仅允许针对当前 self AID 精确调用 `ec aid show`、`ec aid lookup` 或 `ec aid agentmd get`;`ec aid agentmd put` 还要求 request 或 bypass。可选后缀仅限 `--format json`。禁止枚举、切换或操作其他身份。',
2286
+ : isSelfAvatarUpload && (context.permissionMode === 'readonly' || context.permissionMode === 'auto')
2287
+ ? `🔒 ${context.permissionMode} 模式禁止上传头像;请切换到 request 或 bypass 后再执行 \`ec aid avatar <当前 self AID> (--file <path> 或 --url <https-url>)\`。`
2288
+ : '🔒 EvolCore 托管会话允许 `ec aid show <当前 self AID>`、`ec aid lookup <任意合法 AID>` 或 `ec aid agentmd get <任意合法 AID>`;`ec aid agentmd put` 及 `ec aid avatar` 还要求针对当前 self AID 且处于 request 或 bypass。可选后缀仅限 `--format json`。禁止列举本地身份、切换身份或对其它 AID 执行 show/put/avatar。',
2243
2289
  policyCode: 'managed_ec_aid_scope_forbidden',
2244
2290
  };
2245
2291
  }
@@ -32,15 +32,20 @@ function loadSqlite() {
32
32
  return sqliteModule;
33
33
  }
34
34
  export class CodexSessionFileAdapter {
35
+ codexHome;
35
36
  baseagent = 'codex';
36
37
  db = null;
37
38
  dbInitialized = false;
39
+ rolloutPathCache = new Map();
40
+ constructor(codexHome = path.join(os.homedir(), '.codex')) {
41
+ this.codexHome = codexHome;
42
+ }
38
43
  /**
39
44
  * 动态发现最新的 state_*.sqlite 文件
40
45
  * Codex 使用 sqlx 迁移,DB 文件名含版本号(state_5, state_6, ...)
41
46
  */
42
47
  resolveStateDbPath() {
43
- const codexHome = path.join(os.homedir(), '.codex');
48
+ const codexHome = this.codexHome;
44
49
  if (!fs.existsSync(codexHome))
45
50
  return null;
46
51
  try {
@@ -94,11 +99,71 @@ export class CodexSessionFileAdapter {
94
99
  // SDK 创建的 thread 不写 state_*.sqlite,但会持久化到 sessions 目录
95
100
  return !!this.findRolloutFile(agentSessionId);
96
101
  }
102
+ /**
103
+ * A row in state_*.sqlite is created before the first turn is necessarily
104
+ * recoverable. Require the rollout to contain both its own session metadata
105
+ * and the exact turn_context before EvolCore publishes a new binding.
106
+ */
107
+ checkTurnPersisted(_projectPath, agentSessionId, turnId, _agentAid) {
108
+ const normalizedSessionId = agentSessionId.trim();
109
+ const normalizedTurnId = turnId.trim();
110
+ if (!normalizedSessionId || !normalizedTurnId)
111
+ return false;
112
+ let rolloutPath = null;
113
+ const db = this.getDb();
114
+ if (db) {
115
+ try {
116
+ const row = db.prepare('SELECT rollout_path FROM threads WHERE id = ? AND archived = 0').get(normalizedSessionId);
117
+ rolloutPath = this.resolveRolloutPath(row?.rollout_path, normalizedSessionId);
118
+ }
119
+ catch (error) {
120
+ logger.warn('[CodexAdapter] checkTurnPersisted DB query failed:', error);
121
+ }
122
+ }
123
+ rolloutPath ??= this.findRolloutFile(normalizedSessionId);
124
+ if (!rolloutPath)
125
+ return false;
126
+ try {
127
+ let hasMatchingSession = false;
128
+ let hasMatchingTurn = false;
129
+ const lines = fs.readFileSync(rolloutPath, 'utf-8').split('\n');
130
+ for (const line of lines) {
131
+ if (!line.trim())
132
+ continue;
133
+ try {
134
+ const event = JSON.parse(line);
135
+ if (event?.type === 'session_meta') {
136
+ const persistedSessionId = event.payload?.session_id ?? event.payload?.id;
137
+ const version = event.payload?.version ?? event.payload?.cli_version;
138
+ const hasVersion = version !== undefined && version !== null && String(version).trim().length > 0;
139
+ if (persistedSessionId === normalizedSessionId && hasVersion)
140
+ hasMatchingSession = true;
141
+ }
142
+ else if (event?.type === 'turn_context' && event.payload?.turn_id === normalizedTurnId) {
143
+ hasMatchingTurn = true;
144
+ }
145
+ if (hasMatchingSession && hasMatchingTurn)
146
+ return true;
147
+ }
148
+ catch {
149
+ // The last JSONL line may still be in flight; a later poll retries it.
150
+ }
151
+ }
152
+ return false;
153
+ }
154
+ catch {
155
+ return false;
156
+ }
157
+ }
97
158
  /**
98
159
  * 在 ~/.codex/sessions/ 下递归查找含 agentSessionId 的 rollout JSONL 文件
99
160
  */
100
161
  findRolloutFile(agentSessionId) {
101
- const sessionsDir = path.join(os.homedir(), '.codex', 'sessions');
162
+ const cached = this.rolloutPathCache.get(agentSessionId);
163
+ if (cached && fs.existsSync(cached))
164
+ return cached;
165
+ this.rolloutPathCache.delete(agentSessionId);
166
+ const sessionsDir = path.join(this.codexHome, 'sessions');
102
167
  if (!fs.existsSync(sessionsDir))
103
168
  return null;
104
169
  try {
@@ -110,7 +175,9 @@ export class CodexSessionFileAdapter {
110
175
  return found;
111
176
  }
112
177
  else if (entry.name.endsWith('.jsonl') && entry.name.includes(agentSessionId)) {
113
- return path.join(dir, entry.name);
178
+ const rolloutPath = path.join(dir, entry.name);
179
+ this.rolloutPathCache.set(agentSessionId, rolloutPath);
180
+ return rolloutPath;
114
181
  }
115
182
  }
116
183
  return null;
@@ -251,6 +318,7 @@ export class CodexSessionFileAdapter {
251
318
  this.db = null;
252
319
  }
253
320
  this.dbInitialized = false;
321
+ this.rolloutPathCache.clear();
254
322
  }
255
323
  /**
256
324
  * 从 rollout JSONL 读取第一条或最后一条 user_message
@@ -557,13 +557,29 @@ export class SessionManager {
557
557
  }
558
558
  const wasBound = !!normalizeBackendSessionId(current.agentSessionId);
559
559
  const next = JSON.parse(JSON.stringify(current));
560
+ const previousBaseagent = next.baseagent;
561
+ if (input.baseagent !== undefined) {
562
+ const requested = input.baseagent.trim();
563
+ if (!requested) {
564
+ const error = new Error('backend rotation baseagent must be non-empty');
565
+ error.code = 'BASEAGENT_REQUIRED';
566
+ throw error;
567
+ }
568
+ next.baseagent = requested;
569
+ }
560
570
  next.agentSessionId = undefined;
561
571
  const nextMetadata = { ...(next.metadata ?? {}) };
562
572
  delete nextMetadata.resumeAt;
563
573
  delete nextMetadata.agentSessions;
564
574
  next.metadata = nextMetadata;
565
575
  this.persistConfirmedTopicBoundary(current, found, next);
566
- return { status: 'rotated', sessionId: input.sessionId, wasBound };
576
+ return {
577
+ status: 'rotated',
578
+ sessionId: input.sessionId,
579
+ wasBound,
580
+ previousBaseagent,
581
+ baseagent: next.baseagent,
582
+ };
567
583
  });
568
584
  }
569
585
  /**
@@ -830,7 +846,10 @@ export class SessionManager {
830
846
  const sessionMetadata = { ...(metadata || {}) };
831
847
  sessionMetadata.__sessionCreatedAt = Date.now();
832
848
  const newIdentity = identity ?? this.resolveIdentity(channel, userId, chatType, identityConversationId);
833
- const resolvedBaseagent = baseagent || 'claude';
849
+ const resolvedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
850
+ if (!resolvedBaseagent) {
851
+ throw new Error(`[SessionManager] getOrCreateSession: baseagent is empty for channel=${channel} channelId=${channelId}`);
852
+ }
834
853
  const session = {
835
854
  id: generateSessionId(),
836
855
  channel,
@@ -857,6 +876,7 @@ export class SessionManager {
857
876
  projectPath: defaultProjectPath,
858
877
  name: session.name,
859
878
  chatType: session.chatType,
879
+ baseagent: session.baseagent,
860
880
  timestamp: Date.now(),
861
881
  });
862
882
  return session;
@@ -868,9 +888,9 @@ export class SessionManager {
868
888
  if (!loaded)
869
889
  return;
870
890
  const { current } = loaded;
871
- if (current.threadId && updates.baseagent !== undefined) {
872
- const error = new Error('topic baseagent is immutable while backend rotation is managed');
873
- error.code = 'TOPIC_BASEAGENT_IMMUTABLE';
891
+ if (updates.baseagent !== undefined) {
892
+ const error = new Error('session baseagent is immutable; use explicit agent switch or backend rotation');
893
+ error.code = 'BASEAGENT_IMMUTABLE';
874
894
  throw error;
875
895
  }
876
896
  if (current.threadId && Object.prototype.hasOwnProperty.call(updates, 'agentSessionId')) {
@@ -878,8 +898,6 @@ export class SessionManager {
878
898
  error.code = 'TOPIC_BACKEND_BINDING_PROTECTED';
879
899
  throw error;
880
900
  }
881
- if (updates.baseagent !== undefined)
882
- current.baseagent = updates.baseagent;
883
901
  if (updates.chatType !== undefined)
884
902
  current.chatType = updates.chatType;
885
903
  if (updates.name !== undefined)
@@ -988,7 +1006,7 @@ export class SessionManager {
988
1006
  const mainActive = readJsonFile(path.join(chatDir, 'active.json'));
989
1007
  // 兼容旧字段名
990
1008
  const mainBaseagent = mainActive?.baseagent || mainActive?.agentType;
991
- const inheritedBaseagent = baseagent || mainBaseagent;
1009
+ const inheritedBaseagent = mainBaseagent || baseagent;
992
1010
  if (!inheritedBaseagent) {
993
1011
  throw new Error('[SessionManager] getOrCreateThreadSession: baseagent is empty');
994
1012
  }
@@ -1022,6 +1040,7 @@ export class SessionManager {
1022
1040
  channelId,
1023
1041
  projectPath,
1024
1042
  name: session.name,
1043
+ baseagent: session.baseagent,
1025
1044
  timestamp: Date.now(),
1026
1045
  });
1027
1046
  return session;
@@ -1072,6 +1091,7 @@ export class SessionManager {
1072
1091
  channelId,
1073
1092
  projectPath: newProjectPath,
1074
1093
  name: session.name,
1094
+ baseagent: session.baseagent,
1075
1095
  timestamp: Date.now(),
1076
1096
  });
1077
1097
  return session;
@@ -1180,6 +1200,7 @@ export class SessionManager {
1180
1200
  channelId,
1181
1201
  projectPath,
1182
1202
  name: session.name,
1203
+ baseagent: session.baseagent,
1183
1204
  timestamp: Date.now(),
1184
1205
  });
1185
1206
  return session;
@@ -1428,7 +1449,7 @@ export class SessionManager {
1428
1449
  }
1429
1450
  }
1430
1451
  }
1431
- async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata) {
1452
+ async createNewSession(channel, channelId, projectPath, name, baseagent, identityMetadata, eventMetadata) {
1432
1453
  const inheritedChatType = this.getActiveChatType(channel, channelId);
1433
1454
  // Derive selfAID and channelType from existing sessions
1434
1455
  const existingDir = this.findExistingChatDir(channel, channelId);
@@ -1480,6 +1501,8 @@ export class SessionManager {
1480
1501
  channelId,
1481
1502
  projectPath,
1482
1503
  name: session.name,
1504
+ baseagent: session.baseagent,
1505
+ ...eventMetadata,
1483
1506
  timestamp: Date.now(),
1484
1507
  });
1485
1508
  return session;
@@ -1527,6 +1550,7 @@ export class SessionManager {
1527
1550
  channelId: sourceSession.channelId,
1528
1551
  projectPath: sourceSession.projectPath,
1529
1552
  name: session.name,
1553
+ baseagent: session.baseagent,
1530
1554
  timestamp: Date.now(),
1531
1555
  });
1532
1556
  return session;
@@ -1602,6 +1626,7 @@ export class SessionManager {
1602
1626
  name: session.name,
1603
1627
  chatType: session.chatType,
1604
1628
  threadId: session.threadId,
1629
+ baseagent: session.baseagent,
1605
1630
  timestamp: now,
1606
1631
  });
1607
1632
  return session;
@@ -1624,7 +1649,7 @@ export class SessionManager {
1624
1649
  selfAID: sourceSession.selfAID,
1625
1650
  projectPath: sourceSession.projectPath,
1626
1651
  threadId: '',
1627
- baseagent: sourceSession.baseagent,
1652
+ baseagent: context?.baseagent || sourceSession.baseagent,
1628
1653
  sessionKey: formatSessionKey(sourceSession.channelType || sourceSession.channel, sourceSession.channelId, DEFAULT_THREAD_ID),
1629
1654
  chatType: sourceSession.chatType || 'private',
1630
1655
  metadata,
@@ -1642,6 +1667,7 @@ export class SessionManager {
1642
1667
  projectPath: session.projectPath,
1643
1668
  name: session.name,
1644
1669
  chatType: session.chatType,
1670
+ baseagent: session.baseagent,
1645
1671
  cause: 'renew',
1646
1672
  sourceSessionId: sourceSession.id,
1647
1673
  renewSource: context?.source,
@@ -1684,6 +1710,12 @@ export class SessionManager {
1684
1710
  return false;
1685
1711
  return adapter.checkExists(projectPath, agentSessionId, agentAid);
1686
1712
  }
1713
+ checkSessionTurnPersisted(projectPath, agentSessionId, baseagent, turnId, agentAid) {
1714
+ const adapter = this.getFileAdapter(baseagent);
1715
+ if (!adapter?.checkTurnPersisted)
1716
+ return false;
1717
+ return adapter.checkTurnPersisted(projectPath, agentSessionId, turnId, agentAid);
1718
+ }
1687
1719
  readSessionFirstMessage(projectPath, agentSessionId, baseagent, agentAid) {
1688
1720
  const adapter = this.getFileAdapter(baseagent);
1689
1721
  if (!adapter)
@@ -1725,10 +1757,13 @@ export class SessionManager {
1725
1757
  }
1726
1758
  return matched[0];
1727
1759
  }
1728
- async importCliSession(channel, channelId, projectPath, agentSessionId, baseagent = 'claude', agentAid) {
1760
+ async importCliSession(channel, channelId, projectPath, agentSessionId, baseagent, agentAid) {
1761
+ const resolvedBaseagent = typeof baseagent === 'string' ? baseagent.trim() : '';
1762
+ if (!resolvedBaseagent)
1763
+ throw new Error('[SessionManager] importCliSession: baseagent is required');
1729
1764
  const inheritedChatType = this.getActiveChatType(channel, channelId);
1730
1765
  const resolvedAgentAid = agentAid || this.deriveChannelIdentity(channel, channelId).selfAID;
1731
- const fileInfo = this.getSessionFileInfo(projectPath, agentSessionId, baseagent, resolvedAgentAid);
1766
+ const fileInfo = this.getSessionFileInfo(projectPath, agentSessionId, resolvedBaseagent, resolvedAgentAid);
1732
1767
  const name = fileInfo.title || `CLI会话-${new Date().toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })}`;
1733
1768
  // Derive selfAID and channelType from existing sessions
1734
1769
  const existingDir = this.findExistingChatDir(channel, channelId);
@@ -1750,7 +1785,7 @@ export class SessionManager {
1750
1785
  selfAID,
1751
1786
  projectPath,
1752
1787
  threadId: '',
1753
- baseagent,
1788
+ baseagent: resolvedBaseagent,
1754
1789
  sessionKey: formatSessionKey(channelType, channelId, DEFAULT_THREAD_ID),
1755
1790
  chatType: inheritedChatType,
1756
1791
  agentSessionId,
@@ -1767,6 +1802,7 @@ export class SessionManager {
1767
1802
  channelId,
1768
1803
  projectPath,
1769
1804
  name,
1805
+ baseagent: session.baseagent,
1770
1806
  timestamp: Date.now(),
1771
1807
  });
1772
1808
  return session;
@@ -191,6 +191,7 @@ export class SessionRenewService {
191
191
  ? await this.sessionManager.createRenewedSession(request.session, {
192
192
  source,
193
193
  causation: request.causation,
194
+ baseagent: request.currentBaseagent,
194
195
  })
195
196
  : request.session;
196
197
  selected.identity = request.session.identity;