evolcore 0.0.11 → 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 (102) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/dist/agents/claude-runner.js +35 -2
  4. package/dist/agents/codex-app-server-client.js +120 -14
  5. package/dist/agents/codex-runner.js +273 -35
  6. package/dist/agents/ecagent-runner.js +36 -4
  7. package/dist/agents/gemini-runner.js +1 -0
  8. package/dist/aun/aid/client.js +11 -21
  9. package/dist/aun/aid/managed-operation.js +107 -0
  10. package/dist/aun/msg/group.js +81 -12
  11. package/dist/aun/msg/managed-operation.js +804 -0
  12. package/dist/aun/msg/mention-schema.js +20 -0
  13. package/dist/aun/msg/p2p.js +7 -0
  14. package/dist/channels/aun.js +288 -108
  15. package/dist/channels/daemon.js +13 -0
  16. package/dist/cli/agent-command.js +56 -8
  17. package/dist/cli/agent.js +170 -6
  18. package/dist/cli/aun-commands.js +270 -69
  19. package/dist/cli/bench.js +12 -3
  20. package/dist/cli/daemon-commands.js +31 -31
  21. package/dist/cli/fs-command.js +60 -0
  22. package/dist/cli/handoff-command.js +3 -0
  23. package/dist/cli/index.js +30 -2
  24. package/dist/cli/managed-command-guard.js +41 -0
  25. package/dist/cli/model.js +11 -4
  26. package/dist/cli/queue-command.js +11 -1
  27. package/dist/cli/response.js +71 -0
  28. package/dist/cli/restart-monitor.js +4 -18
  29. package/dist/cli/trigger-command.js +4 -0
  30. package/dist/config/builtin-roles.js +4 -0
  31. package/dist/config/contact-book-store.js +11 -3
  32. package/dist/config/contact-request-service.js +161 -15
  33. package/dist/config/mention-mode.js +8 -24
  34. package/dist/core/auth/agent-delegation.js +0 -1
  35. package/dist/core/auth/operation-authorizer.js +30 -7
  36. package/dist/core/auth/operation-catalog.js +187 -25
  37. package/dist/core/bootstrap-messages.js +18 -0
  38. package/dist/core/bootstrap-service.js +63 -4
  39. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  40. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  41. package/dist/core/capability/skill-discovery.js +55 -0
  42. package/dist/core/channel-loader.js +1 -1
  43. package/dist/core/command/cli-intent-parser.js +9 -0
  44. package/dist/core/command/command-handler.js +467 -6
  45. package/dist/core/command/connect-menu.js +15 -0
  46. package/dist/core/command/group-menu.js +4 -4
  47. package/dist/core/command/menu-catalog.js +366 -0
  48. package/dist/core/command/menu-handler.js +147 -35
  49. package/dist/core/command/menu-protocol.js +71 -0
  50. package/dist/core/command/slash-gate.js +5 -2
  51. package/dist/core/command/slash-handler.js +41 -21
  52. package/dist/core/daemon-file-cache.js +2 -1
  53. package/dist/core/data-migration.js +9 -2
  54. package/dist/core/evolagent.js +4 -2
  55. package/dist/core/handoff/dispatcher.js +5 -2
  56. package/dist/core/handoff/runtime.js +145 -21
  57. package/dist/core/handoff/store.js +24 -0
  58. package/dist/core/handoff/types.js +1 -0
  59. package/dist/core/message/logical-queue-bridge.js +1 -0
  60. package/dist/core/message/mention-schema.js +126 -0
  61. package/dist/core/message/message-bridge.js +236 -78
  62. package/dist/core/message/message-log.js +1 -0
  63. package/dist/core/message/message-queue.js +1 -0
  64. package/dist/core/message/response-engine.js +54 -36
  65. package/dist/core/permission/ec-command-parser.js +156 -10
  66. package/dist/core/permission/sandbox-runtime.js +22 -1
  67. package/dist/core/permission/tool-policy.js +73 -30
  68. package/dist/core/protected-paths.js +7 -2
  69. package/dist/core/session/session-manager.js +21 -2
  70. package/dist/eck/manifest-engine.js +39 -13
  71. package/dist/index.js +384 -98
  72. package/dist/ipc.js +218 -9
  73. package/dist/response-system/coordinator.js +6 -4
  74. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  75. package/dist/response-system/modes/single-session/index.js +14 -1
  76. package/dist/response-system/selector.js +6 -5
  77. package/dist/trigger/anomaly-store.js +4 -0
  78. package/dist/trigger/script-executor.js +1 -0
  79. package/dist/utils/codex-app-server-registry.js +90 -0
  80. package/dist/utils/codex-cli.js +5 -1
  81. package/dist/utils/cross-platform.js +15 -0
  82. package/dist/utils/npm-ops.js +5 -12
  83. package/dist/utils/tool-summary.js +11 -0
  84. package/kits/docs/channels/aun.md +1 -1
  85. package/kits/docs/context-assembly.md +2 -2
  86. package/kits/docs/evolcore/agent.md +11 -6
  87. package/kits/docs/evolcore/aid.md +14 -0
  88. package/kits/docs/prompt-loading-architecture.md +1 -2
  89. package/kits/docs/venues/group.md +1 -1
  90. package/kits/eck_manifest.json +0 -12
  91. package/kits/rules/04-relation.md +1 -1
  92. package/kits/rules/05-venue.md +2 -2
  93. package/kits/schemas/single-session.schema.1.json +3 -3
  94. package/kits/schemas/single-session.schema.2.json +3 -3
  95. package/kits/templates/roles/admin.json +22 -0
  96. package/kits/templates/roles/member.json +27 -0
  97. package/kits/templates/roles/visitor.json +33 -3
  98. package/kits/templates/system-fragments/bootstrap.md +1 -1
  99. package/kits/templates/system-fragments/channel.md +1 -1
  100. package/kits/templates/system-fragments/session.md +1 -1
  101. package/package.json +2 -2
  102. package/dist/core/command/evol-menu-version-gate.js +0 -39
