evolcore 0.0.10 → 0.0.12

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 (131) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/README.md +3 -3
  3. package/bin/codex-managed-hook.mjs +80 -0
  4. package/dist/agents/baseagent.js +4 -0
  5. package/dist/agents/claude-runner.js +158 -44
  6. package/dist/agents/codex-app-server-client.js +153 -23
  7. package/dist/agents/codex-runner.js +331 -43
  8. package/dist/agents/ecagent-runner.js +53 -6
  9. package/dist/agents/gemini-runner.js +1 -0
  10. package/dist/agents/request-identity.js +55 -0
  11. package/dist/aun/aid/client.js +11 -21
  12. package/dist/aun/aid/managed-operation.js +107 -0
  13. package/dist/aun/msg/group.js +81 -12
  14. package/dist/aun/msg/managed-operation.js +804 -0
  15. package/dist/aun/msg/mention-schema.js +20 -0
  16. package/dist/aun/msg/p2p.js +7 -0
  17. package/dist/aun/outbox.js +28 -31
  18. package/dist/channels/aun.js +418 -235
  19. package/dist/channels/daemon.js +13 -0
  20. package/dist/cli/agent-command.js +72 -17
  21. package/dist/cli/agent.js +249 -22
  22. package/dist/cli/aun-commands.js +270 -69
  23. package/dist/cli/bench.js +12 -3
  24. package/dist/cli/daemon-commands.js +52 -33
  25. package/dist/cli/fs-command.js +60 -0
  26. package/dist/cli/handoff-command.js +3 -0
  27. package/dist/cli/index.js +106 -63
  28. package/dist/cli/init-cancel.js +208 -0
  29. package/dist/cli/init-channel.js +343 -195
  30. package/dist/cli/init.js +21 -9
  31. package/dist/cli/managed-command-guard.js +41 -0
  32. package/dist/cli/model.js +11 -4
  33. package/dist/cli/queue-command.js +11 -1
  34. package/dist/cli/response.js +71 -0
  35. package/dist/cli/restart-monitor.js +4 -18
  36. package/dist/cli/trigger-command.js +4 -0
  37. package/dist/config/builtin-roles.js +5 -0
  38. package/dist/config/contact-book-store.js +11 -3
  39. package/dist/config/contact-request-service.js +161 -15
  40. package/dist/config/gateway-config.js +26 -10
  41. package/dist/config/mention-mode.js +8 -24
  42. package/dist/core/agent-reload-coordinator.js +53 -0
  43. package/dist/core/auth/agent-delegation.js +0 -1
  44. package/dist/core/auth/operation-authorizer.js +62 -154
  45. package/dist/core/auth/operation-catalog.js +264 -22
  46. package/dist/core/bootstrap-messages.js +68 -0
  47. package/dist/core/bootstrap-service.js +144 -10
  48. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  49. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  50. package/dist/core/capability/skill-discovery.js +55 -0
  51. package/dist/core/channel-loader.js +24 -7
  52. package/dist/core/command/agent-control.js +14 -11
  53. package/dist/core/command/cli-intent-parser.js +9 -0
  54. package/dist/core/command/command-handler.js +467 -6
  55. package/dist/core/command/connect-menu.js +15 -0
  56. package/dist/core/command/group-menu.js +4 -4
  57. package/dist/core/command/menu-catalog.js +366 -0
  58. package/dist/core/command/menu-handler.js +213 -110
  59. package/dist/core/command/menu-protocol.js +71 -0
  60. package/dist/core/command/slash-gate.js +5 -2
  61. package/dist/core/command/slash-handler.js +45 -25
  62. package/dist/core/daemon-file-cache.js +2 -1
  63. package/dist/core/data-migration.js +9 -2
  64. package/dist/core/evolagent-registry.js +125 -35
  65. package/dist/core/evolagent.js +12 -5
  66. package/dist/core/handoff/dispatcher.js +5 -2
  67. package/dist/core/handoff/runtime.js +145 -21
  68. package/dist/core/handoff/store.js +24 -0
  69. package/dist/core/handoff/types.js +1 -0
  70. package/dist/core/inference/text-inference.js +38 -4
  71. package/dist/core/message/logical-queue-bridge.js +1 -0
  72. package/dist/core/message/mention-schema.js +126 -0
  73. package/dist/core/message/message-bridge.js +237 -79
  74. package/dist/core/message/message-log.js +23 -0
  75. package/dist/core/message/message-queue.js +20 -4
  76. package/dist/core/message/response-engine.js +54 -36
  77. package/dist/core/model/model-catalog.js +143 -24
  78. package/dist/core/model/model-diagnostics.js +28 -10
  79. package/dist/core/permission/ec-command-parser.js +156 -10
  80. package/dist/core/permission/index.js +1 -0
  81. package/dist/core/permission/readonly-shell-query.js +532 -0
  82. package/dist/core/permission/sandbox-runtime.js +22 -1
  83. package/dist/core/permission/shell-environment.js +46 -0
  84. package/dist/core/permission/tool-policy.js +292 -111
  85. package/dist/core/protected-paths.js +17 -9
  86. package/dist/core/runner-reload-transaction.js +57 -0
  87. package/dist/core/session/session-manager.js +21 -2
  88. package/dist/eck/manifest-engine.js +39 -13
  89. package/dist/index.js +644 -180
  90. package/dist/ipc.js +246 -19
  91. package/dist/response-system/coordinator.js +6 -4
  92. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  93. package/dist/response-system/modes/single-session/index.js +14 -1
  94. package/dist/response-system/selector.js +6 -5
  95. package/dist/trigger/anomaly-store.js +4 -0
  96. package/dist/trigger/script-executor.js +1 -0
  97. package/dist/utils/aid-bind.js +3 -8
  98. package/dist/utils/codex-app-server-registry.js +90 -0
  99. package/dist/utils/codex-cli.js +5 -1
  100. package/dist/utils/cross-platform.js +15 -0
  101. package/dist/utils/log-writer.js +6 -10
  102. package/dist/utils/logger.js +5 -5
  103. package/dist/utils/npm-ops.js +5 -12
  104. package/dist/utils/tool-summary.js +11 -0
  105. package/kits/docs/channels/aun.md +1 -1
  106. package/kits/docs/context-assembly.md +2 -2
  107. package/kits/docs/evolcore/agent.md +11 -6
  108. package/kits/docs/evolcore/aid.md +14 -0
  109. package/kits/docs/evolcore/msg.md +13 -0
  110. package/kits/docs/prompt-loading-architecture.md +1 -2
  111. package/kits/docs/venues/group.md +1 -1
  112. package/kits/eck_manifest.json +0 -12
  113. package/kits/rules/01-overview.md +9 -0
  114. package/kits/rules/04-relation.md +1 -1
  115. package/kits/rules/05-venue.md +2 -2
  116. package/kits/schemas/agent-config.schema.3.json +1 -1
  117. package/kits/schemas/agent-config.schema.4.json +1 -1
  118. package/kits/schemas/relation-config.schema.2.json +1 -1
  119. package/kits/schemas/role-config.schema.1.json +1 -1
  120. package/kits/schemas/single-session.schema.1.json +3 -3
  121. package/kits/schemas/single-session.schema.2.json +3 -3
  122. package/kits/templates/roles/admin.json +27 -0
  123. package/kits/templates/roles/member.json +44 -0
  124. package/kits/templates/roles/visitor.json +40 -2
  125. package/kits/templates/system-fragments/bootstrap.md +12 -6
  126. package/kits/templates/system-fragments/channel.md +6 -0
  127. package/kits/templates/system-fragments/session.md +2 -0
  128. package/package.json +3 -2
  129. package/skills/eclink/SKILL.md +15 -3
  130. package/skills/eclink/agents/openai.yaml +3 -3
  131. package/dist/core/command/evol-menu-version-gate.js +0 -39
