evolcore 0.0.35 → 0.0.37

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 (224) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +6 -0
  3. package/bin/install-codex-managed-hooks.mjs +20 -3
  4. package/dist/agents/baseagent-availability.js +1 -1
  5. package/dist/agents/baseagent.js +88 -0
  6. package/dist/agents/claude-runner.js +59 -12
  7. package/dist/agents/codex/app-server-client.js +18 -1
  8. package/dist/agents/codex-runner.js +262 -21
  9. package/dist/agents/ecagent-runner.js +47 -11
  10. package/dist/agents/gemini-runner.js +36 -12
  11. package/dist/aun/aid/client.js +4 -3
  12. package/dist/aun/aid/control-aid.js +270 -8
  13. package/dist/aun/aid/identity.js +6 -3
  14. package/dist/aun/aid/ownership-operation.js +58 -0
  15. package/dist/aun/aid/store.js +1 -0
  16. package/dist/aun/group/fs-download.js +67 -2
  17. package/dist/aun/group/history.js +20 -5
  18. package/dist/aun/group/operations.js +8 -2
  19. package/dist/aun/menu/client.js +39 -2
  20. package/dist/aun/menu/transport.js +1 -1
  21. package/dist/aun/msg/history.js +43 -4
  22. package/dist/aun/msg/managed-operation.js +30 -53
  23. package/dist/aun/msg/p2p.js +51 -15
  24. package/dist/aun/outbox.js +4 -0
  25. package/dist/channels/aun.js +260 -73
  26. package/dist/channels/daemon.js +6 -4
  27. package/dist/channels/feishu.js +3 -0
  28. package/dist/cli/agent-command.js +161 -223
  29. package/dist/cli/agent-create.js +190 -223
  30. package/dist/cli/agent.js +3 -1
  31. package/dist/cli/aid-bind-command.js +53 -0
  32. package/dist/cli/aun-commands.js +384 -91
  33. package/dist/cli/bench.js +1 -1
  34. package/dist/cli/cli-argv.js +74 -0
  35. package/dist/cli/daemon-commands.js +105 -74
  36. package/dist/cli/diag.js +1 -1
  37. package/dist/cli/fs-command.js +18 -19
  38. package/dist/cli/getmeta-command.js +159 -6
  39. package/dist/cli/index.js +6 -12
  40. package/dist/cli/init-channel.js +8 -55
  41. package/dist/cli/init.js +125 -218
  42. package/dist/cli/queue-command.js +11 -8
  43. package/dist/cli/restart-monitor.js +177 -85
  44. package/dist/cli/session-command.js +1 -1
  45. package/dist/cli/stats.js +7 -5
  46. package/dist/cli/thread-command.js +103 -30
  47. package/dist/cli/trigger-command.js +1 -1
  48. package/dist/cli/watch-msg-diag.js +1 -0
  49. package/dist/config/access-policy.js +2 -2
  50. package/dist/config/boot-log.js +14 -11
  51. package/dist/config/config-field-policy.js +9 -1
  52. package/dist/config/config-manager.js +176 -90
  53. package/dist/config/config-operation-service.js +7 -14
  54. package/dist/config/contact-book-store.js +142 -26
  55. package/dist/config/contact-book.js +4 -2
  56. package/dist/config/lifecycle.js +1 -1
  57. package/dist/config/owner-config.js +96 -0
  58. package/dist/config/peer-role-resolver.js +12 -18
  59. package/dist/config/providers-config.js +382 -0
  60. package/dist/config/providers-import.js +74 -0
  61. package/dist/config/providers-management.js +328 -0
  62. package/dist/config/providers.js +22 -0
  63. package/dist/config/resolved-config-op.js +17 -14
  64. package/dist/config/snapshot.js +28 -16
  65. package/dist/config-store.js +18 -14
  66. package/dist/core/agent-ipc-registration.js +21 -63
  67. package/dist/core/agent-runtime.js +16 -5
  68. package/dist/core/auth/agent-delegation.js +199 -25
  69. package/dist/core/auth/auth-gateway.js +44 -2
  70. package/dist/core/auth/authorization-denial.js +1 -1
  71. package/dist/core/auth/debug-context.js +2 -1
  72. package/dist/core/auth/delegation-authorization.js +2 -2
  73. package/dist/core/auth/delegation-diagnostics.js +4 -1
  74. package/dist/core/auth/operation-authorizer.js +10 -1
  75. package/dist/core/auth/operation-catalog.js +19 -1
  76. package/dist/core/bootstrap-service.js +43 -20
  77. package/dist/core/channel-loader.js +0 -1
  78. package/dist/core/channel-runtime.js +30 -4
  79. package/dist/core/command/agent-control.js +196 -37
  80. package/dist/core/command/cli-intent-parser.js +60 -4
  81. package/dist/core/command/command-handler.js +212 -60
  82. package/dist/core/command/connect-menu.js +1 -0
  83. package/dist/core/command/diag-service.js +5 -4
  84. package/dist/core/command/file-search.js +368 -0
  85. package/dist/core/command/menu-catalog.js +2 -1
  86. package/dist/core/command/menu-handler.js +292 -107
  87. package/dist/core/command/menu-protocol.js +23 -0
  88. package/dist/core/command/role-menu.js +7 -19
  89. package/dist/core/command/skill-slash.js +130 -25
  90. package/dist/core/command/slash-handler.js +101 -53
  91. package/dist/core/command/topic-slash.js +16 -5
  92. package/dist/core/daemon-bootstrap.js +7 -1
  93. package/dist/core/daemon-control.js +19 -1
  94. package/dist/core/daemon-lifecycle.js +2 -1
  95. package/dist/core/event-catalog.js +2 -0
  96. package/dist/core/evolagent-registry.js +13 -2
  97. package/dist/core/evolagent.js +3 -6
  98. package/dist/core/handoff/ipc-registration.js +2 -2
  99. package/dist/core/handoff/runtime.js +40 -6
  100. package/dist/core/handoff/store.js +1 -0
  101. package/dist/core/inference/text-inference.js +95 -6
  102. package/dist/core/ipc-registration.js +43 -24
  103. package/dist/core/memory/consolidation-context.js +4 -1
  104. package/dist/core/memory/consolidation.js +25 -14
  105. package/dist/core/message/aun-ipc-sender.js +296 -88
  106. package/dist/core/message/aun-request-registry.js +226 -0
  107. package/dist/core/message/create-status.js +33 -4
  108. package/dist/core/message/message-bridge.js +62 -13
  109. package/dist/core/message/message-log.js +0 -1
  110. package/dist/core/message/message-queue.js +4 -1
  111. package/dist/core/message/message-utils.js +8 -1
  112. package/dist/core/message/peer-mode.js +17 -9
  113. package/dist/core/message/response-engine.js +266 -124
  114. package/dist/core/message/retry-scheduler.js +11 -8
  115. package/dist/core/model/catalog-service.js +60 -18
  116. package/dist/core/model/model-catalog.js +112 -9
  117. package/dist/core/model/model-fallback.js +94 -26
  118. package/dist/core/ownership/owner-resolver.js +687 -0
  119. package/dist/core/permission/approval-gateway.js +196 -62
  120. package/dist/core/permission/ec-command-parser.js +138 -25
  121. package/dist/core/permission/filesystem-approval-scope.js +141 -0
  122. package/dist/core/permission/protected-paths.js +14 -1
  123. package/dist/core/permission/sandbox-runtime.js +171 -18
  124. package/dist/core/permission/tool-policy.js +13 -2
  125. package/dist/core/role/runtime-policy.js +3 -4
  126. package/dist/core/session/session-idle-monitor.js +10 -8
  127. package/dist/core/session/session-manager.js +72 -45
  128. package/dist/core/session/session-mapper.js +18 -7
  129. package/dist/core/session/session-renew.js +5 -4
  130. package/dist/core/session/session-title.js +2 -1
  131. package/dist/core/session/session-turn-coordinator.js +4 -2
  132. package/dist/core/skill/index-generator.js +2 -16
  133. package/dist/core/task/task-recovery-service.js +15 -2
  134. package/dist/core/thread/command-parser.js +28 -13
  135. package/dist/core/thread/index.js +1 -0
  136. package/dist/core/thread/ipc-operation.js +11 -8
  137. package/dist/core/thread/thread-operation.js +79 -44
  138. package/dist/core/thread/thread-route.js +54 -6
  139. package/dist/core/thread/thread-service.js +236 -45
  140. package/dist/core/thread/topic-id.js +248 -0
  141. package/dist/eck/kit-renderer.js +1 -1
  142. package/dist/eck/message-renderer.js +12 -2
  143. package/dist/index.js +50 -27
  144. package/dist/ipc.js +59 -6
  145. package/dist/paths.js +1 -0
  146. package/dist/product.js +1 -0
  147. package/dist/response-system/engines/v1/proactive-flow.js +10 -3
  148. package/dist/stats/db.js +8 -0
  149. package/dist/stats/writer.js +26 -4
  150. package/dist/trigger/authorization.js +48 -3
  151. package/dist/trigger/feedback.js +28 -10
  152. package/dist/trigger/ipc-service.js +86 -10
  153. package/dist/trigger/local-files.js +55 -23
  154. package/dist/trigger/scheduler.js +57 -11
  155. package/dist/trigger/system-definitions.js +1 -0
  156. package/dist/trigger/validation.js +44 -15
  157. package/dist/utils/aid-bind.js +17 -297
  158. package/dist/utils/avatar-upload.js +16 -4
  159. package/dist/utils/cross-platform.js +115 -36
  160. package/dist/utils/ecweb-supervisor.js +1 -1
  161. package/dist/utils/ecweb-utils.js +8 -1
  162. package/dist/utils/error-utils.js +44 -6
  163. package/dist/utils/instance-registry.js +12 -2
  164. package/dist/utils/npm-ops.js +102 -27
  165. package/dist/utils/package-version-info.js +72 -27
  166. package/dist/utils/restart-safety.js +50 -0
  167. package/dist/utils/upgrade-jobs.js +304 -0
  168. package/dist/utils/windows-autostart.js +17 -10
  169. package/kits/docs/INDEX.md +5 -0
  170. package/kits/docs/aun/ABILITY_CALLING.md +239 -0
  171. package/kits/docs/evolcore/INDEX.md +14 -3
  172. package/kits/docs/evolcore/agent-create.md +55 -141
  173. package/kits/docs/evolcore/agent.md +38 -16
  174. package/kits/docs/evolcore/aid.md +34 -0
  175. package/kits/docs/evolcore/config.md +20 -1
  176. package/kits/docs/evolcore/event.md +2 -2
  177. package/kits/docs/evolcore/fs.md +18 -2
  178. package/kits/docs/evolcore/group-collaboration.md +13 -246
  179. package/kits/docs/evolcore/group.md +2 -1
  180. package/kits/docs/evolcore/memory-consolidation-batches.md +1 -1
  181. package/kits/docs/evolcore/menu-protocol.md +65 -1
  182. package/kits/docs/evolcore/msg.md +64 -2
  183. package/kits/docs/evolcore/skill.md +86 -0
  184. package/kits/docs/evolcore/stats.md +6 -0
  185. package/kits/docs/evolcore/task.md +2 -0
  186. package/kits/docs/evolcore/thread-chain.md +145 -51
  187. package/kits/docs/evolcore/trigger.md +48 -3
  188. package/kits/eck_manifest.json +21 -0
  189. package/kits/eck_message_manifest.json +4 -4
  190. package/kits/migrations/migrate-role-config-v5.mjs +20 -7
  191. package/kits/rules/01-overview.md +2 -1
  192. package/kits/rules/03-identity.md +3 -1
  193. package/kits/rules/04-relation.md +1 -1
  194. package/kits/rules/07-memory-and-thread.md +1 -1
  195. package/kits/schemas/_meta.json +16 -1
  196. package/kits/schemas/agent-config.schema.13.json +44 -2
  197. package/kits/schemas/contact-book.schema.3.json +5 -0
  198. package/kits/schemas/daemon.schema.6.json +3 -1
  199. package/kits/schemas/daemon.schema.8.json +329 -0
  200. package/kits/schemas/defaults.schema.5.json +39 -3
  201. package/kits/schemas/providers.schema.1.json +76 -0
  202. package/kits/schemas/relation-config.schema.9.json +5 -1
  203. package/kits/skills/.switches.json +28 -0
  204. package/kits/skills/aid-ability/SKILL.md +214 -0
  205. package/kits/skills/ec-agent-create/SKILL.md +162 -0
  206. package/kits/skills/ec-group/SKILL.md +2 -1
  207. package/kits/skills/ec-group-collaboration/SKILL.md +228 -0
  208. package/kits/skills/ec-identity/SKILL.md +143 -0
  209. package/kits/skills/ec-skill/scripts/cli.mjs +36 -3
  210. package/kits/skills/ec-skill/scripts/skill.mjs +202 -15
  211. package/kits/skills/ec-trigger/SKILL.md +167 -0
  212. package/kits/skills/ec-trigger/references/examples.md +153 -0
  213. package/kits/skills/thread-ops/SKILL.md +108 -420
  214. package/kits/templates/message-fragments/thread-context.md +1 -2
  215. package/kits/templates/message-fragments/trigger-thread-check.md +1 -1
  216. package/kits/templates/roles/member.json +1 -1
  217. package/kits/templates/roles/visitor.json +1 -0
  218. package/kits/templates/system-fragments/bootstrap.md +1 -0
  219. package/kits/templates/system-fragments/channel.md +2 -1
  220. package/kits/templates/system-fragments/commands.md +5 -4
  221. package/kits/templates/system-fragments/identity.md +2 -0
  222. package/kits/templates/system-fragments/session.md +1 -1
  223. package/kits/templates/system-fragments/venue.md +1 -1
  224. package/package.json +4 -3
