evolcore 0.0.22 → 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 (75) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +24 -14
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/install-codex-managed-hooks.mjs +373 -95
  6. package/dist/agents/baseagent-availability.js +48 -0
  7. package/dist/agents/baseagent.js +1 -1
  8. package/dist/agents/claude-runner.js +30 -226
  9. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +73 -13
  10. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  11. package/dist/agents/codex/catalog.js +70 -0
  12. package/dist/agents/codex/diagnostics.js +154 -0
  13. package/dist/agents/codex-runner.js +407 -139
  14. package/dist/agents/ecagent-runner.js +10 -31
  15. package/dist/agents/gemini-runner.js +2 -6
  16. package/dist/aun/aid/store.js +2 -0
  17. package/dist/aun/menu-client.js +272 -0
  18. package/dist/aun/msg/group.js +65 -4
  19. package/dist/aun/msg/index.js +1 -1
  20. package/dist/aun/msg/managed-operation.js +51 -4
  21. package/dist/aun/rpc/client.js +8 -1
  22. package/dist/channels/aun.js +31 -1
  23. package/dist/channels/wechat.js +81 -10
  24. package/dist/cli/aun-commands.js +48 -4
  25. package/dist/cli/command-log.js +21 -2
  26. package/dist/cli/daemon-commands.js +142 -45
  27. package/dist/cli/index.js +20 -0
  28. package/dist/cli/init.js +2 -10
  29. package/dist/cli/menu-client.js +95 -0
  30. package/dist/cli/model.js +23 -8
  31. package/dist/cli/task-context.js +12 -2
  32. package/dist/cli/trigger-command.js +9 -1
  33. package/dist/config/schema-migration.js +14 -2
  34. package/dist/core/auth/agent-delegation.js +10 -7
  35. package/dist/core/auth/authorization-audit.js +4 -0
  36. package/dist/core/auth/operation-catalog.js +4 -0
  37. package/dist/core/auth/remote-access-store.js +135 -0
  38. package/dist/core/command/command-handler.js +41 -5
  39. package/dist/core/command/daemon-diagnostics.js +448 -0
  40. package/dist/core/command/menu-catalog.js +3 -0
  41. package/dist/core/command/menu-handler.js +141 -33
  42. package/dist/core/command/menu-protocol.js +119 -0
  43. package/dist/core/command/slash-gate.js +3 -3
  44. package/dist/core/command/slash-handler.js +42 -22
  45. package/dist/core/message/message-queue.js +20 -15
  46. package/dist/core/message/response-engine.js +74 -73
  47. package/dist/core/model/catalog-service.js +308 -0
  48. package/dist/core/model/model-catalog.js +124 -334
  49. package/dist/core/model/model-diagnostics.js +4 -5
  50. package/dist/core/model/model-fallback.js +9 -16
  51. package/dist/core/model/model-permission.js +1 -1
  52. package/dist/core/permission/approval-gateway.js +16 -1
  53. package/dist/core/permission/protected-paths.js +63 -4
  54. package/dist/core/permission/sandbox-runtime.js +4 -2
  55. package/dist/core/permission/tool-policy.js +244 -25
  56. package/dist/core/runtime-lock.js +13 -8
  57. package/dist/index.js +40 -29
  58. package/dist/ipc.js +123 -81
  59. package/dist/trigger/manager.js +3 -1
  60. package/dist/trigger/parser.js +77 -13
  61. package/dist/trigger/patch.js +104 -1
  62. package/dist/utils/ecweb-supervisor.js +11 -2
  63. package/dist/utils/ecweb-utils.js +8 -26
  64. package/dist/utils/error-utils.js +7 -0
  65. package/dist/utils/npm-ops.js +47 -15
  66. package/kits/docs/evolcore/INDEX.md +1 -0
  67. package/kits/docs/evolcore/agent-create.md +13 -0
  68. package/kits/docs/evolcore/group-collaboration.md +7 -2
  69. package/kits/docs/evolcore/menu-client.md +34 -0
  70. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  71. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  72. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  73. package/kits/templates/roles/visitor.json +1 -0
  74. package/kits/templates/system-fragments/bootstrap.md +8 -1
  75. package/package.json +11 -3
package/dist/index.js CHANGED
@@ -45,7 +45,7 @@ import { ResponseEngine } from './core/message/response-engine.js';
45
45
  import { MessageQueue } from './core/message/message-queue.js';
46
46
  import { MessageBridge } from './core/message/message-bridge.js';
47
47
  import { evaluateOutboundContactAdmission } from './core/message/message-admission.js';
