evolcore 0.0.21 → 0.0.23

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 (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
@@ -299,6 +299,14 @@ function renderManagedGroupResult(args, result, formatJson) {
299
299
  console.log(`✓ 成功 ${result.added?.length ?? 0},失败 ${result.failed?.length ?? 0}`);
300
300
  return;
301
301
  }
302
+ if (sub === 'invites') {
303
+ console.log(`✓ ${result.invites?.length ?? 0} 个待处理邀请`);
304
+ return;
305
+ }
306
+ if (sub === 'accept-invite' || sub === 'reject-invite' || sub === 'revoke-invite') {
307
+ console.log(`✓ group ${sub} 完成`);
308
+ return;
309
+ }
302
310
  console.log(`✓ group ${sub} 完成`);
303
311
  }
304
312
  export async function cmdAid(args) {
@@ -1558,7 +1566,11 @@ export async function cmdGroup(args) {
1558
1566
  成员:
1559
1567
  join <from> <group-id> [--message M] [--answer A] 申请加入
1560
1568
  leave <from> <group-id> 退出群
1561
- invite <from> <group-id> <member-aid> [<member-aid>...] 邀请成员
1569
+ invite <from> <group-id> <member-aid> [<member-aid>...] 创建定向邀请
1570
+ invites <from> [--page N] [--size N] 查看收到的邀请
1571
+ accept-invite <from> <invite-id> 接受邀请
1572
+ reject-invite <from> <invite-id> 拒绝邀请
1573
+ revoke-invite <from> <invite-id> 撤回邀请
1562
1574
  kick <from> <group-id> <member-aid> 踢出成员
1563
1575
  members <from> <group-id> [--page N] [--size N] 列出群成员
1564
1576
  online <from> <group-id> 查看在线成员
@@ -1582,6 +1594,7 @@ Options:
1582
1594
  --text <说明> 附件说明文字(仅 --file 模式)
1583
1595
  --transcript <text> 语音转写(仅 --as voice)
1584
1596
  --group-id <aid> 指定自定义群 AID(仅 create)
1597
+ --member-type <type> 定向邀请成员类型:human|ai(仅 invite)
1585
1598
  -- end-of-options:其后所有参数按正文处理
1586
1599
  (用于发送恰好等于某 flag 的文本,如 send a g -- --encrypt)
1587
1600
 
@@ -1617,7 +1630,7 @@ Options:
1617
1630
  renderManagedGroupResult(args, result, formatJson);
1618
1631
  return;
1619
1632
  }
1620
- const { groupSend, groupPull, groupAck, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } = await import('../aun/msg/index.js');
1633
+ const { groupSend, groupPull, groupAck, groupCreate, groupInfo, groupList, groupUpdate, groupDissolve, groupSuspend, groupResume, groupJoin, groupLeave, groupInvite, groupInvites, groupAcceptInvite, groupRejectInvite, groupRevokeInvite, groupKick, groupMembers, groupOnline, groupSetRole, groupTransferOwner, groupBan, groupUnban, groupBanlist, groupRules, groupUpdateRules, groupRulesFileGet, groupRulesFileSet, groupRulesFilePublish, } = await import('../aun/msg/index.js');
1621
1634
  const commonOpts = { aunPath, slotId: appSlot };
1622
1635
  // 通用 group_id 提取(第三参数)
1623
1636
  const requireGroupId = () => {
@@ -1693,6 +1706,31 @@ Options:
1693
1706
  }
1694
1707
  return parsed;
1695
1708
  };
1709
+ if (sub === 'invites') {
1710
+ const page = parseNumberFlag('--page', '--page');
1711
+ const size = parseNumberFlag('--size', '--size');
1712
+ const result = await groupInvites({ from, page, size, ...commonOpts });
1713
+ outputResult(result, () => {
1714
+ const r = result;
1715
+ console.log(`✓ ${r.invites.length} 个待处理邀请:`);
1716
+ for (const invite of r.invites) {
1717
+ console.log(` ${invite.invite_id ?? invite.id ?? '-'} ${invite.group_id ?? invite.group_aid ?? '-'} ${invite.inviter_aid ?? invite.from ?? '-'}`);
1718
+ }
1719
+ });
1720
+ return;
1721
+ }
1722
+ if (sub === 'accept-invite' || sub === 'reject-invite' || sub === 'revoke-invite') {
1723
+ const inviteId = args[2];
1724
+ if (!inviteId || inviteId.startsWith('--')) {
1725
+ console.error(`用法: ec group ${sub} <from> <invite-id>`);
1726
+ process.exit(1);
1727
+ }
1728
+ const action = sub === 'accept-invite' ? groupAcceptInvite
1729
+ : sub === 'reject-invite' ? groupRejectInvite : groupRevokeInvite;
1730
+ const result = await action({ from, inviteId, ...commonOpts });
1731
+ outputResult(result, () => console.log(`✓ 已${sub === 'accept-invite' ? '接受' : sub === 'reject-invite' ? '拒绝' : '撤回'}邀请 ${inviteId}`));
1732
+ return;
1733
+ }
1696
1734
  const requireFlagValue = (flag, label) => {
1697
1735
  const value = getArgValue(args, flag);
1698
1736
  if (value === undefined || value.startsWith('--')) {
@@ -2147,7 +2185,12 @@ Options:
2147
2185
  process.exit(1);
2148
2186
  }
2149
2187
  }