package/dist/ipc.js CHANGED
@@ -1,17 +1,37 @@
1
1
  import net from 'net';
2
2
  import fs from 'fs';
3
+ import crypto from 'crypto';
3
4
  import os from 'os';
4
5
  import path from 'path';
5
6
  import { logger } from './utils/logger.js';
6
7
  import { fileCache } from './core/daemon-file-cache.js';
7
8
  import { HANDOFF_QUERY_MAX_LIMIT, HANDOFF_STATES } from './core/handoff/types.js';
9
+ import { AgentReloadBusyError } from './core/agent-reload-coordinator.js';
8
10
  import { resolvePaths } from './paths.js';
9
11
  import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-introspect.js';
10
12
  import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
11
13
  import { readSystemMemoryUsage } from './utils/system-memory.js';
12
14
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
15
+ import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
13
16
  const isWindows = process.platform === 'win32';
14
17
  const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
18
+ const LOCAL_CONTROL_COMMANDS = new Set([
19
+ 'bind.begin',
20
+ 'bind.status',
21
+ 'bind.cancel',
22
+ 'dingtalk.contact-bind.register',
23
+ 'feishu.contact-bind.register',
24
+ 'qqbot.contact-bind.register',
25
+ 'wecom.contact-bind.register',
26
+ 'wechat.contact-bind.register',
27
+ 'queue-snapshot',
28
+ 'aun-aid-stats-record-outbound',
29
+ 'evolagent.reload',
30
+ 'evolagent.load',
31
+ 'evolagent.resync',
32
+ 'menu.exec',
33
+ 'monitor-snapshot',
34
+ ]);
15
35
  export function summarizeMonitorAgents(agents, aids) {
16
36
  const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
17
37
  const enabledAgents = agents.filter(agent => agent.status !== 'disabled');
@@ -38,8 +58,14 @@ export class IpcServer {
38
58
  agentStatsProvider;
39
59
  queueSnapshotProvider;
40
60
  queueActionExecutor;
61
+ queueOperationExecutor;
62
+ statusAuthorizationExecutor;
63
+ storageAuthorizationExecutor;
41
64
  triggerExecutor;
65
+ bootstrapCompleteExecutor;
66
+ agentReloadExecutor;
42
67
  taskRuntimeContextProvider;
68
+ codexPreToolUseExecutor;
43
69
  aunMsgSender;
44
70
  handoffReturnExecutor;
45
71
  handoffStatusExecutor;
@@ -48,11 +74,16 @@ export class IpcServer {
48
74
  bindExecutor;
49
75
  configOperationExecutor;
50
76
  contactOperationExecutor;
77
+ aidOperationExecutor;
78
+ groupOperationExecutor;
79
+ msgOperationExecutor;
80
+ fsOperationExecutor;
51
81
  dingtalkContactBindExecutor;
52
82
  feishuContactBindExecutor;
53
83
  qqbotContactBindExecutor;
54
84
  wecomContactBindExecutor;
55
85
  wechatContactBindExecutor;
86
+ controlToken;
56
87
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
57
88
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
58
89
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -75,6 +106,10 @@ export class IpcServer {
75
106
  setAgentRegistry(registry) {
76
107
  this.agentRegistry = registry;
77
108
  }
109
+ /** Authenticate local control-plane callers that cannot use task delegation. */
110
+ setControlToken(token) {
111
+ this.controlToken = Buffer.from(token);
112
+ }
78
113
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
79
114
  setMenuExecutor(executor) {
80
115
  this.menuExecutor = executor;
@@ -85,6 +120,21 @@ export class IpcServer {
85
120
  setContactOperationExecutor(executor) {
86
121
  this.contactOperationExecutor = executor;
87
122
  }
123
+ setAidOperationExecutor(executor) {
124
+ this.aidOperationExecutor = executor;
125
+ }
126
+ setGroupOperationExecutor(executor) {
127
+ this.groupOperationExecutor = executor;
128
+ }
129
+ setMsgOperationExecutor(executor) {
130
+ this.msgOperationExecutor = executor;
131
+ }
132
+ setFsOperationExecutor(executor) {
133
+ this.fsOperationExecutor = executor;
134
+ }
135
+ setAgentReloadExecutor(executor) {
136
+ this.agentReloadExecutor = executor;
137
+ }
88
138
  /** Inject AUN AID state aggregator for aun-aids IPC handler */
89
139
  setAunAidProvider(provider) {
90
140
  this.aunAidProvider = provider;
@@ -113,14 +163,30 @@ export class IpcServer {
113
163
  setQueueActionExecutor(executor) {
114
164
  this.queueActionExecutor = executor;
115
165
  }
166
+ setQueueOperationExecutor(executor) {
167
+ this.queueOperationExecutor = executor;
168
+ }
169
+ setStatusAuthorizationExecutor(executor) {
170
+ this.statusAuthorizationExecutor = executor;
171
+ }
172
+ setStorageAuthorizationExecutor(executor) {
173
+ this.storageAuthorizationExecutor = executor;
174
+ }
116
175
  /** Inject daemon-level trigger executor for ec trigger. */
117
176
  setTriggerExecutor(executor) {
118
177
  this.triggerExecutor = executor;
119
178
  }
179
+ /** Complete an agent bootstrap in the daemon so disk and runtime state change together. */
180
+ setBootstrapCompleteExecutor(executor) {
181
+ this.bootstrapCompleteExecutor = executor;
182
+ }
120
183
  /** Inject active task runtime-context provider for in-task CLI routing. */
121
184
  setTaskRuntimeContextProvider(provider) {
122
185
  this.taskRuntimeContextProvider = provider;
123
186
  }
187
+ setCodexPreToolUseExecutor(executor) {
188
+ this.codexPreToolUseExecutor = executor;
189
+ }
124
190
  /** Inject daemon-backed AUN private message sender for in-task `ec msg send`. */
125
191
  setAunMsgSender(sender) {
126
192
  this.aunMsgSender = sender;
@@ -313,9 +379,31 @@ export class IpcServer {
313
379
  }
314
380
  }
315
381
  async handleCommand(cmd) {
382
+ if (LOCAL_CONTROL_COMMANDS.has(cmd.type) && !this.hasValidControlToken(cmd.controlToken)) {
383
+ return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
384
+ }
316
385
  switch (cmd.type) {
317
386
  case 'status':
318
387
  return this.getStatus();
388
+ case 'codex-pre-tool-use': {
389
+ if (!this.codexPreToolUseExecutor) {
390
+ return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore Codex policy executor is unavailable' };
391
+ }
392
+ if (typeof cmd.threadId !== 'string' || !cmd.threadId
393
+ || typeof cmd.toolName !== 'string' || !cmd.toolName) {
394
+ return { ok: false, applicable: true, decision: 'deny', reason: 'invalid Codex hook request' };
395
+ }
396
+ try {
397
+ return await this.codexPreToolUseExecutor({
398
+ threadId: cmd.threadId,
399
+ toolName: cmd.toolName,
400
+ toolInput: cmd.toolInput,
401
+ });
402
+ }
403
+ catch (error) {
404
+ return { ok: false, applicable: true, decision: 'deny', reason: `EvolCore policy unavailable: ${error instanceof Error ? error.message : String(error)}` };
405
+ }
406
+ }
319
407
  case 'ping':
320
408
  return { pong: true, pid: process.pid, protocolVersion: 1 };
321
409
  case 'bind.begin': {
@@ -325,7 +413,6 @@ export class IpcServer {
325
413
  bindType: cmd.bindType,
326
414
  targetAid: cmd.targetAid,
327
415
  agentName: cmd.agentName,
328
- ownerMode: cmd.ownerMode,
329
416
  ttlMs: cmd.ttlMs,
330
417
  });
331
418
  }
@@ -445,6 +532,53 @@ export class IpcServer {
445
532
  sessionKey: cmd.sessionKey,
446
533
  });
447
534
  }
535
+ case 'queue.op': {
536
+ if (!this.queueOperationExecutor)
537
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'queue.op not configured' };
538
+ if (typeof cmd.agent !== 'string' || !cmd.agent || typeof cmd.sessionId !== 'string' || !cmd.sessionId) {
539
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing agent or sessionId' };
540
+ }
541
+ if (cmd.action !== undefined && !['clear', 'cancel', 'interrupt'].includes(cmd.action)) {
542
+ return { ok: false, code: 'INVALID_REQUEST', error: 'invalid queue action' };
543
+ }
544
+ return await this.queueOperationExecutor({
545
+ agent: cmd.agent,
546
+ action: cmd.action,
547
+ messageId: typeof cmd.messageId === 'string' ? cmd.messageId : undefined,
548
+ sessionKey: typeof cmd.sessionKey === 'string' ? cmd.sessionKey : undefined,
549
+ sessionId: cmd.sessionId,
550
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
551
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
552
+ });
553
+ }
554
+ case 'status.auth': {
555
+ if (!this.statusAuthorizationExecutor)
556
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'status authorization not configured' };
557
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
558
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId' };
559
+ return await this.statusAuthorizationExecutor({
560
+ sessionId: cmd.sessionId,
561
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
562
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
563
+ });
564
+ }
565
+ case 'storage.auth': {
566
+ if (!this.storageAuthorizationExecutor)
567
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'storage authorization not configured' };
568
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId || typeof cmd.aid !== 'string' || !cmd.aid || typeof cmd.operation !== 'string') {
569
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId, aid, or operation' };
570
+ }
571
+ if (!['storage.list', 'storage.quota', 'storage.upload', 'storage.download', 'storage.delete'].includes(cmd.operation)) {
572
+ return { ok: false, code: 'INVALID_REQUEST', error: 'invalid storage operation' };
573
+ }
574
+ return await this.storageAuthorizationExecutor({
575
+ sessionId: cmd.sessionId,
576
+ aid: cmd.aid,
577
+ operation: cmd.operation,
578
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
579
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
580
+ });
581
+ }
448
582
  case 'aun-aid-stats-record-outbound': {
449
583
  if (!this.aunAidStatsRecorder)
450
584
  return { ok: false, error: 'recorder not configured' };
@@ -510,14 +644,17 @@ export class IpcServer {
510
644
  }
511
645
  : undefined;
512
646
  try {
647
+ const normalizedPayload = payload && Object.prototype.hasOwnProperty.call(payload, 'mentions')
648
+ ? { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) }
649
+ : payload;
513
650
  return await this.aunMsgSender({
514
651
  aid: cmd.aid,
515
652
  to: cmd.to,
516
653
  scope: cmd.scope === 'group' ? 'group' : 'msg',
517
- payload,
654
+ payload: normalizedPayload,
518
655
  file,
519
- mentions: Array.isArray(cmd.mentions)
520
- ? cmd.mentions.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
656
+ mentions: Object.prototype.hasOwnProperty.call(cmd, 'mentions')
657
+ ? normalizeAunMentionEntries(cmd.mentions)
521
658
  : undefined,
522
659
  encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
523
660
  thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
@@ -530,7 +667,7 @@ export class IpcServer {
530
667
  });
531
668
  }
532
669
  catch (e) {
533
- return { ok: false, error: e?.message || String(e) };
670
+ return { ok: false, error: e?.message || String(e), code: e?.code };
534
671
  }
535
672
  }
536
673
  case 'handoff-return': {
@@ -559,6 +696,8 @@ export class IpcServer {
559
696
  return this.handoffStatusExecutor({
560
697
  sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
561
698
  handoffId: cmd.handoffId,
699
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
700
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
562
701
  });
563
702
  }
564
703
  case 'handoff-list': {
@@ -578,6 +717,8 @@ export class IpcServer {
578
717
  state: state,
579
718
  filterSessionId: typeof cmd.filterSessionId === 'string' ? cmd.filterSessionId : undefined,
580
719
  limit,
720
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
721
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
581
722
  });
582
723
  }
583
724
  case 'handoff-trace': {
@@ -595,6 +736,8 @@ export class IpcServer {
595
736
  agent: typeof cmd.agent === 'string' ? cmd.agent : undefined,
596
737
  handoffId: cmd.handoffId,
597
738
  limit,
739
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
740
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
598
741
  });
599
742
  }
600
743
  case 'ctl': {
@@ -629,6 +772,54 @@ export class IpcServer {
629
772
  }
630
773
  return await this.contactOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
631
774
  }
775
+ case 'aid.op': {
776
+ if (!this.aidOperationExecutor)
777
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'aid.op not configured' };
778
+ const { argv, sessionId, delegationToken } = cmd;
779
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
780
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
781
+ }
782
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
783
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
784
+ }
785
+ return await this.aidOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
786
+ }
787
+ case 'group.op': {
788
+ if (!this.groupOperationExecutor)
789
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'group.op not configured' };
790
+ const { argv, sessionId, delegationToken } = cmd;
791
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
792
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
793
+ }
794
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
795
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
796
+ }
797
+ return await this.groupOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
798
+ }
799
+ case 'msg.op': {
800
+ if (!this.msgOperationExecutor)
801
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'msg.op not configured' };
802
+ const { argv, sessionId, delegationToken } = cmd;
803
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
804
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
805
+ }
806
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
807
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
808
+ }
809
+ return await this.msgOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
810
+ }
811
+ case 'fs.op': {
812
+ if (!this.fsOperationExecutor)
813
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'fs.op not configured' };
814
+ const { argv, sessionId, delegationToken } = cmd;
815
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
816
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
817
+ }
818
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
819
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
820
+ }
821
+ return await this.fsOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
822
+ }
632
823
  case 'trigger.list':