package/dist/index.js CHANGED
@@ -45,12 +45,12 @@ import { buildEnvelope } from './core/message/message-utils.js';
45
45
  import { ResponseEngine } from './core/message/response-engine.js';
46
46
  import { MessageQueue } from './core/message/message-queue.js';
47
47
  import { MessageBridge } from './core/message/message-bridge.js';
48
- import { MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
49
- import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata } from './core/command/evol-menu-version-gate.js';
48
+ import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, parseMenuControl, validateMenuRequest } from './core/command/menu-protocol.js';
50
49
  import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
51
50
  import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
52
51
  import { HandoffRuntime } from './core/handoff/runtime.js';
53
52
  import { BootstrapService, completeBootstrapWithWelcome } from './core/bootstrap-service.js';
53
+ import { postBootstrapWelcomeOperationId } from './core/bootstrap-messages.js';
54
54
  import { MessageCache } from './core/message/message-cache.js';
55
55
  import { CommandHandler, isProcessLevelOwner } from './core/command/command-handler.js';
56
56
  import { EventBus } from './core/event-bus.js';
@@ -62,6 +62,7 @@ import { InteractionRouter } from './core/interaction-router.js';
62
62
  import { registerAdapterInteractions } from './core/interaction-registration.js';
63
63
  import { AgentDelegationRegistry, authorizeDelegatedAunMsgSend } from './core/auth/agent-delegation.js';
64
64
  import { authorizeOperation, buildAuthSubject } from './core/auth/auth-gateway.js';
65
+ import { parsePeerKey } from './core/relation/peer-identity.js';
65
66
  import { isHClassPath } from './core/protected-paths.js';
66
67
  import { ChannelLoader, tryParseChannelKey } from './core/channel-loader.js';
67
68
  import { AgentLoader } from './core/baseagent-loader.js';
@@ -90,6 +91,7 @@ import { validateModelSelectionForRole } from './core/model/model-permission.js'
90
91
  import { constrainRuntimePermissionMode, validateRuntimeStringFieldOverride, } from './core/role/runtime-policy.js';
91
92
  import { atomicWriteJson } from './core/session/session-fs-store.js';
92
93
  import { appendMessageLog, buildOutboundEntry, classifyAunPayloadForLog } from './core/message/message-log.js';
94
+ import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
93
95
  import { MAIN_PACKAGE_NAME } from './product.js';
94
96
  import fs from 'fs';
95
97
  import crypto from 'crypto';
@@ -207,7 +209,7 @@ function originFromActorSession(session, authenticatedPeerId, authenticatedChann
207
209
  }