2150
- const result = await groupInvite({ from, groupId, members, ...commonOpts });
2188
+ const memberType = getArgValue(args, '--member-type');
2189
+ if (memberType !== undefined && memberType !== 'human' && memberType !== 'ai') {
2190
+ console.error('❌ --member-type 必须是 human 或 ai');
2191
+ process.exit(1);
2192
+ }
2193
+ const result = await groupInvite({ from, groupId, members, memberType: memberType, ...commonOpts });
2151
2194
  outputResult(result, () => {
2152
2195
  const r = result;
2153
2196
  console.log(`✓ 成功 ${r.added.length},失败 ${r.failed.length}`);
@@ -2300,7 +2343,7 @@ Options:
2300
2343
  });
2301
2344
  return;
2302
2345
  }
2303
- console.error(`未知子命令: ${sub}\n用法: ec group [send|pull|ack|create|list|info|update|dissolve|suspend|resume|rules|join|leave|invite|kick|members|online|role|owner|ban|unban]`);
2346
+ console.error(`未知子命令: ${sub}\n用法: ec group [send|pull|ack|create|list|info|update|dissolve|suspend|resume|rules|join|leave|invite|invites|accept-invite|reject-invite|revoke-invite|kick|members|online|role|owner|ban|unban]`);
2304
2347
  process.exit(1);
2305
2348
  }
2306
2349
  // ==================== Main ====================
@@ -2322,6 +2365,7 @@ const VALUE_FLAGS = new Set([
2322
2365
  '--payload', '--title', '--description', '--text', '--transcript',
2323
2366
  '--as', '--content-type', '--mention', '--visibility', '--join-mode',
2324
2367
  '--group-id', '--name', '--message', '--answer', '--page', '--size',
2368
+ '--member-type',
2325
2369
  '--aun-path', '--thread', '--return',
2326
2370
  ]);
