evolcore 0.0.2 → 0.0.3

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 (124) hide show
  1. package/CHANGELOG.md +44 -793
  2. package/dist/agents/claude-runner.js +197 -17
  3. package/dist/agents/codex-runner.js +46 -3
  4. package/dist/aun/outbox.js +8 -0
  5. package/dist/channels/aun.js +21 -4
  6. package/dist/channels/contact-bind-code.js +134 -0
  7. package/dist/channels/dingtalk.js +979 -149
  8. package/dist/channels/feishu.js +130 -54
  9. package/dist/channels/wecom-card.js +101 -0
  10. package/dist/channels/wecom-onboarding.js +82 -0
  11. package/dist/channels/wecom-state.js +191 -0
  12. package/dist/channels/wecom.js +755 -163
  13. package/dist/cli/agent-command.js +2 -1
  14. package/dist/cli/aun-commands.js +88 -33
  15. package/dist/cli/bench.js +2 -2
  16. package/dist/cli/contact.js +71 -0
  17. package/dist/cli/ctl-command.js +2 -2
  18. package/dist/cli/daemon-commands.js +77 -214
  19. package/dist/cli/handoff-command.js +2 -2
  20. package/dist/cli/help.js +9 -5
  21. package/dist/cli/index.js +132 -116
  22. package/dist/cli/init-channel.js +92 -97
  23. package/dist/cli/init.js +63 -26
  24. package/dist/cli/model.js +2 -1
  25. package/dist/cli/net-check.js +2 -2
  26. package/dist/cli/queue-command.js +30 -6
  27. package/dist/cli/raw-key-input.js +25 -0
  28. package/dist/cli/response.js +5 -6
  29. package/dist/cli/restart-monitor.js +25 -1
  30. package/dist/cli/stats.js +6 -4
  31. package/dist/cli/trigger-command.js +55 -15
  32. package/dist/cli/version.js +6 -1
  33. package/dist/config/builtin-role-templates.js +22 -10
  34. package/dist/config/builtin-roles.js +7 -1
  35. package/dist/config/config-manager.js +221 -17
  36. package/dist/config/contact-alias.js +68 -0
  37. package/dist/config/contact-book-store.js +454 -0
  38. package/dist/config/contact-book-v2-startup.js +35 -0
  39. package/dist/config/contact-book.js +156 -303
  40. package/dist/config/contact-operation-service.js +110 -0
  41. package/dist/config/peer-role-resolver.js +133 -54
  42. package/dist/config/role-ranks.js +18 -0
  43. package/dist/config/role-service.js +16 -19
  44. package/dist/config/role-store.js +16 -5
  45. package/dist/config/roles.js +10 -1
  46. package/dist/config-store.js +0 -2
  47. package/dist/core/auth/authorization-audit.js +10 -1
  48. package/dist/core/auth/operation-authorizer.js +2 -0
  49. package/dist/core/auth/operation-catalog.js +56 -0
  50. package/dist/core/command/command-handler.js +105 -10
  51. package/dist/core/command/connect-menu.js +374 -0
  52. package/dist/core/command/menu-handler.js +114 -16
  53. package/dist/core/command/role-menu.js +128 -29
  54. package/dist/core/command/slash-handler.js +28 -18
  55. package/dist/core/daemon-file-cache.js +12 -6
  56. package/dist/core/event-catalog.js +70 -0
  57. package/dist/core/evolagent-registry.js +0 -1
  58. package/dist/core/evolagent.js +20 -9
  59. package/dist/core/message/im-renderer.js +2 -0
  60. package/dist/core/message/message-bridge.js +79 -8
  61. package/dist/core/message/message-queue.js +10 -0
  62. package/dist/core/message/message-utils.js +8 -2
  63. package/dist/core/message/response-engine.js +269 -25
  64. package/dist/core/message/send-receipt.js +24 -0
  65. package/dist/core/message/stream-debouncer.js +11 -2
  66. package/dist/core/permission/tool-policy.js +47 -15
  67. package/dist/core/protected-paths.js +2 -0
  68. package/dist/core/session/session-fs-store.js +44 -3
  69. package/dist/core/session/session-manager.js +61 -5
  70. package/dist/index.js +62 -6
  71. package/dist/ipc.js +34 -5
  72. package/dist/stats/billing.js +20 -8
  73. package/dist/trigger/manager.js +3 -0
  74. package/dist/trigger/parser.js +77 -2
  75. package/dist/trigger/patch.js +8 -1
  76. package/dist/trigger/scheduler.js +3 -1
  77. package/dist/trigger/validation.js +13 -0
  78. package/dist/utils/aid-bind.js +43 -29
  79. package/dist/utils/instance-registry.js +14 -7
  80. package/dist/utils/log-writer.js +46 -0
  81. package/dist/utils/media-cache.js +4 -1
  82. package/dist/utils/model-prices.jsonl +6 -3
  83. package/dist/utils/restart-safety.js +31 -0
  84. package/dist/utils/system-memory.js +62 -0
  85. package/kits/docs/INDEX.md +2 -1
  86. package/kits/docs/evolcore/INDEX.md +5 -3
  87. package/kits/docs/evolcore/agent.md +9 -1
  88. package/kits/docs/evolcore/aid.md +5 -2
  89. package/kits/docs/evolcore/contact.md +57 -0
  90. package/kits/docs/evolcore/fs.md +9 -0
  91. package/kits/docs/evolcore/group.md +12 -3
  92. package/kits/docs/evolcore/model.md +4 -1
  93. package/kits/docs/evolcore/msg.md +9 -3
  94. package/kits/docs/evolcore/response.md +16 -21
  95. package/kits/docs/evolcore/rpc.md +2 -0
  96. package/kits/docs/evolcore/stats.md +15 -2
  97. package/kits/docs/evolcore/storage.md +1 -0
  98. package/kits/docs/evolcore/trigger.md +17 -2
  99. package/kits/eck_manifest.json +12 -0
  100. package/kits/migrations/migrate-contact-book-v2.mjs +747 -0
  101. package/kits/schemas/_meta.json +7 -4
  102. package/kits/schemas/agent-config.schema.6.json +322 -0
  103. package/kits/schemas/contact-book.schema.2.json +43 -0
  104. package/kits/schemas/relation-config.schema.5.json +47 -0
  105. package/kits/schemas/role-config.schema.1.json +1 -0
  106. package/kits/schemas/role-registry.schema.1.json +2 -2
  107. package/kits/templates/roles/admin.json +1 -0
  108. package/kits/templates/roles/member.json +1 -0
  109. package/kits/templates/roles/owner.json +1 -0
  110. package/kits/templates/roles/visitor.json +1 -0
  111. package/kits/templates/system-fragments/commands.md +3 -1
  112. package/package.json +4 -4
  113. package/assets/brand/evolcore/README.md +0 -19
  114. package/assets/brand/evolcore/evolcore-app-icon.png +0 -0
  115. package/assets/brand/evolcore/evolcore-app-icon.svg +0 -13
  116. package/assets/brand/evolcore/evolcore-brand-board.png +0 -0
  117. package/assets/brand/evolcore/evolcore-brand-board.svg +0 -126
  118. package/assets/brand/evolcore/evolcore-logo-kit.zip +0 -0
  119. package/assets/brand/evolcore/evolcore-logo-reverse.png +0 -0
  120. package/assets/brand/evolcore/evolcore-logo-reverse.svg +0 -14
  121. package/assets/brand/evolcore/evolcore-logo.png +0 -0
  122. package/assets/brand/evolcore/evolcore-logo.svg +0 -14
  123. package/assets/brand/evolcore/evolcore-mark.png +0 -0
  124. package/assets/brand/evolcore/evolcore-mark.svg +0 -10
