evolcore 0.0.7 → 0.0.9

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 (230) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +38 -4
  3. package/dist/agents/baseagent.js +117 -24
  4. package/dist/agents/claude-runner.js +31 -2
  5. package/dist/agents/codex-app-server-client.js +37 -2
  6. package/dist/agents/codex-runner.js +11 -3
  7. package/dist/aun/aid/control-aid.js +40 -27
  8. package/dist/aun/aid/domain.js +23 -0
  9. package/dist/aun/msg/group.js +9 -9
  10. package/dist/aun/msg/p2p.js +11 -6
  11. package/dist/aun/msg/upload.js +43 -29
  12. package/dist/aun/outbox.js +48 -5
  13. package/dist/channels/aun.js +225 -71
  14. package/dist/cli/aun-commands.js +10 -7
  15. package/dist/cli/bench.js +4 -3
  16. package/dist/cli/daemon-commands.js +208 -68
  17. package/dist/cli/data-command.js +62 -35
  18. package/dist/cli/index.js +11 -1
  19. package/dist/cli/init-channel.js +1 -1
  20. package/dist/cli/init.js +112 -17
  21. package/dist/cli/model.js +5 -5
  22. package/dist/cli/restart-monitor.js +102 -22
  23. package/dist/config/builtin-role-templates.js +4 -3
  24. package/dist/config/config-field-policy.js +6 -2
  25. package/dist/config/config-manager.js +79 -11
  26. package/dist/config/gateway-config.js +80 -35
  27. package/dist/config/role-schema.js +18 -3
  28. package/dist/config/role-service.js +3 -3
  29. package/dist/config/role-store.js +4 -3
  30. package/dist/config-store.js +19 -3
  31. package/dist/core/auth/operation-authorizer.js +3 -1
  32. package/dist/core/baseagent-loader.js +5 -3
  33. package/dist/core/bootstrap-service.js +0 -12
  34. package/dist/core/capability/providers/codex-capability-provider.js +2 -2
  35. package/dist/core/channel-loader.js +15 -2
  36. package/dist/core/command/command-handler.js +1 -1
  37. package/dist/core/command/evol-menu-version-gate.js +3 -2
  38. package/dist/core/command/menu-handler.js +50 -28
  39. package/dist/core/command/menu-protocol.js +7 -4
  40. package/dist/core/command/slash-handler.js +4 -4
  41. package/dist/core/data-migration.js +517 -24
  42. package/dist/core/inference/text-inference.js +18 -5
  43. package/dist/core/message/message-bridge.js +17 -6
  44. package/dist/core/message/response-engine.js +22 -3
  45. package/dist/core/model/config-scope.js +3 -3
  46. package/dist/core/permission/tool-policy.js +18 -0
  47. package/dist/core/protected-paths.js +12 -1
  48. package/dist/index.js +794 -725
  49. package/dist/ipc.js +3 -1
  50. package/dist/product.js +1 -0
  51. package/dist/utils/autostart.js +27 -0
  52. package/dist/utils/codex-cli.js +39 -0
  53. package/dist/utils/cross-platform.js +147 -18
  54. package/dist/utils/ecweb-utils.js +15 -2
  55. package/dist/utils/instance-registry.js +43 -6
  56. package/dist/utils/macos-autostart.js +179 -0
  57. package/dist/utils/process-introspect.js +7 -3
  58. package/dist/utils/windows-autostart.js +130 -0
  59. package/ecagent/dist/agent-loop.d.ts +1 -1
  60. package/ecagent/dist/agent-loop.js +1 -1
  61. package/ecagent/dist/agent.d.ts +1 -1
  62. package/ecagent/dist/agent.js +1 -1
  63. package/ecagent/dist/harness/agent-harness.d.ts +1 -1
  64. package/ecagent/dist/harness/compaction/branch-summarization.d.ts +1 -1
  65. package/ecagent/dist/harness/compaction/compaction.d.ts +1 -1
  66. package/ecagent/dist/harness/compaction/utils.d.ts +1 -1
  67. package/ecagent/dist/harness/messages.d.ts +1 -1
  68. package/ecagent/dist/harness/session/session.d.ts +1 -1
  69. package/ecagent/dist/harness/types.d.ts +1 -1
  70. package/ecagent/dist/proxy.d.ts +1 -1
  71. package/ecagent/dist/proxy.js +1 -1
  72. package/ecagent/dist/types.d.ts +1 -1
  73. package/kits/migrations/migrate-role-config-v5.mjs +27 -2
  74. package/kits/rules/01-overview.md +3 -2
  75. package/kits/schemas/_meta.json +2 -1
  76. package/kits/schemas/agent-config.schema.10.json +19 -1
  77. package/kits/schemas/daemon.schema.4.json +136 -0
  78. package/kits/schemas/defaults.schema.4.json +15 -1
  79. package/kits/schemas/relation-config.schema.8.json +22 -1
  80. package/kits/schemas/role-config.schema.1.json +2 -2
  81. package/package.json +2 -4
  82. package/ecagent/dist/agent-loop.d.ts.map +0 -1
  83. package/ecagent/dist/agent-loop.js.map +0 -1
  84. package/ecagent/dist/agent.d.ts.map +0 -1
  85. package/ecagent/dist/agent.js.map +0 -1
  86. package/ecagent/dist/harness/agent-harness.d.ts.map +0 -1
  87. package/ecagent/dist/harness/agent-harness.js.map +0 -1
  88. package/ecagent/dist/harness/compaction/branch-summarization.d.ts.map +0 -1
  89. package/ecagent/dist/harness/compaction/branch-summarization.js.map +0 -1
  90. package/ecagent/dist/harness/compaction/compaction.d.ts.map +0 -1
  91. package/ecagent/dist/harness/compaction/compaction.js.map +0 -1
  92. package/ecagent/dist/harness/compaction/utils.d.ts.map +0 -1
  93. package/ecagent/dist/harness/compaction/utils.js.map +0 -1
  94. package/ecagent/dist/harness/env/nodejs.d.ts.map +0 -1
  95. package/ecagent/dist/harness/env/nodejs.js.map +0 -1
  96. package/ecagent/dist/harness/messages.d.ts.map +0 -1
  97. package/ecagent/dist/harness/messages.js.map +0 -1
  98. package/ecagent/dist/harness/prompt-templates.d.ts.map +0 -1
  99. package/ecagent/dist/harness/prompt-templates.js.map +0 -1
  100. package/ecagent/dist/harness/session/jsonl-repo.d.ts.map +0 -1
  101. package/ecagent/dist/harness/session/jsonl-repo.js.map +0 -1
  102. package/ecagent/dist/harness/session/jsonl-storage.d.ts.map +0 -1
  103. package/ecagent/dist/harness/session/jsonl-storage.js.map +0 -1
  104. package/ecagent/dist/harness/session/memory-repo.d.ts.map +0 -1
  105. package/ecagent/dist/harness/session/memory-repo.js.map +0 -1
  106. package/ecagent/dist/harness/session/memory-storage.d.ts.map +0 -1
  107. package/ecagent/dist/harness/session/memory-storage.js.map +0 -1
  108. package/ecagent/dist/harness/session/repo-utils.d.ts.map +0 -1
  109. package/ecagent/dist/harness/session/repo-utils.js.map +0 -1
  110. package/ecagent/dist/harness/session/session.d.ts.map +0 -1
  111. package/ecagent/dist/harness/session/session.js.map +0 -1
  112. package/ecagent/dist/harness/session/uuid.d.ts.map +0 -1
  113. package/ecagent/dist/harness/session/uuid.js.map +0 -1
  114. package/ecagent/dist/harness/skills.d.ts.map +0 -1
  115. package/ecagent/dist/harness/skills.js.map +0 -1
  116. package/ecagent/dist/harness/system-prompt.d.ts.map +0 -1
  117. package/ecagent/dist/harness/system-prompt.js.map +0 -1
  118. package/ecagent/dist/harness/types.d.ts.map +0 -1
  119. package/ecagent/dist/harness/types.js.map +0 -1
  120. package/ecagent/dist/harness/utils/shell-output.d.ts.map +0 -1
  121. package/ecagent/dist/harness/utils/shell-output.js.map +0 -1
  122. package/ecagent/dist/harness/utils/truncate.d.ts.map +0 -1
  123. package/ecagent/dist/harness/utils/truncate.js.map +0 -1
  124. package/ecagent/dist/index.d.ts.map +0 -1
  125. package/ecagent/dist/index.js.map +0 -1
  126. package/ecagent/dist/node.d.ts.map +0 -1
  127. package/ecagent/dist/node.js.map +0 -1
  128. package/ecagent/dist/proxy.d.ts.map +0 -1
  129. package/ecagent/dist/proxy.js.map +0 -1
  130. package/ecagent/dist/runtime/api/lazy.d.ts.map +0 -1
  131. package/ecagent/dist/runtime/api/lazy.js.map +0 -1
  132. package/ecagent/dist/runtime/api/pi-messages.d.ts.map +0 -1
  133. package/ecagent/dist/runtime/api/pi-messages.js.map +0 -1
  134. package/ecagent/dist/runtime/api/pi-messages.lazy.d.ts.map +0 -1
  135. package/ecagent/dist/runtime/api/pi-messages.lazy.js.map +0 -1
  136. package/ecagent/dist/runtime/auth/context.d.ts.map +0 -1
  137. package/ecagent/dist/runtime/auth/context.js.map +0 -1
  138. package/ecagent/dist/runtime/auth/credential-store.d.ts.map +0 -1
  139. package/ecagent/dist/runtime/auth/credential-store.js.map +0 -1
  140. package/ecagent/dist/runtime/auth/helpers.d.ts.map +0 -1
  141. package/ecagent/dist/runtime/auth/helpers.js.map +0 -1
  142. package/ecagent/dist/runtime/auth/resolve.d.ts.map +0 -1
  143. package/ecagent/dist/runtime/auth/resolve.js.map +0 -1
  144. package/ecagent/dist/runtime/auth/types.d.ts.map +0 -1
  145. package/ecagent/dist/runtime/auth/types.js.map +0 -1
  146. package/ecagent/dist/runtime/compat.d.ts.map +0 -1
  147. package/ecagent/dist/runtime/compat.js.map +0 -1
  148. package/ecagent/dist/runtime/index.d.ts.map +0 -1
  149. package/ecagent/dist/runtime/index.js.map +0 -1
  150. package/ecagent/dist/runtime/models.d.ts.map +0 -1
  151. package/ecagent/dist/runtime/models.js.map +0 -1
  152. package/ecagent/dist/runtime/oauth.d.ts.map +0 -1
  153. package/ecagent/dist/runtime/oauth.js.map +0 -1
  154. package/ecagent/dist/runtime/providers/faux.d.ts.map +0 -1
  155. package/ecagent/dist/runtime/providers/faux.js.map +0 -1
  156. package/ecagent/dist/runtime/types.d.ts.map +0 -1
  157. package/ecagent/dist/runtime/types.js.map +0 -1
  158. package/ecagent/dist/runtime/utils/diagnostics.d.ts.map +0 -1
  159. package/ecagent/dist/runtime/utils/diagnostics.js.map +0 -1
  160. package/ecagent/dist/runtime/utils/event-stream.d.ts.map +0 -1
  161. package/ecagent/dist/runtime/utils/event-stream.js.map +0 -1
  162. package/ecagent/dist/runtime/utils/headers.d.ts.map +0 -1
  163. package/ecagent/dist/runtime/utils/headers.js.map +0 -1
  164. package/ecagent/dist/runtime/utils/json-parse.d.ts.map +0 -1
  165. package/ecagent/dist/runtime/utils/json-parse.js.map +0 -1
  166. package/ecagent/dist/runtime/utils/oauth/types.d.ts.map +0 -1
  167. package/ecagent/dist/runtime/utils/oauth/types.js.map +0 -1
  168. package/ecagent/dist/runtime/utils/overflow.d.ts.map +0 -1
  169. package/ecagent/dist/runtime/utils/overflow.js.map +0 -1
  170. package/ecagent/dist/runtime/utils/provider-env.d.ts.map +0 -1
  171. package/ecagent/dist/runtime/utils/provider-env.js.map +0 -1
  172. package/ecagent/dist/runtime/utils/retry.d.ts.map +0 -1
  173. package/ecagent/dist/runtime/utils/retry.js.map +0 -1
  174. package/ecagent/dist/runtime/utils/typebox-helpers.d.ts.map +0 -1
  175. package/ecagent/dist/runtime/utils/typebox-helpers.js.map +0 -1
  176. package/ecagent/dist/runtime/utils/validation.d.ts.map +0 -1
  177. package/ecagent/dist/runtime/utils/validation.js.map +0 -1
  178. package/ecagent/dist/types.d.ts.map +0 -1
  179. package/ecagent/dist/types.js.map +0 -1
  180. package/ecagent/package.json +0 -93
  181. package/ecagent/src/agent-loop.ts +0 -792
  182. package/ecagent/src/agent.ts +0 -575
  183. package/ecagent/src/harness/agent-harness.ts +0 -1029
  184. package/ecagent/src/harness/compaction/branch-summarization.ts +0 -261
  185. package/ecagent/src/harness/compaction/compaction.ts +0 -753
  186. package/ecagent/src/harness/compaction/utils.ts +0 -144
  187. package/ecagent/src/harness/env/nodejs.ts +0 -569
  188. package/ecagent/src/harness/messages.ts +0 -164
  189. package/ecagent/src/harness/prompt-templates.ts +0 -267
  190. package/ecagent/src/harness/session/jsonl-repo.ts +0 -179
  191. package/ecagent/src/harness/session/jsonl-storage.ts +0 -314
  192. package/ecagent/src/harness/session/memory-repo.ts +0 -50
  193. package/ecagent/src/harness/session/memory-storage.ts +0 -133
  194. package/ecagent/src/harness/session/repo-utils.ts +0 -51
  195. package/ecagent/src/harness/session/session.ts +0 -338
  196. package/ecagent/src/harness/session/uuid.ts +0 -54
  197. package/ecagent/src/harness/skills.ts +0 -375
  198. package/ecagent/src/harness/system-prompt.ts +0 -34
  199. package/ecagent/src/harness/types.ts +0 -838
  200. package/ecagent/src/harness/utils/shell-output.ts +0 -135
  201. package/ecagent/src/harness/utils/truncate.ts +0 -344
  202. package/ecagent/src/index.ts +0 -46
  203. package/ecagent/src/node.ts +0 -2
  204. package/ecagent/src/proxy.ts +0 -367
  205. package/ecagent/src/runtime/api/lazy.ts +0 -70
  206. package/ecagent/src/runtime/api/pi-messages.lazy.ts +0 -4
  207. package/ecagent/src/runtime/api/pi-messages.ts +0 -436
  208. package/ecagent/src/runtime/auth/context.ts +0 -45
  209. package/ecagent/src/runtime/auth/credential-store.ts +0 -47
  210. package/ecagent/src/runtime/auth/helpers.ts +0 -46
  211. package/ecagent/src/runtime/auth/resolve.ts +0 -141
  212. package/ecagent/src/runtime/auth/types.ts +0 -182
  213. package/ecagent/src/runtime/compat.ts +0 -158
  214. package/ecagent/src/runtime/index.ts +0 -31
  215. package/ecagent/src/runtime/models.ts +0 -452
  216. package/ecagent/src/runtime/oauth.ts +0 -1
  217. package/ecagent/src/runtime/providers/faux.ts +0 -538
  218. package/ecagent/src/runtime/types.ts +0 -718
  219. package/ecagent/src/runtime/utils/diagnostics.ts +0 -45
  220. package/ecagent/src/runtime/utils/event-stream.ts +0 -88
  221. package/ecagent/src/runtime/utils/headers.ts +0 -18
  222. package/ecagent/src/runtime/utils/json-parse.ts +0 -124
  223. package/ecagent/src/runtime/utils/oauth/types.ts +0 -79
  224. package/ecagent/src/runtime/utils/overflow.ts +0 -165
  225. package/ecagent/src/runtime/utils/provider-env.ts +0 -52
  226. package/ecagent/src/runtime/utils/retry.ts +0 -101
  227. package/ecagent/src/runtime/utils/typebox-helpers.ts +0 -24
  228. package/ecagent/src/runtime/utils/validation.ts +0 -310
  229. package/ecagent/src/types.ts +0 -430
  230. package/ecagent/tsconfig.build.json +0 -32
