evolcore 0.0.11 → 0.0.13

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/bin/ec-safe-output.js +26 -7
  4. package/dist/agents/claude-runner.js +86 -7
  5. package/dist/agents/codex-app-server-client.js +174 -20
  6. package/dist/agents/codex-runner.js +375 -92
  7. package/dist/agents/ecagent-runner.js +39 -4
  8. package/dist/agents/gemini-runner.js +1 -0
  9. package/dist/aun/aid/client.js +11 -21
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/managed-operation.js +107 -0
  12. package/dist/aun/aid/store.js +13 -7
  13. package/dist/aun/group-fs-download.js +56 -0
  14. package/dist/aun/group-identity.js +1 -2
  15. package/dist/aun/msg/group.js +91 -44
  16. package/dist/aun/msg/index.js +1 -2
  17. package/dist/aun/msg/managed-operation.js +865 -0
  18. package/dist/aun/msg/mention-schema.js +20 -0
  19. package/dist/aun/msg/p2p.js +7 -0
  20. package/dist/aun/msg/upload.js +16 -1
  21. package/dist/aun/outbox.js +31 -2
  22. package/dist/aun/rpc/client.js +66 -0
  23. package/dist/aun/rpc/index.js +1 -2
  24. package/dist/aun/storage/{upload.js → client.js} +11 -2
  25. package/dist/aun/storage/index.js +1 -2
  26. package/dist/channels/aun.js +655 -240
  27. package/dist/channels/daemon.js +13 -0
  28. package/dist/cli/agent-command.js +56 -8
  29. package/dist/cli/agent.js +108 -31
  30. package/dist/cli/aun-commands.js +270 -69
  31. package/dist/cli/bench.js +12 -3
  32. package/dist/cli/cli-argv.js +10 -0
  33. package/dist/cli/config.js +9 -17
  34. package/dist/cli/ctl-command.js +1 -1
  35. package/dist/cli/daemon-commands.js +70 -55
  36. package/dist/cli/fs-command.js +79 -10
  37. package/dist/cli/handoff-command.js +3 -0
  38. package/dist/cli/index.js +30 -2
  39. package/dist/cli/init.js +77 -8
  40. package/dist/cli/managed-command-guard.js +41 -0
  41. package/dist/cli/model.js +11 -4
  42. package/dist/cli/queue-command.js +53 -1
  43. package/dist/cli/response.js +71 -0
  44. package/dist/cli/restart-monitor.js +7 -32
  45. package/dist/cli/trigger-command.js +11 -0
  46. package/dist/config/access-policy.js +1 -2
  47. package/dist/config/builtin-roles.js +9 -0
  48. package/dist/config/config-manager.js +21 -1
  49. package/dist/config/contact-book-store.js +11 -3
  50. package/dist/config/contact-request-service.js +161 -15
  51. package/dist/config/mention-mode.js +8 -24
  52. package/dist/config/peer-role-resolver.js +6 -2
  53. package/dist/config/role-migration-startup.js +53 -0
  54. package/dist/config/role-store.js +1 -1
  55. package/dist/core/auth/agent-delegation.js +1 -2
  56. package/dist/core/auth/auth-gateway.js +29 -1
  57. package/dist/core/auth/authorization-audit.js +15 -0
  58. package/dist/core/auth/operation-authorizer.js +31 -7
  59. package/dist/core/auth/operation-catalog.js +196 -25
  60. package/dist/core/bootstrap-messages.js +25 -0
  61. package/dist/core/bootstrap-service.js +64 -4
  62. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  63. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  64. package/dist/core/capability/skill-discovery.js +55 -0
  65. package/dist/core/channel-loader.js +1 -1
  66. package/dist/core/command/cli-intent-parser.js +9 -0
  67. package/dist/core/command/command-handler.js +650 -42
  68. package/dist/core/command/connect-menu.js +15 -0
  69. package/dist/core/command/group-menu.js +4 -4
  70. package/dist/core/command/menu-catalog.js +370 -0
  71. package/dist/core/command/menu-handler.js +230 -51
  72. package/dist/core/command/menu-protocol.js +94 -2
  73. package/dist/core/command/slash-gate.js +5 -2
  74. package/dist/core/command/slash-handler.js +99 -31
  75. package/dist/core/daemon-file-cache.js +2 -1
  76. package/dist/core/data-migration.js +9 -2
  77. package/dist/core/event-catalog.js +6 -0
  78. package/dist/core/evolagent.js +4 -2
  79. package/dist/core/handoff/dispatcher.js +5 -2
  80. package/dist/core/handoff/runtime.js +157 -22
  81. package/dist/core/handoff/store.js +29 -1
  82. package/dist/core/handoff/types.js +1 -0
  83. package/dist/core/interaction-router.js +10 -0
  84. package/dist/core/message/im-renderer.js +19 -0
  85. package/dist/core/message/logical-queue-bridge.js +1 -0
  86. package/dist/core/message/mention-schema.js +126 -0
  87. package/dist/core/message/message-bridge.js +298 -88
  88. package/dist/core/message/message-log.js +3 -0
  89. package/dist/core/message/message-queue.js +268 -39
  90. package/dist/core/message/message-utils.js +81 -4
  91. package/dist/core/message/response-engine.js +161 -77
  92. package/dist/core/message/stream-debouncer.js +10 -2
  93. package/dist/core/permission/approval-gateway.js +1 -0
  94. package/dist/core/permission/ec-command-parser.js +243 -18
  95. package/dist/core/permission/index.js +1 -1
  96. package/dist/core/permission/mode.js +15 -0
  97. package/dist/core/permission/readonly-shell-query.js +148 -8
  98. package/dist/core/permission/sandbox-runtime.js +43 -1
  99. package/dist/core/permission/tool-policy.js +322 -57
  100. package/dist/core/protected-paths.js +114 -4
  101. package/dist/core/session/session-manager.js +42 -4
  102. package/dist/core/session/session-mapper.js +20 -1
  103. package/dist/eck/detect.js +18 -1
  104. package/dist/eck/group-rules-sync.js +15 -44
  105. package/dist/eck/manifest-engine.js +39 -13
  106. package/dist/index.js +541 -180
  107. package/dist/ipc.js +232 -10
  108. package/dist/paths.js +3 -0
  109. package/dist/response-system/coordinator.js +6 -4
  110. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  111. package/dist/response-system/modes/single-session/index.js +14 -1
  112. package/dist/response-system/selector.js +6 -5
  113. package/dist/trigger/anomaly-store.js +4 -0
  114. package/dist/trigger/feedback.js +64 -11
  115. package/dist/trigger/manager.js +21 -1
  116. package/dist/trigger/parser.js +14 -2
  117. package/dist/trigger/scheduler.js +1 -0
  118. package/dist/trigger/script-executor.js +1 -0
  119. package/dist/trigger/validation.js +67 -6
  120. package/dist/utils/codex-app-server-registry.js +90 -0
  121. package/dist/utils/codex-cli.js +5 -1
  122. package/dist/utils/cross-platform.js +15 -0
  123. package/dist/utils/ecweb-supervisor.js +332 -0
  124. package/dist/utils/error-utils.js +10 -0
  125. package/dist/utils/npm-ops.js +5 -12
  126. package/dist/utils/tool-summary.js +11 -0
  127. package/kits/docs/channels/aun.md +1 -1
  128. package/kits/docs/context-assembly.md +2 -2
  129. package/kits/docs/evolcore/agent.md +11 -6
  130. package/kits/docs/evolcore/aid.md +14 -0
  131. package/kits/docs/evolcore/trigger.md +17 -1
  132. package/kits/docs/prompt-loading-architecture.md +1 -2
  133. package/kits/docs/venues/group.md +1 -1
  134. package/kits/eck_manifest.json +0 -12
  135. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  136. package/kits/rules/04-relation.md +1 -1
  137. package/kits/rules/05-venue.md +2 -2
  138. package/kits/schemas/role-config.schema.1.json +1 -0
  139. package/kits/schemas/single-session.schema.1.json +3 -3
  140. package/kits/schemas/single-session.schema.2.json +3 -3
  141. package/kits/templates/roles/admin.json +27 -0
  142. package/kits/templates/roles/member.json +34 -1
  143. package/kits/templates/roles/visitor.json +35 -3
  144. package/kits/templates/system-fragments/bootstrap.md +1 -1
  145. package/kits/templates/system-fragments/channel.md +1 -1
  146. package/kits/templates/system-fragments/session.md +1 -1
  147. package/package.json +2 -2
  148. package/dist/aun/msg/payload-type.js +0 -27
  149. package/dist/aun/rpc/caller.js +0 -42
  150. package/dist/aun/rpc/connection.js +0 -25
  151. package/dist/aun/storage/manage.js +0 -10
  152. package/dist/config/access-policy-domain.js +0 -18
  153. package/dist/config/config-batch-get.js +0 -11
  154. package/dist/config/owner-policy.js +0 -4
  155. package/dist/config/role-config-v4-startup.js +0 -32
  156. package/dist/config/role-config-v5-startup.js +0 -27
  157. package/dist/core/auth/trigger-authorization.js +0 -15
  158. package/dist/core/command/evol-menu-version-gate.js +0 -39
  159. package/dist/core/interaction-registration.js +0 -10
  160. package/dist/core/permission/execution-sandbox.js +0 -16
  161. package/dist/core/relation/peer-key.js +0 -1
  162. package/dist/core/session/session-key.js +0 -24
  163. package/dist/eck/baseagent-caps.js +0 -18
  164. package/dist/eck/rules-loader.js +0 -28
