evolcore 0.0.10 → 0.0.12

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 (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
@@ -1,6 +1,5 @@
1
1
  import crypto from 'crypto';
2
2
  import fs from 'fs';
3
- import path from 'path';
4
3
  import { isValidAid } from '../aun/aid/validation.js';
5
4
  import { agentContactOperations } from '../paths.js';
6
5
  import { listStaticAgentAdmins, listStaticAgentOwners } from './peer-role-resolver.js';
@@ -12,6 +11,7 @@ export const CONTACT_REQUEST_MAX_PER_WINDOW = 3;
12
11
  export const CONTACT_REQUEST_MAX_PENDING = 100;
13
12
  export const CONTACT_REQUEST_MAX_RETAINED = 1000;
14
13
  export const CONTACT_REQUEST_NOTE_MAX_LENGTH = 500;
14
+ export const CONTACT_REQUEST_ID_MAX_LENGTH = 128;
15
15
  export async function mutateContactWithOperation(input) {
16
16
  const at = new Date(input.now ?? Date.now()).toISOString();
17
17
  const logFile = agentContactOperations(input.selfAid);
@@ -26,7 +26,7 @@ export async function mutateContactWithOperation(input) {
26
26
  mutation: input.mutation,
27
27
  actor: input.actorId,
28
28
  value: undefined,
29
- beforeCommit: ({ contactRevision: nextRevision }) => appendOperationRecords(logFile, [operationRecord({
29
+ beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
30
30
  at,
31
31
  op: input.operation,
32
32
  primaryId: input.primaryId,
@@ -73,7 +73,17 @@ export async function submitContactRequest(input) {
73
73
  && Date.parse(record.at) > windowStart
74
74
  && Date.parse(record.at) <= nowMs)).length;
75
75
  if (recentCount >= CONTACT_REQUEST_MAX_PER_WINDOW) {
76
- return preparedWithExpirations(expiration, { code: 'rate-limited', contactRevision: '' }, logFile);
76
+ return preparedWithExpirations(expiration, {
77
+ code: 'rate-limited',
78
+ ...(effectiveEntry?.status === 'pending' && effectiveEntry.pendingRequest
79
+ ? {
80
+ requestId: effectiveEntry.pendingRequest.id,
81
+ submittedAt: effectiveEntry.pendingRequest.submittedAt,
82
+ expiresAt: effectiveEntry.pendingRequest.expiresAt,
83
+ }
84
+ : {}),
85
+ contactRevision: '',
86
+ }, logFile);
77
87
  }
78
88
  const counts = countRequestRecordsAfterExpirations(contact, nowMs);
79
89
  const isExistingPending = effectiveEntry?.status === 'pending';
@@ -103,13 +113,14 @@ export async function submitContactRequest(input) {
103
113
  mutation: { type: 'batch', mutations },
104
114
  actor: applicantAid,
105
115
  value,
106
- beforeCommit: ({ contactRevision }) => appendOperationRecords(logFile, [
116
+ beforeCommit: ({ contactRevision }) => operationLogChange(logFile, [
107
117
  ...expiration.events.map(event => ({ ...event, contactRevision })),
108
118
  operationRecord({
109
119
  at: nowIso,
110
120
  op,
111
121
  primaryId: applicantAid,
112
122
  requestId,
123
+ expiresAt,
113
124
  sourceChannelKey: input.sourceChannelKey,
114
125
  ...(note ? { note } : {}),
115
126
  contactRevision,
@@ -143,6 +154,10 @@ export async function reviewContactRequest(input) {
143
154
  if (!entry || entry.status !== 'pending' || entry.pendingRequest?.id !== input.requestId) {
144
155
  return preparedNoop({ code: 'request-stale', contactRevision });
145
156
  }
157
+ const requestMetadata = {
158
+ requestId: entry.pendingRequest.id,
159
+ expiresAt: entry.pendingRequest.expiresAt,
160
+ };
146
161
  if (Date.parse(entry.pendingRequest.expiresAt) <= nowMs) {
147
162
  const mutation = {
148
163
  type: 'expire-request',
@@ -153,8 +168,8 @@ export async function reviewContactRequest(input) {
153
168
  return {
154
169
  mutation,
155
170
  actor: input.actorId,
156
- value: { code: 'request-expired', contactRevision },
157
- beforeCommit: ({ contactRevision: nextRevision }) => appendOperationRecords(logFile, [operationRecord({
171
+ value: { code: 'request-expired', contactRevision, ...requestMetadata },
172
+ beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
158
173
  at: nowIso,
159
174
  op: 'expire',
160
175
  primaryId: input.primaryId,
@@ -177,8 +192,8 @@ export async function reviewContactRequest(input) {
177
192
  now: nowIso,
178
193
  },
179
194
  actor: input.actorId,
180
- value: { code, contactRevision },
181
- beforeCommit: ({ contactRevision: nextRevision }) => appendOperationRecords(logFile, [operationRecord({
195
+ value: { code, contactRevision, ...requestMetadata },
196
+ beforeCommit: ({ contactRevision: nextRevision }) => operationLogChange(logFile, [operationRecord({
182
197
  at: nowIso,
183
198
  op: input.decision === 'approve' ? 'approve' : input.decision === 'reject' ? 'reject' : 'block',
184
199
  primaryId: input.primaryId,
@@ -208,7 +223,7 @@ export async function expirePendingContactRequests(selfAid, now = Date.now()) {
208
223
  mutation: { type: 'batch', mutations: expiration.mutations },
209
224
  actor: 'contact-request-expiry',
210
225
  value: expiration.mutations.length,
211
- beforeCommit: ({ contactRevision }) => appendOperationRecords(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
226
+ beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
212
227
  };
213
228
  });
214
229
  return result.value;
@@ -226,6 +241,91 @@ export async function listPendingContactRequests(selfAid, now = Date.now()) {
226
241
  }))));
227
242
  return result.value;
228
243
  }
244
+ /** Return only the authenticated applicant's own admission/request state. */
245
+ export async function queryOwnContactRequestStatus(input) {
246
+ const applicantAid = String(input.applicantAid || '').trim();
247
+ if (!isValidAid(applicantAid))
248
+ return protocolResult('NOT_ALLOWED');
249
+ const now = input.now ?? Date.now();
250
+ const rawRequestId = input.requestId;
251
+ if (rawRequestId !== undefined && typeof rawRequestId !== 'string')
252
+ return protocolResult('NOT_ALLOWED');
253
+ const requestedRequestId = rawRequestId?.trim();
254
+ if (requestedRequestId !== undefined
255
+ && (!requestedRequestId || requestedRequestId.length > CONTACT_REQUEST_ID_MAX_LENGTH)) {
256
+ return protocolResult('NOT_ALLOWED');
257
+ }
258
+ const owners = listAgentOwners(input.selfAid);
259
+ const result = await mutateContactBookPrepared(input.selfAid, ({ contact }) => {
260
+ const logFile = agentContactOperations(input.selfAid);
261
+ const expiration = collectExpiredMutations(contact, now);
262
+ const entry = effectiveEntryAfterExpirations(contact.contacts[applicantAid], now);
263
+ const operations = readOperationLog(logFile).filter(record => record.primaryId === applicantAid);
264
+ const latestRequestOperation = [...operations].reverse().find(record => (!!record.requestId
265
+ && (record.op === 'request' || record.op === 'resubmit' || record.op === 'approve'
266
+ || record.op === 'reject' || record.op === 'expire' || record.op === 'block')));
267
+ if (requestedRequestId !== undefined && latestRequestOperation?.requestId !== requestedRequestId) {
268
+ return preparedNoop(protocolResult('NOT_ALLOWED'));
269
+ }
270
+ const requestMetadata = requestMetadataFor(operations, latestRequestOperation?.requestId);
271
+ let value;
272
+ if (owners.includes(applicantAid)
273
+ || (entry && (entry.status === undefined || entry.status === 'active'))) {
274
+ value = protocolResult('APPROVED', requestMetadata);
275
+ }
276
+ else if (entry?.status === 'blocked') {
277
+ value = protocolResult('NOT_ALLOWED');
278
+ }
279
+ else if (entry?.status === 'pending' && entry.pendingRequest) {
280
+ value = protocolResult('PENDING', {
281
+ requestId: entry.pendingRequest.id,
282
+ expiresAt: entry.pendingRequest.expiresAt,
283
+ });
284
+ }
285
+ else if (entry?.status === 'declined') {
286
+ value = protocolResult('EXPIRED', {
287
+ requestId: latestRequestOperation?.op === 'expire' ? latestRequestOperation.requestId : requestMetadata.requestId,
288
+ expiresAt: entry.declinedAt ?? requestMetadata.expiresAt,
289
+ });
290
+ }
291
+ else if (latestRequestOperation?.op === 'reject') {
292
+ value = protocolResult('REJECTED', requestMetadata);
293
+ }
294
+ else if (latestRequestOperation?.op === 'expire') {
295
+ value = protocolResult('EXPIRED', requestMetadata);
296
+ }
297
+ else if (owners.length === 0) {
298
+ value = protocolResult('NOT_ALLOWED');
299
+ }
300
+ else {
301
+ value = protocolResult('CONTACT_REQUIRED');
302
+ }
303
+ if (expiration.mutations.length === 0)
304
+ return preparedNoop(value);
305
+ return {
306
+ mutation: { type: 'batch', mutations: expiration.mutations },
307
+ actor: 'contact-request-expiry',
308
+ value,
309
+ beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
310
+ };
311
+ });
312
+ return result.value;
313
+ }
314
+ export function contactRequestSubmissionProtocolResult(result) {
315
+ const metadata = { requestId: result.requestId, expiresAt: result.expiresAt };
316
+ switch (result.code) {
317
+ case 'submitted':
318
+ case 'resubmitted':
319
+ return protocolResult('SUBMITTED', metadata);
320
+ case 'already-contact':
321
+ case 'owner':
322
+ return protocolResult('APPROVED', metadata);
323
+ case 'rate-limited':
324
+ return protocolResult('RATE_LIMITED', metadata);
325
+ default:
326
+ return protocolResult('NOT_ALLOWED', metadata);
327
+ }
328
+ }
229
329
  export function findContactRequestNote(selfAid, requestId) {
230
330
  const records = readOperationLog(agentContactOperations(selfAid));
231
331
  return [...records].reverse().find(record => (record.requestId === requestId
@@ -237,6 +337,24 @@ function normalizeNote(value) {
237
337
  return null;
238
338
  return note;
239
339
  }
340
+ function requestMetadataFor(operations, requestId) {
341
+ if (!requestId)
342
+ return {};
343
+ const submitted = [...operations].reverse().find(record => (record.requestId === requestId && (record.op === 'request' || record.op === 'resubmit')));
344
+ const submittedAt = submitted ? Date.parse(submitted.at) : Number.NaN;
345
+ return {
346
+ requestId,
347
+ expiresAt: submitted?.expiresAt
348
+ ?? (Number.isFinite(submittedAt) ? new Date(submittedAt + CONTACT_REQUEST_TTL_MS).toISOString() : undefined),
349
+ };
350
+ }
351
+ function protocolResult(status, metadata = {}) {
352
+ return {
353
+ status,
354
+ requestId: metadata.requestId ?? null,
355
+ expiresAt: metadata.expiresAt ?? null,
356
+ };
357
+ }
240
358
  function collectExpiredMutations(book, now) {
241
359
  const mutations = [];
242
360
  const events = [];
@@ -286,7 +404,7 @@ function preparedWithExpirations(expiration, value, logFile) {
286
404
  mutation: { type: 'batch', mutations: expiration.mutations },
287
405
  actor: 'contact-request-expiry',
288
406
  value,
289
- beforeCommit: ({ contactRevision }) => appendOperationRecords(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
407
+ beforeCommit: ({ contactRevision }) => operationLogChange(logFile, expiration.events.map(event => ({ ...event, contactRevision }))),
290
408
  };
291
409
  }
292
410
  function preparedNoop(value) {
@@ -310,7 +428,14 @@ function readOperationLog(file) {
310
428
  const record = JSON.parse(line);
311
429
  if (!record || typeof record !== 'object' || typeof record.at !== 'string'
312
430
  || !Number.isFinite(Date.parse(record.at)) || typeof record.op !== 'string'
313
- || typeof record.primaryId !== 'string')
431
+ || !['request', 'resubmit', 'approve', 'reject', 'expire', 'block', 'unblock', 'manual-add'].includes(record.op)
432
+ || typeof record.primaryId !== 'string' || !isValidAid(record.primaryId)
433
+ || (record.requestId !== undefined && typeof record.requestId !== 'string')
434
+ || (record.expiresAt !== undefined && (!isValidIsoDate(record.expiresAt)))
435
+ || (record.actorId !== undefined && typeof record.actorId !== 'string')
436
+ || (record.sourceChannelKey !== undefined && typeof record.sourceChannelKey !== 'string')
437
+ || (record.note !== undefined && (typeof record.note !== 'string' || record.note.length > CONTACT_REQUEST_NOTE_MAX_LENGTH))
438
+ || typeof record.contactRevision !== 'string')
314
439
  throw new Error('invalid record');
315
440
  records.push(record);
316
441
  }
@@ -320,11 +445,32 @@ function readOperationLog(file) {
320
445
  }
321
446
  return records;
322
447
  }
323
- function appendOperationRecords(file, records) {
448
+ function isValidIsoDate(value) {
449
+ return Number.isFinite(Date.parse(value));
450
+ }
451
+ function operationLogChange(file, records) {
324
452
  if (records.length === 0)
325
- return;
326
- fs.mkdirSync(path.dirname(file), { recursive: true });
327
- fs.appendFileSync(file, `${records.map(record => JSON.stringify(record)).join('\n')}\n`, 'utf8');
453
+ return undefined;
454
+ // Validate the existing log while the Contact Book lock is held. The log is
455
+ // part of the same journal transaction as contact.json, so a malformed log
456
+ // must fail closed before either file is changed.
457
+ readOperationLog(file);
458
+ let before;
459
+ try {
460
+ before = fs.readFileSync(file, 'utf8');
461
+ }
462
+ catch (error) {
463
+ if (error?.code === 'ENOENT')
464
+ before = null;
465
+ else
466
+ throw error;
467
+ }
468
+ const prefix = before && !before.endsWith('\n') ? `${before}\n` : (before ?? '');
469
+ return {
470
+ file,
471
+ before,
472
+ after: `${prefix}${records.map(record => JSON.stringify(record)).join('\n')}\n`,
473
+ };
328
474
  }
329
475
  function operationRecord(input) {
330
476
  return { id: `evt_${crypto.randomUUID().replace(/-/g, '')}`, ...input };
@@ -16,9 +16,11 @@ import path from 'path';
16
16
  import { resolvePaths } from '../paths.js';
17
17
  import { saveDefaultsSafe, saveAgent } from '../config-store.js';
18
18
  import { resolveAnthropicConfig, resolveEcagentConfig, resolveOpenaiConfig } from '../agents/baseagent.js';
19
+ import { buildModelRequestHeaders } from '../agents/request-identity.js';
19
20
  import { resolvePriceRow } from '../stats/billing.js';
20
21
  import { ipcQuery } from '../ipc.js';
21
22
  import { logger } from '../utils/logger.js';
23
+ import { ConfigTarget, read as readConfig } from './config-manager.js';
22
24
  /** 已知 baseagent 类型(网关可管理范围)。 */
23
25
  const GATEWAY_TYPES = ['claude', 'codex', 'gemini', 'ecagent'];
24
26
  const DISPLAY_NAMES = {
@@ -522,8 +524,10 @@ export async function gatewayDelete(args) {
522
524
  }
523
525
  /** 解析某 scope/type 的真实 baseUrl + apiKey(在 daemon 内展开 $ENV,不出站)。 */
524
526
  function resolveGatewayCreds(scope, type) {
525
- const raw = scope === 'defaults' ? readDefaultsRaw() : readAgentRaw(scope);
526
- const block = raw?.baseagents?.[type];
527
+ const expandedConfig = scope === 'defaults'
528
+ ? readConfig(ConfigTarget.Defaults, undefined, { expand: true, cache: true })
529
+ : readConfig(ConfigTarget.Agent, { self: scope }, { expand: true, cache: true });
530
+ const block = expandedConfig?.baseagents?.[type];
527
531
  if (!block)
528
532
  return { error: `${scope}/${type} 未配置`, code: 'NOT_FOUND' };
529
533
  const expanded = expandEnv(block);
@@ -531,15 +535,27 @@ function resolveGatewayCreds(scope, type) {
531
535
  const synth = { agents: { [type]: expanded } };
532
536
  if (type === 'codex') {
533
537
  const r = resolveOpenaiConfig(synth, expanded);
534
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
538
+ return {
539
+ baseUrl: r.baseUrl,
540
+ apiKey: r.apiKey,
541
+ headers: buildModelRequestHeaders({ baseagent: 'codex', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
542
+ };
535
543
  }
536
544
  else if (type === 'claude') {
537
545
  const r = resolveAnthropicConfig(synth, expanded);
538
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
546
+ return {
547
+ baseUrl: r.baseUrl,
548
+ apiKey: r.apiKey,
549
+ headers: buildModelRequestHeaders({ baseagent: 'claude', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
550
+ };
539
551
  }
540
552
  else if (type === 'ecagent') {
541
553
  const r = resolveEcagentConfig(synth, expanded);
542
- return { baseUrl: r.baseUrl, apiKey: r.apiKey };
554
+ return {
555
+ baseUrl: r.baseUrl,
556
+ apiKey: r.apiKey,
557
+ headers: buildModelRequestHeaders({ baseagent: 'ecagent', baseUrl: r.baseUrl, agentAid: scope === 'defaults' ? undefined : scope, configuredHeaders: r.headers }),
558
+ };
543
559
  }
544
560
  return { error: 'gemini 暂不支持 HTTP 探测(CLI 后端)', code: 'NOT_SUPPORTED' };
545
561
  }
@@ -556,7 +572,7 @@ export async function gatewayTest(args) {
556
572
  const creds = resolveGatewayCreds(scope, type);
557
573
  if ('error' in creds)
558
574
  return creds;
559
- const { baseUrl, apiKey } = creds;
575
+ const { baseUrl, apiKey, headers } = creds;
560
576
  if (!baseUrl)
561
577
  return { error: '未配置 baseUrl(或为官方占位地址)', code: 'INVALID_ARGS' };
562
578
  const start = Date.now();
@@ -566,7 +582,7 @@ export async function gatewayTest(args) {
566
582
  const base = baseUrl.replace(/\/+$/, '');
567
583
  const modelsUrl = base.endsWith('/v1') ? `${base}/models` : `${base}/v1/models`;
568
584
  const resp = await fetch(modelsUrl, {
569
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
585
+ headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
570
586
  signal: controller.signal,
571
587
  });
572
588
  clearTimeout(timer);
@@ -614,7 +630,7 @@ export async function gatewayModels(args) {
614
630
  const creds = resolveGatewayCreds(scope, type);
615
631
  if ('error' in creds)
616
632
  return creds;
617
- const { baseUrl, apiKey } = creds;
633
+ const { baseUrl, apiKey, headers } = creds;
618
634
  if (!baseUrl)
619
635
  return { error: '未配置 baseUrl(或为官方占位地址)', code: 'INVALID_ARGS' };
620
636
  let arr = [];
@@ -623,7 +639,7 @@ export async function gatewayModels(args) {
623
639
  const controller = new AbortController();
624
640
  const timer = setTimeout(() => controller.abort(), 8000);
625
641
  const resp = await fetch(`${baseUrl.replace(/\/+$/, '')}/v1/models`, {
626
- headers: apiKey ? { Authorization: `Bearer ${apiKey}` } : {},
642
+ headers: { ...(apiKey ? { Authorization: `Bearer ${apiKey}` } : {}), ...headers },
627
643
  signal: controller.signal,
628
644
  });
629
645
  clearTimeout(timer);
@@ -906,7 +922,7 @@ function expandEnv(obj) {
906
922
  if (typeof obj === 'string') {
907
923
  if (obj.startsWith(ENV_PREFIX))
908
924
  return process.env[obj.slice(ENV_PREFIX.length)] ?? '';
909
- return obj;
925
+ return obj.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => process.env[name] ?? '');
910
926
  }
911
927
  if (Array.isArray(obj))
912
928
  return obj.map(expandEnv);
@@ -1,27 +1,11 @@
1
- /**
2
- * mentionMode ↔ AUN 协议 dispatch_mode 词汇翻译(单一事实源)。
3
- *
4
- * 配置层用 mentionMode(disabled / mention-only),AUN 协议层用 dispatch_mode
5
- * (broadcast / mention)。二者语义等价,仅词汇不同:
6
- * - mention-only(只处理被 @ 的消息)≡ mention(仅被 @ 时响应)
7
- * - disabled(关闭 mention 过滤) ≡ broadcast(全部响应)
8
- *
9
- * 仅在协议边界(AUN resolver、render fallback)翻译;配置层、命令层一律用
10
- * mentionMode 词汇。
11
- */
12
- /** config → AUN 协议词汇;未知/未设值返回 undefined(回退服务端 dispatch_mode)。 */
13
- export function mentionModeToDispatch(mode) {
14
- if (mode === 'mention-only')
15
- return 'mention';
16
- if (mode === 'disabled')
17
- return 'broadcast';
18
- return undefined;
19
- }
20
- /** AUN 协议词汇 → config;未知返回 undefined。 */
21
- export function dispatchToMentionMode(dispatch) {
22
- if (dispatch === 'mention')
23
- return 'mention-only';
24
- if (dispatch === 'broadcast')
1
+ /** AUN `mention_mode` and EvolCore `mentionMode` share these wire values. */
2
+ export function normalizeMentionMode(mode) {
3
+ if (typeof mode !== 'string')
4
+ return undefined;
5
+ const normalized = mode.trim().toLowerCase();
6
+ if (normalized === 'disabled')
25
7
  return 'disabled';
8
+ if (normalized === 'mention-only' || normalized === 'mention_only')
9
+ return 'mention-only';
26
10
  return undefined;
27
11
  }
@@ -0,0 +1,53 @@
1
+ export class AgentReloadBusyError extends Error {
2
+ aid;
3
+ busyCount;
4
+ code = 'AGENT_BUSY';
5
+ constructor(aid, busyCount) {
6
+ super(`Agent "${aid}" has ${busyCount} task(s) in progress`);
7
+ this.aid = aid;
8
+ this.busyCount = busyCount;
9
+ this.name = 'AgentReloadBusyError';
10
+ }
11
+ }
12
+ export class AgentReloadCoordinator {
13
+ deps;
14
+ inFlight = new Map();
15
+ constructor(deps) {
16
+ this.deps = deps;
17
+ }
18
+ busyCount(aid) {
19
+ return Math.max(0, this.deps.getBusyCount(aid));
20
+ }
21
+ runExclusive(aid, operation, options = {}) {
22
+ const previous = this.inFlight.get(aid) ?? Promise.resolve();
23
+ const pending = previous
24
+ .catch(() => undefined)
25
+ .then(async () => {
26
+ const releaseWorkGate = await this.deps.acquireWorkGate?.(aid);
27
+ try {
28
+ const busyCount = this.busyCount(aid);
29
+ if (!options.force && busyCount > 0)
30
+ throw new AgentReloadBusyError(aid, busyCount);
31
+ if (options.force && busyCount > 0)
32
+ await this.deps.forceReload?.(aid);
33
+ return await operation();
34
+ }
35
+ finally {
36
+ await releaseWorkGate?.();
37
+ }
38
+ });
39
+ this.inFlight.set(aid, pending);
40
+ return pending.finally(() => {
41
+ if (this.inFlight.get(aid) === pending)
42
+ this.inFlight.delete(aid);
43
+ });
44
+ }
45
+ reload(aid, options = {}) {
46
+ return this.runExclusive(aid, async () => {
47
+ if (!this.deps.registry.get(aid))
48
+ throw new Error(`Agent "${aid}" not found`);
49
+ await this.deps.beforeReload?.();
50
+ await this.deps.registry.reload(aid, this.deps.hooks);
51
+ }, options);
52
+ }
53
+ }
@@ -155,7 +155,6 @@ export function authorizeDelegatedAunMsgSend(registry, input) {
155
155
  channelId,
156
156
  chatType: grant.chatType,
157
157
  conversationId: channelId,
158
- identity: { role: grant.issuedRole, mode: 'interactive' },
159
158
  processOwners: [],
160
159
  fromControlChannel: false,
161
160
  });