package/CHANGELOG.md CHANGED
@@ -3,6 +3,49 @@
3
3
  本文件记录 EvolCore 的重要变更。EvolClaw 版本线的历史记录已归档至
4
4
  [`docs/_archive/CHANGELOG-evolclaw.md`](docs/_archive/CHANGELOG-evolclaw.md)。
5
5
 
6
+ ## 0.0.9 (2026-08-06)
7
+
8
+ ### 安装与自启
9
+
10
+ - 支持在安装或初始化时配置登录自启,Windows 与 macOS 开机登录后自动拉起 EvolCore。
11
+ - 已完成初始化的安装可单独开关登录自启,无需重跑初始化流程。
12
+
13
+ ### AUN 通信
14
+
15
+ - 出站消息默认加密,可通过配置调整;观察者转发与回执一并遵循该设置。
16
+ - 未连接时的图片与消息发送进入待发队列,连接恢复后按序重投,不再直接丢弃。
17
+ - 完善图片上传的类型识别与结构化内容处理。
18
+
19
+ ### BaseAgent 配置
20
+
21
+ - 支持为 BaseAgent 自定义请求头和查询参数,便于对接需要额外标识的网关。
22
+ - 统一各 BaseAgent 的推理强度配置项,旧字段仍可识别。
23
+
24
+ ### 运行可靠性
25
+
26
+ - 修复 Agent 在启动阶段被中断时的状态残留。
27
+ - EC Web 版本过低时拒绝启动并提示升级命令,避免新旧组件搭配下的异常行为。
28
+ - 完善菜单协议的权限校验,并修正系统信息查询。
29
+
30
+ ## 0.0.8 (2026-08-04)
31
+
32
+ ### AID 与域名管理
33
+
34
+ - 支持通过配置控制 AID 所属域名,并可在变更后自动迁移已有身份记录。
35
+
36
+ ### 运行稳定性
37
+
38
+ - 完善数据迁移的归档与收尾清理逻辑,降低目录结构演进带来的遗留数据风险。
39
+ - 增强 daemon 启停流程与运行状态诊断,异常时定位信息更准确。
40
+ - 完善 Agent 运行时在失败场景下的状态标记与回退行为。
41
+ - 修复 Windows 下 Codex CLI 的命令解析与进程启动兼容问题。
42
+
43
+ ### 交互体验
44
+
45
+ - AUN 交互卡片有效期延长至 24 小时,与实际使用节奏匹配。
46
+ - 修复 Web 控制台触发器视图中 daemon 控制触发器无处归属的问题,升级检查触发器包名一并修正。
47
+ - 优化 EC Web 网关状态展示。
48
+
6
49
  ## 0.0.7 (2026-08-03)