208
210
  function seedUpgradeCheckTrigger(manager, owner) {
209
211
  const now = Date.now();
210
- const scriptName = 'upgrade-check.sh';
212
+ const scriptName = 'upgrade-check.cjs';
211
213
  const definition = normalizeTriggerDefinition({
212
214
  $schema_version: 3.1,
213
215
  id: '__upgrade-check',
@@ -234,7 +236,7 @@ function seedUpgradeCheckTrigger(manager, owner) {
234
236
  type: 'script',
235
237
  script: {
236
238
  path: scriptName,
237
- runtime: 'bash',
239
+ runtime: 'node',
238
240
  timeoutMs: 60_000,
239
241
  },
240
242
  permissionMode: 'bypass',
@@ -260,67 +262,107 @@ function seedUpgradeCheckTrigger(manager, owner) {
260
262
  const cliPath = path.join(packageRoot, 'dist', 'cli', 'index.js');
261
263
  const packageJsonPath = path.join(packageRoot, 'package.json');
262
264
  const devMode = fs.existsSync(path.join(packageRoot, 'src', 'index.ts'));
263
- fs.writeFileSync(scriptPath, [
264
- '#!/usr/bin/env bash',
265
- 'set -euo pipefail',
266
- '',
267
- `CLI_PATH=${JSON.stringify(cliPath)}`,
268
- `PACKAGE_JSON=${JSON.stringify(packageJsonPath)}`,
269
- `DEV_MODE=${devMode ? '1' : '0'}`,
270
- '',
271
- 'json() {',
272
- ' local outcome="$1"',
273
- ' local text="$2"',
274
- ' node -e \'console.log(JSON.stringify({ outcome: process.argv[1], text: process.argv[2], files: [] }))\' "$outcome" "$text"',
275
- '}',
276
- '',
277
- 'if [ "$DEV_MODE" = "1" ]; then',
278
- ' json "noop" "upgrade check skipped in dev mode"',
279
- ' exit 0',
280
- 'fi',
281
- '',
282
- 'local_version="$(node -e \'try { console.log(require(process.argv[1]).version || "") } catch { process.exit(0) }\' "$PACKAGE_JSON")"',
283
- `remote_version="$(npm view ${MAIN_PACKAGE_NAME} version 2>/dev/null || true)"`,
284
- '',
285
- 'if [ -z "$remote_version" ]; then',
286
- ' json "error" "failed to check evolcore latest version"',
287
- ' exit 0',
288
- 'fi',
289
- '',
290
- 'if [ -z "$local_version" ]; then',
291
- ' json "error" "failed to read local ec version"',
292
- ' exit 0',
293
- 'fi',
294
- '',
295
- 'version_cmp="$(node -e \'',
296
- 'const [a, b] = process.argv.slice(1);',
297
- 'const pa = a.split("-")[0].split(".").map(Number);',
298
- 'const pb = b.split("-")[0].split(".").map(Number);',
299
- 'const n = Math.max(pa.length, pb.length);',
300
- 'let out = 0;',
301
- 'for (let i = 0; i < n; i++) {',
302
- ' const av = Number.isFinite(pa[i]) ? pa[i] : 0;',
303
- ' const bv = Number.isFinite(pb[i]) ? pb[i] : 0;',
304
- ' if (av < bv) { out = -1; break; }',
305
- ' if (av > bv) { out = 1; break; }',
306
- '}',
307
- 'console.log(out);',
308
- '\' "$local_version" "$remote_version")"',
309
- '',
310
- 'if [ "$version_cmp" != "-1" ]; then',
311
- ' json "noop" "evolcore is already up to date ($local_version; latest $remote_version)"',
312
- ' exit 0',
313
- 'fi',
314
- '',
315
- 'if [ ! -f "$CLI_PATH" ]; then',
316
- ' json "error" "restart-monitor entry not found: $CLI_PATH"',
317
- ' exit 0',
318
- 'fi',
319
- '',
320
- 'nohup node "$CLI_PATH" restart-monitor >/dev/null 2>&1 &',
321
- 'json "success" "evolcore upgrade available: ${local_version:-unknown} -> $remote_version; restart-monitor started"',
322
- '',
323
- ].join('\n'));
265
+ fs.writeFileSync(scriptPath, `#!/usr/bin/env node
266
+ const { existsSync, readFileSync } = require('node:fs');
267
+ const { spawn } = require('node:child_process');
268
+
269
+ const CLI_PATH = ${JSON.stringify(cliPath)};
270
+ const PACKAGE_JSON = ${JSON.stringify(packageJsonPath)};
271
+ const PACKAGE_NAME = ${JSON.stringify(MAIN_PACKAGE_NAME)};
272
+ const DEV_MODE = ${devMode ? 'true' : 'false'};
273
+
274
+ function emit(outcome, text) {
275
+ process.stdout.write(JSON.stringify({ outcome, text, files: [] }));
276
+ }
277
+
278
+ function compareVersions(a, b) {
279
+ const pa = a.split('-')[0].split('.').map(Number);
280
+ const pb = b.split('-')[0].split('.').map(Number);
281
+ const count = Math.max(pa.length, pb.length);
282
+ for (let i = 0; i < count; i += 1) {
283
+ const av = Number.isFinite(pa[i]) ? pa[i] : 0;
284
+ const bv = Number.isFinite(pb[i]) ? pb[i] : 0;
285
+ if (av < bv) return -1;
286
+ if (av > bv) return 1;
287
+ }
288
+ return 0;
289
+ }
290
+
291
+ async function latestVersion() {
292
+ const controller = new AbortController();
293
+ const timer = setTimeout(() => controller.abort(), 10_000);
294
+ try {
295
+ const response = await fetch('https://registry.npmjs.org/' + encodeURIComponent(PACKAGE_NAME) + '/latest', {
296
+ signal: controller.signal,
297
+ headers: { Accept: 'application/json' },
298
+ });
299
+ if (!response.ok) return null;
300
+ const data = await response.json();
301
+ return typeof data.version === 'string' ? data.version : null;
302
+ } finally {
303
+ clearTimeout(timer);
304
+ }
305
+ }
306
+
307
+ async function main() {
308
+ if (DEV_MODE) {
309
+ emit('noop', 'upgrade check skipped in dev mode');
310
+ return;
311
+ }
312
+
313
+ let localVersion;
314
+ try {
315
+ localVersion = JSON.parse(readFileSync(PACKAGE_JSON, 'utf8')).version;
316
+ } catch {
317
+ emit('error', 'failed to read local ec version');
318
+ return;
319
+ }
320
+ if (typeof localVersion !== 'string' || !localVersion) {
321
+ emit('error', 'failed to read local ec version');
322
+ return;
323
+ }
324
+
325
+ let remoteVersion;
326
+ try {
327
+ remoteVersion = await latestVersion();
328
+ } catch {
329
+ remoteVersion = null;
330
+ }
331
+ if (!remoteVersion) {
332
+ emit('error', 'failed to check evolcore latest version');
333
+ return;
334
+ }
335
+ if (compareVersions(localVersion, remoteVersion) >= 0) {
336
+ emit('noop', 'evolcore is already up to date (' + localVersion + '; latest ' + remoteVersion + ')');
337
+ return;
338
+ }
339
+ if (!existsSync(CLI_PATH)) {
340
+ emit('error', 'restart-monitor entry not found: ' + CLI_PATH);
341
+ return;
342
+ }
343
+
344
+ try {
345
+ const child = spawn(process.execPath, [CLI_PATH, 'restart-monitor'], {
346
+ detached: true,
347
+ stdio: 'ignore',
348
+ windowsHide: process.platform === 'win32',
349
+ env: { ...process.env },
350
+ });
351
+ child.unref();
352
+ emit('success', 'evolcore upgrade available: ' + localVersion + ' -> ' + remoteVersion + '; restart-monitor started');
353
+ } catch (error) {
354
+ emit('error', 'failed to start restart-monitor: ' + (error && error.message ? error.message : String(error)));
355
+ }
356
+ }
357
+
358
+ main().catch((error) => {
359
+ emit('error', 'upgrade check failed: ' + (error && error.message ? error.message : String(error)));
360
+ });
361
+ `);
362
+ // Remove the pre-Node script left by older installations so it cannot be
363
+ // selected by a stale trigger definition or run manually by accident.
364
+ fs.rmSync(path.join(dir, 'upgrade-check.sh'), { force: true });
365
+ fs.rmSync(path.join(dir, 'upgrade-check.js'), { force: true });
324
366
  fs.chmodSync(scriptPath, 0o755);
325
367
  atomicWriteJson(manager.definitionPath(definition.id), definition);
326
368
  fs.rmSync(manager.activePath(definition.id), { force: true });
@@ -338,7 +380,9 @@ function removeLegacyAgentUpgradeCheck(manager) {
338
380
  return;
339
381
  const isLegacySystemOrigin = existing.origin?.channelKey === '__system__' || existing.origin?.channelKey === 'daemon';
340
382
  const isUpgradeScript = existing.execution.type === 'script'
341
- && existing.execution.script?.path === 'upgrade-check.sh';
383
+ && (existing.execution.script?.path === 'upgrade-check.sh'
384
+ || existing.execution.script?.path === 'upgrade-check.js'
385
+ || existing.execution.script?.path === 'upgrade-check.cjs');
342
386
  if (!isLegacySystemOrigin || !isUpgradeScript)
343
387
  return;
344
388
  fs.rmSync(manager.triggerDir('__upgrade-check'), { recursive: true, force: true });
@@ -396,6 +440,13 @@ async function runBindBootstrapDaemon(daemonCfg) {
396
440
  await controlChannel.sendMessage(opts.channelId, JSON.stringify(response));
397
441
  }
398
442
  });
443
+ const controlToken = crypto.randomBytes(32).toString('base64url');
444
+ fs.mkdirSync(path.dirname(resolvePaths().controlToken), { recursive: true });
445
+ fs.writeFileSync(resolvePaths().controlToken, controlToken, { mode: 0o600 });
446
+ try {
447
+ fs.chmodSync(resolvePaths().controlToken, 0o600);
448
+ }
449
+ catch { }
399
450
  const ipcServer = new IpcServer(resolvePaths().socket, () => ({
400
451
  pid: process.pid,
401
452
  uptime: Math.round(process.uptime() * 1000),
@@ -417,6 +468,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
417
468
  };
418
469
  })(),
419
470
  }));
471
+ ipcServer.setControlToken(controlToken);
420
472
  ipcServer.setBindExecutor({
421
473
  begin: (cmd) => bindService.begin(cmd),
422
474
  status: (taskId) => bindService.status(taskId),
@@ -1129,6 +1181,7 @@ async function main() {
1129
1181
  return { ok: result.ok, message_id: result.message_id, error: result.error };
1130
1182
  }, undefined, { queueTtlMs: taskExecutionTtlMs });
1131
1183
  responseEngine.setHandoffRuntime(handoffRuntime);
1184
+ cmdHandler.setHandoffRuntime(handoffRuntime);
1132
1185
  // Trigger runtime: daemon-level script + feedback scheduler.
1133
1186
  const triggerSchedulers = new Map();
1134
1187
  const targetSessionLocks = new TargetSessionLockRegistry();
@@ -1993,7 +2046,7 @@ async function main() {
1993
2046
  const delegation = agentDelegationRegistry.validate(delegationToken, sessionId, delegationCommandHash);
1994
2047
  if (!delegation.ok)
1995
2048
  return { ok: false, code: delegation.code, error: delegation.reason };
1996
- return cmdHandler.handleCtl(cmd, sessionId);
2049
+ return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
1997
2050
  });
1998
2051
  // Register every IPC executor/provider before exposing the endpoint. The
1999
2052
  // function declaration is hoisted, while its invocation remains here so a
@@ -2080,10 +2133,11 @@ async function main() {
2080
2133
  }
2081
2134
  else {
2082
2135
  try {
2083
- const ecwebProc = spawn('node', [ecwebEntry], {
2136
+ const ecwebProc = spawn(process.execPath, [ecwebEntry], {
2084
2137
  cwd: ecwebPath,
2085
2138
  detached: true,
2086
2139
  stdio: 'ignore',
2140
+ windowsHide: platform.isWindows,
2087
2141
  env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
2088
2142
  });
2089
2143
  ecwebProc.unref();
@@ -2259,6 +2313,9 @@ async function main() {
2259
2313
  const recoveryAids = triggerStartupAgents.map(agent => agent.aid);
2260
2314
  for (const aid of recoveryAids) {
2261
2315
  try {
2316
+ const discarded = await handoffRuntime.discardExpired([aid]);
2317
+ if (discarded > 0)
2318
+ logger.info(`[Handoff] discarded ${discarded} expired handoff(s) for ${aid}`);
2262
2319
  const expired = handoffRuntime.expireQueued([aid]);
2263
2320
  if (expired > 0)
2264
2321
  logger.info(`[Handoff] expired ${expired} stale queued delivery item(s) for ${aid}`);
@@ -2344,7 +2401,27 @@ async function main() {
2344
2401
  function configureIpc() {
2345
2402
  // M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
2346
2403
  ipcServer.setAgentRegistry(agentRegistry);
2347
- ipcServer.setBootstrapCompleteExecutor(async (aid) => {
2404
+ ipcServer.setControlToken(triggerControlToken);
2405
+ ipcServer.setBootstrapCompleteExecutor(async (aid, auth) => {
2406
+ if (auth?.sessionId) {
2407
+ const authorization = await authorizeManagedAgentOperation({
2408
+ sessionId: auth.sessionId,
2409
+ delegationToken: auth.delegationToken,
2410
+ delegationCommandHash: auth.delegationCommandHash,
2411
+ targetAid: aid,
2412
+ }, 'agent.bootstrapComplete');
2413
+ if (!authorization.ok)
2414
+ return authorization;
2415
+ }
2416
+ else {
2417
+ const providedToken = typeof auth?.controlToken === 'string' ? Buffer.from(auth.controlToken) : undefined;
2418
+ const expectedToken = Buffer.from(triggerControlToken);
2419
+ if (!providedToken
2420
+ || providedToken.length !== expectedToken.length
2421
+ || !crypto.timingSafeEqual(providedToken, expectedToken)) {
2422
+ return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
2423
+ }
2424
+ }
2348
2425
  const aunInstance = channelInstances.find(candidate => {
2349
2426
  if (candidate.channelType !== 'aun')
2350
2427
  return false;
@@ -2353,7 +2430,30 @@ async function main() {
2353
2430
  return owner?.aid === aid;
2354
2431
  });
2355
2432
  const welcome = aunInstance?.channel;
2356
- return completeBootstrapWithWelcome(bootstrapService, aid, welcome);
2433
+ const ownerAid = getFirstStaticAgentOwner(aid);
2434
+ const owningAgent = agentRegistry.get(aid);
2435
+ const sessions = aunInstance && ownerAid && owningAgent
2436
+ ? {
2437
+ ensurePostBootstrapMainSession: async () => {
2438
+ const channel = aunInstance.adapter.channelName;
2439
+ const active = await sessionManager.getActiveSession(channel, ownerAid);
2440
+ const ensured = await sessionManager.ensureNewMainSessionForOperation({
2441
+ channel,
2442
+ channelId: ownerAid,
2443
+ projectPath: active?.projectPath || owningAgent.projectPath || defaultProjectPath,
2444
+ baseagent: owningAgent.baseagent || active?.baseagent || primaryBaseagent,
2445
+ operationId: postBootstrapWelcomeOperationId(aid),
2446
+ identityMetadata: {
2447
+ ...(active?.metadata ?? {}),
2448
+ channelKey: aunInstance.adapter.channelKey,
2449
+ peerId: ownerAid,
2450
+ },
2451
+ });
2452
+ return { sessionId: ensured.session.id, created: ensured.created };
2453
+ },
2454
+ }
2455
+ : undefined;
2456
+ return completeBootstrapWithWelcome(bootstrapService, aid, welcome, sessions);
2357
2457
  });
2358
2458
  ipcServer.setDingtalkContactBindExecutor({
2359
2459
  register: (cmd) => registerPendingDingtalkContactBind(cmd),
@@ -2378,6 +2478,10 @@ async function main() {
2378
2478
  ipcServer.setMenuExecutor((payload, auth) => cmdHandler.execMenuForEcweb(payload, auth));
2379
2479
  ipcServer.setConfigOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleConfigOperation(argv, sessionId, delegationToken, delegationCommandHash));
2380
2480
  ipcServer.setContactOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleContactOperation(argv, sessionId, delegationToken, delegationCommandHash));
2481
+ ipcServer.setAidOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleAidOperation(argv, sessionId, delegationToken, delegationCommandHash));
2482
+ ipcServer.setGroupOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleGroupOperation(argv, sessionId, delegationToken, delegationCommandHash));
2483
+ ipcServer.setMsgOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleMsgOperation(argv, sessionId, delegationToken, delegationCommandHash));
2484
+ ipcServer.setFsOperationExecutor((argv, sessionId, delegationToken, delegationCommandHash) => cmdHandler.handleFsOperation(argv, sessionId, delegationToken, delegationCommandHash));
2381
2485
  cmdHandler.setDaemonStatusProvider(() => {
2382
2486
  const aidState = controlChannel?.getAidState?.();
2383
2487
  return {
@@ -2446,30 +2550,82 @@ async function main() {
2446
2550
  aidStatsCollector.recordOutbound(params.aid, params.toPeer, Buffer.byteLength(params.text || '', 'utf-8'), params.text, false, params.encrypt, params.chatmode, 'send');
2447
2551
  });
2448
2552
  ipcServer.setTaskRuntimeContextProvider(({ sessionId }) => responseEngine.getTaskRuntimeContext(sessionId));
2449
- ipcServer.setHandoffReturnExecutor((params) => responseEngine.returnHandoffResult(params));
2450
- ipcServer.setHandoffStatusExecutor(async (params) => {
2451
- if (!params.sessionId)
2452
- return { ok: false, code: 'HANDOFF_CALL_SESSION_REQUIRED', error: 'current session is required' };
2553
+ ipcServer.setCodexPreToolUseExecutor(async (params) => {
2554
+ for (const runner of new Set(agentMap.values())) {
2555
+ if (typeof runner?.evaluatePreToolUse !== 'function')
2556
+ continue;
2557
+ const result = await runner.evaluatePreToolUse(params.threadId, params.toolName, params.toolInput);
2558
+ if (result.applicable)
2559
+ return result;
2560
+ }
2561
+ return {
2562
+ ok: false,
2563
+ applicable: true,
2564
+ decision: 'deny',
2565
+ reason: 'No active EvolCore Codex runner accepted this managed hook request',
2566
+ };
2567
+ });
2568
+ const resolveManagedHandoffScope = async (params, operation) => {
2569
+ const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
2570
+ if (!delegation.ok)
2571
+ return { ok: false, code: delegation.code, error: delegation.reason };
2453
2572
  const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
2454
2573
  const session = await sessionManager.getSessionById(params.sessionId);
2455
2574
  const selfAid = runtime?.selfAid || session?.selfAID;
2456
- if (!selfAid)
2457
- return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
2458
- return handoffRuntime.status(selfAid, params.handoffId)
2459
- ?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
2460
- });
2461
- const resolveHandoffQueryAid = async (params) => {
2575
+ if (!selfAid || !session) {
2576
+ return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is invalid' };
2577
+ }
2578
+ if (delegation.grant.selfAid !== selfAid) {
2579
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation self agent does not match current session' };
2580
+ }
2581
+ if (params.agent && params.agent !== selfAid) {
2582
+ return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
2583
+ }
2584
+ let conversationId;
2585
+ try {
2586
+ conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
2587
+ }
2588
+ catch {
2589
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation contains an invalid relation key' };
2590
+ }
2591
+ const subject = buildAuthSubject({
2592
+ selfAid,
2593
+ actorId: delegation.grant.actorId,
2594
+ channel: delegation.grant.channel,
2595
+ channelType: delegation.grant.channelType,
2596
+ channelId: conversationId,
2597
+ chatType: delegation.grant.chatType,
2598
+ conversationId,
2599
+ processOwners: daemonCfg.owners ?? [],
2600
+ });
2601
+ const managementRole = isManagementRole(subject.role);
2602
+ const decision = authorizeOperation({
2603
+ source: 'agent-tool',
2604
+ subject,
2605
+ intent: {
2606
+ operation,
2607
+ scope: managementRole ? 'agent' : 'relation',
2608
+ source: 'agent-tool',
2609
+ args: {
2610
+ self: selfAid,
2611
+ peer: conversationId,
2612
+ peerKey: delegation.grant.peerKey,
2613
+ },
2614
+ },
2615
+ auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
2616
+ });
2617
+ if (!decision.allow) {
2618
+ return {
2619
+ ok: false,
2620
+ code: decision.command?.code ?? 'ROLE_ACCESS_DENIED',
2621
+ error: decision.reason,
2622
+ };
2623
+ }
2624
+ return { ok: true, aid: selfAid, currentSessionId: params.sessionId, managementRole };
2625
+ };
2626
+ const resolveHandoffQueryScope = async (params, operation) => {
2462
2627
  if (params.sessionId) {
2463
- const runtime = responseEngine.getTaskRuntimeContext(params.sessionId);
2464
- const session = await sessionManager.getSessionById(params.sessionId);
2465
- const selfAid = runtime?.selfAid || session?.selfAID;
2466
- if (!selfAid) {
2467
- return { ok: false, code: 'HANDOFF_CALL_SESSION_INVALID', error: 'current session is invalid' };
2468
- }
2469
- if (params.agent && params.agent !== selfAid) {
2470
- return { ok: false, code: 'HANDOFF_AGENT_SCOPE_MISMATCH', error: 'agent does not match the current session' };
2471
- }
2472
- return { ok: true, aid: selfAid };
2628
+ return resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, operation);
2473
2629
  }
2474
2630
  if (!params.agent) {
2475
2631
  return { ok: false, code: 'HANDOFF_AGENT_REQUIRED', error: '--agent is required outside a task context' };
@@ -2477,23 +2633,56 @@ async function main() {
2477
2633
  if (!agentRegistry.get(params.agent)) {
2478
2634
  return { ok: false, code: 'HANDOFF_AGENT_NOT_FOUND', error: `agent not found: ${params.agent}` };
2479
2635
  }
2480
- return { ok: true, aid: params.agent };
2636
+ return { ok: true, aid: params.agent, currentSessionId: undefined, managementRole: true };
2481
2637
  };
2638
+ ipcServer.setHandoffReturnExecutor(async (params) => {
2639
+ if (!params.sessionId)
2640
+ return responseEngine.returnHandoffResult(params);
2641
+ const scope = await resolveManagedHandoffScope({ ...params, sessionId: params.sessionId }, 'ec.handoff.return');
2642
+ if (!scope.ok)
2643
+ return scope;
2644
+ return responseEngine.returnAuthorizedHandoffResult({ sessionId: params.sessionId, handoffId: params.handoffId, content: params.content }, scope.aid);
2645
+ });
2646
+ ipcServer.setHandoffStatusExecutor(async (params) => {
2647
+ const scope = await resolveHandoffQueryScope(params, 'ec.handoff.status');
2648
+ if (!scope.ok)
2649
+ return scope;
2650
+ const status = handoffRuntime.status(scope.aid, params.handoffId);
2651
+ if (!status)
2652
+ return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
2653
+ if (!scope.managementRole
2654
+ && status.origin_session_id !== scope.currentSessionId
2655
+ && status.target_session_id !== scope.currentSessionId) {
2656
+ return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found in the current session' };
2657
+ }
2658
+ return status;
2659
+ });
2482
2660
  ipcServer.setHandoffListExecutor(async (params) => {
2483
- const scope = await resolveHandoffQueryAid(params);
2661
+ const scope = await resolveHandoffQueryScope(params, 'ec.handoff.list');
2484
2662
  if (!scope.ok)
2485
2663
  return scope;
2664
+ if (!scope.managementRole && params.filterSessionId && params.filterSessionId !== scope.currentSessionId) {
2665
+ return { ok: false, code: 'HANDOFF_SESSION_SCOPE_MISMATCH', error: 'only the current session can be queried' };
2666
+ }
2486
2667
  return handoffRuntime.listHandoffs({
2487
2668
  selfAid: scope.aid,
2488
2669
  state: params.state,
2489
- sessionId: params.filterSessionId,
2670
+ sessionId: scope.managementRole ? params.filterSessionId : scope.currentSessionId,
2490
2671
  limit: params.limit,
2491
2672
  });
2492
2673
  });
2493
2674
  ipcServer.setHandoffTraceExecutor(async (params) => {
2494
- const scope = await resolveHandoffQueryAid(params);
2675
+ const scope = await resolveHandoffQueryScope(params, 'ec.handoff.trace');
2495
2676
  if (!scope.ok)
2496
2677
  return scope;
2678
+ if (!scope.managementRole) {
2679
+ const status = handoffRuntime.status(scope.aid, params.handoffId);
2680
+ if (!status
2681
+ || (status.origin_session_id !== scope.currentSessionId
2682
+ && status.target_session_id !== scope.currentSessionId)) {
2683
+ return { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found in the current session' };
2684
+ }
2685
+ }
2497
2686
  return handoffRuntime.traceHandoff(scope.aid, params.handoffId, params.limit)
2498
2687
  ?? { ok: false, code: 'HANDOFF_NOT_FOUND', error: 'handoff not found' };
2499
2688
  });
@@ -2565,12 +2754,27 @@ async function main() {
2565
2754
  if (!payload) {
2566
2755
  return { ok: false, error: 'message payload is required', code: 'INVALID_PAYLOAD' };
2567
2756
  }
2757
+ try {
2758
+ if (Object.prototype.hasOwnProperty.call(payload, 'mentions')) {
2759
+ payload = { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) };
2760
+ }
2761
+ if (params.scope === 'group' && params.mentions !== undefined) {
2762
+ const normalizedMentions = normalizeAunMentionEntries(params.mentions);
2763
+ if (normalizedMentions.length > 0) {
2764
+ payload = { ...payload, mentions: normalizedMentions };
2765
+ }
2766
+ }
2767
+ }
2768
+ catch (error) {
2769
+ return {
2770
+ ok: false,
2771
+ error: error instanceof Error ? error.message : String(error),
2772
+ code: error?.code ?? 'INVALID_MENTION_SCHEMA',
2773
+ };
2774
+ }
2568
2775
  if (params.thread && typeof payload.thread_id !== 'string') {
2569
2776
  payload = { ...payload, thread_id: params.thread };
2570
2777
  }
2571
- if (params.scope === 'group' && params.mentions?.length) {
2572
- payload = { ...payload, mentions: params.mentions };
2573
- }
2574
2778
  let targetSessionId;
2575
2779
  if (params.originSessionId && params.originMessageId) {
2576
2780
  try {
@@ -2824,7 +3028,7 @@ async function main() {
2824
3028
  agent.status = 'running';
2825
3029
  // 连接
2826
3030
  await channelLoader.connectAll(instances, { onConnected: markChannelConnected });
2827
- await handoffRuntime.recover([aid]);
3031
+ await handoffRuntime.recover([aid], { discardExpired: false });
2828
3032
  await ensureTriggerSchedulerStarted(agent);
2829
3033
  handoffRuntime.resumeAgent(aid);
2830
3034
  resumed = true;
@@ -3003,6 +3207,88 @@ async function main() {
3003
3207
  return { ok: false, error: `unknown action: ${params.action}` };
3004
3208
  }
3005
3209
  });
3210
+ async function authorizeManagedAgentOperation(params, operation) {
3211
+ const delegation = agentDelegationRegistry.validate(params.delegationToken, params.sessionId, params.delegationCommandHash);
3212
+ if (!delegation.ok)
3213
+ return { ok: false, code: delegation.code, error: delegation.reason };
3214
+ const session = await sessionManager.getSessionById(params.sessionId);
3215
+ if (!session || session.selfAID !== delegation.grant.selfAid) {
3216
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation does not match the current session' };
3217
+ }
3218
+ if (params.targetAid && params.targetAid !== delegation.grant.selfAid) {
3219
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'only the current Agent can be targeted' };
3220
+ }
3221
+ let conversationId;
3222
+ try {
3223
+ conversationId = parsePeerKey(delegation.grant.peerKey).channelId;
3224
+ }
3225
+ catch {
3226
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegation contains an invalid relation key' };
3227
+ }
3228
+ const subject = buildAuthSubject({
3229
+ selfAid: delegation.grant.selfAid,
3230
+ actorId: delegation.grant.actorId,
3231
+ channel: delegation.grant.channel,
3232
+ channelType: delegation.grant.channelType,
3233
+ channelId: conversationId,
3234
+ chatType: delegation.grant.chatType,
3235
+ conversationId,
3236
+ processOwners: daemonCfg.owners ?? [],
3237
+ });
3238
+ if (!isManagementRole(subject.role)) {
3239
+ return { ok: false, code: 'ROLE_ACCESS_DENIED', error: `${operation} requires Agent admin permission` };
3240
+ }
3241
+ const decision = authorizeOperation({
3242
+ source: 'agent-tool',
3243
+ subject,
3244
+ intent: {
3245
+ operation,
3246
+ scope: operation === 'system.status' ? 'process' : 'agent',
3247
+ source: 'agent-tool',
3248
+ args: { self: delegation.grant.selfAid, aid: params.targetAid ?? delegation.grant.selfAid },
3249
+ },
3250
+ auditMetadata: { taskId: delegation.grant.taskId, messageId: delegation.grant.messageId },
3251
+ });
3252
+ return decision.allow
3253
+ ? { ok: true, aid: delegation.grant.selfAid }
3254
+ : { ok: false, code: decision.code, error: decision.reason };
3255
+ }
3256
+ ipcServer.setQueueOperationExecutor(async (params) => {
3257
+ const operation = `ec.queue.${params.action ?? 'read'}`;
3258
+ const authorization = await authorizeManagedAgentOperation(params, operation);
3259
+ if (!authorization.ok)
3260
+ return authorization;
3261
+ const handle = agentRegistry.get(authorization.aid);
3262
+ if (!handle?.name)
3263
+ return { ok: false, code: 'AGENT_NOT_FOUND', error: 'current Agent not found' };
3264
+ if (!params.action)
3265
+ return { ok: true, items: messageQueue.getQueueItemsByAgent(handle.name) };
3266
+ if (params.action === 'clear')
3267
+ return { ok: true, cleared: messageQueue.clearByAgent(handle.name) };
3268
+ if (params.action === 'cancel') {
3269
+ if (!params.messageId)
3270
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'missing messageId' };
3271
+ return { ok: true, cancelled: messageQueue.cancelMessageById(handle.name, params.messageId) };
3272
+ }
3273
+ if (!params.sessionKey)
3274
+ return { ok: false, code: 'INVALID_ARGUMENT', error: 'missing sessionKey' };
3275
+ const targetSessionId = messageQueue.findSessionIdBySessionKey(params.sessionKey);
3276
+ if (!targetSessionId)
3277
+ return { ok: false, code: 'SESSION_NOT_FOUND', error: `session not found: ${params.sessionKey}` };
3278
+ const targetSession = await sessionManager.getSessionById(targetSessionId);
3279
+ if (!targetSession || targetSession.selfAID !== authorization.aid) {
3280
+ return { ok: false, code: 'SCOPE_MISMATCH', error: 'session does not belong to the current Agent' };
3281
+ }
3282
+ return { ok: true, interrupted: await messageQueue.interruptBySession(targetSessionId) };
3283
+ });
3284
+ ipcServer.setStatusAuthorizationExecutor(async (params) => {
3285
+ const authorization = await authorizeManagedAgentOperation(params, 'system.status');
3286
+ return authorization.ok ? { ok: true } : authorization;
3287
+ });
3288
+ ipcServer.setStorageAuthorizationExecutor(async (params) => {
3289
+ const authorization = await authorizeManagedAgentOperation({ ...params, targetAid: params.aid }, params.operation);
3290
+ return authorization.ok ? { ok: true } : authorization;
3291
+ });
3006
3292
  ipcServer.setTriggerExecutor(async (cmd) => {
3007
3293
  const schedulerFor = (agentAid) => {
3008
3294
  const scheduler = triggerSchedulers.get(agentAid);