evolcore 0.0.20 → 0.0.22

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 (147) hide show
  1. package/CHANGELOG.md +65 -0
  2. package/README.md +58 -9
  3. package/bin/codex-managed-hook.mjs +3 -0
  4. package/bin/install-codex-managed-hooks.mjs +3 -1
  5. package/dist/agents/baseagent.js +10 -6
  6. package/dist/agents/claude-runner.js +393 -108
  7. package/dist/agents/codex-app-server-client.js +41 -7
  8. package/dist/agents/codex-runner.js +1292 -220
  9. package/dist/agents/ecagent-runner.js +171 -61
  10. package/dist/agents/gemini-runner.js +130 -30
  11. package/dist/agents/request-identity.js +25 -0
  12. package/dist/agents/runner-types.js +19 -0
  13. package/dist/aun/aid/agentmd.js +59 -2
  14. package/dist/aun/aid/identity.js +4 -1
  15. package/dist/aun/aid/index.js +1 -1
  16. package/dist/aun/msg/group.js +72 -6
  17. package/dist/aun/msg/history.js +213 -36
  18. package/dist/aun/msg/managed-operation.js +58 -9
  19. package/dist/aun/msg/p2p.js +5 -0
  20. package/dist/aun/outbox.js +189 -80
  21. package/dist/aun/service-proxy.js +43 -25
  22. package/dist/channels/aun.js +618 -123
  23. package/dist/channels/daemon.js +6 -1
  24. package/dist/cli/agent-command.js +4 -3
  25. package/dist/cli/agent.js +66 -56
  26. package/dist/cli/aun-commands.js +177 -42
  27. package/dist/cli/command-log.js +10 -11
  28. package/dist/cli/contact.js +1 -0
  29. package/dist/cli/daemon-commands.js +98 -123
  30. package/dist/cli/init.js +27 -15
  31. package/dist/cli/task-context.js +50 -0
  32. package/dist/cli/trigger-command.js +14 -5
  33. package/dist/cli/watch-logs.js +10 -3
  34. package/dist/config/builtin-roles.js +1 -0
  35. package/dist/config/config-field-policy.js +19 -5
  36. package/dist/config/config-manager.js +167 -22
  37. package/dist/config/contact-book-store.js +25 -3
  38. package/dist/config/contact-operation-service.js +32 -1
  39. package/dist/config/contact-request-service.js +44 -0
  40. package/dist/config/daemon-services.js +186 -0
  41. package/dist/config/gateway-config.js +20 -9
  42. package/dist/config/role-service.js +54 -3
  43. package/dist/config/schema-migration.js +550 -0
  44. package/dist/config-store.js +151 -9
  45. package/dist/core/agent-application-service.js +279 -0
  46. package/dist/core/audit/log-integrity.js +102 -0
  47. package/dist/core/auth/agent-delegation.js +43 -1
  48. package/dist/core/auth/auth-gateway.js +41 -4
  49. package/dist/core/auth/authorization-audit.js +216 -8
  50. package/dist/core/auth/operation-authorizer.js +41 -1
  51. package/dist/core/auth/operation-catalog.js +9 -1
  52. package/dist/core/bootstrap-messages.js +8 -0
  53. package/dist/core/bootstrap-service.js +99 -27
  54. package/dist/core/causation/aun-association.js +7 -4
  55. package/dist/core/command/agent-control.js +56 -16
  56. package/dist/core/command/command-handler.js +311 -44
  57. package/dist/core/command/connect-menu.js +3 -4
  58. package/dist/core/command/group-menu.js +5 -7
  59. package/dist/core/command/menu-handler.js +288 -80
  60. package/dist/core/command/menu-protocol.js +1 -1
  61. package/dist/core/command/role-menu.js +21 -11
  62. package/dist/core/command/slash-gate.js +85 -18
  63. package/dist/core/command/slash-handler.js +377 -36
  64. package/dist/core/data-migration.js +11 -1
  65. package/dist/core/event-catalog.js +37 -0
  66. package/dist/core/evolagent.js +4 -0
  67. package/dist/core/handoff/dispatcher.js +4 -0
  68. package/dist/core/handoff/runtime.js +33 -3
  69. package/dist/core/handoff/store.js +32 -9
  70. package/dist/core/inference/text-inference.js +7 -15
  71. package/dist/core/message/im-renderer.js +90 -87
  72. package/dist/core/message/{inbound-admission.js → message-admission.js} +51 -1
  73. package/dist/core/message/message-bridge.js +184 -12
  74. package/dist/core/message/message-log.js +47 -7
  75. package/dist/core/message/message-queue.js +227 -16
  76. package/dist/core/message/message-utils.js +12 -5
  77. package/dist/core/message/response-engine.js +658 -109
  78. package/dist/core/message/send-receipt.js +1 -0
  79. package/dist/core/message/stream-debouncer.js +9 -2
  80. package/dist/core/model/model-catalog.js +23 -15
  81. package/dist/core/model/model-diagnostics.js +28 -10
  82. package/dist/core/permission/approval-gateway.js +180 -6
  83. package/dist/core/permission/ec-command-parser.js +627 -69
  84. package/dist/core/permission/mode.js +18 -3
  85. package/dist/core/{protected-paths.js → permission/protected-paths.js} +27 -14
  86. package/dist/core/permission/readonly-shell-query.js +263 -9
  87. package/dist/core/permission/sandbox-runtime.js +159 -1
  88. package/dist/core/permission/tool-error-code.js +12 -0
  89. package/dist/core/permission/tool-policy.js +618 -23
  90. package/dist/core/session/session-fs-store.js +154 -5
  91. package/dist/core/session/session-manager.js +329 -30
  92. package/dist/core/session/session-renew.js +37 -13
  93. package/dist/core/session/session-turn-coordinator.js +16 -5
  94. package/dist/eck/kit-renderer.js +1 -1
  95. package/dist/index.js +316 -50
  96. package/dist/ipc.js +459 -29
  97. package/dist/paths.js +82 -7
  98. package/dist/response-system/context-builder.js +1 -7
  99. package/dist/response-system/engines/v1/proactive-flow.js +7 -2
  100. package/dist/stats/price-resolver.js +4 -0
  101. package/dist/trigger/anomaly-store.js +1 -0
  102. package/dist/trigger/feedback.js +70 -7
  103. package/dist/trigger/history.js +79 -4
  104. package/dist/trigger/legacy-session-history.js +2 -2
  105. package/dist/trigger/parser.js +13 -3
  106. package/dist/trigger/scheduler.js +20 -3
  107. package/dist/trigger/validation.js +6 -1
  108. package/dist/utils/atomic-write.js +27 -0
  109. package/dist/utils/ecweb-utils.js +16 -2
  110. package/dist/utils/error-utils.js +4 -1
  111. package/dist/utils/logger.js +30 -6
  112. package/dist/utils/process-tree-stats.js +24 -4
  113. package/dist/utils/process-tree-worker.js +31 -0
  114. package/dist/utils/project-path.js +1 -2
  115. package/dist/utils/tool-summary.js +59 -0
  116. package/dist/utils/windows-shell-trust.js +201 -0
  117. package/kits/docs/INDEX.md +1 -1
  118. package/kits/docs/evolcore/INDEX.md +3 -3
  119. package/kits/docs/evolcore/agent-create.md +146 -0
  120. package/kits/docs/evolcore/agent.md +6 -0
  121. package/kits/docs/evolcore/contact.md +7 -1
  122. package/kits/docs/evolcore/group-collaboration.md +251 -0
  123. package/kits/docs/evolcore/group-rules.md +1 -19
  124. package/kits/docs/evolcore/group.md +3 -1
  125. package/kits/docs/evolcore/msg.md +16 -0
  126. package/kits/docs/evolcore/trigger.md +6 -3
  127. package/kits/docs/prompt-loading-architecture.md +6 -0
  128. package/kits/eck_message_manifest.json +6 -6
  129. package/kits/schemas/_meta.json +7 -4
  130. package/kits/schemas/agent-config.schema.11.json +13 -0
  131. package/kits/schemas/agent-config.schema.12.json +427 -0
  132. package/kits/schemas/daemon.schema.5.json +0 -1
  133. package/kits/schemas/daemon.schema.6.json +131 -0
  134. package/kits/schemas/defaults.schema.5.json +15 -3
  135. package/kits/schemas/migrations/README.md +3 -1
  136. package/kits/schemas/relation-config.schema.8.json +13 -0
  137. package/kits/schemas/role-config.schema.1.json +1 -2
  138. package/kits/schemas/single-session.schema.3.json +32 -0
  139. package/kits/templates/message-fragments/item.md +1 -1
  140. package/kits/templates/roles/admin.json +1 -0
  141. package/kits/templates/roles/member.json +1 -0
  142. package/kits/templates/roles/visitor.json +1 -0
  143. package/kits/templates/system-fragments/bootstrap.md +2 -1
  144. package/kits/templates/system-fragments/commands.md +2 -2
  145. package/package.json +6 -3
  146. package/skills/eclink/SKILL.md +2 -0
  147. package/dist/config/aun-gateway-config.js +0 -2