7
50
 
8
51
  ### 运行可靠性
package/README.md CHANGED
@@ -86,16 +86,16 @@ ResponseEngine.processMessage()
86
86
  **一行安装(macOS / Linux)**:
87
87
 
88
88
  ```bash
89
- curl -fsSL https://aun-network.oss-cn-hangzhou.aliyuncs.com/evolcore/install.sh | bash
89
+ curl -fsSL https://download.evolai.cn/releases/evolcore/2026-08-05.2/install.sh | bash
90
90
  ```
91
91
 
92
92
  **Windows PowerShell**:
93
93
 
94
94
  ```powershell
95
- irm https://aun-network.oss-cn-hangzhou.aliyuncs.com/evolcore/install.ps1 | iex
95
+ & ([scriptblock]::Create((irm https://download.evolai.cn/releases/evolcore/2026-08-05.2/install.ps1))) -AutoStart
96
96
  ```
97
97
 
98
- 安装器会自动进入 `ec init` 交互向导;只有已有配置同时包含控制 AID 和 Owner 时才跳过。绑定完成后运行 `ec start` `ec status`。安装器不会停止或重启服务,详细流程见 [一键安装脚本说明](docs/evolcore-install-script.md)。
98
+ 安装器会自动进入 `ec init` 交互向导;只有已有配置同时包含控制 AID 和 Owner 时才跳过。上面的 Windows 命令会在绑定完成后自动运行 `ec start`;macOS/Linux 需手动运行 `ec start`。安装器不会停止或重启已有服务,详细流程见 [一键安装脚本说明](docs/evolcore-install-script.md)。
99
99
 
