evolcore 0.0.11 → 0.0.13

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 (164) hide show
  1. package/CHANGELOG.md +44 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/bin/ec-safe-output.js +26 -7
  4. package/dist/agents/claude-runner.js +86 -7
  5. package/dist/agents/codex-app-server-client.js +174 -20
  6. package/dist/agents/codex-runner.js +375 -92
  7. package/dist/agents/ecagent-runner.js +39 -4
  8. package/dist/agents/gemini-runner.js +1 -0
  9. package/dist/aun/aid/client.js +11 -21
  10. package/dist/aun/aid/control-aid.js +36 -13
  11. package/dist/aun/aid/managed-operation.js +107 -0
  12. package/dist/aun/aid/store.js +13 -7
  13. package/dist/aun/group-fs-download.js +56 -0
  14. package/dist/aun/group-identity.js +1 -2
  15. package/dist/aun/msg/group.js +91 -44
  16. package/dist/aun/msg/index.js +1 -2
  17. package/dist/aun/msg/managed-operation.js +865 -0
  18. package/dist/aun/msg/mention-schema.js +20 -0
  19. package/dist/aun/msg/p2p.js +7 -0
  20. package/dist/aun/msg/upload.js +16 -1
  21. package/dist/aun/outbox.js +31 -2
  22. package/dist/aun/rpc/client.js +66 -0
  23. package/dist/aun/rpc/index.js +1 -2
  24. package/dist/aun/storage/{upload.js → client.js} +11 -2
  25. package/dist/aun/storage/index.js +1 -2
  26. package/dist/channels/aun.js +655 -240
  27. package/dist/channels/daemon.js +13 -0
  28. package/dist/cli/agent-command.js +56 -8
  29. package/dist/cli/agent.js +108 -31
  30. package/dist/cli/aun-commands.js +270 -69
  31. package/dist/cli/bench.js +12 -3
  32. package/dist/cli/cli-argv.js +10 -0
  33. package/dist/cli/config.js +9 -17
  34. package/dist/cli/ctl-command.js +1 -1
  35. package/dist/cli/daemon-commands.js +70 -55
  36. package/dist/cli/fs-command.js +79 -10
  37. package/dist/cli/handoff-command.js +3 -0
  38. package/dist/cli/index.js +30 -2
  39. package/dist/cli/init.js +77 -8
  40. package/dist/cli/managed-command-guard.js +41 -0
  41. package/dist/cli/model.js +11 -4
  42. package/dist/cli/queue-command.js +53 -1
  43. package/dist/cli/response.js +71 -0
  44. package/dist/cli/restart-monitor.js +7 -32
  45. package/dist/cli/trigger-command.js +11 -0
  46. package/dist/config/access-policy.js +1 -2
  47. package/dist/config/builtin-roles.js +9 -0
  48. package/dist/config/config-manager.js +21 -1
  49. package/dist/config/contact-book-store.js +11 -3
  50. package/dist/config/contact-request-service.js +161 -15
  51. package/dist/config/mention-mode.js +8 -24
  52. package/dist/config/peer-role-resolver.js +6 -2
  53. package/dist/config/role-migration-startup.js +53 -0
  54. package/dist/config/role-store.js +1 -1
  55. package/dist/core/auth/agent-delegation.js +1 -2
  56. package/dist/core/auth/auth-gateway.js +29 -1
  57. package/dist/core/auth/authorization-audit.js +15 -0
  58. package/dist/core/auth/operation-authorizer.js +31 -7
  59. package/dist/core/auth/operation-catalog.js +196 -25
  60. package/dist/core/bootstrap-messages.js +25 -0
  61. package/dist/core/bootstrap-service.js +64 -4
  62. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  63. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  64. package/dist/core/capability/skill-discovery.js +55 -0
  65. package/dist/core/channel-loader.js +1 -1
  66. package/dist/core/command/cli-intent-parser.js +9 -0
  67. package/dist/core/command/command-handler.js +650 -42
  68. package/dist/core/command/connect-menu.js +15 -0
  69. package/dist/core/command/group-menu.js +4 -4
  70. package/dist/core/command/menu-catalog.js +370 -0
  71. package/dist/core/command/menu-handler.js +230 -51
  72. package/dist/core/command/menu-protocol.js +94 -2
  73. package/dist/core/command/slash-gate.js +5 -2
  74. package/dist/core/command/slash-handler.js +99 -31
  75. package/dist/core/daemon-file-cache.js +2 -1
  76. package/dist/core/data-migration.js +9 -2
  77. package/dist/core/event-catalog.js +6 -0
  78. package/dist/core/evolagent.js +4 -2
  79. package/dist/core/handoff/dispatcher.js +5 -2
  80. package/dist/core/handoff/runtime.js +157 -22
  81. package/dist/core/handoff/store.js +29 -1
  82. package/dist/core/handoff/types.js +1 -0
  83. package/dist/core/interaction-router.js +10 -0
  84. package/dist/core/message/im-renderer.js +19 -0
  85. package/dist/core/message/logical-queue-bridge.js +1 -0
  86. package/dist/core/message/mention-schema.js +126 -0
  87. package/dist/core/message/message-bridge.js +298 -88
  88. package/dist/core/message/message-log.js +3 -0
  89. package/dist/core/message/message-queue.js +268 -39
  90. package/dist/core/message/message-utils.js +81 -4
  91. package/dist/core/message/response-engine.js +161 -77
  92. package/dist/core/message/stream-debouncer.js +10 -2
  93. package/dist/core/permission/approval-gateway.js +1 -0
  94. package/dist/core/permission/ec-command-parser.js +243 -18
  95. package/dist/core/permission/index.js +1 -1
  96. package/dist/core/permission/mode.js +15 -0
  97. package/dist/core/permission/readonly-shell-query.js +148 -8
  98. package/dist/core/permission/sandbox-runtime.js +43 -1
  99. package/dist/core/permission/tool-policy.js +322 -57
  100. package/dist/core/protected-paths.js +114 -4
  101. package/dist/core/session/session-manager.js +42 -4
  102. package/dist/core/session/session-mapper.js +20 -1
  103. package/dist/eck/detect.js +18 -1
  104. package/dist/eck/group-rules-sync.js +15 -44
  105. package/dist/eck/manifest-engine.js +39 -13
  106. package/dist/index.js +541 -180
  107. package/dist/ipc.js +232 -10
  108. package/dist/paths.js +3 -0
  109. package/dist/response-system/coordinator.js +6 -4
  110. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  111. package/dist/response-system/modes/single-session/index.js +14 -1
  112. package/dist/response-system/selector.js +6 -5
  113. package/dist/trigger/anomaly-store.js +4 -0
  114. package/dist/trigger/feedback.js +64 -11
  115. package/dist/trigger/manager.js +21 -1
  116. package/dist/trigger/parser.js +14 -2
  117. package/dist/trigger/scheduler.js +1 -0
  118. package/dist/trigger/script-executor.js +1 -0
  119. package/dist/trigger/validation.js +67 -6
  120. package/dist/utils/codex-app-server-registry.js +90 -0
  121. package/dist/utils/codex-cli.js +5 -1
  122. package/dist/utils/cross-platform.js +15 -0
  123. package/dist/utils/ecweb-supervisor.js +332 -0
  124. package/dist/utils/error-utils.js +10 -0
  125. package/dist/utils/npm-ops.js +5 -12
  126. package/dist/utils/tool-summary.js +11 -0
  127. package/kits/docs/channels/aun.md +1 -1
  128. package/kits/docs/context-assembly.md +2 -2
  129. package/kits/docs/evolcore/agent.md +11 -6
  130. package/kits/docs/evolcore/aid.md +14 -0
  131. package/kits/docs/evolcore/trigger.md +17 -1
  132. package/kits/docs/prompt-loading-architecture.md +1 -2
  133. package/kits/docs/venues/group.md +1 -1
  134. package/kits/eck_manifest.json +0 -12
  135. package/kits/migrations/migrate-contact-book-v2.mjs +1 -2
  136. package/kits/rules/04-relation.md +1 -1
  137. package/kits/rules/05-venue.md +2 -2
  138. package/kits/schemas/role-config.schema.1.json +1 -0
  139. package/kits/schemas/single-session.schema.1.json +3 -3
  140. package/kits/schemas/single-session.schema.2.json +3 -3
  141. package/kits/templates/roles/admin.json +27 -0
  142. package/kits/templates/roles/member.json +34 -1
  143. package/kits/templates/roles/visitor.json +35 -3
  144. package/kits/templates/system-fragments/bootstrap.md +1 -1
  145. package/kits/templates/system-fragments/channel.md +1 -1
  146. package/kits/templates/system-fragments/session.md +1 -1
  147. package/package.json +2 -2
  148. package/dist/aun/msg/payload-type.js +0 -27
  149. package/dist/aun/rpc/caller.js +0 -42
  150. package/dist/aun/rpc/connection.js +0 -25
  151. package/dist/aun/storage/manage.js +0 -10
  152. package/dist/config/access-policy-domain.js +0 -18
  153. package/dist/config/config-batch-get.js +0 -11
  154. package/dist/config/owner-policy.js +0 -4
  155. package/dist/config/role-config-v4-startup.js +0 -32
  156. package/dist/config/role-config-v5-startup.js +0 -27
  157. package/dist/core/auth/trigger-authorization.js +0 -15
  158. package/dist/core/command/evol-menu-version-gate.js +0 -39
  159. package/dist/core/interaction-registration.js +0 -10
  160. package/dist/core/permission/execution-sandbox.js +0 -16
  161. package/dist/core/relation/peer-key.js +0 -1
  162. package/dist/core/session/session-key.js +0 -24
  163. package/dist/eck/baseagent-caps.js +0 -18
  164. package/dist/eck/rules-loader.js +0 -28
