evolcore 0.0.38 → 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 +64 -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 +54 -30
- 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/agentmd.js +21 -0
- package/dist/aun/aid/client.js +1 -1
- package/dist/aun/aid/control-aid.js +23 -98
- package/dist/aun/aid/ownership-operation.js +35 -12
- 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 +30 -6
- package/dist/cli/agent.js +30 -37
- package/dist/cli/aid-bind-command.js +16 -2
- package/dist/cli/aun-commands.js +31 -23
- package/dist/cli/config.js +15 -18
- package/dist/cli/contact.js +9 -12
- package/dist/cli/ctl-command.js +3 -3
- package/dist/cli/daemon-commands.js +242 -17
- package/dist/cli/diag.js +7 -3
- package/dist/cli/fs-command.js +5 -4
- package/dist/cli/handoff-command.js +11 -11
- package/dist/cli/help.js +18 -0
- package/dist/cli/index.js +25 -13
- package/dist/cli/init-channel.js +2 -1
- package/dist/cli/memory-command.js +47 -27
- package/dist/cli/model.js +5 -4
- package/dist/cli/queue-command.js +3 -3
- package/dist/cli/response.js +5 -4
- package/dist/cli/restart-monitor.js +35 -13
- package/dist/cli/skill-command.js +13 -13
- package/dist/cli/task-command.js +3 -3
- package/dist/cli/task-context.js +1 -0
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +3 -3
- package/dist/cli/version.js +6 -2
- 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 +17 -1
- package/dist/core/bootstrap-service.js +20 -12
- package/dist/core/command/cli-intent-parser.js +5 -2
- package/dist/core/command/command-handler.js +20 -5
- package/dist/core/command/menu-catalog.js +48 -12
- package/dist/core/command/menu-handler.js +74 -6
- 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 +465 -140
- package/dist/core/permission/ec-command-parser.js +19 -3
- package/dist/core/permission/protected-paths.js +51 -0
- 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/dist/utils/welcome.js +18 -24
- 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 +58 -8
- 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
|
@@ -38,6 +38,10 @@ export const MODE_SCOPE = {
|
|
|
38
38
|
promote: 'owner',
|
|
39
39
|
profile: 'relation',
|
|
40
40
|
};
|
|
41
|
+
/** 系统自动发起、不向对端发反馈的触发来源。 */
|
|
42
|
+
export function isSilentConsolidationTrigger(triggeredBy) {
|
|
43
|
+
return triggeredBy === 'periodic-trigger' || triggeredBy === 'relation-idle';
|
|
44
|
+
}
|
|
41
45
|
// 超期判据集中在 staleness.ts;这里做转发,保证 trigger 与 context 用同一套规则
|
|
42
46
|
export { DEFAULT_FORGET_AFTER_DAYS, LONG_TERM_REVIEW_DAYS } from './staleness.js';
|
|
43
47
|
// ── Checkpoint 读写 ──────────────────────────────────────────────────────────
|
|
@@ -136,6 +140,7 @@ function buildScopeContext(scope, input, selfAid,
|
|
|
136
140
|
/** 候选来源作用域,默认与本作用域一致 */
|
|
137
141
|
sourceScope) {
|
|
138
142
|
const memoryDir = memoryDirOf(scope, selfAid, input.taskCtx.peerKey, input.ownerAid);
|
|
143
|
+
// 整理一律按关系进行:generalize / promote 的候选只来自本次会话所属关系(taskCtx.peerKey)。
|
|
139
144
|
const candidateSourceDir = sourceScope === undefined || sourceScope === scope
|
|
140
145
|
? path.join(memoryDir, 'consolidated')
|
|
141
146
|
: path.join(memoryDirOf(sourceScope, selfAid, input.taskCtx.peerKey, input.ownerAid), 'consolidated');
|
|
@@ -212,14 +217,24 @@ function scanCandidates(consolidatedDir, pick) {
|
|
|
212
217
|
out.sort((a, b) => a.relPath.localeCompare(b.relPath));
|
|
213
218
|
return out;
|
|
214
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* 给关系级候选补上来源关系前缀:`consolidated/x.md` → `<peerKey>/consolidated/x.md`。
|
|
222
|
+
* generalize / promote 的产出层(agent / owner)与候选层(relation)不同,不带前缀的
|
|
223
|
+
* 路径在产出层会解析成同名文件。
|
|
224
|
+
*/
|
|
225
|
+
function withPeerPrefix(candidates, peerKey) {
|
|
226
|
+
if (!peerKey)
|
|
227
|
+
return candidates;
|
|
228
|
+
return candidates.map(c => ({ ...c, relPath: `${peerKey}/${c.relPath}` }));
|
|
229
|
+
}
|
|
215
230
|
/** 按模式填充候选清单。 */
|
|
216
|
-
function fillCandidates(ctx, mode, forgetAfterDays) {
|
|
231
|
+
function fillCandidates(ctx, mode, forgetAfterDays, sourcePeerKey) {
|
|
217
232
|
const source = ctx.candidateSourceDir ?? ctx.consolidatedDir;
|
|
218
233
|
if (mode === 'generalize') {
|
|
219
|
-
ctx.candidates = scanCandidates(source, ({ tags }) => tags.includes('to-generalize'));
|
|
234
|
+
ctx.candidates = withPeerPrefix(scanCandidates(source, ({ tags }) => tags.includes('to-generalize')), sourcePeerKey);
|
|
220
235
|
}
|
|
221
236
|
else if (mode === 'promote') {
|
|
222
|
-
ctx.candidates = scanCandidates(source, ({ tags }) => tags.includes('to-promote'));
|
|
237
|
+
ctx.candidates = withPeerPrefix(scanCandidates(source, ({ tags }) => tags.includes('to-promote')), sourcePeerKey);
|
|
223
238
|
}
|
|
224
239
|
else if (mode === 'profile') {
|
|
225
240
|
ctx.candidates = scanCandidates(source, () => true);
|
|
@@ -227,50 +242,15 @@ function fillCandidates(ctx, mode, forgetAfterDays) {
|
|
|
227
242
|
else if (mode === 'forget') {
|
|
228
243
|
const now = Date.now();
|
|
229
244
|
const thresholds = { forgetAfterDays };
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
const consolidatedDir = path.join(relationsRoot, peerKey, 'memory', 'consolidated');
|
|
240
|
-
if (fs.existsSync(consolidatedDir)) {
|
|
241
|
-
const candidates = collectCandidates(consolidatedDir, thresholds, now)
|
|
242
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
243
|
-
.map(info => ({
|
|
244
|
-
relPath: `${peerKey}/consolidated/${info.name}`,
|
|
245
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
246
|
-
tags: info.tags,
|
|
247
|
-
}));
|
|
248
|
-
ctx.candidates.push(...candidates);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
// agent 自身的 consolidated(原 ⑤b 拉起 forget/agent 的来源)
|
|
253
|
-
if (fs.existsSync(ctx.consolidatedDir)) {
|
|
254
|
-
const own = collectCandidates(ctx.consolidatedDir, thresholds, now)
|
|
255
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
256
|
-
.map(info => ({
|
|
257
|
-
relPath: `consolidated/${info.name}`,
|
|
258
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
259
|
-
tags: info.tags,
|
|
260
|
-
}));
|
|
261
|
-
ctx.candidates.push(...own);
|
|
262
|
-
}
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
// relation / owner 级 forget:只扫描当前层级的 consolidated
|
|
266
|
-
ctx.candidates = collectCandidates(source, thresholds, now)
|
|
267
|
-
.filter(info => isDueForReview(info, thresholds, now))
|
|
268
|
-
.map(info => ({
|
|
269
|
-
relPath: `consolidated/${info.name}`,
|
|
270
|
-
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
271
|
-
tags: info.tags,
|
|
272
|
-
}));
|
|
273
|
-
}
|
|
245
|
+
// 一次会话只审一层:relation / agent / owner 各自只扫本层 consolidated/。
|
|
246
|
+
// 关系层由关系整理链逐关系审,agent 层只审 personal/memory 自身。
|
|
247
|
+
ctx.candidates = collectCandidates(source, thresholds, now)
|
|
248
|
+
.filter(info => isDueForReview(info, thresholds, now))
|
|
249
|
+
.map(info => ({
|
|
250
|
+
relPath: `consolidated/${info.name}`,
|
|
251
|
+
description: readFmValue(info.content, 'description') ?? info.name.replace(/\.md$/, ''),
|
|
252
|
+
tags: info.tags,
|
|
253
|
+
}));
|
|
274
254
|
}
|
|
275
255
|
}
|
|
276
256
|
// ── 渲染 ────────────────────────────────────────────────────────────────────
|
|
@@ -285,7 +265,10 @@ function renderScopeSection(ctx, mode, threadFiles) {
|
|
|
285
265
|
lines.push(`- ownerAid: ${ctx.ownerAid}`);
|
|
286
266
|
if (ctx.baseagent)
|
|
287
267
|
lines.push(`- baseagent: ${ctx.baseagent}`);
|
|
288
|
-
|
|
268
|
+
// processingDir 只在 notes 模式下是有效输入(待整理的 note 在那里)。
|
|
269
|
+
// 其他模式不读它,列出来会诱导模型去 Glob 一个 hook 不放行的目录。
|
|
270
|
+
if (mode === 'notes')
|
|
271
|
+
lines.push(`- processingDir: ${ctx.processingDir}`);
|
|
289
272
|
lines.push(`- consolidatedDir: ${ctx.consolidatedDir}`);
|
|
290
273
|
lines.push(`- indexPath: ${ctx.indexPath}`);
|
|
291
274
|
lines.push('');
|
|
@@ -499,7 +482,7 @@ export function serializeConsolidationContext(input) {
|
|
|
499
482
|
const now = new Date();
|
|
500
483
|
const forgetAfterDays = input.forgetAfterDays ?? DEFAULT_FORGET_AFTER_DAYS;
|
|
501
484
|
for (const ctx of scopes) {
|
|
502
|
-
fillCandidates(ctx, input.mode, forgetAfterDays);
|
|
485
|
+
fillCandidates(ctx, input.mode, forgetAfterDays, input.taskCtx.peerKey);
|
|
503
486
|
}
|
|
504
487
|
// ── 批次目录(长期批次)────────────────────────────────────────────────────
|
|
505
488
|
const batchRoot = path.join(resolveRoot(), 'memory', 'consolidation-batches');
|
|
@@ -564,16 +547,24 @@ export function serializeConsolidationContext(input) {
|
|
|
564
547
|
'',
|
|
565
548
|
...scopes.flatMap(ctx => [renderScopeSection(ctx, input.mode, threadFiles), '', '---', '']),
|
|
566
549
|
];
|
|
567
|
-
// 列出可读根(工具 path/file_path
|
|
550
|
+
// 列出可读根(工具 path/file_path 参数必须落在这些路径内)。
|
|
551
|
+
// 这份清单必须与 system-session/memory.ts 的 memoryReadRoots 一致——两边各算一份
|
|
552
|
+
// 就会出现"任务书让模型去读、hook 却拒绝"的假阴性。
|
|
568
553
|
lines.push('', '**可读根(工具 path/file_path 参数必须落在这些路径内)**:');
|
|
569
554
|
lines.push('- 批次目录:`' + batchDir + '`');
|
|
570
555
|
for (const ctx of scopes) {
|
|
571
|
-
|
|
556
|
+
// 与 renderScopeSection 同判据:processingDir 只在 notes 模式可读
|
|
557
|
+
if (input.mode === 'notes' && ctx.processingDir)
|
|
572
558
|
lines.push('- processingDir: `' + ctx.processingDir + '`');
|
|
573
559
|
if (ctx.consolidatedDir)
|
|
574
560
|
lines.push('- consolidatedDir: `' + ctx.consolidatedDir + '`');
|
|
575
561
|
if (ctx.indexPath)
|
|
576
562
|
lines.push('- indexPath: `' + ctx.indexPath + '`');
|
|
563
|
+
// generalize / promote 的候选在本关系的 consolidated/(hook 只放行该目录,不含关系级 MEMORY.md)
|
|
564
|
+
if ((input.mode === 'generalize' || input.mode === 'promote') && input.taskCtx.peerKey) {
|
|
565
|
+
const relationMemoryDir = path.join(resolveRoot(), 'agents', ctx.selfAid, 'relations', input.taskCtx.peerKey, 'memory');
|
|
566
|
+
lines.push('- relationConsolidatedDir: `' + path.join(relationMemoryDir, 'consolidated') + '`');
|
|
567
|
+
}
|
|
577
568
|
}
|
|
578
569
|
if (historyJsonlPath)
|
|
579
570
|
lines.push('- 消息流水: `' + historyJsonlPath + '`');
|
|
@@ -76,15 +76,19 @@ export function readFeedbackText(batchDir) {
|
|
|
76
76
|
return undefined;
|
|
77
77
|
}
|
|
78
78
|
/**
|
|
79
|
-
* 收集 ops
|
|
80
|
-
* 供 finishConsolidationSession
|
|
81
|
-
*
|
|
79
|
+
* 收集 ops 记录里所有被写过的 memory 根目录及其作用域。
|
|
80
|
+
* 供 finishConsolidationSession 决定要对哪几个目录重建索引。
|
|
81
|
+
*
|
|
82
|
+
* 按 memoryDir 去重,不按 scope:同一 scope 可能涉及多个目录(群聊按发言人分流到
|
|
83
|
+
* 多条关系;archive 记的是源文件所属层)。按 scope 去重会只重建第一个目录的索引。
|
|
84
|
+
* 作用域优先从路径推断(archive 流水的 scope 是会话写入层,不一定是 memoryDir 所属层),
|
|
85
|
+
* 推不出来时用记录里的 scope。
|
|
82
86
|
*/
|
|
83
87
|
export function collectAffectedScopes(batchDir) {
|
|
84
88
|
const result = new Map();
|
|
85
89
|
const filePath = path.join(batchDir, 'ops.jsonl');
|
|
86
90
|
if (!fs.existsSync(filePath))
|
|
87
|
-
return
|
|
91
|
+
return [];
|
|
88
92
|
try {
|
|
89
93
|
for (const line of fs.readFileSync(filePath, 'utf-8').split('\n').filter(Boolean)) {
|
|
90
94
|
const entry = JSON.parse(line);
|
|
@@ -92,13 +96,41 @@ export function collectAffectedScopes(batchDir) {
|
|
|
92
96
|
continue;
|
|
93
97
|
const e = entry;
|
|
94
98
|
const s = e.scope;
|
|
95
|
-
if ((s === 'relation' || s === 'agent' || s === 'owner') && e.memoryDir && !result.has(
|
|
96
|
-
result.set(
|
|
99
|
+
if ((s === 'relation' || s === 'agent' || s === 'owner') && e.memoryDir && !result.has(e.memoryDir)) {
|
|
100
|
+
result.set(e.memoryDir, scopeOfMemoryDir(e.memoryDir) ?? s);
|
|
101
|
+
}
|
|
102
|
+
// create --replaces 跨层归档(如泛化把关系级源文件归档):源文件所在层也被改了,
|
|
103
|
+
// 它的索引同样要重建。源路径是绝对路径,形如 <memoryDir>/consolidated/<file>。
|
|
104
|
+
for (const af of e.archivedFiles ?? []) {
|
|
105
|
+
if (!af.source || !path.isAbsolute(af.source))
|
|
106
|
+
continue;
|
|
107
|
+
const consolidatedDir = path.dirname(af.source);
|
|
108
|
+
if (path.basename(consolidatedDir) !== 'consolidated')
|
|
109
|
+
continue;
|
|
110
|
+
const srcMemoryDir = path.dirname(consolidatedDir);
|
|
111
|
+
const srcScope = scopeOfMemoryDir(srcMemoryDir);
|
|
112
|
+
if (srcScope && !result.has(srcMemoryDir))
|
|
113
|
+
result.set(srcMemoryDir, srcScope);
|
|
97
114
|
}
|
|
98
115
|
}
|
|
99
116
|
}
|
|
100
117
|
catch { /* ignore */ }
|
|
101
|
-
return result;
|
|
118
|
+
return [...result].map(([memoryDir, scope]) => [scope, memoryDir]);
|
|
119
|
+
}
|
|
120
|
+
/** 从 memory 根目录路径推断作用域;不是标准布局时返回 undefined。 */
|
|
121
|
+
function scopeOfMemoryDir(memoryDir) {
|
|
122
|
+
const parts = path.resolve(memoryDir).split(path.sep);
|
|
123
|
+
const last = parts.length - 1;
|
|
124
|
+
// .../agents/<selfAid>/relations/<peerKey>/memory
|
|
125
|
+
if (parts[last] === 'memory' && parts[last - 2] === 'relations')
|
|
126
|
+
return 'relation';
|
|
127
|
+
// .../agents/<selfAid>/personal/memory
|
|
128
|
+
if (parts[last] === 'memory' && parts[last - 1] === 'personal')
|
|
129
|
+
return 'agent';
|
|
130
|
+
// .../memory/owner/<ownerAid>
|
|
131
|
+
if (parts[last - 1] === 'owner' && parts[last - 2] === 'memory')
|
|
132
|
+
return 'owner';
|
|
133
|
+
return undefined;
|
|
102
134
|
}
|
|
103
135
|
/**
|
|
104
136
|
* 读取 ops 记录里所有成功的写操作,按写入顺序倒序返回(最后写的先回撤)。
|
|
@@ -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) {
|