evolcore 0.0.39 → 0.0.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (102) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +31 -20
  3. package/bin/ec-safe-output.js +14 -2
  4. package/dist/agents/baseagent-availability.js +1 -0
  5. package/dist/agents/claude-runner.js +32 -46
  6. package/dist/agents/codex-runner.js +51 -27
  7. package/dist/agents/ecagent-runner.js +18 -7
  8. package/dist/agents/gemini-runner.js +1 -2
  9. package/dist/agents/runner-types.js +28 -6
  10. package/dist/aun/aid/client.js +1 -1
  11. package/dist/aun/aid/control-aid.js +23 -98
  12. package/dist/aun/aid/ownership-operation.js +1 -3
  13. package/dist/aun/group/invite-service.js +6 -0
  14. package/dist/aun/msg/p2p.js +8 -12
  15. package/dist/channels/aun.js +66 -48
  16. package/dist/channels/dingtalk.js +8 -2
  17. package/dist/channels/feishu.js +6 -1
  18. package/dist/channels/wecom.js +7 -3
  19. package/dist/cli/agent-create.js +23 -4
  20. package/dist/cli/aid-bind-command.js +4 -1
  21. package/dist/cli/daemon-commands.js +242 -17
  22. package/dist/cli/index.js +25 -13
  23. package/dist/cli/memory-command.js +33 -14
  24. package/dist/cli/restart-monitor.js +35 -13
  25. package/dist/cli/task-context.js +1 -0
  26. package/dist/config/config-field-policy.js +1 -0
  27. package/dist/contracts/ecweb-service-protocol.js +44 -0
  28. package/dist/core/agent-runtime.js +6 -0
  29. package/dist/core/auth/agent-delegation.js +7 -4
  30. package/dist/core/auth/auth-gateway.js +3 -1
  31. package/dist/core/auth/authorization-audit.js +5 -1
  32. package/dist/core/auth/debug-context.js +5 -1
  33. package/dist/core/auth/operation-catalog.js +16 -0
  34. package/dist/core/command/command-handler.js +7 -1
  35. package/dist/core/command/menu-catalog.js +48 -12
  36. package/dist/core/command/menu-handler.js +72 -4
  37. package/dist/core/command/menu-protocol.js +20 -1
  38. package/dist/core/command/slash-gate.js +17 -3
  39. package/dist/core/command/slash-handler.js +90 -7
  40. package/dist/core/event-catalog.js +2 -0
  41. package/dist/core/interaction-router.js +2 -1
  42. package/dist/core/ipc-registration.js +26 -7
  43. package/dist/core/memory/batch-meta.js +2 -0
  44. package/dist/core/memory/consolidation-context.js +42 -51
  45. package/dist/core/memory/consolidation-ops-records.js +39 -7
  46. package/dist/core/memory/consolidation-trigger.js +71 -64
  47. package/dist/core/memory/consolidation.js +226 -20
  48. package/dist/core/memory/memory-writer.js +160 -34
  49. package/dist/core/memory/relation-idle-monitor.js +287 -0
  50. package/dist/core/memory/relation-sweep.js +206 -0
  51. package/dist/core/message/aun-ipc-sender.js +5 -0
  52. package/dist/core/message/message-bridge.js +64 -55
  53. package/dist/core/message/message-log.js +17 -0
  54. package/dist/core/message/message-queue.js +6 -0
  55. package/dist/core/message/outbound.js +70 -0
  56. package/dist/core/message/peer-mode.js +7 -2
  57. package/dist/core/message/response-engine.js +341 -73
  58. package/dist/core/ownership/owner-resolver.js +384 -178
  59. package/dist/core/permission/ec-command-parser.js +19 -3
  60. package/dist/core/permission/tool-policy.js +7 -7
  61. package/dist/core/role/runtime-policy.js +64 -0
  62. package/dist/core/session/session-manager.js +25 -5
  63. package/dist/core/system-session/memory.js +3 -18
  64. package/dist/ecweb/basic-settings-service.js +709 -0
  65. package/dist/ecweb/providers-service.js +172 -0
  66. package/dist/index.js +92 -4
  67. package/dist/ipc.js +78 -11
  68. package/dist/stats/db.js +33 -0
  69. package/dist/stats/index.js +2 -2
  70. package/dist/stats/query.js +246 -0
  71. package/dist/stats/writer.js +41 -1
  72. package/dist/trigger/application-service.js +12 -0
  73. package/dist/trigger/authorization.js +2 -2
  74. package/dist/trigger/history.js +36 -0
  75. package/dist/trigger/scheduler.js +3 -0
  76. package/dist/utils/ecweb-supervisor.js +31 -10
  77. package/dist/utils/error-utils.js +8 -4
  78. package/dist/utils/evolcore-version.js +3 -2
  79. package/dist/utils/host-info.js +52 -0
  80. package/ecagent/dist/harness/agent-harness.js +4 -1
  81. package/kits/docs/evolcore/INDEX.md +1 -1
  82. package/kits/docs/evolcore/agent.md +1 -1
  83. package/kits/docs/evolcore/aid.md +29 -12
  84. package/kits/docs/evolcore/config.md +9 -7
  85. package/kits/docs/evolcore/memory-architecture.md +3 -4
  86. package/kits/docs/evolcore/memory-consolidation.md +28 -7
  87. package/kits/docs/evolcore/memory-tags.md +1 -1
  88. package/kits/docs/evolcore/memory.md +1 -1
  89. package/kits/docs/evolcore/menu-client.md +1 -1
  90. package/kits/docs/evolcore/menu-protocol.md +6 -0
  91. package/kits/docs/evolcore/msg.md +1 -1
  92. package/kits/eck_manifest.json +3 -3
  93. package/kits/rules/04-relation.md +3 -2
  94. package/kits/schemas/agent-config.schema.13.json +5 -1
  95. package/kits/templates/memory-consolidation/generalize.md +32 -4
  96. package/kits/templates/memory-consolidation/notes.md +2 -2
  97. package/kits/templates/memory-consolidation/promote.md +10 -2
  98. package/kits/templates/system-fragments/baseagent.md +5 -0
  99. package/kits/templates/system-fragments/memory.md +12 -3
  100. package/kits/templates/system-fragments/session.md +1 -1
  101. package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
  102. package/package.json +3 -3