2327
2371
  const BOOLEAN_FLAGS = new Set([
@@ -59,8 +59,8 @@ export function logCliCommand(cmd, args) {
59
59
  const line = JSON.stringify({
60
60
  ts: localTimestamp(),
61
61
  cmd,
62
- // 完整命令行,可直接拷贝执行(含 ec 前缀,含空格的参数自动加引号)
63
- command: `ec ${args.map(shellQuote).join(' ')}`,
62
+ // 可审计命令行(敏感参数掩码;含 ec 前缀,含空格参数自动加引号)
63
+ command: `ec ${loggableArgs(args).map(shellQuote).join(' ')}`,
64
64
  selfAid: selfAid || undefined,
65
65
  sessionId: process.env.EVOLCORE_SESSION_ID || undefined,
66
66
  }) + '\n';
@@ -82,6 +82,25 @@ function shellQuote(arg) {
82
82
  return arg;
83
83
  return `'${arg.replace(/'/g, `'\\''`)}'`;
84
84
  }
85
+ function loggableArgs(args) {
86
+ const output = [];
87
+ let redactNext = false;
88
+ for (const arg of args) {
89
+ if (redactNext) {
90
+ output.push('[REDACTED]');
91
+ redactNext = false;
92
+ continue;
93
+ }
94
+ const flag = arg.replace(/^--?/, '').replace(/=.*$/, '').toLowerCase();
95
+ if (/^(?:remote-token|ecweb-token|menu-token|token|password|secret)$/.test(flag)) {
96
+ output.push(arg.includes('=') ? `${arg.slice(0, arg.indexOf('=') + 1)}[REDACTED]` : arg);
97
+ redactNext = !arg.includes('=');
98
+ continue;
99
+ }
100
+ output.push(arg.replace(/((?:"|')?(?:ecweb[_-]?token|menu[_-]?token|access[_-]?token|refresh[_-]?token|password|secret)(?:"|')?\s*[:=]\s*)("(?:[^"\\]|\\.)*"|'[^']*'|[^,}\s]+)/gi, '$1"[REDACTED]"'));
101
+ }
102
+ return output;
103
+ }
85
104
  /** 本地日期 tag YYYYMMDD,用于日志文件按天切分(一天一个文件)。 */
86
105
  function dayTag() {
87
106
  const d = new Date();
@@ -15,7 +15,7 @@ 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
17
  import { tryUpgrade, tryUpgradeGlobalPkg, resolveGlobalPkg } from '../utils/npm-ops.js';
18
- import { ecwebVersionRequirementError, fetchEcwebPairCode, resolveEcwebLaunchCommand } from '../utils/ecweb-utils.js';
18
+ import { ecwebVersionRequirementError, resolveEcwebLaunchCommand, fetchRemotePairingCode } from '../utils/ecweb-utils.js';
19
19
  import { isHostChinese } from '../utils/locale.js';
20
20
  import { scanInstances, cleanupInstances, findOrphanProcesses, killOrphans } from '../utils/instance-registry.js';
21
21
  import { filterLogFiles, deriveLogTypes, computePreChecked, validateLogTypes, shortLogName as shortLogNameLocal, isWatchLogFile } from './watch-logs.js';
@@ -28,8 +28,9 @@ import { WEB_CLI_BIN, WEB_PACKAGE_LATEST, WEB_PACKAGE_NAME } from '../product.js
28
28
  import { rotateStdoutLog } from '../utils/log-writer.js';
29
29
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
30
30
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
31
- import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../utils/codex-app-server-registry.js';
31
+ import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../agents/codex/app-server-registry.js';
32
32
  import { DEFAULT_ECWEB_PORT, ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
33
+ import { checkCodexProjectPath, inspectCodexManagedHook } from '../agents/codex/diagnostics.js';
33
34
  const execFileAsync = promisify(execFile);
34
35
  async function runAutomaticInit(action) {
35
36
  try {
@@ -174,14 +175,40 @@ function sourceBuildNeedsRefresh() {
174
175
  const sourceMtime = Math.max(latestSourceMtime(path.join(packageRoot, 'src')), latestSourceMtime(path.join(packageRoot, 'ecagent', 'src')));
175
176
  return sourceMtime > fs.statSync(daemonEntry).mtimeMs;
176
177
  }
177
- function requireFreshSourceBuild() {
178
+ function runSourceBuild() {
179
+ return new Promise(resolve => {
180
+ console.log('🔧 源码比 daemon 编译产物更新,自动重新构建...');
181
+ // Windows cannot spawn a .cmd shim directly with shell:false on recent
182
+ // Node versions. Invoke cmd.exe explicitly instead of using spawn's
183
+ // shell:true mode, which also avoids the DEP0190 warning.
184
+ const command = platform.isWindows ? (process.env.ComSpec ?? process.env.COMSPEC ?? 'cmd.exe') : 'npm';
185
+ const args = platform.isWindows ? ['/d', '/c', 'npm.cmd', 'run', 'build'] : ['run', 'build'];
186
+ const child = spawn(command, args, {
187
+ cwd: getPackageRoot(),
188
+ stdio: 'inherit',
189
+ windowsHide: platform.isWindows,
190
+ });
191
+ child.on('error', err => {
192
+ console.error(`❌ 无法启动构建进程: ${err.message}`);
193
+ resolve(false);
194
+ });
195
+ child.on('exit', code => {
196
+ if (code === 0) {
197
+ console.log('✅ 构建完成');
198
+ resolve(true);
199
+ }
200
+ else {
201
+ console.error(`❌ 自动构建失败 (exit ${code}),服务未启动。`);
202
+ console.error(' 请手动执行: npm run build 排查错误');
203
+ resolve(false);
204
+ }
205
+ });
206
+ });
207
+ }
208
+ async function requireFreshSourceBuild() {
178
209
  if (!sourceBuildNeedsRefresh())
179
210
  return true;
180
- console.error('❌ 源码比 daemon 编译产物更新,服务未启动。');
181
- console.error(' 请先执行: ec stop && npm run build');
182
- console.error(' 然后执行: ec start');
183
- process.exitCode = 2;
184
- return false;
211
+ return await runSourceBuild();
185
212
  }
186
213
  function requireCompletedDataMigration(root) {
187
214
  const requirement = inspectDataMigrationRequirement(root);
@@ -194,6 +221,64 @@ function requireCompletedDataMigration(root) {
194
221
  process.exitCode = 2;
195
222
  return false;
196
223
  }
224
+ /**
225
+ * Check package updates before starting or restarting the daemon.
226
+ * A failed upgrade is reported, but the currently installed version remains
227
+ * usable and the caller continues with its normal lifecycle.
228
+ */
229
+ export async function runUpgradeChecks(upgradeFn = signal => tryUpgrade({ signal }), ecwebUpgradeFn = signal => tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME, { signal }), timeoutMs = 60_000) {
230
+ console.log('📦 Checking for updates...');
231
+ const runWithTimeout = async (label, fn) => {
232
+ const controller = new AbortController();
233
+ let timer;
234
+ try {
235
+ return await Promise.race([
236
+ fn(controller.signal),
237
+ new Promise(resolve => {
238
+ timer = setTimeout(() => {
239
+ resolve({ status: 'failed', error: `${label} upgrade timed out` });
240
+ controller.abort();
241
+ }, timeoutMs);
242
+ }),
243
+ ]);
244
+ }
245
+ catch (error) {
246
+ return { status: 'failed', error: error instanceof Error ? error.message : String(error) };
247
+ }
248
+ finally {
249
+ if (timer)
250
+ clearTimeout(timer);
251
+ }
252
+ };
253
+ const upgrade = await runWithTimeout('EvolCore', upgradeFn);
254
+ switch (upgrade.status) {
255
+ case 'upgraded':
256
+ console.log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
257
+ break;
258
+ case 'no-update':
259
+ console.log(`✓ Already up to date (${upgrade.from})`);
260
+ break;
261
+ case 'skipped':
262
+ console.log(upgrade.error
263
+ ? '⏭ Skipped upgrade (network unavailable)'
264
+ : '⏭ Skipped upgrade check (dev mode)');
265
+ break;
266
+ case 'failed':
267
+ console.log(`⚠ Upgrade failed${upgrade.from && upgrade.to ? ` (${upgrade.from} → ${upgrade.to})` : ''}, continuing with current version${upgrade.error ? `: ${upgrade.error}` : ''}`);
268
+ break;
269
+ }
270
+ const ecwebUpgrade = await runWithTimeout(WEB_PACKAGE_NAME, ecwebUpgradeFn);
271
+ switch (ecwebUpgrade.status) {
272
+ case 'upgraded':
273
+ console.log(`✅ ${WEB_PACKAGE_NAME} upgraded: ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`);
274
+ break;
275
+ case 'no-update':
276
+ break;
277
+ case 'failed':
278
+ console.log(`⚠ ${WEB_PACKAGE_NAME} upgrade failed${ecwebUpgrade.from && ecwebUpgrade.to ? ` (${ecwebUpgrade.from} → ${ecwebUpgrade.to})` : ''}${ecwebUpgrade.error ? `: ${ecwebUpgrade.error}` : ''}`);
279
+ break;
280
+ }
281
+ }
197
282
  // ==================== Commands ====================
198
283
  /**
199
284
  * 检测并展示跨 HOME 残留的 evolcore 主进程。
@@ -306,8 +391,11 @@ export async function cmdStart(opts = {}) {
306
391
  return;
307
392
  }
308
393
  ensureDataDirs();
309
- if (!requireFreshSourceBuild())
394
+ if (!await requireFreshSourceBuild())
310
395
  return;
396
+ // A stopped container may miss the daily upgrade trigger. Check once on
397
+ // every real start so the service does not stay on an old installation.
398
+ await (opts.upgradeChecks ?? runUpgradeChecks)();
311
399
  // 旧配置自动迁移(daemon.json → 新结构)
312
400
  const { autoMigrateIfNeeded } = await import('../config-store.js');
313
401
  autoMigrateIfNeeded();
@@ -681,39 +769,11 @@ export async function cmdRestart(opts = {}) {
681
769
  if (initialRuntimeOrphans.length === 0 && rejectPidIsolatedLifecycle('restart', initialStatus, ping))
682
770
  return;
683
771
  }
684
- if (!requireFreshSourceBuild())
772
+ if (!await requireFreshSourceBuild())
685
773
  return;
686
- // 版本检查与自动升级
687
- console.log('📦 Checking for updates...');
688
- const upgrade = await tryUpgrade();
689
- switch (upgrade.status) {
690
- case 'upgraded':
691
- console.log(`✅ Upgraded: ${upgrade.from} → ${upgrade.to}`);
692
- break;
693
- case 'no-update':
694
- console.log(`✓ Already up to date (${upgrade.from})`);
695
- break;
696
- case 'skipped':
697
- console.log(upgrade.error
698
- ? '⏭ Skipped upgrade (network unavailable)'
699
- : '⏭ Skipped upgrade check (dev mode)');
700
- break;
701
- case 'failed':
702
- console.log(`⚠ Upgrade failed (${upgrade.from} → ${upgrade.to}), continuing with current version`);
703
- break;
704
- }
705
- // EC Web 是独立全局包,已安装时单独检查升级。
706
- const ecwebUpgrade = await tryUpgradeGlobalPkg(() => resolveGlobalPkg(WEB_PACKAGE_NAME), WEB_PACKAGE_NAME);
707
- switch (ecwebUpgrade.status) {
708
- case 'upgraded':
709
- console.log(`✅ ${WEB_PACKAGE_NAME} upgraded: ${ecwebUpgrade.from} → ${ecwebUpgrade.to}`);
710
- break;
711
- case 'no-update':
712
- break; // silent
713
- case 'failed':
714
- console.log(`⚠ ${WEB_PACKAGE_NAME} upgrade failed (${ecwebUpgrade.from} → ${ecwebUpgrade.to})`);
715
- break;
716
- }
774
+ // Keep restart behavior identical to startup: both paths use one upgrade
775
+ // check implementation and continue when an upgrade is unavailable.
776
+ await runUpgradeChecks();
717
777
  if (!requireCompletedDataMigration(resolveRoot()))
718
778
  return;
719
779
  // 停止所有活 main 进程(可能不止一个)
@@ -2646,7 +2706,7 @@ async function stopEcwebIfRunning(p) {
2646
2706
  throw new Error(`EC Web old process or port ${port} could not be released`);
2647
2707
  }
2648
2708
  // 清理 pid 文件(仅 ecweb-<pid>.json / watch-web-<pid>.json,
2649
- // 不碰 ecweb-tokens.json 等非 pid 文件,否则会清空配对 token 库导致每次重启都要重新配对)
2709
+ // 不碰 daemon-owned control-access-tokens.json 等非 pid 文件,否则会清空凭据库导致每次重启都要重新配对)
2650
2710
  removeEcwebInstanceFiles(p);
2651
2711
  return pids.size > 0;
2652
2712
  }
@@ -2738,23 +2798,32 @@ export async function startEcwebIfEnabled(p, options = {}) {
2738
2798
  fs.mkdirSync(p.instanceDir, { recursive: true });
2739
2799
  const startedAt = getProcessStartTime(pid) ?? Date.now();
2740
2800
  fs.writeFileSync(path.join(p.instanceDir, `ecweb-${pid}.json`), JSON.stringify({ pid, port, startedAt, startedAtIso: new Date(startedAt).toISOString() }, null, 2));
2741
- // 轮询端口确认 HTTP 服务真正就绪(spawn 成功 ≠ 端口绑定成功),顺便拿配对码
2801
+ // 轮询端口确认 HTTP 服务真正就绪(spawn 成功 ≠ 端口绑定成功)。
2802
+ // 配对码由 daemon 提供,daemon 尚未启动时不应把已经就绪的 ECWeb 杀掉。
2803
+ let ready = false;
2742
2804
  let pair = null;
2743
2805
  for (let i = 0; i < 20; i++) {
2744
- pair = await fetchEcwebPairCode(port);
2745
- if (pair)
2806
+ ready = await probeEcwebReady(port);
2807
+ if (ready) {
2808
+ pair = await fetchRemotePairingCode();
2746
2809
  break;
2810
+ }
2747
2811
  await sleep(250);
2748
2812
  }
2749
- if (pair) {
2813
+ if (ready) {
2750
2814
  const actualStartedAt = getProcessStartTime(pid);
2751
2815
  if (actualStartedAt !== null) {
2752
2816
  fs.writeFileSync(path.join(p.instanceDir, `ecweb-${pid}.json`), JSON.stringify({ pid, port, startedAt: actualStartedAt, startedAtIso: new Date(actualStartedAt).toISOString() }, null, 2));
2753
2817
  }
2754
- const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2755
2818
  const actionLabel = wasRunning ? '已重启' : '启动成功';
2756
2819
  console.log(`✓ ECWeb ${actionLabel} (PID: ${pid}) http://localhost:${port}`);
2757
- console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,已配对过的浏览器无需重新配对)`);
2820
+ if (pair) {
2821
+ const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2822
+ console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,已配对过的浏览器无需重新配对)`);
2823
+ }
2824
+ else {
2825
+ console.log(' daemon 尚未提供配对码;EvolCore 启动后可通过 /pair 或 ECWeb 页面获取');
2826
+ }
2758
2827
  return true;
2759
2828
  }