633
824
  case 'trigger.show':
634
825
  case 'trigger.history':
@@ -668,29 +859,43 @@ export class IpcServer {
668
859
  return { ok: false, error: `Agent "${name}" found but info missing (race?)` };
669
860
  return { ok: true, agent: info };
670
861
  }
862
+ case 'evolagent.bootstrapComplete': {
863
+ const aid = cmd.aid;
864
+ if (!aid || typeof aid !== 'string')
865
+ return { ok: false, error: 'missing aid' };
866
+ if (!this.bootstrapCompleteExecutor)
867
+ return { ok: false, error: 'bootstrap complete executor not configured' };
868
+ try {
869
+ return await this.bootstrapCompleteExecutor(aid, {
870
+ sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
871
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
872
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
873
+ controlToken: typeof cmd.controlToken === 'string' ? cmd.controlToken : undefined,
874
+ });
875
+ }
876
+ catch (e) {
877
+ return { ok: false, error: e?.message || String(e) };
878
+ }
879
+ }
671
880
  case 'evolagent.reload': {
672
881
  if (!this.agentRegistry)
673
882
  return { ok: false, error: 'EvolAgentRegistry not available' };
674
883
  const name = cmd.name;
675
884
  if (!name || typeof name !== 'string')
676
885
  return { ok: false, error: 'missing name' };
677
- const hooks = globalThis.__evolcore_reloadHooks;
678
- if (!hooks)
679
- return { ok: false, error: 'Reload hooks not initialized' };
886
+ if (!this.agentReloadExecutor)
887
+ return { ok: false, error: 'Agent reload coordinator not initialized' };
680
888
  try {
681
889
  const a = this.agentRegistry.get(name);
682
890
  if (!a)
683
891
  return { ok: false, error: `Agent "${name}" not found` };
684
- if (!this.agentRegistry.reload)
685
- return { ok: false, error: 'EvolAgentRegistry.reload not available' };
686
- // 'kits' 组(manifest / fragment / schema / 角色模板)走 on-reload 策略,平时不查盘:
687
- // reload 是它唯一的刷新时机,必须先清再 reload,否则重载出来的 agent 仍用旧模板。
688
- const { invalidateKitCache } = await import('./eck/kit-renderer.js');
689
- invalidateKitCache();
690
- await this.agentRegistry.reload(name, hooks);
892
+ await this.agentReloadExecutor(name, { force: cmd.force === true });
691
893
  return { ok: true, result: `Agent "${name}" reloaded` };
692
894
  }
693
895
  catch (e) {
896
+ if (e instanceof AgentReloadBusyError) {
897
+ return { ok: false, code: e.code, busyCount: e.busyCount, error: e.message };
898
+ }
694
899
  return { ok: false, error: e?.message || String(e) };
695
900
  }
696
901
  }
