evolcore 0.0.3 → 0.0.4
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.
- package/CHANGELOG.md +64 -0
- package/README.md +45 -10
- package/bin/ec-safe-output.js +89 -24
- package/dist/agents/baseagent.js +46 -0
- package/dist/agents/claude-runner.js +1 -31
- package/dist/agents/codex-app-server-client.js +68 -0
- package/dist/agents/codex-runner.js +157 -5
- package/dist/agents/runner-types.js +2 -2
- package/dist/aun/aid/agentmd.js +79 -0
- package/dist/aun/aid/encryption-seed-policy.js +29 -0
- package/dist/aun/aid/index.js +1 -1
- package/dist/aun/aid/store.js +2 -5
- package/dist/channels/aun.js +220 -112
- package/dist/channels/daemon.js +18 -4
- package/dist/channels/dingtalk.js +52 -137
- package/dist/channels/feishu.js +56 -4
- package/dist/channels/qqbot.js +23 -1
- package/dist/channels/wechat.js +303 -155
- package/dist/channels/wecom.js +383 -7
- package/dist/cli/aun-commands.js +11 -11
- package/dist/cli/handoff-command.js +2 -1
- package/dist/cli/init-channel.js +185 -67
- package/dist/cli/init.js +7 -5
- package/dist/cli/trigger-command.js +92 -11
- package/dist/config/access-policy-domain.js +38 -0
- package/dist/config/access-policy.js +134 -0
- package/dist/config/builtin-role-templates.js +27 -14
- package/dist/config/builtin-roles.js +25 -6
- package/dist/config/config-field-policy.js +17 -5
- package/dist/config/config-manager.js +198 -22
- package/dist/config/config-operation-service.js +35 -38
- package/dist/config/contact-bind-code.js +274 -0
- package/dist/config/contact-book-store.js +173 -5
- package/dist/config/contact-book.js +32 -0
- package/dist/config/contact-operation-service.js +9 -3
- package/dist/config/contact-request-service.js +331 -0
- package/dist/config/peer-role-resolver.js +3 -1
- package/dist/config/schema-registry.js +49 -24
- package/dist/config-store.js +14 -2
- package/dist/core/auth/agent-delegation.js +105 -11
- package/dist/core/auth/authorization-audit.js +6 -0
- package/dist/core/auth/operation-authorizer.js +8 -0
- package/dist/core/auth/operation-catalog.js +51 -3
- package/dist/core/auth/trigger-authorization.js +15 -0
- package/dist/core/bootstrap-service.js +2 -9
- package/dist/core/channel-loader.js +6 -2
- package/dist/core/command/command-handler.js +10 -13
- package/dist/core/command/connect-menu.js +249 -24
- package/dist/core/command/evol-menu-version-gate.js +38 -0
- package/dist/core/command/group-menu.js +421 -0
- package/dist/core/command/menu-handler.js +269 -85
- package/dist/core/command/menu-protocol.js +8 -2
- package/dist/core/command/menu-token-store.js +102 -0
- package/dist/core/command/slash-gate.js +1 -1
- package/dist/core/command/slash-handler.js +158 -31
- package/dist/core/daemon-file-cache.js +28 -0
- package/dist/core/event-catalog.js +29 -0
- package/dist/core/evolagent-registry.js +4 -39
- package/dist/core/handoff/runtime.js +162 -37
- package/dist/core/handoff/store.js +46 -2
- package/dist/core/handoff/types.js +1 -0
- package/dist/core/inference/text-inference.js +16 -74
- package/dist/core/message/file-markers.js +7 -0
- package/dist/core/message/im-renderer.js +18 -14
- package/dist/core/message/inbound-admission.js +124 -0
- package/dist/core/message/message-bridge.js +616 -74
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +185 -10
- package/dist/core/message/peer-mode.js +7 -8
- package/dist/core/message/response-engine.js +381 -79
- package/dist/core/permission/approval-gateway.js +17 -10
- package/dist/core/permission/ec-command-parser.js +101 -46
- package/dist/core/permission/tool-policy.js +69 -24
- package/dist/core/protected-paths.js +36 -11
- package/dist/core/session/session-manager.js +25 -3
- package/dist/core/session/session-mapper.js +2 -0
- package/dist/core/session/session-renew.js +125 -69
- package/dist/core/session/session-turn-coordinator.js +5 -1
- package/dist/eck/kit-renderer.js +12 -1
- package/dist/eck/message-renderer.js +79 -1
- package/dist/index.js +224 -89
- package/dist/ipc.js +81 -4
- package/dist/paths.js +3 -0
- package/dist/response-system/config-resolver.js +29 -0
- package/dist/response-system/coordinator.js +8 -32
- package/dist/response-system/engines/v1/proactive-flow.js +1 -1
- package/dist/response-system/index.js +1 -0
- package/dist/response-system/modes/single-session/index.js +7 -4
- package/dist/trigger/parser.js +55 -15
- package/dist/trigger/patch.js +4 -1
- package/dist/trigger/scheduler.js +441 -39
- package/dist/utils/error-dict.json +7 -0
- package/dist/utils/evolcore-version.js +21 -0
- package/dist/utils/logger.js +2 -2
- package/dist/utils/stable-semver.js +21 -0
- package/dist/utils/stats.js +33 -9
- package/kits/docs/channels/aun.md +4 -13
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/config.md +47 -2
- package/kits/docs/evolcore/contact.md +7 -3
- package/kits/docs/evolcore/group-rules.md +46 -4
- package/kits/docs/evolcore/group.md +4 -4
- package/kits/docs/evolcore/msg.md +5 -5
- package/kits/docs/evolcore/trigger.md +25 -8
- package/kits/docs/path-registry.md +36 -17
- package/kits/eck_message_manifest.json +12 -1
- package/kits/migrations/migrate-contact-book-v2.mjs +7 -0
- package/kits/rules/01-overview.md +17 -7
- package/kits/rules/02-navigation.md +38 -18
- package/kits/rules/03-identity.md +28 -24
- package/kits/rules/04-relation.md +44 -28
- package/kits/rules/05-venue.md +31 -15
- package/kits/rules/06-channel.md +11 -7
- package/kits/schemas/_meta.json +18 -7
- package/kits/schemas/agent-config.schema.7.json +303 -0
- package/kits/schemas/agent-config.schema.8.json +304 -0
- package/kits/schemas/agent-config.schema.9.json +374 -0
- package/kits/schemas/contact-book.schema.3.json +67 -0
- package/kits/schemas/daemon.schema.1.json +3 -2
- package/kits/schemas/daemon.schema.2.json +101 -0
- package/kits/schemas/daemon.schema.3.json +123 -0
- package/kits/schemas/defaults.schema.2.json +85 -0
- package/kits/schemas/defaults.schema.3.json +73 -0
- package/kits/schemas/relation-config.schema.6.json +46 -0
- package/kits/schemas/relation-config.schema.7.json +59 -0
- package/kits/schemas/single-session.schema.2.json +57 -0
- package/kits/templates/message-fragments/handoff-context-to-target.md +9 -0
- package/kits/templates/message-fragments/handoff-request-to-target.md +14 -8
- package/kits/templates/message-fragments/handoff-response-to-origin.md +5 -6
- package/kits/templates/roles/admin.json +46 -0
- package/kits/templates/roles/member.json +4 -0
- package/kits/templates/roles/visitor.json +4 -0
- package/kits/templates/system-fragments/channel.md +12 -2
- package/kits/templates/system-fragments/identity.md +3 -1
- package/kits/templates/system-fragments/relation.md +1 -1
- package/kits/templates/system-fragments/session.md +6 -2
- package/package.json +4 -2
- package/MIGRATION-0.5.0.md +0 -378
- package/ROLE_ACCESS_CONTROL.md +0 -174
- package/dist/channels/contact-bind-code.js +0 -134
- package/dist/channels/wecom-card.js +0 -101
- package/dist/channels/wecom-onboarding.js +0 -82
- 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
|
-
|
|
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
|
-
|
|
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
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
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.
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
93
|
+
return { ...d }; // d 挂在缓存的 schema 对象上,拷一层避免调用方改写污染缓存
|
|
95
94
|
}
|
|
96
95
|
export function chatmodeFieldForPeer(chatType, peerType) {
|
|
97
96
|
const kind = normalizePeerType(peerType);
|