@@ -3,6 +3,7 @@ import fs from 'fs';
3
3
  import { logger } from '../../utils/logger.js';
4
4
  import { LogicalQueueBridge } from './logical-queue-bridge.js';
5
5
  import { normalizeCausation } from '../causation/context.js';
6
+ import { KeyedFairMutex } from '../handoff/mutex.js';
6
7
  const DEFAULT_AGENT_NAME = '<unknown>';
7
8
  const RESTART_RESUME_CONTENT = 'evolcore 服务已重启,请继续之前未完成的任务。';
8
9
  export class MessageQueue {
@@ -10,6 +11,9 @@ export class MessageQueue {
10
11
  processing = new Set();
11
12
  processingAgent = new Map(); // queueKey → agentName(处理中项目的 agent)
12
13
  externalLocks = new Map();
14
+ prequeueReservations = new Map();
15
+ /** Serializes fast session commands while retaining the existing dequeue gate. */
16
+ sessionBarrierMutex = new KeyedFairMutex();
13
17
  handler;
14
18
  activeStates = new Map(); // queueKey → active task state
15
19
  interruptCallback;
@@ -32,6 +36,7 @@ export class MessageQueue {
32
36
  shutdownPromise;
33
37
  /** 逻辑队列桥接:管理出队顺序(响应模式可注入自定义策略,默认 FIFO) */
34
38
  logicalQueue = new LogicalQueueBridge();
39
+ enqueueSequence = 0;
35
40
  constructor(handler, options) {
36
41
  this.handler = handler;
37
42
  this.persistencePath = options?.persistencePath;
@@ -83,6 +88,17 @@ export class MessageQueue {
83
88
  this.markActiveTerminal(event.sessionId, event.reason || 'interrupted');
84
89
  }
85
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
+ });
86
102
  subscribe.call(eventBus, 'task:completed', event => {
87
103
  if ('sessionId' in event && event.sessionId)
88
104
  this.markActiveTerminal(event.sessionId, 'completed');
@@ -138,6 +154,42 @@ export class MessageQueue {
138
154
  setTimeout(() => this.recentMessageIds.delete(dedupKey), this.DEDUP_WINDOW);
139
155
  return true;
140
156
  }
157
+ /**
158
+ * Claim a stable inbound message for a fast command that bypasses the
159
+ * normal queue. The claim uses exactly the same durable ledger and key as
160
+ * ordinary enqueue deduplication, but deliberately does not enqueue or
161
+ * affect queue scheduling.
162
+ */
163
+ claimInbound(sessionKey, messageId, selfAID) {
164
+ const normalizedMessageId = messageId.trim();
165
+ if (!normalizedMessageId) {
166
+ const error = new Error('inbound claim requires a stable messageId');
167
+ error.code = 'INBOUND_MESSAGE_ID_REQUIRED';
168
+ throw error;
169
+ }
170
+ const key = this.dedupKey(sessionKey, normalizedMessageId, selfAID);
171
+ if (this.recentMessageIds.has(key)
172
+ || this.durableMessageIds.has(key)
173
+ || this.hasEnqueuedMessage(sessionKey, normalizedMessageId, selfAID)) {
174
+ return 'duplicate';
175
+ }
176
+ if (!this.dedupPath) {
177
+ const error = new Error('durable inbound dedup is unavailable');
178
+ error.code = 'INBOUND_DEDUP_UNAVAILABLE';
179
+ throw error;
180
+ }
181
+ const seenAt = Date.now();
182
+ this.durableMessageIds.set(key, seenAt);
183
+ if (!this.persistDurableDedup()) {
184
+ this.durableMessageIds.delete(key);
185
+ const error = new Error('failed to persist inbound dedup claim');
186
+ error.code = 'INBOUND_DEDUP_PERSIST_FAILED';
187
+ throw error;
188
+ }
189
+ this.recentMessageIds.add(key);
190
+ setTimeout(() => this.recentMessageIds.delete(key), this.DEDUP_WINDOW);
191
+ return 'claimed';
192
+ }
141
193
  rememberDurably(sessionKey, message, selfAID) {
142
194
  if (!this.dedupPath || !message.messageId)
143
195
  return;
@@ -174,20 +226,64 @@ export class MessageQueue {
174
226
  }
175
227
  persistDurableDedup() {
176
228
  if (!this.dedupPath)
177
- return;
229
+ return false;
178
230
  const now = Date.now();
179
231
  for (const [key, seenAt] of this.durableMessageIds) {
180
232
  if (now - seenAt > this.dedupTtlMs)
181
233
  this.durableMessageIds.delete(key);
182
234
  }
235
+ const tmp = `${this.dedupPath}.tmp-${process.pid}-${Date.now()}-${this.enqueueSequence}`;
236
+ let fd;
183
237
  try {
184
238
  fs.mkdirSync(path.dirname(this.dedupPath), { recursive: true });
185
- const tmp = `${this.dedupPath}.tmp-${process.pid}-${Date.now()}`;
186
- fs.writeFileSync(tmp, JSON.stringify({ version: 1, updatedAt: now, entries: [...this.durableMessageIds].map(([key, seenAt]) => ({ key, seenAt })) }));
239
+ const content = Buffer.from(JSON.stringify({
240
+ version: 1,
241
+ updatedAt: now,
242
+ entries: [...this.durableMessageIds].map(([key, seenAt]) => ({ key, seenAt })),
243
+ }));
244
+ fd = fs.openSync(tmp, 'w');
245
+ let offset = 0;
246
+ while (offset < content.length) {
247
+ const written = fs.writeSync(fd, content, offset, content.length - offset, null);
248
+ if (written <= 0)
249
+ throw new Error('dedup ledger write made no progress');
250
+ offset += written;
251
+ }
252
+ fs.fsyncSync(fd);
253
+ fs.closeSync(fd);
254
+ fd = undefined;
187
255
  fs.renameSync(tmp, this.dedupPath);
256
+ const persistedFd = fs.openSync(this.dedupPath, 'r');
257
+ try {
258
+ fs.fsyncSync(persistedFd);
259
+ }
260
+ finally {
261
+ fs.closeSync(persistedFd);
262
+ }
263
+ if (process.platform !== 'win32') {
264
+ const directoryFd = fs.openSync(path.dirname(this.dedupPath), 'r');
265
+ try {
266
+ fs.fsyncSync(directoryFd);
267
+ }
268
+ finally {
269
+ fs.closeSync(directoryFd);
270
+ }
271
+ }
272
+ return true;
188
273
  }
189
274
  catch (error) {
190
275
  logger.warn(`[Queue] Failed to persist durable dedup ledger: ${error instanceof Error ? error.message : String(error)}`);
276
+ if (fd !== undefined) {
277
+ try {
278
+ fs.closeSync(fd);
279
+ }
280
+ catch { }
281
+ }
282
+ try {
283
+ fs.unlinkSync(tmp);
284
+ }
285
+ catch { }
286
+ return false;
191
287
  }
192
288
  }
193
289
  hasEnqueuedMessage(sessionKey, messageId, selfAID) {
@@ -538,6 +634,8 @@ export class MessageQueue {
538
634
  sessionId: sessionKey,
539
635
  reason: 'daemon_restart',
540
636
  agentName,
637
+ taskId: this.activeStates.get(queueKey)?.taskId,
638
+ generation: this.activeStates.get(queueKey)?.generation,
541
639
  causation: item.message.causation,
542
640
  });
543
641
  barriers.push(this.triggerInterrupt(queueKey, sessionKey, this.activeStates.get(queueKey)?.baseagent, agentName, 'daemon_restart'));
@@ -768,6 +866,8 @@ export class MessageQueue {
768
866
  shouldRestoreMessage(message, sessionId) {
769
867
  // The Trigger scheduler records open runs as daemon_restart. Replaying their
770
868
  // queued work afterwards would execute side effects for an already-failed run.
869
+ if (message.executionPermissionOverride)
870
+ return false;
771
871
  if (message.source !== 'trigger' && !message.triggerMeta)
772
872
  return true;
773
873
  this.droppedTriggerSessionIds.add(sessionId);
@@ -794,9 +894,9 @@ export class MessageQueue {
794
894
  }
795
895
  // 拦截器检查:AskUserQuestion 等场景的一次性消息拦截
796
896
  const interceptor = this.interceptors.get(sessionKey);
797
- const canAnswerInteraction = message.source === undefined
897
+ const canAnswerInteraction = !message.executionPermissionOverride && (message.source === undefined
798
898
  || message.source === 'user'
799
- || message.source === 'card-trigger';
899
+ || message.source === 'card-trigger');
800
900
  if (interceptor && canAnswerInteraction) {
801
901
  this.interceptors.delete(sessionKey);
802
902
  logger.debug(`[Queue] Message intercepted for ${sessionKey}`);
@@ -849,6 +949,7 @@ export class MessageQueue {
849
949
  // Capture the physical enqueue instant once. It must not be regenerated
850
950
  // on every persistence snapshot, otherwise old messages appear fresh.
851
951
  const enqueuedAt = Date.now();
952
+ this.enqueueSequence += 1;
852
953
  // Clone at the physical queue boundary. The same inbound object may be
853
954
  // broadcast to multiple agents; mutating it would make all copies share
854
955
  // the last queue's enqueue timestamp.
@@ -934,6 +1035,8 @@ export class MessageQueue {
934
1035
  sessionId: sessionKey,
935
1036
  reason: 'new_message',
936
1037
  agentName: this.processingAgent.get(queueKey),
1038
+ taskId: this.activeStates.get(queueKey)?.taskId,
1039
+ generation: this.activeStates.get(queueKey)?.generation,
937
1040
  causation: this.activeBatches.get(queueKey)?.message.causation,
938
1041
  });
939
1042
  if (this.interruptCallback) {
@@ -1154,7 +1257,10 @@ export class MessageQueue {
1154
1257
  return result;
1155
1258
  }
1156
1259
  isMergeBarrier(message) {
1157
- return !!message.handoffDelivery || !!message.triggerMeta || !!message.causation?.trigger?.path.length;
1260
+ return !!message.executionPermissionOverride
1261
+ || !!message.handoffDelivery
1262
+ || !!message.triggerMeta
1263
+ || !!message.causation?.trigger?.path.length;
1158
1264
  }
1159
1265
  isOriginHandoff(message) {
1160
1266
  return message.source === 'handoff' && message.handoffDelivery?.direction === 'origin';
@@ -1427,6 +1533,8 @@ export class MessageQueue {
1427
1533
  sessionId: sessionKey,
1428
1534
  reason: 'recalled',
1429
1535
  agentName,
1536
+ taskId: activeState?.taskId,
1537
+ generation: activeState?.generation,
1430
1538
  causation: active?.message.causation,
1431
1539
  });
1432
1540
  const barrier = this.interruptCallback
@@ -1437,23 +1545,122 @@ export class MessageQueue {
1437
1545
  return { found: false, barrier: Promise.resolve() };
1438
1546
  }
1439
1547
  /**
1440
- * 外部锁:快速命令(/compact, /clear)执行期间阻塞队列处理
1441
- * 返回 release 函数
1548
+ * Capture the session's queue state and install a dequeue gate in one
1549
+ * synchronous critical section. Messages arriving after this call remain
1550
+ * durable in the queue, but processNext will wait until the last holder
1551
+ * releases the gate.
1442
1552
  */
1443
- acquireLock(sessionKey) {
1444
- let releaseFn;
1445
- const promise = new Promise(resolve => { releaseFn = resolve; });
1446
- this.externalLocks.set(sessionKey, promise);
1553
+ acquireDequeueGate(sessionKey) {
1554
+ const existing = this.externalLocks.get(sessionKey);
1555
+ if (existing) {
1556
+ existing.holders += 1;
1557
+ let released = false;
1558
+ return {
1559
+ snapshot: { ...existing.snapshot },
1560
+ release: () => {
1561
+ if (released)
1562
+ return;
1563
+ released = true;
1564
+ existing.holders -= 1;
1565
+ if (existing.holders > 0)
1566
+ return;
1567
+ if (this.externalLocks.get(sessionKey) === existing)
1568
+ this.externalLocks.delete(sessionKey);
1569
+ existing.releasePromise();
1570
+ },
1571
+ };
1572
+ }
1573
+ let releasePromise;
1574
+ const promise = new Promise(resolve => { releasePromise = resolve; });
1575
+ let queued = 0;
1576
+ let active = false;
1577
+ let dispatching = false;
1578
+ for (const [queueKey, queue] of this.queues.entries()) {
1579
+ if (!this.matchesSession(queueKey, sessionKey))
1580
+ continue;
1581
+ queued += this.queueRawMessageCount(queue);
1582
+ if (this.processing.has(queueKey) || this.activeBatches.has(queueKey))
1583
+ active = true;
1584
+ if (this.processing.has(queueKey) && !this.activeBatches.has(queueKey))
1585
+ dispatching = true;
1586
+ }
1587
+ for (const queueKey of this.activeBatches.keys()) {
1588
+ if (this.matchesSession(queueKey, sessionKey))
1589
+ active = true;
1590
+ }
1591
+ const state = {
1592
+ promise,
1593
+ releasePromise,
1594
+ holders: 1,
1595
+ snapshot: {
1596
+ prequeue: this.prequeueReservations.get(sessionKey) ?? 0,
1597
+ active,
1598
+ queued,
1599
+ dispatching,
1600
+ cutoff: this.enqueueSequence,
1601
+ },
1602
+ };
1603
+ this.externalLocks.set(sessionKey, state);
1604
+ let released = false;
1605
+ return {
1606
+ snapshot: { ...state.snapshot },
1607
+ release: () => {
1608
+ if (released)
1609
+ return;
1610
+ released = true;
1611
+ state.holders -= 1;
1612
+ if (state.holders > 0)
1613
+ return;
1614
+ if (this.externalLocks.get(sessionKey) === state)
1615
+ this.externalLocks.delete(sessionKey);
1616
+ state.releasePromise();
1617
+ },
1618
+ };
1619
+ }
1620
+ /**
1621
+ * Mark an admitted message before it enters debounce or other pre-queue
1622
+ * work. The returned release is idempotent so callers can use it both from
1623
+ * onPersisted and a surrounding finally block.
1624
+ */
1625
+ reserveSessionIngress(sessionKey) {
1626
+ this.prequeueReservations.set(sessionKey, (this.prequeueReservations.get(sessionKey) ?? 0) + 1);
1627
+ let released = false;
1447
1628
  return () => {
1448
- this.externalLocks.delete(sessionKey);
1449
- releaseFn();
1629
+ if (released)
1630
+ return;
1631
+ released = true;
1632
+ const remaining = (this.prequeueReservations.get(sessionKey) ?? 1) - 1;
1633
+ if (remaining > 0)
1634
+ this.prequeueReservations.set(sessionKey, remaining);
1635
+ else
1636
+ this.prequeueReservations.delete(sessionKey);
1450
1637
  };
1451
1638
  }
1639
+ /**
1640
+ * Run a fast session command while no queued work can start. The queue
1641
+ * snapshot and external gate are installed before the callback is invoked;
1642
+ * messages arriving afterwards remain durable but are outside this probe.
1643
+ */
1644
+ async withSessionBarrier(sessionKey, callback) {
1645
+ return this.sessionBarrierMutex.forKey(sessionKey).runExclusive(async () => {
1646
+ const gate = this.acquireDequeueGate(sessionKey);
1647
+ try {
1648
+ return await callback(gate.snapshot);
1649
+ }
1650
+ finally {
1651
+ gate.release();
1652
+ }
1653
+ });
1654
+ }
1655
+ /** Backwards-compatible lock API used by existing quick commands. */
1656
+ acquireLock(sessionKey) {
1657
+ return this.acquireDequeueGate(sessionKey).release;
1658
+ }
1452
1659
  /** 检查是否有外部锁 */
1453
1660
  getExternalLock(queueKey) {
1454
- for (const [key, promise] of this.externalLocks) {
1661
+ for (const [key, state] of this.externalLocks) {
1455
1662
  if (this.matchesSession(queueKey, key))
1456
- return promise;
1663
+ return state.promise;
1457
1664
  }
1458
1665
  return undefined;
1459
1666
  }
@@ -1576,6 +1783,8 @@ export class MessageQueue {
1576
1783
  sessionId: sessionKey,
1577
1784
  reason: 'stop',
1578
1785
  agentName: name,
1786
+ taskId: this.activeStates.get(queueKey)?.taskId,
1787
+ generation: this.activeStates.get(queueKey)?.generation,
1579
1788
  causation: this.activeBatches.get(queueKey)?.message.causation,
1580
1789
  });
1581
1790
  if (this.interruptCallback) {
@@ -1848,6 +2057,8 @@ export class MessageQueue {
1848
2057
  sessionId: sessionKey,
1849
2058
  reason: 'new_message',
1850
2059
  agentName: targetAgentName,
2060
+ taskId: this.activeStates.get(targetQueueKey)?.taskId,
2061
+ generation: this.activeStates.get(targetQueueKey)?.generation,
1851
2062
  causation: this.activeBatches.get(targetQueueKey)?.message.causation,
1852
2063
  });
1853
2064
  if (this.interruptCallback) {
@@ -4,6 +4,12 @@
4
4
  * 从 message-processor.ts 提取的独立工具函数,供 ResponseEngine 和其他模块使用。
5
5
  */
6
6
  import { renderCommandCardAsText, renderActionAsText } from '../interaction-router.js';
7
+ /** A legacy adapter resolved its send call without providing a structured receipt. */
8
+ export const LEGACY_INTERACTION_ACCEPTED = 'legacy-accepted';
9
+ export function isInteractionSendAccepted(result) {
10
+ return result === LEGACY_INTERACTION_ACCEPTED
11
+ || (result !== false && (result.status === 'sent' || result.status === 'queued'));
12
+ }
7
13
  /**
8
14
  * 构造出站消息信封
9
15
  */
@@ -129,7 +135,8 @@ export function defaultFallbackText(interaction) {
129
135
  * 通过统一的 adapter.send 入口发送交互负载
130
136
  *
131
137
  * 通过 adapter.send(envelope, { kind: 'interaction', ... }) 发送交互。
132
- * 成功返回 'sent',失败返回 false。
138
+ * 保留 adapter 的结构化回执,让调用方区分 sent、queued、failed
139
+ * suppressed;legacy adapter 的 void 返回表示已接受但尚无可验证回执。
133
140
  */
134
141
  export async function sendInteractionPayload(adapter, envelope, interaction, fallbackText, replyCtx) {
135
142
  const text = fallbackText ?? defaultFallbackText(interaction);
@@ -141,11 +148,11 @@ export async function sendInteractionPayload(adapter, envelope, interaction, fal
141
148
  try {
142
149
  const enriched = replyCtx ? withEnvelopeReplyContext(envelope, replyCtx) : envelope;
143
150
  const receipt = await adapter.send(enriched, payload);
144
- if (receipt?.status === 'suppressed')
151
+ if (!receipt)
152
+ return LEGACY_INTERACTION_ACCEPTED;
153
+ if (receipt.status === 'sent' && !(receipt.messageId || receipt.messages[0]?.messageId))
145
154
  return false;
146
- if (receipt?.status === 'sent')
147
- return receipt.messages[0]?.messageId || false;
148
- return 'sent';
155
+ return receipt;
149
156
  }
150
157
  catch {
151
158
  return false;