evolcore 0.0.28 → 0.0.29
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 +12 -0
- package/bin/ec-safe-output.js +270 -54
- package/dist/agents/baseagent-availability.js +37 -1
- package/dist/agents/baseagent.js +19 -9
- package/dist/agents/claude-runner.js +171 -13
- package/dist/agents/codex/app-server-client.js +6 -2
- package/dist/agents/codex/diagnostics.js +4 -3
- package/dist/agents/codex-runner.js +396 -95
- package/dist/agents/gemini-runner.js +35 -17
- package/dist/aun/group/history.js +130 -90
- package/dist/aun/group/operations.js +1 -3
- package/dist/aun/menu/transport.js +5 -3
- package/dist/aun/msg/managed-operation.js +70 -0
- package/dist/aun/msg/p2p.js +1 -3
- package/dist/aun/msg/upload.js +2 -2
- package/dist/aun/outbox.js +1 -37
- package/dist/channels/aun.js +249 -89
- package/dist/channels/daemon.js +18 -1
- package/dist/channels/feishu.js +1 -2
- package/dist/cli/agent-command.js +23 -3
- package/dist/cli/agent-create.js +68 -2
- package/dist/cli/agent.js +12 -2
- package/dist/cli/aun-commands.js +87 -13
- package/dist/cli/cli-argv.js +26 -0
- package/dist/cli/daemon-commands.js +40 -10
- package/dist/cli/data-command.js +4 -4
- package/dist/cli/handoff-command.js +2 -0
- package/dist/cli/index.js +18 -1
- package/dist/cli/managed-command-guard.js +10 -0
- package/dist/cli/memory-command.js +441 -0
- package/dist/cli/restart-monitor.js +62 -6
- package/dist/cli/skill-command.js +41 -0
- package/dist/cli/task-context.js +2 -23
- package/dist/cli/trigger-command.js +9 -1
- package/dist/cli/watch-msg-diag.js +209 -0
- package/dist/config/config-field-policy.js +52 -3
- package/dist/config/config-manager.js +17 -4
- package/dist/config/contact-book-store.js +2 -2
- package/dist/config/mention-mode.js +7 -2
- package/dist/config/merge.js +28 -9
- package/dist/config/resolved-config-op.js +1 -1
- package/dist/config/runtime-env.js +137 -0
- package/dist/config/schema-registry.js +11 -1
- package/dist/config-store.js +10 -10
- package/dist/core/audit/event-key.js +3 -1
- package/dist/core/audit/log-integrity.js +4 -0
- package/dist/core/auth/agent-delegation.js +2 -1
- package/dist/core/auth/auth-gateway.js +13 -6
- package/dist/core/auth/authorization-audit.js +54 -19
- package/dist/core/auth/operation-authorizer.js +17 -0
- package/dist/core/auth/operation-catalog.js +9 -0
- package/dist/core/causation/context.js +5 -1
- package/dist/core/command/agent-control.js +1 -2
- package/dist/core/command/command-handler.js +51 -23
- package/dist/core/command/memory-slash.js +270 -0
- package/dist/core/command/menu-catalog.js +1 -0
- package/dist/core/command/menu-handler.js +315 -60
- package/dist/core/command/menu-protocol.js +42 -4
- package/dist/core/command/slash-gate.js +10 -10
- package/dist/core/command/slash-handler.js +206 -64
- package/dist/core/data-migration.js +282 -505
- package/dist/core/event-catalog.js +26 -0
- package/dist/core/evolagent-registry.js +6 -0
- package/dist/core/evolagent.js +0 -10
- package/dist/core/handoff/store.js +1 -20
- package/dist/core/inference/text-inference.js +57 -1
- package/dist/core/memory/consolidation-context.js +505 -0
- package/dist/core/memory/consolidation-ops-records.js +124 -0
- package/dist/core/memory/consolidation-trigger.js +394 -0
- package/dist/core/memory/index-manager.js +308 -0
- package/dist/core/memory/launch-consolidation.js +395 -0
- package/dist/core/memory/memory-admin.js +234 -0
- package/dist/core/memory/memory-document.js +100 -0
- package/dist/core/memory/memory-session-policy.js +41 -0
- package/dist/core/memory/memory-signature.js +257 -0
- package/dist/core/memory/memory-writer.js +432 -0
- package/dist/core/memory/staleness.js +117 -0
- package/dist/core/message/auto-mention-gate.js +454 -0
- package/dist/core/message/im-renderer.js +69 -16
- package/dist/core/message/message-bridge.js +287 -21
- package/dist/core/message/message-log.js +1 -0
- package/dist/core/message/message-queue.js +114 -27
- package/dist/core/message/pending-hints.js +329 -161
- package/dist/core/message/response-engine.js +567 -80
- package/dist/core/permission/ec-command-parser.js +1029 -80
- package/dist/core/permission/protected-paths.js +376 -126
- package/dist/core/permission/sandbox-runtime.js +40 -12
- package/dist/core/permission/tool-error-code.js +96 -13
- package/dist/core/permission/tool-policy.js +276 -23
- package/dist/core/role/runtime-policy.js +3 -7
- package/dist/core/session/{runner-task-history.js → runner-event-summary.js} +1 -1
- package/dist/core/session/session-fs-store.js +0 -76
- package/dist/core/session/session-idle-monitor.js +355 -0
- package/dist/core/session/session-manager.js +478 -17
- package/dist/core/session/session-mapper.js +33 -16
- package/dist/core/session/session-renew.js +217 -155
- package/dist/core/session/{session-turns.js → session-transcript.js} +35 -6
- package/dist/core/skill/global-watcher.js +282 -0
- package/dist/core/skill/index-generator.js +84 -0
- package/dist/core/skill/startup.js +73 -0
- package/dist/core/skill/switch-resolver.js +115 -0
- package/dist/core/task/task-activity-store.js +219 -0
- package/dist/eck/kit-renderer.js +30 -3
- package/dist/eck/manifest-engine.js +26 -4
- package/dist/index.js +708 -18
- package/dist/ipc.js +564 -2
- package/dist/paths.js +30 -9
- package/dist/response-system/engines/v1/proactive-flow.js +116 -15
- package/dist/response-system/modes/single-session/index.js +3 -0
- package/dist/trigger/feedback.js +1 -0
- package/dist/trigger/manager.js +0 -10
- package/dist/trigger/scheduler.js +11 -0
- package/dist/trigger/validation.js +22 -0
- package/dist/utils/avatar-upload.js +59 -18
- package/dist/utils/error-dict.json +6 -3
- package/dist/utils/error-utils.js +27 -0
- package/dist/utils/logger.js +32 -10
- package/dist/utils/media-cache.js +84 -0
- package/dist/utils/msg-diag.js +97 -0
- package/dist/utils/welcome.js +37 -8
- package/kits/docs/AUTHORING.md +314 -0
- package/kits/docs/GUIDE.md +2 -0
- package/kits/docs/INDEX.md +2 -0
- package/kits/docs/aun/CHEATSHEET.md +4 -0
- package/kits/docs/aun/SDK.md +60 -0
- package/kits/docs/context-assembly.md +10 -6
- package/kits/docs/evolcore/INDEX.md +17 -3
- package/kits/docs/evolcore/agent-create.md +24 -0
- package/kits/docs/evolcore/agent.md +5 -2
- package/kits/docs/evolcore/config.md +1 -1
- package/kits/docs/evolcore/ec-capabilities.md +364 -0
- package/kits/docs/evolcore/group.md +34 -18
- package/kits/docs/evolcore/memory-architecture.md +78 -0
- package/kits/docs/evolcore/memory-consolidation-batches.md +133 -0
- package/kits/docs/evolcore/memory-consolidation.md +263 -0
- package/kits/docs/evolcore/memory-tags.md +63 -0
- package/kits/docs/evolcore/memory.md +110 -0
- package/kits/docs/evolcore/menu-client.md +27 -11
- package/kits/{schemas → docs/evolcore}/menu-exec-schema-commands.md +12 -6
- package/kits/docs/evolcore/menu-protocol.md +213 -0
- package/kits/docs/evolcore/msg.md +2 -0
- package/kits/docs/evolcore/trigger.md +7 -5
- package/kits/docs/path-registry.md +6 -8
- package/kits/docs/prompt-loading-architecture.md +5 -3
- package/kits/docs/venues/group.md +5 -4
- package/kits/eck_manifest.json +294 -88
- package/kits/eck_manifest.memory.json +257 -0
- package/kits/rules/03-identity.md +8 -8
- package/kits/rules/05-venue.md +4 -2
- package/kits/rules/06-channel.md +1 -1
- package/kits/schemas/_meta.json +276 -47
- package/kits/schemas/agent-config.schema.12.json +38 -2
- package/kits/schemas/agent-config.schema.13.json +591 -0
- package/kits/schemas/daemon.schema.6.json +9 -9
- package/kits/schemas/defaults.schema.5.json +29 -0
- package/kits/schemas/relation-config.schema.8.json +14 -1
- package/kits/schemas/relation-config.schema.9.json +129 -0
- package/kits/schemas/role-config.schema.1.json +3 -3
- package/kits/skills/.switches.json +3 -0
- package/kits/skills/ec-skill/SKILL.md +217 -0
- package/kits/skills/ec-skill/scripts/cli.mjs +128 -0
- package/kits/skills/ec-skill/scripts/deps.mjs +107 -0
- package/kits/skills/ec-skill/scripts/frontmatter.mjs +104 -0
- package/kits/skills/ec-skill/scripts/skill.mjs +629 -0
- package/kits/skills/ec-skill/scripts/template/SKILL.md.tmpl +30 -0
- package/kits/templates/decision/auto-mention.md +25 -0
- package/kits/templates/decision/session-renew.md +22 -0
- package/kits/templates/memory-consolidation/forget.md +104 -0
- package/kits/templates/memory-consolidation/generalize.md +76 -0
- package/kits/templates/memory-consolidation/notes.md +133 -0
- package/kits/templates/memory-consolidation/profile.md +58 -0
- package/kits/templates/memory-consolidation/promote.md +72 -0
- package/kits/templates/memory-consolidation/workflow.md +75 -0
- package/kits/templates/system-fragments/bootstrap.md +14 -5
- package/kits/templates/system-fragments/commands.md +1 -0
- package/kits/templates/system-fragments/group-history.md +6 -0
- package/kits/templates/system-fragments/identity.md +0 -3
- package/kits/templates/system-fragments/memory.md +79 -0
- package/kits/templates/system-fragments/reply.md +1 -0
- package/kits/templates/system-fragments/venue.md +1 -1
- package/package.json +4 -6
- package/dist/trigger/legacy-session-history.js +0 -19
- package/kits/templates/system-fragments/group-unread-history.md +0 -7
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* consolidation-trigger.ts — 整理候选检查(v4,纯函数)
|
|
3
|
+
*
|
|
4
|
+
* 职责只有一个:**检查五类候选,返回该拉起哪些整理会话。**
|
|
5
|
+
*
|
|
6
|
+
* ① pending 堆积超过 backlogHours 的关系 → notes 模式(无会话历史,凭 note 内容整理)
|
|
7
|
+
* ② 带 to-generalize 的记忆 → generalize 模式
|
|
8
|
+
* ③ 带 to-promote 的记忆 → promote 模式
|
|
9
|
+
* ④ 索引有变化且距上次画像超过 profileRegenDays → profile 模式
|
|
10
|
+
* ⑤ 有记忆 mtime 超过 forgetAfterDays → forget 模式
|
|
11
|
+
*
|
|
12
|
+
* 判据是**数文件和数标签**,不看轮次和工具调用次数——活干得多不代表写得出记忆,
|
|
13
|
+
* 只干两轮也可能产出五条要整理的内容。
|
|
14
|
+
*
|
|
15
|
+
* v4 变化:不再自己实现定时器(原 setInterval 类已删除)。定时由 evolcore trigger 的
|
|
16
|
+
* cron `execution.type='script'` 驱动,脚本调 daemon 的 `memory.check-consolidation`,
|
|
17
|
+
* daemon 侧调用本模块的 `checkConsolidationCandidates()`,结果经 enqueueConsolidation 入队。
|
|
18
|
+
*
|
|
19
|
+
* 因此本模块**不持有任何进程内状态**(无 lastTaskEndAt 空闲门控)——所有判据都读磁盘。
|
|
20
|
+
* 并发安全由 daemon 侧的整理队列保证(同 agent 串行)。
|
|
21
|
+
*/
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import path from 'path';
|
|
24
|
+
import { getPackageRoot, resolveRoot } from '../../paths.js';
|
|
25
|
+
import { atomicWriteJson } from '../../utils/atomic-write.js';
|
|
26
|
+
import { normalizeTriggerDefinition } from '../../trigger/validation.js';
|
|
27
|
+
import { DEFAULT_FORGET_AFTER_DAYS, collectCandidates, isDueForReview, deferCandidate, } from './staleness.js';
|
|
28
|
+
import { readMemoryMeta } from './index-manager.js';
|
|
29
|
+
import { getMemoryArray, parseMemoryDocument } from './memory-document.js';
|
|
30
|
+
/**
|
|
31
|
+
* 走 `_meta.json` 快路径的规模阈值:本机 agent × 关系数超过此值才启用。
|
|
32
|
+
* 小规模下全量扫描开销可忽略,且无元数据陈旧风险,故作为默认。
|
|
33
|
+
*/
|
|
34
|
+
export const META_INDEX_THRESHOLD = 200;
|
|
35
|
+
const DEFAULT_CRON = '17 * * * *';
|
|
36
|
+
const SCRIPT_NAME = 'consolidation-check.cjs';
|
|
37
|
+
export const CONSOLIDATION_CHECK_TRIGGER_ID = '__memory-consolidation-check';
|
|
38
|
+
/** Register the periodic consolidation candidate check trigger. */
|
|
39
|
+
export function seedConsolidationCheckTrigger(manager, owner, packageRoot = getPackageRoot()) {
|
|
40
|
+
const now = Date.now();
|
|
41
|
+
const definition = normalizeTriggerDefinition({
|
|
42
|
+
$schema_version: 3.1,
|
|
43
|
+
id: CONSOLIDATION_CHECK_TRIGGER_ID,
|
|
44
|
+
agentAid: owner.aid,
|
|
45
|
+
enabled: true,
|
|
46
|
+
name: CONSOLIDATION_CHECK_TRIGGER_ID,
|
|
47
|
+
description: 'System trigger: 定期检查记忆整理候选并拉起整理会话。',
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
origin: {
|
|
51
|
+
channelKey: 'daemon', channelType: 'daemon', channelId: owner.aid, session: 'main',
|
|
52
|
+
peerId: owner.aid, sessionKey: `daemon#${owner.aid}#__memory-check__`,
|
|
53
|
+
},
|
|
54
|
+
source: { type: 'cron', expression: owner.cronExpression ?? DEFAULT_CRON, timezone: owner.timezone ?? 'Asia/Shanghai' },
|
|
55
|
+
execution: {
|
|
56
|
+
type: 'script', script: { path: SCRIPT_NAME, runtime: 'node', timeoutMs: 60_000 },
|
|
57
|
+
permissionMode: 'bypass', onError: 'fail', noopSentinel: '[[NOOP]]',
|
|
58
|
+
},
|
|
59
|
+
feedback: { strategy: 'silent' },
|
|
60
|
+
reliability: { concurrency: 'forbid', missedPolicy: 'run_once', retry: { maxAttempts: 0, backoffMs: 30_000 } },
|
|
61
|
+
});
|
|
62
|
+
const dir = manager.triggerDir(definition.id);
|
|
63
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
64
|
+
const scriptPath = path.join(dir, SCRIPT_NAME);
|
|
65
|
+
fs.writeFileSync(scriptPath, buildConsolidationCheckScript(packageRoot, owner.aid));
|
|
66
|
+
fs.chmodSync(scriptPath, 0o755);
|
|
67
|
+
atomicWriteJson(manager.definitionPath(definition.id), definition);
|
|
68
|
+
fs.rmSync(manager.activePath(definition.id), { force: true });
|
|
69
|
+
}
|
|
70
|
+
export function removeConsolidationCheckTrigger(manager) {
|
|
71
|
+
fs.rmSync(manager.triggerDir(CONSOLIDATION_CHECK_TRIGGER_ID), { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
function buildConsolidationCheckScript(packageRoot, selfAid) {
|
|
74
|
+
const ipcEntry = path.join(packageRoot, 'dist', 'ipc.js');
|
|
75
|
+
const pathsEntry = path.join(packageRoot, 'dist', 'paths.js');
|
|
76
|
+
return `#!/usr/bin/env node
|
|
77
|
+
const { pathToFileURL } = require('node:url');
|
|
78
|
+
const fs = require('node:fs');
|
|
79
|
+
async function main() {
|
|
80
|
+
const { ipcQuery } = await import(pathToFileURL(${JSON.stringify(ipcEntry)}).href);
|
|
81
|
+
const { resolvePaths } = await import(pathToFileURL(${JSON.stringify(pathsEntry)}).href);
|
|
82
|
+
const paths = resolvePaths();
|
|
83
|
+
let controlToken;
|
|
84
|
+
try { controlToken = fs.readFileSync(paths.controlToken, 'utf8').trim(); } catch {}
|
|
85
|
+
const result = await ipcQuery(paths.socket, {
|
|
86
|
+
type: 'memory.check-consolidation',
|
|
87
|
+
selfAid: ${JSON.stringify(selfAid)},
|
|
88
|
+
...(controlToken ? { controlToken } : {}),
|
|
89
|
+
}, 55_000);
|
|
90
|
+
if (!result) {
|
|
91
|
+
process.stdout.write(JSON.stringify({ outcome: 'error', text: 'daemon unreachable', files: [] }));
|
|
92
|
+
process.exitCode = 1;
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const launched = result.launched ?? [];
|
|
96
|
+
process.stdout.write(JSON.stringify({ outcome: 'success', text: launched.length > 0 ? '拉起 ' + launched.length + ' 个整理会话: ' + launched.map(l => l.mode).join(', ') : '[[NOOP]]', files: [] }));
|
|
97
|
+
}
|
|
98
|
+
main().catch(error => { process.stdout.write(JSON.stringify({ outcome: 'error', text: String(error && error.message || error), files: [] })); process.exitCode = 1; });
|
|
99
|
+
`;
|
|
100
|
+
}
|
|
101
|
+
const DEFAULT_BACKLOG_HOURS = 24;
|
|
102
|
+
const DEFAULT_PROFILE_REGEN_DAYS = 3;
|
|
103
|
+
function effectiveConfig(config) {
|
|
104
|
+
const c = config ?? {};
|
|
105
|
+
return {
|
|
106
|
+
enableRelation: c.enableRelation ?? true,
|
|
107
|
+
enableAgent: c.enableAgent ?? true,
|
|
108
|
+
enableOwner: c.enableOwner ?? true,
|
|
109
|
+
backlogHours: c.backlogHours ?? DEFAULT_BACKLOG_HOURS,
|
|
110
|
+
forgetAfterDays: c.forgetAfterDays ?? DEFAULT_FORGET_AFTER_DAYS,
|
|
111
|
+
profileRegenDays: c.profileRegenDays ?? DEFAULT_PROFILE_REGEN_DAYS,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function relationMemoryDir(root, selfAid, peerKey) {
|
|
115
|
+
return path.join(root, 'agents', selfAid, 'relations', peerKey, 'memory');
|
|
116
|
+
}
|
|
117
|
+
/** pending/ 里最老一条 note 距今多少小时(没有 note 返回 -1)。 */
|
|
118
|
+
function pendingBacklogHours(pendingDir, now) {
|
|
119
|
+
if (!fs.existsSync(pendingDir))
|
|
120
|
+
return -1;
|
|
121
|
+
let oldest;
|
|
122
|
+
for (const f of fs.readdirSync(pendingDir)) {
|
|
123
|
+
if (!f.endsWith('.md'))
|
|
124
|
+
continue;
|
|
125
|
+
try {
|
|
126
|
+
const m = fs.statSync(path.join(pendingDir, f)).mtimeMs;
|
|
127
|
+
if (oldest === undefined || m < oldest)
|
|
128
|
+
oldest = m;
|
|
129
|
+
}
|
|
130
|
+
catch { /* ignore */ }
|
|
131
|
+
}
|
|
132
|
+
if (oldest === undefined)
|
|
133
|
+
return -1;
|
|
134
|
+
return (now - oldest) / (60 * 60 * 1000);
|
|
135
|
+
}
|
|
136
|
+
/** 扫这些关系的 consolidated/,数带指定标签的文件。 */
|
|
137
|
+
function findTagged(root, selfAid, peerKeys, tag) {
|
|
138
|
+
let count = 0;
|
|
139
|
+
for (const peerKey of peerKeys) {
|
|
140
|
+
const dir = path.join(relationMemoryDir(root, selfAid, peerKey), 'consolidated');
|
|
141
|
+
if (!fs.existsSync(dir))
|
|
142
|
+
continue;
|
|
143
|
+
for (const f of fs.readdirSync(dir)) {
|
|
144
|
+
if (!f.endsWith('.md'))
|
|
145
|
+
continue;
|
|
146
|
+
try {
|
|
147
|
+
const content = fs.readFileSync(path.join(dir, f), 'utf-8');
|
|
148
|
+
const tags = getMemoryArray(parseMemoryDocument(content), 'tags');
|
|
149
|
+
if (tags.includes(tag))
|
|
150
|
+
count++;
|
|
151
|
+
}
|
|
152
|
+
catch { /* ignore */ }
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return count > 0 ? { count } : null;
|
|
156
|
+
}
|
|
157
|
+
/** 找出对端是 owner 的关系目录名。peerKey 形如 aun#<ownerAid>。 */
|
|
158
|
+
function findOwnerPeerKey(peerKeys, ownerAid) {
|
|
159
|
+
if (!ownerAid)
|
|
160
|
+
return null;
|
|
161
|
+
for (const peerKey of peerKeys) {
|
|
162
|
+
const peerId = peerKey.includes('#') ? peerKey.slice(peerKey.indexOf('#') + 1) : peerKey;
|
|
163
|
+
if (peerId === ownerAid)
|
|
164
|
+
return peerKey;
|
|
165
|
+
}
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
/** 在 consolidated/ 下找第一个带 profile tag 的文件路径。 */
|
|
169
|
+
function findProfileMemoryPath(consolidatedDir) {
|
|
170
|
+
if (!fs.existsSync(consolidatedDir))
|
|
171
|
+
return null;
|
|
172
|
+
for (const f of fs.readdirSync(consolidatedDir)) {
|
|
173
|
+
if (!f.endsWith('.md'))
|
|
174
|
+
continue;
|
|
175
|
+
const fp = path.join(consolidatedDir, f);
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(fp, 'utf-8');
|
|
178
|
+
const tags = getMemoryArray(parseMemoryDocument(content), 'tags');
|
|
179
|
+
if (tags.includes('profile'))
|
|
180
|
+
return fp;
|
|
181
|
+
}
|
|
182
|
+
catch { /* ignore */ }
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
/** consolidated/ 里有没有记忆。 */
|
|
187
|
+
function hasAnyMemory(memDir) {
|
|
188
|
+
const dir = path.join(memDir, 'consolidated');
|
|
189
|
+
if (!fs.existsSync(dir))
|
|
190
|
+
return false;
|
|
191
|
+
return fs.readdirSync(dir).some(f => f.endsWith('.md'));
|
|
192
|
+
}
|
|
193
|
+
/** 画像是否该重建:索引 mtime 晚于画像 mtime,且画像超过 profileRegenDays 没更新。 */
|
|
194
|
+
function profileDue(memDir, now, profileRegenDays) {
|
|
195
|
+
const indexPath = path.join(memDir, 'MEMORY.md');
|
|
196
|
+
const consolidatedDir = path.join(memDir, 'consolidated');
|
|
197
|
+
const profilePath = findProfileMemoryPath(consolidatedDir);
|
|
198
|
+
// 还没有画像:只要 consolidated/ 里已经有记忆,就该建。
|
|
199
|
+
// 不能要求索引存在 —— 索引是整理会话跑完才生成的,
|
|
200
|
+
// 而「有记忆但还没整理过」恰恰是最该建画像的时候。
|
|
201
|
+
if (!profilePath)
|
|
202
|
+
return hasAnyMemory(memDir);
|
|
203
|
+
if (!fs.existsSync(indexPath))
|
|
204
|
+
return false;
|
|
205
|
+
let indexMtime;
|
|
206
|
+
try {
|
|
207
|
+
indexMtime = fs.statSync(indexPath).mtimeMs;
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
let profileMtime;
|
|
213
|
+
try {
|
|
214
|
+
profileMtime = fs.statSync(profilePath).mtimeMs;
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
const days = (now - profileMtime) / (24 * 60 * 60 * 1000);
|
|
220
|
+
return indexMtime > profileMtime && days >= profileRegenDays;
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* 数带指定标签的记忆。
|
|
224
|
+
*
|
|
225
|
+
* useMetaIndex=true 时先读各关系的 `_meta.json`:
|
|
226
|
+
* - to-generalize / to-promote 直接由布尔字段判定(无需读正文)
|
|
227
|
+
* - 元数据缺失 → 该关系回退全量扫描
|
|
228
|
+
* 返回值只关心「有没有 + 有几条」,元数据只给出布尔,故命中时给个占位计数。
|
|
229
|
+
*/
|
|
230
|
+
function countTagged(root, selfAid, peerKeys, tag, useMetaIndex) {
|
|
231
|
+
if (!useMetaIndex)
|
|
232
|
+
return findTagged(root, selfAid, peerKeys, tag);
|
|
233
|
+
let count = 0;
|
|
234
|
+
const fallbackPeerKeys = [];
|
|
235
|
+
for (const peerKey of peerKeys) {
|
|
236
|
+
const memDir = relationMemoryDir(root, selfAid, peerKey);
|
|
237
|
+
const meta = readMemoryMeta(memDir);
|
|
238
|
+
if (!meta) {
|
|
239
|
+
fallbackPeerKeys.push(peerKey);
|
|
240
|
+
continue;
|
|
241
|
+
}
|
|
242
|
+
if (tag === 'to-generalize' ? meta.hasToGeneralize : meta.hasToPromote)
|
|
243
|
+
count++;
|
|
244
|
+
}
|
|
245
|
+
// 元数据缺失的关系回退全量扫描
|
|
246
|
+
if (fallbackPeerKeys.length > 0) {
|
|
247
|
+
const fallback = findTagged(root, selfAid, fallbackPeerKeys, tag);
|
|
248
|
+
if (fallback)
|
|
249
|
+
count += fallback.count;
|
|
250
|
+
}
|
|
251
|
+
return count > 0 ? { count } : null;
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* 画像是否该重建。
|
|
255
|
+
* useMetaIndex=true 时用 `_meta.json` 的 profileSlug/profileMtimeMs 判定;
|
|
256
|
+
* 元数据缺失或没有画像记录时回退全量扫描。
|
|
257
|
+
*/
|
|
258
|
+
function profileDueWithMeta(memDir, now, profileRegenDays, useMetaIndex) {
|
|
259
|
+
if (!useMetaIndex)
|
|
260
|
+
return profileDue(memDir, now, profileRegenDays);
|
|
261
|
+
const meta = readMemoryMeta(memDir);
|
|
262
|
+
if (!meta)
|
|
263
|
+
return profileDue(memDir, now, profileRegenDays);
|
|
264
|
+
const indexPath = path.join(memDir, 'MEMORY.md');
|
|
265
|
+
// 没有画像:只要 consolidated/ 里已经有记忆,就该建。
|
|
266
|
+
if (meta.profileSlug === null)
|
|
267
|
+
return meta.count > 0;
|
|
268
|
+
if (!fs.existsSync(indexPath))
|
|
269
|
+
return false;
|
|
270
|
+
let indexMtime;
|
|
271
|
+
try {
|
|
272
|
+
indexMtime = fs.statSync(indexPath).mtimeMs;
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
const profileMtime = meta.profileMtimeMs ?? 0;
|
|
278
|
+
const days = (now - profileMtime) / (24 * 60 * 60 * 1000);
|
|
279
|
+
return indexMtime > profileMtime && days >= profileRegenDays;
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* 检查一个作用域的超期记忆,并对**尚未到审查期**的长期记忆做减速。
|
|
283
|
+
*
|
|
284
|
+
* 两个时间戳分工:
|
|
285
|
+
* mtime 进候选的门票(超 forgetAfterDays)。长期记忆进候选后由这里刷新 mtime,
|
|
286
|
+
* 于是 forgetAfterDays 内不会再进候选 —— 但 updatedAt 不动。
|
|
287
|
+
* updatedAt 真正的审查节拍。普通记忆超 forgetAfterDays、长期记忆超 30 天才该审。
|
|
288
|
+
*
|
|
289
|
+
* 注意:本函数有副作用(刷新 mtime + 移动验签失败文件由上游负责)。
|
|
290
|
+
*
|
|
291
|
+
* @returns 真正该交给模型审查的条数
|
|
292
|
+
*/
|
|
293
|
+
function checkStale(consolidatedDir, now, forgetAfterDays) {
|
|
294
|
+
const thresholds = { forgetAfterDays };
|
|
295
|
+
let due = 0;
|
|
296
|
+
for (const info of collectCandidates(consolidatedDir, thresholds, now)) {
|
|
297
|
+
if (isDueForReview(info, thresholds, now)) {
|
|
298
|
+
due++;
|
|
299
|
+
}
|
|
300
|
+
else {
|
|
301
|
+
try {
|
|
302
|
+
deferCandidate(info.filePath, now);
|
|
303
|
+
}
|
|
304
|
+
catch { /* ignore */ }
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
return due;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* 是否该走 `_meta.json` 快路径。
|
|
311
|
+
* 小规模(agent × 关系数 ≤ 阈值)用全量扫描:实现简单、无陈旧风险。
|
|
312
|
+
*/
|
|
313
|
+
export function shouldUseMetaIndex(agentCount, relationCount) {
|
|
314
|
+
return agentCount * relationCount > META_INDEX_THRESHOLD;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* 检查五类候选,返回该拉起的整理会话列表。
|
|
318
|
+
*
|
|
319
|
+
* 纯检查 + 少量副作用(forget 的长期记忆减速刷新 mtime)。
|
|
320
|
+
* 调用方负责把结果入队(见 index.ts 的 enqueueConsolidation)。
|
|
321
|
+
*
|
|
322
|
+
* 两种扫描路径:
|
|
323
|
+
* - useMetaIndex=false(默认):读每个 .md 全文数标签 / 找画像(全量扫描)
|
|
324
|
+
* - useMetaIndex=true:先读 `_meta.json` 汇总;缺失或不符时该作用域回退全量扫描
|
|
325
|
+
*/
|
|
326
|
+
export function checkConsolidationCandidates(params) {
|
|
327
|
+
const { selfAid, ownerAid } = params;
|
|
328
|
+
const now = params.now ?? Date.now();
|
|
329
|
+
const root = params.root ?? resolveRoot();
|
|
330
|
+
const config = effectiveConfig(params.config);
|
|
331
|
+
const useMetaIndex = params.useMetaIndex ?? false;
|
|
332
|
+
const out = [];
|
|
333
|
+
if (!config.enableRelation)
|
|
334
|
+
return out;
|
|
335
|
+
const relationsDir = path.join(root, 'agents', selfAid, 'relations');
|
|
336
|
+
const peerKeys = fs.existsSync(relationsDir)
|
|
337
|
+
? fs.readdirSync(relationsDir, { withFileTypes: true }).filter(d => d.isDirectory()).map(d => d.name)
|
|
338
|
+
: [];
|
|
339
|
+
// ① pending 堆积 → notes
|
|
340
|
+
for (const peerKey of peerKeys) {
|
|
341
|
+
const pendingDir = path.join(relationsDir, peerKey, 'memory', 'pending');
|
|
342
|
+
const backlog = pendingBacklogHours(pendingDir, now);
|
|
343
|
+
if (backlog >= config.backlogHours) {
|
|
344
|
+
out.push({
|
|
345
|
+
mode: 'notes',
|
|
346
|
+
peerKey,
|
|
347
|
+
reason: `pending 堆积 ${backlog.toFixed(1)}h(>${config.backlogHours}h)`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// ② 待泛化 → generalize(扫所有关系)
|
|
352
|
+
if (config.enableAgent) {
|
|
353
|
+
const hit = countTagged(root, selfAid, peerKeys, 'to-generalize', useMetaIndex);
|
|
354
|
+
if (hit)
|
|
355
|
+
out.push({ mode: 'generalize', reason: `发现 ${hit.count} 条 to-generalize` });
|
|
356
|
+
}
|
|
357
|
+
// ③ 待提升 → promote(只看 owner 关系)
|
|
358
|
+
if (config.enableOwner && ownerAid) {
|
|
359
|
+
const ownerPeerKey = findOwnerPeerKey(peerKeys, ownerAid);
|
|
360
|
+
if (ownerPeerKey) {
|
|
361
|
+
const hit = countTagged(root, selfAid, [ownerPeerKey], 'to-promote', useMetaIndex);
|
|
362
|
+
if (hit) {
|
|
363
|
+
out.push({ mode: 'promote', peerKey: ownerPeerKey, reason: `发现 ${hit.count} 条 to-promote` });
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
// ④ 画像超期 → profile
|
|
368
|
+
for (const peerKey of peerKeys) {
|
|
369
|
+
const memDir = relationMemoryDir(root, selfAid, peerKey);
|
|
370
|
+
if (profileDueWithMeta(memDir, now, config.profileRegenDays, useMetaIndex)) {
|
|
371
|
+
out.push({ mode: 'profile', peerKey, reason: '索引有变化且画像超期' });
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
// ⑤ 超期记忆 → forget(关系级)
|
|
375
|
+
for (const peerKey of peerKeys) {
|
|
376
|
+
const dir = path.join(relationMemoryDir(root, selfAid, peerKey), 'consolidated');
|
|
377
|
+
const due = checkStale(dir, now, config.forgetAfterDays);
|
|
378
|
+
if (due > 0) {
|
|
379
|
+
out.push({ mode: 'forget', peerKey, scope: 'relation', reason: `${due} 条记忆超过审查周期(未续期)` });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
// ⑤b 超期记忆 → forget(agent 级 / owner 级)
|
|
383
|
+
const agentConsolidated = path.join(root, 'agents', selfAid, 'personal', 'memory', 'consolidated');
|
|
384
|
+
if (config.enableAgent && checkStale(agentConsolidated, now, config.forgetAfterDays) > 0) {
|
|
385
|
+
out.push({ mode: 'forget', scope: 'agent', reason: 'agent 级记忆超期' });
|
|
386
|
+
}
|
|
387
|
+
if (config.enableOwner && ownerAid) {
|
|
388
|
+
const ownerConsolidated = path.join(root, 'memory', 'owner', ownerAid, 'consolidated');
|
|
389
|
+
if (checkStale(ownerConsolidated, now, config.forgetAfterDays) > 0) {
|
|
390
|
+
out.push({ mode: 'forget', scope: 'owner', reason: 'owner 级记忆超期' });
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
return out;
|
|
394
|
+
}
|