2760
2829
  console.log(`❌ ECWeb 启动失败:端口 ${port} 未就绪(进程 PID ${pid} 可能已退出,查看 logs/watch-web.log)`);
@@ -2780,13 +2849,13 @@ async function printEcwebAccess(port) {
2780
2849
  console.log(`🔭 ECWeb http://localhost:${port}`);
2781
2850
  let pair = null;
2782
2851
  for (let i = 0; i < 10 && !pair; i++) {
2783
- pair = await fetchEcwebPairCode(port);
2852
+ pair = await fetchRemotePairingCode();
2784
2853
  if (!pair)
2785
2854
  await sleep(300);
2786
2855
  }
2787
2856
  if (pair) {
2788
2857
  const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2789
- console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,配对后 token 缓存 24h)`);
2858
+ console.log(` 配对码: ${pair.code} (约 ${mins} 分钟内有效,配对后远程访问 token 缓存 30 天)`);
2790
2859
  }
2791
2860
  else {
2792
2861
  console.log(' 配对码: 暂不可用(稍后重试 ec watch web,或查看 logs/watch-web.log)');
@@ -2903,7 +2972,56 @@ export async function cmdDiagnose() {
2903
2972
  else {
2904
2973
  console.log(`[diagnose] ✓ 已加载 ${agents.length} 个 self-agent`);
2905
2974
  }
2906
- // 3. 检查 Anthropic 配置(用首个 self-agent 的 effective config)
2975
+ // 3. 检查实际使用 Codex 的 Agent 项目目录。项目路径属于 Agent 的
2976
+ // effective config,逐个输出 AID,避免只检查第一个 agent 掩盖坏配置。
2977
+ const codexAgents = agents.filter(agent => {
2978
+ try {
2979
+ return resolveEffective({ self: agent.aid }).active_baseagent === 'codex';
2980
+ }
2981
+ catch (error) {
2982
+ console.error(`[diagnose] ❌ Agent ${agent.aid} effective config 解析失败: ${error instanceof Error ? error.message : String(error)}`);
2983
+ hasError = true;
2984
+ return false;
2985
+ }
2986
+ });
2987
+ for (const agent of codexAgents) {
2988
+ try {
2989
+ const effective = resolveEffective({ self: agent.aid });
2990
+ const check = checkCodexProjectPath(effective.projects?.defaultPath);
2991
+ if (check.status === 'ok') {
2992
+ console.log(`[diagnose] ✓ Codex 项目路径 ${agent.aid}: ${check.path}`);
2993
+ }
2994
+ else {
2995
+ console.error(`[diagnose] ❌ Codex 项目路径 ${agent.aid}: ${check.path || '(未配置)'} (${check.reason || check.status})`);
2996
+ hasError = true;
2997
+ }
2998
+ for (const warning of check.warnings)
2999
+ console.warn(`[diagnose] ⚠️ Codex 项目路径 ${agent.aid}: ${warning}`);
3000
+ }
3001
+ catch (error) {
3002
+ console.error(`[diagnose] ❌ Codex 项目路径检查失败 ${agent.aid}: ${error instanceof Error ? error.message : String(error)}`);
3003
+ hasError = true;
3004
+ }
3005
+ }
3006
+ if (codexAgents.length === 0) {
3007
+ console.log('[diagnose] - Codex managed hook: 跳过(没有 Agent 使用 Codex)');
3008
+ }
3009
+ else {
3010
+ const hook = inspectCodexManagedHook({ packageRoot: getPackageRoot() });
3011
+ if (hook.status === 'installed') {
3012
+ console.log(`[diagnose] ✓ Codex managed hook 已安装: ${hook.hookPath}`);
3013
+ }
3014
+ else if (hook.status === 'skipped') {
3015
+ console.log(`[diagnose] - Codex managed hook: 跳过(${hook.reason})`);
3016
+ }
3017
+ else {
3018
+ const prefix = hook.status === 'missing' ? '⚠️' : '❌';
3019
+ const writer = hook.status === 'missing' ? console.warn : console.error;
3020
+ writer(`[diagnose] ${prefix} Codex managed hook ${hook.status}: ${hook.reason || 'unknown error'}${hook.requirements ? ` (${hook.requirements})` : ''}`);
3021
+ hasError = true;
3022
+ }
3023
+ }
3024
+ // 4. 检查 Anthropic 配置(用首个 self-agent 的 effective config)
2907
3025
  if (agents.length > 0) {
2908
3026
  try {
2909
3027
  const merged = resolveEffective({ self: agents[0].aid });
package/dist/cli/index.js CHANGED
@@ -46,6 +46,7 @@ AUN:
46
46
  fs AUN 文件系统
47
47
  storage 底层对象存储命令
48
48
  rpc 调用底层 AUN RPC
49
+ menu-client 向远程 daemon 发送标准 Menu 请求
49
50
  contact 联系人黑名单管理
50
51
  net AUN 网络链路诊断
51
52
 
@@ -350,6 +351,25 @@ export async function main(args) {
350
351
  await cmdRpc(args.slice(1));
351
352
  break;
352
353
  }
354
+ case 'menu-client': {
355
+ const { suppressSdkLogs } = await import('../aun/aid/index.js');
356
+ suppressSdkLogs();
357
+ const { cmdMenuClient } = await import('./menu-client.js');
358
+ try {
359
+ await cmdMenuClient(args.slice(1));
360
+ }
361
+ catch (error) {
362
+ const payload = {
363
+ ok: false,
364
+ code: typeof error?.code === 'string' ? error.code : 'MENU_CLIENT_FAILED',
365
+ error: error?.message ?? String(error),
366
+ ...(error?.data === undefined ? {} : { data: error.data }),
367
+ };
368
+ console.error(JSON.stringify(payload));
369
+ process.exitCode = 1;
370
+ }
371
+ break;
372
+ }
353
373
  case 'storage': {
354
374
  const { suppressSdkLogs } = await import('../aun/aid/index.js');
355
375
  suppressSdkLogs();
package/dist/cli/init.js CHANGED
@@ -14,6 +14,7 @@ import { defaultProjectsRoot } from '../utils/project-path.js';
14
14
  import { WEB_CLI_BIN, WEB_PACKAGE_LATEST } from '../product.js';
15
15
  import { autostartInstalled, autostartPlatformLabel, configureAutostart } from '../utils/autostart.js';
16
16
  import { askInit, isInitCancelledError, printInitCancelHint } from './init-cancel.js';
17
+ import { AUTO_BASEAGENT_CANDIDATES, BASEAGENT_CANDIDATES, detectAvailableBaseagents, } from '../agents/baseagent-availability.js';
17
18
  // ==================== Helpers ====================
18
19
  function ask(rl, question) {
19
20
  return askInit(rl, question);
@@ -27,17 +28,8 @@ export function expandLeadingTildePath(value, homeDir = os.homedir()) {
27
28
  }
28
29
  return value;
29
30
  }
30
- const BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini', 'ecagent'];
31
- const AUTO_BASEAGENT_CANDIDATES = ['claude', 'codex', 'gemini'];
32
- function isBaseagentAvailable(baseagent, codexAvailability) {
33
- if (baseagent === 'codex')
34
- return (codexAvailability ?? getCodexAppServerAvailability()).available;
35
- if (baseagent === 'ecagent')
36
- return true;
37
- return commandExists(baseagent);
38
- }
39
31
  function detectAvailable(codexAvailability = getCodexAppServerAvailability()) {
40
- return BASEAGENT_CANDIDATES.filter(baseagent => isBaseagentAvailable(baseagent, codexAvailability));
32
+ return detectAvailableBaseagents(codexAvailability);
41
33
  }
42
34
  /** Offer installation only when the probe positively identified an old CLI. */
43
35
  export async function maybeUpgradeCodexForInit(availability, dependencies) {
@@ -0,0 +1,95 @@
1
+ import fs from 'node:fs';
2
+ import { getArgValue, wantsHelp } from './help.js';
3
+ import { executeMenuClientRequest, MenuClientError } from '../aun/menu-client.js';
4
+ function parseDurationMs(value) {
5
+ if (value === undefined)
6
+ return undefined;
7
+ const match = value.trim().match(/^(\d+(?:\.\d+)?)(ms|s|m|h)$/i);
8
+ if (!match)
9
+ throw new MenuClientError('INVALID_ARGUMENT', `Invalid timeout: ${value}`);
10
+ const amount = Number(match[1]);
11
+ const unit = match[2].toLowerCase();
12
+ return amount * (unit === 'ms' ? 1 : unit === 's' ? 1_000 : unit === 'm' ? 60_000 : 3_600_000);
13
+ }
14
+ function readRequest(args) {
15
+ const file = getArgValue(args, '--request-file');
16
+ const inline = getArgValue(args, '--request-json');
17
+ if (!!file === !!inline)
18
+ throw new MenuClientError('INVALID_ARGUMENT', '--request-file and --request-json are mutually exclusive');
19
+ let raw;
20
+ try {
21
+ raw = file ? fs.readFileSync(file, 'utf8') : inline;
22
+ }
23
+ catch (error) {
24
+ throw new MenuClientError('INVALID_ARGUMENT', `Unable to read request: ${error?.message ?? String(error)}`);
25
+ }
26
+ try {
27
+ const parsed = JSON.parse(raw);
28
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed))
29
+ throw new Error('request must be a JSON object');
30
+ return parsed;
31
+ }
32
+ catch (error) {
33
+ throw new MenuClientError('INVALID_ARGUMENT', `Invalid request JSON: ${error?.message ?? String(error)}`);
34
+ }
35
+ }
36
+ function validateOptions(args) {
37
+ const valueOptions = new Set(['--request-file', '--request-json', '--timeout', '--remote-token', '--ecweb-token', '--app', '--aun-path', '--format']);
38
+ const booleanOptions = new Set(['--encrypt', '--no-encrypt']);
39
+ for (let index = 0; index < args.length; index += 1) {
40
+ const option = args[index];
41
+ if (booleanOptions.has(option))
42
+ continue;
43
+ if (!valueOptions.has(option))
44
+ throw new MenuClientError('INVALID_ARGUMENT', `Unknown menu-client option: ${option}`);
45
+ if (index + 1 >= args.length || args[index + 1].startsWith('--')) {
46
+ throw new MenuClientError('INVALID_ARGUMENT', `${option} requires a value`);
47
+ }
48
+ index += 1;
49
+ }
50
+ if (args.includes('--encrypt') && args.includes('--no-encrypt')) {
51
+ throw new MenuClientError('INVALID_ARGUMENT', '--encrypt and --no-encrypt are mutually exclusive');
52
+ }
53
+ const format = getArgValue(args, '--format');
54
+ if (format !== undefined && format !== 'json')
55
+ throw new MenuClientError('INVALID_ARGUMENT', '--format currently supports only json');
56
+ }
57
+ export async function cmdMenuClient(args) {
58
+ if (wantsHelp(args)) {
59
+ console.log(`用法: ec menu-client request <from-aid> <daemon-aid> [选项]
60
+
61
+ --request-file <path> 从 JSON 文件读取一个 menu.* 请求
62
+ --request-json <json> 直接传入一个 menu.* JSON 请求
63
+ --timeout <时长> 默认 15s,最大 120s
64
+ --encrypt 使用 E2EE(默认)
65
+ --no-encrypt 已拒绝:ec-menu-client 必须使用 E2EE
66
+ --remote-token <token> 使用 daemon 远程访问 token 临时授权
67
+ --ecweb-token <token> --remote-token 的兼容别名
68
+ --app <slot> 覆盖默认 evolcore-menu-client 消费槽位
69
+ --aun-path <path> 覆盖本地 AUN keystore 根目录
70
+ --format json 输出标准 menu.response JSON`);
71
+ return;
72
+ }
73
+ if (args[0] !== 'request')
74
+ throw new MenuClientError('INVALID_ARGUMENT', 'menu-client currently supports only the request subcommand');
75
+ const fromAid = args[1]?.trim();
76
+ const targetAid = args[2]?.trim();
77
+ if (!fromAid || !targetAid)
78
+ throw new MenuClientError('INVALID_ARGUMENT', 'request requires <from-aid> and <daemon-aid>');
79
+ const options = args.slice(3);
80
+ validateOptions(options);
81
+ const request = readRequest(options);
82
+ const timeoutMs = parseDurationMs(getArgValue(args, '--timeout'));
83
+ const encrypt = args.includes('--no-encrypt') ? false : true;
84
+ const response = await executeMenuClientRequest({
85
+ fromAid,
86
+ targetAid,
87
+ request,
88
+ timeoutMs,
89
+ encrypt,
90
+ remoteAccessToken: getArgValue(args, '--remote-token') ?? getArgValue(args, '--ecweb-token'),
91
+ slotId: getArgValue(args, '--app'),
92
+ aunPath: getArgValue(args, '--aun-path'),
93
+ });
94
+ console.log(JSON.stringify(response, null, 2));
95
+ }