evolcore 0.0.39 → 0.0.40
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 +49 -0
- package/README.md +31 -20
- package/bin/ec-safe-output.js +14 -2
- package/dist/agents/baseagent-availability.js +1 -0
- package/dist/agents/claude-runner.js +32 -46
- package/dist/agents/codex-runner.js +51 -27
- package/dist/agents/ecagent-runner.js +18 -7
- package/dist/agents/gemini-runner.js +1 -2
- package/dist/agents/runner-types.js +28 -6
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +1 -3
- package/dist/aun/group/invite-service.js +6 -0
- package/dist/aun/msg/p2p.js +8 -12
- package/dist/channels/aun.js +66 -48
- package/dist/channels/dingtalk.js +8 -2
- package/dist/channels/feishu.js +6 -1
- package/dist/channels/wecom.js +7 -3
- package/dist/cli/agent-create.js +23 -4
- package/dist/cli/aid-bind-command.js +4 -1
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/index.js +25 -13
- package/dist/cli/memory-command.js +33 -14
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/task-context.js +1 -0
- package/dist/config/config-field-policy.js +1 -0
- package/dist/contracts/ecweb-service-protocol.js +44 -0
- package/dist/core/agent-runtime.js +6 -0
- package/dist/core/auth/agent-delegation.js +7 -4
- package/dist/core/auth/auth-gateway.js +3 -1
- package/dist/core/auth/authorization-audit.js +5 -1
- package/dist/core/auth/debug-context.js +5 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/command-handler.js +7 -1
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +72 -4
- package/dist/core/command/menu-protocol.js +20 -1
- package/dist/core/command/slash-gate.js +17 -3
- package/dist/core/command/slash-handler.js +90 -7
- package/dist/core/event-catalog.js +2 -0
- package/dist/core/interaction-router.js +2 -1
- package/dist/core/ipc-registration.js +26 -7
- package/dist/core/memory/batch-meta.js +2 -0
- package/dist/core/memory/consolidation-context.js +42 -51
- package/dist/core/memory/consolidation-ops-records.js +39 -7
- package/dist/core/memory/consolidation-trigger.js +71 -64
- package/dist/core/memory/consolidation.js +226 -20
- package/dist/core/memory/memory-writer.js +160 -34
- package/dist/core/memory/relation-idle-monitor.js +287 -0
- package/dist/core/memory/relation-sweep.js +206 -0
- package/dist/core/message/aun-ipc-sender.js +5 -0
- package/dist/core/message/message-bridge.js +64 -55
- package/dist/core/message/message-log.js +17 -0
- package/dist/core/message/message-queue.js +6 -0
- package/dist/core/message/outbound.js +70 -0
- package/dist/core/message/peer-mode.js +7 -2
- package/dist/core/message/response-engine.js +341 -73
- package/dist/core/ownership/owner-resolver.js +384 -178
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/tool-policy.js +7 -7
- package/dist/core/role/runtime-policy.js +64 -0
- package/dist/core/session/session-manager.js +25 -5
- package/dist/core/system-session/memory.js +3 -18
- package/dist/ecweb/basic-settings-service.js +709 -0
- package/dist/ecweb/providers-service.js +172 -0
- package/dist/index.js +92 -4
- package/dist/ipc.js +78 -11
- package/dist/stats/db.js +33 -0
- package/dist/stats/index.js +2 -2
- package/dist/stats/query.js +246 -0
- package/dist/stats/writer.js +41 -1
- package/dist/trigger/application-service.js +12 -0
- package/dist/trigger/authorization.js +2 -2
- package/dist/trigger/history.js +36 -0
- package/dist/trigger/scheduler.js +3 -0
- package/dist/utils/ecweb-supervisor.js +31 -10
- package/dist/utils/error-utils.js +8 -4
- package/dist/utils/evolcore-version.js +3 -2
- package/dist/utils/host-info.js +52 -0
- package/ecagent/dist/harness/agent-harness.js +4 -1
- package/kits/docs/evolcore/INDEX.md +1 -1
- package/kits/docs/evolcore/agent.md +1 -1
- package/kits/docs/evolcore/aid.md +29 -12
- package/kits/docs/evolcore/config.md +9 -7
- package/kits/docs/evolcore/memory-architecture.md +3 -4
- package/kits/docs/evolcore/memory-consolidation.md +28 -7
- package/kits/docs/evolcore/memory-tags.md +1 -1
- package/kits/docs/evolcore/memory.md +1 -1
- package/kits/docs/evolcore/menu-client.md +1 -1
- package/kits/docs/evolcore/menu-protocol.md +6 -0
- package/kits/docs/evolcore/msg.md +1 -1
- package/kits/eck_manifest.json +3 -3
- package/kits/rules/04-relation.md +3 -2
- package/kits/schemas/agent-config.schema.13.json +5 -1
- package/kits/templates/memory-consolidation/generalize.md +32 -4
- package/kits/templates/memory-consolidation/notes.md +2 -2
- package/kits/templates/memory-consolidation/promote.md +10 -2
- package/kits/templates/system-fragments/baseagent.md +5 -0
- package/kits/templates/system-fragments/memory.md +12 -3
- package/kits/templates/system-fragments/session.md +1 -1
- package/kits/{rules/07-memory-and-thread.md → templates/system-fragments/thread.md} +17 -80
- package/package.json +3 -3
|
@@ -1,23 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* consolidation-trigger.ts —
|
|
2
|
+
* consolidation-trigger.ts — 整理候选判据(v5,纯函数)
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 整理一律以关系为单位。两类判据:
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* **关系级**(`isRelationModeDue`,供关系整理链逐步调用,见 relation-sweep.ts):
|
|
7
|
+
* notes pending/ 里有 note
|
|
8
|
+
* generalize 本关系有 to-generalize
|
|
9
|
+
* promote 本关系是 owner 关系且有 to-promote
|
|
10
|
+
* profile 索引有变化且距上次画像超过 profileRegenDays(或还没有画像)
|
|
11
|
+
* forget 本关系有超期记忆
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* **全局定时检查**(`checkConsolidationCandidates`,cron script 驱动):只管不属于任何关系的部分——
|
|
14
|
+
* agent 层 / owner 层 forget,外加 pending 堆积超过 backlogHours 的兜底。
|
|
14
15
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* daemon 侧调用本模块的 `checkConsolidationCandidates()`,结果经 enqueueConsolidation 入队。
|
|
18
|
-
*
|
|
19
|
-
* 因此本模块**不持有任何进程内状态**(无 lastTaskEndAt 空闲门控)——所有判据都读磁盘。
|
|
20
|
-
* 并发安全由 daemon 侧的整理队列保证(同 agent 串行)。
|
|
16
|
+
* 判据是**数文件和数标签**,不看轮次和工具调用次数。本模块不持有进程内状态,判据都读磁盘;
|
|
17
|
+
* 关系的空闲 / 兜底计时在 relation-idle-monitor.ts。
|
|
21
18
|
*/
|
|
22
19
|
import fs from 'fs';
|
|
23
20
|
import path from 'path';
|
|
@@ -321,22 +318,70 @@ function checkStale(consolidatedDir, now, forgetAfterDays) {
|
|
|
321
318
|
export function shouldUseMetaIndex(agentCount, relationCount) {
|
|
322
319
|
return agentCount * relationCount > META_INDEX_THRESHOLD;
|
|
323
320
|
}
|
|
321
|
+
/** 关系整理链的固定顺序。每步在上一步收尾后重新判定,notes 新产出的标签要被后面几步看到。 */
|
|
322
|
+
export const RELATION_SWEEP_ORDER = ['notes', 'generalize', 'promote', 'profile', 'forget'];
|
|
323
|
+
/** pending/ 里有没有待整理的 note。 */
|
|
324
|
+
function hasPendingNotes(pendingDir) {
|
|
325
|
+
if (!fs.existsSync(pendingDir))
|
|
326
|
+
return false;
|
|
327
|
+
try {
|
|
328
|
+
return fs.readdirSync(pendingDir).some(f => f.endsWith('.md'));
|
|
329
|
+
}
|
|
330
|
+
catch {
|
|
331
|
+
return false;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
/** 默认的关系空闲时长与兜底时长(小时)。 */
|
|
335
|
+
export const DEFAULT_RELATION_IDLE_HOURS = 1;
|
|
336
|
+
export const DEFAULT_MAX_UNCONSOLIDATED_HOURS = 8;
|
|
324
337
|
/**
|
|
325
|
-
*
|
|
338
|
+
* 单个关系的某个整理模式现在该不该跑。关系整理链逐步调用。
|
|
326
339
|
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
340
|
+
* 与全局检查不同:notes 只要有 pending 就跑(关系已空闲 / 兜底到期,不再等堆积),
|
|
341
|
+
* promote 只在该关系就是 owner 关系时才可能命中。
|
|
342
|
+
*/
|
|
343
|
+
export function isRelationModeDue(params) {
|
|
344
|
+
const { mode, selfAid, peerKey } = params;
|
|
345
|
+
const now = params.now ?? Date.now();
|
|
346
|
+
const root = params.root ?? resolveRoot();
|
|
347
|
+
const config = effectiveConfig(params.config);
|
|
348
|
+
const useMetaIndex = params.useMetaIndex ?? false;
|
|
349
|
+
if (!config.enableRelation)
|
|
350
|
+
return false;
|
|
351
|
+
const memDir = relationMemoryDir(root, selfAid, peerKey);
|
|
352
|
+
switch (mode) {
|
|
353
|
+
case 'notes':
|
|
354
|
+
return hasPendingNotes(path.join(memDir, 'pending'));
|
|
355
|
+
case 'generalize':
|
|
356
|
+
return config.enableAgent && !!countTagged(root, selfAid, [peerKey], 'to-generalize', useMetaIndex);
|
|
357
|
+
case 'promote':
|
|
358
|
+
return config.enableOwner
|
|
359
|
+
&& findOwnerPeerKey([peerKey], params.ownerAid) === peerKey
|
|
360
|
+
&& !!countTagged(root, selfAid, [peerKey], 'to-promote', useMetaIndex);
|
|
361
|
+
case 'profile':
|
|
362
|
+
return profileDueWithMeta(memDir, now, config.profileRegenDays, useMetaIndex);
|
|
363
|
+
case 'forget':
|
|
364
|
+
return checkStale(path.join(memDir, 'consolidated'), now, config.forgetAfterDays) > 0;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* 全局定时检查:只管**不属于任何一个关系**的部分,外加 pending 堆积兜底。
|
|
369
|
+
*
|
|
370
|
+
* ① pending 堆积超过 backlogHours 的关系 → notes(由调用方转成该关系的整理链)
|
|
371
|
+
* ② agent 层超期记忆(只看 personal/memory 自身)→ forget scope=agent
|
|
372
|
+
* ③ owner 层超期记忆 → forget scope=owner
|
|
329
373
|
*
|
|
330
|
-
*
|
|
331
|
-
*
|
|
332
|
-
*
|
|
374
|
+
* generalize / promote / profile / 关系层 forget 由关系整理链负责(relation-sweep.ts),
|
|
375
|
+
* 这里不再产出:整理一律以关系为单位。
|
|
376
|
+
*
|
|
377
|
+
* 纯检查 + 少量副作用(forget 的长期记忆减速刷新 mtime)。
|
|
378
|
+
* useMetaIndex 参数保留兼容:当前三类判据都不读 `_meta.json`(关系级判据见 isRelationModeDue)。
|
|
333
379
|
*/
|
|
334
380
|
export function checkConsolidationCandidates(params) {
|
|
335
381
|
const { selfAid, ownerAid } = params;
|
|
336
382
|
const now = params.now ?? Date.now();
|
|
337
383
|
const root = params.root ?? resolveRoot();
|
|
338
384
|
const config = effectiveConfig(params.config);
|
|
339
|
-
const useMetaIndex = params.useMetaIndex ?? false;
|
|
340
385
|
const out = [];
|
|
341
386
|
if (!config.enableRelation)
|
|
342
387
|
return out;
|
|
@@ -356,50 +401,12 @@ export function checkConsolidationCandidates(params) {
|
|
|
356
401
|
});
|
|
357
402
|
}
|
|
358
403
|
}
|
|
359
|
-
// ②
|
|
360
|
-
if (config.enableAgent) {
|
|
361
|
-
const hit = countTagged(root, selfAid, peerKeys, 'to-generalize', useMetaIndex);
|
|
362
|
-
if (hit)
|
|
363
|
-
out.push({ mode: 'generalize', reason: `发现 ${hit.count} 条 to-generalize` });
|
|
364
|
-
}
|
|
365
|
-
// ③ 待提升 → promote(只看 owner 关系)
|
|
366
|
-
if (config.enableOwner && ownerAid) {
|
|
367
|
-
const ownerPeerKey = findOwnerPeerKey(peerKeys, ownerAid);
|
|
368
|
-
if (ownerPeerKey) {
|
|
369
|
-
const hit = countTagged(root, selfAid, [ownerPeerKey], 'to-promote', useMetaIndex);
|
|
370
|
-
if (hit) {
|
|
371
|
-
out.push({ mode: 'promote', peerKey: ownerPeerKey, reason: `发现 ${hit.count} 条 to-promote` });
|
|
372
|
-
}
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
// ④ 画像超期 → profile
|
|
376
|
-
for (const peerKey of peerKeys) {
|
|
377
|
-
const memDir = relationMemoryDir(root, selfAid, peerKey);
|
|
378
|
-
if (profileDueWithMeta(memDir, now, config.profileRegenDays, useMetaIndex)) {
|
|
379
|
-
out.push({ mode: 'profile', peerKey, reason: '索引有变化且画像超期' });
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// ⑤ 超期记忆 → forget(agent 级,扫描所有 relation + agent 自身,合并成一个会话)
|
|
383
|
-
let hasRelationForget = false;
|
|
384
|
-
for (const peerKey of peerKeys) {
|
|
385
|
-
const dir = path.join(relationMemoryDir(root, selfAid, peerKey), 'consolidated');
|
|
386
|
-
const due = checkStale(dir, now, config.forgetAfterDays);
|
|
387
|
-
if (due > 0)
|
|
388
|
-
hasRelationForget = true;
|
|
389
|
-
}
|
|
390
|
-
// ⑤b agent 自身的 consolidated 超期
|
|
404
|
+
// ② agent 层超期 → forget scope=agent(只审 personal/memory 自身;关系层由关系整理链负责)
|
|
391
405
|
const agentConsolidated = path.join(root, 'agents', selfAid, 'personal', 'memory', 'consolidated');
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
if (hasRelationForget || hasAgentForget) {
|
|
395
|
-
const parts = [];
|
|
396
|
-
if (hasRelationForget)
|
|
397
|
-
parts.push('关系级记忆超期');
|
|
398
|
-
if (hasAgentForget)
|
|
399
|
-
parts.push('agent 级记忆超期');
|
|
400
|
-
out.push({ mode: 'forget', scope: 'agent', reason: parts.join('、') });
|
|
406
|
+
if (config.enableAgent && checkStale(agentConsolidated, now, config.forgetAfterDays) > 0) {
|
|
407
|
+
out.push({ mode: 'forget', scope: 'agent', reason: 'agent 级记忆超期' });
|
|
401
408
|
}
|
|
402
|
-
//
|
|
409
|
+
// ③ owner 层超期 → forget scope=owner(owner 层与 agent / relation 层是平行的)
|
|
403
410
|
if (config.enableOwner && ownerAid) {
|
|
404
411
|
const ownerConsolidated = path.join(root, 'memory', 'owner', ownerAid, 'consolidated');
|
|
405
412
|
if (checkStale(ownerConsolidated, now, config.forgetAfterDays) > 0) {
|
|
@@ -12,13 +12,14 @@ import fs from 'fs';
|
|
|
12
12
|
import { getPrimaryOwner, ownerResolver } from '../ownership/owner-resolver.js';
|
|
13
13
|
import path from 'path';
|
|
14
14
|
import { agentDir, resolveRoot } from '../../paths.js';
|
|
15
|
-
import { serializeConsolidationContext } from './consolidation-context.js';
|
|
15
|
+
import { isSilentConsolidationTrigger, serializeConsolidationContext } from './consolidation-context.js';
|
|
16
16
|
import { assertSystemSessionLaunchable } from '../system-session/registry.js';
|
|
17
17
|
import { resolveAgentLifecycle } from '../../config/lifecycle.js';
|
|
18
18
|
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
19
19
|
import { formatChannelKey } from '../channel-loader.js';
|
|
20
20
|
import { isTopicThreadId } from '../thread/topic-id.js';
|
|
21
21
|
import { logger } from '../../utils/logger.js';
|
|
22
|
+
import { atomicWriteText } from '../../utils/atomic-write.js';
|
|
22
23
|
import { appendOpsRecord, feedbackFilePath, readFeedbackText } from './consolidation-ops-records.js';
|
|
23
24
|
import { updateBatchMeta } from './batch-meta.js';
|
|
24
25
|
import { checkConsolidationCandidates, shouldUseMetaIndex } from './consolidation-trigger.js';
|
|
@@ -51,6 +52,54 @@ function cleanupPreviousConsolidationBatches(_selfAid) {
|
|
|
51
52
|
logger.warn(`[Memory] 清理整理批次时出错: ${e?.message}`);
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
const PROCESSING_LEASE_FILENAME = '.processing-lease.json';
|
|
56
|
+
export const PROCESSING_LEASE_TIMEOUT_MS = 2 * 60 * 60_000;
|
|
57
|
+
function processingLeasePath(memoryDir) {
|
|
58
|
+
return path.join(memoryDir, PROCESSING_LEASE_FILENAME);
|
|
59
|
+
}
|
|
60
|
+
function writeProcessingLease(memoryDir, lease) {
|
|
61
|
+
atomicWriteText(processingLeasePath(memoryDir), JSON.stringify(lease) + '\n');
|
|
62
|
+
}
|
|
63
|
+
function readProcessingLease(memoryDir) {
|
|
64
|
+
const filePath = processingLeasePath(memoryDir);
|
|
65
|
+
let stat;
|
|
66
|
+
try {
|
|
67
|
+
stat = fs.lstatSync(filePath);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (error.code === 'ENOENT')
|
|
71
|
+
return undefined;
|
|
72
|
+
throw error;
|
|
73
|
+
}
|
|
74
|
+
if (!stat.isFile())
|
|
75
|
+
throw new Error(`记忆整理 processing 租约不是普通文件: ${filePath}`);
|
|
76
|
+
let value;
|
|
77
|
+
try {
|
|
78
|
+
value = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
throw new Error(`记忆整理 processing 租约损坏: ${filePath}`);
|
|
82
|
+
}
|
|
83
|
+
if (!value || typeof value !== 'object'
|
|
84
|
+
|| value.version !== 1
|
|
85
|
+
|| !Number.isSafeInteger(value.claimedAt)
|
|
86
|
+
|| value.claimedAt < 0) {
|
|
87
|
+
throw new Error(`记忆整理 processing 租约格式无效: ${filePath}`);
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function removeProcessingLease(memoryDir) {
|
|
92
|
+
const filePath = processingLeasePath(memoryDir);
|
|
93
|
+
fs.rmSync(filePath, { force: true });
|
|
94
|
+
try {
|
|
95
|
+
for (const entry of fs.readdirSync(memoryDir)) {
|
|
96
|
+
if (entry.startsWith(`${PROCESSING_LEASE_FILENAME}.tmp.`)) {
|
|
97
|
+
fs.rmSync(path.join(memoryDir, entry), { force: true });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch { /* The memory directory may already have been removed. */ }
|
|
102
|
+
}
|
|
54
103
|
/**
|
|
55
104
|
* 首次设定(lifecycle)没完成的 agent 不能整理记忆:整理会话会被注入首次设定规则而拒绝干活,
|
|
56
105
|
* note 却照样被认领。返回拒绝原因;可以整理时返回 undefined。
|
|
@@ -93,27 +142,39 @@ export async function launchConsolidation(channel, params) {
|
|
|
93
142
|
// notes 模式:pending/ → processing/ 快照迁移(在序列化上下文之前执行,声明认领)
|
|
94
143
|
let memoryDir;
|
|
95
144
|
let claimedProcessing = false;
|
|
145
|
+
let processingClaimedAt;
|
|
96
146
|
if (mode === 'notes') {
|
|
97
147
|
memoryDir = effectiveCtx.peerKey
|
|
98
148
|
? path.join(resolveRoot(), 'agents', selfAid, 'relations', effectiveCtx.peerKey, 'memory')
|
|
99
149
|
: path.join(resolveRoot(), 'agents', selfAid, 'personal', 'memory');
|
|
100
150
|
const pendingDir = path.join(memoryDir, 'pending');
|
|
101
151
|
const processingDir = path.join(memoryDir, 'processing');
|
|
152
|
+
if (fs.existsSync(processingDir))
|
|
153
|
+
recoverStaleProcessing(memoryDir, Date.now());
|
|
102
154
|
if (fs.existsSync(processingDir)) {
|
|
103
155
|
throw new Error(`上一批整理尚未收尾,不能重新认领 processing: ${processingDir}`);
|
|
104
156
|
}
|
|
105
|
-
|
|
157
|
+
if (fs.existsSync(pendingDir)) {
|
|
106
158
|
try {
|
|
107
159
|
const count = fs.readdirSync(pendingDir).filter(f => f.endsWith('.md')).length;
|
|
108
160
|
fs.renameSync(pendingDir, processingDir);
|
|
109
161
|
claimedProcessing = true;
|
|
162
|
+
processingClaimedAt = Date.now();
|
|
110
163
|
// 立即重建空 pending 目录,供主会话后续写 note
|
|
111
164
|
fs.mkdirSync(pendingDir, { recursive: true });
|
|
165
|
+
writeProcessingLease(memoryDir, { version: 1, claimedAt: processingClaimedAt });
|
|
112
166
|
logger.info(`[Memory] pending → processing 快照完成(${count} 条 note): ${processingDir}`);
|
|
113
167
|
}
|
|
114
168
|
catch (e) {
|
|
115
|
-
|
|
116
|
-
|
|
169
|
+
if (claimedProcessing) {
|
|
170
|
+
try {
|
|
171
|
+
restorePendingNotes(memoryDir);
|
|
172
|
+
}
|
|
173
|
+
catch (restoreError) {
|
|
174
|
+
logger.error(`[Memory] pending → processing 失败后归还 note 失败: ${restoreError instanceof Error ? restoreError.message : String(restoreError)}`);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
throw new Error(`pending → processing 迁移失败: ${e?.message ?? String(e)}`, { cause: e });
|
|
117
178
|
}
|
|
118
179
|
}
|
|
119
180
|
}
|
|
@@ -189,6 +250,7 @@ export async function launchConsolidation(channel, params) {
|
|
|
189
250
|
...(effectiveCtx.peerKey ? { peerKey: effectiveCtx.peerKey } : {}),
|
|
190
251
|
...(feedbackOrigin ? { feedbackOrigin } : {}),
|
|
191
252
|
consolidationTriggeredBy: triggeredBy,
|
|
253
|
+
...(params.relationSweepId ? { relationSweepId: params.relationSweepId } : {}),
|
|
192
254
|
},
|
|
193
255
|
// cleanupPolicy 用默认 on_next_launch:清 evolcore session 记录;日志目录由 cleanupPreviousConsolidationLogs() 负责
|
|
194
256
|
});
|
|
@@ -255,12 +317,60 @@ function restorePendingNotes(memoryDir) {
|
|
|
255
317
|
const processingDir = path.join(memoryDir, 'processing');
|
|
256
318
|
if (!fs.existsSync(processingDir))
|
|
257
319
|
return;
|
|
320
|
+
if (!fs.lstatSync(processingDir).isDirectory())
|
|
321
|
+
throw new Error(`processing 不是普通目录: ${processingDir}`);
|
|
258
322
|
const pendingDir = path.join(memoryDir, 'pending');
|
|
259
323
|
fs.mkdirSync(pendingDir, { recursive: true });
|
|
260
324
|
const stamp = Date.now().toString(36);
|
|
261
|
-
for (const entry of fs.readdirSync(processingDir))
|
|
325
|
+
for (const entry of fs.readdirSync(processingDir)) {
|
|
326
|
+
if (entry === PROCESSING_LEASE_FILENAME) {
|
|
327
|
+
fs.rmSync(path.join(processingDir, entry), { force: true });
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
262
330
|
moveIntoDir(path.join(processingDir, entry), pendingDir, stamp);
|
|
331
|
+
}
|
|
263
332
|
fs.rmdirSync(processingDir);
|
|
333
|
+
removeProcessingLease(memoryDir);
|
|
334
|
+
}
|
|
335
|
+
function recoverStaleProcessing(memoryDir, now) {
|
|
336
|
+
const processingDir = path.join(memoryDir, 'processing');
|
|
337
|
+
const stat = fs.lstatSync(processingDir);
|
|
338
|
+
if (!stat.isDirectory())
|
|
339
|
+
throw new Error(`processing 不是普通目录: ${processingDir}`);
|
|
340
|
+
const lease = readProcessingLease(memoryDir);
|
|
341
|
+
const claimedAt = lease?.claimedAt ?? stat.mtimeMs;
|
|
342
|
+
const ageMs = Math.max(0, now - claimedAt);
|
|
343
|
+
if (ageMs < PROCESSING_LEASE_TIMEOUT_MS)
|
|
344
|
+
return false;
|
|
345
|
+
restorePendingNotes(memoryDir);
|
|
346
|
+
logger.warn(`[Memory] 回收过期 processing 锁并归还 note(ageMs=${ageMs}): ${processingDir}`);
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
export function recoverStaleConsolidationProcessing(selfAid, now = Date.now()) {
|
|
350
|
+
const root = resolveRoot();
|
|
351
|
+
const memoryDirs = [path.join(root, 'agents', selfAid, 'personal', 'memory')];
|
|
352
|
+
const relationsDir = path.join(root, 'agents', selfAid, 'relations');
|
|
353
|
+
try {
|
|
354
|
+
for (const entry of fs.readdirSync(relationsDir, { withFileTypes: true })) {
|
|
355
|
+
if (entry.isDirectory())
|
|
356
|
+
memoryDirs.push(path.join(relationsDir, entry.name, 'memory'));
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch { /* The Agent may not have relation data yet. */ }
|
|
360
|
+
const recovered = [];
|
|
361
|
+
for (const memoryDir of memoryDirs) {
|
|
362
|
+
const processingDir = path.join(memoryDir, 'processing');
|
|
363
|
+
if (!fs.existsSync(processingDir))
|
|
364
|
+
continue;
|
|
365
|
+
try {
|
|
366
|
+
if (recoverStaleProcessing(memoryDir, now))
|
|
367
|
+
recovered.push(processingDir);
|
|
368
|
+
}
|
|
369
|
+
catch (error) {
|
|
370
|
+
logger.warn(`[Memory] 检查 processing 租约失败: ${error instanceof Error ? error.message : String(error)}`);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
return recovered;
|
|
264
374
|
}
|
|
265
375
|
/**
|
|
266
376
|
* 整理会话结束后的收尾,task:completed / task:error / task:interrupted 都走这里。
|
|
@@ -283,6 +393,10 @@ export async function onConsolidationComplete(params) {
|
|
|
283
393
|
fs.mkdirSync(pendingDir, { recursive: true });
|
|
284
394
|
for (const entry of fs.readdirSync(processingDir)) {
|
|
285
395
|
const src = path.join(processingDir, entry);
|
|
396
|
+
if (entry === PROCESSING_LEASE_FILENAME) {
|
|
397
|
+
fs.rmSync(src, { force: true });
|
|
398
|
+
continue;
|
|
399
|
+
}
|
|
286
400
|
const isNote = entry.endsWith('.md') && fs.statSync(src).isFile();
|
|
287
401
|
moveIntoDir(src, isNote ? noteDest : pendingDir, stamp);
|
|
288
402
|
if (isNote)
|
|
@@ -291,6 +405,7 @@ export async function onConsolidationComplete(params) {
|
|
|
291
405
|
fs.rmdirSync(processingDir);
|
|
292
406
|
result.movedTo = noteDest;
|
|
293
407
|
}
|
|
408
|
+
removeProcessingLease(params.memoryDir);
|
|
294
409
|
}
|
|
295
410
|
// C3: update mode-specific checkpoint only when thread snapshots were used
|
|
296
411
|
if (params.success && params.chatDir && params.mode && params.usedThreadSnapshots) {
|
|
@@ -382,24 +497,52 @@ export async function rollbackConsolidation(batchDir, selfAid) {
|
|
|
382
497
|
return { rolledBack, errors };
|
|
383
498
|
}
|
|
384
499
|
const MAX_CONCURRENT_CONSOLIDATION = 5;
|
|
385
|
-
|
|
500
|
+
/** 从 ops 记录涉及的 memory 目录里挑出关系目录,返回它们的 peerKey(排除 exclude)。 */
|
|
501
|
+
function relationPeerKeysOf(scopes, exclude) {
|
|
502
|
+
const out = new Set();
|
|
503
|
+
for (const [scope, memoryDir] of scopes) {
|
|
504
|
+
if (scope !== 'relation')
|
|
505
|
+
continue;
|
|
506
|
+
// .../relations/<peerKey>/memory
|
|
507
|
+
const peerKey = path.basename(path.dirname(memoryDir));
|
|
508
|
+
if (peerKey && peerKey !== exclude)
|
|
509
|
+
out.add(peerKey);
|
|
510
|
+
}
|
|
511
|
+
return [...out];
|
|
512
|
+
}
|
|
513
|
+
export function createConsolidationSessionFinalizer({ sessionManager, getAgent, agentMap, releaseSlot, enqueueConsolidation, sendFeedback, onSessionFinalized, }) {
|
|
386
514
|
const finalizedSessions = new Set();
|
|
387
|
-
return async (sessionId, success) => {
|
|
515
|
+
return async (sessionId, success, models = {}) => {
|
|
388
516
|
let releaseAid;
|
|
517
|
+
let finalizedInfo;
|
|
389
518
|
try {
|
|
390
519
|
if (!sessionId)
|
|
391
520
|
return;
|
|
521
|
+
if (finalizedSessions.has(sessionId))
|
|
522
|
+
return;
|
|
523
|
+
// 读不到会话或批次目录时也要通知一次(只带 sessionId,链按它认领),否则链只能干等看门狗
|
|
524
|
+
finalizedInfo = { sessionId, success, touchedPeerKeys: [] };
|
|
392
525
|
const session = await sessionManager.getSessionById(sessionId);
|
|
393
526
|
if (!session)
|
|
394
527
|
return;
|
|
395
528
|
const meta = (session.metadata ?? {});
|
|
529
|
+
if (typeof meta.relationSweepId === 'string')
|
|
530
|
+
finalizedInfo.relationSweepId = meta.relationSweepId;
|
|
396
531
|
const batchDir = (meta.consolidationBatchDir ?? meta.consolidationLogBase);
|
|
397
532
|
if (!batchDir)
|
|
398
533
|
return;
|
|
399
|
-
if (finalizedSessions.has(sessionId))
|
|
400
|
-
return;
|
|
401
534
|
finalizedSessions.add(sessionId);
|
|
402
535
|
releaseAid = session.selfAID;
|
|
536
|
+
// 先占位:后续步骤即使抛错,链也要能推进,不能卡在这一步
|
|
537
|
+
finalizedInfo = {
|
|
538
|
+
sessionId,
|
|
539
|
+
selfAid: session.selfAID,
|
|
540
|
+
peerKey: typeof meta.peerKey === 'string' ? meta.peerKey : undefined,
|
|
541
|
+
mode: typeof meta.memoryMode === 'string' ? meta.memoryMode : undefined,
|
|
542
|
+
success,
|
|
543
|
+
relationSweepId: typeof meta.relationSweepId === 'string' ? meta.relationSweepId : undefined,
|
|
544
|
+
touchedPeerKeys: [],
|
|
545
|
+
};
|
|
403
546
|
const { summarizeOpsRecords, collectAffectedScopes } = await import('./consolidation-ops-records.js');
|
|
404
547
|
// 会话正常结束不等于整理做完了。notes 模式认领了 note,却既没写记忆也没写反馈,
|
|
405
548
|
// 说明 agent 没处理这批 note(例如拒绝了任务)——按失败收尾,note 回 pending 下次重试。
|
|
@@ -427,7 +570,10 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
427
570
|
});
|
|
428
571
|
try {
|
|
429
572
|
const affectedScopes = collectAffectedScopes(batchDir);
|
|
430
|
-
|
|
573
|
+
// 回滚后仍在流水里的写入不算数:失败的会话不让别的关系去整理
|
|
574
|
+
if (success)
|
|
575
|
+
finalizedInfo.touchedPeerKeys = relationPeerKeysOf(affectedScopes, finalizedInfo.peerKey);
|
|
576
|
+
if (affectedScopes.length > 0) {
|
|
431
577
|
const { rebuildIndex } = await import('./index-manager.js');
|
|
432
578
|
const selfAid = session.selfAID;
|
|
433
579
|
const memoryConfig = selfAid ? getAgent(selfAid)?.config.memory : undefined;
|
|
@@ -443,12 +589,17 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
443
589
|
}
|
|
444
590
|
}
|
|
445
591
|
catch { }
|
|
592
|
+
// feedback 只读一次:先落 batch.md,再清 scratch/,投递复用同一份文本。
|
|
593
|
+
// 同时挂到 session-end 记录上——scratch/ 收尾即删,batch.md 那段又嵌在
|
|
594
|
+
// frontmatter 后不好按时间线翻,ops 里留一份才能 grep 到。
|
|
595
|
+
const customText = readFeedbackText(batchDir);
|
|
446
596
|
appendOpsRecord(batchDir, {
|
|
447
597
|
ts: new Date().toISOString(),
|
|
448
598
|
op: 'session-end',
|
|
449
599
|
success,
|
|
450
600
|
movedNotes: completeResult.moved,
|
|
451
601
|
checkpointWritten: completeResult.checkpointWritten,
|
|
602
|
+
...(customText ? { feedbackText: customText } : {}),
|
|
452
603
|
});
|
|
453
604
|
if (success) {
|
|
454
605
|
const memoryMode = meta.memoryMode;
|
|
@@ -466,6 +617,9 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
466
617
|
const now = Date.now();
|
|
467
618
|
const affectedScopes = collectAffectedScopes(batchDir);
|
|
468
619
|
for (const [scope, memoryDir] of affectedScopes) {
|
|
620
|
+
// 关系层的到期审查是关系整理链的最后一步(relation-sweep.ts),这里不重复拉会话
|
|
621
|
+
if (scope === 'relation')
|
|
622
|
+
continue;
|
|
469
623
|
const consolidatedDir = path.join(memoryDir, 'consolidated');
|
|
470
624
|
let due = 0;
|
|
471
625
|
try {
|
|
@@ -519,8 +673,6 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
519
673
|
fs.writeFileSync(historyPath, JSON.stringify({ note: `runner ${baseagent} does not support getSessionMessages` }) + '\n', 'utf-8');
|
|
520
674
|
}
|
|
521
675
|
}
|
|
522
|
-
// feedback 只读一次:先落 batch.md,再清 scratch/,投递复用同一份文本
|
|
523
|
-
const customText = readFeedbackText(batchDir);
|
|
524
676
|
// batch.md 收尾更新:写入最终状态和 feedback 正文
|
|
525
677
|
try {
|
|
526
678
|
updateBatchMeta(batchDir, {
|
|
@@ -528,6 +680,8 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
528
680
|
finishedAt: new Date().toISOString(),
|
|
529
681
|
success,
|
|
530
682
|
...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
|
|
683
|
+
...(models.requestedModel ? { requestedModel: models.requestedModel } : {}),
|
|
684
|
+
...(models.actualModel ? { actualModel: models.actualModel } : {}),
|
|
531
685
|
...(failureReason ? { failureReason } : {}),
|
|
532
686
|
feedbackText: customText,
|
|
533
687
|
});
|
|
@@ -540,7 +694,7 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
540
694
|
catch { /* 静默,不影响投递 */ }
|
|
541
695
|
const feedbackOrigin = meta.feedbackOrigin;
|
|
542
696
|
const triggeredBy = meta.consolidationTriggeredBy;
|
|
543
|
-
if (feedbackOrigin && triggeredBy && triggeredBy
|
|
697
|
+
if (feedbackOrigin && triggeredBy && !isSilentConsolidationTrigger(triggeredBy) && session.selfAID) {
|
|
544
698
|
try {
|
|
545
699
|
const text = customText ?? renderDefaultConsolidationFeedback({
|
|
546
700
|
mode: meta.memoryMode,
|
|
@@ -550,7 +704,18 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
550
704
|
batchDir,
|
|
551
705
|
failureReason,
|
|
552
706
|
});
|
|
553
|
-
|
|
707
|
+
// 反馈是系统发起的一次性发送,不经过 runner 回复路径,context.debug 不会被自动填充。
|
|
708
|
+
// 这里显式补上,线上 payload 才能通过 debug.agentSessionId 回溯到本次整理会话。
|
|
709
|
+
// model 与 runner 回复路径同义(EvolCore 请求的模型),actualModel 为 runner 实际调用的模型。
|
|
710
|
+
await sendFeedback({
|
|
711
|
+
...feedbackOrigin,
|
|
712
|
+
debug: {
|
|
713
|
+
sessionId: session.id,
|
|
714
|
+
...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
|
|
715
|
+
...(models.requestedModel ? { model: models.requestedModel } : {}),
|
|
716
|
+
...(models.actualModel ? { actualModel: models.actualModel } : {}),
|
|
717
|
+
},
|
|
718
|
+
}, text, session.selfAID);
|
|
554
719
|
}
|
|
555
720
|
catch (error) {
|
|
556
721
|
logger.warn(`[Memory] 整理反馈投递失败(session=${sessionId}): ${error?.message}`);
|
|
@@ -563,6 +728,15 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
563
728
|
finally {
|
|
564
729
|
if (releaseAid && sessionId)
|
|
565
730
|
releaseSlot(releaseAid, sessionId);
|
|
731
|
+
// 槽位释放之后再通知:链的下一步入队时,本 agent 的串行队列已经空出来
|
|
732
|
+
if (finalizedInfo && onSessionFinalized) {
|
|
733
|
+
try {
|
|
734
|
+
onSessionFinalized(finalizedInfo);
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
logger.warn(`[Memory] 整理收尾回调失败(session=${sessionId}): ${error?.message}`);
|
|
738
|
+
}
|
|
739
|
+
}
|
|
566
740
|
// runtime 目录清理:immediate 策略下,finalizer 是最后一个要读它的消费者。
|
|
567
741
|
// 用 sessionId 无状态重算路径,不依赖 spec 传递——保证即使 spec 不传 runtimeDir
|
|
568
742
|
// 字段,目录也能在会话结束后被清掉。
|
|
@@ -602,6 +776,10 @@ export function createConsolidationFeedbackSender(getChannel) {
|
|
|
602
776
|
replyContext.threadId = threadId;
|
|
603
777
|
if (chatType)
|
|
604
778
|
replyContext.delivery = { chatType };
|
|
779
|
+
// 诊断字典(sessionId/agentSessionId),由整理收尾流程写入 origin;缺省时不上线。
|
|
780
|
+
const debug = origin.debug;
|
|
781
|
+
if (debug)
|
|
782
|
+
replyContext.debug = debug;
|
|
605
783
|
// 对比期:同一份反馈 notice、text 各发一条,观察两种模式在对端的差异,
|
|
606
784
|
// 跑通后删掉 text 那条。两条 taskId 后缀不同,避免共用 operationId 被出站去重吞掉一条;
|
|
607
785
|
// 各自 try,一条失败不连累另一条。
|
|
@@ -646,6 +824,13 @@ export function createConsolidationFeedbackSender(getChannel) {
|
|
|
646
824
|
throw new Error(failures.join('; '));
|
|
647
825
|
};
|
|
648
826
|
}
|
|
827
|
+
/** 从 bg-session 终态事件里取出模型字段,原样交给收尾流程。 */
|
|
828
|
+
function modelsFromEvent(event) {
|
|
829
|
+
return {
|
|
830
|
+
...(event.requestedModel ? { requestedModel: event.requestedModel } : {}),
|
|
831
|
+
...(event.actualModel ? { actualModel: event.actualModel } : {}),
|
|
832
|
+
};
|
|
833
|
+
}
|
|
649
834
|
export function registerConsolidationSessionEvents({ eventBus, permissionGateway, finalize, }) {
|
|
650
835
|
eventBus.subscribe('task:interrupted', event => {
|
|
651
836
|
if ('sessionId' in event && event.sessionId) {
|
|
@@ -664,15 +849,15 @@ export function registerConsolidationSessionEvents({ eventBus, permissionGateway
|
|
|
664
849
|
});
|
|
665
850
|
eventBus.subscribe('bg-session:completed', event => {
|
|
666
851
|
if (event.type === 'bg-session:completed')
|
|
667
|
-
void finalize(event.sessionId, true);
|
|
852
|
+
void finalize(event.sessionId, true, modelsFromEvent(event));
|
|
668
853
|
});
|
|
669
854
|
eventBus.subscribe('bg-session:error', event => {
|
|
670
855
|
if (event.type === 'bg-session:error')
|
|
671
|
-
void finalize(event.sessionId, false);
|
|
856
|
+
void finalize(event.sessionId, false, modelsFromEvent(event));
|
|
672
857
|
});
|
|
673
858
|
eventBus.subscribe('bg-session:interrupted', event => {
|
|
674
859
|
if (event.type === 'bg-session:interrupted')
|
|
675
|
-
void finalize(event.sessionId, false);
|
|
860
|
+
void finalize(event.sessionId, false, modelsFromEvent(event));
|
|
676
861
|
});
|
|
677
862
|
}
|
|
678
863
|
export class ConsolidationQueue {
|
|
@@ -682,12 +867,20 @@ export class ConsolidationQueue {
|
|
|
682
867
|
activeSessions = new Map();
|
|
683
868
|
timers = new Map();
|
|
684
869
|
finalizer;
|
|
870
|
+
launchFailedListener;
|
|
685
871
|
constructor(daemonChannel) {
|
|
686
872
|
this.daemonChannel = daemonChannel;
|
|
687
873
|
}
|
|
688
874
|
setFinalizer(finalizer) {
|
|
689
875
|
this.finalizer = finalizer;
|
|
690
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* 排队的请求轮到时拉起失败的通知。直接 enqueue 立即拉起失败时调用方已拿到 null,
|
|
879
|
+
* 不再重复通知;只有入队后由 drain 拉起的失败,调用方无从得知,靠这里补上。
|
|
880
|
+
*/
|
|
881
|
+
setLaunchFailedListener(listener) {
|
|
882
|
+
this.launchFailedListener = listener;
|
|
883
|
+
}
|
|
691
884
|
async enqueue(params) {
|
|
692
885
|
const aid = params.taskCtx.selfAid;
|
|
693
886
|
if (!aid)
|
|
@@ -729,7 +922,7 @@ export class ConsolidationQueue {
|
|
|
729
922
|
this.activeAgents.delete(aid);
|
|
730
923
|
void this.drain();
|
|
731
924
|
}
|
|
732
|
-
async start(params) {
|
|
925
|
+
async start(params, fromDrain = false) {
|
|
733
926
|
const aid = params.taskCtx.selfAid;
|
|
734
927
|
if (!aid)
|
|
735
928
|
return null;
|
|
@@ -763,6 +956,12 @@ export class ConsolidationQueue {
|
|
|
763
956
|
catch (error) {
|
|
764
957
|
logger.warn(`[Memory] 拉起整理会话失败(aid=${aid},mode=${params.mode}): ${error?.message}`);
|
|
765
958
|
this.activeAgents.delete(aid);
|
|
959
|
+
if (fromDrain && this.launchFailedListener) {
|
|
960
|
+
try {
|
|
961
|
+
this.launchFailedListener(params);
|
|
962
|
+
}
|
|
963
|
+
catch { /* 通知失败不影响队列 */ }
|
|
964
|
+
}
|
|
766
965
|
void this.drain();
|
|
767
966
|
return null;
|
|
768
967
|
}
|
|
@@ -782,7 +981,7 @@ export class ConsolidationQueue {
|
|
|
782
981
|
break;
|
|
783
982
|
if (this.queues.get(picked.aid)?.length === 0)
|
|
784
983
|
this.queues.delete(picked.aid);
|
|
785
|
-
await this.start(picked.params);
|
|
984
|
+
await this.start(picked.params, true);
|
|
786
985
|
}
|
|
787
986
|
}
|
|
788
987
|
}
|
|
@@ -803,7 +1002,7 @@ export function registerConsolidationLaunchExecutor({ ipcServer, enqueueConsolid
|
|
|
803
1002
|
return result;
|
|
804
1003
|
});
|
|
805
1004
|
}
|
|
806
|
-
export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation, }) {
|
|
1005
|
+
export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, enqueueConsolidation, requestRelationSweep, }) {
|
|
807
1006
|
ipcServer.setConsolidationCheckExecutor(async ({ selfAid }) => {
|
|
808
1007
|
const agent = agentRegistry.get(selfAid);
|
|
809
1008
|
const agentConfig = agent?.config;
|
|
@@ -811,6 +1010,7 @@ export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, e
|
|
|
811
1010
|
return { launched: [] };
|
|
812
1011
|
if (consolidationBlockedReason(selfAid))
|
|
813
1012
|
return { launched: [] };
|
|
1013
|
+
recoverStaleConsolidationProcessing(selfAid);
|
|
814
1014
|
await ownerResolver.refresh(selfAid);
|
|
815
1015
|
const ownerAid = getPrimaryOwner(selfAid);
|
|
816
1016
|
const agentCount = agentRegistry.runnableAgents().length;
|
|
@@ -833,6 +1033,11 @@ export function registerConsolidationCheckExecutor({ ipcServer, agentRegistry, e
|
|
|
833
1033
|
});
|
|
834
1034
|
const launched = [];
|
|
835
1035
|
for (const candidate of candidates) {
|
|
1036
|
+
if (candidate.peerKey && requestRelationSweep) {
|
|
1037
|
+
requestRelationSweep(selfAid, candidate.peerKey);
|
|
1038
|
+
launched.push({ mode: `sweep:${candidate.mode}`, peerKey: candidate.peerKey });
|
|
1039
|
+
continue;
|
|
1040
|
+
}
|
|
836
1041
|
const taskCtx = {
|
|
837
1042
|
selfAid,
|
|
838
1043
|
peerKey: candidate.peerKey,
|
|
@@ -928,6 +1133,7 @@ export function renderDefaultConsolidationFeedback(input) {
|
|
|
928
1133
|
compress: '上下文压缩',
|
|
929
1134
|
'session-new': '新会话',
|
|
930
1135
|
'periodic-trigger': '定时检查',
|
|
1136
|
+
'relation-idle': '关系空闲整理',
|
|
931
1137
|
};
|
|
932
1138
|
const mode = input.mode ?? 'notes';
|
|
933
1139
|
const cause = triggerLabel[input.triggeredBy] ?? input.triggeredBy;
|