evolcore 0.0.28 → 0.0.29-test.0

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 (183) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/bin/ec-safe-output.js +270 -54
  3. package/dist/agents/baseagent-availability.js +37 -1
  4. package/dist/agents/baseagent.js +19 -9
  5. package/dist/agents/claude-runner.js +171 -13
  6. package/dist/agents/codex/app-server-client.js +6 -2
  7. package/dist/agents/codex/diagnostics.js +4 -3
  8. package/dist/agents/codex-runner.js +396 -95
  9. package/dist/agents/gemini-runner.js +35 -17
  10. package/dist/aun/group/history.js +130 -90
  11. package/dist/aun/group/operations.js +1 -3
  12. package/dist/aun/menu/transport.js +5 -3
  13. package/dist/aun/msg/managed-operation.js +70 -0
  14. package/dist/aun/msg/p2p.js +1 -3
  15. package/dist/aun/msg/upload.js +2 -2
  16. package/dist/aun/outbox.js +1 -37
  17. package/dist/channels/aun.js +249 -89
  18. package/dist/channels/daemon.js +18 -1
  19. package/dist/channels/feishu.js +1 -2
  20. package/dist/cli/agent-command.js +23 -3
  21. package/dist/cli/agent-create.js +68 -2
  22. package/dist/cli/agent.js +12 -2
  23. package/dist/cli/aun-commands.js +87 -13
  24. package/dist/cli/cli-argv.js +26 -0
  25. package/dist/cli/daemon-commands.js +40 -10
  26. package/dist/cli/data-command.js +4 -4
  27. package/dist/cli/handoff-command.js +2 -0
  28. package/dist/cli/index.js +18 -1
  29. package/dist/cli/managed-command-guard.js +10 -0
  30. package/dist/cli/memory-command.js +441 -0
  31. package/dist/cli/restart-monitor.js +62 -6
  32. package/dist/cli/skill-command.js +41 -0
  33. package/dist/cli/task-context.js +2 -23
  34. package/dist/cli/trigger-command.js +9 -1
  35. package/dist/cli/watch-msg-diag.js +209 -0
  36. package/dist/config/config-field-policy.js +52 -3
  37. package/dist/config/config-manager.js +17 -4
  38. package/dist/config/contact-book-store.js +2 -2
  39. package/dist/config/mention-mode.js +7 -2
  40. package/dist/config/merge.js +28 -9
  41. package/dist/config/resolved-config-op.js +1 -1
  42. package/dist/config/runtime-env.js +137 -0
  43. package/dist/config/schema-registry.js +11 -1
  44. package/dist/config-store.js +10 -10
  45. package/dist/core/audit/event-key.js +3 -1
  46. package/dist/core/audit/log-integrity.js +4 -0
  47. package/dist/core/auth/agent-delegation.js +2 -1
  48. package/dist/core/auth/auth-gateway.js +13 -6
  49. package/dist/core/auth/authorization-audit.js +54 -19
  50. package/dist/core/auth/operation-authorizer.js +17 -0
  51. package/dist/core/auth/operation-catalog.js +9 -0
  52. package/dist/core/causation/context.js +5 -1
  53. package/dist/core/command/agent-control.js +1 -2
  54. package/dist/core/command/command-handler.js +51 -23
  55. package/dist/core/command/memory-slash.js +270 -0
  56. package/dist/core/command/menu-catalog.js +1 -0
  57. package/dist/core/command/menu-handler.js +315 -60
  58. package/dist/core/command/menu-protocol.js +42 -4
  59. package/dist/core/command/slash-gate.js +10 -10
  60. package/dist/core/command/slash-handler.js +206 -64
  61. package/dist/core/data-migration.js +282 -505
  62. package/dist/core/event-catalog.js +26 -0
  63. package/dist/core/evolagent-registry.js +6 -0
  64. package/dist/core/evolagent.js +0 -10
  65. package/dist/core/handoff/store.js +1 -20
  66. package/dist/core/inference/text-inference.js +57 -1
  67. package/dist/core/memory/consolidation-context.js +505 -0
  68. package/dist/core/memory/consolidation-ops-records.js +124 -0
  69. package/dist/core/memory/consolidation-trigger.js +394 -0
  70. package/dist/core/memory/index-manager.js +308 -0
  71. package/dist/core/memory/launch-consolidation.js +395 -0
  72. package/dist/core/memory/memory-admin.js +234 -0
  73. package/dist/core/memory/memory-document.js +100 -0
  74. package/dist/core/memory/memory-session-policy.js +41 -0
  75. package/dist/core/memory/memory-signature.js +257 -0
  76. package/dist/core/memory/memory-writer.js +432 -0
  77. package/dist/core/memory/staleness.js +117 -0
  78. package/dist/core/message/auto-mention-gate.js +454 -0
  79. package/dist/core/message/im-renderer.js +69 -16
  80. package/dist/core/message/message-bridge.js +287 -21
  81. package/dist/core/message/message-log.js +1 -0
  82. package/dist/core/message/message-queue.js +114 -27
  83. package/dist/core/message/pending-hints.js +329 -161
  84. package/dist/core/message/response-engine.js +567 -80
  85. package/dist/core/permission/ec-command-parser.js +1029 -80
  86. package/dist/core/permission/protected-paths.js +376 -126
  87. package/dist/core/permission/sandbox-runtime.js +40 -12
  88. package/dist/core/permission/tool-error-code.js +96 -13
  89. package/dist/core/permission/tool-policy.js +276 -23
  90. package/dist/core/role/runtime-policy.js +3 -7
  91. package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
  92. package/dist/core/session/session-fs-store.js +0 -76
  93. package/dist/core/session/session-idle-monitor.js +355 -0
  94. package/dist/core/session/session-manager.js +478 -17
  95. package/dist/core/session/session-mapper.js +33 -16
  96. package/dist/core/session/session-renew.js +217 -155
  97. package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
  98. package/dist/core/skill/global-watcher.js +282 -0
  99. package/dist/core/skill/index-generator.js +84 -0
  100. package/dist/core/skill/startup.js +73 -0
  101. package/dist/core/skill/switch-resolver.js +115 -0
  102. package/dist/core/task/task-activity-store.js +219 -0
  103. package/dist/eck/kit-renderer.js +30 -3
  104. package/dist/eck/manifest-engine.js +26 -4
  105. package/dist/index.js +708 -18
  106. package/dist/ipc.js +564 -2
  107. package/dist/paths.js +30 -9
  108. package/dist/response-system/engines/v1/proactive-flow.js +116 -15
  109. package/dist/response-system/modes/single-session/index.js +3 -0
  110. package/dist/trigger/feedback.js +1 -0
  111. package/dist/trigger/manager.js +0 -10
  112. package/dist/trigger/scheduler.js +11 -0
  113. package/dist/trigger/validation.js +22 -0
  114. package/dist/utils/avatar-upload.js +59 -18
  115. package/dist/utils/error-dict.json +6 -3
  116. package/dist/utils/error-utils.js +27 -0
  117. package/dist/utils/logger.js +32 -10
  118. package/dist/utils/media-cache.js +84 -0
  119. package/dist/utils/msg-diag.js +97 -0
  120. package/dist/utils/welcome.js +37 -8
  121. package/kits/docs/AUTHORING.md +314 -0
  122. package/kits/docs/GUIDE.md +2 -0
  123. package/kits/docs/INDEX.md +2 -0
  124. package/kits/docs/aun/CHEATSHEET.md +4 -0
  125. package/kits/docs/aun/SDK.md +60 -0
  126. package/kits/docs/context-assembly.md +10 -6
  127. package/kits/docs/evolcore/INDEX.md +17 -3
  128. package/kits/docs/evolcore/agent-create.md +24 -0
  129. package/kits/docs/evolcore/agent.md +5 -2
  130. package/kits/docs/evolcore/config.md +1 -1
  131. package/kits/docs/evolcore/ec-capabilities.md +364 -0
  132. package/kits/docs/evolcore/group.md +34 -18
  133. package/kits/docs/evolcore/memory-architecture.md +78 -0
  134. package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
  135. package/kits/docs/evolcore/memory-consolidation.md +263 -0
  136. package/kits/docs/evolcore/memory-tags.md +63 -0
  137. package/kits/docs/evolcore/memory.md +110 -0
  138. package/kits/docs/evolcore/menu-client.md +27 -11
  139. package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
  140. package/kits/docs/evolcore/menu-protocol.md +213 -0
  141. package/kits/docs/evolcore/msg.md +2 -0
  142. package/kits/docs/evolcore/trigger.md +7 -5
  143. package/kits/docs/path-registry.md +6 -8
  144. package/kits/docs/prompt-loading-architecture.md +5 -3
  145. package/kits/docs/venues/group.md +5 -4
  146. package/kits/eck_manifest.json +294 -88
  147. package/kits/eck_manifest.memory.json +257 -0
  148. package/kits/rules/03-identity.md +8 -8
  149. package/kits/rules/05-venue.md +4 -2
  150. package/kits/rules/06-channel.md +1 -1
  151. package/kits/schemas/_meta.json +276 -47
  152. package/kits/schemas/agent-config.schema.12.json +38 -2
  153. package/kits/schemas/agent-config.schema.13.json +591 -0
  154. package/kits/schemas/daemon.schema.6.json +9 -9
  155. package/kits/schemas/defaults.schema.5.json +29 -0
  156. package/kits/schemas/relation-config.schema.8.json +14 -1
  157. package/kits/schemas/relation-config.schema.9.json +129 -0
  158. package/kits/schemas/role-config.schema.1.json +3 -3
  159. package/kits/skills/.switches.json +3 -0
  160. package/kits/skills/ec-skill/SKILL.md +217 -0
  161. package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
  162. package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
  163. package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
  164. package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
  165. package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
  166. package/kits/templates/decision/auto-mention.md +25 -0
  167. package/kits/templates/decision/session-renew.md +22 -0
  168. package/kits/templates/memory-consolidation/forget.md +104 -0
  169. package/kits/templates/memory-consolidation/generalize.md +76 -0
  170. package/kits/templates/memory-consolidation/notes.md +133 -0
  171. package/kits/templates/memory-consolidation/profile.md +58 -0
  172. package/kits/templates/memory-consolidation/promote.md +72 -0
  173. package/kits/templates/memory-consolidation/workflow.md +75 -0
  174. package/kits/templates/system-fragments/bootstrap.md +14 -5
  175. package/kits/templates/system-fragments/commands.md +1 -0
  176. package/kits/templates/system-fragments/group-history.md +6 -0
  177. package/kits/templates/system-fragments/identity.md +0 -3
  178. package/kits/templates/system-fragments/memory.md +79 -0
  179. package/kits/templates/system-fragments/reply.md +1 -0
  180. package/kits/templates/system-fragments/venue.md +1 -1
  181. package/package.json +4 -6
  182. package/dist/trigger/legacy-session-history.js +0 -19
  183. package/kits/templates/system-fragments/group-unread-history.md +0 -7
