evolcore 0.0.11 → 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 (102) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/bin/codex-managed-hook.mjs +80 -0
  3. package/dist/agents/claude-runner.js +35 -2
  4. package/dist/agents/codex-app-server-client.js +120 -14
  5. package/dist/agents/codex-runner.js +273 -35
  6. package/dist/agents/ecagent-runner.js +36 -4
  7. package/dist/agents/gemini-runner.js +1 -0
  8. package/dist/aun/aid/client.js +11 -21
  9. package/dist/aun/aid/managed-operation.js +107 -0
  10. package/dist/aun/msg/group.js +81 -12
  11. package/dist/aun/msg/managed-operation.js +804 -0
  12. package/dist/aun/msg/mention-schema.js +20 -0
  13. package/dist/aun/msg/p2p.js +7 -0
  14. package/dist/channels/aun.js +288 -108
  15. package/dist/channels/daemon.js +13 -0
  16. package/dist/cli/agent-command.js +56 -8
  17. package/dist/cli/agent.js +170 -6
  18. package/dist/cli/aun-commands.js +270 -69
  19. package/dist/cli/bench.js +12 -3
  20. package/dist/cli/daemon-commands.js +31 -31
  21. package/dist/cli/fs-command.js +60 -0
  22. package/dist/cli/handoff-command.js +3 -0
  23. package/dist/cli/index.js +30 -2
  24. package/dist/cli/managed-command-guard.js +41 -0
  25. package/dist/cli/model.js +11 -4
  26. package/dist/cli/queue-command.js +11 -1
  27. package/dist/cli/response.js +71 -0
  28. package/dist/cli/restart-monitor.js +4 -18
  29. package/dist/cli/trigger-command.js +4 -0
  30. package/dist/config/builtin-roles.js +4 -0
  31. package/dist/config/contact-book-store.js +11 -3
  32. package/dist/config/contact-request-service.js +161 -15
  33. package/dist/config/mention-mode.js +8 -24
  34. package/dist/core/auth/agent-delegation.js +0 -1
  35. package/dist/core/auth/operation-authorizer.js +30 -7
  36. package/dist/core/auth/operation-catalog.js +187 -25
  37. package/dist/core/bootstrap-messages.js +18 -0
  38. package/dist/core/bootstrap-service.js +63 -4
  39. package/dist/core/capability/providers/claude-capability-provider.js +9 -31
  40. package/dist/core/capability/providers/codex-capability-provider.js +4 -15
  41. package/dist/core/capability/skill-discovery.js +55 -0
  42. package/dist/core/channel-loader.js +1 -1
  43. package/dist/core/command/cli-intent-parser.js +9 -0
  44. package/dist/core/command/command-handler.js +467 -6
  45. package/dist/core/command/connect-menu.js +15 -0
  46. package/dist/core/command/group-menu.js +4 -4
  47. package/dist/core/command/menu-catalog.js +366 -0
  48. package/dist/core/command/menu-handler.js +147 -35
  49. package/dist/core/command/menu-protocol.js +71 -0
  50. package/dist/core/command/slash-gate.js +5 -2
  51. package/dist/core/command/slash-handler.js +41 -21
  52. package/dist/core/daemon-file-cache.js +2 -1
  53. package/dist/core/data-migration.js +9 -2
  54. package/dist/core/evolagent.js +4 -2
  55. package/dist/core/handoff/dispatcher.js +5 -2
  56. package/dist/core/handoff/runtime.js +145 -21
  57. package/dist/core/handoff/store.js +24 -0
  58. package/dist/core/handoff/types.js +1 -0
  59. package/dist/core/message/logical-queue-bridge.js +1 -0
  60. package/dist/core/message/mention-schema.js +126 -0
  61. package/dist/core/message/message-bridge.js +236 -78
  62. package/dist/core/message/message-log.js +1 -0
  63. package/dist/core/message/message-queue.js +1 -0
  64. package/dist/core/message/response-engine.js +54 -36
  65. package/dist/core/permission/ec-command-parser.js +156 -10
  66. package/dist/core/permission/sandbox-runtime.js +22 -1
  67. package/dist/core/permission/tool-policy.js +73 -30
  68. package/dist/core/protected-paths.js +7 -2
  69. package/dist/core/session/session-manager.js +21 -2
  70. package/dist/eck/manifest-engine.js +39 -13
  71. package/dist/index.js +384 -98
  72. package/dist/ipc.js +218 -9
  73. package/dist/response-system/coordinator.js +6 -4
  74. package/dist/response-system/engines/v1/proactive-flow.js +26 -8
  75. package/dist/response-system/modes/single-session/index.js +14 -1
  76. package/dist/response-system/selector.js +6 -5
  77. package/dist/trigger/anomaly-store.js +4 -0
  78. package/dist/trigger/script-executor.js +1 -0
  79. package/dist/utils/codex-app-server-registry.js +90 -0
  80. package/dist/utils/codex-cli.js +5 -1
  81. package/dist/utils/cross-platform.js +15 -0
  82. package/dist/utils/npm-ops.js +5 -12
  83. package/dist/utils/tool-summary.js +11 -0
  84. package/kits/docs/channels/aun.md +1 -1
  85. package/kits/docs/context-assembly.md +2 -2
  86. package/kits/docs/evolcore/agent.md +11 -6
  87. package/kits/docs/evolcore/aid.md +14 -0
  88. package/kits/docs/prompt-loading-architecture.md +1 -2
  89. package/kits/docs/venues/group.md +1 -1
  90. package/kits/eck_manifest.json +0 -12
  91. package/kits/rules/04-relation.md +1 -1
  92. package/kits/rules/05-venue.md +2 -2
  93. package/kits/schemas/single-session.schema.1.json +3 -3
  94. package/kits/schemas/single-session.schema.2.json +3 -3
  95. package/kits/templates/roles/admin.json +22 -0
  96. package/kits/templates/roles/member.json +27 -0
  97. package/kits/templates/roles/visitor.json +33 -3
  98. package/kits/templates/system-fragments/bootstrap.md +1 -1
  99. package/kits/templates/system-fragments/channel.md +1 -1
  100. package/kits/templates/system-fragments/session.md +1 -1
  101. package/package.json +2 -2
  102. package/dist/core/command/evol-menu-version-gate.js +0 -39
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,26 @@ 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 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
+ ]);
16
35
  export function summarizeMonitorAgents(agents, aids) {
17
36
  const connectedAgents = new Set(aids.filter(aid => aid.status === 'connected').map(aid => aid.agentName || aid.aid));
18
37
  const enabledAgents = agents.filter(agent => agent.status !== 'disabled');
@@ -39,10 +58,14 @@ export class IpcServer {
39
58
  agentStatsProvider;
40
59
  queueSnapshotProvider;
41
60
  queueActionExecutor;
61
+ queueOperationExecutor;
62
+ statusAuthorizationExecutor;
63
+ storageAuthorizationExecutor;
42
64
  triggerExecutor;
43
65
  bootstrapCompleteExecutor;
44
66
  agentReloadExecutor;
45
67
  taskRuntimeContextProvider;
68
+ codexPreToolUseExecutor;
46
69
  aunMsgSender;
47
70
  handoffReturnExecutor;
48
71
  handoffStatusExecutor;
@@ -51,11 +74,16 @@ export class IpcServer {
51
74
  bindExecutor;
52
75
  configOperationExecutor;
53
76
  contactOperationExecutor;
77
+ aidOperationExecutor;
78
+ groupOperationExecutor;
79
+ msgOperationExecutor;
80
+ fsOperationExecutor;
54
81
  dingtalkContactBindExecutor;
55
82
  feishuContactBindExecutor;
56
83
  qqbotContactBindExecutor;
57
84
  wecomContactBindExecutor;
58
85
  wechatContactBindExecutor;
86
+ controlToken;
59
87
  // CPU 占用追踪:IPC handler 是一次性同步调用,无法在响应里做 200ms 异步采样,
60
88
  // 故用后台 1s interval 累积 process.cpuUsage() 增量,handler 直接读最近值。
61
89
  // procCpuPercent = 本 daemon 进程占单核的百分比(可 >100% 仅当多核,已 clamp 到 100);
@@ -78,6 +106,10 @@ export class IpcServer {
78
106
  setAgentRegistry(registry) {
79
107
  this.agentRegistry = registry;
80
108
  }
109
+ /** Authenticate local control-plane callers that cannot use task delegation. */
110
+ setControlToken(token) {
111
+ this.controlToken = Buffer.from(token);
112
+ }
81
113
  /** Inject menu.* executor (ECWeb Control proxies menu requests through this) */
82
114
  setMenuExecutor(executor) {
83
115
  this.menuExecutor = executor;
@@ -88,6 +120,18 @@ export class IpcServer {
88
120
  setContactOperationExecutor(executor) {
89
121
  this.contactOperationExecutor = executor;
90
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
+ }
91
135
  setAgentReloadExecutor(executor) {
92
136
  this.agentReloadExecutor = executor;
93
137
  }
@@ -119,6 +163,15 @@ export class IpcServer {
119
163
  setQueueActionExecutor(executor) {
120
164
  this.queueActionExecutor = executor;
121
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
+ }
122
175
  /** Inject daemon-level trigger executor for ec trigger. */
123
176
  setTriggerExecutor(executor) {
124
177
  this.triggerExecutor = executor;
@@ -131,6 +184,9 @@ export class IpcServer {
131
184
  setTaskRuntimeContextProvider(provider) {
132
185
  this.taskRuntimeContextProvider = provider;
133
186
  }
187
+ setCodexPreToolUseExecutor(executor) {
188
+ this.codexPreToolUseExecutor = executor;
189
+ }
134
190
  /** Inject daemon-backed AUN private message sender for in-task `ec msg send`. */
135
191
  setAunMsgSender(sender) {
136
192
  this.aunMsgSender = sender;
@@ -323,9 +379,31 @@ export class IpcServer {
323
379
  }
324
380
  }
325
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
+ }
326
385
  switch (cmd.type) {
327
386
  case 'status':
328
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
+ }
329
407
  case 'ping':
330
408
  return { pong: true, pid: process.pid, protocolVersion: 1 };
331
409
  case 'bind.begin': {
@@ -454,6 +532,53 @@ export class IpcServer {
454
532
  sessionKey: cmd.sessionKey,
455
533
  });
456
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
+ }
457
582
  case 'aun-aid-stats-record-outbound': {
458
583
  if (!this.aunAidStatsRecorder)
459
584
  return { ok: false, error: 'recorder not configured' };
@@ -519,14 +644,17 @@ export class IpcServer {
519
644
  }
520
645
  : undefined;
521
646
  try {
647
+ const normalizedPayload = payload && Object.prototype.hasOwnProperty.call(payload, 'mentions')
648
+ ? { ...payload, mentions: normalizeAunMentionEntries(payload.mentions) }
649
+ : payload;
522
650
  return await this.aunMsgSender({
523
651
  aid: cmd.aid,
524
652
  to: cmd.to,
525
653
  scope: cmd.scope === 'group' ? 'group' : 'msg',
526
- payload,
654
+ payload: normalizedPayload,
527
655
  file,
528
- mentions: Array.isArray(cmd.mentions)
529
- ? cmd.mentions.filter((entry) => !!entry && typeof entry === 'object' && !Array.isArray(entry))
656
+ mentions: Object.prototype.hasOwnProperty.call(cmd, 'mentions')
657
+ ? normalizeAunMentionEntries(cmd.mentions)
530
658
  : undefined,
531
659
  encrypt: typeof cmd.encrypt === 'boolean' ? cmd.encrypt : undefined,
532
660
  thread: typeof cmd.thread === 'string' ? cmd.thread : undefined,
@@ -539,7 +667,7 @@ export class IpcServer {
539
667
  });
540
668
  }
541
669
  catch (e) {
542
- return { ok: false, error: e?.message || String(e) };
670
+ return { ok: false, error: e?.message || String(e), code: e?.code };
543
671
  }
544
672
  }
545
673
  case 'handoff-return': {
@@ -568,6 +696,8 @@ export class IpcServer {
568
696
  return this.handoffStatusExecutor({
569
697
  sessionId: typeof cmd.sessionId === 'string' ? cmd.sessionId : undefined,
570
698
  handoffId: cmd.handoffId,
699
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
700
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
571
701
  });
572
702
  }
573
703
  case 'handoff-list': {
@@ -587,6 +717,8 @@ export class IpcServer {
587
717
  state: state,
588
718
  filterSessionId: typeof cmd.filterSessionId === 'string' ? cmd.filterSessionId : undefined,
589
719
  limit,
720
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
721
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
590
722
  });
591
723
  }
592
724
  case 'handoff-trace': {
@@ -604,6 +736,8 @@ export class IpcServer {
604
736
  agent: typeof cmd.agent === 'string' ? cmd.agent : undefined,
605
737
  handoffId: cmd.handoffId,
606
738
  limit,
739
+ delegationToken: typeof cmd.delegationToken === 'string' ? cmd.delegationToken : undefined,
740
+ delegationCommandHash: typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined,
607
741
  });
608
742
  }
609
743
  case 'ctl': {
@@ -638,6 +772,54 @@ export class IpcServer {
638
772
  }
639
773
  return await this.contactOperationExecutor(argv, sessionId, delegationToken, typeof cmd.delegationCommandHash === 'string' ? cmd.delegationCommandHash : undefined);
640
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
+ }
641
823
  case 'trigger.list':
642
824
  case 'trigger.show':
643
825
  case 'trigger.history':
@@ -684,7 +866,12 @@ export class IpcServer {
684
866
  if (!this.bootstrapCompleteExecutor)
685
867
  return { ok: false, error: 'bootstrap complete executor not configured' };
686
868
  try {
687
- return await this.bootstrapCompleteExecutor(aid);
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
+ });
688
875
  }
689
876
  catch (e) {
690
877
  return { ok: false, error: e?.message || String(e) };
@@ -747,7 +934,11 @@ export class IpcServer {
747
934
  if (!this.menuExecutor)
748
935
  return { ok: false, error: 'menu.exec not configured' };
749
936
  try {
750
- 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
+ });
751
942
  return { ok: true, response };
752
943
  }
753
944
  catch (e) {
@@ -831,17 +1022,35 @@ export class IpcServer {
831
1022
  return { error: `unknown command: ${cmd.type}` };
832
1023
  }
833
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
+ }
834
1032
  }
835
1033
  /**
836
1034
  * Query the running EvolCore daemon via Unix socket.
837
1035
  * Returns null if the service is not running or the socket is unreachable.
838
1036
  */
839
1037
  export function ipcQuery(socketPath, cmd, timeoutMs = 3000) {
840
- const delegationCommandHash = typeof cmd.delegationToken === 'string'
841
- && 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'
842
1051
  ? hashCurrentDelegatedCommand()
843
1052
  : undefined;
844
- const request = delegationCommandHash ? { ...cmd, delegationCommandHash } : cmd;
1053
+ const request = delegationCommandHash ? { ...authenticatedCmd, delegationCommandHash } : authenticatedCmd;
845
1054
  return new Promise((resolve) => {
846
1055
  const conn = net.connect(socketPath);
847
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 = () => {
@@ -0,0 +1,90 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { resolvePaths } from '../paths.js';
4
+ import { getProcessStartTime } from './process-introspect.js';
5
+ const CODEX_APP_SERVER_RECORD_PREFIX = 'codex-app-server-';
6
+ const CODEX_APP_SERVER_RECORD_SUFFIX = '.json';
7
+ function recordFilePath(pid) {
8
+ return path.join(resolvePaths().instanceDir, `${CODEX_APP_SERVER_RECORD_PREFIX}${pid}${CODEX_APP_SERVER_RECORD_SUFFIX}`);
9
+ }
10
+ function writeAtomic(filePath, data) {
11
+ const temporaryPath = `${filePath}.tmp`;
12
+ fs.writeFileSync(temporaryPath, data, { mode: 0o600 });
13
+ fs.renameSync(temporaryPath, filePath);
14
+ }
15
+ /** Register a Codex app-server child owned by this EvolCore runtime. */
16
+ export function registerCodexAppServerProcess(pid) {
17
+ if (!Number.isInteger(pid) || pid <= 0)
18
+ throw new Error(`Invalid Codex app-server PID: ${pid}`);
19
+ const startedAt = getProcessStartTime(pid) ?? Date.now();
20
+ const filePath = recordFilePath(pid);
21
+ const record = {
22
+ owner: 'evolcore',
23
+ pid,
24
+ startedAt,
25
+ startedAtIso: new Date(startedAt).toISOString(),
26
+ parentPid: process.pid,
27
+ };
28
+ fs.mkdirSync(resolvePaths().instanceDir, { recursive: true });
29
+ writeAtomic(filePath, JSON.stringify(record, null, 2));
30
+ return filePath;
31
+ }
32
+ /** Remove a process record. Safe to call after a crash or a failed spawn. */
33
+ export function unregisterCodexAppServerProcess(filePath) {
34
+ if (!filePath)
35
+ return;
36
+ try {
37
+ fs.unlinkSync(filePath);
38
+ }
39
+ catch { }
40
+ try {
41
+ fs.unlinkSync(`${filePath}.tmp`);
42
+ }
43
+ catch { }
44
+ }
45
+ /** Read only well-formed records; malformed records are removed as stale. */
46
+ export function listCodexAppServerProcesses() {
47
+ const directory = resolvePaths().instanceDir;
48
+ let names;
49
+ try {
50
+ names = fs.readdirSync(directory);
51
+ }
52
+ catch {
53
+ return [];
54
+ }
55
+ const entries = [];
56
+ for (const name of names) {
57
+ if (!new RegExp(`^${CODEX_APP_SERVER_RECORD_PREFIX}\\d+\\${CODEX_APP_SERVER_RECORD_SUFFIX}$`).test(name))
58
+ continue;
59
+ const filePath = path.join(directory, name);
60
+ try {
61
+ const value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
62
+ const pid = value.pid;
63
+ const startedAt = value.startedAt;
64
+ const parentPid = value.parentPid;
65
+ if (value.owner !== 'evolcore'
66
+ || typeof pid !== 'number' || !Number.isInteger(pid) || pid <= 0
67
+ || typeof startedAt !== 'number' || !Number.isFinite(startedAt) || startedAt <= 0
68
+ || typeof parentPid !== 'number' || !Number.isInteger(parentPid) || parentPid <= 0) {
69
+ unregisterCodexAppServerProcess(filePath);
70
+ continue;
71
+ }
72
+ entries.push({
73
+ record: {
74
+ owner: 'evolcore',
75
+ pid,
76
+ startedAt,
77
+ startedAtIso: typeof value.startedAtIso === 'string'
78
+ ? value.startedAtIso
79
+ : new Date(startedAt).toISOString(),
80
+ parentPid,
81
+ },
82
+ filePath,
83
+ });
84
+ }
85
+ catch {
86
+ unregisterCodexAppServerProcess(filePath);
87
+ }
88
+ }
89
+ return entries;
90
+ }
@@ -35,5 +35,9 @@ export function execCodexCliSync(args, options) {
35
35
  const launchCommand = resolveCodexLaunchCommand(args, { commandPath });
36
36
  if (!launchCommand)
37
37
  throw new Error('Codex CLI not found');
38
- return execFileSync(launchCommand.command, launchCommand.args, options);
38
+ return execFileSync(launchCommand.command, launchCommand.args, {
39
+ ...(options ?? {}),
40
+ // Keep the Windows console policy authoritative for every Codex CLI call.
41
+ windowsHide: process.platform === 'win32',
42
+ });
39
43
  }