@@ -32,6 +32,9 @@ import { writeRoleDefinition, writeRoleRegistry } from './role-service.js';
32
32
  import { mutateContactBookTransactionSync } from './contact-book-store.js';
33
33
  import { DAEMON_SCHEMA_NAME } from '../product.js';
34
34
  import { normalizeDaemonServices } from './daemon-services.js';
35
+ import { assertProviderSelectionWrite, resolveProviderModelOverrides, validateProvidersFile } from './providers-config.js';
36
+ import { assertOwnerConfigRecord, configuredOwnerList, firstConfiguredOwner, normalizeOwnerConfigTree, withOwnerListCompatibility } from './owner-config.js';
37
+ import { getOwners, getPrimaryOwner, ownerResolver } from '../core/ownership/owner-resolver.js';
35
38
  // AUN gateway discovery belongs to fastaun. Keep the retired gatewayUrl
36
39
  // compatibility handling beside the rest of the configuration read/write
37
40
  // policy so all config callers share one implementation.
@@ -105,25 +108,15 @@ export function shouldFailFastForMissingOwners(env = process.env) {
105
108
  return value === '1' || value === 'true' || value === 'yes';
106
109
  }
107
110
  export function validAgentOwnerIds(agent) {
108
- return [...new Set((agent?.owners ?? [])
109
- .map(value => String(value || '').trim())
110
- .filter(isValidAid))];
111
+ return agent?.aid ? getOwners(agent.aid) : [];
111
112
  }
