evolcore 0.0.26 → 0.0.28

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 (177) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/bin/codex-managed-acl.ps1 +43 -0
  3. package/dist/agents/baseagent-availability.js +68 -0
  4. package/dist/agents/baseagent.js +10 -5
  5. package/dist/agents/claude-runner.js +123 -22
  6. package/dist/agents/codex/app-server-client.js +70 -26
  7. package/dist/agents/codex/catalog.js +7 -1
  8. package/dist/agents/codex-runner.js +363 -421
  9. package/dist/agents/ecagent-runner.js +170 -25
  10. package/dist/agents/gemini-runner.js +6 -0
  11. package/dist/agents/runner-types.js +50 -2
  12. package/dist/aun/aid/agentmd.js +73 -31
  13. package/dist/aun/aid/client.js +1 -1
  14. package/dist/aun/aid/control-aid.js +180 -1
  15. package/dist/aun/aid/identity.js +3 -3
  16. package/dist/aun/group/history.js +303 -0
  17. package/dist/aun/group/identity.js +43 -0
  18. package/dist/aun/group/index.js +5 -0
  19. package/dist/aun/{msg/group.js → group/operations.js} +161 -38
  20. package/dist/aun/{menu-client.js → menu/client.js} +13 -7
  21. package/dist/aun/menu/index.js +2 -0
  22. package/dist/aun/menu/transport.js +49 -0
  23. package/dist/aun/msg/index.js +2 -1
  24. package/dist/aun/msg/managed-operation.js +84 -18
  25. package/dist/aun/msg/mention-schema.js +51 -0
  26. package/dist/aun/msg/p2p.js +13 -8
  27. package/dist/aun/msg/upload.js +43 -88
  28. package/dist/aun/outbox.js +29 -7
  29. package/dist/aun/rpc/client.js +1 -0
  30. package/dist/aun/storage/client.js +39 -50
  31. package/dist/channels/aun.js +544 -239
  32. package/dist/channels/daemon.js +10 -6
  33. package/dist/cli/agent-command.js +108 -6
  34. package/dist/cli/agent-create.js +515 -2
  35. package/dist/cli/agent.js +109 -8
  36. package/dist/cli/aun-commands.js +191 -42
  37. package/dist/cli/config.js +8 -5
  38. package/dist/cli/contact.js +2 -2
  39. package/dist/cli/ctl-command.js +2 -2
  40. package/dist/cli/daemon-commands.js +111 -162
  41. package/dist/cli/diag.js +3 -3
  42. package/dist/cli/fs-command.js +3 -3
  43. package/dist/cli/handoff-command.js +6 -6
  44. package/dist/cli/index.js +8 -2
  45. package/dist/cli/init.js +34 -1
  46. package/dist/cli/managed-command-guard.js +2 -1
  47. package/dist/cli/menu-client.js +13 -5
  48. package/dist/cli/model.js +3 -3
  49. package/dist/cli/queue-command.js +2 -2
  50. package/dist/cli/response.js +2 -2
  51. package/dist/cli/restart-monitor.js +81 -19
  52. package/dist/cli/task-command.js +59 -0
  53. package/dist/cli/task-context.js +97 -49
  54. package/dist/cli/trigger-command.js +20 -10
  55. package/dist/config/builtin-roles.js +10 -0
  56. package/dist/config/config-field-policy.js +11 -0
  57. package/dist/config/config-manager.js +32 -4
  58. package/dist/config/config-operation-service.js +20 -5
  59. package/dist/config/contact-book-store.js +1 -1
  60. package/dist/config/contact-book.js +1 -1
  61. package/dist/config/contact-operation-service.js +1 -1
  62. package/dist/config/peer-role-resolver.js +1 -1
  63. package/dist/config/resolved-config-op.js +3 -0
  64. package/dist/config-store.js +1 -1
  65. package/dist/core/agent-application-service.js +5 -0
  66. package/dist/core/audit/event-key.js +17 -2
  67. package/dist/core/audit/log-integrity.js +34 -8
  68. package/dist/core/auth/agent-delegation.js +48 -14
  69. package/dist/core/auth/auth-gateway.js +2 -0
  70. package/dist/core/auth/authorization-audit.js +34 -38
  71. package/dist/core/auth/operation-authorizer.js +67 -5
  72. package/dist/core/auth/operation-catalog.js +110 -0
  73. package/dist/core/auth/remote-access-store.js +115 -9
  74. package/dist/core/bootstrap-messages.js +66 -7
  75. package/dist/core/bootstrap-service.js +110 -19
  76. package/dist/core/command/agent-control.js +88 -7
  77. package/dist/core/command/agent-storage-menu.js +443 -0
  78. package/dist/core/command/cli-intent-parser.js +19 -0
  79. package/dist/core/command/command-handler.js +105 -40
  80. package/dist/core/command/data-root-resolver.js +273 -0
  81. package/dist/core/command/diag-service.js +292 -27
  82. package/dist/core/command/group-menu.js +7 -2
  83. package/dist/core/command/menu-catalog.js +34 -4
  84. package/dist/core/command/menu-handler.js +364 -96
  85. package/dist/core/command/menu-protocol.js +43 -6
  86. package/dist/core/command/role-menu.js +2 -2
  87. package/dist/core/command/slash-handler.js +61 -37
  88. package/dist/core/event-catalog.js +4 -0
  89. package/dist/core/evolagent-registry.js +28 -2
  90. package/dist/core/group/invite-service.js +924 -0
  91. package/dist/core/interaction-router.js +22 -8
  92. package/dist/core/message/im-renderer.js +94 -16
  93. package/dist/core/message/message-bridge.js +140 -13
  94. package/dist/core/message/message-log.js +5 -3
  95. package/dist/core/message/message-queue.js +73 -41
  96. package/dist/core/message/response-engine.js +729 -118
  97. package/dist/core/permission/approval-gateway.js +36 -3
  98. package/dist/core/permission/ec-command-parser.js +76 -36
  99. package/dist/core/permission/protected-paths.js +134 -16
  100. package/dist/core/permission/sandbox-runtime.js +72 -3
  101. package/dist/core/permission/tool-error-code.js +15 -0
  102. package/dist/core/permission/tool-policy.js +8 -8
  103. package/dist/core/relation/peer-identity.js +57 -6
  104. package/dist/core/relation/shared-data.js +106 -0
  105. package/dist/core/role/runtime-policy.js +64 -0
  106. package/dist/core/runtime-lock.js +10 -26
  107. package/dist/core/session/runner-task-history.js +170 -0
  108. package/dist/core/session/session-manager.js +38 -6
  109. package/dist/core/session/session-mapper.js +41 -0
  110. package/dist/core/session/session-turn-coordinator.js +37 -7
  111. package/dist/core/session/session-turns.js +48 -7
  112. package/dist/core/task/task-recovery-service.js +176 -0
  113. package/dist/core/task/task-resume-operation.js +32 -0
  114. package/dist/eck/group-rules-sync.js +5 -3
  115. package/dist/eck/kit-renderer.js +1 -0
  116. package/dist/index.js +322 -207
  117. package/dist/ipc.js +141 -15
  118. package/dist/paths.js +17 -4
  119. package/dist/product.js +3 -0
  120. package/dist/trigger/event-source.js +19 -0
  121. package/dist/trigger/feedback.js +233 -40
  122. package/dist/trigger/parser.js +55 -5
  123. package/dist/trigger/patch.js +9 -0
  124. package/dist/trigger/scheduler.js +30 -4
  125. package/dist/trigger/validation.js +20 -3
  126. package/dist/utils/atomic-write.js +98 -0
  127. package/dist/utils/cross-platform.js +87 -16
  128. package/dist/utils/ecweb-utils.js +1 -0
  129. package/dist/utils/error-utils.js +40 -2
  130. package/dist/utils/logger.js +23 -13
  131. package/dist/utils/npm-ops.js +119 -4
  132. package/dist/utils/package-version-info.js +244 -0
  133. package/dist/utils/tool-summary.js +59 -3
  134. package/dist/utils/welcome.js +174 -1
  135. package/dist/utils/windows-shell-trust.js +37 -0
  136. package/ecagent/dist/harness/agent-harness.js +3 -0
  137. package/ecagent/dist/harness/session/jsonl-storage.js +21 -2
  138. package/kits/docs/INDEX.md +0 -6
  139. package/kits/docs/channels/aun.md +23 -2
  140. package/kits/docs/channels/feishu.md +1 -1
  141. package/kits/docs/context-assembly.md +29 -17
  142. package/kits/docs/evolcore/INDEX.md +8 -3
  143. package/kits/docs/evolcore/agent-create.md +39 -7
  144. package/kits/docs/evolcore/agent.md +33 -1
  145. package/kits/docs/evolcore/aid.md +9 -1
  146. package/kits/docs/evolcore/config.md +40 -1
  147. package/kits/docs/evolcore/group-collaboration.md +1 -1
  148. package/kits/docs/evolcore/group.md +117 -2
  149. package/kits/docs/evolcore/menu-client.md +16 -0
  150. package/kits/docs/evolcore/msg.md +3 -0
  151. package/kits/docs/evolcore/storage.md +3 -0
  152. package/kits/docs/evolcore/task.md +14 -0
  153. package/kits/docs/evolcore/trigger.md +51 -3
  154. package/kits/docs/venues/group.md +5 -0
  155. package/kits/eck_manifest.json +27 -0
  156. package/kits/rules/01-overview.md +11 -7
  157. package/kits/rules/04-relation.md +2 -1
  158. package/kits/rules/05-venue.md +1 -1
  159. package/kits/rules/06-channel.md +6 -4
  160. package/kits/schemas/agent-config.schema.12.json +25 -1
  161. package/kits/schemas/daemon.schema.6.json +2 -2
  162. package/kits/schemas/defaults.schema.5.json +1 -1
  163. package/kits/schemas/relation-config.schema.1.json +1 -1
  164. package/kits/schemas/relation-config.schema.5.json +1 -1
  165. package/kits/schemas/role-config.schema.1.json +14 -0
  166. package/kits/templates/roles/member.json +5 -0
  167. package/kits/templates/roles/visitor.json +5 -0
  168. package/kits/templates/system-fragments/baseagent.md +1 -1
  169. package/kits/templates/system-fragments/bootstrap.md +26 -9
  170. package/kits/templates/system-fragments/channel.md +8 -44
  171. package/kits/templates/system-fragments/commands.md +10 -2
  172. package/kits/templates/system-fragments/group-unread-history.md +7 -0
  173. package/kits/templates/system-fragments/reply.md +56 -0
  174. package/package.json +7 -3
  175. package/dist/aun/group-identity.js +0 -9
  176. /package/dist/aun/{group-fs-download.js → group/fs-download.js} +0 -0
  177. /package/dist/aun/{msg/group-index.js → group/index-rpc.js} +0 -0