@@ -14,21 +14,21 @@ 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, tryUpgradeAunSdk, tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
17
+ import { tryUpgrade, tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
18
18
  import { ecwebVersionRequirementError, fetchEcwebPairCode, resolveEcwebLaunchCommand } from '../utils/ecweb-utils.js';
19
19
  import { isHostChinese } from '../utils/locale.js';
20
- import { resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG } from '../aun/aid/client.js';
21
20
  import { scanInstances, cleanupInstances, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
22
21
  import { filterLogFiles, deriveLogTypes, computePreChecked, validateLogTypes, shortLogName as shortLogNameLocal } from './watch-logs.js';
23
22
  import { displaySessionTitle } from '../core/session/session-title.js';
24
23
  import { isNonAgentSessionChannel } from '../core/system-channels.js';
25
24
  import { printCodeStats } from './code-stats.js';
26
- import { getProcessStartTime } from '../utils/process-introspect.js';
25
+ import { getProcessStartTime, startTimeMatches } from '../utils/process-introspect.js';
27
26
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
28
27
  import { WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
29
28
  import { rotateStdoutLog } from '../utils/log-writer.js';
30
29
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
31
30
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
31
+ import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../utils/codex-app-server-registry.js';
32
32
  const execFileAsync = promisify(execFile);
33
33
  async function runAutomaticInit(action) {
34
34
  try {
@@ -389,9 +389,6 @@ export async function cmdStart(opts = {}) {
389
389
  cleanEnv();
390
390
  // 在启动前确保 serviceProxy 配置完整(如果 ecweb 启用但 serviceProxy 未配置)
391
391
  ensureServiceProxyConfigBeforeStart(p);
392
- const ecwebStartedBeforeDaemon = serviceProxyNeedsEcweb(loadDaemonConfig())
393
- ? await startEcwebIfEnabled(p)
394
- : false;
395
392
  // 删除旧的 ready signal
396
393
  try {
397
394
  fs.unlinkSync(p.readySignal);
@@ -421,23 +418,11 @@ export async function cmdStart(opts = {}) {
421
418
  console.log(` EVOLCORE_HOME: ${resolveRoot()}`);
422
419
  console.log(` Logs: ${p.logs}/`);
423
420
  console.log(`⏱ ready in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
424
- if (!ecwebStartedBeforeDaemon) {
425
- startEcwebIfEnabled(p)
426
- .then((ecwebStarted) => {
427
- if (shouldPrintNoSelfAgentHints) {
428
- printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted, skipped });
429
- }
430
- })
431
- .catch((err) => {
432
- console.error(`⚠ ECWeb 启动检查失败: ${err instanceof Error ? err.message : String(err)}`);
433
- if (shouldPrintNoSelfAgentHints) {
434
- printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: false, skipped });
435
- }
436
- });
437
- }
438
- else if (shouldPrintNoSelfAgentHints) {
439
- printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: true, skipped });
440
- }
421
+ void waitForExistingEcweb(p).then((ecweb) => {
422
+ if (shouldPrintNoSelfAgentHints) {
423
+ printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted: ecweb !== null, skipped });
424
+ }
425
+ });
441
426
  }, 500);
442
427
  let forwardingShutdown = false;
443
428
  const forwardSignal = (signal) => {
@@ -536,8 +521,9 @@ export async function cmdStart(opts = {}) {
536
521
  printCodeStats(getPackageRoot(), p.logs);
537
522
  }
538
523
  console.log(`⏱ done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s`);
539
- // ECWeb 自动后台启动
540
- const ecwebStarted = ecwebStartedBeforeDaemon || await startEcwebIfEnabled(p);
524
+ // ECWeb lifecycle belongs to the daemon; the CLI only reports whether
525
+ // the supervised instance became healthy.
526
+ const ecwebStarted = await waitForExistingEcweb(p) !== null;
541
527
  if (shouldPrintNoSelfAgentHints) {
542
528
  printNoSelfAgentHints({ daemonConfig: loadDaemonConfig(), ecwebStarted, skipped });
543
529
  }
@@ -668,19 +654,7 @@ export async function cmdRestart(opts = {}) {
668
654
  console.log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}), continuing with current version`);
669
655
  break;
670
656
  }
671
- // AUN SDK 版本检查与升级
672
- const aunUpgrade = await tryUpgradeAunSdk(resolveAunCoreSdkPkg, AUN_CORE_SDK_PKG);
673
- switch (aunUpgrade.status) {
674
- case 'upgraded':
675
- console.log(`✅ AUN SDK upgraded: ${aunUpgrade.from} → ${aunUpgrade.to}`);
676
- break;
677
- case 'no-update':
678
- break; // silent
679
- case 'failed':
680
- console.log(`⚠ AUN SDK upgrade failed (${aunUpgrade.from} → ${aunUpgrade.to})`);
681
- break;
682
- }
683
- // EC Web 版本检查与升级(已安装才检查,与 AUN SDK 同级)
657
+ // EC Web 是独立全局包,已安装时单独检查升级。
684
658
  const ecwebUpgrade = await tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME);
685
659
  switch (ecwebUpgrade.status) {
686
660
  case 'upgraded':
@@ -753,22 +727,20 @@ export async function cmdRestart(opts = {}) {
753
727
  reportOrphans(otherHome);
754
728
  }
755
729
  }
756
- // 清理 codex app-server 孤儿进程(无 HOME 区分,全局清理)
730
+ // 只清理 EvolCore 自己登记的 codex app-server,不能影响桌面端实例。
757
731
  {
758
732
  const killed = stopCodexAppServerOrphans();
759
733
  if (killed > 0) {
760
- console.log(`☠ 已清理 ${killed} 个 codex app-server 孤儿进程`);
734
+ console.log(`☠ 已清理 ${killed} 个 EvolCore codex app-server 孤儿进程`);
761
735
  await sleep(500);
762
736
  }
763
737
  }
764
738
  console.log(`⏱ restart prep done in ${((Date.now() - cmdStartedAt) / 1000).toFixed(1)}s, starting...`);
765
739
  // 通过 restart-monitor 启动,避免在 agent 内调用时自杀
766
740
  const appCli = path.join(getPackageRoot(), 'dist', 'cli', 'index.js');
767
- spawn('node', [appCli, 'restart-monitor'], {
768
- detached: true,
769
- stdio: 'ignore',
770
- env: { ...process.env, EVOLCORE_HOME: resolvePaths().root }
771
- }).unref();
741
+ platform.spawnDetachedNode([appCli, 'restart-monitor'], {
742
+ EVOLCORE_HOME: resolvePaths().root,
743
+ });
772
744
  console.log('🔄 Restart monitor started, waiting for service to come back online...');
773
745
  await sleep(2000);
774
746
  // 代码统计(开发环境)
@@ -2436,6 +2408,24 @@ async function probeEcwebReady(port) {
2436
2408
  return false;
2437
2409
  }
2438
2410
  }
2411
+ /**
2412
+ * Wait briefly for an already-launched ECWeb to become healthy. `ec start`,
2413
+ * restart-monitor, and the daemon can overlap by a few milliseconds; treating
2414
+ * a healthy instance as an adoption target avoids killing/replacing it.
2415
+ */
2416
+ async function waitForExistingEcweb(p, timeoutMs = 3_000) {
2417
+ const deadline = Date.now() + timeoutMs;
2418
+ do {
2419
+ for (const entry of findAliveEcwebs(p)) {
2420
+ if (await probeEcwebReady(entry.port))
2421
+ return entry;
2422
+ }
2423
+ if (Date.now() >= deadline)
2424
+ break;
2425
+ await sleep(250);
2426
+ } while (true);
2427
+ return null;
2428
+ }
2439
2429
  /**
2440
2430
  * 输出 ECWeb 状态。判定口径:
2441
2431
  * 1. 进程是否存活(instance/ 下的 pid 文件 + isProcessRunning)
@@ -2513,15 +2503,29 @@ function removeEcwebInstanceFiles(p) {
2513
2503
  catch { }
2514
2504
  }
2515
2505
  }
2516
- /** 清理所有 codex app-server 孤儿进程,返回清理的进程数。 */
2517
- function stopCodexAppServerOrphans() {
2518
- // 查找所有 codex app-server 进程(无论是 node 启动的还是原生二进制)
2519
- const codexProcs = platform.findProcesses('codex app-server');
2506
+ /** 清理 EvolCore 登记的 codex app-server 孤儿进程,返回清理的进程数。 */
2507
+ export function stopCodexAppServerOrphans() {
2508
+ const codexProcs = new Set(platform.findProcesses('codex app-server'));
2520
2509
  let killed = 0;
2521
- for (const pid of codexProcs) {
2510
+ for (const entry of listCodexAppServerProcesses()) {
2511
+ const { pid, startedAt } = entry.record;
2512
+ if (!platform.isProcessRunning(pid)) {
2513
+ unregisterCodexAppServerProcess(entry.filePath);
2514
+ continue;
2515
+ }
2516
+ // PID 复用时丢弃登记,不发送信号;命令行探测失败则保留登记,等待下次重试。
2517
+ const actualStartedAt = getProcessStartTime(pid);
2518
+ if (!startTimeMatches(startedAt, actualStartedAt)) {
2519
+ unregisterCodexAppServerProcess(entry.filePath);
2520
+ continue;
2521
+ }
2522
+ if (!codexProcs.has(pid))
2523
+ continue;
2522
2524
  try {
2523
- platform.killProcess(pid, true);
2524
- killed++;
2525
+ if (platform.killProcess(pid, true)) {
2526
+ killed++;
2527
+ unregisterCodexAppServerProcess(entry.filePath);
2528
+ }
2525
2529
  }
2526
2530
  catch { }
2527
2531
  }
@@ -2633,10 +2637,21 @@ function ensureServiceProxyConfig(cfg, port) {
2633
2637
  * Kept public for restart-monitor, which launches the daemon directly and
2634
2638
  * therefore cannot rely on cmdStart's post-ready ECWeb startup path.
2635
2639
  */
2636
- export async function startEcwebIfEnabled(p) {
2640
+ export async function startEcwebIfEnabled(p, options = {}) {
2637
2641
  const cfg = loadDaemonConfig();
2638
2642
  if (!cfg.ecweb?.enabled)
2639
2643
  return false;
2644
+ // The daemon is the lifecycle owner. CLI callers normally only need to
2645
+ // ensure ECWeb is available, so adopt a healthy process instead of killing
2646
+ // it. `ec watch web` passes forceRestart explicitly for an intentional
2647
+ // manual replacement.
2648
+ if (!options.forceRestart) {
2649
+ const existing = await waitForExistingEcweb(p);
2650
+ if (existing) {
2651
+ console.log(`✓ ECWeb 已在运行 (PID: ${existing.pid}) http://localhost:${existing.port}`);
2652
+ return true;
2653
+ }
2654
+ }
2640
2655
  const installedPkg = resolveGlobalPkg(WEB_PACKAGE_NAME);
2641
2656
  if (!installedPkg) {
2642
2657
  console.log(`⚠ EC Web 未安装,跳过启动(运行 npm i -g ${WEB_PACKAGE_NAME}@latest 后可用)`);
@@ -2777,7 +2792,7 @@ async function cmdWatchWeb() {
2777
2792
  }
2778
2793
  }
2779
2794
  else {
2780
- // 已安装:检查并自动升级到最新版(参考 fastaun 自动升级机制)
2795
+ // 已安装:检查并自动升级独立的 EC Web 全局包。
2781
2796
  const upgrade = await tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME);
2782
2797
  switch (upgrade.status) {
2783
2798
  case 'upgraded':
@@ -2795,7 +2810,7 @@ async function cmdWatchWeb() {
2795
2810
  saveDaemonConfig({ ...cfg, ecweb: { ...(cfg.ecweb ?? {}), enabled: true, port } });
2796
2811
  // 自动配置 serviceProxy(如果控制 AID 已配置)
2797
2812
  ensureServiceProxyConfigBeforeStart(p);
2798
- const ok = await startEcwebIfEnabled(p);
2813
+ const ok = await startEcwebIfEnabled(p, { forceRestart: true });
2799
2814
  if (!ok)
2800
2815
  process.exit(1); // 失败原因已由 startEcwebIfEnabled 打印
2801
2816
  // 启动成功的访问信息已由 startEcwebIfEnabled 打印,无需 printEcwebAccess 重复输出
@@ -3,6 +3,10 @@ import path from 'path';
3
3
  import { TextDecoder } from 'util';
4
4
  import { getArgValue, isHelpFlag, wantsHelp } from './help.js';
5
5
  import { agentmdGet, getAidStore, isValidAid, loadClient, SLOT } from '../aun/aid/index.js';
6
+ import { ipcQuery } from '../ipc.js';
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';
6
10
  class FsCliError extends Error {
7
11
  code;
8
12
  suggestion;
@@ -130,6 +134,10 @@ export async function cmdFs(args) {
130
134
  console.log(HELP);
131
135
  return;
132
136
  }
137
+ if (process.env.EVOLCORE_SESSION_ID) {
138
+ await runManagedFs(args, opts.formatJson);
139
+ return;
140
+ }
133
141
  try {
134
142
  switch (sub) {
135
143
  case 'ls':
@@ -199,6 +207,74 @@ export async function cmdFs(args) {
199
207
  process.exit(1);
200
208
  }
201
209
  }
210
+ async function runManagedFs(args, formatJson) {
211
+ const sessionId = process.env.EVOLCORE_SESSION_ID;
212
+ const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
213
+ const fail = (code, message) => {
214
+ if (formatJson)
215
+ console.log(JSON.stringify({ ok: false, code, error: message }));
216
+ else
217
+ console.error(`❌ ${message}`);
218
+ process.exit(1);
219
+ };
220
+ if (!sessionId)
221
+ fail('NO_SESSION', 'EVOLCORE_SESSION_ID is not set');
222
+ if (!delegationToken)
223
+ fail('DELEGATION_REQUIRED', `${AGENT_DELEGATION_TOKEN_ENV} is not set for the active task`);
224
+ const response = await ipcQuery(resolvePaths().socket, {
225
+ type: 'fs.op',
226
+ argv: ['fs', ...args],
227
+ sessionId,
228
+ delegationToken,
229
+ }, 30_000);
230
+ if (!response)
231
+ return fail('DAEMON_UNAVAILABLE', 'Unable to connect to the evolcore daemon');
232
+ if (!response.ok || !response.result)
233
+ fail(String(response.code || 'FS_OPERATION_FAILED'), response.error || 'Filesystem operation failed');
234
+ const result = response.result;
235
+ if (formatJson) {
236
+ console.log(JSON.stringify(result));
237
+ return;
238
+ }
239
+ switch (result.command) {
240
+ case 'cat':
241
+ if (result.binary)
242
+ console.log(JSON.stringify({ path: result.path, size: result.size, binary: true, head: result.head }, null, 2));
243
+ else
244
+ process.stdout.write(String(result.text ?? '').endsWith('\n') ? result.text : `${result.text ?? ''}\n`);
245
+ return;
246
+ case 'df': {
247
+ const usage = result.usage ?? {};
248
+ console.log(`Filesystem: ${result.path}`);
249
+ console.log(JSON.stringify(usage, null, 2));
250
+ return;
251
+ }
252
+ case 'stat':
253
+ case 'lstat':
254
+ console.log(JSON.stringify(result.node ?? {}, null, 2));
255
+ return;
256
+ case 'ls':
257
+ case 'find':
258
+ for (const item of result.items ?? [])
259
+ console.log(JSON.stringify(item));
260
+ return;
261
+ case 'mkdir':
262
+ console.log(`✓ 已创建目录: ${result.path}`);
263
+ return;
264
+ case 'upload':
265
+ console.log(`✓ 已上传: ${result.localPath} -> ${result.path}`);
266
+ return;
267
+ case 'copy':
268
+ console.log(`✓ 已复制: ${result.src} -> ${result.path}`);
269
+ return;
270
+ case 'move':
271
+ console.log(`✓ 已移动: ${result.src} -> ${result.path}`);
272
+ return;
273
+ case 'remove':
274
+ console.log(`✓ 已删除: ${result.path}`);
275
+ return;
276
+ }
277
+ }
202
278
  function parseCommonOptions(args) {
203
279
  const maxCatBytesRaw = getArgValue(args, '--max-bytes');
204
280
  const maxCatBytes = maxCatBytesRaw === undefined ? 1024 * 1024 : Number(maxCatBytesRaw);
@@ -325,8 +401,9 @@ async function fsCat(args, opts) {
325
401
  if (route.backend === 'group') {
326
402
  statInfo = await client.group.fs.stat(remoteRef(target));
327
403
  const size = numberFrom(statInfo, ['size', 'size_bytes', 'bytes']);
328
- const downloaded = await client.group.fs.cp(remoteRef(target), { kind: 'blob' }, { verifyHash: true });
329
- bytes = bytesFromDownload(downloaded);
404
+ const downloaded = await downloadGroupFsBytes(client, remoteRef(target));
405
+ bytes = downloaded.bytes;
406
+ statInfo = { ...statInfo, ...downloaded.ticket };
330
407
  statInfo = { ...statInfo, size: bytes.byteLength };
331
408
  if (size !== undefined && size > opts.maxCatBytes) {
332
409
  binaryHeadOnly = true;
@@ -1360,14 +1437,6 @@ function contentTypeFrom(value) {
1360
1437
  const raw = value?.contentType ?? value?.content_type ?? value?.mime ?? value?.mime_type;
1361
1438
  return raw ? String(raw).toLowerCase() : undefined;
1362
1439
  }
1363
- function bytesFromDownload(value) {
1364
- const data = value?.data;
1365
- if (data instanceof Uint8Array)
1366
- return data;
1367
- if (data instanceof ArrayBuffer)
1368
- return new Uint8Array(data);
1369
- throw new FsCliError('BACKEND_ERROR', '下载接口未返回字节内容', undefined, value);
1370
- }
1371
1440
  function looksBinary(bytes, contentType) {
1372
1441
  if (contentType) {
1373
1442
  if (contentType.startsWith('text/'))
@@ -203,6 +203,7 @@ Commands:
203
203
  }
204
204
  const result = await ipcQuery(resolvePaths().socket, {
205
205
  type: 'handoff-status', sessionId: process.env.EVOLCORE_SESSION_ID, handoffId,
206
+ delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
206
207
  }, 5000);
207
208
  if (!result) {
208
209
  console.error('✗ daemon 暂时不可用');
@@ -234,6 +235,7 @@ Commands:
234
235
  state: parsed.state,
235
236
  filterSessionId: parsed.sessionId,
236
237
  limit: parsed.limit,
238
+ delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
237
239
  }, 5000);
238
240
  if (!result) {
239
241
  console.error('✗ daemon 暂时不可用');
@@ -276,6 +278,7 @@ Commands:
276
278
  agent: parsed.agent,
277
279
  handoffId: parsed.handoffId,
278
280
  limit: parsed.limit,
281
+ delegationToken: process.env[AGENT_DELEGATION_TOKEN_ENV],
279
282
  }, 5000);
280
283
  if (!result) {
281
284
  console.error('✗ daemon 暂时不可用');
package/dist/cli/index.js CHANGED
@@ -3,7 +3,9 @@
3
3
  process.env.AUN_LOG_INI_DISABLE = '1';
4
4
  import fs from 'fs';
5
5
  import path from 'path';
6
- import { getPackageRoot } from '../paths.js';
6
+ import { getPackageRoot, resolvePaths } from '../paths.js';
7
+ import { ipcQuery } from '../ipc.js';
8
+ import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
7
9
  import { cmdInit } from './init.js';
8
10
  import { cmdInitWechat, cmdInitFeishu, cmdInitDingtalk, cmdInitQQBot, cmdInitWecom, cmdInitAun } from './init-channel.js';
9
11
  import { isHelpFlag, wantsHelp, getArgValue } from './help.js';
@@ -16,6 +18,7 @@ import { cmdQueue } from './queue-command.js';
16
18
  import { cmdStart, cmdStop, cmdRestart, cmdStatus, cmdLogs, cmdWatchCommand, cmdMv, cmdDiagnose } from './daemon-commands.js';
17
19
  import { logCliCommand } from './command-log.js';
18
20
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
21
+ import { assertManagedTopLevelCommandAllowed, ManagedCommandNotAllowedError } from './managed-command-guard.js';
19
22
  const MAIN_HELP = `用法: ec <command> [选项]
20
23
 
21
24
  服务:
@@ -87,6 +90,16 @@ process.on('warning', (w) => { if (w.name === 'ExperimentalWarning')
87
90
  return; process.stderr.write((w.stack ?? String(w)) + '\n'); });
88
91
  export async function main(args) {
89
92
  const cmd = args[0] || 'start';
93
+ try {
94
+ assertManagedTopLevelCommandAllowed(cmd, args);
95
+ }
96
+ catch (error) {
97
+ if (!(error instanceof ManagedCommandNotAllowedError))
98
+ throw error;
99
+ console.error(`❌ [${error.code}] ${error.message}`);
100
+ process.exitCode = 1;
101
+ return;
102
+ }
90
103
  logCliCommand(cmd, args);
91
104
  if (cmd === '--version' || cmd === '-v' || cmd === '-V') {
92
105
  const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf-8'));
@@ -229,7 +242,22 @@ export async function main(args) {
229
242
  printLifecycleHelp('status');
230
243
  break;
231
244
  }
232
- await cmdStatus();
245
+ if (process.env.EVOLCORE_SESSION_ID) {
246
+ const sessionId = process.env.EVOLCORE_SESSION_ID;
247
+ const delegationToken = process.env[AGENT_DELEGATION_TOKEN_ENV];
248
+ const result = await ipcQuery(resolvePaths().socket, {
249
+ type: 'status.auth', sessionId, delegationToken,
250
+ });
251
+ if (!result?.ok) {
252
+ console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
253
+ process.exitCode = 1;
254
+ break;
255
+ }
256
+ await cmdStatus();
257
+ }
258
+ else {
259
+ await cmdStatus();
260
+ }
233
261
  break;
234
262
  case 'logs':
235
263
  if (wantsHelp(args.slice(1))) {
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 { generateControlAid, resolveControlAidDomain } from '../aun/aid/control-aid.js';
10
- import { getCodexAppServerAvailability, isCodexAppServerAvailable } from '../agents/codex-runner.js';
10
+ import { getCodexAppServerAvailability } from '../agents/codex-runner.js';
11
11
  import { resolveEcagentConfig } from '../agents/baseagent.js';
12
12
  import { defaultProjectsRoot } from '../utils/project-path.js';
13
13
  import { WEB_CLI_BIN, WEB_PACKAGE_LATEST } from '../product.js';
@@ -28,15 +28,44 @@ export function expandLeadingTildePath(value, homeDir = os.homedir()) {
28
28
  }
29
29
  const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
30
30
  const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
31
- function isBaseagentAvailable(baseagent) {
31
+ function isBaseagentAvailable(baseagent, codexAvailability) {
32
32
  if (baseagent === 'codex')
33
- return isCodexAppServerAvailable();
33
+ return (codexAvailability ?? getCodexAppServerAvailability()).available;
34
34
  if (baseagent === 'ecagent')
35
35
  return true;
36
36
  return commandExists(baseagent);
37
37
  }
38
- function detectAvailable() {
39
- return BASEAGENT_CANDIDATES.filter(isBaseagentAvailable);
38
+ function detectAvailable(codexAvailability = getCodexAppServerAvailability()) {
39
+ return BASEAGENT_CANDIDATES.filter(baseagent => isBaseagentAvailable(baseagent, codexAvailability));
40
+ }
41
+ /** Offer installation only when the probe positively identified an old CLI. */
42
+ export async function maybeUpgradeCodexForInit(availability, dependencies) {
43
+ if (!availability.requiresUpgrade) {
44
+ return { availability, offered: false, accepted: false };
45
+ }
46
+ const accepted = await dependencies.confirm();
47
+ if (!accepted)
48
+ return { availability, offered: true, accepted: false };
49
+ try {
50
+ const install = dependencies.install ?? (async () => {
51
+ const { npmInstallGlobal } = await import('../utils/npm-ops.js');
52
+ await npmInstallGlobal('@openai/codex@latest');
53
+ });
54
+ await install();
55
+ }
56
+ catch (error) {
57
+ return {
58
+ availability,
59
+ offered: true,
60
+ accepted: true,
61
+ error: installErrorMessage(error),
62
+ };
63
+ }
64
+ return {
65
+ availability: (dependencies.probe ?? getCodexAppServerAvailability)(),
66
+ offered: true,
67
+ accepted: true,
68
+ };
40
69
  }
41
70
  function pickDefault(available) {
42
71
  return AUTO_BASEAGENT_CANDIDATES.find(candidate => available.includes(candidate));
@@ -192,7 +221,45 @@ export async function cmdInit(options) {
192
221
  return false;
193
222
  }
194
223
  // ── 2. 探测 baseagent ──
195
- const available = detectAvailable();
224
+ let codexAvailability = getCodexAppServerAvailability();
225
+ if (!options?.nonInteractive && codexAvailability.version) {
226
+ console.log(`检测到 Codex CLI: ${codexAvailability.version}`);
227
+ }
228
+ if (!options?.nonInteractive && codexAvailability.requiresUpgrade) {
229
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
230
+ let result;
231
+ try {
232
+ result = await maybeUpgradeCodexForInit(codexAvailability, {
233
+ confirm: async () => {
234
+ console.log(`⚠ ${codexAvailability.reason}`);
235
+ const answer = (await ask(rl, '是否立即升级 Codex CLI?[Y/n] ')).trim().toLowerCase();
236
+ return answer === '' || answer === 'y' || answer === 'yes';
237
+ },
238
+ });
239
+ }
240
+ finally {
241
+ try {
242
+ rl.close();
243
+ }
244
+ catch { /* ignore */ }
245
+ }
246
+ if (!result.accepted) {
247
+ console.log(' 已跳过 Codex CLI 升级');
248
+ }
249
+ else if (result.error) {
250
+ console.log(` ⚠ Codex CLI 升级失败: ${result.error}`);
251
+ }
252
+ else {
253
+ codexAvailability = result.availability;
254
+ if (codexAvailability.available) {
255
+ console.log(` ✓ Codex CLI 已升级至 ${codexAvailability.version}`);
256
+ }
257
+ else {
258
+ console.log(` ⚠ Codex CLI 升级后仍不可用: ${codexAvailability.reason ?? '未知原因'}`);
259
+ }
260
+ }
261
+ }
262
+ const available = detectAvailable(codexAvailability);
196
263
  if (available.length === 0 || (available.length === 1 && available[0] === 'ecagent' && ecagentUnavailableReason())) {
197
264
  console.log('❌ 未检测到可用 baseagent。请安装至少一款:');
198
265
  console.log(' - claude CLI');
@@ -202,8 +269,10 @@ export async function cmdInit(options) {
202
269
  return false;
203
270
  }
204
271
  console.log('检测到可用的 baseagent:');
205
- for (const b of available)
206
- console.log(` ● ${b}`);
272
+ for (const b of available) {
273
+ const version = b === 'codex' ? codexAvailability.version : undefined;
274
+ console.log(` ● ${b}${version ? ` (CLI ${version})` : ''}`);
275
+ }
207
276
  console.log('');
208
277
  const defaultsPath = p.defaultsConfig;
209
278
  const defaultsExisted = fs.existsSync(defaultsPath);
@@ -0,0 +1,41 @@
1
+ import { wantsHelp } from './help.js';
2
+ const MANAGED_TASK_COMMANDS = new Set([
3
+ 'aid',
4
+ 'agent',
5
+ 'config',
6
+ 'contact',
7
+ 'ctl',
8
+ 'fs',
9
+ 'group',
10
+ 'handoff',
11
+ 'model',
12
+ 'msg',
13
+ 'queue',
14
+ 'response',
15
+ 'status',
16
+ 'storage',
17
+ 'trigger',
18
+ 'version',
19
+ '-v',
20
+ '-V',
21
+ '--version',
22
+ ]);
23
+ export class ManagedCommandNotAllowedError extends Error {
24
+ command;
25
+ code = 'NOT_ALLOWED';
26
+ constructor(command) {
27
+ super(`Command ${command} is not available in a managed Agent task`);
28
+ this.command = command;
29
+ this.name = 'ManagedCommandNotAllowedError';
30
+ }
31
+ }
32
+ /**
33
+ * Local maintenance commands do not have daemon authorization entrypoints.
34
+ * Keep them outside managed tasks so they cannot read or mutate host state.
35
+ */
36
+ export function assertManagedTopLevelCommandAllowed(command, args, env = process.env) {
37
+ if (!env.EVOLCORE_SESSION_ID || wantsHelp(args.slice(1)) || command === '--help' || command === '-h')
38
+ return;
39
+ if (!MANAGED_TASK_COMMANDS.has(command))
40
+ throw new ManagedCommandNotAllowedError(command);
41
+ }
package/dist/cli/model.js CHANGED
@@ -46,10 +46,8 @@ function fail(formatJson, code, message, data) {
46
46
  process.exit(1);
47
47
  }
48
48
  function isManagedSessionEnv(args) {
49
- return !!process.env.EVOLCORE_SESSION_ID
50
- && !getArgValue(args, '--self')
51
- && !getArgValue(args, '--peer')
52
- && !getArgValue(args, '--role');
49
+ void args;
50
+ return !!process.env.EVOLCORE_SESSION_ID;
53
51
  }
54
52
  function isManagedTargetedWriteEnv(args) {
55
53
  return !!process.env.EVOLCORE_SESSION_ID
@@ -368,6 +366,11 @@ async function cmdInfo(args, formatJson) {
368
366
  const modelId = args[1] && !args[1].startsWith('--') ? args[1] : undefined;
369
367
  if (!modelId)
370
368
  fail(formatJson, 'MISSING_MODEL_ID', 'info 需要 <model-id>');
369
+ if (isManagedSessionEnv(args)) {
370
+ const out = await runCtlCommand(`/model info ${modelId}`, formatJson);
371
+ printCtlResult(out, formatJson);
372
+ return;
373
+ }
371
374
  const sel = parseSelector(args, formatJson);
372
375
  const self = getArgValue(args, '--self');
373
376
  const info = await getModelInfo(modelId, self);
@@ -587,5 +590,9 @@ export async function cmdModel(args) {
587
590
  console.log(HELP);
588
591
  return;
589
592
  }
593
+ if (process.env.EVOLCORE_SESSION_ID
594
+ && (getArgValue(args, '--self') || getArgValue(args, '--peer') || getArgValue(args, '--role'))) {
595
+ fail(formatJson, 'NOT_ALLOWED', 'Managed model commands cannot select --self, --peer, or --role; they always use the authenticated current relation');
596
+ }
590
597
  await dispatch(sub, args, formatJson);
591
598
  }