48
- import { evaluateEvolMenuVersionGate, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
48
+ import { enforceMenuResponseSize, evaluateMenuClientCompatibility, evolMenuResponseTransportMetadata, MenuRequestDeduper, hasValidMenuId, menuFailure, menuPayloadFingerprint, normalizeMenuResponseTiming, parseMenuControl, validateMenuRequest, withMenuProcessingTime, logMenuRequestCompleted, logMenuRequestReceived } from './core/command/menu-protocol.js';
49
49
  import { readInstalledEvolcoreVersion } from './utils/evolcore-version.js';
50
50
  import { recoverAllRoleMutationsSync } from './core/command/role-menu.js';
51
51
  import { HandoffRuntime } from './core/handoff/runtime.js';
@@ -74,7 +74,7 @@ import { AgentReloadBusyError, AgentReloadCoordinator } from './core/agent-reloa
74
74
  import { buildReloadHooks } from './core/channel-loader.js';
75
75
  import { IpcServer } from './ipc.js';
76
76
  import { logger, setLogLevel } from './utils/logger.js';
77
- import { fetchEcwebPairCode } from './utils/ecweb-utils.js';
77
+ import { remoteAccessService } from './core/auth/remote-access-store.js';
78
78
  import { createEcwebSupervisor } from './utils/ecweb-supervisor.js';
79
79
  import { writeMain, removeAll, isMainWinner, scanInstances } from './utils/instance-registry.js';
80
80
  import { detectDuplicates } from './core/evolagent-registry.js';
@@ -551,6 +551,7 @@ async function runBindBootstrapDaemon(daemonCfg) {
551
551
  })(),
552
552
  }));
553
553
  ipcServer.setControlToken(controlToken);