@@ -729,7 +934,11 @@ export class IpcServer {
729
934
  if (!this.menuExecutor)
730
935
  return { ok: false, error: 'menu.exec not configured' };
731
936
  try {
732
- const response = await this.menuExecutor(cmd.payload, cmd.auth);
937
+ const response = await this.menuExecutor(cmd.payload, {
938
+ source: 'ecweb',
939
+ actorAid: null,
940
+ localDirect: true,
941
+ });
733
942
  return { ok: true, response };
734
943
  }
735
944
  catch (e) {
@@ -813,17 +1022,35 @@ export class IpcServer {
813
1022
  return { error: `unknown command: ${cmd.type}` };
814
1023
  }
815
1024
  }
1025
+ hasValidControlToken(value) {
1026
+ if (!this.controlToken || typeof value !== 'string')
1027
+ return false;
1028
+ const provided = Buffer.from(value);
1029
+ return provided.length === this.controlToken.length
1030
+ && crypto.timingSafeEqual(provided, this.controlToken);
1031
+ }
816
1032
  }
817
1033
  /**
818
1034
  * Query the running EvolCore daemon via Unix socket.
819
1035
  * Returns null if the service is not running or the socket is unreachable.
820
1036
  */
821
1037
  export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
822
- const delegationCommandHash = typeof cmd.delegationToken === 'string'
823
- && typeof cmd.delegationCommandHash !== 'string'
1038
+ let authenticatedCmd = cmd;
1039
+ if (!process.env.EVOLCORE_SESSION_ID
1040
+ && LOCAL_CONTROL_COMMANDS.has(cmd.type)
1041
+ && typeof cmd.controlToken !== 'string') {
1042
+ try {
1043
+ const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
1044
+ if (controlToken)
1045
+ authenticatedCmd = { ...cmd, controlToken };
1046
+ }
1047
+ catch { }
1048
+ }
1049
+ const delegationCommandHash = typeof authenticatedCmd.delegationToken === 'string'
1050
+ && typeof authenticatedCmd.delegationCommandHash !== 'string'
824
1051
  ? hashCurrentDelegatedCommand()