package/dist/ipc.js CHANGED
@@ -1,5 +1,6 @@
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';
@@ -11,8 +12,33 @@ import { getProcessStartTime, isSameOrOlderProcess } from './utils/process-intro
11
12
  import { classifyProcessTree, ProcessTreeSampler } from './utils/process-tree-stats.js';
12
13
  import { readSystemMemoryUsage } from './utils/system-memory.js';
13
14
  import { hashCurrentDelegatedCommand } from './core/auth/agent-delegation.js';
15
+ import { normalizeAunMentionEntries } from './aun/msg/mention-schema.js';
14
16
  const isWindows = process.platform === 'win32';
15
17
  const isNamedPipe = (p) => isWindows && p.startsWith('\\\\.\\pipe\\');
18
+ const finiteTimestamp = (value) => (typeof value === 'number' && Number.isFinite(value) ? value : undefined);
19
+ const LOCAL_CONTROL_COMMANDS = new Set([
20
+ 'bind.begin',
21
+ 'bind.status',
22
+ 'bind.cancel',
23
+ 'dingtalk.contact-bind.register',
24
+ 'feishu.contact-bind.register',
25
+ 'qqbot.contact-bind.register',
26
+ 'wecom.contact-bind.register',
27
+ 'wechat.contact-bind.register',
28
+ 'queue-snapshot',
29
+ 'aun-aid-stats-record-outbound',
30
+ 'evolagent.reload',
31
+ 'evolagent.load',
32
+ 'evolagent.resync',
33
+ 'menu.exec',
34
+ 'monitor-snapshot',
35
+ ]);
36
+ function requiresLocalControlToken(cmd) {
37
+ // Managed bootstrap completion is authenticated by the session delegation
38
+ // ticket. A standalone CLI invocation still needs the daemon control token.
39
+ return LOCAL_CONTROL_COMMANDS.has(String(cmd.type ?? ''))
40
+ || (cmd.type === 'evolagent.bootstrapComplete' && typeof cmd.sessionId !== 'string');
41
+ }
16
42
  export function summarizeMonitorAgents(agents, aids) {
17
43
  const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
18
44
  const enabledAgents = agents.filter(agent => agent.status !== 'disabled');
@@ -39,10 +65,14 @@ export class IpcServer {
39
65
  agentStatsProvider;
40
66
  queueSnapshotProvider;
41
67
  queueActionExecutor;
68
+ queueOperationExecutor;
69
+ statusAuthorizationExecutor;
70
+ storageAuthorizationExecutor;
42
71
  triggerExecutor;
43
72
  bootstrapCompleteExecutor;
44
73
  agentReloadExecutor;
45
74
  taskRuntimeContextProvider;
75
+ codexPreToolUseExecutor;
46
76
  aunMsgSender;
47
77
  handoffReturnExecutor;
48
78
  handoffStatusExecutor;
@@ -51,11 +81,16 @@ export class IpcServer {
51
81
  bindExecutor;
52
82
  configOperationExecutor;
53
83
  contactOperationExecutor;
84
+ aidOperationExecutor;
85
+ groupOperationExecutor;
86
+ msgOperationExecutor;
87
+ fsOperationExecutor;
54
88
  dingtalkContactBindExecutor;
55
89
  feishuContactBindExecutor;
56
90
  qqbotContactBindExecutor;
57
91
  wecomContactBindExecutor;
58
92
  wechatContactBindExecutor;
93
+ controlToken;
59
94
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
60
95
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
61
96
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -78,6 +113,10 @@ export class IpcServer {
78
113
  setAgentRegistry(registry) {
79
114
  this.agentRegistry = registry;
80
115
  }
116
+ /** Authenticate local control-plane callers that cannot use task delegation. */
117
+ setControlToken(token) {
118
+ this.controlToken = Buffer.from(token);
119
+ }
81
120
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
82
121
  setMenuExecutor(executor) {
83
122
  this.menuExecutor = executor;
@@ -88,6 +127,18 @@ export class IpcServer {
88
127
  setContactOperationExecutor(executor) {
89
128
  this.contactOperationExecutor = executor;
90
129
  }
130
+ setAidOperationExecutor(executor) {
131
+ this.aidOperationExecutor = executor;
132
+ }
133
+ setGroupOperationExecutor(executor) {
134
+ this.groupOperationExecutor = executor;
135
+ }
136
+ setMsgOperationExecutor(executor) {
137
+ this.msgOperationExecutor = executor;
138
+ }
139
+ setFsOperationExecutor(executor) {
140
+ this.fsOperationExecutor = executor;
141
+ }
91
142
  setAgentReloadExecutor(executor) {
92
143
  this.agentReloadExecutor = executor;
93
144
  }
@@ -119,6 +170,15 @@ export class IpcServer {
119
170
  setQueueActionExecutor(executor) {
120
171
  this.queueActionExecutor = executor;
121
172
  }
173
+ setQueueOperationExecutor(executor) {
174
+ this.queueOperationExecutor = executor;
175
+ }
176
+ setStatusAuthorizationExecutor(executor) {
177
+ this.statusAuthorizationExecutor = executor;
178
+ }
179
+ setStorageAuthorizationExecutor(executor) {
180
+ this.storageAuthorizationExecutor = executor;
181
+ }
122
182
  /** Inject daemon-level trigger executor for ec trigger. */
123
183
  setTriggerExecutor(executor) {
124
184
  this.triggerExecutor = executor;
@@ -131,6 +191,9 @@ export class IpcServer {
131
191
  setTaskRuntimeContextProvider(provider) {
132
192
  this.taskRuntimeContextProvider = provider;
133
193
  }
194
+ setCodexPreToolUseExecutor(executor) {
195
+ this.codexPreToolUseExecutor = executor;
196
+ }
134
197
  /** Inject daemon-backed AUN private message sender for in-task `ec msg send`. */
135
198
  setAunMsgSender(sender) {
136
199
  this.aunMsgSender = sender;
@@ -323,9 +386,31 @@ export class IpcServer {
323
386
  }
324
387
  }
325
388
  async handleCommand(cmd) {
389
+ if (requiresLocalControlToken(cmd) && !this.hasValidControlToken(cmd.controlToken)) {
390
+ return { ok: false, code: 'INVALID_CONTROL_TOKEN', error: 'valid local control token is required' };
391
+ }
326
392
  switch (cmd.type) {
327
393
  case 'status':
328
394
  return this.getStatus();
395
+ case 'codex-pre-tool-use': {
396
+ if (!this.codexPreToolUseExecutor) {
397
+ return { ok: false, applicable: true, decision: 'deny', reason: 'EvolCore Codex policy executor is unavailable' };
398
+ }
399
+ if (typeof cmd.threadId !== 'string' || !cmd.threadId
400
+ || typeof cmd.toolName !== 'string' || !cmd.toolName) {
401
+ return { ok: false, applicable: true, decision: 'deny', reason: 'invalid Codex hook request' };
402
+ }
403
+ try {
404
+ return await this.codexPreToolUseExecutor({
405
+ threadId: cmd.threadId,
406
+ toolName: cmd.toolName,
407
+ toolInput: cmd.toolInput,
408
+ });
409
+ }
410
+ catch (error) {
411
+ return { ok: false, applicable: true, decision: 'deny', reason: `EvolCore policy unavailable: ${error instanceof Error ? error.message : String(error)}` };
412
+ }
413
+ }
329
414
  case 'ping':
330
415
  return { pong: true, pid: process.pid, protocolVersion: 1 };
331
416
  case 'bind.begin': {
@@ -442,7 +527,11 @@ export class IpcServer {
442
527
  // 纯查询
443
528
  if (!this.queueSnapshotProvider)
444
529
  return { ok: false, error: 'queue-snapshot not configured' };
445
- return { ok: true, items: this.queueSnapshotProvider({ agent: cmd.agent }) };
530
+ return { ok: true, items: this.queueSnapshotProvider({
531
+ agent: cmd.agent,
532
+ after: finiteTimestamp(cmd.after),
533
+ before: finiteTimestamp(cmd.before),
534
+ }) };
446
535
  }
447
536
  // 操作:clear / cancel / interrupt
448
537
  if (!this.queueActionExecutor)
@@ -454,6 +543,55 @@ export class IpcServer {
454
543
  sessionKey: cmd.sessionKey,
455
544
  });
456
545
  }
546
+ case 'queue.op': {
547
+ if (!this.queueOperationExecutor)
548
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'queue.op not configured' };
549
+ if (typeof cmd.agent !== 'string' || !cmd.agent || typeof cmd.sessionId !== 'string' || !cmd.sessionId) {
550
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing agent or sessionId' };
551
+ }
552
+ if (cmd.action !== undefined && !['clear', 'cancel', 'interrupt'].includes(cmd.action)) {
553
+ return { ok: false, code: 'INVALID_REQUEST', error: 'invalid queue action' };
554
+ }
555
+ return await this.queueOperationExecutor({
556
+ agent: cmd.agent,
557
+ action: cmd.action,
558
+ messageId: typeof cmd.messageId === 'string' ? cmd.messageId : undefined,
559
+ sessionKey: typeof cmd.sessionKey === 'string' ? cmd.sessionKey : undefined,
560
+ after: finiteTimestamp(cmd.after),
561
+ before: finiteTimestamp(cmd.before),
562
+ sessionId: cmd.sessionId,
563
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
564
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
565
+ });
566
+ }
567
+ case 'status.auth': {
568
+ if (!this.statusAuthorizationExecutor)
569
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'status authorization not configured' };
570
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId)
571
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId' };
572
+ return await this.statusAuthorizationExecutor({
573
+ sessionId: cmd.sessionId,
574
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
575
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
576
+ });
577
+ }
578
+ case 'storage.auth': {
579
+ if (!this.storageAuthorizationExecutor)
580
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'storage authorization not configured' };
581
+ if (typeof cmd.sessionId !== 'string' || !cmd.sessionId || typeof cmd.aid !== 'string' || !cmd.aid || typeof cmd.operation !== 'string') {
582
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing sessionId, aid, or operation' };
583
+ }
584
+ if (!['storage.list', 'storage.quota', 'storage.upload', 'storage.download', 'storage.delete'].includes(cmd.operation)) {
585
+ return { ok: false, code: 'INVALID_REQUEST', error: 'invalid storage operation' };
586
+ }
587
+ return await this.storageAuthorizationExecutor({
588
+ sessionId: cmd.sessionId,
589
+ aid: cmd.aid,
590
+ operation: cmd.operation,
591
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
592
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
593
+ });
594
+ }
457
595
  case 'aun-aid-stats-record-outbound': {
458
596
  if (!this.aunAidStatsRecorder)
459
597
  return { ok: false, error: 'recorder not configured' };
@@ -519,14 +657,17 @@ export class IpcServer {
519
657
  }
520
658
  : undefined;
521
659
  try {
660
+ const normalizedPayload = payload && Object.prototype.hasOwnProperty.call(payload, 'mentions')
661
+ ? { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) }
662
+ : payload;
522
663
  return await this.aunMsgSender({
523
664
  aid: cmd.aid,
524
665
  to: cmd.to,
525
666
  scope: cmd.scope === 'group' ? 'group' : 'msg',
526
- payload,
667
+ payload: normalizedPayload,
527
668
  file,
528
- mentions: Array.isArray(cmd.mentions)
529
- ? cmd.mentions.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
669
+ mentions: Object.prototype.hasOwnProperty.call(cmd, 'mentions')
670
+ ? normalizeAunMentionEntries(cmd.mentions)
530
671
  : undefined,
531
672
  encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
532
673
  thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
@@ -539,7 +680,7 @@ export class IpcServer {
539
680
  });
540
681
  }
541
682
  catch (e) {
542
- return { ok: false, error: e?.message || String(e) };
683
+ return { ok: false, error: e?.message || String(e), code: e?.code };
543
684
  }
544
685
  }
545
686
  case 'handoff-return': {
@@ -568,6 +709,8 @@ export class IpcServer {
568
709
  return this.handoffStatusExecutor({
569
710
  sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
570
711
  handoffId: cmd.handoffId,
712
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
713
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
571
714
  });
572
715
  }
573
716
  case 'handoff-list': {
@@ -587,6 +730,8 @@ export class IpcServer {
587
730
  state: state,
588
731
  filterSessionId: typeof cmd.filterSessionId === 'string' ? cmd.filterSessionId : undefined,
589
732
  limit,
733
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
734
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
590
735
  });
591
736
  }
592
737
  case 'handoff-trace': {
@@ -604,6 +749,8 @@ export class IpcServer {
604
749
  agent: typeof cmd.agent === 'string' ? cmd.agent : undefined,
605
750
  handoffId: cmd.handoffId,
606
751
  limit,
752
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
753
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
607
754
  });
608
755
  }
609
756
  case 'ctl': {
@@ -638,6 +785,54 @@ export class IpcServer {
638
785
  }
639
786
  return await this.contactOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
640
787
  }
788
+ case 'aid.op': {
789
+ if (!this.aidOperationExecutor)
790
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'aid.op not configured' };
791
+ const { argv, sessionId, delegationToken } = cmd;
792
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
793
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
794
+ }
795
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
796
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
797
+ }
798
+ return await this.aidOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
799
+ }
800
+ case 'group.op': {
801
+ if (!this.groupOperationExecutor)
802
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'group.op not configured' };
803
+ const { argv, sessionId, delegationToken } = cmd;
804
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
805
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
806
+ }
807
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
808
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
809
+ }
810
+ return await this.groupOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
811
+ }
812
+ case 'msg.op': {
813
+ if (!this.msgOperationExecutor)
814
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'msg.op not configured' };
815
+ const { argv, sessionId, delegationToken } = cmd;
816
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
817
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
818
+ }
819
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
820
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
821
+ }
822
+ return await this.msgOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
823
+ }
824
+ case 'fs.op': {
825
+ if (!this.fsOperationExecutor)
826
+ return { ok: false, code: 'NOT_CONFIGURED', error: 'fs.op not configured' };
827
+ const { argv, sessionId, delegationToken } = cmd;
828
+ if (!Array.isArray(argv) || argv.some(value => typeof value !== 'string') || !sessionId) {
829
+ return { ok: false, code: 'INVALID_REQUEST', error: 'missing argv or sessionId' };
830
+ }
831
+ if (delegationToken !== undefined && typeof delegationToken !== 'string') {
832
+ return { ok: false, code: 'INVALID_DELEGATION', error: 'delegationToken must be a string' };
833
+ }
834
+ return await this.fsOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
835
+ }
641
836
  case 'trigger.list':
