@nowcrew/daemon 0.6.99-beta.3 → 0.6.99
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/README.md +9 -4
- package/dist/cloud-report-upload.js +10 -1
- package/dist/computer-main.js +0 -0
- package/dist/computer-service.js +30 -5
- package/dist/execution-journal.js +1 -3
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +1 -4
- package/dist/local-execution-prompt.js +3 -8
- package/dist/local-executor.js +39 -64
- package/dist/local-memory/index-budget.js +3 -9
- package/dist/local-memory/inventory-cli.js +8 -52
- package/dist/local-memory/inventory-source.js +8 -15
- package/dist/local-memory/pending-query.js +2 -5
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +2 -16
- package/dist/local-memory/prompt.js +5 -21
- package/dist/local-memory/read-cli.js +76 -98
- package/dist/local-memory/rollout.js +11 -79
- package/dist/local-memory/runtime.js +13 -181
- package/dist/local-memory/write.js +21 -189
- package/dist/local-memory-diagnostics.js +15 -96
- package/dist/local-memory-telemetry.js +3 -20
- package/dist/machine-info.js +0 -3
- package/dist/main.js +0 -0
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/memory-prune-diagnostics.js +4 -60
- package/dist/message-file-preview.js +1 -11
- package/dist/prompt.js +17 -9
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +4 -25
- package/dist/serve.js +3 -38
- package/dist/token.js +0 -1
- package/dist/workspace-control-handler.js +3 -16
- package/dist/workspace.js +2 -13
- package/package.json +10 -8
- package/dist/local-memory/commit-telemetry.js +0 -30
- package/dist/local-memory/execution-workspace.js +0 -62
- package/dist/local-memory/full-index-search.js +0 -47
- package/dist/local-memory/initialization-identity.js +0 -36
- package/dist/local-memory/inventory-v2.js +0 -362
- package/dist/local-memory/legacy-adapter.js +0 -233
- package/dist/local-memory/migration-access.js +0 -242
- package/dist/local-memory/migration-cli.js +0 -54
- package/dist/local-memory/migration-io.js +0 -199
- package/dist/local-memory/migration-pending.js +0 -9
- package/dist/local-memory/migration-plan.js +0 -432
- package/dist/local-memory/migration-schema.js +0 -55
- package/dist/local-memory/migration-state-invariants.js +0 -65
- package/dist/local-memory/migration-store.js +0 -414
- package/dist/local-memory/migration-tool-entry.js +0 -21
- package/dist/local-memory/source-path.js +0 -13
- package/dist/local-memory/storage-format.js +0 -249
- package/dist/local-memory/writer-mode.js +0 -13
- package/dist/memory-migration-controller.js +0 -89
- package/dist/memory-migration-inspect.js +0 -108
- package/dist/memory-migration-journal.js +0 -14
- package/dist/memory-migration-protocol.js +0 -14
- package/dist/memory-migration-runtime.js +0 -305
|
@@ -2,12 +2,6 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { constants } from "node:fs";
|
|
3
3
|
import { lstat, mkdir, open, readFile, rm, writeFile } from "node:fs/promises";
|
|
4
4
|
import { join } from "node:path";
|
|
5
|
-
import { applyMemoryMigration, prepareMemoryMigration, recoverMemoryMigration } from "./migration-store.js";
|
|
6
|
-
import { MigrationStateSchema } from "./migration-schema.js";
|
|
7
|
-
import { detectLocalMemoryStorageVersion } from "./storage-format.js";
|
|
8
|
-
import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from "./initialization-identity.js";
|
|
9
|
-
import { inventory } from "./migration-io.js";
|
|
10
|
-
import { initializeExactWorkspaceSeed } from "./migration-plan.js";
|
|
11
5
|
import { applyLocalMemoryResult, parseLocalMemoryResult } from "./write.js";
|
|
12
6
|
import { verifyWorkLogSource } from "./work-log.js";
|
|
13
7
|
export function isLocalMemoryV2Eligible(input) {
|
|
@@ -18,7 +12,7 @@ export function isLocalMemoryV2Eligible(input) {
|
|
|
18
12
|
return input.enabledForHandle && input.memoryPrune;
|
|
19
13
|
}
|
|
20
14
|
const LOCAL_MEMORY_V2_MARKER = "v2-enabled";
|
|
21
|
-
async function
|
|
15
|
+
export async function initializeLocalMemoryV2(agentDir) {
|
|
22
16
|
const crewDir = join(agentDir, ".crew");
|
|
23
17
|
const memoryDir = join(crewDir, "memory");
|
|
24
18
|
await mkdir(crewDir, { recursive: true });
|
|
@@ -37,151 +31,24 @@ async function writeLocalMemoryV2Marker(agentDir) {
|
|
|
37
31
|
throw error;
|
|
38
32
|
}
|
|
39
33
|
}
|
|
40
|
-
function
|
|
41
|
-
return {
|
|
42
|
-
...identity,
|
|
43
|
-
kind: "server_write_intent",
|
|
44
|
-
sourceDigest: state.sourceDigest,
|
|
45
|
-
planDigest: state.planDigest,
|
|
46
|
-
intentVersion: 1,
|
|
47
|
-
// New-Agent initialization is derived from the exact pristine seed. Keep
|
|
48
|
-
// the receipt deterministic so a crash between intent publication and the
|
|
49
|
-
// mutable state update can be retried without creating a conflicting fact.
|
|
50
|
-
confirmedAt: "1970-01-01T00:00:00.000Z",
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Initialize a pristine new Agent through the same journaled schema2
|
|
55
|
-
* publication path used by migration. The marker is written only after the
|
|
56
|
-
* committed catalog and state have both been verified.
|
|
57
|
-
*/
|
|
58
|
-
async function initializeLocalMemoryV2WithBinding(agentDir, agentId, binding) {
|
|
59
|
-
const identity = await localMemoryInitializationIdentity(agentDir, agentId, binding);
|
|
60
|
-
let state = await prepareMemoryMigration({
|
|
61
|
-
agentDir,
|
|
62
|
-
identity,
|
|
63
|
-
serverHistory: { kind: "none", isNewAgent: true },
|
|
64
|
-
});
|
|
65
|
-
if (state.origin !== "initialized")
|
|
66
|
-
throw new Error("local_memory_v2_initialization_source_invalid");
|
|
67
|
-
if (state.phase === "prepared") {
|
|
68
|
-
state = await applyMemoryMigration({
|
|
69
|
-
agentDir,
|
|
70
|
-
identity,
|
|
71
|
-
intentReceipt: initializedIntent(identity, state),
|
|
72
|
-
});
|
|
73
|
-
}
|
|
74
|
-
else if (state.phase === "applying" || state.phase === "repair_required") {
|
|
75
|
-
state = await recoverMemoryMigration({
|
|
76
|
-
agentDir,
|
|
77
|
-
identity,
|
|
78
|
-
intentReceipt: state.intentReceipt ?? initializedIntent(identity, state),
|
|
79
|
-
stoppedReceipt: {
|
|
80
|
-
kind: "migration_process_stopped",
|
|
81
|
-
migrationId: identity.migrationId,
|
|
82
|
-
machineId: identity.machineId,
|
|
83
|
-
executionId: identity.executionId,
|
|
84
|
-
ownerIdentity: identity.ownerIdentity,
|
|
85
|
-
pid: null,
|
|
86
|
-
stoppedAt: new Date().toISOString(),
|
|
87
|
-
proof: "journal_reconciled",
|
|
88
|
-
},
|
|
89
|
-
cancelled: false,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
const memory = await readFile(join(agentDir, "MEMORY.md"), "utf8");
|
|
93
|
-
if (state.phase !== "committed" || state.origin !== "initialized" || state.storageVersion !== 2
|
|
94
|
-
|| detectLocalMemoryStorageVersion(memory) !== 2) {
|
|
95
|
-
throw new Error("local_memory_v2_initialization_incomplete");
|
|
96
|
-
}
|
|
97
|
-
await writeLocalMemoryV2Marker(agentDir);
|
|
98
|
-
}
|
|
99
|
-
/** Fresh initialization requires the identity from the authenticated ready frame. */
|
|
100
|
-
export function initializeLocalMemoryV2(agentDir, agentId, binding) {
|
|
101
|
-
return initializeLocalMemoryV2WithBinding(agentDir, agentId, binding);
|
|
102
|
-
}
|
|
103
|
-
export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle, agentId, binding) {
|
|
34
|
+
export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle) {
|
|
104
35
|
const initialized = workspace.localMemoryV2Initialized === true;
|
|
105
|
-
if (initialized
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
// callers side-effect free instead of probing an unrelated filesystem path.
|
|
109
|
-
if (workspace.localMemoryV2Initialized !== false)
|
|
110
|
-
return false;
|
|
111
|
-
let resumable = false;
|
|
112
|
-
let effectiveBinding = binding;
|
|
36
|
+
if (!enabledForHandle || initialized || workspace.localMemoryV2Initialized !== false
|
|
37
|
+
|| workspace.agentHomePreexisted === true || workspace.memorySeedCreated !== true)
|
|
38
|
+
return initialized;
|
|
113
39
|
try {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const legacyIdentity = binding === undefined
|
|
117
|
-
? identity
|
|
118
|
-
: await localMemoryInitializationIdentity(workspace.dir, agentId);
|
|
119
|
-
resumable = state.origin === "initialized"
|
|
120
|
-
&& state.phase !== "rolled_back"
|
|
121
|
-
&& (isLocalMemoryInitializationIdentity(state.identity, identity)
|
|
122
|
-
|| isLocalMemoryInitializationIdentity(state.identity, legacyIdentity));
|
|
123
|
-
if (resumable && isLocalMemoryInitializationIdentity(state.identity, legacyIdentity))
|
|
124
|
-
effectiveBinding = undefined;
|
|
125
|
-
}
|
|
126
|
-
catch (error) {
|
|
127
|
-
if (error.code !== "ENOENT")
|
|
128
|
-
throw error;
|
|
129
|
-
try {
|
|
130
|
-
const migrations = await lstat(join(workspace.dir, ".crew", "memory", "migrations"));
|
|
131
|
-
if (!migrations.isDirectory() || migrations.isSymbolicLink()) {
|
|
132
|
-
throw new Error("local_memory_v2_initialization_residue_invalid");
|
|
133
|
-
}
|
|
134
|
-
const identity = await localMemoryInitializationIdentity(workspace.dir, agentId, binding);
|
|
135
|
-
const legacyIdentity = binding === undefined
|
|
136
|
-
? identity
|
|
137
|
-
: await localMemoryInitializationIdentity(workspace.dir, agentId);
|
|
138
|
-
let operationIdentity = identity;
|
|
139
|
-
const operation = await lstat(join(workspace.dir, ".crew", "memory", "migrations", identity.migrationId))
|
|
140
|
-
.catch(async (error) => {
|
|
141
|
-
if (error.code !== "ENOENT" || identity.migrationId === legacyIdentity.migrationId)
|
|
142
|
-
throw error;
|
|
143
|
-
operationIdentity = legacyIdentity;
|
|
144
|
-
return lstat(join(workspace.dir, ".crew", "memory", "migrations", legacyIdentity.migrationId));
|
|
145
|
-
});
|
|
146
|
-
if (!operation.isDirectory() || operation.isSymbolicLink()) {
|
|
147
|
-
throw new Error("local_memory_v2_initialization_residue_invalid");
|
|
148
|
-
}
|
|
149
|
-
const current = await inventory(workspace.dir);
|
|
150
|
-
const memory = current.files.get("MEMORY.md");
|
|
151
|
-
resumable = current.files.size === 1 && memory !== undefined
|
|
152
|
-
&& current.directories.every(path => path === "notes" || path === ".crew/memory/pending")
|
|
153
|
-
&& initializeExactWorkspaceSeed(memory) !== null;
|
|
154
|
-
if (resumable && operationIdentity.migrationId === legacyIdentity.migrationId)
|
|
155
|
-
effectiveBinding = undefined;
|
|
156
|
-
}
|
|
157
|
-
catch (residueError) {
|
|
158
|
-
if (residueError.code !== "ENOENT")
|
|
159
|
-
throw residueError;
|
|
160
|
-
}
|
|
40
|
+
await initializeLocalMemoryV2(workspace.dir);
|
|
41
|
+
return true;
|
|
161
42
|
}
|
|
162
|
-
|
|
163
|
-
&& workspace.localMemoryV2Initialized === false
|
|
164
|
-
&& workspace.agentHomePreexisted !== true
|
|
165
|
-
&& workspace.memorySeedCreated === true;
|
|
166
|
-
if (!resumable && !pristineFirstInitialization)
|
|
167
|
-
return false;
|
|
168
|
-
// Never create another unbound sentinel. Undefined is retained solely to
|
|
169
|
-
// finish a pre-upgrade journal already proven above.
|
|
170
|
-
if (!resumable && effectiveBinding === undefined)
|
|
43
|
+
catch {
|
|
171
44
|
return false;
|
|
172
|
-
|
|
173
|
-
return true;
|
|
45
|
+
}
|
|
174
46
|
}
|
|
175
47
|
export function localMemoryResultPath(runDir, executionId) {
|
|
176
48
|
const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
|
|
177
49
|
return join(runDir, `.memory-result.v1-${suffix}.json`);
|
|
178
50
|
}
|
|
179
|
-
export function renderLocalMemoryResultInstructions(resultPath
|
|
180
|
-
const storagePolicy = migrationDeferred
|
|
181
|
-
? "该 Agent 的旧记忆仍在迁移审阅中。本轮只允许提交新的 create 候选;daemon 会把候选原子保存到 .crew/memory/pending/,reason=legacy_requires_migration,并提供 search-pending/read-pending 查询。候选不会进入 active,也不会自动注入普通上下文;更新、替代和 statusChanges 必须留待迁移完成后按当前 V2 ID/revision/hash 重新核对。旧 MEMORY.md、notes 和已有 active/pending 保持不变。"
|
|
182
|
-
: storageVersion === 2
|
|
183
|
-
? "完整目录保存在 MEMORY.md,模型只看到有界预览;未展示记录仍然有效。涉及旧规则时先用 crew-memory search 查询完整目录,按当前 ID/revision 更新,不因预览未命中就重复新建。20条预览不是有效记录上限;完整存储超出资源上限会拒绝提交,不得自行删除或降级旧记录。已有 pending 仍是未应用候选,须重新核对授权、重复与版本后通过 changes 申请保存,不因迁移自动生效。"
|
|
184
|
-
: "daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。";
|
|
51
|
+
export function renderLocalMemoryResultInstructions(resultPath) {
|
|
185
52
|
return `
|
|
186
53
|
|
|
187
54
|
## Structured memory prune (daemon-managed)
|
|
@@ -203,7 +70,7 @@ outcome 只允许 no_change、proposed、conflict、failed。新增、更新、
|
|
|
203
70
|
\`\`\`
|
|
204
71
|
|
|
205
72
|
每个 changes 项须保留示例中的全部字段。operation=create|update|supersede;kind=procedural|semantic|episodic|preference;priority=high|medium|low;detailCategory=preferences|project-rules|testing|debugging|git|incidents|lessons。create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null;update 按现有索引填写 memoryId/baseRevision,supersedesMemoryId 为 null;supersede 填 supersedesMemoryId/baseRevision,memoryId 为 null。
|
|
206
|
-
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token
|
|
73
|
+
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。
|
|
207
74
|
写入候选文件只表示提交候选,不能提前宣称已经记住或规则已经更新;daemon 在本轮结束后才处理,候选持久化与进入有效索引也不是同一结果。本结构化轮次覆盖前文提前清理工作日志的要求:保留 $CREW_TASK_LOG,不要清空或删除;daemon 将在处理成功后清理,失败时保留日志和候选用于排查或重试。
|
|
208
75
|
`;
|
|
209
76
|
}
|
|
@@ -214,20 +81,6 @@ export function renderLocalMemoryV2WriteGuard() {
|
|
|
214
81
|
## V2 shared memory write safety
|
|
215
82
|
本节覆盖前文关于普通任务直接更新共享记忆的例外规则。本 Agent 已启用结构化本地记忆 V2。普通任务不得直接编辑共享的 $CREW_HOME/MEMORY.md 或 $CREW_HOME/notes/ 文件;即使用户明确要求记住内容,也只把候选、用户授权和分类理由记录到本任务的 $CREW_TASK_LOG,留给任务结束后的 memory-prune 结构化收尾应用。只有 memory-prune 收尾提示明确要求写入 daemon 提供的 result 文件时,才按该协议提交候选。`;
|
|
216
83
|
}
|
|
217
|
-
async function localMemoryStorageFacts(agentDir) {
|
|
218
|
-
const memory = await readFile(join(agentDir, "MEMORY.md"), "utf8");
|
|
219
|
-
const storageVersion = detectLocalMemoryStorageVersion(memory);
|
|
220
|
-
let committedState = false;
|
|
221
|
-
try {
|
|
222
|
-
const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(agentDir, ".crew", "memory", "migration.v1.json"), "utf8")));
|
|
223
|
-
committedState = state.phase === "committed" && state.storageVersion === 2;
|
|
224
|
-
}
|
|
225
|
-
catch (error) {
|
|
226
|
-
if (error.code !== "ENOENT" && storageVersion === 2)
|
|
227
|
-
throw error;
|
|
228
|
-
}
|
|
229
|
-
return { storageVersion, committedState };
|
|
230
|
-
}
|
|
231
84
|
function workLogFailure(error) {
|
|
232
85
|
const reason = error instanceof Error ? error.message : "";
|
|
233
86
|
return { status: "failed", error: reason === "local_memory_work_log_source_changed"
|
|
@@ -245,9 +98,6 @@ export async function commitLocalMemoryResultAfterExit(input) {
|
|
|
245
98
|
return { status: "missing" };
|
|
246
99
|
return { status: "failed", error: error instanceof Error ? error.message : "read_failed" };
|
|
247
100
|
}
|
|
248
|
-
let storageBefore;
|
|
249
|
-
let storageAfter;
|
|
250
|
-
let applied;
|
|
251
101
|
try {
|
|
252
102
|
const result = parseLocalMemoryResult(raw);
|
|
253
103
|
if (result.outcome === "conflict" || result.outcome === "failed") {
|
|
@@ -261,25 +111,12 @@ export async function commitLocalMemoryResultAfterExit(input) {
|
|
|
261
111
|
return workLogFailure(error);
|
|
262
112
|
}
|
|
263
113
|
}
|
|
264
|
-
|
|
265
|
-
if (input.requiredStorageVersion === 2
|
|
266
|
-
&& (storageBefore.storageVersion !== 2 || !storageBefore.committedState)) {
|
|
267
|
-
return { status: "failed", error: "local_memory_v2_state_invalid_before_commit" };
|
|
268
|
-
}
|
|
269
|
-
applied = await applyLocalMemoryResult({
|
|
114
|
+
await applyLocalMemoryResult({
|
|
270
115
|
agentDir: input.agentDir,
|
|
271
116
|
result,
|
|
272
117
|
executionId: input.executionId,
|
|
273
118
|
pruneTraceId: input.pruneTraceId,
|
|
274
|
-
...(input.writerCapabilities === undefined ? {} : { writerCapabilities: input.writerCapabilities }),
|
|
275
|
-
...(input.requiredStorageVersion === undefined ? {} : { requiredStorageVersion: input.requiredStorageVersion }),
|
|
276
|
-
...(input.migrationDeferredPending ? { migrationDeferredPending: true } : {}),
|
|
277
119
|
});
|
|
278
|
-
storageAfter = await localMemoryStorageFacts(input.agentDir);
|
|
279
|
-
if (input.requiredStorageVersion === 2
|
|
280
|
-
&& (storageAfter.storageVersion !== 2 || !storageAfter.committedState)) {
|
|
281
|
-
return { status: "failed", error: "local_memory_v2_state_invalid_after_commit" };
|
|
282
|
-
}
|
|
283
120
|
}
|
|
284
121
|
catch (error) {
|
|
285
122
|
return { status: "failed", error: error instanceof Error ? error.message : "apply_failed" };
|
|
@@ -310,10 +147,5 @@ export async function commitLocalMemoryResultAfterExit(input) {
|
|
|
310
147
|
catch {
|
|
311
148
|
return { status: "failed", error: "local_memory_result_cleanup_failed" };
|
|
312
149
|
}
|
|
313
|
-
return { status: "applied"
|
|
314
|
-
storageVersionAfter: storageAfter.storageVersion, committedState: storageAfter.committedState,
|
|
315
|
-
createdIds: applied.createdIds, updatedIds: applied.updatedIds,
|
|
316
|
-
statusChangedIds: applied.statusChangedIds, pendingIds: applied.pendingIds,
|
|
317
|
-
...(input.migrationDeferredPending
|
|
318
|
-
? { pendingReason: "legacy_requires_migration" } : {}) };
|
|
150
|
+
return { status: "applied" };
|
|
319
151
|
}
|
|
@@ -6,8 +6,6 @@ import { dslog } from "../slog.js";
|
|
|
6
6
|
import { LocalMemoryChangeSchema, LocalMemorySingleLineText, validateLocalMemoryChange, } from "./candidate.js";
|
|
7
7
|
import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderGeneratedIndex, selectLocalMemoryIndexEntries, } from "./index-budget.js";
|
|
8
8
|
import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
|
|
9
|
-
import { assertLocalMemoryStorageSize, detectLocalMemoryStorageVersion, LOCAL_MEMORY_GENERATED_INDEX_PATTERN, LOCAL_MEMORY_NOTE_BYTE_CAP, LOCAL_MEMORY_READ_BYTE_CAP, parseFullMemoryIndex, replaceFullMemoryIndex, selectLocalMemoryPreview, } from "./storage-format.js";
|
|
10
|
-
import { assertMemoryInventoryHealth, assertMemoryWriterAllowed, withMemoryAccess } from "./migration-access.js";
|
|
11
9
|
const StatusChangeSchema = z.object({
|
|
12
10
|
memoryId: LocalMemorySingleLineText,
|
|
13
11
|
expectedRevision: z.number().int().positive(),
|
|
@@ -92,14 +90,12 @@ function parseCards(section) {
|
|
|
92
90
|
});
|
|
93
91
|
}
|
|
94
92
|
export function parseLocalMemoryIndex(memory) {
|
|
95
|
-
if (detectLocalMemoryStorageVersion(memory) === 2)
|
|
96
|
-
return parseFullMemoryIndex(memory);
|
|
97
93
|
const section = indexSection(memory);
|
|
98
94
|
const generated = GENERATED_INDEX_PATTERN.exec(section)?.[0];
|
|
99
95
|
return parseCards(generated ?? section);
|
|
100
96
|
}
|
|
101
97
|
function parseUnmarkedMemoryIndex(memory) {
|
|
102
|
-
const section = indexSection(memory).replace(
|
|
98
|
+
const section = indexSection(memory).replace(GENERATED_INDEX_PATTERN, "");
|
|
103
99
|
return parseCards(section);
|
|
104
100
|
}
|
|
105
101
|
function replaceGeneratedIndex(memory, entries) {
|
|
@@ -126,36 +122,11 @@ function noteSection(id, revision, title, body) {
|
|
|
126
122
|
}
|
|
127
123
|
function notePattern(id) {
|
|
128
124
|
const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
129
|
-
return new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}
|
|
130
|
-
}
|
|
131
|
-
function uniqueNoteSection(content, id) {
|
|
132
|
-
const escaped = id.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
|
|
133
|
-
const begins = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}(?=\\s|-->)`, "gu"))];
|
|
134
|
-
const ends = [...content.matchAll(new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->`, "gu"))];
|
|
135
|
-
if (begins.length === 0 && ends.length === 0)
|
|
136
|
-
return null;
|
|
137
|
-
const section = notePattern(id).exec(content)?.[0];
|
|
138
|
-
if (begins.length !== 1 || ends.length !== 1 || section === undefined
|
|
139
|
-
|| (section.match(/NOWWORK:MEMORY_ENTRY:(?:BEGIN|END)/gu) ?? []).length !== 2) {
|
|
140
|
-
throw new Error("local_memory_note_anchor_invalid");
|
|
141
|
-
}
|
|
142
|
-
return section;
|
|
125
|
+
return new RegExp(`<!--\\s*NOWWORK:MEMORY_ENTRY:BEGIN\\s+id=${escaped}[^>]*-->[\\s\\S]*?<!--\\s*NOWWORK:MEMORY_ENTRY:END\\s+id=${escaped}\\s*-->\\s*`, "u");
|
|
143
126
|
}
|
|
144
127
|
function replaceNote(content, id, replacement) {
|
|
145
128
|
const pattern = notePattern(id);
|
|
146
|
-
return
|
|
147
|
-
}
|
|
148
|
-
function updateNoteRevision(content, id, revision) {
|
|
149
|
-
const section = uniqueNoteSection(content, id);
|
|
150
|
-
const beginning = `<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id} `;
|
|
151
|
-
const ending = `<!-- NOWWORK:MEMORY_ENTRY:END id=${id} -->`;
|
|
152
|
-
if (section === null || content.split(beginning).length !== 2 || content.split(ending).length !== 2) {
|
|
153
|
-
throw new Error("local_memory_note_anchor_missing");
|
|
154
|
-
}
|
|
155
|
-
const updated = section.replace(/^(<!--\s*NOWWORK:MEMORY_ENTRY:BEGIN\s+id=[A-Za-z0-9_-]+\s+revision=)[1-9][0-9]*(\s*-->)/u, (_match, prefix, suffix) => `${prefix}${revision}${suffix}`);
|
|
156
|
-
if (!updated.startsWith(`${beginning}revision=${revision} -->`))
|
|
157
|
-
throw new Error("local_memory_note_anchor_missing");
|
|
158
|
-
return content.replace(notePattern(id), () => updated);
|
|
129
|
+
return pattern.test(content) ? content.replace(pattern, replacement) : null;
|
|
159
130
|
}
|
|
160
131
|
function validateCandidate(result) {
|
|
161
132
|
if (result.outcome === "no_change" && (result.changes.length > 0 || result.statusChanges.length > 0)) {
|
|
@@ -209,25 +180,6 @@ function stableId(executionId, index, change) {
|
|
|
209
180
|
const input = [executionId, String(index), change.operation, change.title, change.summary, change.detailBody].join("\0");
|
|
210
181
|
return `mem_${createHash("sha256").update(input, "utf8").digest("hex").slice(0, 12)}`;
|
|
211
182
|
}
|
|
212
|
-
function migrationPendingId(change) {
|
|
213
|
-
// Keep the digest independent of object insertion order for callers that
|
|
214
|
-
// reach applyLocalMemoryResult directly instead of the JSON/Zod boundary.
|
|
215
|
-
const canonicalChange = {
|
|
216
|
-
operation: change.operation,
|
|
217
|
-
memoryId: change.memoryId,
|
|
218
|
-
baseRevision: change.baseRevision,
|
|
219
|
-
supersedesMemoryId: change.supersedesMemoryId,
|
|
220
|
-
title: change.title,
|
|
221
|
-
kind: change.kind,
|
|
222
|
-
summary: change.summary,
|
|
223
|
-
triggers: change.triggers,
|
|
224
|
-
priority: change.priority,
|
|
225
|
-
detailCategory: change.detailCategory,
|
|
226
|
-
detailBody: change.detailBody,
|
|
227
|
-
reviewAfter: change.reviewAfter,
|
|
228
|
-
};
|
|
229
|
-
return `pending-migration-${createHash("sha256").update(JSON.stringify(canonicalChange), "utf8").digest("hex").slice(0, 12)}`;
|
|
230
|
-
}
|
|
231
183
|
function normalize(value) {
|
|
232
184
|
return value.normalize("NFKC").toLocaleLowerCase().replace(/\s+/gu, " ").trim();
|
|
233
185
|
}
|
|
@@ -247,14 +199,14 @@ function detailCategoryFromFile(file) {
|
|
|
247
199
|
return DETAIL_CATEGORIES.has(category) ? category : null;
|
|
248
200
|
}
|
|
249
201
|
function readNoteBody(content, id) {
|
|
250
|
-
const
|
|
251
|
-
if (
|
|
202
|
+
const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
|
|
203
|
+
if (begin < 0)
|
|
252
204
|
return null;
|
|
253
|
-
const bodyStart =
|
|
254
|
-
const bodyEnd =
|
|
205
|
+
const bodyStart = content.indexOf("\n\n", begin);
|
|
206
|
+
const bodyEnd = content.indexOf(`\n<!-- NOWWORK:MEMORY_ENTRY:END id=${id}`, bodyStart + 2);
|
|
255
207
|
if (bodyStart < 0 || bodyEnd < 0)
|
|
256
208
|
return null;
|
|
257
|
-
return
|
|
209
|
+
return content.slice(bodyStart + 2, bodyEnd).trim();
|
|
258
210
|
}
|
|
259
211
|
function archivedChange(entry, detailBody) {
|
|
260
212
|
const detailCategory = detailCategoryFromFile(entry.file);
|
|
@@ -278,34 +230,6 @@ function archivedChange(entry, detailBody) {
|
|
|
278
230
|
function emptyResult() {
|
|
279
231
|
return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: [], pendingIds: [] };
|
|
280
232
|
}
|
|
281
|
-
async function stageMigrationDeferredPending(input) {
|
|
282
|
-
if (input.result.outcome !== "proposed")
|
|
283
|
-
return emptyResult();
|
|
284
|
-
if (input.result.statusChanges.length > 0 || input.result.changes.some((change) => change.operation !== "create")) {
|
|
285
|
-
throw new Error("local_memory_migration_pending_create_only");
|
|
286
|
-
}
|
|
287
|
-
const executionId = input.executionId ?? "unknown";
|
|
288
|
-
const pending = input.result.changes.map((change) => ({
|
|
289
|
-
schemaVersion: 1,
|
|
290
|
-
pendingId: migrationPendingId(change),
|
|
291
|
-
createdAt: new Date().toISOString(),
|
|
292
|
-
reason: "legacy_requires_migration",
|
|
293
|
-
source: { executionId, pruneTraceId: input.pruneTraceId ?? null },
|
|
294
|
-
candidate: change,
|
|
295
|
-
}));
|
|
296
|
-
await assertMemoryAccessDirectory(input.agentDir);
|
|
297
|
-
await withMemoryAccess(input.agentDir, async () => {
|
|
298
|
-
const memory = await readFile(join(input.agentDir, "MEMORY.md"), "utf8").catch(() => null);
|
|
299
|
-
if (memory === null || detectLocalMemoryStorageVersion(memory) !== 1) {
|
|
300
|
-
throw new Error("local_memory_migration_pending_storage_invalid");
|
|
301
|
-
}
|
|
302
|
-
for (const candidate of pending) {
|
|
303
|
-
await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
|
|
304
|
-
}
|
|
305
|
-
}, { pendingOnly: true });
|
|
306
|
-
const pendingIds = [...new Set(pending.map((candidate) => candidate.pendingId))];
|
|
307
|
-
return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: pendingIds, pendingIds };
|
|
308
|
-
}
|
|
309
233
|
function isWithin(root, candidate) {
|
|
310
234
|
const path = relative(root, candidate);
|
|
311
235
|
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
|
|
@@ -320,29 +244,12 @@ async function assertWritablePaths(agentDir, memoryPath) {
|
|
|
320
244
|
if (!memoryStat.isFile() || memoryStat.isSymbolicLink())
|
|
321
245
|
throw new Error("local_memory_memory_path_invalid");
|
|
322
246
|
}
|
|
323
|
-
async function assertMemoryAccessDirectory(agentDir) {
|
|
324
|
-
try {
|
|
325
|
-
const stat = await lstat(join(agentDir, ".crew/memory"));
|
|
326
|
-
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
327
|
-
throw new Error("local_memory_pending_path_invalid");
|
|
328
|
-
}
|
|
329
|
-
catch (error) {
|
|
330
|
-
if (error.code === "ENOENT")
|
|
331
|
-
return;
|
|
332
|
-
throw error;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
247
|
async function readNote(path) {
|
|
336
248
|
try {
|
|
337
249
|
const stat = await lstat(path);
|
|
338
250
|
if (!stat.isFile() || stat.isSymbolicLink())
|
|
339
251
|
throw new Error("local_memory_note_path_invalid");
|
|
340
|
-
|
|
341
|
-
throw new Error("local_memory_note_byte_limit");
|
|
342
|
-
const content = await readFile(path, "utf8");
|
|
343
|
-
if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
344
|
-
throw new Error("local_memory_note_byte_limit");
|
|
345
|
-
return content;
|
|
252
|
+
return await readFile(path, "utf8");
|
|
346
253
|
}
|
|
347
254
|
catch (error) {
|
|
348
255
|
if (error.code === "ENOENT")
|
|
@@ -362,11 +269,6 @@ async function atomicWrite(path, content) {
|
|
|
362
269
|
}
|
|
363
270
|
}
|
|
364
271
|
function projectIndex(memory, entries, protectedIds) {
|
|
365
|
-
if (detectLocalMemoryStorageVersion(memory) === 2) {
|
|
366
|
-
const updated = replaceFullMemoryIndex(memory, entries);
|
|
367
|
-
selectLocalMemoryPreview(updated);
|
|
368
|
-
return { memory: updated, omittedIds: [], reason: null };
|
|
369
|
-
}
|
|
370
272
|
const empty = replaceGeneratedIndex(memory, []);
|
|
371
273
|
if (codePointLength(empty) > LOCAL_MEMORY_TOTAL_CODE_POINT_CAP)
|
|
372
274
|
throw new Error("local_memory_memory_over_budget");
|
|
@@ -404,29 +306,12 @@ async function withApplyLease(agentDir, operation) {
|
|
|
404
306
|
}
|
|
405
307
|
export async function applyLocalMemoryResult(input) {
|
|
406
308
|
validateCandidate(input.result);
|
|
407
|
-
if (input.
|
|
408
|
-
return stageMigrationDeferredPending(input);
|
|
409
|
-
if (input.result.outcome !== "proposed" && input.requiredStorageVersion !== 2)
|
|
309
|
+
if (input.result.outcome !== "proposed")
|
|
410
310
|
return emptyResult();
|
|
411
311
|
const memoryPath = join(input.agentDir, "MEMORY.md");
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
await assertMemoryAccessDirectory(input.agentDir);
|
|
415
|
-
return withApplyLease(input.agentDir, async () => withMemoryAccess(input.agentDir, async () => {
|
|
416
|
-
await assertMemoryWriterAllowed(input.agentDir, input.writerCapabilities ?? []);
|
|
417
|
-
const memoryStat = await lstat(memoryPath);
|
|
418
|
-
if (memoryStat.size > 16 * 1024 * 1024)
|
|
419
|
-
throw new Error("local_memory_storage_byte_limit");
|
|
312
|
+
await assertWritablePaths(input.agentDir, memoryPath);
|
|
313
|
+
return withApplyLease(input.agentDir, async () => {
|
|
420
314
|
const memory = await readFile(memoryPath, "utf8");
|
|
421
|
-
assertLocalMemoryStorageSize(memory);
|
|
422
|
-
const fullStorage = detectLocalMemoryStorageVersion(memory) === 2;
|
|
423
|
-
if (input.requiredStorageVersion === 2 && !fullStorage) {
|
|
424
|
-
throw new Error("local_memory_v2_state_invalid_before_commit");
|
|
425
|
-
}
|
|
426
|
-
// A V2 no-op is still a commit decision. Return only after the access
|
|
427
|
-
// lock and committed-state writer gate have validated the same snapshot.
|
|
428
|
-
if (input.result.outcome !== "proposed")
|
|
429
|
-
return emptyResult();
|
|
430
315
|
if (parseUnmarkedMemoryIndex(memory).length > 0)
|
|
431
316
|
throw new Error("local_memory_legacy_index_requires_migration");
|
|
432
317
|
let entries = [...parseLocalMemoryIndex(memory)];
|
|
@@ -457,26 +342,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
457
342
|
const pendingCandidates = [];
|
|
458
343
|
const resolvedPendingIds = new Set();
|
|
459
344
|
const noteUpdates = new Map();
|
|
460
|
-
const noteCache = new Map();
|
|
461
|
-
let totalReadBytes = Buffer.byteLength(memory, "utf8");
|
|
462
|
-
const readStoredNote = async (path) => {
|
|
463
|
-
const cached = noteUpdates.get(path) ?? noteCache.get(path);
|
|
464
|
-
if (cached !== undefined)
|
|
465
|
-
return cached;
|
|
466
|
-
const stat = await lstat(path).catch((error) => {
|
|
467
|
-
if (error.code === "ENOENT")
|
|
468
|
-
return null;
|
|
469
|
-
throw error;
|
|
470
|
-
});
|
|
471
|
-
if (stat !== null && totalReadBytes + stat.size > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
472
|
-
throw new Error("local_memory_read_byte_limit");
|
|
473
|
-
const content = await readNote(path);
|
|
474
|
-
totalReadBytes += Buffer.byteLength(content, "utf8");
|
|
475
|
-
if (totalReadBytes > LOCAL_MEMORY_READ_BYTE_CAP)
|
|
476
|
-
throw new Error("local_memory_read_byte_limit");
|
|
477
|
-
noteCache.set(path, content);
|
|
478
|
-
return content;
|
|
479
|
-
};
|
|
480
345
|
let allowActiveEviction = false;
|
|
481
346
|
const randomId = input.randomId ?? randomUUID;
|
|
482
347
|
const executionId = input.executionId ?? "unknown";
|
|
@@ -518,21 +383,10 @@ export async function applyLocalMemoryResult(input) {
|
|
|
518
383
|
if (existingCreate !== undefined && !matchesCreateCandidate(existingCreate, change)) {
|
|
519
384
|
throw new Error("local_memory_duplicate_candidate");
|
|
520
385
|
}
|
|
521
|
-
if (existingCreate === undefined && entries.some((entry) => (
|
|
522
|
-
|
|
386
|
+
if (existingCreate === undefined && entries.some((entry) => normalize(entry.title) === normalize(change.title)
|
|
387
|
+
|| normalize(entry.summary) === normalize(change.summary))) {
|
|
523
388
|
throw new Error("local_memory_duplicate_candidate");
|
|
524
389
|
}
|
|
525
|
-
if (existingCreate !== undefined) {
|
|
526
|
-
if (existingCreate.revision !== 1 || existingCreate.status !== "active") {
|
|
527
|
-
throw new Error("local_memory_create_replay_conflict");
|
|
528
|
-
}
|
|
529
|
-
const stored = await readStoredNote(resolve(input.agentDir, existingCreate.file));
|
|
530
|
-
if (uniqueNoteSection(stored, id)?.trimEnd() !== noteSection(id, 1, change.title, change.detailBody).trimEnd()) {
|
|
531
|
-
throw new Error("local_memory_create_replay_conflict");
|
|
532
|
-
}
|
|
533
|
-
// A proven duplicate is a no-op; it must never rewrite a newer body or status.
|
|
534
|
-
continue;
|
|
535
|
-
}
|
|
536
390
|
}
|
|
537
391
|
const revision = existingCreate?.revision ?? (current === undefined ? 1 : current.revision + 1);
|
|
538
392
|
const entry = existingCreate ?? entryFromChange(change, id, revision, current?.status ?? "active");
|
|
@@ -546,7 +400,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
546
400
|
: item),
|
|
547
401
|
entry,
|
|
548
402
|
];
|
|
549
|
-
const allowEvictionForChange =
|
|
403
|
+
const allowEvictionForChange = change.operation === "create" && change.priority === "high";
|
|
550
404
|
const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
551
405
|
if (allowEvictionForChange)
|
|
552
406
|
allowActiveEviction = true;
|
|
@@ -563,7 +417,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
563
417
|
const notePath = resolve(input.agentDir, entry.file);
|
|
564
418
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
565
419
|
throw new Error("local_memory_note_path_invalid");
|
|
566
|
-
const previous = await
|
|
420
|
+
const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
567
421
|
const replacement = noteSection(id, revision, change.title, change.detailBody);
|
|
568
422
|
const existingSection = replaceNote(previous, id, replacement);
|
|
569
423
|
const next = existingSection ?? (current === undefined
|
|
@@ -581,7 +435,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
581
435
|
// Pending create candidates are durable overflow, not dead ends. Promote only
|
|
582
436
|
// when the current catalog has room; supersede/update candidates stay pending
|
|
583
437
|
// until a fresh revision-aware decision can safely replay them.
|
|
584
|
-
const pendingCandidatesOnDisk =
|
|
438
|
+
const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir }).catch((error) => {
|
|
585
439
|
if (!(error instanceof Error) || error.message !== "memory_pending_directory_over_budget")
|
|
586
440
|
throw error;
|
|
587
441
|
// 自动提升是可选工作,目录查询预算不应阻断本轮已验证的更新或腾位操作。
|
|
@@ -592,10 +446,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
592
446
|
return [];
|
|
593
447
|
});
|
|
594
448
|
for (const pending of pendingCandidatesOnDisk) {
|
|
595
|
-
// Migration-deferred candidates require reviewed import; only capacity
|
|
596
|
-
// overflow is eligible for the existing automatic promotion path.
|
|
597
|
-
if (pending.reason !== "index_over_budget")
|
|
598
|
-
continue;
|
|
599
449
|
const change = pending.candidate;
|
|
600
450
|
if (change.operation !== "create")
|
|
601
451
|
continue;
|
|
@@ -610,7 +460,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
610
460
|
|| normalize(entry.summary) === normalize(change.summary)));
|
|
611
461
|
if (duplicate !== undefined) {
|
|
612
462
|
const duplicatePath = resolve(input.agentDir, duplicate.file);
|
|
613
|
-
const duplicateBody = readNoteBody(await
|
|
463
|
+
const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
|
|
614
464
|
if (duplicateBody === change.detailBody
|
|
615
465
|
|| duplicateBody === unwrappedNoteBody(change.title, change.detailBody).trim()) {
|
|
616
466
|
resolvedPendingIds.add(pending.pendingId);
|
|
@@ -628,7 +478,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
628
478
|
const notePath = resolve(input.agentDir, entry.file);
|
|
629
479
|
if (!isWithin(resolve(input.agentDir, "notes"), notePath))
|
|
630
480
|
throw new Error("local_memory_note_path_invalid");
|
|
631
|
-
const previous = await
|
|
481
|
+
const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
632
482
|
const replacement = noteSection(id, 1, change.title, change.detailBody);
|
|
633
483
|
const existingSection = replaceNote(previous, id, replacement);
|
|
634
484
|
const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
|
|
@@ -637,17 +487,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
637
487
|
createdIds.push(id);
|
|
638
488
|
resolvedPendingIds.add(pending.pendingId);
|
|
639
489
|
}
|
|
640
|
-
if (fullStorage) {
|
|
641
|
-
const changedMetadataIds = new Set([
|
|
642
|
-
...statusChangedIds,
|
|
643
|
-
...input.result.changes.flatMap(change => change.supersedesMemoryId === null ? [] : [change.supersedesMemoryId]),
|
|
644
|
-
]);
|
|
645
|
-
for (const id of changedMetadataIds) {
|
|
646
|
-
const entry = entries.find(item => item.id === id);
|
|
647
|
-
const notePath = resolve(input.agentDir, entry.file);
|
|
648
|
-
noteUpdates.set(notePath, updateNoteRevision(await readStoredNote(notePath), id, entry.revision));
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
490
|
let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
|
|
652
491
|
if (finalProjection.reason !== null
|
|
653
492
|
&& (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
|
|
@@ -659,7 +498,7 @@ export async function applyLocalMemoryResult(input) {
|
|
|
659
498
|
.filter((entry) => entry?.status === "active");
|
|
660
499
|
for (const entry of omittedActiveEntries) {
|
|
661
500
|
const notePath = resolve(input.agentDir, entry.file);
|
|
662
|
-
const content = await
|
|
501
|
+
const content = noteUpdates.get(notePath) ?? await readNote(notePath);
|
|
663
502
|
const detailBody = readNoteBody(content, entry.id);
|
|
664
503
|
const change = detailBody === null ? null : archivedChange(entry, detailBody);
|
|
665
504
|
if (change === null)
|
|
@@ -680,13 +519,6 @@ export async function applyLocalMemoryResult(input) {
|
|
|
680
519
|
if (finalProjection.reason !== null)
|
|
681
520
|
throw new Error(finalProjection.reason);
|
|
682
521
|
}
|
|
683
|
-
// Complete every resource check before the first persistent write.
|
|
684
|
-
assertLocalMemoryStorageSize(finalProjection.memory);
|
|
685
|
-
for (const content of noteUpdates.values()) {
|
|
686
|
-
if (Buffer.byteLength(content, "utf8") > LOCAL_MEMORY_NOTE_BYTE_CAP)
|
|
687
|
-
throw new Error("local_memory_note_byte_limit");
|
|
688
|
-
}
|
|
689
|
-
await assertMemoryInventoryHealth(input.agentDir);
|
|
690
522
|
for (const candidate of pendingCandidates)
|
|
691
523
|
await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
|
|
692
524
|
for (const [path, content] of noteUpdates)
|
|
@@ -703,5 +535,5 @@ export async function applyLocalMemoryResult(input) {
|
|
|
703
535
|
deferredIds,
|
|
704
536
|
pendingIds: deferredIds,
|
|
705
537
|
};
|
|
706
|
-
})
|
|
538
|
+
});
|
|
707
539
|
}
|