evolcore 0.0.21 → 0.0.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/AGENTS.md +62 -0
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/bin/codex-managed-acl.ps1 +155 -0
  5. package/bin/codex-managed-hook.mjs +3 -0
  6. package/bin/install-codex-managed-hooks.mjs +376 -96
  7. package/dist/agents/baseagent-availability.js +48 -0
  8. package/dist/agents/baseagent.js +1 -1
  9. package/dist/agents/claude-runner.js +44 -226
  10. package/dist/agents/{codex-app-server-client.js → codex/app-server-client.js} +103 -17
  11. package/dist/{utils/codex-app-server-registry.js → agents/codex/app-server-registry.js} +2 -2
  12. package/dist/agents/codex/catalog.js +70 -0
  13. package/dist/agents/codex/diagnostics.js +154 -0
  14. package/dist/agents/codex-runner.js +1292 -219
  15. package/dist/agents/ecagent-runner.js +10 -31
  16. package/dist/agents/gemini-runner.js +2 -6
  17. package/dist/aun/aid/store.js +2 -0
  18. package/dist/aun/menu-client.js +272 -0
  19. package/dist/aun/msg/group.js +65 -4
  20. package/dist/aun/msg/index.js +1 -1
  21. package/dist/aun/msg/managed-operation.js +51 -4
  22. package/dist/aun/outbox.js +7 -0
  23. package/dist/aun/rpc/client.js +8 -1
  24. package/dist/channels/aun.js +240 -36
  25. package/dist/channels/wechat.js +81 -10
  26. package/dist/cli/aun-commands.js +48 -4
  27. package/dist/cli/command-log.js +21 -2
  28. package/dist/cli/daemon-commands.js +169 -51
  29. package/dist/cli/index.js +20 -0
  30. package/dist/cli/init.js +2 -10
  31. package/dist/cli/menu-client.js +95 -0
  32. package/dist/cli/model.js +23 -8
  33. package/dist/cli/task-context.js +16 -2
  34. package/dist/cli/trigger-command.js +22 -5
  35. package/dist/config/config-field-policy.js +3 -0
  36. package/dist/config/config-manager.js +32 -5
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/schema-migration.js +14 -2
  39. package/dist/core/auth/agent-delegation.js +15 -0
  40. package/dist/core/auth/auth-gateway.js +8 -0
  41. package/dist/core/auth/authorization-audit.js +70 -6
  42. package/dist/core/auth/operation-catalog.js +4 -0
  43. package/dist/core/auth/remote-access-store.js +135 -0
  44. package/dist/core/bootstrap-messages.js +8 -0
  45. package/dist/core/bootstrap-service.js +93 -25
  46. package/dist/core/command/command-handler.js +62 -5
  47. package/dist/core/command/daemon-diagnostics.js +448 -0
  48. package/dist/core/command/menu-catalog.js +3 -0
  49. package/dist/core/command/menu-handler.js +150 -33
  50. package/dist/core/command/menu-protocol.js +120 -1
  51. package/dist/core/command/slash-gate.js +3 -3
  52. package/dist/core/command/slash-handler.js +83 -40
  53. package/dist/core/data-migration.js +11 -1
  54. package/dist/core/event-catalog.js +32 -0
  55. package/dist/core/handoff/runtime.js +23 -3
  56. package/dist/core/message/im-renderer.js +7 -3
  57. package/dist/core/message/message-bridge.js +60 -2
  58. package/dist/core/message/message-log.js +33 -0
  59. package/dist/core/message/message-queue.js +41 -15
  60. package/dist/core/message/response-engine.js +246 -114
  61. package/dist/core/model/catalog-service.js +308 -0
  62. package/dist/core/model/model-catalog.js +124 -334
  63. package/dist/core/model/model-diagnostics.js +4 -5
  64. package/dist/core/model/model-fallback.js +9 -16
  65. package/dist/core/model/model-permission.js +1 -1
  66. package/dist/core/permission/approval-gateway.js +16 -1
  67. package/dist/core/permission/ec-command-parser.js +272 -70
  68. package/dist/core/permission/protected-paths.js +74 -14
  69. package/dist/core/permission/sandbox-runtime.js +4 -2
  70. package/dist/core/permission/tool-error-code.js +12 -0
  71. package/dist/core/permission/tool-policy.js +290 -30
  72. package/dist/core/runtime-lock.js +13 -8
  73. package/dist/core/session/session-manager.js +30 -0
  74. package/dist/core/session/session-renew.js +18 -1
  75. package/dist/core/session/session-turn-coordinator.js +5 -1
  76. package/dist/index.js +104 -34
  77. package/dist/ipc.js +218 -96
  78. package/dist/paths.js +18 -0
  79. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  80. package/dist/stats/price-resolver.js +4 -0
  81. package/dist/trigger/feedback.js +14 -2
  82. package/dist/trigger/manager.js +3 -1
  83. package/dist/trigger/parser.js +86 -13
  84. package/dist/trigger/patch.js +104 -1
  85. package/dist/trigger/scheduler.js +20 -3
  86. package/dist/utils/ecweb-supervisor.js +11 -2
  87. package/dist/utils/ecweb-utils.js +8 -26
  88. package/dist/utils/error-utils.js +7 -0
  89. package/dist/utils/logger.js +9 -4
  90. package/dist/utils/npm-ops.js +47 -15
  91. package/dist/utils/tool-summary.js +59 -0
  92. package/dist/utils/windows-shell-trust.js +201 -0
  93. package/kits/docs/evolcore/INDEX.md +3 -2
  94. package/kits/docs/evolcore/agent-create.md +159 -0
  95. package/kits/docs/evolcore/agent.md +6 -0
  96. package/kits/docs/evolcore/group-collaboration.md +256 -0
  97. package/kits/docs/evolcore/group-rules.md +1 -19
  98. package/kits/docs/evolcore/group.md +3 -1
  99. package/kits/docs/evolcore/menu-client.md +34 -0
  100. package/kits/docs/evolcore/trigger.md +6 -3
  101. package/kits/docs/prompt-loading-architecture.md +6 -0
  102. package/kits/eck_message_manifest.json +6 -6
  103. package/kits/migrations/migrate-contact-book-v2.mjs +9 -1
  104. package/kits/migrations/migrate-role-config-v4.mjs +11 -5
  105. package/kits/migrations/migrate-role-config-v5.mjs +8 -2
  106. package/kits/schemas/_meta.json +3 -2
  107. package/kits/schemas/agent-config.schema.12.json +427 -0
  108. package/kits/templates/message-fragments/item.md +1 -1
  109. package/kits/templates/roles/visitor.json +1 -0
  110. package/kits/templates/system-fragments/bootstrap.md +10 -2
  111. package/kits/templates/system-fragments/commands.md +2 -2
  112. package/package.json +11 -3