642
837
  case 'trigger.show':
643
838
  case 'trigger.history':
@@ -684,7 +879,12 @@ export class IpcServer {
684
879
  if (!this.bootstrapCompleteExecutor)
685
880
  return { ok: false, error: 'bootstrap complete executor not configured' };
686
881
  try {
687
- return await this.bootstrapCompleteExecutor(aid);
882
+ return await this.bootstrapCompleteExecutor(aid, {
883
+ sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
884
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
885
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
886
+ controlToken: typeof cmd.controlToken === 'string' ? cmd.controlToken : undefined,
887
+ });
688
888
  }
689
889
  catch (e) {
690
890
  return { ok: false, error: e?.message || String(e) };
@@ -747,7 +947,11 @@ export class IpcServer {
747
947
  if (!this.menuExecutor)
748
948
  return { ok: false, error: 'menu.exec not configured' };
749
949
  try {
750
- const response = await this.menuExecutor(cmd.payload, cmd.auth);
950
+ const response = await this.menuExecutor(cmd.payload, {
951
+ source: 'ecweb',
952
+ actorAid: null,
953
+ localDirect: true,
954
+ });
751
955
  return { ok: true, response };
752
956
  }
753
957
  catch (e) {
@@ -831,17 +1035,35 @@ export class IpcServer {
831
1035
  return { error: `unknown command: ${cmd.type}` };
832
1036
  }
833
1037
  }
1038
+ hasValidControlToken(value) {
1039
+ if (!this.controlToken || typeof value !== 'string')
1040
+ return false;
1041
+ const provided = Buffer.from(value);
1042
+ return provided.length === this.controlToken.length
1043
+ && crypto.timingSafeEqual(provided, this.controlToken);
1044
+ }
834
1045
  }
835
1046
  /**
836
1047
  * Query the running EvolCore daemon via Unix socket.
837
1048
  * Returns null if the service is not running or the socket is unreachable.
838
1049
  */
839
1050
  export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
840
- const delegationCommandHash = typeof cmd.delegationToken === 'string'
841
- && typeof cmd.delegationCommandHash !== 'string'
1051
+ let authenticatedCmd = cmd;
1052
+ if (!process.env.EVOLCORE_SESSION_ID
1053
+ && requiresLocalControlToken(cmd)
1054
+ && typeof cmd.controlToken !== 'string') {
1055
+ try {
1056
+ const controlToken = fs.readFileSync(resolvePaths().controlToken, 'utf8').trim();
1057
+ if (controlToken)
1058
+ authenticatedCmd = { ...cmd, controlToken };
1059
+ }
1060
+ catch { }
1061
+ }
1062
+ const delegationCommandHash = typeof authenticatedCmd.delegationToken === 'string'
1063
+ && typeof authenticatedCmd.delegationCommandHash !== 'string'
842
1064
  ? hashCurrentDelegatedCommand()
843
1065
  : undefined;
844
- const request = delegationCommandHash ? { ...cmd, delegationCommandHash } : cmd;
1066
+ const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
845
1067
  return new Promise((resolve) => {
846
1068
  const conn = net.connect(socketPath);
847
1069
  let buf = '';
package/dist/paths.js CHANGED
@@ -149,6 +149,9 @@ export function daemonControlDir() {
149
149
  export function daemonQueuePath() {
150
150
  return path.join(daemonDataDir(), 'message-queue.json');
151
151
  }
152
+ export function daemonMessageDedupPath() {
153
+ return path.join(daemonDataDir(), 'message-dedup.json');
154
+ }
152
155
  export function channelStateDir(channelKey) {
153
156
  if (!channelKey || channelKey === '.' || channelKey === '..') {
154
157
  throw new Error('channelKey is required');
@@ -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
  ]);