100
100
  **npm 全局安装**:
101
101
 
@@ -169,11 +169,45 @@ ec init aun
169
169
  ```
170
170
 
171
171
  **API 继承机制**:`agents.claude` 整个 section 可省略,系统自动按以下优先级继承:
172
- - `apiKey`:配置文件 → `ANTHROPIC_AUTH_TOKEN` 环境变量 → `~/.claude/settings.json`
172
+ - `apiKey`:配置文件 → `ANTHROPIC_AUTH_TOKEN` / `ANTHROPIC_API_KEY` 环境变量 → `~/.claude/settings.json` 中对应变量
173
173
  - `baseUrl`:配置文件 → `ANTHROPIC_BASE_URL` 环境变量 → `~/.claude/settings.json`
174
174
  - `model`:配置文件 → `~/.claude/settings.json` → 默认 `sonnet`
175
175
  - `effort`:配置文件 → `~/.claude/settings.json` → SDK 默认值(`auto`)
176
176
 
177
+ Claude/Codex 兼容网关需要额外请求头或查询参数时,可以直接在 EvolCore 的
178
+ `baseagents` 配置中设置,不需要修改 Claude/Codex 的全局配置文件:
179
+
180
+ ```json
181
+ {
182
+ "baseagents": {
183
+ "claude": {
184
+ "baseUrl": "https://example.com/claude",
185
+ "headers": {
186
+ "X-Tenant-Id": "${TENANT_ID}"
187
+ },
188
+ "queryParams": {
189
+ "source": "evolcore"
190
+ }
191
+ },
192
+ "codex": {
193
+ "baseUrl": "https://example.com/codex",
194
+ "headers": {
195
+ "X-Tenant-Id": "${TENANT_ID}"
196
+ },
197
+ "queryParams": {
198
+ "source": "evolcore"
199
+ }
200
+ }
201
+ }
202
+ }
203
+ ```
204
+
205
+ `${VAR}` 会按 EvolCore 的环境变量规则展开。Claude 通过 `ANTHROPIC_CUSTOM_HEADERS`
206
+ 传递 Header;Codex app-server 启动时由 EvolCore 注入 `env_http_headers` 和
207
+ `query_params`,所以不需要手改 `~/.codex/config.toml`。`queryParams` 不应放置密钥等敏感信息。
208
+
209
+ 完整设计、运行链路和排障说明见:[Claude/Codex 自定义请求元数据技术文档](docs/baseagent-request-metadata.md)。
210
+
177
211
  ### 3. 运行
178
212
 
179
213
  ```bash
@@ -50,6 +50,39 @@ function directInferenceValue(...values) {
50
50
  }
51
51
  return undefined;
52
52
  }