@@ -101,6 +101,8 @@ export class MessageBridge {
101
101
  processOwnersProvider;
102
102
  debouncers = new Map();
103
103
  defaultDebounce;
104
+ /** Serializes session selection/renewal with inbound log admission per route. */
105
+ admissionLocks = new Map();
104
106
  agentRegistry;
105
107
  bootstrapService;
106
108
  menuDeduper = new MenuRequestDeduper();
@@ -724,6 +726,7 @@ export class MessageBridge {
724
726
  register(channelName, onMessage, sendReply, adapter, channelType) {
725
727
  const effectiveChannelType = channelType || channelName;
726
728
  onMessage(async (msg) => {
729
+ let releaseAdmission;
727
730
  let releaseSessionIngress;
728
731
  const releaseIngress = () => {
729
732
  releaseSessionIngress?.();
@@ -754,6 +757,12 @@ export class MessageBridge {
754
757
  const conversationId = chatType === 'group' ? (msg.groupId || msg.channelId) : msg.peerId;
755
758
  const selfAid = msg.selfAID || owningAgent?.aid || parsedChannelKey?.selfAID;
756
759
  const resolvedChannelType = msg.channelType || parsedChannelKey?.type || effectiveChannelType;
760
+ const admissionRoute = [
761
+ resolvedChannelType,
762
+ selfAid || '',
763
+ msg.channelId,
764
+ msg.threadId || '',
765
+ ].join('#');
757
766
  const trustedHandoffEcho = msg.source === 'handoff'
758
767
  && resolvedChannelType === 'aun'
759
768
  && !!selfAid
@@ -1034,6 +1043,12 @@ export class MessageBridge {
1034
1043
  && !executionPermissionOverride
1035
1044
  && !parseFullAccessCommand(cmdContent).matched
1036
1045
  && this.cmdHandler.isCommand(cmdContent);
1046
+ if (isCmd && /^\/new(?:\s|$)/i.test(cmdContent)) {
1047
+ // /new changes the active main-session boundary. Serialize it with
1048
+ // ordinary session admission so an in-flight renew cannot append or
1049
+ // enqueue against a superseded session after /new commits.
1050
+ releaseAdmission = await this.acquireAdmission(admissionRoute);
1051
+ }
1037
1052
  if (isCmd) {
1038
1053
  logger.debug(`[MessageBridge] Command detected: "${cmdContent}", routing to handler`);
1039
1054
  // 命令也要记录入方向 jsonl(不创建 session,直接用 chatDirPath 计算路径)
@@ -1115,6 +1130,11 @@ export class MessageBridge {
1115
1130
  // 通道找不到归属时退回到 globalConfig(一般是测试场景)
1116
1131
  const effectiveProjectPath = owningAgent?.projectPath
1117
1132
  ?? this.defaultProjectPath;
1133
+ // Session renewal and inbound history append form one admission
1134
+ // transaction. Without this per-route single-flight, a second message
1135
+ // can observe the just-renewed empty session before the first message
1136
+ // has been appended and create another renewed session.
1137
+ releaseAdmission = await this.acquireAdmission(admissionRoute);
1118
1138
  const hadMainSession = msg.threadId ? true : this.sessionManager.hasMainSession(channelName, msg.channelId, msg.channelType || effectiveChannelType, msg.selfAID || selfAid);
1119
1139
  let session = await this.sessionManager.getOrCreateSession(channelName, msg.channelId, effectiveProjectPath, msg.threadId, Object.keys(metadata).length ? metadata : undefined, this.extractTopicName(msg), msg.peerId, chatType, owningAgent?.baseagent, msg.selfAID, msg.channelType || effectiveChannelType, msg.peerType, identity);
1120
1140
  if (session.threadId && typeof this.messageQueue.reserveSessionIngress === 'function') {
@@ -1244,6 +1264,12 @@ export class MessageBridge {
1244
1264
  }
1245
1265
  if (!handoffCandidate)
1246
1266
  appendMessageLog(inboundEntry.chatDir, inboundEntry.entry);
1267
+ // The authoritative session choice is now paired with its first
1268
+ // conversation record. Release before waiting for queue completion so
1269
+ // later group messages can still exercise the normal interrupt/FIFO
1270
+ // policy instead of being serialized behind the whole model turn.
1271
+ releaseAdmission?.();
1272
+ releaseAdmission = undefined;
1247
1273
  // 6. ACK + debounce/enqueue
1248
1274
  // ACK 在到达时立即做(每条独立 ACK),不等合并
1249
1275
  // Interrupt 模式(单聊)→ 入队前 debounce 合并
@@ -1305,10 +1331,28 @@ export class MessageBridge {
1305
1331
  logger.error(`[MessageBridge] Error in onMessage handler for ${channelName}:`, error);
1306
1332
  }
1307
1333
  finally {
1334
+ releaseAdmission?.();
1308
1335
  releaseIngress();
1309
1336
  }
1310
1337
  });
1311
1338
  }
1339
+ async acquireAdmission(key) {
1340
+ const previous = this.admissionLocks.get(key) ?? Promise.resolve();
1341
+ let release;
1342
+ const current = new Promise(resolve => { release = resolve; });
1343
+ const tail = previous.then(() => current);
1344
+ this.admissionLocks.set(key, tail);
1345
+ await previous;
1346
+ let released = false;
1347
+ return () => {
1348
+ if (released)
1349
+ return;
1350
+ released = true;
1351
+ release();
1352
+ if (this.admissionLocks.get(key) === tail)
1353
+ this.admissionLocks.delete(key);
1354
+ };
1355
+ }
1312
1356
  // ── Menu Protocol ──
1313
1357
  extractTopicName(msg) {
1314
1358
  const raw = msg.topicName
@@ -1717,7 +1761,22 @@ export class MessageBridge {
1717
1761
  if (!this.cmdHandler.isCommand(content))
1718
1762
  return false;
1719
1763
  logger.info(`[${channel}] ${channelId}: ${content}${source === 'card-trigger' ? ' [card]' : ''}`);
1720
- const cmdResult = await this.cmdHandler.handle(content, channel, channelId, (_cid, text, opts) => sendReply(text), userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext);
1764
+ const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
1765
+ const cmdResult = await this.cmdHandler.handle(content, channel, channelId, async (_cid, text, opts, updatePayload) => {
1766
+ if (updatePayload && adapter?.send) {
1767
+ const envelope = buildEnvelope({
1768
+ taskId: `cmd-update-${randomBytes(5).toString('hex')}`,
1769
+ channel,
1770
+ channelId,
1771
+ agentName: this.agentRegistry?.resolveByChannel(channel)?.name ?? '<unknown>',
1772
+ chatmode: 'interactive',
1773
+ replyContext: opts ?? replyContext,
1774
+ });
1775
+ await adapter.send(envelope, updatePayload);
1776
+ return;
1777
+ }
1778
+ await sendReply(text, opts, updatePayload);
1779
+ }, userId, threadId, chatType, source, messageId, selfAID, authSubject?.identity, authSubject, replyContext);
1721
1780
  logger.debug(`[MessageBridge] handleCommand: result type=${typeof cmdResult}`);
1722
1781
  if (cmdResult === undefined)
1723
1782
  return false;
@@ -1735,7 +1794,6 @@ export class MessageBridge {
1735
1794
  return true;
1736
1795
  }
1737
1796
  // 出站走 adapter.send 统一入口
1738
- const adapter = this.processor.getChannelInfo?.(channel)?.adapter;
1739
1797
  let responseSessionId;
1740
1798
  try {
1741
1799
  const responseSession = threadId
@@ -233,6 +233,39 @@ export function messageLogPath(chatDir) {
233
233
  export function hasMessageLogOperation(chatDir, operationId) {
234
234
  return findMessageLogOperationMessageId(chatDir, operationId) !== undefined;
235
235
  }
236
+ /** Find the newest completed outbound operation whose ID starts with prefix. */
237
+ export function findLatestMessageLogOperation(chatDir, operationPrefix) {
238
+ const file = messageLogPath(chatDir);
239
+ if (!fs.existsSync(file))
240
+ return undefined;
241
+ let latest;
242
+ try {
243
+ for (const line of fs.readFileSync(file, 'utf-8').split('\n')) {
244
+ if (!line.trim())
245
+ continue;
246
+ try {
247
+ const entry = JSON.parse(line);
248
+ if (entry?.dir !== 'out'
249
+ || typeof entry.operationId !== 'string'
250
+ || (!entry.operationId.startsWith(operationPrefix))
251
+ || typeof entry.msgId !== 'string'
252
+ || !entry.msgId)
253
+ continue;
254
+ const ts = typeof entry.ts === 'number' && Number.isFinite(entry.ts) ? entry.ts : 0;
255
+ if (!latest || ts >= latest.ts) {
256
+ latest = { messageId: entry.msgId, operationId: entry.operationId, ts };
257
+ }
258
+ }
259
+ catch {
260
+ // skip malformed message log lines
261
+ }
262
+ }
263
+ }
264
+ catch {
265
+ return undefined;
266
+ }
267
+ return latest;
268
+ }
236
269
  export function findMessageLogOperationMessageId(chatDir, operationId) {
237
270
  const file = messageLogPath(chatDir);
238
271
  if (!fs.existsSync(file))
@@ -88,6 +88,17 @@ export class MessageQueue {
88
88
  this.markActiveTerminal(event.sessionId, event.reason || 'interrupted');
89
89
  }
90
90
  });
91
+ subscribe.call(eventBus, 'task:started', event => {
92
+ if (!('sessionId' in event) || !event.sessionId)
93
+ return;
94
+ const taskEvent = event;
95
+ for (const [queueKey, state] of this.activeStates) {
96
+ if (this.matchesSession(queueKey, taskEvent.sessionId)) {
97
+ state.taskId = taskEvent.taskId;
98
+ state.generation = taskEvent.generation;
99
+ }
100
+ }
101
+ });
91
102
  subscribe.call(eventBus, 'task:completed', event => {
92
103
  if ('sessionId' in event && event.sessionId)
93
104
  this.markActiveTerminal(event.sessionId, 'completed');
@@ -223,6 +234,7 @@ export class MessageQueue {
223
234
  }
224
235
  const tmp = `${this.dedupPath}.tmp-${process.pid}-${Date.now()}-${this.enqueueSequence}`;
225
236
  let fd;
237
+ let renamed = false;
226
238
  try {
227
239
  fs.mkdirSync(path.dirname(this.dedupPath), { recursive: true });
228
240
  const content = Buffer.from(JSON.stringify({
@@ -242,20 +254,22 @@ export class MessageQueue {
242
254
  fs.closeSync(fd);
243
255
  fd = undefined;
244
256
  fs.renameSync(tmp, this.dedupPath);
245
- const persistedFd = fs.openSync(this.dedupPath, 'r');
246
- try {
247
- fs.fsyncSync(persistedFd);
248
- }
249
- finally {
250
- fs.closeSync(persistedFd);
251
- }
257
+ renamed = true;
252
258
  if (process.platform !== 'win32') {
253
- const directoryFd = fs.openSync(path.dirname(this.dedupPath), 'r');
254
259
  try {
255
- fs.fsyncSync(directoryFd);
260
+ const directoryFd = fs.openSync(path.dirname(this.dedupPath), 'r');
261
+ try {
262
+ fs.fsyncSync(directoryFd);
263
+ }
264
+ finally {
265
+ fs.closeSync(directoryFd);
266
+ }
256
267
  }
257
- finally {
258
- fs.closeSync(directoryFd);
268
+ catch (error) {
269
+ // The file contents were already fsynced before rename. A platform
270
+ // that cannot fsync directory metadata must not make a committed
271
+ // dedup claim look uncommitted to the caller.
272
+ logger.warn(`[Queue] Durable dedup ledger directory fsync unavailable after commit: ${error instanceof Error ? error.message : String(error)}`);
259
273
  }
260
274
  }
261
275
  return true;
@@ -268,11 +282,13 @@ export class MessageQueue {
268
282
  }
269
283
  catch { }
270
284
  }
271
- try {
272
- fs.unlinkSync(tmp);
285
+ if (!renamed) {
286
+ try {
287
+ fs.unlinkSync(tmp);
288
+ }
289
+ catch { }
273
290
  }
274
- catch { }
275
- return false;
291
+ return renamed;
276
292
  }
277
293
  }
278
294
  hasEnqueuedMessage(sessionKey, messageId, selfAID) {
@@ -623,6 +639,8 @@ export class MessageQueue {
623
639
  sessionId: sessionKey,
624
640
  reason: 'daemon_restart',
625
641
  agentName,
642
+ taskId: this.activeStates.get(queueKey)?.taskId,
643
+ generation: this.activeStates.get(queueKey)?.generation,
626
644
  causation: item.message.causation,
627
645
  });
628
646
  barriers.push(this.triggerInterrupt(queueKey, sessionKey, this.activeStates.get(queueKey)?.baseagent, agentName, 'daemon_restart'));
@@ -1022,6 +1040,8 @@ export class MessageQueue {
1022
1040
  sessionId: sessionKey,
1023
1041
  reason: 'new_message',
1024
1042
  agentName: this.processingAgent.get(queueKey),
1043
+ taskId: this.activeStates.get(queueKey)?.taskId,
1044
+ generation: this.activeStates.get(queueKey)?.generation,
1025
1045
  causation: this.activeBatches.get(queueKey)?.message.causation,
1026
1046
  });
1027
1047
  if (this.interruptCallback) {
@@ -1518,6 +1538,8 @@ export class MessageQueue {
1518
1538
  sessionId: sessionKey,
1519
1539
  reason: 'recalled',
1520
1540
  agentName,
1541
+ taskId: activeState?.taskId,
1542
+ generation: activeState?.generation,
1521
1543
  causation: active?.message.causation,
1522
1544
  });
1523
1545
  const barrier = this.interruptCallback
@@ -1766,6 +1788,8 @@ export class MessageQueue {
1766
1788
  sessionId: sessionKey,
1767
1789
  reason: 'stop',
1768
1790
  agentName: name,
1791
+ taskId: this.activeStates.get(queueKey)?.taskId,
1792
+ generation: this.activeStates.get(queueKey)?.generation,
1769
1793
  causation: this.activeBatches.get(queueKey)?.message.causation,
1770
1794
  });
1771
1795
  if (this.interruptCallback) {
@@ -2038,6 +2062,8 @@ export class MessageQueue {
2038
2062
  sessionId: sessionKey,
2039
2063
  reason: 'new_message',
2040
2064
  agentName: targetAgentName,
2065
+ taskId: this.activeStates.get(targetQueueKey)?.taskId,
2066
+ generation: this.activeStates.get(targetQueueKey)?.generation,
2041
2067
  causation: this.activeBatches.get(targetQueueKey)?.message.causation,
2042
2068
  });
2043
2069
  if (this.interruptCallback) {