825
1052
  : undefined;
826
- const request = delegationCommandHash ? { ...cmd, delegationCommandHash } : cmd;
1053
+ const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
827
1054
  return new Promise((resolve) => {
828
1055
  const conn = net.connect(socketPath);
829
1056
  let buf = '';
@@ -41,7 +41,8 @@ export class ResponseModeCoordinator {
41
41
  const { mode, modeConfig } = resolved;
42
42
  contextDeps.logger.debug('[ResponseSystem] resolveMode mode=' + mode.id + ' source=' + resolved.source + ' chatmode=' + (modeConfig.chatmode ?? 'none'));
43
43
  const context = this.contextBuilder.build(mode.id, { ...contextDeps, modeConfig });
44
- return { mode, context, source: resolved.source };
44
+ const boundMode = mode.bind?.(context) ?? mode;
45
+ return { mode: boundMode, context, source: resolved.source };
45
46
  }
46
47
  catch (e) {
47
48
  contextDeps.logger.warn(`[Coordinator] resolveMode failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -64,9 +65,10 @@ export class ResponseModeCoordinator {
64
65
  ...deps.contextDeps,
65
66
  modeConfig,
66
67
  });
67
- await mode.initialize(context);
68
- const decision = await mode.handleInbound(message);
69
- return { modeId: mode.id, decision, mode, context };
68
+ const boundMode = mode.bind?.(context) ?? mode;
69
+ await boundMode.initialize(context);
70
+ const decision = await boundMode.handleInbound(message);
71
+ return { modeId: boundMode.id, decision, mode: boundMode, context };
70
72
  }
71
73
  catch (e) {
72
74
  deps.contextDeps.logger.warn(`[Coordinator] resolveInbound failed: ${e instanceof Error ? e.message : String(e)}`);
@@ -65,29 +65,37 @@ export const proactiveFlow = {
65
65
  const state = buildState(ctx.message, ctx.modeConfig ?? {});
66
66
  ctx.state.set(STATE_KEY, state);
67
67
  },
68
- // ─── configureRun:policyHook 首工具表态 ───
68
+ // ─── configureRun:policyHook 只判定,不推进状态 ───
69
69
  configureRun(ctx) {
70
70
  const state = ctx.state.get(STATE_KEY);
71
- if (!state || (!state.firstSendRequired && !state.toolReportRequired))
71
+ if (!state)
72
72
  return undefined;
73
73
  return {
74
74
  policyHook: (toolName, toolInput) => {
75
+ // Codex only invokes this hook for local tool paths. Hosted tools stay
76
+ // enabled and do not satisfy or clear the pending first-send gate.
75
77
  const isSendCommand = ctx.isSendCommand(toolName, toolInput);
76
78
  if (state.firstSendRequired && !state.firstToolDone) {
77
79
  if (!isSendCommand) {
78
80
  const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
79
81
  const target = state.chatType === 'group' ? '群里' : '对方';
80
- return { block: true, reason: `请先用 ${cmdHint} 向${target}说明你的意图,再执行其他工具` };
82
+ return {
83
+ block: true,
84
+ policyCode: 'proactive_first_send_required',
85
+ reason: `请先用 ${cmdHint} 向${target}说明你的意图,再执行其他工具`,
86
+ };
81
87
  }
82
- state.firstToolDone = true;
83
88
  }
84
89
  if (state.toolReportPending) {
85
90
  if (!isSendCommand) {
86
91
  const cmdHint = state.chatType === 'group' ? 'ec group send' : 'ec msg send';
87
92
  const target = state.chatType === 'group' ? '群里' : '对方';
88
- return { block: true, reason: `工具调用已达到 ${state.toolCount} 次,请先用 ${cmdHint} 向${target}汇报当前情况和下一步意图,再执行其他工具` };
93
+ return {
94
+ block: true,
95
+ policyCode: 'proactive_tool_report_required',
96
+ reason: `工具调用已达到 ${state.toolCount} 次,请先用 ${cmdHint} 向${target}汇报当前情况和下一步意图,再执行其他工具`,
97
+ };
89
98
  }
90
- state.toolReportPending = false;
91
99
  }
92
100
  return undefined;
93
101
  },
@@ -96,7 +104,18 @@ export const proactiveFlow = {
96
104
  // ─── onToolUse:队列未读提醒 + 工具汇报提醒 ───
97
105
  onToolUse(ctx) {
98
106
  const state = ctx.state.get(STATE_KEY);
99
- if (!state || !state.toolUseReminder)
107
+ if (!state)
108
+ return;
109
+ // PreToolUse 可能被工具子进程直接调用,因此审批阶段不能推进门禁状态。
110
+ // 只有 runner 发布真实工具事件后,精确匹配的发送命令才提交状态。
111
+ const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
112
+ if (isAllowedTool) {
113
+ if (state.firstSendRequired && !state.firstToolDone)
114
+ state.firstToolDone = true;
115
+ if (state.toolReportPending)
116
+ state.toolReportPending = false;
117
+ }
118
+ if (!state.toolUseReminder)
100
119
  return;
101
120
  // 队列未读变化提醒
102
121
  const queueLen = ctx.getQueueLength();
@@ -110,7 +129,6 @@ export const proactiveFlow = {
110
129
  }
111
130
  }
112
131
  // 工具计数(排除表态命令白名单)
113
- const isAllowedTool = ctx.isSendCommand(ctx.toolName, ctx.toolInput);
114
132
  if (state.toolReportRequired && !isAllowedTool)
115
133
  state.toolCount++;
116
134
  // 每 N 次工具调用警告
@@ -19,6 +19,7 @@ import { loadSchema } from '../../../config/schema-registry.js';
19
19
  * 解析后注入,见 implementation-plan.md §三;无 auto 值)。
20
20
  */
21
21
  export class SingleSessionMode {
22
+ context;
22
23
  id = 'single-session';
23
24
  displayName = '单会话模式';
24
25
  description = '单会话直接响应,按 chatMode 决定投递方式。';
@@ -26,8 +27,20 @@ export class SingleSessionMode {
26
27
  applicableScenes = ['private', 'group'];
27
28
  get configSchema() { return loadSchema('single-session').raw; }
28
29
  queue = new FIFOQueue();
29
- context;
30
+ /**
31
+ * Registry 中的实例只承担模式定义的职责。每个实际会话都由 bind() 创建
32
+ * 独立实例,以免并发会话覆盖彼此的 modeConfig 和流程上下文。
33
+ */
34
+ constructor(context) {
35
+ this.context = context;
36
+ }
37
+ bind(context) {
38
+ return new SingleSessionMode(context);
39
+ }
30
40
  async initialize(context) {
41
+ if (this.context && this.context !== context) {
42
+ throw new Error('[single-session] bound instance initialized with a different context');
43
+ }
31
44
  this.context = context;
32
45
  context.logger.debug('[ResponseSystem] single-session initialized chatMode=' + this.chatMode());
33
46
  }
@@ -12,12 +12,13 @@ export class ResponseModeSelector {
12
12
  if (!mode) {
13
13
  throw new Error(`[ResponseModeSelector] mode not found: ${modeId}`);
14
14
  }
15
- await mode.initialize(context);
16
- context.logger.debug('[ResponseSystem] selector selected mode=' + mode.id + ' engine=' + (mode.engineType ?? 'v1'));
15
+ const boundMode = mode.bind?.(context) ?? mode;
16
+ await boundMode.initialize(context);
17
+ context.logger.debug('[ResponseSystem] selector selected mode=' + boundMode.id + ' engine=' + (boundMode.engineType ?? 'v1'));
17
18
  return {
18
- id: mode.id,
19
- engineType: mode.engineType ?? 'v1',
20
- mode,
19
+ id: boundMode.id,
20
+ engineType: boundMode.engineType ?? 'v1',
21
+ mode: boundMode,
21
22
  };
22
23
  }
23
24
  }
@@ -251,7 +251,11 @@ function anomalyClassKey(anomaly) {
251
251
  anomaly.severity,
252
252
  anomaly.phase,
253
253
  anomaly.toolName,
254
+ anomaly.policyCode,
254
255
  anomaly.policy,
256
+ anomaly.agentAid,
257
+ anomaly.sessionId,
258
+ anomaly.permissionMode,
255
259
  anomaly.summary,
256
260
  anomaly.effect,
257
261
  ]);
@@ -52,6 +52,7 @@ export class TriggerScriptExecutor {
52
52
  const child = spawn(cmd, args, {
53
53
  stdio: ['pipe', 'pipe', 'pipe'],
54
54
  shell: false,
55
+ windowsHide: process.platform === 'win32',
55
56
  env: process.env,
56
57
  });
57
58
  const abortHandler = () => {
@@ -45,7 +45,6 @@ export class BindService {
45
45
  }
46
46
  const ttlMs = clampTtl(req.ttlMs);
47
47
  const expiresAt = Date.now() + ttlMs;
48
- const ownerMode = req.ownerMode === 'replace' ? 'replace' : 'append';
49
48
  const task = {
50
49
  taskId,
51
50
  bindType: req.bindType,
@@ -56,7 +55,6 @@ export class BindService {
56
55
  createdAt: Date.now(),
57
56
  expiresAt,
58
57
  status: 'pending',
59
- ownerMode,
60
58
  };
61
59
  this.tasks.set(taskId, task);
62
60
  this.taskIdsByTokenHash.set(tokenHash, taskId);
@@ -209,9 +207,7 @@ export class BindService {
209
207
  if (task.bindType === 'daemon') {
210
208
  const cfg = loadDaemonConfig();
211
209
  const current = cfg.owners ?? [];
212
- const owners = task.ownerMode === 'replace'
213
- ? [ownerAid]
214
- : [ownerAid, ...current.filter(o => o !== ownerAid)];
210
+ const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
215
211
  saveDaemonConfig({ ...cfg, $schema_version: cfg.$schema_version ?? 1, owners });
216
212
  this.opts.onDaemonOwnersUpdated?.(owners);
217
213
  return;
@@ -220,10 +216,9 @@ export class BindService {
220
216
  if (!agent)
221
217
  throw new Error(`agent not found: ${task.targetAid}`);
222
218
  const current = agent.owners ?? [];
223
- const owners = task.ownerMode === 'replace'
224
- ? [ownerAid]
225
- : [ownerAid, ...current.filter(o => o !== ownerAid)];
219
+ const owners = [ownerAid, ...current.filter(o => o !== ownerAid)];
226
220
  saveAgent({ ...agent, owners });
221
+ this.opts.onAgentOwnerUpdated?.(task.targetAid, ownerAid);
227
222
  }
228
223
  cleanup() {
229
224
  const now = Date.now();