@@ -9,22 +9,23 @@ import { loadDefaults, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from
9
9
  import { resolveEffective } from '../config/config-manager.js';
10
10
  import { resolveAnthropicConfig } from '../agents/baseagent.js';
11
11
  import { migrateProject } from '../config-store.js';
12
- import { ipcQuery } from '../ipc.js';
12
+ import { ipcQuery, IpcTransportError } from '../ipc.js';
13
13
  import { isHelpFlag } from './help.js';
14
14
  import { cmdInit, needsControlAidInit, initTail } from './init.js';
15
15
  import * as platform from '../utils/cross-platform.js';
16
16
  import { EventBus } from '../core/event-bus.js';
17
- import { tryUpgrade, tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
17
+ import { tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
18
+ import { ensureFreshPackageVersionInfo } from '../utils/package-version-info.js';
18
19
  import { ecwebVersionRequirementError, resolveEcwebLaunchCommand, fetchRemotePairingCode } from '../utils/ecweb-utils.js';
19
20
  import { isHostChinese } from '../utils/locale.js';
20
- import { scanInstances, cleanupInstances, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
21
+ import { scanInstances, cleanupInstances, findOrphanProcesses } from '../utils/instance-registry.js';
21
22
  import { filterLogFiles, deriveLogTypes, computePreChecked, validateLogTypes, shortLogName as shortLogNameLocal, isWatchLogFile } from './watch-logs.js';
22
23
  import { displaySessionTitle } from '../core/session/session-title.js';
23
24
  import { isNonAgentSessionChannel } from '../core/system-channels.js';
24
25
  import { printCodeStats } from './code-stats.js';
25
26
  import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
26
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
27
- import { WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
27
+ import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
28
+ import { RUNTIME_HOME_ENV, WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
28
29
  import { rotateStdoutLog } from '../utils/log-writer.js';
29
30
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
30
31
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
@@ -40,6 +41,8 @@ export function ensureCodexHookBeforeLifecycle(onRepaired) {
40
41
  try {
41
42
  const usesCodex = loadDefaults()?.active_baseagent === 'codex'
42
43
  || loadAllAgents().agents.some(agent => {
44
+ if (agent.enabled === false)
45
+ return false;
43
46
  try {
44
47
  return resolveEffective({ self: agent.aid }).active_baseagent === 'codex';
45
48
  }
@@ -146,7 +149,7 @@ function rejectPidIsolatedLifecycle(action, status, ping) {
146
149
  }
147
150
  async function tryDelegatedDaemonRestart(socketPath) {
148
151
  const sessionId = process.env.EVOLCORE_SESSION_ID;
149
- const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
152
+ const delegationToken = readDelegationCredential();
150
153
  if (!sessionId || !delegationToken)
151
154
  return false;
152
155
  const response = await ipcQuery(socketPath, {
@@ -248,27 +251,46 @@ function runSourceBuild() {
248
251
  });
249
252
  });
250
253
  }
251
- /**
252
- * Spawn the restart monitor and confirm it actually reached its own code.
253
- *
254
- * The daemon is already stopped at this point, so a monitor that dies on
255
- * startup leaves nothing running and nobody watching — the exact silent
256
- * failure this mechanism exists to prevent.
257
- */
258
- async function spawnRestartMonitorAndConfirm(monitorEntry, reportLine) {
259
- const result = await platform.launchMonitorAndConfirm([...monitorEntry.args, 'restart-monitor'], { EVOLCORE_HOME: resolvePaths().root, ...(monitorEntry.env ?? {}) }, pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid === pid), pid => scanInstances().restartMonitors.some(m => m.alive && m.record.pid !== pid));
260
- if (result.ok)
261
- return { ok: true };
262
- for (const line of (result.stderrTail ?? '').split(/\r?\n/).filter(Boolean).slice(-8)) {
263
- reportLine(` | ${line}`);
264
- }
265
- return { ok: false, reason: result.reason ?? 'unknown' };
266
- }
267
254
  async function requireFreshSourceBuild() {
268
255
  if (!sourceBuildNeedsRefresh())
269
256
  return true;
270
257
  return await runSourceBuild();
271
258
  }
259
+ async function launchPackageLifecycle(source, log, options = {}) {
260
+ if (!options.prepared)
261
+ await ensureFreshPackageVersionInfo(undefined, { source, log });
262
+ const root = resolvePaths().root;
263
+ const launch = platform.resolveMonitorLaunch(getPackageRoot(), path.join(root, 'data', 'monitor-stage'));
264
+ if (launch.note)
265
+ log(launch.note);
266
+ if (!fs.existsSync(launch.entry)) {
267
+ if (launch.stageDir)
268
+ platform.cleanupMonitorStage(launch.stageDir);
269
+ return { ok: false, reason: 'RESTART_ENTRY_MISSING' };
270
+ }
271
+ const args = [...launch.args, 'restart-monitor', ...(options.clear ? ['--clear'] : [])];
272
+ const env = { ...options.env, [RUNTIME_HOME_ENV]: root, ...launch.env, [AGENT_DELEGATION_TOKEN_ENV]: '' };
273
+ if (options.foreground) {
274
+ return new Promise(resolve => {
275
+ const child = spawn(process.execPath, [...args, '--foreground'], {
276
+ stdio: 'inherit', detached: false, windowsHide: true, env: { ...process.env, ...env },
277
+ });
278
+ child.once('error', () => {
279
+ if (launch.stageDir)
280
+ platform.cleanupMonitorStage(launch.stageDir);
281
+ resolve({ ok: false, reason: 'Foreground monitor could not start' });
282
+ });
283
+ child.once('exit', code => {
284
+ process.exitCode = code ?? 1;
285
+ resolve({ ok: code === 0, ...(code !== 0 ? { reason: 'Foreground lifecycle exited unsuccessfully' } : {}) });
286
+ });
287
+ });
288
+ }
289
+ const result = await platform.launchMonitorAndConfirm(args, env, pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid === pid), pid => scanInstances().restartMonitors.some(item => item.alive && item.record.pid !== pid));
290
+ if (!result.ok && launch.stageDir)
291
+ platform.cleanupMonitorStage(launch.stageDir);
292
+ return { ok: result.ok, ...(!result.ok ? { reason: result.reason } : {}) };
293
+ }
272
294
  function requireCompletedDataMigration(root, action = 'start') {
273
295
  const requirement = inspectDataMigrationRequirement(root);
274
296
  if (!requirement.required)
@@ -289,7 +311,11 @@ function requireCompletedDataMigration(root, action = 'start') {
289
311
  * A failed upgrade is reported, but the currently installed version remains
290
312
  * usable and the caller continues with its normal lifecycle.
291
313
  */
292
- export async function runUpgradeChecks(upgradeFn = signal => tryUpgrade({ signal }), ecwebUpgradeFn = signal => tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME, { signal }), timeoutMs = 60_000) {
314
+ export async function runUpgradeChecks(upgradeFn, ecwebUpgradeFn, timeoutMs = 60_000) {
315
+ if (!upgradeFn || !ecwebUpgradeFn) {
316
+ await ensureFreshPackageVersionInfo(undefined, { source: 'start', log: console.log });
317
+ return;
318
+ }
293
319
  console.log('📦 Checking for updates...');
294
320
  const runWithTimeout = async (label, fn) => {
295
321
  const controller = new AbortController();
@@ -468,7 +494,9 @@ export async function cmdStart(opts = {}) {
468
494
  return;
469
495
  // A stopped container may miss the daily upgrade trigger. Check once on
470
496
  // every real start so the service does not stay on an old installation.
471
- await (opts.upgradeChecks ?? runUpgradeChecks)();
497
+ const versionInfo = await ensureFreshPackageVersionInfo(undefined, { source: 'start', log: console.log });
498
+ if (opts.upgradeChecks)
499
+ await opts.upgradeChecks();
472
500
  // 旧配置自动迁移(daemon.json → 新结构)
473
501
  const { autoMigrateIfNeeded } = await import('../config-store.js');
474
502
  autoMigrateIfNeeded();
@@ -593,6 +621,20 @@ export async function cmdStart(opts = {}) {
593
621
  return;
594
622
  }
595
623
  reportOrphans(orphans);
624
+ if (versionInfo.some(result => result.hasUpdate)) {
625
+ cleanEnv();
626
+ const launched = await launchPackageLifecycle('start', console.log, {
627
+ prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
628
+ });
629
+ if (!launched.ok) {
630
+ console.error(`❌ 启动事务未能建立:${launched.reason}`);
631
+ process.exitCode = 1;
632
+ }
633
+ else if (!opts.foreground) {
634
+ console.log('📦 已交由 restart-monitor 应用缓存版本并启动服务');
635
+ }
636
+ return;
637
+ }
596
638
  console.log('🚀 Starting EvolCore...');
597
639
  const stdoutRotation = rotateStdoutLog(p.logs);
598
640
  if (stdoutRotation.rotatedPath) {
@@ -881,126 +923,18 @@ export async function cmdRestart(opts = {}) {
881
923
  if (initialRuntimeOrphans.length === 0 && rejectPidIsolatedLifecycle('restart', initialStatus, ping))
882
924
  return;
883
925
  }
884
- // 构建交给 restart-monitor:它在 daemon 停止之后才编译,dist/ 不会被活进程
885
- // 占用,失败还能回滚到上一份产物。这里再编译一次会落在事务保护之外。
886
- // restart-monitor owns upgrade checks. Running them here as well caused a
887
- // normal restart to perform every registry lookup twice, and in installed
888
- // mode could race the monitor while it was preparing its rollback point.
926
+ // The shared launchPackageLifecycle path delegates to launchMonitorAndConfirm;
927
+ // the monitor retains shutdown, orphan checks, build and rollback ownership.
928
+ // Network preflight must finish before a confirmed monitor stops the daemon.
889
929
  if (!requireCompletedDataMigration(resolveRoot(), 'restart'))
890
930
  return;
891
- // 停止所有活 main 进程(可能不止一个)
892
- const status = initialStatus;
893
- const aliveMains = status.mains.filter(m => m.alive);
894
- if (aliveMains.length > 0) {
895
- if (aliveMains.length > 1) {
896
- console.log(`⚠ 检测到 ${aliveMains.length} 个 main 实例,将一并停止: ${aliveMains.map(m => m.record.pid).join(', ')}`);
897
- }
898
- const mainPids = new Set(aliveMains.map(entry => entry.record.pid));
899
- const currentPing = await probeDaemon(socketPath);
900
- const gracefulPid = currentPing && mainPids.has(currentPing.pid)
901
- ? await requestDaemonShutdown(socketPath, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec restart', mainPids)
902
- : null;
903
- const stopped = await Promise.all(aliveMains.map(m => stopPid(m.record.pid, m.record.pid === gracefulPid, m.record.startedAt)));
904
- if (stopped.some(result => !result)) {
905
- console.error('❌ Restart aborted because an existing EvolCore process could not be stopped');
906
- process.exitCode = 1;
907
- return;
908
- }
909
- await sleep(500);
910
- }
911
- cleanupInstances();
912
- const remainingMainProcesses = scanInstances().mains.filter(entry => entry.alive);
913
- if (remainingMainProcesses.length > 0) {
914
- console.error(`❌ Restart aborted: EvolCore process still registered and alive (PID: ${remainingMainProcesses.map(entry => entry.record.pid).join(', ')})`);
915
- process.exitCode = 1;
916
- return;
917
- }
918
- // 孤儿处理:同 HOME/同安装包的孤儿属于本运行时,先确认退出再替换;
919
- // 跨 HOME 的孤儿只在 --clear 时 kill,否则仅警告。findOrphanProcesses
920
- // 在 Windows 上需要一次 CIM 查询;保留一个快照,避免同一阶段重复扫描。
921
- {
922
- const orphans = findOrphanProcesses();
923
- const confirmedTestDaemons = orphans.filter(o => o.confirmedTestDaemon);
924
- if (confirmedTestDaemons.length > 0) {
925
- const killed = killOrphans(confirmedTestDaemons);
926
- console.log(`☠ 已清理 ${killed.length} 个泄漏的测试 daemon: ${killed.join(', ')}`);
927
- await sleep(500);
928
- }
929
- const runtimeOrphans = orphans.filter(o => isRuntimeOrphan(o));
930
- if (runtimeOrphans.length > 0) {
931
- console.log(`⚠ 检测到未登记的 EvolCore 进程,将一并停止: ${runtimeOrphans.map(o => o.pid).join(', ')}`);
932
- const orphanPids = new Set(runtimeOrphans.map(orphan => orphan.pid));
933
- const currentPing = await probeDaemon(socketPath);
934
- const gracefulPid = currentPing && orphanPids.has(currentPing.pid)
935
- ? await requestDaemonShutdown(socketPath, DAEMON_SHUTDOWN_TIMEOUT_MS, 'ec restart', orphanPids)
936
- : null;
937
- const stopped = await Promise.all(runtimeOrphans.map(o => stopPid(o.pid, o.pid === gracefulPid)));
938
- if (stopped.some(result => !result)) {
939
- console.error('❌ Restart aborted because an unregistered EvolCore process could not be stopped');
940
- process.exitCode = 1;
941
- return;
942
- }
943
- await sleep(500);
944
- }
945
- // stopPid() already waited for every runtime orphan above, so avoid a
946
- // second scan before handling cross-HOME records.
947
- const otherHome = orphans.filter(o => !o.confirmedTestDaemon && !isRuntimeOrphan(o));
948
- if (opts.clear && otherHome.length > 0) {
949
- const killed = killOrphans(otherHome);
950
- console.log(`☠ 已 SIGKILL ${killed.length} 个跨 HOME 孤儿进程: ${killed.join(', ')}`);
951
- await sleep(500);
952
- }
953
- else if (otherHome.length > 0) {
954
- reportOrphans(otherHome);
955
- }
956
- // Keep the post-stop ownership guard: a process that appeared during the
957
- // cleanup window must still block replacement.
958
- const remainingRuntimeOrphans = findOrphanProcesses().filter(o => isRuntimeOrphan(o));
959
- if (remainingRuntimeOrphans.length > 0) {
960
- console.error(`❌ Restart aborted: EvolCore process still running (PID: ${remainingRuntimeOrphans.map(o => o.pid).join(', ')})`);
961
- process.exitCode = 1;
962
- return;
963
- }
964
- }
965
- // 只清理 EvolCore 自己登记的 codex app-server,不能影响桌面端实例。
966
- {
967
- const killed = stopCodexAppServerOrphans();
968
- if (killed > 0) {
969
- console.log(`☠ 已清理 ${killed} 个 EvolCore codex app-server 孤儿进程`);
970
- await sleep(500);
971
- }
972
- }
973
- console.log(`⏱ restart prep done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s, starting...`);
974
- // 通过 restart-monitor 启动,避免在 agent 内调用时自杀。
975
- // 开发仓下 monitor 从源码跑;安装包下先复制到 stage 目录,两种情况都让看门狗
976
- // 不住在会被替换的产物里。
977
- const monitorEntry = platform.resolveMonitorLaunch(getPackageRoot(), path.join(resolvePaths().root, 'data', 'monitor-stage'));
978
- if (monitorEntry.note)
979
- console.log(`ℹ ${monitorEntry.note}`);
980
- if (!fs.existsSync(monitorEntry.entry)) {
981
- console.error(`❌ 重启中止:找不到 restart-monitor 入口 ${monitorEntry.entry}`);
982
- console.error(' 请先执行 npm run build 重建产物');
983
- process.exitCode = 1;
984
- return;
985
- }
986
- const monitorStarted = await spawnRestartMonitorAndConfirm(monitorEntry, log => console.error(log));
987
- if (!monitorStarted.ok) {
988
- if (monitorEntry.stageDir)
989
- platform.cleanupMonitorStage(monitorEntry.stageDir);
990
- console.error(`❌ 重启中止:restart-monitor 未能启动(${monitorStarted.reason})`);
991
- if (fs.existsSync(path.join(getPackageRoot(), 'src', 'cli', 'index.ts'))) {
992
- console.error(' 源码可能存在语法错误导致 tsx 无法加载。请执行 npx tsc --noEmit 检查。');
993
- }
994
- console.error(' 服务已停止,请修复后执行 ec start。');
931
+ const launched = await launchPackageLifecycle('restart', console.log, { clear: opts.clear });
932
+ if (!launched.ok) {
933
+ console.error(`❌ 重启中止:restart-monitor 未能启动(${launched.reason});当前服务未主动停止`);
995
934
  process.exitCode = 1;
996
935
  return;
997
936
  }
998
- console.log('🔄 Restart monitor started; service is coming back online in the background...');
999
- // 代码统计(开发环境)
1000
- if (path.normalize(resolveRoot()) === path.normalize(getPackageRoot())) {
1001
- console.log('');
1002
- printCodeStats(getPackageRoot(), resolvePaths().logs);
1003
- }
937
+ console.log(`🔄 Restart monitor started (${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s); service is restarting...`);
1004
938
  }
1005
939
  function normalizeRuntimePath(value) {
1006
940
  return value.replace(/[\\/]+/g, '/').toLowerCase();
@@ -1308,8 +1242,13 @@ export async function cmdStatus() {
1308
1242
  console.log(' 请稍后再次执行 ec status。');
1309
1243
  }
1310
1244
  else {
1311
- console.log(ipcFailure ? '⚠ 无法确认 EvolCore 运行状态:IPC 不可用,且没有可见的存活 PID。' : '⚠ EvolCore is not running');
1312
- if (ipcFailure)
1245
+ // With no live main/monitor, a missing Windows pipe is the normal stopped
1246
+ // state. Preserve uncertainty for permissions, timeouts, unknown errors,
1247
+ // and Unix callers that may be isolated from the daemon's PID namespace.
1248
+ const stopped = !ipcFailure || (platform.isWindows && ipcFailure instanceof IpcTransportError
1249
+ && ['ENOENT', 'ECONNREFUSED'].includes(ipcFailure.code));
1250
+ console.log(stopped ? '⚠ EvolCore is not running' : '⚠ 无法确认 EvolCore 运行状态:IPC 不可用,且没有可见的存活 PID。');
1251
+ if (!stopped && ipcFailure)
1313
1252
  console.log(` ${ipcFailure instanceof Error ? ipcFailure.message : String(ipcFailure)}`);
1314
1253
  }
1315
1254
  // Session & Project statistics (从文件系统读)
@@ -2720,17 +2659,18 @@ function findAliveEcweb(p) {
2720
2659
  return alive[0] ?? null;
2721
2660
  }
2722
2661
  /**
2723
- * 轻量 HTTP 就绪探测:GET / 返回 2xx 即视为就绪。
2724
- * 不走 /api/pair-code(会触发配对码刷新副作用),不走 /api/health(不存在)。
2725
- * 根路径由 serveStatic 处理,返回 index.html,无副作用。
2662
+ * 轻量 HTTP 就绪探测:ECWeb 的健康端点必须返回明确的服务标识。
2663
+ * 不走 /api/pair-code(会触发配对码刷新副作用),也不依赖静态页面内容。
2726
2664
  */
2727
2665
  async function probeEcwebReady(port) {
2728
2666
  try {
2729
- const res = await fetch(`http://127.0.0.1:${port}/`, {
2730
- method: 'GET',
2667
+ const res = await fetch(`http://127.0.0.1:${port}/api/health`, {
2731
2668
  signal: AbortSignal.timeout(2000),
2732
2669
  });
2733
- return res.ok;
2670
+ if (!res.ok)
2671
+ return false;
2672
+ const body = await res.json();
2673
+ return body.ok === true && body.service === 'ecweb';
2734
2674
  }
2735
2675
  catch {
2736
2676
  return false;
@@ -2756,8 +2696,9 @@ async function waitForExistingEcweb(p, timeoutMs = 3_000) {
2756
2696
  }
2757
2697
  /**
2758
2698
  * 输出 ECWeb 状态。判定口径:
2759
- * 1. 进程是否存活(instance/ 下的 pid 文件 + isProcessRunning)
2760
- * 2. HTTP 是否就绪——探测根路径 /(serveStatic → index.html, 无副作用)。
2699
+ * 1. HTTP 是否就绪——探测 ECWeb 专用 /api/health 端点。
2700
+ * 2. 进程是否存活(instance/ 下的 pid 文件 + isProcessRunning)仅用于补充诊断;
2701
+ * 某些 CLI 运行在隔离 PID namespace 中,看不到宿主 ECWeb PID 是正常情况。
2761
2702
  */
2762
2703
  async function printEcwebStatus(p) {
2763
2704
  try {
@@ -2773,6 +2714,15 @@ async function printEcwebStatus(p) {
2773
2714
  // still provide an authoritative runtime status without local config access.
2774
2715
  }
2775
2716
  const entries = readEcwebInstanceEntries(p);
2717
+ // Probe before consulting PID visibility. The daemon and CLI can be in
2718
+ // different PID namespaces while sharing the host network namespace.
2719
+ let readyEntry;
2720
+ for (const entry of entries) {
2721
+ if (await probeEcwebReady(entry.record.port)) {
2722
+ readyEntry = entry;
2723
+ break;
2724
+ }
2725
+ }
2776
2726
  const aliveEntries = [];
2777
2727
  for (const entry of entries) {
2778
2728
  if (platform.isProcessRunning(entry.record.pid)) {
@@ -2781,22 +2731,21 @@ async function printEcwebStatus(p) {
2781
2731
  else {
2782
2732
  // A stale EC Web record must never be used to report the PID of an
2783
2733
  // unrelated process that later inherited the port.
2784
- try {
2785
- fs.unlinkSync(entry.filePath);
2734
+ // Keep a record whose dedicated health endpoint just proved live: the
2735
+ // PID may be hidden by a namespace boundary rather than actually dead.
2736
+ if (entry.filePath !== readyEntry?.filePath) {
2737
+ try {
2738
+ fs.unlinkSync(entry.filePath);
2739
+ }
2740
+ catch { }
2786
2741
  }
2787
- catch { }
2788
- }
2789
- }
2790
- let readyEntry;
2791
- for (const entry of aliveEntries) {
2792
- if (await probeEcwebReady(entry.record.port)) {
2793
- readyEntry = entry;
2794
- break;
2795
2742
  }
2796
2743
  }
2797
2744
  if (readyEntry) {
2798
2745
  console.log('');
2799
- console.log(`🔭 ECWeb: 运行中 (PID: ${readyEntry.record.pid}) http://localhost:${readyEntry.record.port}`);
2746
+ const pidVisible = aliveEntries.some(entry => entry.filePath === readyEntry.filePath);
2747
+ const pidText = pidVisible ? `PID: ${readyEntry.record.pid}` : `PID: ${readyEntry.record.pid}(当前 namespace 不可见)`;
2748
+ console.log(`🔭 ECWeb: 运行中 (${pidText}) http://localhost:${readyEntry.record.port}`);
2800
2749
  return;
2801
2750
  }
2802
2751
  const occupiedPids = new Set();
@@ -2991,7 +2940,7 @@ export async function startEcwebIfEnabled(p, options = {}) {
2991
2940
  console.log(`✓ ECWeb ${actionLabel} (PID: ${pid}) http://localhost:${port}`);
2992
2941
  if (pair) {
2993
2942
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2994
- console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,已配对过的浏览器无需重新配对)`);
2943
+ console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,仅可配对一次,已配对过的浏览器无需重新配对)`);
2995
2944
  }
2996
2945
  else {
2997
2946
  console.log(' daemon 尚未提供配对码;EvolCore 启动后可通过 /pair 或 ECWeb 页面获取');
@@ -3027,7 +2976,7 @@ async function printEcwebAccess(port) {
3027
2976
  }
3028
2977
  if (pair) {
3029
2978
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
3030
- console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,配对后远程访问 token 缓存 30 天)`);
2979
+ console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,仅可配对一次,token 缓存 7 天,有访问自动续期)`);
3031
2980
  }
3032
2981
  else {
3033
2982
  console.log(' 配对码: 暂不可用(稍后重试 ec watch web,或查看 logs/watch-web.log)');
package/dist/cli/diag.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import { ipcQuery } from '../ipc.js';
2
2
  import { resolvePaths } from '../paths.js';
3
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
3
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
4
4
  import { parseDiagArgs } from '../core/command/diag-service.js';
5
5
  import { wantsHelp } from './help.js';
6
6
  export async function cmdDiag(args) {
7
7
  if (wantsHelp(args)) {
8
- console.log('用法:ec diag <问题描述> [--owner <daemonOwner-aid>] [--format json]\n ec diag stop [--format json]\n未指定 --owner 时联系本机 daemon.json.owners[0]。');
8
+ console.log('用法:ec diag <问题描述> [--owner <daemonOwner-aid>] [--format json]\n ec diag stop [--format json]\n未指定 --owner 时联系本机 daemon.json.owners[0];有效诊断期间再次执行会追加到原 case。');
9
9
  return;
10
10
  }
11
11
  const json = args.at(-2) === '--format' && args.at(-1) === 'json';
@@ -17,7 +17,7 @@ export async function cmdDiag(args) {
17
17
  type: 'diag', argv,
18
18
  ...(process.env.EVOLCORE_SESSION_ID ? {
19
19
  sessionId: process.env.EVOLCORE_SESSION_ID,
20
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
20
+ delegationToken: readDelegationCredential(),
21
21
  } : {}),
22
22
  }, 30_000) ?? { ok: false, code: 'DAEMON_UNAVAILABLE', message: 'daemon 未返回结果,请检查其运行状态;发送结果尚未确认' };
23
23
  }
@@ -5,8 +5,8 @@ import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
6
6
  import { ipcQuery } from '../ipc.js';
7
7
  import { resolvePaths } from '../paths.js';
8
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
9
- import { downloadGroupFsBytes } from '../aun/group-fs-download.js';
8
+ import { AGENT_DELEGATION_TOKEN_ENV, readDelegationCredential } from '../core/auth/agent-delegation.js';
9
+ import { downloadGroupFsBytes } from '../aun/group/index.js';
10
10
  class FsCliError extends Error {
11
11
  code;
12
12
  suggestion;
@@ -209,7 +209,7 @@ export async function cmdFs(args) {
209
209
  }
210
210
  async function runManagedFs(args, formatJson) {
211
211
  const sessionId = process.env.EVOLCORE_SESSION_ID;
212
- const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
212
+ const delegationToken = readDelegationCredential();
213
213
  const fail = (code, message) => {
214
214
  if (formatJson)
215
215
  console.log(JSON.stringify({ ok: false, code, error: message }));
@@ -4,7 +4,7 @@ import { ipcQuery } from '../ipc.js';
4
4
  import { HANDOFF_QUERY_DEFAULT_LIMIT, HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from '../core/handoff/types.js';
5
5
  import { HANDOFF_ID_RE } from '../core/handoff/store.js';
6
6
  import { isHelpFlag, wantsHelp } from './help.js';
7
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
7
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
8
8
  function parseLimit(value) {
9
9
  const limit = Number(value);
10
10
  if (!value || !Number.isInteger(limit) || limit < 1 || limit > HANDOFF_QUERY_MAX_LIMIT) {
@@ -186,7 +186,7 @@ Commands:
186
186
  sessionId: process.env.EVOLCORE_SESSION_ID,
187
187
  handoffId: parsed.handoffId,
188
188
  content: parsed.content,
189
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
189
+ delegationToken: readDelegationCredential(),
190
190
  }, 5000);
191
191
  if (!result) {
192
192
  console.error('✗ daemon 暂时不可用');
@@ -216,7 +216,7 @@ Commands:
216
216
  sessionId: process.env.EVOLCORE_SESSION_ID,
217
217
  handoffId: parsed.handoffId,
218
218
  content: parsed.content,
219
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
219
+ delegationToken: readDelegationCredential(),
220
220
  }, 5000);
221
221
  if (!result) {
222
222
  console.error('✗ daemon 暂时不可用');
@@ -235,7 +235,7 @@ Commands:
235
235
  }
236
236
  const result = await ipcQuery(resolvePaths().socket, {
237
237
  type: 'handoff-status', sessionId: process.env.EVOLCORE_SESSION_ID, handoffId,
238
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
238
+ delegationToken: readDelegationCredential(),
239
239
  }, 5000);
240
240
  if (!result) {
241
241
  console.error('✗ daemon 暂时不可用');
@@ -267,7 +267,7 @@ Commands:
267
267
  state: parsed.state,
268
268
  filterSessionId: parsed.sessionId,
269
269
  limit: parsed.limit,
270
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
270
+ delegationToken: readDelegationCredential(),
271
271
  }, 5000);
272
272
  if (!result) {
273
273
  console.error('✗ daemon 暂时不可用');
@@ -310,7 +310,7 @@ Commands:
310
310
  agent: parsed.agent,
311
311
  handoffId: parsed.handoffId,
312
312
  limit: parsed.limit,
313
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
313
+ delegationToken: readDelegationCredential(),
314
314
  }, 5000);
315
315
  if (!result) {
316
316
  console.error('✗ daemon 暂时不可用');
package/dist/cli/index.js CHANGED
@@ -5,7 +5,7 @@ import fs from 'fs';
5
5
  import path from 'path';
6
6
  import { getPackageRoot, resolvePaths } from '../paths.js';
7
7
  import { ipcQuery } from '../ipc.js';
8
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
8
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
9
9
  import { cmdInit } from './init.js';
10
10
  import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
11
11
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
@@ -34,6 +34,7 @@ Agent 与会话:
34
34
  agent 管理 EvolAgent
35
35
  ctl 当前托管会话的控制命令
36
36
  queue 查询或操作 agent 消息队列
37
+ task 恢复暂停或异常中断的会话任务
37
38
  trigger 管理定时与事件触发器
38
39
  handoff 查询和完成跨会话回流
39
40
  model 查看或设置模型与推理强度
@@ -247,7 +248,7 @@ export async function main(args) {
247
248
  }
248
249
  if (process.env.EVOLCORE_SESSION_ID) {
249
250
  const sessionId = process.env.EVOLCORE_SESSION_ID;
250
- const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
251
+ const delegationToken = readDelegationCredential();
251
252
  const result = await ipcQuery(resolvePaths().socket, {
252
253
  type: 'status.auth', sessionId, delegationToken,
253
254
  });
@@ -328,6 +329,11 @@ export async function main(args) {
328
329
  case 'queue':
329
330
  await cmdQueue(args.slice(1));
330
331
  break;
332
+ case 'task': {
333
+ const { cmdTask } = await import('./task-command.js');
334
+ await cmdTask(args.slice(1));
335
+ break;
336
+ }
331
337
  case 'trigger':
332
338
  {
333
339
  const { cmdTrigger } = await import('./trigger-command.js');
package/dist/cli/init.js CHANGED
@@ -7,7 +7,7 @@ import { commandExists } from '../utils/cross-platform.js';
7
7
  import { scanInstances } from '../utils/instance-registry.js';
8
8
  import { saveDefaultsSafe, loadAllAgents, loadDaemonConfig, saveDaemonConfig } from '../config-store.js';
9
9
  import { ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
10
- import { generateControlAid, resolveControlAidDomain } from '../aun/aid/control-aid.js';
10
+ import { ensureControlAgentMd, generateControlAid, resolveControlAidDomain } from '../aun/aid/control-aid.js';
11
11
  import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
12
12
  import { DEFAULT_ECAGENT_BASE_URL, resolveEcagentConfig } from '../agents/baseagent.js';
13
13
  import { defaultProjectsRoot } from '../utils/project-path.js';
@@ -484,6 +484,12 @@ export async function initTail(options = {}) {
484
484
  let ownerBindingInterrupted = false;
485
485
  if (daemonConfig.aid) {
486
486
  console.log(`✓ 控制 AID 已存在: ${daemonConfig.aid}`);
487
+ try {
488
+ await ensureControlAgentMd(daemonConfig.aid);
489
+ }
490
+ catch (e) {
491
+ console.error(`⚠️ 控制 AID agent.md 补全失败(本次继续,后续启动会重试): ${e?.message || e}`);
492
+ }
487
493
  }
488
494
  else {
489
495
  try {
@@ -861,6 +867,33 @@ export async function cmdInitNonInteractive(opts, dependencies = {}) {
861
867
  fail('CONTROL_AID_CREATE_FAILED', `control AID creation failed: ${e?.message || e}`, EXIT_RUNTIME, format);
862
868
  }
863
869
  }
870
+ if (controlAid) {
871
+ const certPath = path.join(resolvePaths().root, 'AIDs', controlAid, 'public', 'cert.pem');
872
+ if (!fs.existsSync(certPath)) {
873
+ // An externally supplied/pre-created AID without a local certificate
874
+ // cannot be signed or uploaded by this process. Avoid entering the SDK
875
+ // network path during non-interactive init; later daemon startup can
876
+ // retry once the identity material is provisioned.
877
+ console.error(`control AID agent.md publish skipped: local certificate not found for ${controlAid}`);
878
+ }
879
+ else {
880
+ try {
881
+ await ensureControlAgentMd(controlAid);
882
+ }
883
+ catch (e) {
884
+ // A registered control AID remains usable when the Name Service is
885
+ // temporarily unavailable; startup and the next init retry publication.
886
+ if (format === 'json') {
887
+ // Keep the successful init result machine-readable; surface the
888
+ // warning through stderr so stdout remains valid JSON.
889
+ console.error(`control AID agent.md publish warning: ${e?.message || e}`);
890
+ }
891
+ else {
892
+ console.error(`⚠️ 控制 AID agent.md 补全失败(本次继续,后续启动会重试): ${e?.message || e}`);
893
+ }
894
+ }
895
+ }
896
+ }
864
897
  // ── 9. 写 daemon.json(aid + owners + ecweb)──
865
898
  try {
866
899
  const next = {
@@ -20,6 +20,7 @@ const MANAGED_TASK_COMMANDS = new Set([
20
20
  'stats',
21
21
  'status',
22
22
  'storage',
23
+ 'task',
23
24
  'trigger',
24
25
  'version',
25
26
  '-v',
@@ -30,7 +31,7 @@ export class ManagedCommandNotAllowedError extends Error {
30
31
  command;
31
32
  code = 'NOT_ALLOWED';
32
33
  constructor(command) {
33
- super(`Command ${command} is not available in a managed Agent task`);
34
+ super(`Command ${command} is not available in a managed Agent task; run it from the host terminal after the task completes`);
34
35
  this.command = command;
35
36
  this.name = 'ManagedCommandNotAllowedError';
36
37
  }
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import { getArgValue, wantsHelp } from './help.js';
3
- import { executeMenuClientRequest, MenuClientError } from '../aun/menu-client.js';
4
- import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
3
+ import { executeMenuClientRequest, MenuClientError } from '../aun/menu/index.js';
4
+ import { readDelegationCredential } from '../core/auth/agent-delegation.js';
5
5
  function parseDurationMs(value) {
6
6
  if (value === undefined)
7
7
  return undefined;
@@ -94,12 +94,16 @@ export async function cmdMenuClient(args) {
94
94
  }
95
95
  let response;
96
96
  if (process.env.EVOLCORE_SESSION_ID) {
97
- const { ipcQuery } = await import('../ipc.js');
97
+ const { ipcQuery, IpcTransportError } = await import('../ipc.js');
98
98
  const { resolvePaths } = await import('../paths.js');
99
99
  const result = await ipcQuery(resolvePaths().socket, {
100
100
  type: 'menu-client.request', argv: args, sessionId: process.env.EVOLCORE_SESSION_ID,
101
- delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
102
- }, 250_000);
101
+ delegationToken: readDelegationCredential(),
102
+ }, 250_000, { throwOnError: true }).catch(error => {
103
+ if (!(error instanceof IpcTransportError))
104
+ throw error;
105
+ throw new MenuClientError(error.code, error.message);
106
+ });
103
107
  if (!result?.ok)
104
108
  throw new MenuClientError(result?.code ?? 'DAEMON_UNAVAILABLE', result?.error ?? 'Managed Menu daemon is unavailable');
105
109
  response = result.response;
@@ -107,4 +111,8 @@ export async function cmdMenuClient(args) {
107
111
  else
108
112
  response = await executeMenuClientRequest(parseMenuClientArgs(args));
109
113
  console.log(JSON.stringify(response, null, 2));
114
+ // Transport success does not imply the requested operation succeeded.
115
+ // Preserve the complete wire response for callers while failing the CLI.
116
+ if (response?.error)
117
+ process.exitCode = 1;
110
118
  }