@@ -19,6 +19,7 @@ export class MessageQueue {
19
19
  activeStates = new Map(); // queueKey → active task state
20
20
  interruptCallback;
21
21
  interruptBarriers = new Map();
22
+ interruptFailures = new Map();
22
23
  activeTerminalReasons = new Map();
23
24
  eventBus;
24
25
  recentMessageIds = new Set();
@@ -53,7 +54,7 @@ export class MessageQueue {
53
54
  setInterruptCallback(callback) {
54
55
  this.interruptCallback = callback;
55
56
  }
56
- triggerInterrupt(queueKey, sessionKey, baseagent, evolagentName, reason, timeout) {
57
+ triggerInterrupt(queueKey, sessionKey, baseagent, evolagentName, reason, timeout, strict) {
57
58
  const existing = this.interruptBarriers.get(queueKey);
58
59
  if (existing)
59
60
  return existing;
@@ -62,15 +63,20 @@ export class MessageQueue {
62
63
  this.activeTerminalReasons.set(queueKey, reason);
63
64
  let interruption;
64
65
  try {
65
- interruption = Promise.resolve(timeout
66
- ? this.interruptCallback(sessionKey, baseagent, evolagentName, reason, timeout)
67
- : this.interruptCallback(sessionKey, baseagent, evolagentName, reason));
66
+ interruption = Promise.resolve(strict
67
+ ? this.interruptCallback(sessionKey, baseagent, evolagentName, reason, timeout, true)
68
+ : timeout
69
+ ? this.interruptCallback(sessionKey, baseagent, evolagentName, reason, timeout)
70
+ : this.interruptCallback(sessionKey, baseagent, evolagentName, reason));
68
71
  }
69
72
  catch (error) {
70
73
  interruption = Promise.reject(error);
71
74
  }
72
75
  const barrier = interruption
73
76
  .catch(error => {
77
+ this.interruptFailures.set(queueKey, error instanceof Error ? error : new Error(String(error)));
78
+ const cleanup = setTimeout(() => this.interruptFailures.delete(queueKey), 60_000);
79
+ cleanup.unref?.();
74
80
  logger.warn(`[Queue] Interrupt barrier failed for ${queueKey}: ${error instanceof Error ? error.message : String(error)}`);
75
81
  })
76
82
  .finally(() => {
@@ -379,25 +385,35 @@ export class MessageQueue {
379
385
  }
380
386
  messageIdsFor(item) {
381
387
  return this.partsOf(item)
382
- .map(part => part.message.messageId)
388
+ .flatMap(part => [...(part.message.sourceMessageIds ?? []), part.message.messageId])
383
389
  .filter((id) => !!id);
384
390
  }
385
391
  partMatchesMessage(part, messageId, selfAID, queueKey) {
386
- if (part.message.messageId !== messageId)
392
+ if (part.message.messageId !== messageId && !part.message.sourceMessageIds?.includes(messageId))
387
393
  return false;
388
394
  if (selfAID === undefined)
389
395
  return true;
390
396
  const messageScope = part.message.selfAID ?? (queueKey ? this.selfAIDFromQueueKey(queueKey) : undefined);
391
397
  return messageScope === selfAID;
392
398
  }
399
+ partMatchesScope(part, scope) {
400
+ if (!scope)
401
+ return true;
402
+ const message = part.message;
403
+ if (scope.channelId !== undefined && message.channelId !== scope.channelId)
404
+ return false;
405
+ if (scope.groupId !== undefined && message.groupId !== scope.groupId)
406
+ return false;
407
+ if (scope.threadId !== undefined && (message.threadId || '') !== (scope.threadId || ''))
408
+ return false;
409
+ return true;
410
+ }
393
411
  peerIdsFor(item) {
394
412
  return this.partsOf(item).map(part => part.message.peerId);
395
413
  }
396
414
  serializePart(part, fallbackEnqueuedAt) {
397
415
  const enqueuedAt = part.enqueuedAt ?? part.message.enqueuedAt ?? fallbackEnqueuedAt ?? Date.now();
398
- const message = part.message.enqueuedAt === enqueuedAt
399
- ? part.message
400
- : { ...part.message, enqueuedAt };
416
+ const message = { ...part.message, enqueuedAt };
401
417
  return {
402
418
  message,
403
419
  projectPath: part.projectPath,
@@ -408,9 +424,7 @@ export class MessageQueue {
408
424
  }
409
425
  serializeItem(item, fallbackEnqueuedAt) {
410
426
  const enqueuedAt = item.enqueuedAt ?? item.message.enqueuedAt ?? fallbackEnqueuedAt ?? Date.now();
411
- const message = item.message.enqueuedAt === enqueuedAt
412
- ? item.message
413
- : { ...item.message, enqueuedAt };
427
+ const message = { ...item.message, enqueuedAt };
414
428
  return {
415
429
  message,
416
430
  projectPath: item.projectPath,
@@ -580,6 +594,8 @@ export class MessageQueue {
580
594
  canDequeueGroupTimeline(first, next) {
581
595
  if (first.role === undefined || next.role === undefined)
582
596
  return false;
597
+ if (first.message.attentionPolicy !== next.message.attentionPolicy)
598
+ return false;
583
599
  if ((first.agentName || DEFAULT_AGENT_NAME) !== (next.agentName || DEFAULT_AGENT_NAME))
584
600
  return false;
585
601
  if (path.resolve(first.projectPath) !== path.resolve(next.projectPath))
@@ -678,14 +694,17 @@ export class MessageQueue {
678
694
  const buckets = [];
679
695
  const activeBuckets = [];
680
696
  for (const [queueKey, queue] of this.queues.entries()) {
681
- if (queue.length === 0)
697
+ const durable = queue.filter(item => item.message.source !== 'owner-inject');
698
+ if (durable.length === 0)
682
699
  continue;
683
700
  buckets.push({
684
701
  queueKey,
685
- items: queue.map(item => this.serializeItem(item)),
702
+ items: durable.map(item => this.serializeItem(item)),
686
703
  });
687
704
  }
688
705
  for (const [queueKey, item] of this.activeBatches.entries()) {
706
+ if (item.message.source === 'owner-inject')
707
+ continue;
689
708
  activeBuckets.push({
690
709
  queueKey,
691
710
  items: [this.serializeItem(item)],
@@ -872,6 +891,8 @@ export class MessageQueue {
872
891
  return sessionIds;
873
892
  }
874
893
  shouldRestoreMessage(message, sessionId) {
894
+ if (message.source === 'owner-inject')
895
+ return false;
875
896
  // The Trigger scheduler records open runs as daemon_restart. Replaying their
876
897
  // queued work afterwards would execute side effects for an already-failed run.
877
898
  if (message.executionPermissionOverride)
@@ -896,7 +917,7 @@ export class MessageQueue {
896
917
  message = { ...message, selfAID: options.selfAID };
897
918
  }
898
919
  // 消息去重检查
899
- if (!this.shouldProcess(sessionKey, message, options?.selfAID)) {
920
+ if (message.source !== 'owner-inject' && !this.shouldProcess(sessionKey, message, options?.selfAID)) {
900
921
  options?.onPersisted?.();
901
922
  return Promise.resolve();
902
923
  }
@@ -920,6 +941,19 @@ export class MessageQueue {
920
941
  const isProcessing = this.processing.has(queueKey);
921
942
  const currentQueue = this.queues.get(queueKey);
922
943
  logger.info(`[Queue] enqueue: key=${queueKey} processing=${isProcessing} queueLen=${currentQueue?.length ?? 0} pending=${currentQueue?.length ?? 0} rawPending=${currentQueue ? this.queueRawMessageCount(currentQueue) : 0} agent=${agentName} role=${options?.role ?? '<none>'} peer=${message.peerId ?? '<none>'} msg=${message.messageId ?? '<none>'}`);
944
+ // Do not await diagnostic logging before the queue entry is admitted. The
945
+ // enqueue API must expose the message to concurrent session barriers in the
946
+ // same turn; yielding here creates a window where recovery can observe an
947
+ // empty queue and enqueue a duplicate task.
948
+ void import('../../utils/msg-diag.js').then(({ diagLog }) => {
949
+ diagLog('queue_enqueue', {
950
+ aid: message.selfAID ?? agentName,
951
+ from: message.peerId ?? '',
952
+ to: message.selfAID ?? agentName,
953
+ msgId: message.messageId ?? '',
954
+ text: message.content ?? '',
955
+ });
956
+ }).catch(() => { });
923
957
  return new Promise((resolve, reject) => {
924
958
  let settled = false;
925
959
  let aborting = false;
@@ -967,8 +1001,14 @@ export class MessageQueue {
967
1001
  timestamp: message.receivedAt ?? message.timestamp ?? enqueuedAt,
968
1002
  };
969
1003
  const queuedItem = { message, projectPath, agentName, role: options?.role, resolve: queuedResolve, reject: queuedReject, enqueuedAt };
970
- queue.push(queuedItem);
971
- this.logicalQueue.enqueue(queueKey, message); // 通知逻辑队列记录出队顺序
1004
+ if (options?.priority) {
1005
+ const firstOrdinary = queue.findIndex(item => item.message.source !== 'owner-inject');
1006
+ queue.splice(firstOrdinary < 0 ? queue.length : firstOrdinary, 0, queuedItem);
1007
+ }
1008
+ else
1009
+ queue.push(queuedItem);
1010
+ if (message.source !== 'owner-inject' && message.attentionPolicy !== 'interrupt')
1011
+ this.logicalQueue.enqueue(queueKey, message);
972
1012
  abortHandler = () => {
973
1013
  if (settled || aborting)
974
1014
  return;
@@ -1009,7 +1049,8 @@ export class MessageQueue {
1009
1049
  // A duplicate must not re-enter a runner after the queue entry has
1010
1050
  // survived its write-ahead snapshot. Keep this separate from the queue
1011
1051
  // snapshot because the latter is removed as soon as the task completes.
1012
- this.rememberDurably(sessionKey, message, options?.selfAID);
1052
+ if (message.source !== 'owner-inject')
1053
+ this.rememberDurably(sessionKey, message, options?.selfAID);
1013
1054
  try {
1014
1055
  options?.onPersisted?.();
1015
1056
  }
@@ -1030,9 +1071,12 @@ export class MessageQueue {
1030
1071
  // - 单聊(interruptible !== false):始终打断
1031
1072
  // - 群聊(interruptible === false)+ interruptSamePeer:仅当「同一发送者连发」
1032
1073
  // 且队列里没有其他人的消息时打断,避免抢占群里他人的排队消息
1033
- const interruptSingle = options?.interruptible !== false;
1074
+ const interruptSingle = options?.attentionPolicy === 'interrupt'
1075
+ || (options?.attentionPolicy !== 'defer' && options?.interruptible !== false);
1034
1076
  const activeState = this.activeStates.get(queueKey);
1035
- const interruptGroupSamePeer = options?.interruptible === false &&
1077
+ const interruptGroupSamePeer = options?.attentionPolicy !== 'interrupt' &&
1078
+ options?.attentionPolicy !== 'defer' &&
1079
+ options?.interruptible === false &&
1036
1080
  options?.interruptSamePeer === true &&
1037
1081
  !!message.peerId &&
1038
1082
  activeState?.peerId === message.peerId &&
@@ -1125,7 +1169,8 @@ export class MessageQueue {
1125
1169
  return;
1126
1170
  }
1127
1171
  // 逻辑队列重排:让响应模式的出队策略决定队首(默认 FIFO,不改变顺序)
1128
- this.logicalQueue.reorderPhysical(queueKey, queue);
1172
+ if (queue[0]?.message.source !== 'owner-inject' && queue[0]?.message.attentionPolicy !== 'interrupt')
1173
+ this.logicalQueue.reorderPhysical(queueKey, queue);
1129
1174
  // 贪心合并:群聊按完整时间线,旧路径/私聊按 peerId
1130
1175
  const items = this.dequeueGreedy(queue);
1131
1176
  // 同步逻辑队列:移除所有被合并处理的消息
@@ -1158,8 +1203,8 @@ export class MessageQueue {
1158
1203
  // 记录正在执行的 messageId(用于撤回中断)
1159
1204
  const activeMessageIds = new Set();
1160
1205
  for (const item of rawItems) {
1161
- if (item.message.messageId)
1162
- activeMessageIds.add(item.message.messageId);
1206
+ for (const messageId of this.messageIdsFor(item))
1207
+ activeMessageIds.add(messageId);
1163
1208
  }
1164
1209
  const activeState = {
1165
1210
  baseagent: merged.message.baseagent,
@@ -1266,7 +1311,8 @@ export class MessageQueue {
1266
1311
  return result;
1267
1312
  }
1268
1313
  isMergeBarrier(message) {
1269
- return !!message.taskRecovery
1314
+ return message.source === 'owner-inject'
1315
+ || !!message.taskRecovery
1270
1316
  || !!message.asyncQuestionAnswer
1271
1317
  || !!message.executionPermissionOverride
1272
1318
  || !!message.handoffDelivery
@@ -1424,6 +1470,12 @@ export class MessageQueue {
1424
1470
  const earliestEnqueuedAt = this.earliestEnqueuedAt(items);
1425
1471
  const merged = {
1426
1472
  ...last.message,
1473
+ autoMentionBatch: items.some(item => item.message.autoMentionBatch),
1474
+ attentionPolicy: items.some(item => item.message.attentionPolicy === 'interrupt')
1475
+ ? 'interrupt'
1476
+ : items.some(item => item.message.attentionPolicy === 'defer')
1477
+ ? 'defer'
1478
+ : last.message.attentionPolicy,
1427
1479
  content: contents.join('\n'),
1428
1480
  items: subMessages,
1429
1481
  images: allImages.length > 0 ? allImages : undefined,
@@ -1558,9 +1610,7 @@ export class MessageQueue {
1558
1610
  }
1559
1611
  else {
1560
1612
  this.activeBatches.set(queueKey, this.buildCoalescedItem(remaining));
1561
- activeState.messageIds = new Set(remaining
1562
- .map(part => part.message.messageId)
1563
- .filter((id) => !!id));
1613
+ activeState.messageIds = new Set(remaining.flatMap(part => this.messageIdsFor(this.itemFromPart(part))));
1564
1614
  }
1565
1615
  this.persistQueuesImmediate();
1566
1616
  }
@@ -1651,6 +1701,22 @@ export class MessageQueue {
1651
1701
  }
1652
1702
  return { state: 'missing' };
1653
1703
  }
1704
+ /** Resolve a stable inbound message ID when the caller has not yet resolved a thread session. */
1705
+ getMessageStateByMessageId(messageId, selfAid, scope) {
1706
+ for (const [key, queue] of this.queues) {
1707
+ if (queue.some(item => this.partsOf(item)
1708
+ .some(part => this.partMatchesMessage(part, messageId, selfAid, key)
1709
+ && this.partMatchesScope(part, scope))))
1710
+ return { state: 'queued' };
1711
+ }
1712
+ for (const [key, item] of this.activeBatches) {
1713
+ if (this.partsOf(item)
1714
+ .some(part => this.partMatchesMessage(part, messageId, selfAid, key)
1715
+ && this.partMatchesScope(part, scope)))
1716
+ return { state: 'active' };
1717
+ }
1718
+ return { state: 'missing' };
1719
+ }
1654
1720
  /** Live admission state, including work that arrived after an older gate. */
1655
1721
  getSessionSnapshot(sessionKey) {
1656
1722
  let queued = 0;
@@ -2129,6 +2195,27 @@ export class MessageQueue {
2129
2195
  }
2130
2196
  return true;
2131
2197
  }
2198
+ async interruptForPriority(sessionId) {
2199
+ for (const [queueKey, active] of this.activeBatches) {
2200
+ if (!this.matchesSession(queueKey, sessionId))
2201
+ continue;
2202
+ if (!this.interruptCallback)
2203
+ throw new Error('Interrupt callback is unavailable');
2204
+ this.interruptFailures.delete(queueKey);
2205
+ this.eventBus?.publish({
2206
+ type: 'task:interrupted', sessionId, reason: 'new_message',
2207
+ agentName: this.processingAgent.get(queueKey),
2208
+ taskId: this.activeStates.get(queueKey)?.taskId,
2209
+ generation: this.activeStates.get(queueKey)?.generation,
2210
+ causation: active.message.causation,
2211
+ });
2212
+ await this.triggerInterrupt(queueKey, sessionId, this.activeStates.get(queueKey)?.baseagent, this.processingAgent.get(queueKey), 'new_message', undefined, true);
2213
+ const failure = this.interruptFailures.get(queueKey);
2214
+ this.interruptFailures.delete(queueKey);
2215
+ if (failure)
2216
+ throw failure;
2217
+ }
2218
+ }
2132
2219
  }
2133
2220
  function abortError(reason) {
2134
2221
  if (reason instanceof Error)