evolcore 0.0.33 → 0.0.34
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 +37 -0
- package/bin/ec.js +3 -2
- package/bin/install-codex-managed-hooks.mjs +12 -1
- package/dist/agents/baseagent-availability.js +20 -36
- package/dist/agents/claude-runner.js +82 -13
- package/dist/agents/codex/diagnostics.js +37 -11
- package/dist/agents/codex-runner.js +218 -44
- package/dist/agents/ecagent-runner.js +73 -20
- package/dist/agents/runner-types.js +5 -6
- package/dist/aun/aid/identity.js +1 -1
- package/dist/channels/aun.js +16 -6
- package/dist/channels/daemon.js +6 -3
- package/dist/cli/aun-commands.js +3 -2
- package/dist/cli/ctl-command.js +3 -1
- package/dist/cli/daemon-commands.js +120 -19
- package/dist/cli/fs-command.js +2 -1
- package/dist/cli/index.js +20 -6
- package/dist/cli/integrity-command.js +196 -0
- package/dist/cli/memory-command.js +158 -145
- package/dist/cli/thread-command.js +5 -3
- package/dist/cli/trigger-command.js +6 -1
- package/dist/config/config-field-policy.js +4 -0
- package/dist/config/config-manager.js +19 -0
- package/dist/config/contact-book-store.js +5 -0
- package/dist/config/role-service.js +13 -0
- package/dist/config/runtime-env.js +16 -0
- package/dist/config-store.js +30 -0
- package/dist/core/audit/log-integrity.js +3 -1
- package/dist/core/auth/agent-delegation.js +108 -35
- package/dist/core/auth/delegation-diagnostics.js +414 -0
- package/dist/core/auth/operation-authorizer.js +1 -1
- package/dist/core/auth/operation-catalog.js +16 -0
- package/dist/core/command/agent-control.js +18 -26
- package/dist/core/command/command-handler.js +138 -4
- package/dist/core/command/daemon-diagnostics.js +1 -0
- package/dist/core/command/menu-catalog.js +3 -0
- package/dist/core/command/menu-handler.js +22 -7
- package/dist/core/command/menu-protocol.js +1 -0
- package/dist/core/command/slash-handler.js +2 -0
- package/dist/core/daemon-bootstrap.js +4 -7
- package/dist/core/file-integrity/read.js +61 -0
- package/dist/core/file-integrity/store.js +262 -0
- package/dist/core/ipc-registration.js +36 -1
- package/dist/core/memory/batch-meta.js +132 -0
- package/dist/core/memory/consolidation-context.js +35 -14
- package/dist/core/memory/consolidation-ops-records.js +14 -16
- package/dist/core/memory/consolidation.js +91 -26
- package/dist/core/memory/index-manager.js +19 -28
- package/dist/core/memory/memory-admin.js +19 -14
- package/dist/core/memory/memory-document.js +53 -9
- package/dist/core/memory/memory-writer.js +30 -15
- package/dist/core/message/im-renderer.js +20 -4
- package/dist/core/message/message-bridge.js +2 -1
- package/dist/core/message/response-engine.js +584 -47
- package/dist/core/permission/ec-command-parser.js +93 -4
- package/dist/core/permission/protected-paths.js +5 -1
- package/dist/core/permission/readonly-shell-query.js +43 -5
- package/dist/core/permission/session-path-grants.js +334 -0
- package/dist/core/permission/tool-canonical.js +226 -0
- package/dist/core/permission/tool-policy.js +30 -62
- package/dist/core/role/runtime-policy.js +21 -10
- package/dist/core/session/session-fs-store.js +123 -8
- package/dist/core/session/session-manager.js +75 -5
- package/dist/core/skill/index-generator.js +17 -3
- package/dist/core/system-session/grants.js +40 -0
- package/dist/core/system-session/memory.js +222 -0
- package/dist/core/system-session/registry.js +74 -0
- package/dist/core/system-session/types.js +14 -0
- package/dist/core/thread/thread-service.js +13 -20
- package/dist/eck/kit-renderer.js +7 -2
- package/dist/eck/manifest-engine.js +24 -4
- package/dist/index.js +11 -2
- package/dist/ipc.js +51 -40
- package/dist/paths.js +70 -0
- package/dist/stats/billing.js +20 -8
- package/dist/trigger/feedback.js +6 -1
- package/dist/trigger/parser.js +8 -1
- package/dist/trigger/system-definitions.js +3 -3
- package/dist/utils/cross-platform.js +11 -2
- package/dist/utils/error-dict.json +18 -4
- package/dist/utils/error-utils.js +121 -0
- package/dist/utils/instance-registry.js +13 -8
- package/dist/utils/log-writer.js +12 -0
- package/dist/utils/process-introspect.js +11 -0
- package/kits/docs/context-assembly.md +0 -1
- package/kits/docs/evolcore/agent-create.md +5 -4
- package/kits/docs/evolcore/agent.md +2 -2
- package/kits/docs/evolcore/config.md +2 -2
- package/kits/docs/evolcore/ctl.md +2 -2
- package/kits/docs/evolcore/ec-capabilities.md +1 -1
- package/kits/docs/evolcore/fs-architecture.md +4 -5
- package/kits/docs/evolcore/fs.md +1 -1
- package/kits/docs/evolcore/group-collaboration.md +2 -2
- package/kits/docs/evolcore/group.md +10 -10
- package/kits/docs/evolcore/memory-architecture.md +4 -5
- package/kits/docs/evolcore/memory-consolidation.md +18 -14
- package/kits/docs/evolcore/memory-tags.md +3 -1
- package/kits/docs/evolcore/memory.md +24 -12
- package/kits/docs/evolcore/model.md +16 -0
- package/kits/docs/evolcore/msg.md +7 -7
- package/kits/docs/evolcore/thread-chain.md +5 -5
- package/kits/docs/prompt-loading-architecture.md +0 -1
- package/kits/eck_manifest.json +0 -20
- package/kits/eck_manifest.memory.json +0 -12
- package/kits/rules/01-overview.md +11 -0
- package/kits/rules/03-identity.md +6 -3
- package/kits/rules/06-channel.md +1 -1
- package/kits/rules/07-memory-and-thread.md +12 -15
- package/kits/schemas/_meta.json +7 -1
- package/kits/schemas/agent-config.schema.13.json +1 -1
- package/kits/schemas/daemon.schema.7.json +140 -0
- package/kits/skills/ec-group/SKILL.md +4 -4
- package/kits/skills/ec-skill/scripts/cli.mjs +1 -1
- package/kits/skills/ec-skill/scripts/skill.mjs +1 -1
- package/kits/templates/memory-consolidation/forget.md +2 -2
- package/kits/templates/memory-consolidation/generalize.md +4 -4
- package/kits/templates/memory-consolidation/notes.md +11 -11
- package/kits/templates/memory-consolidation/profile.md +3 -3
- package/kits/templates/memory-consolidation/promote.md +3 -3
- package/kits/templates/memory-consolidation/workflow.md +6 -6
- package/kits/templates/message-fragments/handoff-request-to-target.md +1 -1
- package/kits/templates/system-fragments/bootstrap.md +4 -3
- package/kits/templates/system-fragments/channel.md +2 -2
- package/kits/templates/system-fragments/memory.md +16 -9
- package/kits/templates/system-fragments/reply.md +6 -6
- package/kits/templates/system-fragments/session.md +1 -1
- package/package.json +4 -3
- package/dist/core/memory/memory-session-policy.js +0 -41
- package/dist/core/memory/memory-signature.js +0 -257
|
@@ -12,35 +12,35 @@ import fs from 'fs';
|
|
|
12
12
|
import path from 'path';
|
|
13
13
|
import { agentDir, resolveRoot } from '../../paths.js';
|
|
14
14
|
import { serializeConsolidationContext } from './consolidation-context.js';
|
|
15
|
+
import { assertSystemSessionLaunchable } from '../system-session/registry.js';
|
|
16
|
+
import { ensureSessionRuntimeDir } from '../../cli/task-context.js';
|
|
15
17
|
import { formatChannelKey } from '../channel-loader.js';
|
|
16
18
|
import { logger } from '../../utils/logger.js';
|
|
17
|
-
import { appendOpsRecord } from './consolidation-ops-records.js';
|
|
19
|
+
import { appendOpsRecord, feedbackFilePath, readFeedbackText } from './consolidation-ops-records.js';
|
|
20
|
+
import { updateBatchMeta } from './batch-meta.js';
|
|
18
21
|
import { checkConsolidationCandidates, shouldUseMetaIndex } from './consolidation-trigger.js';
|
|
19
22
|
/**
|
|
20
|
-
*
|
|
21
|
-
*
|
|
23
|
+
* 清理旧整理批次,全局保留最新的 10 个(不区分 aid 或 mode)。
|
|
24
|
+
* 启动新整理时调用;selfAid 参数保留供日志上下文用,不参与过滤。
|
|
22
25
|
*/
|
|
23
|
-
function cleanupPreviousConsolidationBatches(
|
|
26
|
+
function cleanupPreviousConsolidationBatches(_selfAid) {
|
|
24
27
|
try {
|
|
25
28
|
const batchRoot = path.join(resolveRoot(), 'memory', 'consolidation-batches');
|
|
26
29
|
if (!fs.existsSync(batchRoot))
|
|
27
30
|
return;
|
|
28
|
-
// 匹配格式:YYYYMMDDTHHMMSS-<selfAid>-*
|
|
29
|
-
const escapedAid = selfAid.replace(/\./g, '\\.');
|
|
30
|
-
const pattern = new RegExp(`^\\d{8}T\\d{6}-${escapedAid}-`);
|
|
31
31
|
const entries = fs.readdirSync(batchRoot, { withFileTypes: true })
|
|
32
|
-
.filter(e => e.isDirectory()
|
|
32
|
+
.filter(e => e.isDirectory())
|
|
33
33
|
.map(e => ({ name: e.name, path: path.join(batchRoot, e.name) }))
|
|
34
|
-
.sort((a, b) => b.name.localeCompare(a.name)); //
|
|
35
|
-
|
|
36
|
-
for (let i =
|
|
34
|
+
.sort((a, b) => b.name.localeCompare(a.name)); // 时间戳前缀,字典序即时间序,最新的在前
|
|
35
|
+
const KEEP = 10;
|
|
36
|
+
for (let i = KEEP; i < entries.length; i++) {
|
|
37
37
|
const dir = entries[i];
|
|
38
38
|
try {
|
|
39
39
|
fs.rmSync(dir.path, { recursive: true, force: true });
|
|
40
|
-
logger.debug(`[Memory]
|
|
40
|
+
logger.debug(`[Memory] 清理旧整理批次: ${dir.name}`);
|
|
41
41
|
}
|
|
42
42
|
catch (e) {
|
|
43
|
-
logger.warn(`[Memory]
|
|
43
|
+
logger.warn(`[Memory] 清理旧整理批次失败 ${dir.name}: ${e?.message}`);
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
}
|
|
@@ -60,6 +60,10 @@ export async function launchConsolidation(channel, params) {
|
|
|
60
60
|
const selfAid = effectiveCtx.selfAid;
|
|
61
61
|
if (!selfAid)
|
|
62
62
|
throw new Error('无法确定当前 agent AID(selfAid 为空)');
|
|
63
|
+
// 系统会话标记:类型 / subtype 必须已注册,runner 必须受支持(Gemini 拒绝)。
|
|
64
|
+
// 放在认领 processing 之前,拒绝时不留副作用。
|
|
65
|
+
const systemSession = { type: 'memory', subtype: mode };
|
|
66
|
+
assertSystemSessionLaunchable(systemSession, effectiveCtx.baseagent ?? 'claude');
|
|
63
67
|
// 清理旧的整理批次(保留最新的一个历史)
|
|
64
68
|
cleanupPreviousConsolidationBatches(selfAid);
|
|
65
69
|
// notes 模式:pending/ → processing/ 快照迁移(在序列化上下文之前执行,声明认领)
|
|
@@ -96,7 +100,6 @@ export async function launchConsolidation(channel, params) {
|
|
|
96
100
|
triggeredBy,
|
|
97
101
|
taskCtx: effectiveCtx,
|
|
98
102
|
ownerAid,
|
|
99
|
-
sessionRuntimeDir: effectiveCtx.sessionRuntimeDir,
|
|
100
103
|
ownerInvolved: params.ownerInvolved,
|
|
101
104
|
forgetAfterDays: params.forgetAfterDays,
|
|
102
105
|
scopeOverride: params.scopeOverride,
|
|
@@ -147,6 +150,8 @@ export async function launchConsolidation(channel, params) {
|
|
|
147
150
|
projectPath,
|
|
148
151
|
prompt,
|
|
149
152
|
metadata: {
|
|
153
|
+
// 系统会话的唯一判据:路径 / 工具 / ec 命令由 system-session/memory.ts 决定,与角色无关
|
|
154
|
+
systemSession,
|
|
150
155
|
sessionType: 'memory',
|
|
151
156
|
memoryMode: mode,
|
|
152
157
|
...(params.scopeOverride ? { memoryScope: params.scopeOverride } : {}),
|
|
@@ -154,6 +159,7 @@ export async function launchConsolidation(channel, params) {
|
|
|
154
159
|
consolidationBatchDir: ctxFile.batchDir,
|
|
155
160
|
...(ctxFile.chatDir ? { consolidationChatDir: ctxFile.chatDir } : {}),
|
|
156
161
|
...(claimedProcessing && memoryDir ? { consolidationMemoryDir: memoryDir } : {}),
|
|
162
|
+
...(ownerAid ? { consolidationOwnerAid: ownerAid } : {}),
|
|
157
163
|
consolidationUsedThreadSnapshots: !!ctxFile.threadFiles?.length,
|
|
158
164
|
...(ctxFile.snapshotThroughTs ? { consolidationSnapshotThroughTs: ctxFile.snapshotThroughTs } : {}),
|
|
159
165
|
...(effectiveCtx.peerKey ? { peerKey: effectiveCtx.peerKey } : {}),
|
|
@@ -380,9 +386,8 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
380
386
|
for (const [scope, memoryDir] of affectedScopes) {
|
|
381
387
|
const consolidatedDir = path.join(memoryDir, 'consolidated');
|
|
382
388
|
const indexPath = path.join(memoryDir, 'MEMORY.md');
|
|
383
|
-
const invalidDir = path.join(memoryDir, 'archive', 'invalid');
|
|
384
389
|
try {
|
|
385
|
-
await rebuildIndex(consolidatedDir, indexPath, scope,
|
|
390
|
+
await rebuildIndex(consolidatedDir, indexPath, scope, maxBytes);
|
|
386
391
|
}
|
|
387
392
|
catch { }
|
|
388
393
|
}
|
|
@@ -464,12 +469,28 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
464
469
|
fs.writeFileSync(historyPath, JSON.stringify({ note: `runner ${baseagent} does not support getSessionMessages` }) + '\n', 'utf-8');
|
|
465
470
|
}
|
|
466
471
|
}
|
|
472
|
+
// feedback 只读一次:先落 batch.md,再清 scratch/,投递复用同一份文本
|
|
473
|
+
const customText = readFeedbackText(batchDir);
|
|
474
|
+
// batch.md 收尾更新:写入最终状态和 feedback 正文
|
|
475
|
+
try {
|
|
476
|
+
updateBatchMeta(batchDir, {
|
|
477
|
+
status: success ? 'completed' : 'failed',
|
|
478
|
+
finishedAt: new Date().toISOString(),
|
|
479
|
+
success,
|
|
480
|
+
...(session.agentSessionId ? { agentSessionId: session.agentSessionId } : {}),
|
|
481
|
+
feedbackText: customText,
|
|
482
|
+
});
|
|
483
|
+
}
|
|
484
|
+
catch { /* 失败静默 */ }
|
|
485
|
+
// scratch/ 是会话期临时工作区,feedback 已落 batch.md,不分成败与触发方式一律清掉
|
|
486
|
+
try {
|
|
487
|
+
fs.rmSync(path.dirname(feedbackFilePath(batchDir)), { recursive: true, force: true });
|
|
488
|
+
}
|
|
489
|
+
catch { /* 静默,不影响投递 */ }
|
|
467
490
|
const feedbackOrigin = meta.feedbackOrigin;
|
|
468
491
|
const triggeredBy = meta.consolidationTriggeredBy;
|
|
469
492
|
if (feedbackOrigin && triggeredBy && triggeredBy !== 'periodic-trigger' && session.selfAID) {
|
|
470
493
|
try {
|
|
471
|
-
const { readFeedbackText } = await import('./consolidation-ops-records.js');
|
|
472
|
-
const customText = readFeedbackText(batchDir);
|
|
473
494
|
const text = customText ?? renderDefaultConsolidationFeedback({
|
|
474
495
|
mode: meta.memoryMode,
|
|
475
496
|
triggeredBy,
|
|
@@ -490,6 +511,21 @@ export function createConsolidationSessionFinalizer({ sessionManager, getAgent,
|
|
|
490
511
|
finally {
|
|
491
512
|
if (releaseAid && sessionId)
|
|
492
513
|
releaseSlot(releaseAid, sessionId);
|
|
514
|
+
// runtime 目录清理:immediate 策略下,finalizer 是最后一个要读它的消费者。
|
|
515
|
+
// 用 sessionId 无状态重算路径,不依赖 spec 传递——保证即使 spec 不传 runtimeDir
|
|
516
|
+
// 字段,目录也能在会话结束后被清掉。
|
|
517
|
+
if (sessionId) {
|
|
518
|
+
const runtimeDir = ensureSessionRuntimeDir(sessionId);
|
|
519
|
+
if (runtimeDir) {
|
|
520
|
+
try {
|
|
521
|
+
fs.rmSync(runtimeDir, { recursive: true, force: true });
|
|
522
|
+
logger.debug(`[Memory] 清理 runtime 目录: ${runtimeDir}`);
|
|
523
|
+
}
|
|
524
|
+
catch (cleanupError) {
|
|
525
|
+
logger.warn(`[Memory] runtime 目录清理失败(session=${sessionId}): ${cleanupError?.message}`);
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
}
|
|
493
529
|
}
|
|
494
530
|
};
|
|
495
531
|
}
|
|
@@ -514,19 +550,48 @@ export function createConsolidationFeedbackSender(getChannel) {
|
|
|
514
550
|
replyContext.threadId = threadId;
|
|
515
551
|
if (chatType)
|
|
516
552
|
replyContext.delivery = { chatType };
|
|
517
|
-
|
|
518
|
-
|
|
553
|
+
// 对比期:同一份反馈 notice、text 各发一条,观察两种模式在对端的差异,
|
|
554
|
+
// 跑通后删掉 text 那条。两条 taskId 后缀不同,避免共用 operationId 被出站去重吞掉一条;
|
|
555
|
+
// 各自 try,一条失败不连累另一条。
|
|
556
|
+
const stamp = Date.now();
|
|
557
|
+
const envelopeFor = (suffix) => buildEnvelope({
|
|
558
|
+
taskId: `memory-feedback-${stamp}-${suffix}`,
|
|
519
559
|
channel: binding.adapter.channelKey,
|
|
520
560
|
channelId,
|
|
521
561
|
agentName: binding.agentName,
|
|
522
562
|
replyContext: Object.keys(replyContext).length > 0 ? replyContext : undefined,
|
|
523
|
-
}), {
|
|
524
|
-
kind: 'result.text',
|
|
525
|
-
text,
|
|
526
|
-
isFinal: true,
|
|
527
|
-
format: 'markdown',
|
|
528
563
|
});
|
|
529
|
-
|
|
564
|
+
const attempts = [
|
|
565
|
+
{
|
|
566
|
+
mode: 'notice',
|
|
567
|
+
send: () => binding.adapter.send(envelopeFor('notice'), {
|
|
568
|
+
kind: 'system.notice',
|
|
569
|
+
text,
|
|
570
|
+
subtype: 'memory.consolidation.result',
|
|
571
|
+
}),
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
mode: 'text',
|
|
575
|
+
send: () => binding.adapter.send(envelopeFor('text'), {
|
|
576
|
+
kind: 'result.text',
|
|
577
|
+
text,
|
|
578
|
+
isFinal: true,
|
|
579
|
+
format: 'markdown',
|
|
580
|
+
}),
|
|
581
|
+
},
|
|
582
|
+
];
|
|
583
|
+
const failures = [];
|
|
584
|
+
for (const { mode, send } of attempts) {
|
|
585
|
+
try {
|
|
586
|
+
await send();
|
|
587
|
+
logger.info(`[Memory] 整理反馈已投递(mode=${mode},aid=${selfAid},channelId=${channelId})`);
|
|
588
|
+
}
|
|
589
|
+
catch (error) {
|
|
590
|
+
failures.push(`${mode}: ${error?.message}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
if (failures.length > 0)
|
|
594
|
+
throw new Error(failures.join('; '));
|
|
530
595
|
};
|
|
531
596
|
}
|
|
532
597
|
export function registerConsolidationSessionEvents({ eventBus, permissionGateway, finalize, }) {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* index-manager.ts — MEMORY.md 索引管理(v3)
|
|
3
3
|
*
|
|
4
4
|
* 负责:
|
|
5
|
-
* 1. 重建索引:扫描 consolidated
|
|
5
|
+
* 1. 重建索引:扫描 consolidated/,逐份过台账校验,按 tags 筛选,截断到 indexMaxBytes
|
|
6
6
|
* 2. 追加条目:单条写入时的快速路径(写入后仍以 rebuild 为准)
|
|
7
7
|
*
|
|
8
8
|
* 排序与超期判断一律读**文件 mtime**;frontmatter 的 updatedAt 只作可读信息。
|
|
@@ -10,15 +10,16 @@
|
|
|
10
10
|
* 索引行格式:`- 20261005 [slug] — 描述 [tag1,tag2]`
|
|
11
11
|
* slug 即文件名(consolidated/<slug>.md),不再重复写路径。
|
|
12
12
|
*
|
|
13
|
-
*
|
|
14
|
-
* -
|
|
15
|
-
* -
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* 完整性校验走 file-integrity 台账(docs/file-security/):
|
|
14
|
+
* - 没被动过 → 进索引
|
|
15
|
+
* - 被带外修改 → 恢复到最后登记的版本,再进索引
|
|
16
|
+
* - 被改动且无法恢复 → 跳过,不进索引,也不移动文件
|
|
17
|
+
* MEMORY.md 本身每次写入都会登记:它整份注入上下文,被改了同样要能恢复。
|
|
18
18
|
*/
|
|
19
19
|
import fs from 'fs';
|
|
20
20
|
import path from 'path';
|
|
21
21
|
import { getMemoryArray, getMemoryString, parseMemoryDocument } from './memory-document.js';
|
|
22
|
+
import { registerFile, verifyFile } from '../file-integrity/store.js';
|
|
22
23
|
/** 索引容量上限(字节)。实际取值由调用方从 memory.indexMaxBytes 传入。 */
|
|
23
24
|
export const DEFAULT_INDEX_MAX_BYTES = 5 * 1024;
|
|
24
25
|
/** Fast metadata index for consolidation candidate scans. */
|
|
@@ -246,20 +247,19 @@ function writeIndexFile(indexPath, content) {
|
|
|
246
247
|
const tmp = indexPath + '.tmp';
|
|
247
248
|
fs.writeFileSync(tmp, content, 'utf-8');
|
|
248
249
|
fs.renameSync(tmp, indexPath);
|
|
250
|
+
registerFile(indexPath);
|
|
249
251
|
}
|
|
250
252
|
// ── 重建索引 ────────────────────────────────────────────────────────────────
|
|
251
253
|
/**
|
|
252
254
|
* 从 consolidated/ 重建 MEMORY.md。
|
|
253
255
|
*
|
|
254
|
-
*
|
|
255
|
-
*
|
|
256
|
-
* 开启后才需要的,暂未实现。排序读文件 mtime,按 tags 分级,超 indexMaxBytes
|
|
257
|
-
* 从末尾截断(pinned 标签保留)。
|
|
256
|
+
* 每份文件先过台账校验(被带外修改的先恢复),被改动且无法恢复的跳过,不移动文件。
|
|
257
|
+
* 排序读文件 mtime,按 tags 分级,超 indexMaxBytes 从末尾截断(pinned 标签保留)。
|
|
258
258
|
*/
|
|
259
|
-
export async function rebuildIndex(consolidatedDir, indexPath, scope,
|
|
259
|
+
export async function rebuildIndex(consolidatedDir, indexPath, scope, indexMaxBytes = DEFAULT_INDEX_MAX_BYTES) {
|
|
260
260
|
if (!fs.existsSync(consolidatedDir)) {
|
|
261
261
|
writeIndexFile(indexPath, serializeIndex(scope, []));
|
|
262
|
-
return { indexed: 0,
|
|
262
|
+
return { indexed: 0, verifiedFiles: [] };
|
|
263
263
|
}
|
|
264
264
|
const files = fs.readdirSync(consolidatedDir).filter(f => f.endsWith('.md')).sort();
|
|
265
265
|
const entries = [];
|
|
@@ -269,26 +269,17 @@ export async function rebuildIndex(consolidatedDir, indexPath, scope, invalidDir
|
|
|
269
269
|
let content;
|
|
270
270
|
let mtimeMs;
|
|
271
271
|
try {
|
|
272
|
-
|
|
272
|
+
const checked = verifyFile(filePath, { restoreOnFail: true });
|
|
273
|
+
if (!checked.bytes)
|
|
274
|
+
continue; // missing / 无法恢复:不进索引
|
|
275
|
+
content = checked.bytes.toString('utf-8');
|
|
276
|
+
// 恢复会把 mtime 还原成登记时的值,所以在校验之后再取
|
|
273
277
|
mtimeMs = fs.statSync(filePath).mtimeMs;
|
|
274
278
|
}
|
|
275
279
|
catch {
|
|
276
280
|
continue;
|
|
277
281
|
}
|
|
278
|
-
|
|
279
|
-
const verifyResult = await verifyFn(content);
|
|
280
|
-
if (verifyResult.status === 'verified') {
|
|
281
|
-
verifiedFiles.push(file);
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
// 验签失败:跳过,不进索引,不移动文件(门控开启后的移动逻辑暂未实现)
|
|
285
|
-
continue;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
else {
|
|
289
|
-
// 未开启验签:所有文件视为通过
|
|
290
|
-
verifiedFiles.push(file);
|
|
291
|
-
}
|
|
282
|
+
verifiedFiles.push(file);
|
|
292
283
|
const document = parseMemoryDocument(content);
|
|
293
284
|
const description = getMemoryString(document, 'description') ?? file.replace(/\.md$/, '');
|
|
294
285
|
const tags = getMemoryArray(document, 'tags');
|
|
@@ -304,5 +295,5 @@ export async function rebuildIndex(consolidatedDir, indexPath, scope, invalidDir
|
|
|
304
295
|
sortEntries(entries);
|
|
305
296
|
const kept = truncateToLimit(scope, entries, indexMaxBytes);
|
|
306
297
|
writeIndexFile(indexPath, serializeIndex(scope, kept));
|
|
307
|
-
return { indexed: kept.length,
|
|
298
|
+
return { indexed: kept.length, verifiedFiles };
|
|
308
299
|
}
|
|
@@ -12,7 +12,8 @@ import path from 'path';
|
|
|
12
12
|
import { resolveScopeDirs, archiveMemory, dropMemoryNote, listMemoryNotes } from './memory-writer.js';
|
|
13
13
|
import { rebuildIndex, DEFAULT_INDEX_MAX_BYTES } from './index-manager.js';
|
|
14
14
|
import { refreshMemoryMeta, readMemoryMeta } from './index-manager.js';
|
|
15
|
-
import {
|
|
15
|
+
import { stripLegacySignature } from './memory-document.js';
|
|
16
|
+
import { moveRecord, verifyFile } from '../file-integrity/store.js';
|
|
16
17
|
import { readFmArray, readFmValue, readUpdatedAtMs, reviewWindowDays, DEFAULT_FORGET_AFTER_DAYS } from './staleness.js';
|
|
17
18
|
export const MEMORY_SCOPES = ['relation', 'agent', 'owner'];
|
|
18
19
|
const SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,63}$/;
|
|
@@ -45,10 +46,6 @@ function fmtDate(ms) {
|
|
|
45
46
|
return '-';
|
|
46
47
|
return new Date(ms).toISOString().slice(0, 16).replace('T', ' ');
|
|
47
48
|
}
|
|
48
|
-
/** 去掉签名块,只留正文(含 frontmatter) */
|
|
49
|
-
function stripSignature(content) {
|
|
50
|
-
return parseSignatureBlock(content).payload.trimEnd();
|
|
51
|
-
}
|
|
52
49
|
// ── 查看 ────────────────────────────────────────────────────────────────────
|
|
53
50
|
/** 概览:各作用域条数、pending 数、索引大小、最近更新时间。 */
|
|
54
51
|
export function memoryOverview(target, scopes) {
|
|
@@ -120,7 +117,6 @@ export function listMemoryFiles(target, scope, archived = false, now = Date.now(
|
|
|
120
117
|
tags,
|
|
121
118
|
mtimeMs,
|
|
122
119
|
updatedAtMs,
|
|
123
|
-
signed: !!parseSignatureBlock(content).fields,
|
|
124
120
|
reviewInDays: Math.floor((updatedAtMs + windowMs - now) / DAY_MS),
|
|
125
121
|
});
|
|
126
122
|
}
|
|
@@ -135,7 +131,7 @@ export function formatMemoryFiles(scope, files, archived = false) {
|
|
|
135
131
|
for (const f of files) {
|
|
136
132
|
const tags = f.tags.length ? ` [${f.tags.join(',')}]` : '';
|
|
137
133
|
const review = archived ? '' : f.reviewInDays < 0 ? ' ⏳待审查' : ` ${f.reviewInDays}天后审查`;
|
|
138
|
-
lines.push(`- ${f.slug}${tags}${
|
|
134
|
+
lines.push(`- ${f.slug}${tags}${review}`);
|
|
139
135
|
if (f.description)
|
|
140
136
|
lines.push(` ${f.description}`);
|
|
141
137
|
lines.push(` 更新于 ${fmtDate(f.updatedAtMs)}`);
|
|
@@ -158,16 +154,21 @@ export async function showMemoryFile(target, scope, slug, archived = false) {
|
|
|
158
154
|
const fp = path.join(dir, `${slug}.md`);
|
|
159
155
|
if (!fs.existsSync(fp))
|
|
160
156
|
throw new Error(`找不到记忆: ${scope}/${archived ? 'archive/' : ''}${slug}`);
|
|
161
|
-
const content = fs.readFileSync(fp, 'utf-8');
|
|
162
157
|
let verify;
|
|
158
|
+
let content;
|
|
163
159
|
try {
|
|
164
|
-
const r =
|
|
165
|
-
verify = r.status === 'verified' ? '✅
|
|
160
|
+
const r = verifyFile(fp, { restoreOnFail: true });
|
|
161
|
+
verify = r.status === 'verified' ? '✅ 校验通过'
|
|
162
|
+
: r.status === 'restored' ? '♻️ 检测到带外修改,已恢复到最后登记版本'
|
|
163
|
+
: '❌ 被改动且无法恢复';
|
|
164
|
+
// tampered 时没有校验过的字节,显示磁盘现状供排查
|
|
165
|
+
content = r.bytes ? r.bytes.toString('utf-8') : fs.readFileSync(fp, 'utf-8');
|
|
166
166
|
}
|
|
167
167
|
catch (e) {
|
|
168
|
-
verify = `⚠️
|
|
168
|
+
verify = `⚠️ 无法校验(${e?.message || String(e)})`;
|
|
169
|
+
content = fs.readFileSync(fp, 'utf-8');
|
|
169
170
|
}
|
|
170
|
-
let body =
|
|
171
|
+
let body = stripLegacySignature(content).trimEnd();
|
|
171
172
|
if (body.length > MAX_SHOW_CHARS)
|
|
172
173
|
body = `${body.slice(0, MAX_SHOW_CHARS)}\n…(已截断)`;
|
|
173
174
|
return `📄 ${scope}/${archived ? 'archive/' : ''}${slug} ${verify}\n${body}`;
|
|
@@ -201,8 +202,7 @@ export function showPendingNote(target, slug) {
|
|
|
201
202
|
/** 重建某作用域的 MEMORY.md 并刷新 _meta.json。 */
|
|
202
203
|
export async function reindexScope(target, scope) {
|
|
203
204
|
const dirs = scopeDirs(target, scope);
|
|
204
|
-
const r = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope,
|
|
205
|
-
target.memoryConfig?.indexMaxBytes ?? DEFAULT_INDEX_MAX_BYTES);
|
|
205
|
+
const r = await rebuildIndex(dirs.consolidatedDir, path.join(dirs.memoryDir, 'MEMORY.md'), scope, target.memoryConfig?.indexMaxBytes ?? DEFAULT_INDEX_MAX_BYTES);
|
|
206
206
|
refreshMemoryMeta(dirs.memoryDir);
|
|
207
207
|
return { indexed: r.indexed };
|
|
208
208
|
}
|
|
@@ -228,7 +228,12 @@ export async function restoreMemoryFile(target, scope, slug) {
|
|
|
228
228
|
throw new Error(`找不到已归档记忆: ${scope}/${slug}`);
|
|
229
229
|
if (fs.existsSync(dest))
|
|
230
230
|
throw new Error(`正式记忆中已存在同名文件: ${scope}/${slug},未恢复`);
|
|
231
|
+
// 归档期间被改过的话先恢复;恢复不了就拒绝,不把篡改内容放回正式记忆
|
|
232
|
+
const checked = verifyFile(src, { restoreOnFail: true });
|
|
233
|
+
if (!checked.bytes)
|
|
234
|
+
throw new Error(`MEMORY_TAMPERED: 已归档记忆被改动且无法恢复,未恢复: ${scope}/${slug}`);
|
|
231
235
|
fs.mkdirSync(dirs.consolidatedDir, { recursive: true });
|
|
232
236
|
fs.renameSync(src, dest);
|
|
237
|
+
moveRecord(src, dest);
|
|
233
238
|
await reindexScope(target, scope);
|
|
234
239
|
}
|
|
@@ -2,10 +2,58 @@
|
|
|
2
2
|
* Shared representation for memory Markdown documents.
|
|
3
3
|
*
|
|
4
4
|
* Memory files have two mutable blocks: YAML-like frontmatter and Markdown
|
|
5
|
-
* body.
|
|
6
|
-
*
|
|
5
|
+
* body. 新记忆不再签名(完整性改由 file-integrity 台账保证);磁盘上的老记忆
|
|
6
|
+
* 可能还带尾部 AUN 签名块,解析时剥掉,重写时不再写回。
|
|
7
7
|
*/
|
|
8
|
-
|
|
8
|
+
// ── 老签名块兼容剥离 ──────────────────────────────────────────────────────────
|
|
9
|
+
// 规则与原 memory-signature.ts 的 parseSignatureBlock 一致:只有格式完全合法的
|
|
10
|
+
// 签名块才剥;格式不对的标记可能就是正文,原样保留每个字节。
|
|
11
|
+
const LEGACY_SIGNATURE_MARKER = '<!-- AUN-SIGNATURE';
|
|
12
|
+
const LEGACY_SIGNATURE_RE = /^<!-- AUN-SIGNATURE(?:\r\n|\n|\r)(?<body>[\s\S]*?)(?:\r\n|\n|\r)-->\s*$/;
|
|
13
|
+
const LEGACY_REQUIRED_FIELDS = ['cert_fingerprint', 'timestamp', 'signature'];
|
|
14
|
+
const LEGACY_ALLOWED_FIELDS = [...LEGACY_REQUIRED_FIELDS, 'public_key_fingerprint'];
|
|
15
|
+
const SHA256_FP_RE = /^sha256:[0-9a-f]{64}$/i;
|
|
16
|
+
function isStrictBase64(value) {
|
|
17
|
+
if (!value || value.length % 4 !== 0 || !/^[A-Za-z0-9+/]+={0,2}$/.test(value))
|
|
18
|
+
return false;
|
|
19
|
+
return Buffer.from(value, 'base64').toString('base64') === value;
|
|
20
|
+
}
|
|
21
|
+
function isValidLegacySignatureBlock(tail) {
|
|
22
|
+
const match = tail.match(LEGACY_SIGNATURE_RE);
|
|
23
|
+
if (!match)
|
|
24
|
+
return false;
|
|
25
|
+
const fields = {};
|
|
26
|
+
for (const rawLine of (match.groups?.body ?? '').split(/\r\n|\n|\r/)) {
|
|
27
|
+
const line = rawLine.trim();
|
|
28
|
+
if (!line)
|
|
29
|
+
continue;
|
|
30
|
+
const colon = line.indexOf(':');
|
|
31
|
+
if (colon < 0)
|
|
32
|
+
return false;
|
|
33
|
+
const key = line.slice(0, colon).trim().toLowerCase();
|
|
34
|
+
if (Object.prototype.hasOwnProperty.call(fields, key))
|
|
35
|
+
return false;
|
|
36
|
+
fields[key] = line.slice(colon + 1).trim();
|
|
37
|
+
}
|
|
38
|
+
if (LEGACY_REQUIRED_FIELDS.some(k => !fields[k]))
|
|
39
|
+
return false;
|
|
40
|
+
if (Object.keys(fields).some(k => !LEGACY_ALLOWED_FIELDS.includes(k)))
|
|
41
|
+
return false;
|
|
42
|
+
if (!SHA256_FP_RE.test(fields.cert_fingerprint))
|
|
43
|
+
return false;
|
|
44
|
+
if (fields.public_key_fingerprint && !SHA256_FP_RE.test(fields.public_key_fingerprint))
|
|
45
|
+
return false;
|
|
46
|
+
return /^\d+$/.test(fields.timestamp) && isStrictBase64(fields.signature);
|
|
47
|
+
}
|
|
48
|
+
/** 去掉老记忆尾部合法的 AUN 签名块;没有或不合法时原样返回。 */
|
|
49
|
+
export function stripLegacySignature(content) {
|
|
50
|
+
const idx = content.lastIndexOf(LEGACY_SIGNATURE_MARKER);
|
|
51
|
+
if (idx < 0 || content.indexOf(LEGACY_SIGNATURE_MARKER) !== idx)
|
|
52
|
+
return content;
|
|
53
|
+
if (idx > 0 && content[idx - 1] !== '\n' && content[idx - 1] !== '\r')
|
|
54
|
+
return content;
|
|
55
|
+
return isValidLegacySignatureBlock(content.slice(idx)) ? content.slice(0, idx) : content;
|
|
56
|
+
}
|
|
9
57
|
function parseArray(raw) {
|
|
10
58
|
const value = raw.match(/^\[([^\]]*)\]$/)?.[1] ?? raw;
|
|
11
59
|
return value
|
|
@@ -29,13 +77,9 @@ function splitDocument(content) {
|
|
|
29
77
|
hasFrontmatter: true,
|
|
30
78
|
};
|
|
31
79
|
}
|
|
32
|
-
/** Parse frontmatter and body;
|
|
80
|
+
/** Parse frontmatter and body; a legacy AUN signature block is ignored. */
|
|
33
81
|
export function parseMemoryDocument(content) {
|
|
34
|
-
const
|
|
35
|
-
// A malformed marker may be ordinary body text. Only strip a signature when
|
|
36
|
-
// the signature parser accepted the block; otherwise preserve every byte.
|
|
37
|
-
const unsigned = signature.fields ? signature.payload : content;
|
|
38
|
-
const parts = splitDocument(unsigned);
|
|
82
|
+
const parts = splitDocument(stripLegacySignature(content));
|
|
39
83
|
const fields = {};
|
|
40
84
|
const fieldOrder = [];
|
|
41
85
|
const extraFrontmatterLines = [];
|
|
@@ -4,21 +4,22 @@
|
|
|
4
4
|
* 只有整理会话(sessionType=memory)能调用这三个操作,由 ipc.ts 做会话类型与
|
|
5
5
|
* 模式↔作用域的双重校验后委托到这里。本模块只负责文件层面的动作:
|
|
6
6
|
*
|
|
7
|
-
* - createMemory 新建正式记忆:写 consolidated
|
|
8
|
-
* - updateMemory 改标签或续期(--touch
|
|
7
|
+
* - createMemory 新建正式记忆:写 consolidated/、归档 --replaces 的旧文件
|
|
8
|
+
* - updateMemory 改标签或续期(--touch)
|
|
9
9
|
* - archiveMemory 归档(唯一的下线方式,没有删除)
|
|
10
10
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
11
|
+
* 正式记忆的完整性由 file-integrity 台账保证(docs/file-security/):
|
|
12
|
+
* 落盘后 registerFile,rename 后 moveRecord,读改写之前先 verifyFile。
|
|
13
|
+
* pending note 是草稿,不进台账。
|
|
13
14
|
*/
|
|
14
15
|
import fs from 'fs';
|
|
15
16
|
import path from 'path';
|
|
16
17
|
import { resolveRoot } from '../../paths.js';
|
|
17
|
-
import {
|
|
18
|
+
import { moveRecord, registerFile, verifyFile } from '../file-integrity/store.js';
|
|
18
19
|
import { refreshMemoryMeta } from './index-manager.js';
|
|
19
20
|
import { getMemoryArray, getMemoryString, parseMemoryDocument, serializeMemoryDocument, setMemoryField, } from './memory-document.js';
|
|
20
21
|
const SLUG_RE = /^[a-zA-Z0-9][a-zA-Z0-9-]{0,63}$/;
|
|
21
|
-
/**
|
|
22
|
+
/** 一条记忆应是一句话能说清的结论,10KB 足够;超过通常意味着该拆成多条。 */
|
|
22
23
|
const MAX_CONTENT_BYTES = 10 * 1024;
|
|
23
24
|
/** 解析某个作用域的记忆目录。 */
|
|
24
25
|
export function resolveScopeDirs(scope, taskCtx, ownerAid, peerKeyOverride) {
|
|
@@ -76,8 +77,18 @@ function assertSlug(slug) {
|
|
|
76
77
|
function readMemory(content) {
|
|
77
78
|
return parseMemoryDocument(content);
|
|
78
79
|
}
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
/**
|
|
81
|
+
* 读 → 改 → 重写之前先过台账:被带外改过就先恢复,用校验过的内容做修改基础。
|
|
82
|
+
* 直接读盘的话,篡改内容改一下就会被重新登记成新基线。
|
|
83
|
+
*/
|
|
84
|
+
function readVerifiedMemory(filePath) {
|
|
85
|
+
const r = verifyFile(filePath, { restoreOnFail: true });
|
|
86
|
+
if (r.status === 'missing')
|
|
87
|
+
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
88
|
+
if (!r.bytes) {
|
|
89
|
+
throw new Error(`MEMORY_TAMPERED: 记忆文件被改动且无法恢复,已拒绝操作: ${filePath}`);
|
|
90
|
+
}
|
|
91
|
+
return r.bytes.toString('utf-8');
|
|
81
92
|
}
|
|
82
93
|
export async function createMemory(req) {
|
|
83
94
|
assertSlug(req.slug);
|
|
@@ -134,10 +145,10 @@ export async function createMemory(req) {
|
|
|
134
145
|
if (replacePaths.length > 0)
|
|
135
146
|
setMemoryField(document, 'replaces', replacePaths.map(p => path.relative(dirs.memoryDir, p)));
|
|
136
147
|
document.body = req.content.trimEnd();
|
|
137
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
138
148
|
const tmp = targetPath + '.tmp';
|
|
139
|
-
fs.writeFileSync(tmp,
|
|
149
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
140
150
|
fs.renameSync(tmp, targetPath);
|
|
151
|
+
registerFile(targetPath, req.taskCtx.selfAid);
|
|
141
152
|
// 归档被替换的旧文件(在写入成功之后做,失败不影响新文件)
|
|
142
153
|
const archived = [];
|
|
143
154
|
for (const p of replacePaths) {
|
|
@@ -150,6 +161,7 @@ export async function createMemory(req) {
|
|
|
150
161
|
try {
|
|
151
162
|
fs.renameSync(p, dest);
|
|
152
163
|
archived.push(dest);
|
|
164
|
+
moveRecord(p, dest);
|
|
153
165
|
}
|
|
154
166
|
catch { /* 归档失败不影响主流程,由下次整理清理 */ }
|
|
155
167
|
}
|
|
@@ -206,7 +218,8 @@ export async function demoteMemory(req) {
|
|
|
206
218
|
const srcPath = path.join(srcDirs.consolidatedDir, `${req.slug}.md`);
|
|
207
219
|
if (!fs.existsSync(srcPath))
|
|
208
220
|
throw new Error(`找不到记忆文件: ${srcPath}`);
|
|
209
|
-
|
|
221
|
+
// 降级目标由 frontmatter 决定,必须读校验过的内容,否则改一下来源字段就能把记忆搬去别处
|
|
222
|
+
const content = readVerifiedMemory(srcPath);
|
|
210
223
|
const fromRelPath = getMemoryString(readMemory(content), req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom');
|
|
211
224
|
if (!fromRelPath) {
|
|
212
225
|
throw new Error(`DEMOTE_NO_SOURCE: 记忆文件缺少来源字段(${req.scope === 'agent' ? 'generalizedFrom' : 'promotedFrom'}),` +
|
|
@@ -229,12 +242,14 @@ export async function demoteMemory(req) {
|
|
|
229
242
|
const archiveDest = archivePath(srcDirs.memoryDir, srcPath);
|
|
230
243
|
fs.mkdirSync(path.dirname(archiveDest), { recursive: true });
|
|
231
244
|
fs.renameSync(srcPath, archiveDest);
|
|
245
|
+
moveRecord(srcPath, archiveDest);
|
|
232
246
|
finalDestPath = archiveDest;
|
|
233
247
|
conflictArchived = true;
|
|
234
248
|
}
|
|
235
249
|
else {
|
|
236
250
|
fs.mkdirSync(path.dirname(destPath), { recursive: true });
|
|
237
251
|
fs.renameSync(srcPath, destPath);
|
|
252
|
+
moveRecord(srcPath, destPath);
|
|
238
253
|
}
|
|
239
254
|
// 跨作用域搬运,源与目标两个目录都要刷新元数据
|
|
240
255
|
refreshMemoryMeta(srcDirs.memoryDir);
|
|
@@ -252,18 +267,17 @@ export async function updateMemory(req) {
|
|
|
252
267
|
const filePath = resolveConsolidatedPath(dirs, req.target);
|
|
253
268
|
if (!fs.existsSync(filePath))
|
|
254
269
|
throw new Error(`找不到记忆文件: ${filePath}`);
|
|
255
|
-
const
|
|
256
|
-
const document = readMemory(raw);
|
|
270
|
+
const document = readMemory(readVerifiedMemory(filePath));
|
|
257
271
|
const prevTags = getMemoryArray(document, 'tags');
|
|
258
272
|
if (req.tags !== undefined)
|
|
259
273
|
setMemoryField(document, 'tags', req.tags.length > 0 ? req.tags : undefined);
|
|
260
274
|
// 刷新 updatedAt(--touch 的续期语义就是刷新这里 + 文件 mtime)
|
|
261
275
|
const now = new Date().toISOString();
|
|
262
276
|
setMemoryField(document, 'updatedAt', now);
|
|
263
|
-
const signed = await writeSignedMemory(document, req.taskCtx.selfAid);
|
|
264
277
|
const tmp = filePath + '.tmp';
|
|
265
|
-
fs.writeFileSync(tmp,
|
|
278
|
+
fs.writeFileSync(tmp, serializeMemoryDocument(document), 'utf-8');
|
|
266
279
|
fs.renameSync(tmp, filePath);
|
|
280
|
+
registerFile(filePath, req.taskCtx.selfAid);
|
|
267
281
|
refreshMemoryMeta(dirs.memoryDir);
|
|
268
282
|
return { ok: true, filePath, prevTags, scopeDirs: dirs };
|
|
269
283
|
}
|
|
@@ -278,6 +292,7 @@ export async function archiveMemory(req) {
|
|
|
278
292
|
const dest = archivePath(dirs.memoryDir, p);
|
|
279
293
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
280
294
|
fs.renameSync(p, dest);
|
|
295
|
+
moveRecord(p, dest);
|
|
281
296
|
archived.push(dest);
|
|
282
297
|
}
|
|
283
298
|
refreshMemoryMeta(dirs.memoryDir);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { logger } from '../../utils/logger.js';
|
|
2
2
|
import { summarizeToolInput, summarizeToolInputForDisplay, toolInputForDisplay } from '../../utils/tool-summary.js';
|
|
3
|
-
import { getErrorMessage, isContextTooLongText } from '../../utils/error-utils.js';
|
|
3
|
+
import { getErrorMessage, getToolResultErrorText, isContextTooLongText } from '../../utils/error-utils.js';
|
|
4
4
|
import { tagErrorTextWithEvolcoreVersion } from '../../utils/evolcore-version.js';
|
|
5
5
|
import { DEFAULT_FLUSH_DELAY_MS } from '../../types.js';
|
|
6
6
|
import { classifyAunToolErrorCode, normalizeToolErrorClassification } from '../permission/tool-error-code.js';
|
|
@@ -275,7 +275,13 @@ export class IMRenderer {
|
|
|
275
275
|
name,
|
|
276
276
|
ok,
|
|
277
277
|
...(result !== undefined && { result }),
|
|
278
|
-
...(
|
|
278
|
+
...(!ok
|
|
279
|
+
? { error: getToolResultErrorText({ name, result, error, exitCode: classification?.exitCode }) }
|
|
280
|
+
: error !== undefined ? { error } : {}),
|
|
281
|
+
...(typeof classification?.exitCode === 'number' && Number.isInteger(classification.exitCode)
|
|
282
|
+
? { exit_code: classification.exitCode }
|
|
283
|
+
: {}),
|
|
284
|
+
...(classification?.outputKind ? { output_kind: classification.outputKind } : {}),
|
|
279
285
|
...(!ok && {
|
|
280
286
|
error_code: classifyAunToolErrorCode({
|
|
281
287
|
...(classification ?? {}),
|
|
@@ -904,7 +910,13 @@ export class IMRenderer {
|
|
|
904
910
|
item.arguments = toolInputForDisplay(event.name, event.input);
|
|
905
911
|
return item;
|
|
906
912
|
}
|
|
907
|
-
case 'tool_result':
|
|
913
|
+
case 'tool_result': {
|
|
914
|
+
const executionFields = {
|
|
915
|
+
...(typeof event.exitCode === 'number' && Number.isInteger(event.exitCode)
|
|
916
|
+
? { exit_code: event.exitCode }
|
|
917
|
+
: {}),
|
|
918
|
+
...(event.outputKind ? { output_kind: event.outputKind } : {}),
|
|
919
|
+
};
|
|
908
920
|
if (event.isError) {
|
|
909
921
|
const classification = normalizeToolErrorClassification(event);
|
|
910
922
|
return {
|
|
@@ -912,9 +924,11 @@ export class IMRenderer {
|
|
|
912
924
|
call_id: event.callId || this.synthCallId(),
|
|
913
925
|
name: event.name,
|
|
914
926
|
ok: false,
|
|
927
|
+
...executionFields,
|
|
928
|
+
...(event.result !== undefined ? { result: event.result } : {}),
|
|
915
929
|
error_code: classifyAunToolErrorCode({ ...event, ...classification }),
|
|
916
930
|
...(classification.errorDetailCode ? { detail_code: classification.errorDetailCode } : {}),
|
|
917
|
-
error:
|
|
931
|
+
error: getToolResultErrorText(event),
|
|
918
932
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
919
933
|
};
|
|
920
934
|
}
|
|
@@ -925,11 +939,13 @@ export class IMRenderer {
|
|
|
925
939
|
call_id: event.callId || this.synthCallId(),
|
|
926
940
|
name: event.name,
|
|
927
941
|
ok: true,
|
|
942
|
+
...executionFields,
|
|
928
943
|
result: event.result,
|
|
929
944
|
text: resultText,
|
|
930
945
|
...(event.durationMs !== undefined && { duration_ms: event.durationMs }),
|
|
931
946
|
};
|
|
932
947
|
}
|
|
948
|
+
}
|
|
933
949
|
case 'compact':
|
|
934
950
|
return {
|
|
935
951
|
kind: 'notice',
|