554
+ ipcServer.setRemoteAccessService(remoteAccessService);
554
555
  ipcServer.setShutdownExecutor(async (reason) => {
555
556
  if (!shutdown) {
556
557
  pendingShutdownReason = reason;
@@ -1354,6 +1355,7 @@ async function main() {
1354
1355
  baseagent: primaryAgent?.baseagent || 'codex',
1355
1356
  projectPath: primaryAgent?.projectPath || process.cwd(),
1356
1357
  };
1358
+ cmdHandler.setDaemonTriggerOwner(daemonTriggerOwner.aid, daemonChannel.channelName);
1357
1359
  const triggerBaseagentsForAgent = (agentAid) => {
1358
1360
  const ownerName = agentRegistry.get(agentAid)?.name
1359
1361
  ?? (agentAid === daemonTriggerOwner.aid ? primaryAgent?.name : undefined);
@@ -2309,6 +2311,7 @@ async function main() {
2309
2311
  return cmdHandler.handleCtl(cmd, sessionId, delegation.grant);
2310
2312
  });
2311
2313
  ipcServer.setDaemonRuntimeEpoch(agentDelegationRegistry.getRuntimeEpoch());
2314
+ cmdHandler.setDaemonMonitorProvider(() => ipcServer.getMonitorSnapshot().snapshot);
2312
2315
  // Register every IPC executor/provider before exposing the endpoint. The
2313
2316
  // function declaration is hoisted, while its invocation remains here so a
2314
2317
  // failed bind still aborts before any AUN connection is attempted.
@@ -2378,10 +2381,10 @@ async function main() {
2378
2381
  catch (e) {
2379
2382
  logger.warn(`控制 AID 首连失败(后台自动重连,不影响 daemon 主流程): ${e?.message || e}`);
2380
2383
  }
2381
- // 控制 AID 接收 owner 指令:
2382
- // 1. /pair — ECWeb 配对码(文本快路径)
2384
+ // 控制 AID 接收 daemon-owner 或有效远程访问 token 授权的指令:
2385
+ // 1. /pair — daemon 远程控制配对码(文本快路径)
2383
2386
  // 2. menu.* JSON — 路由到 cmdHandler.execMenuForControl(进程级 + 全量权限)
2384
- // 发送方身份由 AUN X.509 证书链验证,非 owner 完全静默。
2387
+ // 发送方身份由 AUN X.509 证书链验证;非 Menu 文本仍只接受 daemon-owner。
2385
2388
  controlChannel.onMessage(async (opts) => {
2386
2389
  let activeMenuRequest;
2387
2390
  let menuCompletionLogged = false;
@@ -2389,6 +2392,11 @@ async function main() {
2389
2392
  source: 'control-aun',
2390
2393
  selfAid: daemonCfg.aid,
2391
2394
  messageId: opts.messageId,
2395
+ requesterAid: opts.peerId,
2396
+ authMethod: opts.protectedHeaders?.auth_method === 'remote-access-token' || opts.protectedHeaders?.auth_method === 'ecweb-token'
2397
+ ? opts.protectedHeaders.auth_method
2398
+ : 'daemon-owner',
2399
+ clientName: opts.protectedHeaders?.client_name ?? 'evol',
2392
2400
  };
2393
2401
  try {
2394
2402
  const menuTiming = normalizeMenuResponseTiming(opts);
@@ -2402,7 +2410,7 @@ async function main() {
2402
2410
  }
2403
2411
  const menuResponseContext = () => controlReplyContext(opts, evolMenuResponseTransportMetadata());
2404
2412
  const sendControlMenuResponse = async (response) => {
2405
- const timedResponse = withMenuProcessingTime(response, menuTiming);
2413
+ const timedResponse = withMenuProcessingTime(enforceMenuResponseSize(response, 128 * 1024 - 1024), menuTiming);
2406
2414
  const request = activeMenuRequest ?? {
2407
2415
  type: 'menu.unknown',
2408
2416
  id: response.id,
@@ -2450,7 +2458,21 @@ async function main() {
2450
2458
  menuCompletionLogged = true;
2451
2459
  return;
2452
2460
  }
2453
- if (menuControl.isMenu && !isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
2461
+ const menuClientCompatibilityError = menuControl.isMenu
2462
+ ? evaluateMenuClientCompatibility({ encrypted: opts.encrypted, protectedHeaders: opts.protectedHeaders })
2463
+ : null;
2464
+ if (menuControl.isMenu && menuClientCompatibilityError) {
2465
+ await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, menuClientCompatibilityError));
2466
+ return;
2467
+ }
2468
+ const protectedMenuHeaders = opts.protectedHeaders;
2469
+ const hasRemoteAccessToken = protectedMenuHeaders?.auth_method === 'remote-access-token'
2470
+ || protectedMenuHeaders?.auth_method === 'ecweb-token';
2471
+ if (text.toLowerCase() === '/pair' && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
2472
+ logger.debug(`控制 AID 收到非 owner 配对请求,忽略: from=${opts.peerId}`);
2473
+ return;
2474
+ }
2475
+ if (menuControl.isMenu && !isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
2454
2476
  const response = menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, {
2455
2477
  code: 'NOT_ALLOWED',
2456
2478
  message: formatAuthorizationDenial('Control-channel process operations require DaemonOwner.', 'DAEMON_OWNER_REQUIRED'),
@@ -2459,40 +2481,28 @@ async function main() {
2459
2481
  await sendControlMenuResponse(response);
2460
2482
  return;
2461
2483
  }
2462
- if (!isRoleMenu && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
2484
+ if (!isRoleMenu && !hasRemoteAccessToken && !isProcessLevelOwner(opts.peerId, currentProcessOwners)) {
2463
2485
  logger.debug(`控制 AID 收到非 owner 消息,忽略: from=${opts.peerId}`);
2464
2486
  return;
2465
2487
  }
2466
2488
  if (text.toLowerCase() === '/pair') {
2467
- const port = ecwebService(loadDaemonConfig().services)?.port ?? DEFAULT_ECWEB_PORT;
2468
- const pair = await fetchEcwebPairCode(port, opts.peerId);
2469
- let reply;
2470
- if (pair) {
2471
- const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2472
- reply = `ECWeb 配对码:${pair.code}(约 ${mins} 分钟内有效)\n在浏览器打开 ECWeb 后输入此码登录`;
2473
- }
2474
- else {
2475
- reply = 'ECWeb 未运行或暂不可达。请在主机运行 ec watch web 启动后重试。';
2476
- }
2489
+ const pair = remoteAccessService.getPairingCode(opts.peerId);
2490
+ const mins = Math.max(0, Math.round((pair.expiresAt - Date.now()) / 60000));
2491
+ const reply = `远程控制配对码:${pair.code}(约 ${mins} 分钟内有效)`;
2477
2492
  await controlChannel.sendMessage(opts.channelId, reply, controlReplyContext(opts));
2478
2493
  return;
2479
2494
  }
2480
- // menu.* JSON 路由:owner 已在上方校验,转交 execMenuForControl(fromControlChannel=true)
2495
+ // menu.* JSON 路由:准入来源在上方分流,最终 token/owner 校验由 execMenuForControl 完成。
2481
2496
  if (menuControl.isMenu) {
2482
- const versionError = evaluateEvolMenuVersionGate({
2483
- encrypted: opts.encrypted,
2484
- protectedHeaders: opts.protectedHeaders,
2485
- });
2486
- if (versionError) {
2487
- await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, versionError));
2488
- return;
2489
- }
2490
2497
  const validationError = validateMenuRequest(menuControl.request);
2491
2498
  if (validationError) {
2492
2499
  await sendControlMenuResponse(menuFailure({ id: menuControl.id, ...(menuControl.name?.trim() ? { name: menuControl.name } : {}) }, validationError));
2493
2500
  return;
2494
2501
  }
2495
- const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId));
2502
+ const deduped = await controlMenuDeduper.execute([opts.channelId, opts.peerId, menuControl.id].join('\u001f'), menuPayloadFingerprint(menuControl.raw), () => cmdHandler.execMenuForControl(parsed, opts.peerId, {
2503
+ encrypted: opts.encrypted,
2504
+ protectedHeaders: opts.protectedHeaders,
2505
+ }));
2496
2506
  if ('retry' in deduped) {
2497
2507
  if (deduped.replayed) {
2498
2508
  logger.warn(`[MenuControl] category=request-replay request=${menuControl.id}`
@@ -2512,7 +2522,7 @@ async function main() {
2512
2522
  return;
2513
2523
  }
2514
2524
  // owner 发的其他内容:提示可用指令
2515
- await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取 ECWeb 登录配对码)', controlReplyContext(opts));
2525
+ await controlChannel.sendMessage(opts.channelId, '可用指令:/pair(获取远程控制配对码)', controlReplyContext(opts));
2516
2526
  }
2517
2527
  catch (e) {
2518
2528
  if (activeMenuRequest && !menuCompletionLogged) {
@@ -2688,6 +2698,7 @@ async function main() {
2688
2698
  // M3: direct call (not cast) — wire EvolAgentRegistry into IPC for evolagent.* handlers
2689
2699
  ipcServer.setAgentRegistry(agentRegistry);
2690
2700
  ipcServer.setControlToken(triggerControlToken);
2701
+ ipcServer.setRemoteAccessService(remoteAccessService);
2691
2702
  ipcServer.setBootstrapCompleteExecutor(async (aid, auth) => {
2692
2703
  if (auth?.sessionId) {
2693
2704
  const authorization = await authorizeManagedAgentOperation({
package/dist/ipc.js CHANGED
@@ -17,6 +17,8 @@ import { classifyProcessTree, ProcessTreeSampler, readProcessMetrics } from './u
17
17
  import { readSystemMemoryUsage } from './utils/system-memory.js';
18
18
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
19
19
  import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
20
+ import { remoteAccessService } from './core/auth/remote-access-store.js';
21
+ import { setModelCatalogDaemonOwner } from './core/model/model-catalog.js';
20
22
  const isWindows = process.platform === 'win32';
21
23
  const isNamedPipe = (p) => isWindows && p.toLowerCase().startsWith('\\\\.\\pipe\\');
22
24
  const CPU_SAMPLE_INTERVAL_MS = 1000;
@@ -44,10 +46,15 @@ const LOCAL_CONTROL_COMMANDS = new Set([
44
46
  'evolagent.load',
45
47
  'evolagent.resync',
46
48
  'menu.exec',
49
+ 'model.catalog',
47
50
  'monitor-snapshot',
48
51
  'monitor-subscribe',
49
52
  'monitor-unsubscribe',
50
53
  'shutdown',
54
+ 'remote-auth.pair-code',
55
+ 'remote-auth.issue-local',
56
+ 'remote-auth.redeem',
57
+ 'remote-auth.validate',
51
58
  ]);
52
59
  let ipcQuerySequence = 0;
53
60
  function ipcLogToken(value, fallback = 'unknown') {
@@ -127,6 +134,7 @@ export class IpcServer {
127
134
  wechatContactBindExecutor;
128
135
  controlToken;
129
136
  daemonRuntimeEpoch;
137
+ remoteAccessService = remoteAccessService;
130
138
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
131
139
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
132
140
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -177,6 +185,10 @@ export class IpcServer {
177
185
  setControlToken(token) {
178
186
  this.controlToken = Buffer.from(token);
179
187
  }
188
+ /** Use the daemon-owned remote access service for local consumers such as ECWeb. */
189
+ setRemoteAccessService(service) {
190
+ this.remoteAccessService = service;
191
+ }
180
192
  /** Set the process-lifetime marker used to reject stale managed children. */
181
193
  setDaemonRuntimeEpoch(epoch) {
182
194
  this.daemonRuntimeEpoch = epoch;
@@ -233,6 +245,87 @@ export class IpcServer {
233
245
  setAgentStatsProvider(provider) {
234
246
  this.agentStatsProvider = provider;
235
247
  }
248
+ /** Single current snapshot shared by ECWeb IPC and the daemon Menu view. */
249
+ getMonitorSnapshot(leaseId) {
250
+ if (typeof leaseId === 'string' && leaseId.trim())
251
+ this.refreshProcessTreeMonitor(leaseId);
252
+ else
253
+ this.sampleProcessTree();
254
+ const mem = process.memoryUsage();
255
+ const systemMemory = readSystemMemoryUsage();
256
+ const aids = this.aunAidProvider ? this.aunAidProvider() : [];
257
+ const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
258
+ const statsMap = new Map(aidStats.map(stat => [stat.aid, stat]));
259
+ const agentInfos = this.agentRegistry ? this.agentRegistry.list() : [];
260
+ const agentRuntimeStats = this.agentStatsProvider ? this.agentStatsProvider() : [];
261
+ const agentRuntimeStatsMap = new Map(agentRuntimeStats.map(stat => [stat.aid, stat]));
262
+ const aidStateMap = new Map(aids.map(state => [state.agentName || state.aid, state]));
263
+ const processTree = this.processTreeSnapshot;
264
+ const processTreeCpu = processTree?.processCount ? processTree.cpuPercent : this.cpuPercent;
265
+ const processTreeRss = processTree?.processCount ? processTree.rss : mem.rss;
266
+ const processTreeDetails = processTree ? {
267
+ ...processTree,
268
+ processes: classifyProcessTree(processTree.processes, process.pid, this.associatedProcessKinds),
269
+ } : {
270
+ rootPid: process.pid,
271
+ rootPids: [process.pid],
272
+ processCount: 1,
273
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
274
+ rss: mem.rss,
275
+ processes: [{
276
+ pid: process.pid,
277
+ ppid: process.ppid,
278
+ name: 'evolcore',
279
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
280
+ rss: mem.rss,
281
+ kind: 'daemon',
282
+ }],
283
+ };
284
+ return {
285
+ ok: true,
286
+ snapshot: {
287
+ ts: Date.now(),
288
+ uptimeMs: Math.round(process.uptime() * 1000),
289
+ cpuCount: os.cpus().length,
290
+ memory: {
291
+ rss: processTreeRss,
292
+ heapUsed: mem.heapUsed,
293
+ heapTotal: mem.heapTotal,
294
+ external: mem.external,
295
+ },
296
+ cpuPercent: processTreeCpu,
297
+ mainProcess: {
298
+ pid: process.pid,
299
+ cpuPercent: Math.round(this.cpuPercent * 10) / 10,
300
+ memory: mem,
301
+ },
302
+ processTree: processTreeDetails,
303
+ sampling: {
304
+ cpuIntervalMs: CPU_SAMPLE_INTERVAL_MS,
305
+ processTreeIntervalMs: PROCESS_TREE_SAMPLE_INTERVAL_MS,
306
+ eventLoopLagMs: this.eventLoopLagMs,
307
+ maxEventLoopLagMs: this.maxEventLoopLagMs,
308
+ processTree: { ...this.processTreeSampling },
309
+ },
310
+ system: {
311
+ ...systemMemory,
312
+ cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
313
+ loadAvg: os.loadavg(),
314
+ },
315
+ stats: this.statsProvider ? this.statsProvider() : null,
316
+ agentSummary: summarizeMonitorAgents(agentInfos, aids),
317
+ agents: agentInfos.map(agent => ({
318
+ aid: agent.aid,
319
+ agentName: agent.name,
320
+ status: agent.status,
321
+ connectionStatus: aidStateMap.get(agent.aid)?.status ?? (agent.status === 'disabled' ? 'disabled' : 'unavailable'),
322
+ online: agent.status !== 'disabled' && aidStateMap.get(agent.aid)?.status === 'connected',
323
+ stats: statsMap.get(agent.aid) ?? null,
324
+ runtimeStats: agentRuntimeStatsMap.get(agent.aid) ?? null,
325
+ })),
326
+ },
327
+ };
328
+ }
236
329
  /** Inject queue snapshot provider for queue-snapshot IPC handler (query mode) */
237
330
  setQueueSnapshotProvider(provider) {
238
331
  this.queueSnapshotProvider = provider;
@@ -623,6 +716,7 @@ export class IpcServer {
623
716
  return roots;
624
717
  }
625
718
  start() {
719
+ setModelCatalogDaemonOwner(true);
626
720
  if (this.server)
627
721
  return Promise.resolve();
628
722
  // Remove stale socket file (Unix only — named pipes auto-cleanup on process exit)
@@ -685,6 +779,7 @@ export class IpcServer {
685
779
  });
686
780
  }
687
781
  stop() {
782
+ setModelCatalogDaemonOwner(false);
688
783
  if (this.server) {
689
784
  this.server.close();
690
785
  this.server = null;
@@ -712,6 +807,19 @@ export class IpcServer {
712
807
  };
713
808
  }
714
809
  switch (cmd.type) {
810
+ case 'model.catalog': {
811
+ if ((cmd.self !== undefined && typeof cmd.self !== 'string')
812
+ || (cmd.baseagent !== undefined && !['claude', 'codex', 'ecagent', 'gemini'].includes(cmd.baseagent))) {
813
+ return { ok: false, error: 'Invalid model catalog scope' };
814
+ }
815
+ try {
816
+ const { getLocalCatalog } = await import('./core/model/model-catalog.js');
817
+ return { ok: true, snapshot: await getLocalCatalog(cmd.self, cmd.baseagent, cmd.refresh === true) };
818
+ }
819
+ catch {
820
+ return { ok: false, error: 'Unable to resolve model catalog configuration' };
821
+ }
822
+ }
715
823
  case 'status':
716
824
  return this.getStatus();
717
825
  case 'codex-pre-tool-use': {
@@ -769,6 +877,19 @@ export class IpcServer {
769
877
  ttlMs: cmd.ttlMs,
770
878
  });
771
879
  }
880
+ case 'remote-auth.pair-code': {
881
+ const pair = this.remoteAccessService.getPairingCode(typeof cmd.authorizedBy === 'string' ? cmd.authorizedBy : undefined);
882
+ return { ok: true, ...pair };
883
+ }
884
+ case 'remote-auth.issue-local': {
885
+ return this.remoteAccessService.issueLocalToken(cmd.label);
886
+ }
887
+ case 'remote-auth.redeem': {
888
+ return this.remoteAccessService.redeemPairingCode(cmd.code, cmd.label, cmd.authorizedBy);
889
+ }
890
+ case 'remote-auth.validate': {
891
+ return { ok: true, ...this.remoteAccessService.validateToken(cmd.token) };
892
+ }
772
893
  case 'bind.status': {
773
894
  if (!this.bindExecutor)
774
895
  return { ok: false, error: 'bind executor not configured' };
@@ -1385,87 +1506,8 @@ export class IpcServer {
1385
1506
  return { ok: false, error: e?.message ?? String(e) };
1386
1507
  }
1387
1508
  }
1388
- case 'monitor-snapshot': {
1389
- this.refreshProcessTreeMonitor(cmd.leaseId);
1390
- // watch web Monitor 页用:进程级 + 系统级运行指标 + 全局 stats + per-agent 汇总。
1391
- const mem = process.memoryUsage();
1392
- const systemMemory = readSystemMemoryUsage();
1393
- const aids = this.aunAidProvider ? this.aunAidProvider() : [];
1394
- const aidStats = this.aunAidStatsProvider ? this.aunAidStatsProvider() : [];
1395
- const statsMap = new Map(aidStats.map((s) => [s.aid, s]));
1396
- const agentInfos = this.agentRegistry ? this.agentRegistry.list() : [];
1397
- const agentRuntimeStats = this.agentStatsProvider ? this.agentStatsProvider() : [];
1398
- const agentRuntimeStatsMap = new Map(agentRuntimeStats.map((s) => [s.aid, s]));
1399
- const aidStateMap = new Map(aids.map((state) => [state.agentName || state.aid, state]));
1400
- const agentSummary = summarizeMonitorAgents(agentInfos, aids);
1401
- const processTree = this.processTreeSnapshot;
1402
- const processTreeCpu = processTree?.processCount ? processTree.cpuPercent : this.cpuPercent;
1403
- const processTreeRss = processTree?.processCount ? processTree.rss : mem.rss;
1404
- const processTreeDetails = processTree ? {
1405
- ...processTree,
1406
- processes: classifyProcessTree(processTree.processes, process.pid, this.associatedProcessKinds),
1407
- } : {
1408
- rootPid: process.pid,
1409
- rootPids: [process.pid],
1410
- processCount: 1,
1411
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1412
- rss: mem.rss,
1413
- processes: [{
1414
- pid: process.pid,
1415
- ppid: process.ppid,
1416
- name: 'evolcore',
1417
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1418
- rss: mem.rss,
1419
- kind: 'daemon',
1420
- }],
1421
- };
1422
- return {
1423
- ok: true,
1424
- snapshot: {
1425
- ts: Date.now(),
1426
- uptimeMs: Math.round(process.uptime() * 1000),
1427
- cpuCount: os.cpus().length,
1428
- // 进程级:daemon + 子进程 + 登记在册的关联进程树
1429
- memory: {
1430
- rss: processTreeRss,
1431
- heapUsed: mem.heapUsed,
1432
- heapTotal: mem.heapTotal,
1433
- external: mem.external,
1434
- },
1435
- cpuPercent: processTreeCpu,
1436
- mainProcess: {
1437
- pid: process.pid,
1438
- cpuPercent: Math.round(this.cpuPercent * 10) / 10,
1439
- memory: mem,
1440
- },
1441
- processTree: processTreeDetails,
1442
- sampling: {
1443
- cpuIntervalMs: CPU_SAMPLE_INTERVAL_MS,
1444
- processTreeIntervalMs: PROCESS_TREE_SAMPLE_INTERVAL_MS,
1445
- eventLoopLagMs: this.eventLoopLagMs,
1446
- maxEventLoopLagMs: this.maxEventLoopLagMs,
1447
- processTree: { ...this.processTreeSampling },
1448
- },
1449
- // 系统级:整机
1450
- system: {
1451
- ...systemMemory,
1452
- cpuPercent: Math.round(this.sysCpuPercent * 10) / 10,
1453
- loadAvg: os.loadavg(), // [1m, 5m, 15m](Windows 恒 0)
1454
- },
1455
- stats: this.statsProvider ? this.statsProvider() : null,
1456
- agentSummary,
1457
- agents: agentInfos.map((a) => ({
1458
- aid: a.aid,
1459
- agentName: a.name,
1460
- status: a.status,
1461
- connectionStatus: aidStateMap.get(a.aid)?.status ?? (a.status === 'disabled' ? 'disabled' : 'unavailable'),
1462
- online: a.status !== 'disabled' && aidStateMap.get(a.aid)?.status === 'connected',
1463
- stats: statsMap.get(a.aid) ?? null,
1464
- runtimeStats: agentRuntimeStatsMap.get(a.aid) ?? null,
1465
- })),
1466
- },
1467
- };
1468
- }
1509
+ case 'monitor-snapshot':
1510
+ return this.getMonitorSnapshot(cmd.leaseId);
1469
1511
  case 'monitor-subscribe': {
1470
1512
  const leaseId = typeof cmd.leaseId === 'string' ? cmd.leaseId.trim() : '';
1471
1513
  if (!leaseId || leaseId.length > 128)
@@ -92,7 +92,9 @@ export class TriggerDefinitionManager {
92
92
  assertExplicitAunDeliveryRoutes(updated);
93
93
  this.assertAgent(updated);
94
94
  if (existing.execution.baseagent !== updated.execution.baseagent) {
95
- throw new Error('execution.baseagent is immutable; create a new Trigger to use another baseagent');
95
+ if (existing.execution.type !== 'trigger_session' || updated.execution.type !== 'trigger_session') {
96
+ throw new Error('execution.baseagent is only mutable between trigger_session Triggers');
97
+ }
96
98
  }
97
99
  updated.createdAt = existing.createdAt;
98
100
  updated.updatedAt = Date.now();
@@ -1,8 +1,11 @@
1
1
  import { CronExpressionParser } from 'cron-parser';
2
2
  const TRIGGER_UPDATE_FLAGS = new Set([
3
3
  'once', 'delay', 'at', 'cron', 'every', 'event', 'tz',
4
- 'prompt', 'name',
5
- 'script-file',
4
+ 'prompt', 'name', 'description',
5
+ 'script-file', 'script-runtime', 'script-args', 'script-timeout',
6
+ 'feedback', 'target-channel', 'target-channel-id', 'target-chat-type', 'target-session', 'target-thread-id',
7
+ 'trigger-thread', 'baseagent',
8
+ 'on-error', 'noop-sentinel', 'retry-attempts', 'retry-backoff',
6
9
  'model', 'effort', 'permission',
7
10
  'max-runs', 'max-duration', 'concurrency', 'missed-policy',
8
11
  ]);
@@ -396,18 +399,22 @@ function commonParsed(flags, opts = {}) {
396
399
  if (!opts.update && !execution.value) {
397
400
  return { ok: false, error: '创建 trigger 必须显式指定 --exec script|trigger-session|target-session' };
398
401
  }
402
+ // update 场景下执行类型未知(不重命名 --exec),execution-type 相关校验
403
+ // 全部交给 patch/validation 按现有定义执行。
399
404
  const inferredExecution = execution.value;
400
- if (baseagent.value && inferredExecution !== 'trigger_session') {
401
- return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
402
- }
403
- if (inferredExecution === 'script') {
404
- if (!scriptPath && !opts.update)
405
- return { ok: false, error: '--exec script 需要 --script-path' };
406
- if (scriptPath && !scriptRuntime && !opts.update)
407
- return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
408
- }
409
- else if (!prompt && !opts.update) {
410
- return { ok: false, error: '--prompt 为必填项' };
405
+ if (!opts.update) {
406
+ if (baseagent.value && inferredExecution !== 'trigger_session') {
407
+ return { ok: false, error: '--baseagent 仅适用于 --exec trigger-session' };
408
+ }
409
+ if (inferredExecution === 'script') {
410
+ if (!scriptPath)
411
+ return { ok: false, error: '--exec script 需要 --script-path' };
412
+ if (scriptPath && !scriptRuntime)
413
+ return { ok: false, error: '--script-path 必须配合 --script-runtime 使用(node|python|bash)' };
414
+ }
415
+ else if (!prompt) {
416
+ return { ok: false, error: '--prompt 为必填项' };
417
+ }
411
418
  }
412
419
  const targetSessionValue = targetSession.value ?? (targetThreadId ? 'thread' : 'main');
413
420
  const feedbackStrategy = feedback.value ?? (hasTargetField ? 'target' : 'origin');
@@ -645,6 +652,63 @@ function parseTriggerUpdateFlags(nameOrId, flags, opts = {}) {
645
652
  }
646
653
  update.missedPolicy = normalized;
647
654
  }
655
+ const description = optionalFlagString(flags, 'description');
656
+ if ('error' in description)
657
+ return { ok: false, error: description.error };
658
+ if (description.value !== undefined)
659
+ update.description = description.value || null;
660
+ if (flags.has('feedback') && value.feedbackStrategy !== undefined)
661
+ update.feedback = value.feedbackStrategy;
662
+ if (value.targetChannel !== undefined)
663
+ update.targetChannel = value.targetChannel;
664
+ if (value.targetChannelId !== undefined)
665
+ update.targetChannelId = value.targetChannelId;
666
+ if (value.targetChatType !== undefined)
667
+ update.targetChatType = value.targetChatType;
668
+ if (value.targetSession !== undefined && flags.has('target-session'))
669
+ update.targetSession = value.targetSession;
670
+ if (value.targetThreadId !== undefined)
671
+ update.targetThreadId = value.targetThreadId;
672
+ if (value.triggerThread !== undefined)
673
+ update.triggerThread = value.triggerThread;
674
+ if (value.baseagent !== undefined)
675
+ update.baseagent = value.baseagent;
676
+ const onError = optionalFlagString(flags, 'on-error');
677
+ if ('error' in onError)
678
+ return { ok: false, error: onError.error };
679
+ if (onError.value !== undefined) {
680
+ if (onError.value !== 'fail' && onError.value !== 'retry') {
681
+ return { ok: false, error: '--on-error 只接受 fail 或 retry' };
682
+ }
683
+ update.onError = onError.value;
684
+ }
685
+ const noopSentinel = optionalFlagString(flags, 'noop-sentinel');
686
+ if ('error' in noopSentinel)
687
+ return { ok: false, error: noopSentinel.error };
688
+ if (noopSentinel.value !== undefined)
689
+ update.noopSentinel = noopSentinel.value || null;
690
+ const retryAttempts = optionalFlagString(flags, 'retry-attempts');
691
+ if ('error' in retryAttempts)
692
+ return { ok: false, error: retryAttempts.error };
693
+ if (retryAttempts.value !== undefined) {
694
+ if (!/^\d+$/.test(retryAttempts.value))
695
+ return { ok: false, error: '--retry-attempts 必须是非负整数' };
696
+ update.retryAttempts = Number(retryAttempts.value);
697
+ }
698
+ const retryBackoff = optionalFlagString(flags, 'retry-backoff');
699
+ if ('error' in retryBackoff)
700
+ return { ok: false, error: retryBackoff.error };
701
+ if (retryBackoff.value !== undefined) {
702
+ if (!/^\d+$/.test(retryBackoff.value))
703
+ return { ok: false, error: '--retry-backoff 必须是毫秒正整数' };
704
+ update.retryBackoff = Number(retryBackoff.value);
705
+ }
706
+ if (value.scriptRuntime !== undefined)
707
+ update.scriptRuntime = value.scriptRuntime;
708
+ if (value.scriptArgs !== undefined)
709
+ update.scriptArgs = value.scriptArgs;
710
+ if (value.scriptTimeoutMs !== undefined)
711
+ update.scriptTimeout = value.scriptTimeoutMs;
648
712
  if (Object.keys(update).length === 0)
649
713
  return { ok: false, error: '没有可更新的 trigger 参数' };
650
714
  return { ok: true, nameOrId, value: update };