@@ -1,7 +1,7 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import os from 'os';
4
- import { spawn, execFileSync, execFile } from 'child_process';
4
+ import { spawn, execFile } from 'child_process';
5
5
  import { promisify } from 'util';
6
6
  import readline from 'readline';
7
7
  import { resolveRoot, resolvePaths, ensureDataDirs, getPackageRoot, agentMdPath } from '../paths.js';
@@ -27,6 +27,7 @@ import { printCodeStats } from './code-stats.js';
27
27
  import { getProcessStartTime } from '../utils/process-introspect.js';
28
28
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
29
29
  import { WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js';
30
+ import { rotateStdoutLog } from '../utils/log-writer.js';
30
31
  const execFileAsync = promisify(execFile);
31
32
  async function probeDaemon(socketPath, timeoutMs = 1000) {
32
33
  const response = await ipcQuery(socketPath, { type: 'ping' }, timeoutMs);
@@ -81,42 +82,6 @@ export function cleanEnv() {
81
82
  delete process.env[key];
82
83
  }
83
84
  }
84
- /**
85
- * 启动时归档过大的 stdout.log。其它 .log 文件由各自的 LogWriter 管理切片/清理,
86
- * 不再扫描整个目录——LogWriter 模式下重复的 size 检查会和 hourly rotation 冲突。
87
- */
88
- function rotateStdoutIfNeeded(logDir) {
89
- if (!fs.existsSync(logDir))
90
- return;
91
- const MAX_SIZE = 10 * 1024 * 1024; // 10MB
92
- const cutoff = Date.now() - 7 * 24 * 60 * 60 * 1000;
93
- const stdoutLog = path.join(logDir, 'stdout.log');
94
- // 归档当前 stdout.log(若超过 10MB)
95
- try {
96
- const stat = fs.statSync(stdoutLog);
97
- if (stat.size > MAX_SIZE) {
98
- const timestamp = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
99
- const newPath = `${stdoutLog}.${timestamp}`;
100
- fs.renameSync(stdoutLog, newPath);
101
- console.log(` Rotated: stdout.log -> ${path.basename(newPath)}`);
102
- }
103
- }
104
- catch { /* file not exist */ }
105
- // 清理 7 天前的 stdout.log.* 归档
106
- try {
107
- for (const file of fs.readdirSync(logDir)) {
108
- if (!file.startsWith('stdout.log.'))
109
- continue;
110
- const full = path.join(logDir, file);
111
- try {
112
- if (fs.statSync(full).mtimeMs < cutoff)
113
- fs.unlinkSync(full);
114
- }
115
- catch { }
116
- }
117
- }
118
- catch { }
119
- }
120
85
  function buildDaemonEnv(p, opts) {
121
86
  return {
122
87
  ...process.env,
@@ -318,7 +283,10 @@ export async function cmdStart(opts = {}) {
318
283
  // 跨 HOME 孤儿(未登记进程)只警告,不动
319
284
  reportOrphans(findOrphanProcesses());
320
285
  console.log('🚀 Starting EvolCore...');
321
- rotateStdoutIfNeeded(p.logs);
286
+ const stdoutRotation = rotateStdoutLog(p.logs);
287
+ if (stdoutRotation.rotatedPath) {
288
+ console.log(` Rotated: stdout.log -> ${path.basename(stdoutRotation.rotatedPath)}`);
289
+ }
322
290
  cleanEnv();
323
291
  // 在启动前确保 serviceProxy 配置完整(如果 ecweb 启用但 serviceProxy 未配置)
324
292
  ensureServiceProxyConfigBeforeStart(p);
@@ -604,9 +572,16 @@ export async function cmdRestart(opts = {}) {
604
572
  // 跨 HOME 的孤儿只在 --clear 时 kill,否则仅警告。
605
573
  {
606
574
  const orphans = findOrphanProcesses();
575
+ const confirmedTestDaemons = orphans.filter(o => o.confirmedTestDaemon);
576
+ const nonTestOrphans = orphans.filter(o => !o.confirmedTestDaemon);
577
+ if (confirmedTestDaemons.length > 0) {
578
+ const killed = killOrphans(confirmedTestDaemons);
579
+ console.log(`☠ 已清理 ${killed.length} 个泄漏的测试 daemon: ${killed.join(', ')}`);
580
+ await sleep(500);
581
+ }
607
582
  const currentHome = resolveRoot();
608
- const sameHome = orphans.filter(o => o.evolcoreHome === currentHome);
609
- const otherHome = orphans.filter(o => o.evolcoreHome !== currentHome);
583
+ const sameHome = nonTestOrphans.filter(o => o.evolcoreHome === currentHome);
584
+ const otherHome = nonTestOrphans.filter(o => o.evolcoreHome !== currentHome);
610
585
  if (sameHome.length > 0) {
611
586
  const killed = killOrphans(sameHome);
612
587
  console.log(`☠ 已 SIGKILL ${killed.length} 个同 HOME 孤儿进程: ${killed.join(', ')}`);
@@ -617,6 +592,9 @@ export async function cmdRestart(opts = {}) {
617
592
  console.log(`☠ 已 SIGKILL ${killed.length} 个跨 HOME 孤儿进程: ${killed.join(', ')}`);
618
593
  await sleep(500);
619
594
  }
595
+ else if (otherHome.length > 0) {
596
+ reportOrphans(otherHome);
597
+ }
620
598
  }
621
599
  // 清理 codex app-server 孤儿进程(无 HOME 区分,全局清理)
622
600
  {
@@ -642,141 +620,6 @@ export async function cmdRestart(opts = {}) {
642
620
  printCodeStats(getPackageRoot(), resolvePaths().logs);
643
621
  }
644
622
  }
645
- export async function cmdDev(args) {
646
- const pkgRoot = getPackageRoot();
647
- const isNpmInstall = pkgRoot.includes('node_modules');
648
- const p = resolvePaths();
649
- const devMarker = path.join(p.dataDir, 'dev-repo.path');
650
- const sub = args[0];
651
- if (!sub) {
652
- if (!isNpmInstall) {
653
- console.log(`当前: [dev] ${pkgRoot}`);
654
- console.log('');
655
- console.log('断开开发仓链接:');
656
- console.log(' ec dev off');
657
- }
658
- else {
659
- console.log(`当前: [pkg] ${pkgRoot}`);
660
- console.log('');
661
- let devPath = null;
662
- try {
663
- devPath = fs.readFileSync(devMarker, 'utf-8').trim();
664
- }
665
- catch { }
666
- if (devPath && fs.existsSync(devPath)) {
667
- console.log('链接到开发仓:');
668
- console.log(` ec dev on`);
669
- console.log(` (已记录路径: ${devPath})`);
670
- }
671
- else {
672
- console.log('链接到开发仓:');
673
- console.log(' ec dev <开发仓路径>');
674
- }
675
- }
676
- return;
677
- }
678
- if (sub === 'off') {
679
- if (isNpmInstall) {
680
- console.log('当前已是 [pkg] 模式,无需断开');
681
- return;
682
- }
683
- console.log('🔗 断开开发仓链接...');
684
- let npmPrefix;
685
- if (process.platform === 'win32' && process.env.APPDATA) {
686
- npmPrefix = path.join(process.env.APPDATA, 'npm');
687
- }
688
- else {
689
- npmPrefix = execFileSync('npm', ['prefix', '-g'], { encoding: 'utf-8', shell: true }).trim();
690
- }
691
- const linkPath = path.join(npmPrefix, 'node_modules', 'evolcore');
692
- const binPath = path.join(npmPrefix, 'evolcore');
693
- try {
694
- fs.rmSync(linkPath, { recursive: true });
695
- }
696
- catch { }
697
- try {
698
- fs.unlinkSync(binPath);
699
- }
700
- catch { }
701
- try {
702
- fs.unlinkSync(binPath + '.cmd');
703
- }
704
- catch { }
705
- try {
706
- fs.unlinkSync(binPath + '.ps1');
707
- }
708
- catch { }
709
- console.log('✓ 已断开');
710
- console.log(` 已删除: ${linkPath}`);
711
- console.log(` 如需恢复: ec dev on(需从已安装的 evolcore 执行)`);
712
- return;
713
- }
714
- if (sub === 'on') {
715
- if (!isNpmInstall) {
716
- console.log(`当前已是 [dev] 模式: ${pkgRoot}`);
717
- return;
718
- }
719
- let devPath = null;
720
- try {
721
- devPath = fs.readFileSync(devMarker, 'utf-8').trim();
722
- }
723
- catch { }
724
- if (!devPath || !fs.existsSync(devPath)) {
725
- console.log('未记录开发仓路径');
726
- console.log('');
727
- console.log('用法: ec dev <开发仓路径>');
728
- process.exit(1);
729
- }
730
- console.log(`🔗 链接开发仓: ${devPath}`);
731
- try {
732
- execFileSync('npm', ['link'], { stdio: 'inherit', cwd: devPath, shell: true });
733
- console.log(`✓ 已链接 [dev] ${devPath}`);
734
- }
735
- catch (e) {
736
- console.error('❌ npm link 失败:', e.message);
737
- process.exit(1);
738
- }
739
- return;
740
- }
741
- // ec dev <path> — 记录路径 + 建立链接
742
- const devPath = path.resolve(sub);
743
- const pkgJson = path.join(devPath, 'package.json');
744
- if (!fs.existsSync(pkgJson)) {
745
- console.error(`❌ 路径不存在或无 package.json: ${devPath}`);
746
- process.exit(1);
747
- }
748
- let pkg;
749
- try {
750
- pkg = JSON.parse(fs.readFileSync(pkgJson, 'utf-8'));
751
- }
752
- catch {
753
- console.error(`❌ 无法解析 package.json: ${pkgJson}`);
754
- process.exit(1);
755
- }
756
- if (pkg.name !== 'evolcore') {
757
- console.error(`❌ package.json name 不是 evolcore(是 "${pkg.name}")`);
758
- process.exit(1);
759
- }
760
- // 已经链接到同一路径,只记录不重复 link
761
- if (!isNpmInstall && path.resolve(pkgRoot) === devPath) {
762
- fs.mkdirSync(p.dataDir, { recursive: true });
763
- fs.writeFileSync(devMarker, devPath, 'utf-8');
764
- console.log(`✓ 当前已链接到该路径,路径已记录`);
765
- return;
766
- }
767
- console.log(`🔗 链接开发仓: ${devPath}`);
768
- try {
769
- execFileSync('npm', ['link'], { stdio: 'inherit', cwd: devPath, shell: true });
770
- }
771
- catch (e) {
772
- console.error('❌ npm link 失败:', e.message);
773
- process.exit(1);
774
- }
775
- fs.mkdirSync(p.dataDir, { recursive: true });
776
- fs.writeFileSync(devMarker, devPath, 'utf-8');
777
- console.log(`✓ 已链接 [dev] ${devPath}`);
778
- console.log(` 路径已记录,下次可用 ec dev on 快速切换`);
779
- }
780
623
  function formatTimeAgo(ms) {
781
624
  const sec = Math.floor(ms / 1000);
782
625
  if (sec < 60)
@@ -885,6 +728,16 @@ function renderAunAidsTable(aids) {
885
728
  note);
886
729
  }
887
730
  }
731
+ function readStatusDefaultsConfig(file) {
732
+ try {
733
+ return JSON.parse(fs.readFileSync(file, 'utf-8'));
734
+ }
735
+ catch (error) {
736
+ const detail = error?.code ? String(error.code) : error instanceof Error ? error.message : String(error);
737
+ console.log(`⚠ Channel configuration unavailable (${detail})`);
738
+ return null;
739
+ }
740
+ }
888
741
  function showConfigChannels(config) {
889
742
  const groups = [];
890
743
  const channelChecks = [
@@ -1061,48 +914,52 @@ export async function cmdStatus() {
1061
914
  }
1062
915
  catch { }
1063
916
  }
1064
- // Channel status
1065
- if (fs.existsSync(p.defaultsConfig)) {
917
+ // Channel status. A running daemon is authoritative and avoids reading
918
+ // protected config files from the CLI's potentially sandboxed process.
919
+ if (pid) {
1066
920
  console.log('');
1067
- const config = JSON.parse(fs.readFileSync(p.defaultsConfig, 'utf-8'));
1068
- if (pid) {
1069
- // Running: query IPC for real-time status
1070
- const status = await ipcQuery(p.socket, { type: 'status' });
1071
- if (status) {
1072
- // 🔑 AUN AIDs 表格(详细 AUN 实例状态)
1073
- try {
1074
- const aidsResp = await ipcQuery(p.socket, { type: 'aun-aids' });
1075
- if (aidsResp?.ok && aidsResp.aids?.length > 0) {
1076
- console.log('🔑 AUN AIDs:');
1077
- renderAunAidsTable(aidsResp.aids);
1078
- }
1079
- }
1080
- catch { /* ignore */ }
1081
- // 控制 AID(daemon 进程身份)状态
1082
- if (status.controlAid) {
1083
- const state = status.controlAid.connected ? 'connected' : 'disconnected';
1084
- console.log(`control: ${status.controlAid.aid} [${state}]`);
1085
- }
1086
- else {
1087
- console.log('control: not configured');
1088
- }
1089
- if (status.stats) {
1090
- console.log('');
1091
- console.log('📊 Last hour:');
1092
- console.log(` Messages: ${status.stats.received} received, ${status.stats.completed} completed`);
1093
- if (status.stats.errors > 0)
1094
- console.log(` Errors: ${status.stats.errors}`);
1095
- if (status.stats.completed > 0)
1096
- console.log(` Avg response: ${(status.stats.avgResponseMs / 1000).toFixed(1)}s`);
921
+ const status = await ipcQuery(p.socket, { type: 'status' });
922
+ if (status) {
923
+ // 🔑 AUN AIDs 表格(详细 AUN 实例状态)
924
+ try {
925
+ const aidsResp = await ipcQuery(p.socket, { type: 'aun-aids' });
926
+ if (aidsResp?.ok && aidsResp.aids?.length > 0) {
927
+ console.log('🔑 AUN AIDs:');
928
+ renderAunAidsTable(aidsResp.aids);
1097
929
  }
1098
930
  }
931
+ catch { /* ignore */ }
932
+ // 控制 AID(daemon 进程身份)状态
933
+ if (status.controlAid) {
934
+ const state = status.controlAid.connected ? 'connected' : 'disconnected';
935
+ console.log(`control: ${status.controlAid.aid} [${state}]`);
936
+ }
1099
937
  else {
1100
- // IPC unreachable but PID exists — show config only
938
+ console.log('control: not configured');
939
+ }
940
+ if (status.stats) {
941
+ console.log('');
942
+ console.log('📊 Last hour:');
943
+ console.log(` Messages: ${status.stats.received} received, ${status.stats.completed} completed`);
944
+ if (status.stats.errors > 0)
945
+ console.log(` Errors: ${status.stats.errors}`);
946
+ if (status.stats.completed > 0)
947
+ console.log(` Avg response: ${(status.stats.avgResponseMs / 1000).toFixed(1)}s`);
948
+ }
949
+ }
950
+ else {
951
+ // IPC unreachable but PID exists — fall back to local config if readable.
952
+ const config = readStatusDefaultsConfig(p.defaultsConfig);
953
+ if (config) {
1101
954
  console.log('🔌 Channels (IPC unreachable):');
1102
955
  showConfigChannels(config);
1103
956
  }
1104
957
  }
1105
- else {
958
+ }
959
+ else if (fs.existsSync(p.defaultsConfig)) {
960
+ console.log('');
961
+ const config = readStatusDefaultsConfig(p.defaultsConfig);
962
+ if (config) {
1106
963
  console.log('🔌 Channel Configuration:');
1107
964
  showConfigChannels(config);
1108
965
  }
@@ -2406,11 +2263,17 @@ async function probeEcwebReady(port) {
2406
2263
  * 2. HTTP 是否就绪——探测根路径 /(serveStatic → index.html, 无副作用)。
2407
2264
  */
2408
2265
  async function printEcwebStatus(p) {
2409
- const cfg = loadDaemonConfig();
2410
- if (cfg.ecweb?.enabled === false) {
2411
- console.log('');
2412
- console.log('🔭 ECWeb: 已禁用 (daemon.json → ecweb.enabled: false)');
2413
- return;
2266
+ try {
2267
+ const cfg = loadDaemonConfig();
2268
+ if (cfg.ecweb?.enabled === false) {
2269
+ console.log('');
2270
+ console.log('🔭 ECWeb: 已禁用 (daemon.json → ecweb.enabled: false)');
2271
+ return;
2272
+ }
2273
+ }
2274
+ catch {
2275
+ // Agent tool sandboxes may mask daemon.json. Instance and HTTP probes below
2276
+ // still provide an authoritative runtime status without local config access.
2414
2277
  }
2415
2278
  const entries = readEcwebInstanceEntries(p);
2416
2279
  let readyEntry;
@@ -3,7 +3,7 @@ import { resolvePaths } from '../paths.js';
3
3
  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
- import { isHelpFlag } from './help.js';
6
+ import { isHelpFlag, wantsHelp } from './help.js';
7
7
  import { AGENT_DELEGATION_TOKEN_ENV } from '../core/auth/agent-delegation.js';
8
8
  function parseLimit(value) {
9
9
  const limit = Number(value);
@@ -152,7 +152,7 @@ function formatTimestamp(value) {
152
152
  }
153
153
  export async function cmdHandoff(args) {
154
154
  const sub = args[0];
155
- if (!sub || isHelpFlag(sub)) {
155
+ if (!sub || isHelpFlag(sub) || wantsHelp(args)) {
156
156
  console.log(`用法: ec handoff <command> [args]
157
157
 
158
158
  Commands:
package/dist/cli/help.js CHANGED
@@ -3,22 +3,26 @@
3
3
  *
4
4
  * 把 7 种历史写法统一为两个语义清晰的函数:
5
5
  * - isHelpFlag(token):单 token 是否是 help 标记('help' / '--help' / '-h')
6
- * - wantsHelp(args):args 任意位置出现 help 标记
6
+ * - wantsHelp(args):首 token help,或 `--` 分隔符前出现 --help/-h
7
7
  *
8
8
  * 两套 API 服务于不同语义:
9
9
  * - 顶层路由(如 cmdAid 看到第一个 token 是子命令名时)必须用 isHelpFlag(sub),
10
10
  * 否则 `ec aid delete --help` 会被顶层吞掉,永远到不了 delete 自己的 help。
11
- * - 单层命令(如 cmdLinkRules、cmdAid 的具体 sub 处理块内)用 wantsHelp(args)
12
- * 更宽松,任意位置都识别。
11
+ * - 单层命令(如 cmdLinkRules、cmdAid 的具体 sub 处理块内)用 wantsHelp(args)
12
+ * 支持 option 风格帮助,同时不把 `--` 后的正文误判为帮助。
13
13
  */
14
14
  const HELP_TOKENS = new Set(['help', '--help', '-h']);
15
15
  export function isHelpFlag(token) {
16
16
  return token !== undefined && HELP_TOKENS.has(token);
17
17
  }
18
18
  export function wantsHelp(args) {
19
- for (const a of args)
20
- if (HELP_TOKENS.has(a))
19
+ for (let i = 0; i < args.length; i++) {
20
+ const arg = args[i];
21
+ if (arg === '--')
22
+ break;
23
+ if (arg === '--help' || arg === '-h' || (i === 0 && arg === 'help'))
21
24
  return true;
25
+ }
22
26
  return false;
23
27
  }
24
28
  /**