evolcore 0.0.3 → 0.0.5

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 (148) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +45 -10
  3. package/bin/ec-safe-output.js +89 -24
  4. package/dist/agents/baseagent.js +46 -0
  5. package/dist/agents/claude-runner.js +1 -31
  6. package/dist/agents/codex-app-server-client.js +68 -0
  7. package/dist/agents/codex-runner.js +157 -5
  8. package/dist/agents/runner-types.js +2 -2
  9. package/dist/aun/aid/agentmd.js +79 -0
  10. package/dist/aun/aid/encryption-seed-policy.js +29 -0
  11. package/dist/aun/aid/index.js +1 -1
  12. package/dist/aun/aid/store.js +2 -5
  13. package/dist/channels/aun.js +220 -112
  14. package/dist/channels/daemon.js +18 -4
  15. package/dist/channels/dingtalk.js +52 -137
  16. package/dist/channels/feishu.js +56 -4
  17. package/dist/channels/qqbot.js +23 -1
  18. package/dist/channels/wechat.js +303 -155
  19. package/dist/channels/wecom.js +383 -7
  20. package/dist/cli/aun-commands.js +11 -11
  21. package/dist/cli/daemon-commands.js +46 -15
  22. package/dist/cli/handoff-command.js +2 -1
  23. package/dist/cli/init-channel.js +185 -67
  24. package/dist/cli/init.js +49 -27
  25. package/dist/cli/trigger-command.js +92 -11
  26. package/dist/config/access-policy-domain.js +18 -0
  27. package/dist/config/access-policy.js +110 -0
  28. package/dist/config/builtin-role-templates.js +27 -14
  29. package/dist/config/builtin-roles.js +25 -6
  30. package/dist/config/config-field-policy.js +17 -5
  31. package/dist/config/config-manager.js +198 -22
  32. package/dist/config/config-operation-service.js +35 -38
  33. package/dist/config/contact-bind-code.js +274 -0
  34. package/dist/config/contact-book-store.js +173 -5
  35. package/dist/config/contact-book.js +32 -0
  36. package/dist/config/contact-operation-service.js +9 -3
  37. package/dist/config/contact-request-service.js +331 -0
  38. package/dist/config/peer-role-resolver.js +3 -1
  39. package/dist/config/schema-registry.js +49 -24
  40. package/dist/config-store.js +14 -2
  41. package/dist/core/auth/agent-delegation.js +105 -11
  42. package/dist/core/auth/authorization-audit.js +6 -0
  43. package/dist/core/auth/operation-authorizer.js +8 -0
  44. package/dist/core/auth/operation-catalog.js +51 -3
  45. package/dist/core/auth/trigger-authorization.js +15 -0
  46. package/dist/core/bootstrap-service.js +2 -9
  47. package/dist/core/channel-loader.js +6 -2
  48. package/dist/core/command/command-handler.js +10 -13
  49. package/dist/core/command/connect-menu.js +229 -26
  50. package/dist/core/command/evol-menu-version-gate.js +38 -0
  51. package/dist/core/command/group-menu.js +421 -0
  52. package/dist/core/command/menu-handler.js +269 -85
  53. package/dist/core/command/menu-protocol.js +8 -2
  54. package/dist/core/command/menu-token-store.js +102 -0
  55. package/dist/core/command/role-menu.js +16 -0
  56. package/dist/core/command/slash-gate.js +1 -1
  57. package/dist/core/command/slash-handler.js +158 -31
  58. package/dist/core/daemon-file-cache.js +28 -0
  59. package/dist/core/event-catalog.js +29 -0
  60. package/dist/core/evolagent-registry.js +4 -39
  61. package/dist/core/handoff/runtime.js +162 -37
  62. package/dist/core/handoff/store.js +46 -2
  63. package/dist/core/handoff/types.js +1 -0
  64. package/dist/core/inference/text-inference.js +16 -74
  65. package/dist/core/message/file-markers.js +7 -0
  66. package/dist/core/message/im-renderer.js +18 -14
  67. package/dist/core/message/inbound-admission.js +134 -0
  68. package/dist/core/message/message-bridge.js +616 -74
  69. package/dist/core/message/message-log.js +1 -0
  70. package/dist/core/message/message-queue.js +185 -10
  71. package/dist/core/message/peer-mode.js +7 -8
  72. package/dist/core/message/response-engine.js +381 -79
  73. package/dist/core/permission/approval-gateway.js +17 -10
  74. package/dist/core/permission/ec-command-parser.js +101 -46
  75. package/dist/core/permission/tool-policy.js +69 -24
  76. package/dist/core/protected-paths.js +36 -11
  77. package/dist/core/session/session-fs-store.js +19 -4
  78. package/dist/core/session/session-manager.js +232 -4
  79. package/dist/core/session/session-mapper.js +2 -0
  80. package/dist/core/session/session-renew.js +125 -69
  81. package/dist/core/session/session-turn-coordinator.js +5 -1
  82. package/dist/eck/kit-renderer.js +12 -1
  83. package/dist/eck/message-renderer.js +79 -1
  84. package/dist/index.js +224 -89
  85. package/dist/ipc.js +81 -4
  86. package/dist/paths.js +3 -0
  87. package/dist/response-system/config-resolver.js +29 -0
  88. package/dist/response-system/coordinator.js +8 -32
  89. package/dist/response-system/engines/v1/proactive-flow.js +1 -1
  90. package/dist/response-system/index.js +1 -0
  91. package/dist/response-system/modes/single-session/index.js +7 -4
  92. package/dist/trigger/parser.js +55 -15
  93. package/dist/trigger/patch.js +4 -1
  94. package/dist/trigger/scheduler.js +441 -39
  95. package/dist/utils/cross-platform.js +8 -2
  96. package/dist/utils/error-dict.json +7 -0
  97. package/dist/utils/evolcore-version.js +21 -0
  98. package/dist/utils/logger.js +2 -2
  99. package/dist/utils/process-introspect.js +19 -3
  100. package/dist/utils/stable-semver.js +21 -0
  101. package/dist/utils/stats.js +33 -9
  102. package/kits/docs/channels/aun.md +4 -13
  103. package/kits/docs/evolcore/INDEX.md +1 -1
  104. package/kits/docs/evolcore/config.md +47 -2
  105. package/kits/docs/evolcore/contact.md +8 -3
  106. package/kits/docs/evolcore/group-rules.md +46 -4
  107. package/kits/docs/evolcore/group.md +4 -4
  108. package/kits/docs/evolcore/msg.md +5 -5
  109. package/kits/docs/evolcore/trigger.md +25 -8
  110. package/kits/docs/path-registry.md +36 -17
  111. package/kits/eck_message_manifest.json +12 -1
  112. package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
  113. package/kits/rules/01-overview.md +17 -7
  114. package/kits/rules/02-navigation.md +38 -18
  115. package/kits/rules/03-identity.md +28 -24
  116. package/kits/rules/04-relation.md +44 -28
  117. package/kits/rules/05-venue.md +31 -15
  118. package/kits/rules/06-channel.md +11 -7
  119. package/kits/schemas/_meta.json +18 -7
  120. package/kits/schemas/agent-config.schema.7.json +303 -0
  121. package/kits/schemas/agent-config.schema.8.json +304 -0
  122. package/kits/schemas/agent-config.schema.9.json +364 -0
  123. package/kits/schemas/contact-book.schema.3.json +67 -0
  124. package/kits/schemas/daemon.schema.1.json +3 -2
  125. package/kits/schemas/daemon.schema.2.json +101 -0
  126. package/kits/schemas/daemon.schema.3.json +123 -0
  127. package/kits/schemas/defaults.schema.2.json +85 -0
  128. package/kits/schemas/defaults.schema.3.json +73 -0
  129. package/kits/schemas/relation-config.schema.6.json +46 -0
  130. package/kits/schemas/relation-config.schema.7.json +59 -0
  131. package/kits/schemas/single-session.schema.2.json +57 -0
  132. package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
  133. package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
  134. package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
  135. package/kits/templates/roles/admin.json +46 -0
  136. package/kits/templates/roles/member.json +4 -0
  137. package/kits/templates/roles/visitor.json +4 -0
  138. package/kits/templates/system-fragments/channel.md +12 -2
  139. package/kits/templates/system-fragments/identity.md +3 -1
  140. package/kits/templates/system-fragments/relation.md +1 -1
  141. package/kits/templates/system-fragments/session.md +6 -2
  142. package/package.json +4 -2
  143. package/MIGRATION-0.5.0.md +0 -378
  144. package/ROLE_ACCESS_CONTROL.md +0 -174
  145. package/dist/channels/contact-bind-code.js +0 -134
  146. package/dist/channels/wecom-card.js +0 -101
  147. package/dist/channels/wecom-onboarding.js +0 -82
  148. package/dist/channels/wecom-state.js +0 -191