@@ -257,8 +257,9 @@ export class PersistentWecomCardStore {
257
257
  let changed = false;
258
258
  for (const [taskId, record] of this.records) {
259
259
  const terminalExpiry = record.updatedAt + DEFAULT_CARD_RETENTION_MS;
260
- const pendingExpiry = (record.expiresAt ?? record.createdAt + 7 * DEFAULT_CARD_RETENTION_MS)
261
- + DEFAULT_CARD_RETENTION_MS;
260
+ const pendingExpiry = record.interaction.blocking === true
261
+ ? Number.POSITIVE_INFINITY
262
+ : (record.expiresAt ?? record.createdAt + 7 * DEFAULT_CARD_RETENTION_MS) + DEFAULT_CARD_RETENTION_MS;
262
263
  const expiresAt = record.status === 'pending' ? pendingExpiry : terminalExpiry;
263
264
  if (now > expiresAt) {
264
265
  this.records.delete(taskId);
@@ -322,7 +323,10 @@ export function buildWecomInteractionCard(interaction, taskId) {
322
323
  .filter(({ button }) => !('disabled' in button) || !button.disabled)
323
324
  .slice(0, 6)
324
325
  .map(({ button, index }) => ({
325
- text: truncate(button.label, 10) || `选项 ${index + 1}`,
326
+ text: truncate(interaction.kind.kind === 'action' && 'key' in button
327
+ && interaction.kind.recommendedOption === button.key
328
+ ? `💡 ${button.label}`
329
+ : button.label, 10) || `选项 ${index + 1}`,
326
330
  style: buttonStyle(button.style),
327
331
  key: interaction.kind.kind === 'command-card' ? `cmd_${index}` : `act_${index}`,
328
332
  })),
@@ -881,8 +881,15 @@ export class AgentInitializationService {
881
881
  return;
882
882
  try {
883
883
  const record = readInitialization(aid);
884
- if (!record || record.status !== 'in_progress' || record.phase !== 'ownerpending'
885
- || this.running.has(aid))
884
+ if (!record || this.running.has(aid))
885
+ return;
886
+ const waitingForOwner = record.status === 'in_progress' && record.phase === 'ownerpending';
887
+ // Records written before ownerBindSubmitted existed could fail by resubmitting the bind
888
+ // while SDK ownership events invalidated the consensus cache. Retry them once the
889
+ // requested Owner is observable; other failures still require an explicit --resume.
890
+ const lostConsensusRace = record.status === 'failed' && record.code === 'CONSENSUS_REQUIRED'
891
+ && record.plan.ownerAid === ownerAid;
892
+ if (!waitingForOwner && !lostConsensusRace)
886
893
  return;
887
894
  this.resume(aid, record.actor);
888
895
  }
@@ -1052,8 +1059,20 @@ export class AgentInitializationService {
1052
1059
  }
1053
1060
  this.phase(r, 'hot_loading');
1054
1061
  await this.deps.hotLoad(p.aid);
1055
- if (this.deps.bindOwner)
1056
- await this.deps.bindOwner(p.aid, p.ownerAid);
1062
+ if (this.deps.bindOwner) {
1063
+ // Owner-consensus recovery and --resume re-run from the start. The SDK keeps emitting
1064
+ // ownership events for a few seconds after approval, each invalidating the cached
1065
+ // consensus, so resubmitting an accepted bind then fails with CONSENSUS_REQUIRED.
1066
+ // Retry when no pending or completed task for this Owner remains.
1067
+ const bindStillActive = this.deps.ownerBindActive
1068
+ ? this.deps.ownerBindActive(p.aid, p.ownerAid)
1069
+ : r.ownerBindSubmitted === true;
1070
+ if (!bindStillActive) {
1071
+ this.phase(r, 'binding_owner');
1072
+ await this.deps.bindOwner(p.aid, p.ownerAid);
1073
+ r.ownerBindSubmitted = true;
1074
+ }
1075
+ }
1057
1076
  this.phase(r, 'ownerpending');
1058
1077
  const ownership = await this.deps.checkOwner(p.aid, p.ownerAid);
1059
1078
  this.check(r);
@@ -20,10 +20,11 @@ export async function cmdAidBind(args) {
20
20
  const delegationToken = sessionId ? readDelegationCredential() : undefined;
21
21
  if (sessionId && !delegationToken)
22
22
  throw Object.assign(new Error('Active task delegation is required'), { code: 'DELEGATION_REQUIRED' });
23
+ const isDecision = args[0] === 'accept' || args[0] === 'reject';
23
24
  const response = await ipcQuery(resolvePaths().socket, {
24
25
  type: 'aid.ownership', argv: ['aid', 'bind', ...args],
25
26
  ...(sessionId ? { sessionId, delegationToken } : {}),
26
- }, 30_000);
27
+ }, isDecision ? 150_000 : 30_000);
27
28
  if (!response?.ok)
28
29
  throw Object.assign(new Error(response?.error ?? 'daemon 未运行或 IPC 不可用'), { code: response?.code, retryable: response?.retryable });
29
30
  if (formatJson)
@@ -40,6 +41,8 @@ export async function cmdAidBind(args) {
40
41
  console.log(`绑定任务: ${state.task.operation_id} (${state.task.status}/${state.task.step})`);
41
42
  if (state.decisionTask)
42
43
  console.log(`审批任务: ${state.decisionTask.operation_id} (${state.decisionTask.status}/${state.decisionTask.step})`);
44
+ if (state.decisionConsensus)
45
+ console.log(`审批目标共识: ${state.decisionConsensus.status} (owner=${state.decisionConsensus.owner_aid ?? '-'}, version=${state.decisionConsensus.relation_version}, lock=${state.decisionConsensus.lock})`);
43
46
  for (const request of state.requests ?? []) {
44
47
  console.log(`待确认邀约: ${request.operation_id} (sub=${request.sub_aid}, owner=${request.owner_aid}, 本端=${request.local_role})`);
45
48
  }
@@ -31,6 +31,8 @@ import { rotateStdoutLog } from '../utils/log-writer.js';
31
31
  import { inspectDataMigrationRequirement } from '../core/data-migration.js';
32
32
  import { isInitCancelledError, printInitCancelHint } from './init-cancel.js';
33
33
  import { computeBuildId } from '../utils/evolcore-version.js';
34
+ import { readHostInfo } from '../utils/host-info.js';
35
+ import { queryStatusActivity, queryStatusActivityByAgent } from '../stats/query.js';
34
36
  import { listCodexAppServerProcesses, unregisterCodexAppServerProcess } from '../agents/codex/app-server-registry.js';
35
37
  import { DEFAULT_ECWEB_PORT, ecwebService, setEcwebEnabled } from '../config/daemon-services.js';
36
38
  import { checkCodexProjectPath, inspectCodexManagedHook, ensureCodexManagedHook, runCodexHookLaunchDiagnostic, } from '../agents/codex/diagnostics.js';
@@ -301,7 +303,20 @@ async function requireFreshSourceBuild() {
301
303
  async function launchPackageLifecycle(source, log, options = {}) {
302
304
  const correlated = options.requestId ?? process.env[RESTART_REQUEST_ID_ENV];
303
305
  const upgradeJobId = correlated ? readRestartRequest(path.join(daemonControlDir(), 'restart-pending.json'), correlated)?.upgradeJobId : undefined;
304
- if (!options.prepared && !upgradeJobId)
306
+ let configuredAutoUpgrade = true;
307
+ if (!upgradeJobId && options.applyPackageUpgrades === undefined) {
308
+ try {
309
+ configuredAutoUpgrade = loadDaemonConfig().upgrade?.autoOnStart !== false;
310
+ }
311
+ catch {
312
+ configuredAutoUpgrade = false;
313
+ log('Could not read autoOnStart; skipping automatic package upgrade');
314
+ }
315
+ }
316
+ const applyPackageUpgrades = upgradeJobId
317
+ ? true
318
+ : options.applyPackageUpgrades ?? configuredAutoUpgrade;
319
+ if (!options.prepared && !upgradeJobId && applyPackageUpgrades)
305
320
  await ensureFreshPackageVersionInfo(undefined, { source, log });
306
321
  const root = resolvePaths().root;
307
322
  const launch = platform.resolveMonitorLaunch(getPackageRoot(), path.join(root, 'data', 'monitor-stage'));
@@ -313,7 +328,12 @@ async function launchPackageLifecycle(source, log, options = {}) {
313
328
  return { ok: false, reason: 'RESTART_ENTRY_MISSING' };
314
329
  }
315
330
  const requestId = options.requestId ?? process.env[RESTART_REQUEST_ID_ENV];
316
- const args = [...launch.args, 'restart-monitor', ...(options.clear ? ['--clear'] : [])];
331
+ const args = [
332
+ ...launch.args,
333
+ 'restart-monitor',
334
+ ...(options.clear ? ['--clear'] : []),
335
+ applyPackageUpgrades ? '--apply-package-upgrades' : '--skip-package-upgrades',
336
+ ];
317
337
  const env = {
318
338
  ...options.env,
319
339
  [RUNTIME_HOME_ENV]: root,
@@ -758,7 +778,7 @@ export async function cmdStart(opts = {}) {
758
778
  // 这条路由 restart-monitor 拉起 daemon,同样靠覆盖文件把 --as-mode 带过去
759
779
  const overrideFile = writeAsModeOverride(opts.asMode);
760
780
  const launched = await launchPackageLifecycle('start', console.log, {
761
- prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts),
781
+ prepared: true, foreground: opts.foreground, env: buildDaemonEnv(p, opts), applyPackageUpgrades: true,
762
782
  });
763
783
  if (!launched.ok) {
764
784
  if (!launched.pending)
@@ -1291,7 +1311,180 @@ function showConfigChannels(config) {
1291
1311
  console.log(' (no channels configured)');
1292
1312
  }
1293
1313
  }
1294
- export async function cmdStatus() {
1314
+ function showLiveChannels(status) {
1315
+ const channels = status?.channels;
1316
+ if (!channels || typeof channels !== 'object' || Array.isArray(channels))
1317
+ return false;
1318
+ const entries = Object.entries(channels);
1319
+ console.log('🔌 Channels (live status):');
1320
+ if (entries.length === 0) {
1321
+ console.log(' (no channel instances loaded)');
1322
+ return true;
1323
+ }
1324
+ for (const [name, channel] of entries) {
1325
+ const state = channel?.connected === true ? '✓ Connected'
1326
+ : channel?.connected === false ? '✗ Disconnected' : '? Unknown';
1327
+ const type = typeof channel?.channelType === 'string' ? ` (${channel.channelType})` : '';
1328
+ console.log(` ${name}: ${state}${type}`);
1329
+ }
1330
+ return true;
1331
+ }
1332
+ function localDayStartMs(now = new Date()) {
1333
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate()).getTime();
1334
+ }
1335
+ function statusActivityJson(window) {
1336
+ return {
1337
+ received: window.received,
1338
+ sent: window.sent,
1339
+ completed: window.completed,
1340
+ errors: window.errors,
1341
+ interrupts: window.interrupts,
1342
+ avgResponseMs: window.avgResponseMs,
1343
+ modelCalls: window.modelCalls,
1344
+ tokens: window.tokens,
1345
+ cost: window.cost,
1346
+ };
1347
+ }
1348
+ function readLocalPackageVersion() {
1349
+ try {
1350
+ const pkg = JSON.parse(fs.readFileSync(path.join(getPackageRoot(), 'package.json'), 'utf8'));
1351
+ return typeof pkg.version === 'string' ? pkg.version : null;
1352
+ }
1353
+ catch {
1354
+ return null;
1355
+ }
1356
+ }
1357
+ async function cmdStatusJson() {
1358
+ const p = resolvePaths();
1359
+ const started = Date.now();
1360
+ const instanceStatus = scanInstances();
1361
+ const aliveMains = instanceStatus.mains.filter(instance => instance.alive);
1362
+ let ipcError;
1363
+ const pingPromise = probeDaemon(p.socket, DAEMON_PROBE_TIMEOUT_MS, true).catch(error => {
1364
+ ipcError = error;
1365
+ return null;
1366
+ });
1367
+ const [ping, statusResponse, aidsResponse, agentsResponse, agentStatsResponse] = await Promise.all([
1368
+ pingPromise,
1369
+ ipcQuery(p.socket, { type: 'status' }).catch(() => null),
1370
+ ipcQuery(p.socket, { type: 'aun-aids' }).catch(() => null),
1371
+ ipcQuery(p.socket, { type: 'evolagent.list' }).catch(() => null),
1372
+ ipcQuery(p.socket, { type: 'agent-stats' }).catch(() => null),
1373
+ ]);
1374
+ const now = Date.now();
1375
+ const runningPid = ping?.pid ?? aliveMains[0]?.record.pid ?? null;
1376
+ const mainRecord = runningPid ? aliveMains.find(instance => instance.record.pid === runningPid)?.record : undefined;
1377
+ const statusAvailable = !!statusResponse;
1378
+ const overallStatus = statusAvailable
1379
+ ? 'running'
1380
+ : platform.isWindows && ipcError instanceof IpcTransportError
1381
+ && ['ENOENT', 'ECONNREFUSED'].includes(ipcError.code)
1382
+ ? 'stopped'
1383
+ : 'unknown';
1384
+ const todayStart = localDayStartMs(new Date(now));
1385
+ const lastHourActivity = queryStatusActivity(p.root, now - 3_600_000, now);
1386
+ const todayActivity = statusResponse?.activity?.today ?? queryStatusActivity(p.root, todayStart, now);
1387
+ const daemonLastHourActivity = statusResponse?.activity?.lastHour;
1388
+ const effectiveLastHourActivity = daemonLastHourActivity ?? lastHourActivity;
1389
+ const agents = agentsResponse?.agents ?? [];
1390
+ const agentStats = new Map((agentStatsResponse?.stats ?? []).map((item) => [item.aid, item]));
1391
+ const statusAgents = Array.isArray(statusResponse?.agents) ? statusResponse.agents : null;
1392
+ const agentUsage = statusAgents
1393
+ ? new Map()
1394
+ : queryStatusActivityByAgent(p.root, todayStart, now, agents.map((agent) => String(agent.aid ?? agent.name ?? '')));
1395
+ const diagnostics = [];
1396
+ if (!statusAvailable) {
1397
+ diagnostics.push({ code: 'DAEMON_IPC_UNAVAILABLE', message: ipcError instanceof Error ? ipcError.message : 'daemon status unavailable' });
1398
+ }
1399
+ const agentItems = statusAgents ?? agents.map((agent) => {
1400
+ const aid = String(agent.aid ?? agent.name ?? '');
1401
+ const runtime = agentStats.get(aid);
1402
+ const usage = agentUsage.get(aid) ?? queryStatusActivity(p.root, todayStart, now, aid);
1403
+ return {
1404
+ aid,
1405
+ name: agent.name ?? aid,
1406
+ status: agent.status ?? 'unknown',
1407
+ baseagent: agent.baseagent ?? null,
1408
+ model: agent.model ?? null,
1409
+ effort: agent.effort ?? null,
1410
+ queue: runtime ? { pending: runtime.queued ?? 0, processing: runtime.processing ?? 0 } : null,
1411
+ usage: {
1412
+ calls: usage.modelCalls,
1413
+ tokens: usage.tokens,
1414
+ cost: usage.cost,
1415
+ },
1416
+ };
1417
+ });
1418
+ const channels = Object.entries(statusResponse?.channels ?? {})
1419
+ .filter(([, channel]) => channel?.channelType !== 'aun')
1420
+ .map(([name, channel]) => ({
1421
+ name,
1422
+ type: channel?.channelType ?? name,
1423
+ status: channel?.status ?? (channel?.connected ? 'connected' : 'disconnected'),
1424
+ connected: channel?.connected ?? false,
1425
+ }));
1426
+ const aunItems = (aidsResponse?.aids ?? []).map((aid) => ({
1427
+ aid: aid.aid,
1428
+ channelName: aid.channelName,
1429
+ status: aid.status ?? 'unknown',
1430
+ connected: aid.status === 'connected',
1431
+ queue: aid.queueStatus ? {
1432
+ pending: aid.queueStatus.queued ?? 0,
1433
+ processing: aid.queueStatus.processing ?? 0,
1434
+ } : null,
1435
+ reconnectCount: aid.reconnectCount ?? 0,
1436
+ }));
1437
+ const host = statusResponse?.host ?? readHostInfo(readLocalPackageVersion());
1438
+ const ecwebEntry = readEcwebInstanceEntries(p)[0];
1439
+ const ecweb = ecwebEntry
1440
+ ? { source: 'instance-record', status: platform.isProcessRunning(ecwebEntry.record.pid) ? 'running' : 'unknown', port: ecwebEntry.record.port, url: `http://localhost:${ecwebEntry.record.port}` }
1441
+ : { source: 'instance-record', status: 'stopped', port: null, url: null };
1442
+ const triggerSummary = statusResponse?.triggers
1443
+ ? { source: 'runtime+history', ...statusResponse.triggers }
1444
+ : { source: 'unavailable', enabled: null, today: { calls: null, failures: null } };
1445
+ const build = statusResponse?.build
1446
+ ? { runtimeMode: statusResponse.build.runtimeMode, version: statusResponse.build.version ?? null }
1447
+ : null;
1448
+ const output = {
1449
+ schemaVersion: 1,
1450
+ generatedAt: new Date(now).toISOString(),
1451
+ timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
1452
+ overall: {
1453
+ status: overallStatus,
1454
+ degraded: !statusAvailable || statusResponse?.agentRuntime?.state === 'error',
1455
+ },
1456
+ host,
1457
+ daemon: {
1458
+ pid: runningPid,
1459
+ uptimeMs: ping?.uptime ?? statusResponse?.uptime ?? null,
1460
+ ipc: { reachable: statusAvailable, latencyMs: Date.now() - started },
1461
+ runMode: statusResponse?.runMode ?? mainRecord?.runMode ?? null,
1462
+ build,
1463
+ queue: statusResponse?.queue ?? { pending: null, processing: null },
1464
+ },
1465
+ baseagents: statusResponse?.baseagents ?? { source: 'unavailable', items: [] },
1466
+ activity: {
1467
+ lastHour: statusActivityJson(effectiveLastHourActivity),
1468
+ today: statusActivityJson(todayActivity),
1469
+ },
1470
+ agents: {
1471
+ source: statusAvailable ? 'daemon+stats-db' : 'stats-db',
1472
+ items: agentItems,
1473
+ },
1474
+ aun: { source: aidsResponse ? 'daemon' : 'unavailable', items: aunItems },
1475
+ channels: { source: statusAvailable ? 'daemon' : 'unavailable', items: channels },
1476
+ controlAid: statusResponse?.controlAid ?? null,
1477
+ triggers: triggerSummary,
1478
+ ecweb,
1479
+ diagnostics,
1480
+ };
1481
+ console.log(JSON.stringify(output, null, 2));
1482
+ }
1483
+ export async function cmdStatus(options = {}) {
1484
+ if (options.format === 'json') {
1485
+ await cmdStatusJson();
1486
+ return;
1487
+ }
1295
1488
  const p = resolvePaths();
1296
1489
  const status = scanInstances();
1297
1490
  const aliveMains = status.mains.filter(m => m.alive);
@@ -1300,13 +1493,19 @@ export async function cmdStatus() {
1300
1493
  // query can otherwise wait behind the same IPC/event-loop delay and make the
1301
1494
  // total `ec status` time the sum of several individual timeouts.
1302
1495
  let ipcFailure;
1496
+ let agentsQueryFailure;
1303
1497
  const pingPromise = probeDaemon(p.socket, DAEMON_PROBE_TIMEOUT_MS, true).catch(error => {
1304
1498
  ipcFailure = error;
1305
1499
  return null;
1306
1500
  });
1307
1501
  const statusPromise = ipcQuery(p.socket, { type: 'status' }).catch(() => null);
1308
1502
  const aidsPromise = ipcQuery(p.socket, { type: 'aun-aids' }).catch(() => null);
1309
- const agentsPromise = ipcQuery(p.socket, { type: 'evolagent.list' }).catch(() => null);
1503
+ const agentsPromise = process.env.EVOLCORE_SESSION_ID
1504
+ ? Promise.resolve({ ok: false, error: 'per-agent listing requires a local control token' })
1505
+ : ipcQuery(p.socket, { type: 'evolagent.list' }, 5_000).catch(error => {
1506
+ agentsQueryFailure = error;
1507
+ return null;
1508
+ });
1310
1509
  // Only the ping is needed to decide whether to show the live-daemon
1311
1510
  // sections. The remaining responses continue in parallel while local
1312
1511
  // process/session information is rendered.
@@ -1484,6 +1683,7 @@ export async function cmdStatus() {
1484
1683
  // Channel status. A running daemon is authoritative and avoids reading
1485
1684
  // protected config files from the CLI's potentially sandboxed process.
1486
1685
  let agentsResponse = null;
1686
+ let liveAgentRuntime = null;
1487
1687
  if (pid) {
1488
1688
  console.log('');
1489
1689
  const [statusResponse, aidsResponse, agentResponse] = await Promise.all([
@@ -1492,6 +1692,7 @@ export async function cmdStatus() {
1492
1692
  agentsPromise,
1493
1693
  ]);
1494
1694
  agentsResponse = agentResponse;
1695
+ liveAgentRuntime = statusResponse?.agentRuntime ?? null;
1495
1696
  if (statusResponse) {
1496
1697
  if (statusResponse.build) {
1497
1698
  if (statusResponse.build.runtimeMode === 'source') {
@@ -1511,6 +1712,16 @@ export async function cmdStatus() {
1511
1712
  console.log(` Build ID: runtime=unavailable (daemon 未提供 build identity) dist=${computeBuildId(path.join(getPackageRoot(), 'dist')) ?? '?'}`);
1512
1713
  console.log(' ⚠ 当前 daemon 未提供 build ID;请重启到包含 build identity 的版本后再判断源码行为。');
1513
1714
  }
1715
+ if (!showLiveChannels(statusResponse)) {
1716
+ const config = readStatusDefaultsConfig(p.defaultsConfig);
1717
+ if (config) {
1718
+ console.log('🔌 Channels (live status unavailable; configuration is stale):');
1719
+ showConfigChannels(config);
1720
+ }
1721
+ else {
1722
+ console.log('🔌 Channels: live status unavailable');
1723
+ }
1724
+ }
1514
1725
  // 🔑 AUN AIDs 表格(详细 AUN 实例状态)
1515
1726
  try {
1516
1727
  if (aidsResponse?.ok && aidsResponse.aids?.length > 0) {
@@ -1558,17 +1769,17 @@ export async function cmdStatus() {
1558
1769
  if (pid) {
1559
1770
  try {
1560
1771
  const agentResult = agentsResponse;
1561
- if (agentResult?.ok && agentResult.agents?.length > 0) {
1772
+ if (agentResult?.ok && Array.isArray(agentResult.agents)) {
1562
1773
  const agents = agentResult.agents;
1563
- if (agents.length > 0) {
1564
- console.log('');
1565
- console.log('🤖 EvolAgents:');
1566
- for (const a of agents) {
1567
- const statusIcon = a.status === 'running' ? '●' : a.status === 'error' ? '✗' : a.status === 'disabled' ? '○' : '◌';
1568
- const channels = summarizeChannelFingerprints(a.channels || []);
1569
- const shortName = a.name.replace(/\.agentid\.pub$/, '');
1570
- console.log(` ${statusIcon} ${shortName.padEnd(20)} ${a.status.padEnd(10)} ${channels}`);
1571
- }
1774
+ console.log('');
1775
+ console.log('🤖 EvolAgents (live status):');
1776
+ if (agents.length === 0)
1777
+ console.log(' (no agents loaded)');
1778
+ for (const a of agents) {
1779
+ const statusIcon = a.status === 'running' ? '●' : a.status === 'error' ? '✗' : a.status === 'disabled' ? '○' : '◌';
1780
+ const channels = summarizeChannelFingerprints(a.channels || []);
1781
+ const shortName = a.name.replace(/\.agentid\.pub$/, '');
1782
+ console.log(` ${statusIcon} ${shortName.padEnd(20)} ${a.status.padEnd(10)} ${channels}`);
1572
1783
  }
1573
1784
  }
1574
1785
  else {
@@ -1578,7 +1789,18 @@ export async function cmdStatus() {
1578
1789
  const agents = diskRegistry.list();
1579
1790
  if (agents.length > 0) {
1580
1791
  console.log('');
1581
- console.log('🤖 EvolAgents (live status unavailable; stale configuration):');
1792
+ const detail = typeof agentResult?.error === 'string'
1793
+ ? agentResult.error
1794
+ : agentsQueryFailure instanceof Error ? agentsQueryFailure.message : 'IPC request failed';
1795
+ const running = Number.isFinite(liveAgentRuntime?.runningAgents) ? liveAgentRuntime.runningAgents : null;
1796
+ const runnable = Number.isFinite(liveAgentRuntime?.runnableAgents) ? liveAgentRuntime.runnableAgents : null;
1797
+ if (running !== null && runnable !== null) {
1798
+ console.log(`🤖 EvolAgents (live aggregate: ${running} running / ${runnable} runnable):`);
1799
+ console.log(` Per-agent details unavailable (${detail}); following entries are stale configuration only:`);
1800
+ }
1801
+ else {
1802
+ console.log(`🤖 EvolAgents (live status unavailable: ${detail}; stale configuration):`);
1803
+ }
1582
1804
  for (const a of agents) {
1583
1805
  const diskStatus = a.status === 'disabled' || a.status === 'error' ? a.status : 'unknown';
1584
1806
  const channels = summarizeChannelFingerprints(a.channels || []);
@@ -2965,7 +3187,7 @@ async function printEcwebStatus(p) {
2965
3187
  console.log('');
2966
3188
  if (aliveEntries.length > 0) {
2967
3189
  const visibleEntry = aliveEntries[0];
2968
- console.log(`🔭 ECWeb: 进程存活 (PID: ${visibleEntry.record.pid}) 但 HTTP 未就绪 (端口 ${visibleEntry.record.port}),查看 logs/watch-web.log`);
3190
+ console.log(`🔭 ECWeb: 进程存活 (PID: ${visibleEntry.record.pid}) 但 HTTP 未就绪 (端口 ${visibleEntry.record.port}),查看 logs/ecweb-daemon.log`);
2969
3191
  }
2970
3192
  else if (occupiedPids.size > 0) {
2971
3193
  console.log(`🔭 ECWeb: 端口仍被未登记进程占用 (PID: ${[...occupiedPids].join(', ')}),请先执行 ec restart`);
@@ -2973,6 +3195,9 @@ async function printEcwebStatus(p) {
2973
3195
  else if (entries.length > 0) {
2974
3196
  console.log('🔭 ECWeb: instance 记录存在,但宿主 PID 不可见且 HTTP 未就绪');
2975
3197
  }
3198
+ else if (externalUrl) {
3199
+ console.log('🔭 ECWeb: 已启用但未运行,查看 logs/ecweb-daemon.log');
3200
+ }
2976
3201
  else {
2977
3202
  console.log('🔭 ECWeb: 未运行');
2978
3203
  }
package/dist/cli/index.js CHANGED
@@ -82,7 +82,7 @@ function printLifecycleHelp(command) {
82
82
  start: `用法: ec start [--foreground] [--diagnose|-d] [--debug] [--as-mode dev|install]\n\n --foreground 前台运行,适合 systemd 或容器直接管理\n --diagnose, -d 以诊断模式启动 daemon\n --debug 调试模式:注册调试 IPC,可在宿主终端以任意 session 身份发命令\n --as-mode 覆盖运行模式判定(仅本次启动生效,不写配置):\n install = 按安装模式(校验随包文件完整性,篡改自动恢复)\n dev = 按开发模式(不校验随包文件)`,
83
83
  stop: '用法: ec stop\n\n停止当前 EVOLCORE_HOME 下运行的 EvolCore 服务。',
84
84
  restart: `用法: ec restart [--clear] [--diagnose|-d]\n\n --clear 同时清理跨 HOME 残留的 EvolCore 主进程\n --diagnose, -d 以诊断模式启动新 daemon`,
85
- status: '用法: ec status\n\n查看 daemon、agent、渠道与会话运行状态。',
85
+ status: '用法: ec status [--format json]\n\n查看 daemon、Agent、渠道与队列运行状态。JSON 模式输出结构化状态快照。',
86
86
  logs: `用法: ec logs [选项]\n\n --level <error|warn|info|debug> 最低日志级别\n --module <name> 只显示指定模块\n --raw 原始输出\n --no-color 禁用颜色`,
87
87
  watch: `用法: ec watch [logs|log|aid|msg|diag|web|session]\n\n ec watch 打开监控选择菜单\n ec watch logs 选择日志类型后监控\n ec watch log <类型...> 直接监控指定日志类型\n ec watch aid 监控 AID 连接\n ec watch msg 打开消息 TUI\n ec watch diag 实时诊断消息生命周期(AUN→daemon→任务)\n ec watch web 安装/升级并启动 EC Web\n ec watch session ec watch web 的兼容别名`,
88
88
  'restart-monitor': '用法: ec restart-monitor\n\n运行内 daemon 重启与升级监控器。',
@@ -266,21 +266,33 @@ export async function main(args) {
266
266
  printLifecycleHelp('status');
267
267
  break;
268
268
  }
269
- if (process.env.EVOLCORE_SESSION_ID) {
270
- const sessionId = process.env.EVOLCORE_SESSION_ID;
271
- const delegationToken = readDelegationCredential();
272
- const result = await ipcQuery(resolvePaths().socket, {
273
- type: 'status.auth', sessionId, delegationToken,
274
- });
275
- if (!result?.ok) {
276
- console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
269
+ {
270
+ const statusArgs = strippedArgs.slice(1);
271
+ const formatValue = getArgValue(statusArgs, '--format');
272
+ const validStatusArgs = statusArgs.length === 0
273
+ || (statusArgs.length === 2 && statusArgs[0] === '--format' && formatValue === 'json');
274
+ if (!validStatusArgs) {
275
+ console.error('用法: ec status [--format json]');
277
276
  process.exitCode = 1;
278
277
  break;
279
278
  }
280
- await cmdStatus();
281
- }
282
- else {
283
- await cmdStatus();
279
+ const format = formatValue;
280
+ if (process.env.EVOLCORE_SESSION_ID) {
281
+ const sessionId = process.env.EVOLCORE_SESSION_ID;
282
+ const delegationToken = readDelegationCredential();
283
+ const result = await ipcQuery(resolvePaths().socket, {
284
+ type: 'status.auth', sessionId, delegationToken,
285
+ });
286
+ if (!result?.ok) {
287
+ console.error(`❌ ${result?.error || 'status is not allowed in this task'}`);
288
+ process.exitCode = 1;
289
+ break;
290
+ }
291
+ await cmdStatus({ format });
292
+ }
293
+ else {
294
+ await cmdStatus({ format });
295
+ }
284
296
  }
285
297
  break;
286
298
  case 'logs':
@@ -43,11 +43,12 @@ ec memory note drop <slug>
43
43
  ec memory note list
44
44
 
45
45
  ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<description>' \\
46
- --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<path1>,<path2>'] '<content>'
46
+ --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<path1>,<path2>'] \\
47
+ [--peer <peerKey>] [--reason '<why>'] [--generalized-from <path>] [--promoted-from <path>] '<content>'
47
48
 
48
- ec memory update <path> [--tags '<tag1>,<tag2>'] [--touch]
49
+ ec memory update <path> [--tags '<tag1>,<tag2>'] [--touch] [--reason '<why>']
49
50
 
50
- ec memory archive <path...>
51
+ ec memory archive <path...> [--reason '<why>']
51
52
 
52
53
  ec memory consolidate [--mode <notes|generalize|promote|profile|forget>]
53
54
 
@@ -168,8 +169,9 @@ export function resolveMemoryCommandArgv(argv) {
168
169
  const kind = noteSub ? `note.${noteSub}` : argv[1];
169
170
  const known = {
170
171
  note: ['--slug', '--tags', '--speakers'], 'note.update': ['--slug', '--tags'],
171
- 'note.drop': [], 'note.list': [], create: ['--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer'],
172
- update: ['--tags', '--touch'], archive: [], demote: ['--scope'], consolidate: ['--mode'],
172
+ 'note.drop': [], 'note.list': [],
173
+ create: ['--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer', '--reason', '--generalized-from', '--promoted-from'],
174
+ update: ['--tags', '--touch', '--reason'], archive: ['--reason'], demote: ['--scope'], consolidate: ['--mode'],
173
175
  };
174
176
  // feedback 已移除:旧会话/旧文档可能还在调用,给一句能照做的说明而不是泛化的非法命令。
175
177
  if (kind === 'feedback') {
@@ -209,17 +211,19 @@ export function resolveMemoryCommandArgv(argv) {
209
211
  throw new Error('--peer requires relation scope');
210
212
  return { type, scope, slug: required(flags.get('--slug'), '--slug'), description: required(flags.get('--desc'), '--desc'),
211
213
  content: required(content, 'content'), tags: parseTags(flags.get('--tags')), keywords: parseTags(flags.get('--keywords')),
212
- replaces: parseTags(flags.get('--replaces')), targetPeerKey: flags.get('--peer') };
214
+ replaces: parseTags(flags.get('--replaces')), targetPeerKey: flags.get('--peer'),
215
+ reason: flags.get('--reason'), generalizedFrom: flags.get('--generalized-from'), promotedFrom: flags.get('--promoted-from') };
213
216
  }
214
217
  if (kind === 'update') {
215
218
  if (!flags.has('--tags') && !flags.has('--touch'))
216
219
  throw new Error('No memory changes supplied');
217
- return { type, filePath: required(positional[0], 'path'), tags: parseTags(flags.get('--tags')), touch: flags.has('--touch') };
220
+ return { type, filePath: required(positional[0], 'path'), tags: parseTags(flags.get('--tags')), touch: flags.has('--touch'),
221
+ reason: flags.get('--reason') };
218
222
  }
219
223
  if (kind === 'archive') {
220
224
  if (!positional.length)
221
225
  throw new Error('Memory paths are required');
222
- return { type, filePaths: positional };
226
+ return { type, filePaths: positional, reason: flags.get('--reason') };
223
227
  }
224
228
  if (kind === 'demote') {
225
229
  const scope = flags.get('--scope') ?? 'agent';
@@ -240,9 +244,9 @@ const USAGE = {
240
244
  note: "ec memory note --slug <slug> [--tags '<tag1>,<tag2>'] [--speakers '<aid1>,<aid2>'] '<正文>'",
241
245
  noteUpdate: "ec memory note update <slug> [--slug <新slug>] [--tags '<tag1>,<tag2>'] ['<新正文>']",
242
246
  noteDrop: 'ec memory note drop <slug>',
243
- create: "ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<描述>' --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<路径1>,<路径2>'] [--peer <peerKey>] '<正文>'",
244
- update: "ec memory update <路径> [--tags '<tag1>,<tag2>'] [--touch](至少给一项)",
245
- archive: 'ec memory archive <路径...>',
247
+ create: "ec memory create --scope <relation|agent|owner> --slug <slug> --desc '<描述>' --tags '<tag1>,<tag2>' [--keywords '<kw1>,<kw2>'] [--replaces '<路径1>,<路径2>'] [--peer <peerKey>] [--reason '<为什么>'] [--generalized-from <路径>] [--promoted-from <路径>] '<正文>'",
248
+ update: "ec memory update <路径> [--tags '<tag1>,<tag2>'] [--touch] [--reason '<为什么>'](至少给一项)",
249
+ archive: "ec memory archive <路径...> [--reason '<为什么>']",
246
250
  };
247
251
  /**
248
252
  * 统一失败出口。JSON 模式与 `ec fs` / `ec response` 一致:stdout 输出 `{ ok: false, code, error }`;
@@ -406,6 +410,7 @@ async function cmdMemoryNoteList(args, json) {
406
410
  async function cmdMemoryCreate(args, json) {
407
411
  const { flags, positional } = parseCliArgs(args, [
408
412
  '--scope', '--slug', '--desc', '--tags', '--keywords', '--replaces', '--peer',
413
+ '--reason', '--generalized-from', '--promoted-from',
409
414
  ], json);
410
415
  const content = positional.join(' ').trim();
411
416
  const scope = flags.get('--scope');
@@ -415,6 +420,9 @@ async function cmdMemoryCreate(args, json) {
415
420
  const keywordsRaw = flags.get('--keywords');
416
421
  const replacesRaw = flags.get('--replaces');
417
422
  const targetPeerKey = flags.get('--peer');
423
+ const reason = flags.get('--reason');
424
+ const generalizedFrom = flags.get('--generalized-from');
425
+ const promotedFrom = flags.get('--promoted-from');
418
426
  if (!scope || !['agent', 'relation', 'owner'].includes(scope)) {
419
427
  failWithUsage(json, '--scope 必须是 agent / relation / owner 之一', USAGE.create);
420
428
  }
@@ -429,6 +437,12 @@ async function cmdMemoryCreate(args, json) {
429
437
  if (targetPeerKey !== undefined && scope !== 'relation') {
430
438
  failWithUsage(json, '--peer 只在 --scope relation 时有效', USAGE.create);
431
439
  }
440
+ if (generalizedFrom !== undefined && scope !== 'agent') {
441
+ failWithUsage(json, '--generalized-from 只在 --scope agent 时有效', USAGE.create);
442
+ }
443
+ if (promotedFrom !== undefined && scope !== 'owner') {
444
+ failWithUsage(json, '--promoted-from 只在 --scope owner 时有效', USAGE.create);
445
+ }
432
446
  const { socketPath, ...session } = requireManagedSession('ec memory create', flags, json);
433
447
  const result = await queryDaemon(socketPath, {
434
448
  type: 'memory.create', argv: ['memory', 'create', ...args], ...session, scope, slug, description: desc,
@@ -437,6 +451,9 @@ async function cmdMemoryCreate(args, json) {
437
451
  keywords: parseTags(keywordsRaw),
438
452
  replaces: replacesRaw ? replacesRaw.split(',').map(s => s.trim()).filter(Boolean) : undefined,
439
453
  targetPeerKey: targetPeerKey ?? undefined,
454
+ reason: reason ?? undefined,
455
+ generalizedFrom: generalizedFrom ?? undefined,
456
+ promotedFrom: promotedFrom ?? undefined,
440
457
  }, 15_000);
441
458
  reportResult(result, json, (r) => {
442
459
  if (r.filePath)
@@ -447,10 +464,11 @@ async function cmdMemoryCreate(args, json) {
447
464
  });
448
465
  }
449
466
  async function cmdMemoryConsolidatedUpdate(args, json) {
450
- const { flags, positional } = parseCliArgs(args, ['--tags', '--touch'], json);
467
+ const { flags, positional } = parseCliArgs(args, ['--tags', '--touch', '--reason'], json);
451
468
  const filePath = positional[0];
452
469
  const tagsRaw = flags.get('--tags');
453
470
  const touch = flags.has('--touch');
471
+ const reason = flags.get('--reason');
454
472
  if (!filePath)
455
473
  failWithUsage(json, '必须提供要修改的记忆文件路径', USAGE.update);
456
474
  if (tagsRaw === undefined && !touch)
@@ -458,19 +476,20 @@ async function cmdMemoryConsolidatedUpdate(args, json) {
458
476
  const { socketPath, ...session } = requireManagedSession('ec memory update', flags, json);
459
477
  const result = await queryDaemon(socketPath, {
460
478
  type: 'memory.update', argv: ['memory', 'update', ...args], ...session, filePath,
461
- tags: parseTags(tagsRaw), touch,
479
+ tags: parseTags(tagsRaw), touch, reason: reason ?? undefined,
462
480
  }, 15_000);
463
481
  reportResult(result, json, () => {
464
482
  console.log(`✓ 已更新: ${filePath}`);
465
483
  });
466
484
  }
467
485
  async function cmdMemoryArchive(args, json) {
468
- const { flags, positional } = parseCliArgs(args, [], json);
486
+ const { flags, positional } = parseCliArgs(args, ['--reason'], json);
469
487
  if (positional.length === 0)
470
488
  failWithUsage(json, '必须提供至少一个要归档的文件路径', USAGE.archive);
471
489
  const { socketPath, ...session } = requireManagedSession('ec memory archive', flags, json);
472
490
  const result = await queryDaemon(socketPath, {
473
491
  type: 'memory.archive', argv: ['memory', 'archive', ...args], ...session, filePaths: positional,
492
+ reason: flags.get('--reason') ?? undefined,
474
493
  }, 15_000);
475
494
  reportResult(result, json, (r) => {
476
495
  const archived = r.archived ?? positional;