112
113
  export function validateAccessPolicyDomain(selfAid, agent) {
113
- const access = agent.access;
114
- if (!access)
115
- return undefined;
116
- if (access.policyMode !== 'open' && validAgentOwnerIds(agent).length === 0) {
117
- return {
118
- code: 'restricted_mode_without_owner',
119
- message: `Agent ${selfAid} cannot use ${access.policyMode} without an Owner`,
120
- };
121
- }
122
114
  return undefined;
123
115
  }
124
116
  export var ConfigTarget;
125
117
  (function (ConfigTarget) {
126
118
  ConfigTarget["Process"] = "process";
119
+ ConfigTarget["Providers"] = "providers";
127
120
  ConfigTarget["Defaults"] = "defaults";
128
121
  ConfigTarget["Agent"] = "agent";
129
122
  ConfigTarget["Relation"] = "relation";
@@ -141,6 +134,7 @@ export function onConfigWrite(listener) {
141
134
  }
142
135
  const TARGET_SCHEMA = {
143
136
  [ConfigTarget.Process]: DAEMON_SCHEMA_NAME,
137
+ [ConfigTarget.Providers]: 'providers',
144
138
  [ConfigTarget.Defaults]: 'defaults',
145
139
  [ConfigTarget.Agent]: 'agent-config',
146
140
  [ConfigTarget.Relation]: 'relation-config',
@@ -175,14 +169,15 @@ export function assertRoleConfigV4Ready() {
175
169
  const agentsRoot = resolvePaths().agentsDir;
176
170
  if (!fs.existsSync(agentsRoot))
177
171
  return;
178
- const issues = [];
172
+ const migrationTargets = [];
173
+ const degraded = [];
179
174
  const inspect = (file, logicalName, minimumVersion) => {
180
175
  let value;
181
176
  try {
182
177
  value = JSON.parse(fs.readFileSync(file, 'utf8'));
183
178
  }
184
179
  catch (error) {
185
- issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
180
+ degraded.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
186
181
  return;
187
182
  }
188
183
  value = stripLegacyAunGatewayConfig(value, file, message => configWarn(message));
@@ -190,7 +185,8 @@ export function assertRoleConfigV4Ready() {
190
185
  ? value.$schema_version
191
186
  : undefined;
192
187
  if (typeof actualVersion !== 'number' || actualVersion < minimumVersion) {
193
- issues.push({
188
+ // 旧形态才是迁移目标;其余问题只影响该文件对应的 agent/relation。
189
+ migrationTargets.push({
194
190
  file,
195
191
  detail: `$schema_version must be at least ${minimumVersion}, got ${String(actualVersion)}`,
196
192
  });
@@ -201,7 +197,7 @@ export function assertRoleConfigV4Ready() {
201
197
  schema = loadSchema(logicalName, actualVersion);
202
198
  }
203
199
  catch {
204
- issues.push({ file, detail: `unsupported ${logicalName} schema version ${actualVersion}` });
200
+ degraded.push({ file, detail: `unsupported ${logicalName} schema version ${actualVersion}` });
205
201
  return;
206
202
  }
207
203
  if (!schema.validate(value)) {
@@ -211,7 +207,7 @@ export function assertRoleConfigV4Ready() {
211
207
  : '';
212
208
  return `${error.instancePath || '/'} ${error.message}${extra}`;
213
209
  }).join('; ');
214
- issues.push({ file, detail });
210
+ degraded.push({ file, detail });
215
211
  }
216
212
  };
217
213
  const walkRelationConfigs = (dir) => {
@@ -239,33 +235,45 @@ export function assertRoleConfigV4Ready() {
239
235
  inspect(relationFile, 'relation-config', 4);
240
236
  }
241
237
  }
242
- if (!issues.length)
238
+ if (degraded.length) {
239
+ configWarn(`[config] ${degraded.length} config issue(s) degraded to warnings; the daemon keeps starting:\n`
240
+ + degraded.slice(0, 20).map(issue => ` - ${path.relative(root, issue.file)}: ${issue.detail}`).join('\n'));
241
+ }
242
+ if (!migrationTargets.length)
243
243
  return;
244
- const shown = issues.slice(0, 20)
244
+ const shown = migrationTargets.slice(0, 20)
245
245
  .map(issue => `- ${path.relative(root, issue.file)}: ${issue.detail}`)
246
246
  .join('\n');
247
- const omitted = issues.length > 20 ? `\n- ... ${issues.length - 20} more issue(s)` : '';
247
+ const omitted = migrationTargets.length > 20 ? `\n- ... ${migrationTargets.length - 20} more issue(s)` : '';
248
248
  const quotedRoot = JSON.stringify(root);
249
249
  throw new ConfigError('ROLE_CONFIG_MIGRATION_REQUIRED', `Role config v4 migration is required before daemon startup:\n${shown}${omitted}\n` +
250
250
  `Stop the daemon, then run dry-run and review it:\n` +
251
251
  `node kits/migrations/migrate-role-config-v4.mjs --home ${quotedRoot}\n` +
252
252
  `Apply only after review:\n` +
253
- `node kits/migrations/migrate-role-config-v4.mjs --apply --home ${quotedRoot}`, { issues });
253
+ `node kits/migrations/migrate-role-config-v4.mjs --apply --home ${quotedRoot}`, { issues: migrationTargets });
254
254
  }
255
- /** Validate the standalone role domain after the v4 -> v5 migration. */
255
+ /**
256
+ * Validate the standalone role domain after the v4 -> v5 migration.
257
+ *
258
+ * 只有「确实需要迁移」的 Agent 才阻止 daemon 启动(仍是旧形态:schema < 5,
259
+ * 或 v5 还内联 roles 定义)。单个 Agent 的形态问题——坏 JSON、未来版本号、
260
+ * 缺/坏 roles/index.json——只告警:这类 Agent 由运行时回退注册表和
261
+ * error-agent 加载路径各自降级,不拖累整个进程。
262
+ */
256
263
  export function assertRoleConfigV5Ready() {
257
264
  const root = resolvePaths().root;
258
265
  const agentsRoot = resolvePaths().agentsDir;
259
266
  if (!fs.existsSync(agentsRoot))
260
267
  return;
261
- const issues = [];
268
+ const migrationTargets = [];
269
+ const degraded = [];
262
270
  const registrySchema = loadSchema('role-registry');
263
- const readJson = (file) => {
271
+ const readJson = (file, sink) => {
264
272
  try {
265
273
  return JSON.parse(fs.readFileSync(file, 'utf8'));
266
274
  }
267
275
  catch (error) {
268
- issues.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
276
+ sink.push({ file, detail: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` });
269
277
  return null;
270
278
  }
271
279
  };
@@ -278,46 +286,51 @@ export function assertRoleConfigV5Ready() {
278
286
  const agentFile = path.join(agentsRoot, aid, 'config.json');
279
287
  if (!fs.existsSync(agentFile))
280
288
  continue;
281
- const agent = readJson(agentFile);
282
- const sanitizedAgent = agent
283
- ? stripLegacyAunGatewayConfig(agent, agentFile, message => configWarn(message))
284
- : agent;
289
+ const agent = readJson(agentFile, degraded);
290
+ if (!agent)
291
+ continue;
292
+ const sanitizedAgent = stripLegacyAunGatewayConfig(agent, agentFile, message => configWarn(message));
285
293
  const agentVersion = sanitizedAgent?.$schema_version;
294
+ if (typeof agentVersion !== 'number' || agentVersion < 5) {
295
+ migrationTargets.push({
296
+ file: agentFile,
297
+ detail: `agent schema version must be supported and >= 5, got ${String(agentVersion)}`,
298
+ });
299
+ continue;
300
+ }
286
301
  let agentSchema = null;
287
- if (typeof agentVersion === 'number' && agentVersion >= 5) {
288
- try {
289
- agentSchema = loadSchema('agent-config', agentVersion);
290
- }
291
- catch { }
292
- }
293
- if (!sanitizedAgent || !agentSchema || !agentSchema.validate(sanitizedAgent)) {
294
- if (sanitizedAgent)
295
- issues.push({
296
- file: agentFile,
297
- detail: agentSchema
298
- ? schemaErrors(agentSchema)
299
- : `agent schema version must be supported and >= 5, got ${String(agentVersion)}`,
300
- });
302
+ try {
303
+ agentSchema = loadSchema('agent-config', agentVersion);
304
+ }
305
+ catch { }
306
+ if (!agentSchema) {
307
+ degraded.push({ file: agentFile, detail: `unsupported agent-config schema version ${agentVersion}` });
308
+ continue;
309
+ }
310
+ if (!agentSchema.validate(sanitizedAgent)) {
311
+ degraded.push({ file: agentFile, detail: schemaErrors(agentSchema) });
301
312
  continue;
302
313
  }
303
314
  const registryFile = agentRolesIndex(aid);
304
315
  if (!fs.existsSync(registryFile)) {
305
- issues.push({ file: registryFile, detail: 'roles/index.json is required for Agent config v5' });
316
+ degraded.push({ file: registryFile, detail: 'roles/index.json is missing (runtime falls back to the builtin role set)' });
306
317
  continue;
307
318
  }
308
- const registry = readJson(registryFile);
309
- if (!registry || !registrySchema.validate(registry)) {
310
- if (registry)
311
- issues.push({ file: registryFile, detail: schemaErrors(registrySchema) });
312
- continue;
319
+ const registry = readJson(registryFile, degraded);
320
+ if (registry && !registrySchema.validate(registry)) {
321
+ degraded.push({ file: registryFile, detail: schemaErrors(registrySchema) });
313
322
  }
314
323
  }
315
- if (!issues.length)
324
+ if (degraded.length) {
325
+ configWarn(`[config] ${degraded.length} agent config issue(s) degraded to warnings; the daemon keeps starting:\n`
326
+ + degraded.slice(0, 20).map(issue => ` - ${path.relative(root, issue.file)}: ${issue.detail}`).join('\n'));
327
+ }
328
+ if (!migrationTargets.length)
316
329
  return;
317
- const shown = issues.slice(0, 20).map(issue => `- ${path.relative(root, issue.file)}: ${issue.detail}`).join('\n');
330
+ const shown = migrationTargets.slice(0, 20).map(issue => `- ${path.relative(root, issue.file)}: ${issue.detail}`).join('\n');
318
331
  throw new ConfigError('ROLE_CONFIG_V5_MIGRATION_REQUIRED', `Standalone role config migration is required before daemon startup:\n${shown}\n` +
319
332
  `node kits/migrations/migrate-role-config-v5.mjs --home ${JSON.stringify(root)}\n` +
320
- `node kits/migrations/migrate-role-config-v5.mjs --apply --home ${JSON.stringify(root)}`, { issues });
333
+ `node kits/migrations/migrate-role-config-v5.mjs --apply --home ${JSON.stringify(root)}`, { issues: migrationTargets });
321
334
  }
322
335
  /** Final startup gate for the contact-book authorization migration. */
323
336
  export function assertContactBookV2Ready() {
@@ -410,7 +423,8 @@ function inspectContactBookDomain(contactFile, agentFile, issues) {
410
423
  catch {
411
424
  return;
412
425
  }
413
- const owners = new Set(Array.isArray(agent?.owners) ? agent.owners.map(String) : []);
426
+ const configuredOwners = getOwners(String(agent?.aid ?? ''));
427
+ const owners = new Set(configuredOwners);
414
428
  const configuredChannelKeys = new Set((Array.isArray(agent?.channels) ? agent.channels : [])
415
429
  .filter((channel) => channel && typeof channel === 'object' && channel.type !== 'aun')
416
430
  .map((channel) => {
@@ -521,6 +535,10 @@ export function checkSchemaVersionsOnStartup(selfAid) {
521
535
  read(ConfigTarget.Process);
522
536
  }
523
537
  catch { }
538
+ try {
539
+ read(ConfigTarget.Providers);
540
+ }
541
+ catch { }
524
542
  // 检查 defaults 层
525
543
  try {
526
544
  read(ConfigTarget.Defaults);
@@ -545,6 +563,7 @@ function targetPath(target, sel) {
545
563
  const p = resolvePaths();
546
564
  switch (target) {
547
565
  case ConfigTarget.Process: return p.daemonConfig;
566
+ case ConfigTarget.Providers: return path.resolve(sel?.providerFile ?? p.providersConfig);
548
567
  case ConfigTarget.Defaults: return p.defaultsConfig;
549
568
  case ConfigTarget.Agent:
550
569
  requireSelf(sel, target);
@@ -593,6 +612,11 @@ export function read(target, sel, opts = {}) {
593
612
  else {
594
613
  raw = atomicReadJson(file);
595
614
  }
615
+ if (target === ConfigTarget.Providers) {
616
+ if (raw === null && !fs.existsSync(file) && !fs.existsSync(`${file}_`))
617
+ return null;
618
+ return normalizeProvidersConfig(migrateIfNeeded(target, raw, file));
619
+ }
596
620
  if (raw === null)
597
621
  return null;
598
622
  raw = stripLegacyAunGatewayConfig(raw, file, message => configWarn(message));
@@ -641,7 +665,7 @@ function normalizeBaseagentEffortCompat(value) {
641
665
  return { ...value, baseagents };
642
666
  }
643
667
  function normalizeAgentConfigForRead(value, aid) {
644
- const config = foldLegacyProactiveBlock(normalizeAgentLifecycle(value));
668
+ const config = withOwnerListCompatibility(normalizeOwnerConfigTree(foldLegacyProactiveBlock(normalizeAgentLifecycle(value))));
645
669
  const mutable = config;
646
670
  normalizeShowActivitiesCompat(mutable);
647
671
  // AID 校验(如果提供了 aid)
@@ -678,24 +702,29 @@ function groupFor(target, sel) {
678
702
  }
679
703
  return 'config';
680
704
  }
705
+ function normalizeProvidersConfig(value) {
706
+ try {
707
+ return validateProvidersFile(value);
708
+ }
709
+ catch (error) {
710
+ throw new ConfigError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error));
711
+ }
712
+ }
681
713
  function normalizeAgentConfigForWrite(value) {
682
- const mutable = normalizeBaseagentEffortCompat({ ...value });
683
- normalizeShowActivitiesCompat(mutable);
684
- if (Object.prototype.hasOwnProperty.call(mutable, 'owners')) {
685
- if (!Array.isArray(mutable.owners)) {
686
- throw new ConfigError('VALIDATION_ERROR', 'owners must be an array');
687
- }
688
- const owners = [];
689
- for (const rawOwner of mutable.owners) {
690
- if (typeof rawOwner !== 'string' || !rawOwner.trim() || !isValidAid(rawOwner.trim())) {
691
- throw new ConfigError('VALIDATION_ERROR', `Invalid owner AID: ${JSON.stringify(rawOwner)}`);
692
- }
693
- const owner = rawOwner.trim();
694
- if (!owners.includes(owner))
695
- owners.push(owner);
714
+ try {
715
+ assertOwnerConfigRecord(value, 'agent config', isValidAid);
716
+ for (const [index, channel] of (Array.isArray(value.channels) ? value.channels : []).entries()) {
717
+ const channelType = channel && typeof channel === 'object' && !Array.isArray(channel)
718
+ ? String(channel.type || '').trim().toLowerCase()
719
+ : undefined;
720
+ assertOwnerConfigRecord(channel, `channels[${index}]`, channelType === 'aun' ? isValidAid : undefined);
696
721
  }
697
- mutable.owners = owners;
698
722
  }
723
+ catch (error) {
724
+ throw new ConfigError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error));
725
+ }
726
+ const mutable = normalizeOwnerConfigTree(normalizeBaseagentEffortCompat({ ...value }));
727
+ normalizeShowActivitiesCompat(mutable);
699
728
  // AID 格式校验
700
729
  if (mutable.aid && !isValidAid(mutable.aid)) {
701
730
  throw new ConfigError('VALIDATION_ERROR', `Invalid aid "${mutable.aid}" (must be a valid multi-level domain like mybot.agentid.pub)`);
@@ -717,6 +746,12 @@ function normalizeAgentConfigForWrite(value) {
717
746
  return mutable;
718
747
  }
719
748
  function normalizeProcessConfigForWrite(value) {
749
+ try {
750
+ assertOwnerConfigRecord(value, 'daemon.json', isValidAid);
751
+ }
752
+ catch (error) {
753
+ throw new ConfigError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error));
754
+ }
720
755
  let normalized;
721
756
  try {
722
757
  normalized = normalizeProcessConfigCompat(normalizeDaemonServices(value));
@@ -747,7 +782,7 @@ export function normalizeProcessConfigCompat(value) {
747
782
  const idleMonitor = value.idleMonitor;
748
783
  const debug = value.debug;
749
784
  let changed = false;
750
- let normalized = value;
785
+ let normalized = normalizeOwnerConfigTree(value, { projectChannels: false });
751
786
  if (Object.prototype.hasOwnProperty.call(normalized, 'fullaccess')) {
752
787
  normalized = { ...normalized };
753
788
  delete normalized.fullaccess;
@@ -860,10 +895,24 @@ export function write(target, value, sel, opts = {}) {
860
895
  ? normalizeRelationConfigForWrite(canonicalized)
861
896
  : target === ConfigTarget.Process
862
897
  ? normalizeProcessConfigForWrite(canonicalized)
863
- : canonicalized;
898
+ : target === ConfigTarget.Providers
899
+ ? normalizeProvidersConfig(canonicalized)
900
+ : canonicalized;
864
901
  // 1. Schema 校验
865
902
  if (!opts.skipValidate) {
866
903
  validateOrThrow(schema, normalized, target);
904
+ if (target === ConfigTarget.Defaults || target === ConfigTarget.Agent || target === ConfigTarget.Relation) {
905
+ const previous = atomicReadJson(file) ?? {};
906
+ const inherited = target === ConfigTarget.Defaults ? {}
907
+ : target === ConfigTarget.Agent ? read(ConfigTarget.Defaults, undefined, { cache: false }) ?? {}
908
+ : resolveAgentConfig({ self: sel?.self }, { cache: false });
909
+ try {
910
+ assertProviderSelectionWrite(normalized, previous, inherited, (providerId, serviceType) => resolveProviderModelOverrides(providerId, serviceType, read(ConfigTarget.Providers) ?? { providers: {} }));
911
+ }
912
+ catch (error) {
913
+ throw new ConfigError('VALIDATION_ERROR', error instanceof Error ? error.message : String(error));
914
+ }
915
+ }
867
916
  // 1b. responseModeParams 桶内容校验:主 schema 把 responseModeParams 当黑箱,
868
917
  // 此处使用“模式桶 schema”逐桶校验内容(桶键 = 已登记的响应模式 id)。
869
918
  if (target === ConfigTarget.Defaults || target === ConfigTarget.Agent || target === ConfigTarget.Relation) {
@@ -876,30 +925,48 @@ export function write(target, value, sel, opts = {}) {
876
925
  throw new ConfigError(issue.code, issue.message);
877
926
  }
878
927
  }
879
- // Owner 是实际联系人。将 Agent 配置和缺失的 Owner 联系人写入同一可恢复事务,
880
- // 避免渠道身份绑定承担联系人创建职责。
881
- const ownerIds = target === ConfigTarget.Agent && Array.isArray(normalized.owners)
882
- ? [...new Set(normalized.owners
883
- .map((value) => String(value || '').trim())
884
- .filter((value) => isValidAid(value)))]
885
- : [];
886
- if (target === ConfigTarget.Agent && sel?.self && ownerIds.length > 0) {
928
+ if (target === ConfigTarget.Contact && sel?.self) {
929
+ const contact = { ...normalized, $schema_version: currentVersion('contact-book') };
930
+ mutateContactBookTransactionSync({
931
+ selfAid: sel.self,
932
+ actor: 'contact-config-write',
933
+ mutation: { type: 'replace', contact },
934
+ }, []);
935
+ return;
936
+ }
937
+ if ((target === ConfigTarget.Agent || target === ConfigTarget.Relation) && sel?.self) {
938
+ const syncAid = sel.self;
939
+ if (target === ConfigTarget.Relation && sel.peerKey) {
940
+ ensureRelationSharedDirectory(syncAid, sel.peerKey);
941
+ }
887
942
  const before = readTextOrNull(file);
888
943
  const after = `${JSON.stringify(normalized, null, 2)}\n`;
889
944
  mutateContactBookTransactionSync({
890
- selfAid: sel.self,
891
- actor: 'agent-owner-sync',
892
- mutation: { type: 'ensure-contacts', primaryIds: ownerIds },
945
+ selfAid: syncAid,
946
+ actor: target === ConfigTarget.Agent ? 'agent-management-role-sync' : 'relation-management-role-sync',
947
+ mutation: { type: 'sync-management-roles' },
893
948
  }, [{ file, before, after }]);
949
+ if (target === ConfigTarget.Agent) {
950
+ ownerResolver.setAgentConfiguredOwners(syncAid, configuredOwnerList(normalized));
951
+ }
894
952
  finishConfigWrite(target, sel, file);
895
953
  return;
896
954
  }
897
955
  // 2. 写入文件
898
- fs.mkdirSync(path.dirname(file), { recursive: true });
899
- if (target === ConfigTarget.Relation && sel?.self && sel.peerKey) {
900
- ensureRelationSharedDirectory(sel.self, sel.peerKey);
901
- }
956
+ fs.mkdirSync(path.dirname(file), { recursive: true, ...(target === ConfigTarget.Providers ? { mode: 0o700 } : {}) });
902
957
  atomicWriteJson(file, normalized);
958
+ if (target === ConfigTarget.Providers && process.platform !== 'win32') {
959
+ for (const protectedFile of [file, `${file}_`]) {
960
+ if (fs.existsSync(protectedFile))
961
+ fs.chmodSync(protectedFile, 0o600);
962
+ }
963
+ }
964
+ if (target === ConfigTarget.Agent && sel?.self) {
965
+ ownerResolver.setAgentConfiguredOwners(sel.self, configuredOwnerList(normalized));
966
+ }
967
+ if (target === ConfigTarget.Process && typeof normalized?.aid === 'string') {
968
+ ownerResolver.setConfiguredOwners(normalized.aid, configuredOwnerList(normalized));
969
+ }
903
970
  finishConfigWrite(target, sel, file);
904
971
  }
905
972
  /**
@@ -1206,6 +1273,8 @@ export function validateConfig(target, value) {
1206
1273
  try {
1207
1274
  if (target === ConfigTarget.Process)
1208
1275
  normalized = normalizeProcessConfigCompat(normalizeDaemonServices(withVer));
1276
+ if (target === ConfigTarget.Providers)
1277
+ normalized = normalizeProvidersConfig(withVer);
1209
1278
  }
1210
1279
  catch (error) {
1211
1280
  return [error instanceof Error ? error.message : String(error)];
@@ -1239,8 +1308,12 @@ export function validateConfig(target, value) {
1239
1308
  export function validateConfigFile(target, sel) {
1240
1309
  const file = targetPath(target, sel);
1241
1310
  const value = atomicReadJson(file);
1242
- if (value === null)
1311
+ if (value === null) {
1312
+ if (target === ConfigTarget.Providers && fs.existsSync(file)) {
1313
+ return { exists: true, errors: validateConfig(target, value) };
1314
+ }
1243
1315
  return { exists: false, errors: [] };
1316
+ }
1244
1317
  // Persisted pre-removal files remain readable: validate the same sanitized
1245
1318
  // view used by runtime reads, while new writes are still rejected by write().
1246
1319
  let sanitized;
@@ -1269,6 +1342,10 @@ export function ensureFile(target, sel) {
1269
1342
  if (fs.existsSync(file))
1270
1343
  return;
1271
1344
  const skeleton = configSkeleton(target, sel);
1345
+ if (target === ConfigTarget.Providers) {
1346
+ write(target, skeleton, sel);
1347
+ return;
1348
+ }
1272
1349
  fs.mkdirSync(path.dirname(file), { recursive: true });
1273
1350
  atomicWriteJson(file, skeleton);
1274
1351
  }
@@ -1323,6 +1400,12 @@ export function resolveAgentConfig(sel, opts = {}) {
1323
1400
  // gateway credentials and endpoint. Keep the generic dictionary merge
1324
1401
  // contract unchanged for every other field.
1325
1402
  merged.baseagents = mergeBaseagentBlocks(layers);
1403
+ merged.Services = layers.reduce((selections, layer) => {
1404
+ for (const [serviceType, selection] of Object.entries(layer?.Services ?? {})) {
1405
+ selections[serviceType] = { ...selections[serviceType], ...selection };
1406
+ }
1407
+ return selections;
1408
+ }, {});
1326
1409
  return merged;
1327
1410
  }
1328
1411
  function mergeBaseagentBlocks(layers) {
@@ -1371,7 +1454,8 @@ export function normalizeDefaultsBehaviorCompat(defaults) {
1371
1454
  function stripStaticOwnerField(config) {
1372
1455
  if (!config)
1373
1456
  return config;
1374
- if (!Object.prototype.hasOwnProperty.call(config, 'owners')
1457
+ if (!Object.prototype.hasOwnProperty.call(config, 'owner')
1458
+ && !Object.prototype.hasOwnProperty.call(config, 'owners')
1375
1459
  && !Object.prototype.hasOwnProperty.call(config, 'admins')
1376
1460
  && !Object.prototype.hasOwnProperty.call(config, 'access')
1377
1461
  && !Object.prototype.hasOwnProperty.call(config, 'groupSlashRequireMentions')
@@ -1380,7 +1464,7 @@ function stripStaticOwnerField(config) {
1380
1464
  // fullaccess and groupSlashRequireMentions are Agent-only. Relation/defaults
1381
1465
  // files allow forward-compatible unknown properties, so strip it before the
1382
1466
  // generic merge chain can treat a manually written value as an override.
1383
- const { owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, fullaccess: _fullaccess, ...rest } = config;
1467
+ const { owner: _owner, owners: _owners, admins: _admins, access: _access, groupSlashRequireMentions: _groupSlashRequireMentions, fullaccess: _fullaccess, ...rest } = config;
1384
1468
  return rest;
1385
1469
  }
1386
1470
  function stripRelationRoleData(config) {
@@ -1406,7 +1490,8 @@ export function resolveEffective(sel, opts = {}) {
1406
1490
  lifecycle: config.lifecycle,
1407
1491
  initialized: config.initialized,
1408
1492
  initialization: config.initialization,
1409
- owners: config.owners,
1493
+ owner: getPrimaryOwner(sel.self ?? config.aid ?? '') ?? firstConfiguredOwner(config),
1494
+ owners: [...new Set([...getOwners(sel.self ?? config.aid ?? ''), ...configuredOwnerList(config)])],
1410
1495
  fullaccess: config.fullaccess,
1411
1496
  aun: config.aun,
1412
1497
  decision_model: config.decision_model,
@@ -1422,6 +1507,7 @@ export function resolveEffective(sel, opts = {}) {
1422
1507
  // Runtime configuration parameters
1423
1508
  active_baseagent: config.active_baseagent,
1424
1509
  baseagents: config.baseagents,
1510
+ Services: config.Services,
1425
1511
  chatmode: config.chatmode,
1426
1512
  mentionMode: config.mentionMode,
1427
1513
  autoMentionQuietWindowMs: config.autoMentionQuietWindowMs
@@ -394,6 +394,13 @@ function executeGet(op, options) {
394
394
  ...(note ? { note } : {}),
395
395
  };
396
396
  }
397
+ // Management fields (e.g. 'owners') are computed by resolveEffective() rather than
398
+ // stored in any config file, so source is always null. op.route is undefined for these
399
+ // cases (routeFieldPath threw but was swallowed because routeRequired=false).
400
+ // Return the effective value directly when it is present.
401
+ if (withSource.value !== undefined && !op.route) {
402
+ return { ok: true, subcommand: 'get', field: op.field, value: withSource.value, scope: op.configScope };
403
+ }
397
404
  return getWithSchemaDefault(op, null, note);
398
405
  }
399
406
  /**
@@ -455,14 +462,6 @@ function executeSet(op, options) {
455
462
  return { ok: false, code: 'UNKNOWN_FIELD', error: `Unknown config field: ${op.field}` };
456
463
  const selector = selectorFor(op, options);
457
464
  const mutation = mutateConfig(op.route.target, selector, existing => {
458
- if (op.field === 'owners' && op.route.target === ConfigTarget.Agent) {
459
- const nextOwners = Array.isArray(op.value) ? op.value : [];
460
- if (nextOwners.length === 0) {
461
- throw new ConfigError('LAST_OWNER_PROTECTED', 'owners must contain at least one owner');
462
- }
463
- setNested(existing, op.field, nextOwners);
464
- return nextOwners;
465
- }
466
465
  if (op.route.merge === 'list') {
467
466
  const previous = getNested(existing, op.field);
468
467
  const previousItems = Array.isArray(previous) ? previous : [];
@@ -506,12 +505,6 @@ function executeUnset(op, options) {
506
505
  const selector = selectorFor(op, options);
507
506
  const target = op.route.target;
508
507
  const { value: removed } = mutateConfig(target, selector, existing => {
509
- if (op.field === 'owners' && target === ConfigTarget.Agent) {
510
- const current = getNested(existing, op.field);
511
- if (Array.isArray(current) && current.length > 0) {
512
- throw new ConfigError('LAST_OWNER_PROTECTED', 'cannot unset owners while an Agent still has owners; use owners replace with another owner');
513
- }
514
- }
515
508
  const parts = op.field.split('.');
516
509
  let current = existing;
517
510
  for (let i = 0; i < parts.length - 1; i++) {