@@ -320,6 +320,7 @@ export function buildOutboundEntry(opts) {
320
320
  usage: opts.usage ?? null,
321
321
  encrypt: opts.encrypt,
322
322
  chatmode: opts.chatmode,
323
+ transport: opts.transport,
323
324
  peerType: opts.peerType,
324
325
  source: opts.source ?? 'daemon',
325
326
  handoff_trace: opts.handoff_trace,
@@ -14,6 +14,7 @@ export class MessageQueue {
14
14
  activeStates = new Map(); // queueKey → active task state
15
15
  interruptCallback;
16
16
  interruptBarriers = new Map();
17
+ activeTerminalReasons = new Map();
17
18
  eventBus;
18
19
  recentMessageIds = new Set();
19
20
  DEDUP_WINDOW = 60_000; // 1 分钟窗口
@@ -24,6 +25,8 @@ export class MessageQueue {
24
25
  processingStartTime = new Map(); // queueKey → 处理开始时间戳
25
26
  queueKeyToSessionKey = new Map(); // queueKey → sessionKey(human-readable 格式)
26
27
  droppedTriggerSessionIds = new Set();
28
+ shuttingDown = false;
29
+ shutdownPromise;
27
30
  /** 逻辑队列桥接:管理出队顺序(响应模式可注入自定义策略,默认 FIFO) */
28
31
  logicalQueue = new LogicalQueueBridge();
29
32
  constructor(handler, options) {
@@ -43,7 +46,15 @@ export class MessageQueue {
43
46
  return existing;
44
47
  if (!this.interruptCallback)
45
48
  return Promise.resolve();
46
- const barrier = this.interruptCallback(sessionKey, baseagent, evolagentName, reason)
49
+ this.activeTerminalReasons.set(queueKey, reason);
50
+ let interruption;
51
+ try {
52
+ interruption = Promise.resolve(this.interruptCallback(sessionKey, baseagent, evolagentName, reason));
53
+ }
54
+ catch (error) {
55
+ interruption = Promise.reject(error);
56
+ }
57
+ const barrier = interruption
47
58
  .catch(error => {
48
59
  logger.warn(`[Queue] Interrupt barrier failed for ${queueKey}: ${error instanceof Error ? error.message : String(error)}`);
49
60
  })
@@ -57,6 +68,29 @@ export class MessageQueue {
57
68
  }
58
69
  setEventBus(eventBus) {
59
70
  this.eventBus = eventBus;
71
+ const subscribe = eventBus.subscribe;
72
+ if (typeof subscribe !== 'function')
73
+ return;
74
+ subscribe.call(eventBus, 'task:interrupted', event => {
75
+ if ('sessionId' in event && event.sessionId) {
76
+ this.markActiveTerminal(event.sessionId, event.reason || 'interrupted');
77
+ }
78
+ });
79
+ subscribe.call(eventBus, 'task:completed', event => {
80
+ if ('sessionId' in event && event.sessionId)
81
+ this.markActiveTerminal(event.sessionId, 'completed');
82
+ });
83
+ subscribe.call(eventBus, 'task:error', event => {
84
+ if ('sessionId' in event && event.sessionId)
85
+ this.markActiveTerminal(event.sessionId, 'error');
86
+ });
87
+ }
88
+ /** 标记 active handler 已有业务终态,避免 shutdown 将其重新放回恢复队列。 */
89
+ markActiveTerminal(sessionId, reason) {
90
+ for (const queueKey of this.activeBatches.keys()) {
91
+ if (this.matchesSession(queueKey, sessionId))
92
+ this.activeTerminalReasons.set(queueKey, reason);
93
+ }
60
94
  }
61
95
  /**
62
96
  * 注册一次性消息拦截器:下一条来自 sessionKey 的消息不入队、不触发 interrupt,
@@ -213,7 +247,9 @@ export class MessageQueue {
213
247
  images: undefined,
214
248
  mentions: undefined,
215
249
  mentionAids: undefined,
216
- messageId: undefined,
250
+ // Retain the inbound anchor so the resumed task can replace the
251
+ // daemon-restart CrossMark with its current processing state.
252
+ messageId: source.messageId,
217
253
  batchRole: item.role,
218
254
  items: [{
219
255
  kind: 'restart-resume',
@@ -326,6 +362,69 @@ export class MessageQueue {
326
362
  persistQueuesImmediate() {
327
363
  this.persistQueuesSync();
328
364
  }
365
+ /**
366
+ * 冻结当前队列并优雅中断所有正在执行的主任务。
367
+ *
368
+ * activeBatches 会原样保留到进程退出,供下次启动恢复主消息;runner 内部
369
+ * 子任务不进入队列,也不在这里重建。task:interrupted 必须同步发布,确保
370
+ * ResponseEngine 在任何 handler continuation 之前先使旧 turn lease 失效。
371
+ */
372
+ shutdownGracefully(timeoutMs = 6_500) {
373
+ if (this.shutdownPromise)
374
+ return this.shutdownPromise;
375
+ this.shuttingDown = true;
376
+ const allActive = Array.from(this.activeBatches.entries());
377
+ const active = allActive.filter(([queueKey]) => !this.activeTerminalReasons.has(queueKey));
378
+ const barriers = [];
379
+ // 已因 stop/new_message/timeout/recalled 进入中断的任务不应被 restart 恢复。
380
+ // pending 队列仍照常保留,下一次启动只处理尚未消费的后续输入。
381
+ for (const [queueKey] of allActive) {
382
+ if (!this.activeTerminalReasons.has(queueKey))
383
+ continue;
384
+ this.activeBatches.delete(queueKey);
385
+ const existingBarrier = this.interruptBarriers.get(queueKey);
386
+ if (existingBarrier)
387
+ barriers.push(existingBarrier);
388
+ }
389
+ const persisted = this.persistQueuesSync() || this.persistQueuesSync();
390
+ if (!persisted) {
391
+ logger.error('[Queue] CRITICAL: graceful shutdown snapshot could not be persisted');
392
+ }
393
+ for (const [queueKey, item] of active) {
394
+ const sessionKey = this.sessionKeyFromQueueKey(queueKey);
395
+ const agentName = this.processingAgent.get(queueKey) || item.agentName;
396
+ this.eventBus?.publish({
397
+ type: 'task:interrupted',
398
+ sessionId: sessionKey,
399
+ reason: 'daemon_restart',
400
+ agentName,
401
+ causation: item.message.causation,
402
+ });
403
+ barriers.push(this.triggerInterrupt(queueKey, sessionKey, this.activeStates.get(queueKey)?.baseagent, agentName, 'daemon_restart'));
404
+ }
405
+ this.shutdownPromise = (async () => {
406
+ let timedOut = false;
407
+ let timer;
408
+ if (barriers.length > 0) {
409
+ await Promise.race([
410
+ Promise.allSettled(barriers),
411
+ new Promise(resolve => {
412
+ timer = setTimeout(() => { timedOut = true; resolve(); }, Math.max(1, timeoutMs));
413
+ }),
414
+ ]);
415
+ }
416
+ if (timer)
417
+ clearTimeout(timer);
418
+ if (timedOut) {
419
+ logger.warn(`[Queue] Graceful shutdown interrupt barrier timed out after ${timeoutMs}ms`);
420
+ }
421
+ // 发布 interrupt 前的同步写盘是唯一恢复点。runner 收尾可能继续修改
422
+ // handler 持有的 message 对象,不能在这里用可变对象覆盖冻结快照。
423
+ logger.info(`[Queue] Graceful shutdown froze ${active.length} active task(s)`);
424
+ return active.length;
425
+ })();
426
+ return this.shutdownPromise;
427
+ }
329
428
  persistQueuesSync() {
330
429
  if (!this.persistencePath)
331
430
  return true;
@@ -430,10 +529,15 @@ export class MessageQueue {
430
529
  if (rawParts.length === 0)
431
530
  continue;
432
531
  const first = rawParts[0];
433
- const submittedPart = first.message.handoffDelivery
434
- ? this.restoredPart(first)
435
- : this.restoreSubmittedPart(first);
436
- const submittedItem = this.itemFromPart(submittedPart);
532
+ const restoredOriginParts = rawParts.length > 1
533
+ && rawParts.every(part => this.isOriginHandoff(part.message))
534
+ ? rawParts.map(part => this.restoredPart(part))
535
+ : undefined;
536
+ const submittedItem = restoredOriginParts
537
+ ? this.buildCoalescedItem(restoredOriginParts)
538
+ : this.itemFromPart(first.message.handoffDelivery
539
+ ? this.restoredPart(first)
540
+ : this.restoreSubmittedPart(first));
437
541
  submittedItem.message.timestamp = first.message.timestamp;
438
542
  queue.push(submittedItem);
439
543
  for (const part of rawParts) {
@@ -505,6 +609,11 @@ export class MessageQueue {
505
609
  return false;
506
610
  }
507
611
  async enqueue(sessionKey, message, projectPath, options) {
612
+ if (this.shuttingDown) {
613
+ const error = new Error('Daemon is shutting down');
614
+ error.code = 'DAEMON_SHUTTING_DOWN';
615
+ return Promise.reject(error);
616
+ }
508
617
  if (options?.signal?.aborted) {
509
618
  return Promise.reject(abortError(options.signal.reason));
510
619
  }
@@ -680,6 +789,11 @@ export class MessageQueue {
680
789
  this.processing.add(queueKey);
681
790
  logger.info(`[Queue] processNext: start key=${queueKey}`);
682
791
  while (true) {
792
+ if (this.shuttingDown) {
793
+ this.processing.delete(queueKey);
794
+ this.processingAgent.delete(queueKey);
795
+ return;
796
+ }
683
797
  const interruptBarrier = this.interruptBarriers.get(queueKey);
684
798
  if (interruptBarrier) {
685
799
  logger.debug(`[Queue] Waiting for interrupt barrier on ${queueKey}`);
@@ -691,6 +805,11 @@ export class MessageQueue {
691
805
  logger.debug(`[Queue] Waiting for external lock on ${queueKey}`);
692
806
  await lock;
693
807
  }
808
+ if (this.shuttingDown) {
809
+ this.processing.delete(queueKey);
810
+ this.processingAgent.delete(queueKey);
811
+ return;
812
+ }
694
813
  const queue = this.queues.get(queueKey);
695
814
  if (!queue || queue.length === 0) {
696
815
  logger.info(`[Queue] processNext: queue empty, releasing key=${queueKey}`);
@@ -766,10 +885,18 @@ export class MessageQueue {
766
885
  rejects.forEach(r => r(error));
767
886
  }
768
887
  finally {
769
- this.activeBatches.delete(queueKey);
770
- this.processingStartTime.delete(queueKey);
771
- this.activeStates.delete(queueKey);
772
- this.persistQueuesImmediate();
888
+ if (!this.shuttingDown) {
889
+ this.activeBatches.delete(queueKey);
890
+ this.processingStartTime.delete(queueKey);
891
+ this.activeStates.delete(queueKey);
892
+ this.activeTerminalReasons.delete(queueKey);
893
+ this.persistQueuesImmediate();
894
+ }
895
+ }
896
+ if (this.shuttingDown) {
897
+ this.processing.delete(queueKey);
898
+ this.processingAgent.delete(queueKey);
899
+ return;
773
900
  }
774
901
  }
775
902
  }
@@ -781,6 +908,15 @@ export class MessageQueue {
781
908
  dequeueGreedy(queue) {
782
909
  const first = queue.shift();
783
910
  const result = [first];
911
+ if (this.isOriginHandoff(first.message)) {
912
+ while (queue.length > 0 && this.canMergeOriginHandoffs(first, queue[0])) {
913
+ result.push(queue.shift());
914
+ }
915
+ if (result.length > 1) {
916
+ logger.info(`[Queue] Greedy dequeue: merged ${result.length} pending origin handoff result(s)`);
917
+ }
918
+ return result;
919
+ }
784
920
  if (this.isMergeBarrier(first.message))
785
921
  return result;
786
922
  const mergeSubmittedResume = first.message.restartResume?.submitted === true;
@@ -833,6 +969,24 @@ export class MessageQueue {
833
969
  isMergeBarrier(message) {
834
970
  return !!message.handoffDelivery || !!message.triggerMeta || !!message.causation?.trigger?.path.length;
835
971
  }
972
+ isOriginHandoff(message) {
973
+ return message.source === 'handoff' && message.handoffDelivery?.direction === 'origin';
974
+ }
975
+ canMergeOriginHandoffs(first, next) {
976
+ if (!this.isOriginHandoff(next.message))
977
+ return false;
978
+ if (first.projectPath !== next.projectPath || first.agentName !== next.agentName)
979
+ return false;
980
+ const a = first.message.handoffDelivery?.originAuthorization;
981
+ const b = next.message.handoffDelivery?.originAuthorization;
982
+ if (!a || !b)
983
+ return a === b;
984
+ return a.actorId === b.actorId
985
+ && a.channelKey === b.channelKey
986
+ && a.channelType === b.channelType
987
+ && a.chatType === b.chatType
988
+ && a.peerKey === b.peerKey;
989
+ }
836
990
  firstTimestamp(item) {
837
991
  const parts = this.partsOf(item);
838
992
  let ts = Number.POSITIVE_INFINITY;
@@ -869,6 +1023,9 @@ export class MessageQueue {
869
1023
  const seenSourceMessageIds = new Set();
870
1024
  let hasRestartResume = false;
871
1025
  let hasMessagesAfterRestartResume = false;
1026
+ const originHandoffIds = [];
1027
+ const seenOriginHandoffIds = new Set();
1028
+ const allOriginHandoffs = items.every(item => this.isOriginHandoff(item.message));
872
1029
  for (const item of items) {
873
1030
  const m = item.message;
874
1031
  if (hasRestartResume && !m.restartResume?.submitted) {
@@ -882,6 +1039,17 @@ export class MessageQueue {
882
1039
  allImages.push(...m.images);
883
1040
  if (m.mentions)
884
1041
  allMentions.push(...m.mentions);
1042
+ if (allOriginHandoffs && m.handoffDelivery) {
1043
+ const ids = m.handoffDelivery.handoffIds?.length
1044
+ ? m.handoffDelivery.handoffIds
1045
+ : [m.handoffDelivery.handoffId];
1046
+ for (const id of ids) {
1047
+ if (!seenOriginHandoffIds.has(id)) {
1048
+ seenOriginHandoffIds.add(id);
1049
+ originHandoffIds.push(id);
1050
+ }
1051
+ }
1052
+ }
885
1053
  for (const id of [...(m.sourceMessageIds ?? []), ...(m.messageId ? [m.messageId] : [])]) {
886
1054
  if (!seenSourceMessageIds.has(id)) {
887
1055
  seenSourceMessageIds.add(id);
@@ -939,6 +1107,13 @@ export class MessageQueue {
939
1107
  restartResume: hasRestartResume
940
1108
  ? { submitted: true, pendingInterrupted: hasMessagesAfterRestartResume }
941
1109
  : undefined,
1110
+ handoffDelivery: allOriginHandoffs && originHandoffIds.length > 0
1111
+ ? {
1112
+ ...last.message.handoffDelivery,
1113
+ handoffId: originHandoffIds[0],
1114
+ handoffIds: originHandoffIds,
1115
+ }
1116
+ : last.message.handoffDelivery,
942
1117
  };
943
1118
  return {
944
1119
  message: merged,
@@ -77,21 +77,20 @@ export function resolveChatModeForPeer(params) {
77
77
  const field = chatmodeFieldForPeer(params.chatType, params.peerType);
78
78
  return params.configured?.[field] ?? chatmodeFactoryDefaults()[field];
79
79
  }
80
- let _factoryChatmode = null;
81
80
  /**
82
81
  * chatMode 出厂默认表 —— 唯一来源是 agent-config schema 的 chatmode.default
83
82
  * (配置数据,非代码分支)。agent 级配置改默认表、关系级按键覆盖,均经
84
83
  * ConfigManager 合并后以 `configured` 传入;此表仅在配置链完全未设值时兜底。
84
+ *
85
+ * 不做二级 memo:`loadSchema` 自己走 FileCache,这里再存一份等于绕过
86
+ * invalidateKitCache——schema 换了新的,出厂默认表却永久停在进程启动那一刻。
85
87
  */
86
88
  export function chatmodeFactoryDefaults() {
87
- if (!_factoryChatmode) {
88
- const d = loadSchema('agent-config').fields.get('chatmode')?.default;
89
- if (!d?.private || !d?.group || !d?.nothuman) {
90
- throw new Error('[peer-mode] agent-config schema 缺少 chatmode 出厂默认表(default.private/group/nothuman)');
91
- }
92
- _factoryChatmode = d;
89
+ const d = loadSchema('agent-config').fields.get('chatmode')?.default;
90
+ if (!d?.private || !d?.group || !d?.nothuman) {
91
+ throw new Error('[peer-mode] agent-config schema 缺少 chatmode 出厂默认表(default.private/group/nothuman)');
93
92
  }
94
- return _factoryChatmode;
93
+ return { ...d }; // d 挂在缓存的 schema 对象上,拷一层避免调用方改写污染缓存
95
94
  }
96
95
  export function chatmodeFieldForPeer(chatType, peerType) {
97
96
  const kind = normalizePeerType(peerType);