53
+ const HTTP_HEADER_NAME = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
54
+ function normalizeStringMap(value, field) {
55
+ if (value === undefined || value === null)
56
+ return undefined;
57
+ if (typeof value !== 'object' || Array.isArray(value)) {
58
+ throw new Error(`${field} must be an object with string values`);
59
+ }
60
+ const entries = [];
61
+ for (const [key, item] of Object.entries(value)) {
62
+ if (!key)
63
+ throw new Error(`${field} names cannot be empty`);
64
+ if (typeof item !== 'string')
65
+ throw new Error(`${field}.${key} must be a string`);
66
+ if (field === 'headers') {
67
+ if (!HTTP_HEADER_NAME.test(key))
68
+ throw new Error(`Invalid HTTP header name: ${key}`);
69
+ if (/[\0\r\n]/.test(item))
70
+ throw new Error(`HTTP header ${key} cannot contain NUL or line breaks`);
71
+ }
72
+ entries.push([key, item]);
73
+ }
74
+ return entries.length > 0 ? Object.fromEntries(entries) : undefined;
75
+ }
76
+ function normalizeRequestMetadata(value) {
77
+ return {
78
+ headers: normalizeStringMap(value?.headers, 'headers'),
79
+ queryParams: normalizeStringMap(value?.queryParams, 'queryParams'),
80
+ };
81
+ }
82
+ /** Claude Code accepts both names; AUTH_TOKEN is kept first for compatibility. */
83
+ function resolveAnthropicApiKey(...values) {
84
+ return directInferenceValue(...values);
85
+ }
53
86
  function loadClaudeSettings() {
54
87
  try {
55
88
  const settingsPath = path.join(os.homedir(), '.claude', 'settings.json');
@@ -67,25 +100,36 @@ function loadClaudeSettings() {
67
100
  */
68
101
  export function resolveAnthropicDirectConfig(override) {
69
102
  const explicit = directInferencePair(override?.apiKey, override?.baseUrl);
70
- if (explicit)
71
- return explicit;
103
+ if (explicit) {
104
+ return {
105
+ ...explicit,
106
+ ...normalizeRequestMetadata(override),
107
+ };
108
+ }
72
109
  const environment = directInferencePair(directInferenceValue(process.env.ANTHROPIC_AUTH_TOKEN, process.env.ANTHROPIC_API_KEY), process.env.ANTHROPIC_BASE_URL);
73
- if (environment)
74
- return environment;
110
+ if (environment) {
111
+ return {
112
+ ...environment,
113
+ ...normalizeRequestMetadata(override),
114
+ };
115
+ }
75
116
  const settings = loadClaudeSettings();
76
- return directInferencePair(directInferenceValue(settings.env?.ANTHROPIC_AUTH_TOKEN, settings.env?.ANTHROPIC_API_KEY), settings.env?.ANTHROPIC_BASE_URL);
117
+ const settingsResolved = directInferencePair(directInferenceValue(settings.env?.ANTHROPIC_AUTH_TOKEN, settings.env?.ANTHROPIC_API_KEY), settings.env?.ANTHROPIC_BASE_URL);
118
+ return settingsResolved
119
+ ? {
120
+ ...settingsResolved,
121
+ ...normalizeRequestMetadata(override),
122
+ }
123
+ : undefined;
77
124
  }
78
125
  export function resolveAnthropicConfig(config, override) {
79
126
  const settings = loadClaudeSettings();
80
127
  const isPlaceholder = (v) => !v || v.includes('your-') || v.includes('placeholder');
81
128
  const overrideApiKey = isPlaceholder(override?.apiKey) ? undefined : override?.apiKey;
82
129
  const globalApiKey = isPlaceholder(config.agents?.claude?.apiKey) ? undefined : config.agents?.claude?.apiKey;
83
- const apiKey = overrideApiKey
84
- || globalApiKey
85
- || process.env.ANTHROPIC_AUTH_TOKEN
86
- || settings.env?.ANTHROPIC_AUTH_TOKEN;
130
+ const apiKey = resolveAnthropicApiKey(overrideApiKey, globalApiKey, process.env.ANTHROPIC_AUTH_TOKEN, process.env.ANTHROPIC_API_KEY, settings.env?.ANTHROPIC_AUTH_TOKEN, settings.env?.ANTHROPIC_API_KEY);
87
131
  if (!apiKey) {
88
- throw new Error('No API key found. Set one of: baseagents.claude.apiKey (per-agent or defaults), env ANTHROPIC_AUTH_TOKEN, or ~/.claude/settings.json env.ANTHROPIC_AUTH_TOKEN');
132
+ throw new Error('No API key found for Claude. Set baseagents.claude.apiKey, env ANTHROPIC_AUTH_TOKEN or ANTHROPIC_API_KEY, or the corresponding variable in ~/.claude/settings.json');
89
133
  }
90
134
  const isPlaceholderUrl = (v) => !v || v.includes('api.anthropic.com');
91
135
  const overrideBaseUrl = isPlaceholderUrl(override?.baseUrl) ? undefined : override?.baseUrl;
@@ -94,6 +138,8 @@ export function resolveAnthropicConfig(config, override) {
94
138
  || globalBaseUrl
95
139
  || process.env.ANTHROPIC_BASE_URL
96
140
  || settings.env?.ANTHROPIC_BASE_URL;
141
+ const headers = normalizeStringMap(override?.headers ?? config.agents?.claude?.headers, 'headers');
142
+ const queryParams = normalizeStringMap(override?.queryParams ?? config.agents?.claude?.queryParams, 'queryParams');
97
143
  const model = override?.model
98
144
  || config.agents?.claude?.model
99
145
  || settings.model
@@ -105,19 +151,22 @@ export function resolveAnthropicConfig(config, override) {
105
151
  const pickExec = (v) => (!v || v.includes('your-') || v.includes('placeholder')) ? undefined : v;
106
152
  const pathToClaudeCodeExecutable = pickExec(override?.pathToClaudeCodeExecutable)
107
153
  || pickExec(config.agents?.claude?.pathToClaudeCodeExecutable);
108
- return { apiKey, baseUrl, model, effort, pathToClaudeCodeExecutable };
154
+ return { apiKey, baseUrl, headers, queryParams, model, effort, pathToClaudeCodeExecutable };
109
155
  }
110
156
  function loadCodexSettings() {
111
157
  try {
112
- const authPath = path.join(os.homedir(), '.codex', 'auth.json');
158
+ const configuredHome = process.env.CODEX_HOME?.trim();
159
+ const codexHome = configuredHome ? path.resolve(configuredHome) : path.join(os.homedir(), '.codex');
160
+ const authPath = path.join(codexHome, 'auth.json');
113
161
  let apiKey;
114
162
  if (fs.existsSync(authPath)) {
115
163
  const auth = JSON.parse(fs.readFileSync(authPath, 'utf-8'));
116
164
  apiKey = auth.OPENAI_API_KEY;
117
165
  }
118
- const configPath = path.join(os.homedir(), '.codex', 'config.toml');
166
+ const configPath = path.join(codexHome, 'config.toml');
119
167
  let model;
120
168
  let baseUrl;
169
+ let provider;
121
170
  if (fs.existsSync(configPath)) {
122
171
  const content = fs.readFileSync(configPath, 'utf-8');
123
172
  const modelMatch = content.match(/^model\s*=\s*"([^"]+)"/m);
@@ -125,17 +174,35 @@ function loadCodexSettings() {
125
174
  model = modelMatch[1];
126
175
  const providerMatch = content.match(/^model_provider\s*=\s*"([^"]+)"/m);
127
176
  if (providerMatch) {
128
- const provider = providerMatch[1];
129
- const baseUrlMatch = content.match(new RegExp(`\\[model_providers\\.${provider}\\][\\s\\S]*?base_url\\s*=\\s*"([^"]+)"`, 'm'));
130
- if (baseUrlMatch)
131
- baseUrl = baseUrlMatch[1];
177
+ provider = providerMatch[1];
178
+ baseUrl = readCodexProviderBaseUrl(content, provider);
132
179
  }
133
180
  }
134
- return { apiKey, baseUrl, model };
181
+ return { apiKey, baseUrl, model, provider };
135
182
  }
136
183
  catch { }
137
184
  return {};
138
185
  }
186
+ function escapeRegExp(value) {
187
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
188
+ }
189
+ function readCodexProviderBaseUrl(content, provider) {
190
+ const segments = /^[A-Za-z0-9_-]+$/.test(provider)
191
+ ? [provider, JSON.stringify(provider)]
192
+ : [JSON.stringify(provider)];
193
+ for (const segment of segments) {
194
+ const section = new RegExp(`^\\s*\\[\\s*model_providers\\s*\\.\\s*${escapeRegExp(segment)}\\s*\\]\\s*(?:#.*)?$`, 'm').exec(content);
195
+ if (!section || section.index === undefined)
196
+ continue;
197
+ const tail = content.slice(section.index + section[0].length);
198
+ const nextSection = tail.search(/^\s*\[/m);
199
+ const block = nextSection >= 0 ? tail.slice(0, nextSection) : tail;
200
+ const baseUrl = block.match(/^\s*base_url\s*=\s*"([^"]+)"/m)?.[1];
201
+ if (baseUrl)
202
+ return baseUrl;
203
+ }
204
+ return undefined;
205
+ }
139
206
  /**
140
207
  * Resolve a complete credential/endpoint pair for direct OpenAI HTTP calls.
141
208
  * auth.json and config.toml together form the Codex SDK source; fields are
@@ -143,13 +210,27 @@ function loadCodexSettings() {
143
210
  */
144
211
  export function resolveOpenaiDirectConfig(override) {
145
212
  const explicit = directInferencePair(override?.apiKey, override?.baseUrl);
146
- if (explicit)
147
- return explicit;
213
+ if (explicit) {
214
+ return {
215
+ ...explicit,
216
+ ...normalizeRequestMetadata(override),
217
+ };
218
+ }
148
219
  const environment = directInferencePair(process.env.OPENAI_API_KEY, process.env.OPENAI_BASE_URL);
149
- if (environment)
150
- return environment;
220
+ if (environment) {
221
+ return {
222
+ ...environment,
223
+ ...normalizeRequestMetadata(override),
224
+ };
225
+ }
151
226
  const settings = loadCodexSettings();
152
- return directInferencePair(settings.apiKey, settings.baseUrl);
227
+ const settingsResolved = directInferencePair(settings.apiKey, settings.baseUrl);
228
+ return settingsResolved
229
+ ? {
230
+ ...settingsResolved,
231
+ ...normalizeRequestMetadata(override),
232
+ }
233
+ : undefined;
153
234
  }
154
235
  export function resolveOpenaiConfig(config, override) {
155
236
  const codexSettings = loadCodexSettings();
@@ -171,6 +252,8 @@ export function resolveOpenaiConfig(config, override) {
171
252
  || process.env.OPENAI_BASE_URL
172
253
  || codexSettings.baseUrl
173
254
  || undefined;
255
+ const headers = normalizeStringMap(override?.headers ?? config.agents?.codex?.headers, 'headers');
256
+ const queryParams = normalizeStringMap(override?.queryParams ?? config.agents?.codex?.queryParams, 'queryParams');
174
257
  const model = override?.model
175
258
  || config.agents?.codex?.model
176
259
  || codexSettings.model
@@ -186,7 +269,17 @@ export function resolveOpenaiConfig(config, override) {
186
269
  const approvalsReviewer = override?.approvalsReviewer
187
270
  ?? config.agents?.codex?.approvalsReviewer
188
271
  ?? undefined;
189
- return { apiKey, baseUrl, model, effort, enableRequestUserInput, approvalsReviewer };
272
+ return {
273
+ apiKey,
274
+ baseUrl,
275
+ provider: codexSettings.provider,
276
+ headers,
277
+ queryParams,
278
+ model,
279
+ effort,
280
+ enableRequestUserInput,
281
+ approvalsReviewer,
282
+ };
190
283
  }
191
284
  export function resolveGoogleConfig(config, override) {
192
285
  const googleCfg = config.agents?.gemini;
@@ -65,6 +65,19 @@ function isClaudeMcpTool(toolName) {
65
65
  function isUnknownClaudeTool(toolName) {
66
66
  return !isClaudeMcpTool(toolName) && !CLAUDE_BUILTIN_TOOLS.has(toolName);
67
67
  }
68
+ function appendQueryParams(baseUrl, queryParams) {
69
+ if (!baseUrl || !queryParams || Object.keys(queryParams).length === 0)
70
+ return baseUrl;
71
+ try {
72
+ const parsed = new URL(baseUrl);
73
+ for (const [name, value] of Object.entries(queryParams))
74
+ parsed.searchParams.set(name, value);
75
+ return parsed.toString();
76
+ }
77
+ catch {
78
+ return baseUrl;
79
+ }
80
+ }
68
81
  function stableClaudePermissionValue(value, depth = 0) {
69
82
  if (depth > 8)
70
83
  return '[truncated]';
@@ -529,6 +542,8 @@ export class AgentRunner {
529
542
  effort;
530
543
  permissionMode = DEFAULT_PERMISSION_MODE;
531
544
  baseUrl;
545
+ customHeaders;
546
+ customQueryParams;
532
547
  config;
533
548
  activeSessions = new Map();
534
549
  activeStreams = new Map();
@@ -556,15 +571,22 @@ export class AgentRunner {
556
571
  if (config) {
557
572
  const anthropic = resolveAnthropicConfig(config);
558
573
  this.claudeExecutablePath = anthropic.pathToClaudeCodeExecutable;
574
+ this.customHeaders = anthropic.headers;
575
+ this.customQueryParams = anthropic.queryParams;
559
576
  }
560
577
  }
561
578
  getAgentEnv(runtimeEnv, evolcoreSessionId) {
579
+ const anthropicBaseUrl = appendQueryParams(this.baseUrl, this.customQueryParams);
562
580
  const env = {
563
581
  ...process.env,
564
582
  ANTHROPIC_AUTH_TOKEN: this.apiKey,
583
+ ANTHROPIC_API_KEY: this.apiKey,
565
584
  PATH: process.env.PATH,
566
585
  DISABLE_AUTOUPDATER: '1',
567
- ...(this.baseUrl ? { ANTHROPIC_BASE_URL: this.baseUrl } : {}),
586
+ ...(anthropicBaseUrl ? { ANTHROPIC_BASE_URL: anthropicBaseUrl } : {}),
587
+ ...(this.customHeaders
588
+ ? { ANTHROPIC_CUSTOM_HEADERS: Object.entries(this.customHeaders).map(([name, value]) => `${name}: ${value}`).join('\n') }
589
+ : {}),
568
590
  ...(evolcoreSessionId ? { EVOLCORE_SESSION_ID: evolcoreSessionId } : {}),
569
591
  ...(runtimeEnv ?? {}),
570
592
  };
@@ -2297,6 +2319,7 @@ export class AgentRunner {
2297
2319
  })();
2298
2320
  }
2299
2321
  async interrupt(sessionId) {
2322
+ const streamRegistered = this.activeStreams.has(sessionId);
2300
2323
  const fn = this.interruptFns.get(sessionId);
2301
2324
  const query = this.activeQueries.get(sessionId);
2302
2325
  const cancelledInputIds = [];
@@ -2326,7 +2349,10 @@ export class AgentRunner {
2326
2349
  if (query && typeof query.close === 'function')
2327
2350
  query.close();
2328
2351
  const done = this.streamDone.get(sessionId);
2329
- if (done) {
2352
+ // A stream interrupted after runQuery resolves but before registerStream has
2353
+ // no consumer that can enter the wrapper generator's finally block. Waiting
2354
+ // for streamDone here would always burn the full timeout.
2355
+ if (done && streamRegistered) {
2330
2356
  await Promise.race([done, new Promise(resolve => setTimeout(resolve, 5_000))]);
2331
2357
  }
2332
2358
  this.interruptFns.delete(sessionId);
@@ -2342,6 +2368,9 @@ export class AgentRunner {
2342
2368
  cleanupStream(sessionId) {
2343
2369
  this.activeMessageStreams.get(sessionId)?.end();
2344
2370
  this.activeMessageStreams.delete(sessionId);
2371
+ this.streamDoneResolvers.get(sessionId)?.();
2372
+ this.streamDoneResolvers.delete(sessionId);
2373
+ this.streamDone.delete(sessionId);
2345
2374
  this.activeStreams.delete(sessionId);
2346
2375
  this.interruptFns.delete(sessionId);
2347
2376
  this.activeQueries.delete(sessionId);
@@ -2,6 +2,23 @@ import { spawn } from 'child_process';
2
2
  import readline from 'readline';
3
3
  import { logger } from '../utils/logger.js';
4
4
  import { buildHClassGuardCommand } from '../core/permission/sandbox-runtime.js';
5
+ import { resolveCodexLaunchCommand } from '../utils/codex-cli.js';
6
+ import { createHash } from 'crypto';
7
+ function tomlProviderKey(value) {
8
+ if (!value || /[\0\r\n]/.test(value)) {
9
+ throw new Error(`Invalid Codex provider name: ${value}`);
10
+ }
11
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : JSON.stringify(value);
12
+ }
13
+ function tomlInlineStringTable(values) {
14
+ return `{${Object.entries(values)
15
+ .map(([name, value]) => `${JSON.stringify(name)}=${JSON.stringify(value)}`)
16
+ .join(',')}}`;
17
+ }
18
+ function codexHeaderEnvName(headerName) {
19
+ const digest = createHash('sha256').update(headerName).digest('hex').slice(0, 16).toUpperCase();
20
+ return `EVOLCORE_CODEX_HEADER_${digest}`;
21
+ }
5
22
  function hasPermissionProfile(config) {
6
23
  if (!config || typeof config.default_permissions !== 'string' || !config.default_permissions)
7
24
  return false;
@@ -326,15 +343,21 @@ export class CodexAppServerClient {
326
343
  startProcess() {
327
344
  const env = { ...process.env, ...this.options.env };
328
345
  env.CODEX_API_KEY = this.options.apiKey;
346
+ for (const [name, value] of Object.entries(this.options.headers ?? {})) {
347
+ env[codexHeaderEnvName(name)] = value;
348
+ }
329
349
  if (this.options.baseUrl) {
330
350
  env.OPENAI_BASE_URL = this.options.baseUrl;
331
351
  }
332
352
  const args = this.buildProcessArgs();
333
- const guardedCommand = buildHClassGuardCommand('codex', args);
353
+ const launchCommand = resolveCodexLaunchCommand(args);
354
+ if (!launchCommand)
355
+ throw new Error('Codex CLI not found');
356
+ const guardedCommand = buildHClassGuardCommand(launchCommand.command, launchCommand.args);
334
357
  if (process.platform === 'linux' && !guardedCommand) {
335
358
  throw new Error('Codex 缺少 EvolCore H 类路径隔离运行时,已拒绝启动 app-server');
336
359
  }
337
- this.proc = spawn(guardedCommand?.command ?? 'codex', guardedCommand?.args ?? args, {
360
+ this.proc = spawn(guardedCommand?.command ?? launchCommand.command, guardedCommand?.args ?? launchCommand.args, {
338
361
  cwd: this.options.cwd,
339
362
  env,
340
363
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -363,6 +386,18 @@ export class CodexAppServerClient {
363
386
  if (this.options.baseUrl) {
364
387
  args.push('--config', `openai_base_url=${JSON.stringify(this.options.baseUrl)}`);
365
388
  }
389
+ const headerEnvNames = Object.fromEntries(Object.keys(this.options.headers ?? {}).map(name => [name, codexHeaderEnvName(name)]));
390
+ const hasHeaders = Object.keys(headerEnvNames).length > 0;
391
+ const hasQueryParams = Object.keys(this.options.queryParams ?? {}).length > 0;
392
+ if (hasHeaders || hasQueryParams) {
393
+ const provider = tomlProviderKey(this.options.provider || 'openai');
394
+ if (hasHeaders) {
395
+ args.push('--config', `model_providers.${provider}.env_http_headers=${tomlInlineStringTable(headerEnvNames)}`);
396
+ }
397
+ if (hasQueryParams) {
398
+ args.push('--config', `model_providers.${provider}.query_params=${tomlInlineStringTable(this.options.queryParams ?? {})}`);
399
+ }
400
+ }
366
401
  if (this.options.effort) {
367
402
  args.push('--config', `model_reasoning_effort=${JSON.stringify(this.options.effort)}`);
368
403
  }
@@ -23,6 +23,7 @@ import { compareVersions } from '../utils/npm-ops.js';
23
23
  import { resolvePaths, resolveRoot } from '../paths.js';
24
24
  import { buildSessionTurnList } from '../core/session/session-turns.js';
25
25
  import { execFileSync } from 'child_process';
26
+ import { execCodexCliSync, resolveCodexCliPath } from '../utils/codex-cli.js';
26
27
  import { createHash, randomBytes } from 'crypto';
27
28
  import fs from 'fs';
28
29
  import path from 'path';
@@ -128,7 +129,7 @@ export function isCodexCliVersionSupported(version) {
128
129
  }
129
130
  export function getCodexCliVersion() {
130
131
  try {
131
- const output = execFileSync('codex', ['--version'], {
132
+ const output = execCodexCliSync(['--version'], {
132
133
  encoding: 'utf-8',
133
134
  timeout: 3000,
134
135
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -140,6 +141,10 @@ export function getCodexCliVersion() {
140
141
  }
141
142
  }
142
143
  export function getCodexAppServerAvailability() {
144
+ if (!resolveCodexCliPath()) {
145
+ const upgradeHint = '请升级 Codex CLI:npm install -g @openai/codex@latest';
146
+ return { available: false, reason: `未检测到可用 Codex CLI。${upgradeHint}` };
147
+ }
143
148
  const version = getCodexCliVersion();
144
149
  const upgradeHint = '请升级 Codex CLI:npm install -g @openai/codex@latest';
145
150
  if (!version) {
@@ -153,7 +158,7 @@ export function getCodexAppServerAvailability() {
153
158
  };
154
159
  }
155
160
  try {
156
- execFileSync('codex', ['app-server', '--help'], {
161
+ execCodexCliSync(['app-server', '--help'], {
157
162
  encoding: 'utf-8',
158
163
  timeout: 3000,
159
164
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -171,7 +176,7 @@ function fetchCodexCatalog() {
171
176
  if (codexCatalogCache)
172
177
  return codexCatalogCache;
173
178
  try {
174
- const output = execFileSync('codex', ['debug', 'models'], {
179
+ const output = execCodexCliSync(['debug', 'models'], {
175
180
  encoding: 'utf-8',
176
181
  timeout: 5000,
177
182
  stdio: ['pipe', 'pipe', 'pipe'],
@@ -250,6 +255,9 @@ export class CodexRunner {
250
255
  const client = new CodexAppServerClient({
251
256
  apiKey: this.resolvedConfig.apiKey,
252
257
  baseUrl: this.resolvedConfig.baseUrl,
258
+ provider: this.resolvedConfig.provider,
259
+ headers: this.resolvedConfig.headers,
260
+ queryParams: this.resolvedConfig.queryParams,
253
261
  model: this.model,
254
262
  effort: this.effort,
255
263
  enableRequestUserInput: this.resolvedConfig.enableRequestUserInput,
@@ -1,35 +1,46 @@
1
1
  import crypto from 'crypto';
2
2
  import { aidCreate } from './index.js';
3
+ import { DEFAULT_AID_DOMAIN, normalizeAidDomain } from './domain.js';
3
4
  import { getAidStore, SLOT } from './store.js';
4
5
  import { logger } from '../../utils/logger.js';
5
6
  const MAX_ATTEMPTS = 5;
6
- /** 解析控制 AID issuer:环境变量 EVOLCORE_ISSUER → 兜底 agentid.pub */
7
- export function resolveControlIssuer() {
8
- const env = process.env.EVOLCORE_ISSUER?.trim();
9
- if (env) {
10
- // Validate issuer format: must be valid domain-like structure
11
- if (!/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(env)) {
12
- logger.error(`[control-aid] Invalid EVOLCORE_ISSUER format: ${env}, using default`);
13
- return 'agentid.pub';
14
- }
15
- // Prevent localhost/local domains
16
- if (env === 'localhost' || env.endsWith('.localhost') || env.startsWith('127.') || env.startsWith('0.')) {
17
- logger.error(`[control-aid] EVOLCORE_ISSUER cannot be localhost/local: ${env}, using default`);
18
- return 'agentid.pub';
19
- }
20
- // Length limit
21
- if (env.length > 253) {
22
- logger.error(`[control-aid] EVOLCORE_ISSUER too long: ${env.length} chars, using default`);
23
- return 'agentid.pub';
24
- }
7
+ export { DEFAULT_AID_DOMAIN } from './domain.js';
8
+ function environmentValue(name) {
9
+ if (!Object.prototype.hasOwnProperty.call(process.env, name))
10
+ return undefined;
11
+ return process.env[name]?.trim() ?? '';
12
+ }
13
+ /**
14
+ * Resolve the AID domain for automatically generated control identities.
15
+ *
16
+ * Explicit values fail closed. This avoids silently registering an identity
17
+ * under a different domain when deployment configuration is malformed.
18
+ */
19
+ export function resolveControlAidDomain(configuredAidDomain) {
20
+ const environmentDomain = environmentValue('EVOLCORE_AID_DOMAIN');
21
+ if (environmentDomain !== undefined)
22
+ return normalizeAidDomain(environmentDomain, 'EVOLCORE_AID_DOMAIN');
23
+ const legacyEnvironmentDomain = environmentValue('EVOLCORE_ISSUER');
24
+ if (legacyEnvironmentDomain !== undefined) {
25
+ logger.warn('[control-aid] EVOLCORE_ISSUER is deprecated; use EVOLCORE_AID_DOMAIN instead');
26
+ return normalizeAidDomain(legacyEnvironmentDomain, 'EVOLCORE_ISSUER');
25
27
  }
26
- return env || 'agentid.pub';
28
+ if (configuredAidDomain !== undefined) {
29
+ return normalizeAidDomain(configuredAidDomain, 'daemon.json.aun.defaultAidDomain');
30
+ }
31
+ return DEFAULT_AID_DOMAIN;
32
+ }
33
+ /** @deprecated Use resolveControlAidDomain(). */
34
+ export function resolveControlIssuer(configuredAidDomain) {
35
+ return resolveControlAidDomain(configuredAidDomain);
27
36
  }
28
- /** 生成候选控制 AID:ec + 5位随机数字 + .{issuer} */
29
- export function candidateAid(issuer) {
37
+ /** 生成候选控制 AID:ec + 5位随机数字 + .{aidDomain} */
38
+ export function candidateAid(aidDomain) {
30
39
  const n = crypto.randomInt(10000, 100000); // 5 位:10000-99999
31
- const finalIssuer = issuer || resolveControlIssuer();
32
- return `ec${n}.${finalIssuer}`;
40
+ const finalAidDomain = aidDomain === undefined
41
+ ? resolveControlAidDomain()
42
+ : normalizeAidDomain(aidDomain, 'aidDomain');
43
+ return `ec${n}.${finalAidDomain}`;
33
44
  }
34
45
  /**
35
46
  * 候选 AID 是否已在 PKI 注册。
@@ -56,12 +67,14 @@ async function candidateExists(store, candidate) {
56
67
  * - fail-fast:查重探测失败(网关不可达)立即抛错,不掩盖成"均冲突"
57
68
  * - agent.md 不上传:aidCreate 仅注册身份 + 写私钥,不调 agentmdPut
58
69
  */
59
- export async function generateControlAid() {
60
- const issuer = resolveControlIssuer();
70
+ export async function generateControlAid(aidDomain) {
71
+ const finalAidDomain = aidDomain === undefined
72
+ ? resolveControlAidDomain()
73
+ : normalizeAidDomain(aidDomain, 'aidDomain');
61
74
  const store = await getAidStore({ slotId: SLOT.cli });
62
75
  try {
63
76
  for (let i = 0; i < MAX_ATTEMPTS; i++) {
64
- const candidate = candidateAid(issuer);
77
+ const candidate = candidateAid(finalAidDomain);
65
78
  if (await candidateExists(store, candidate)) {
66
79
  logger.info(`[control-aid] ${candidate} 已注册,重试 (${i + 1}/${MAX_ATTEMPTS})`);
67
80
  continue;
@@ -0,0 +1,23 @@
1
+ export const DEFAULT_AID_DOMAIN = 'agentid.cn';
2
+ const DOMAIN_RE = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)+$/i;
3
+ const IPV4_RE = /^(?:\d{1,3}\.){3}\d{1,3}$/;
4
+ /** Normalize and validate the suffix domain used to construct an AID. */
5
+ export function normalizeAidDomain(value, source) {
6
+ if (typeof value !== 'string') {
7
+ throw new Error(`[aid-domain] ${source} must be a string`);
8
+ }
9
+ const aidDomain = value.trim().toLowerCase();
10
+ if (!DOMAIN_RE.test(aidDomain)) {
11
+ throw new Error(`[aid-domain] ${source} must be a valid multi-level domain: ${value}`);
12
+ }
13
+ if (IPV4_RE.test(aidDomain)) {
14
+ throw new Error(`[aid-domain] ${source} cannot be an IP address: ${value}`);
15
+ }
16
+ if (aidDomain.endsWith('.localhost')) {
17
+ throw new Error(`[aid-domain] ${source} cannot be a localhost/local domain: ${value}`);
18
+ }
19
+ if (aidDomain.length > 253) {
20
+ throw new Error(`[aid-domain] ${source} is too long: ${aidDomain.length} chars`);
21
+ }
22
+ return aidDomain;
23
+ }