@nowcrew/daemon 0.6.63 → 0.6.65
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 -8
- package/dist/agent-ability/materializer.js +20 -1
- package/dist/agent-ability/runtime.js +3 -0
- package/dist/automatic-install-target.js +15 -10
- package/dist/computer-cli.js +10 -1
- package/dist/computer-service.js +41 -1
- package/dist/config.js +44 -0
- package/dist/daemon-migration-controller.js +16 -18
- package/dist/execution-runner.js +16 -35
- package/dist/local-execution-prompt.js +14 -9
- package/dist/local-executor.js +56 -3
- package/dist/local-memory/index-budget.js +74 -0
- package/dist/local-memory/pending.js +111 -0
- package/dist/local-memory/runtime.js +87 -0
- package/dist/local-memory/types.js +1 -0
- package/dist/local-memory/write.js +448 -0
- package/dist/machine-info.js +3 -2
- package/dist/main.js +83 -0
- package/dist/managed-service-lifecycle.js +47 -2
- package/dist/managed-service-startup.js +2 -0
- package/dist/profile-layout.js +10 -4
- package/dist/project-skills/controller.js +2 -0
- package/dist/prompt.js +19 -1
- package/dist/runner.js +3 -0
- package/dist/runtimes/codex-app-server-runner.js +54 -14
- package/dist/runtimes/codex-command-safety.js +75 -0
- package/dist/runtimes/codex-contract.js +20 -5
- package/dist/runtimes/codex-turn-retry.js +69 -0
- package/dist/runtimes/codex.js +4 -0
- package/dist/serve.js +68 -3
- package/dist/storage-config.js +175 -0
- package/dist/storage-control.js +193 -0
- package/dist/storage-copy.js +609 -0
- package/dist/storage-credential-guard.js +21 -0
- package/dist/storage-doctor.js +228 -0
- package/dist/storage-identity.js +29 -0
- package/dist/storage-layout.js +143 -0
- package/dist/storage-manifest.js +855 -0
- package/dist/storage-migration-controller.js +417 -0
- package/dist/storage-migration-manifest.js +105 -0
- package/dist/storage-migration-policy.js +100 -0
- package/dist/storage-runtime-controller.js +339 -0
- package/dist/storage-runtime-readiness.js +184 -0
- package/dist/storage-runtime.js +39 -0
- package/dist/storage-safe-fs.js +302 -0
- package/dist/storage-serve-adapter.js +65 -0
- package/dist/storage-status-delivery.js +209 -0
- package/dist/storage-status-replay.js +125 -0
- package/dist/storage-target-confirmation.js +129 -0
- package/dist/storage-worker-command.js +27 -0
- package/dist/storage-worker-executor.js +384 -0
- package/dist/storage-worker-handoff.js +151 -0
- package/dist/storage-worker-operation.js +284 -0
- package/dist/storage-worker-status.js +105 -0
- package/dist/supervised-runtime.js +3 -0
- package/dist/workspace.js +24 -1
- package/package.json +1 -1
package/dist/local-executor.js
CHANGED
|
@@ -31,6 +31,7 @@ import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPr
|
|
|
31
31
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
32
32
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
33
33
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
34
|
+
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, renderLocalMemoryResultInstructions, } from "./local-memory/runtime.js";
|
|
34
35
|
function memoryPruneFileFactFields(fileFacts) {
|
|
35
36
|
const fields = {};
|
|
36
37
|
for (const [label, fact] of Object.entries(fileFacts)) {
|
|
@@ -77,6 +78,7 @@ const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
|
|
|
77
78
|
const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
78
79
|
const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
|
|
79
80
|
const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
|
|
81
|
+
const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
|
|
80
82
|
const ENV_KEY_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
81
83
|
const RESERVED_ENV = new Set([
|
|
82
84
|
"PATH",
|
|
@@ -277,6 +279,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
277
279
|
}, "workspace_prepare", error);
|
|
278
280
|
throw error;
|
|
279
281
|
}
|
|
282
|
+
const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, input.localMemoryV2Enabled === true);
|
|
280
283
|
let materialized = null;
|
|
281
284
|
let knownAttachmentDirectory = null;
|
|
282
285
|
const startupReservationState = { current: null };
|
|
@@ -286,6 +289,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
286
289
|
let memoryPruneFailurePhase = "diagnostics_before";
|
|
287
290
|
let memoryPruneBeforeSnapshot = null;
|
|
288
291
|
let memoryPruneSharedWriteKey = null;
|
|
292
|
+
let localMemoryResultPath = null;
|
|
289
293
|
let executionWorkspace = workspace;
|
|
290
294
|
let localMemoryTelemetry = null;
|
|
291
295
|
const inheritedEnv = { ...process.env };
|
|
@@ -371,7 +375,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
371
375
|
nearBudget,
|
|
372
376
|
...(input.projectContext === undefined ? {} : { projectContext: input.projectContext }),
|
|
373
377
|
};
|
|
374
|
-
const systemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
375
378
|
if (input.attachments && input.attachments.length > 0) {
|
|
376
379
|
if (dependencies.cancellation?.isRequested())
|
|
377
380
|
throw new RuntimeCancelledError();
|
|
@@ -405,6 +408,29 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
405
408
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
406
409
|
const wakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
407
410
|
memoryPruneTraceId = parseMemoryPruneTraceId(wakePrompt);
|
|
411
|
+
if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
|
|
412
|
+
&& isLocalMemoryV2Eligible({
|
|
413
|
+
enabledForHandle: true,
|
|
414
|
+
agentHomePreexisted: workspace.agentHomePreexisted === true,
|
|
415
|
+
memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
|
|
416
|
+
localMemoryV2Initialized,
|
|
417
|
+
memoryPrune: true,
|
|
418
|
+
})) {
|
|
419
|
+
localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
|
|
420
|
+
}
|
|
421
|
+
const baseSystemPrompt = resolvePrompt(input.systemPrompt, promptContext);
|
|
422
|
+
const structuredMemoryInstructions = localMemoryResultPath === null
|
|
423
|
+
? null
|
|
424
|
+
: renderLocalMemoryResultInstructions(localMemoryResultPath);
|
|
425
|
+
const structuredPromptFits = structuredMemoryInstructions === null
|
|
426
|
+
|| input.maxSystemPromptBytes === undefined
|
|
427
|
+
|| Buffer.byteLength(baseSystemPrompt, "utf8") + Buffer.byteLength(structuredMemoryInstructions, "utf8")
|
|
428
|
+
<= input.maxSystemPromptBytes;
|
|
429
|
+
if (!structuredPromptFits)
|
|
430
|
+
localMemoryResultPath = null;
|
|
431
|
+
const systemPrompt = localMemoryResultPath === null
|
|
432
|
+
? baseSystemPrompt
|
|
433
|
+
: `${baseSystemPrompt}${structuredMemoryInstructions ?? ""}`;
|
|
408
434
|
try {
|
|
409
435
|
localMemoryTelemetry = createLocalMemoryTelemetry({
|
|
410
436
|
executionId: input.executionId,
|
|
@@ -513,7 +539,13 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
513
539
|
ability_release_id: input.abilityRelease?.releaseId,
|
|
514
540
|
ability_root_commit: input.abilityRelease?.rootCommit,
|
|
515
541
|
});
|
|
516
|
-
const
|
|
542
|
+
const warn = dependencies.warnRuntime ?? warnRuntimeLocally;
|
|
543
|
+
const launchRuntime = dependencies.launchRuntime ?? (async (launchReq) => {
|
|
544
|
+
if (launchReq.runtime === "codex" && launchReq.codexCollaborationTools === true) {
|
|
545
|
+
warn(CODEX_COLLABORATION_EXEC_WARNING);
|
|
546
|
+
}
|
|
547
|
+
return launchLegacyRuntime(launchReq);
|
|
548
|
+
});
|
|
517
549
|
if (dependencies.cancellation?.isRequested())
|
|
518
550
|
throw new RuntimeCancelledError();
|
|
519
551
|
let activeProjectSkillRuntimeRoot;
|
|
@@ -582,7 +614,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
582
614
|
}
|
|
583
615
|
else {
|
|
584
616
|
runtimeEnv = withoutClaudeInstructionFlag(childEnv);
|
|
585
|
-
(
|
|
617
|
+
warn(CLAUDE_INSTRUCTION_WARNING);
|
|
586
618
|
callbacks.onConsole?.({
|
|
587
619
|
stream: "system",
|
|
588
620
|
text: `[runtime-warning] ${CLAUDE_INSTRUCTION_WARNING}`,
|
|
@@ -606,6 +638,9 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
606
638
|
...(launchReasoning === undefined ? {} : { reasoning: launchReasoning }),
|
|
607
639
|
...(launchSessionId === null ? {} : { sessionId: launchSessionId }),
|
|
608
640
|
resume: resuming,
|
|
641
|
+
...(runtime.codexCollaborationTools === undefined
|
|
642
|
+
? {}
|
|
643
|
+
: { codexCollaborationTools: runtime.codexCollaborationTools }),
|
|
609
644
|
...(attachmentPlan.nativeImagePaths.length > 0
|
|
610
645
|
? { imagePaths: attachmentPlan.nativeImagePaths }
|
|
611
646
|
: {}),
|
|
@@ -892,6 +927,24 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
892
927
|
const { exitCode, spawnError, terminationSignal } = runtimeExit;
|
|
893
928
|
localMemoryTelemetry?.markRuntimeExited(exitCode);
|
|
894
929
|
memoryPruneRuntimeExitCode = exitCode;
|
|
930
|
+
if (localMemoryResultPath !== null) {
|
|
931
|
+
const committed = await commitLocalMemoryResultAfterExit({
|
|
932
|
+
resultPath: localMemoryResultPath,
|
|
933
|
+
agentDir: executionWorkspace.dir,
|
|
934
|
+
executionId: input.executionId,
|
|
935
|
+
pruneTraceId: memoryPruneTraceId,
|
|
936
|
+
exitCode,
|
|
937
|
+
});
|
|
938
|
+
if (committed.status === "failed") {
|
|
939
|
+
dslog("local_memory.commit_failed", "结构化本地记忆候选未落地,保留结果文件", {
|
|
940
|
+
level: "WARN",
|
|
941
|
+
execution_id: input.executionId,
|
|
942
|
+
agent_handle: input.handle,
|
|
943
|
+
error_code: committed.error,
|
|
944
|
+
result_path: localMemoryResultPath,
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
}
|
|
895
948
|
const errorTail = [
|
|
896
949
|
stderrTail.trim(),
|
|
897
950
|
spawnError === undefined
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const LOCAL_MEMORY_TOTAL_CODE_POINT_CAP = 6_000;
|
|
2
|
+
export const LOCAL_MEMORY_INDEX_CODE_POINT_CAP = 3_000;
|
|
3
|
+
export const LOCAL_MEMORY_INDEX_ENTRY_CAP = 20;
|
|
4
|
+
const PROJECTABLE_STATUSES = new Set(["pinned", "active"]);
|
|
5
|
+
function codePointLength(value) {
|
|
6
|
+
return Array.from(value).length;
|
|
7
|
+
}
|
|
8
|
+
export function renderLocalMemoryIndexCard(entry) {
|
|
9
|
+
return `### ${entry.title}\n`
|
|
10
|
+
+ `- id: ${entry.id}\n`
|
|
11
|
+
+ `- file: ${entry.file}\n`
|
|
12
|
+
+ `- type: ${entry.type}\n`
|
|
13
|
+
+ `- status: ${entry.status}\n`
|
|
14
|
+
+ `- summary: ${entry.summary}\n`
|
|
15
|
+
+ `- triggers: ${entry.triggers.join(", ")}\n`
|
|
16
|
+
+ `- priority: ${entry.priority}\n`
|
|
17
|
+
+ `- revision: ${entry.revision}\n`
|
|
18
|
+
+ `- anchor: ${entry.anchor ?? ""}\n`
|
|
19
|
+
+ `- updated: ${entry.updated}\n`
|
|
20
|
+
+ (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
|
|
21
|
+
}
|
|
22
|
+
function compareEntries(left, right) {
|
|
23
|
+
const statusRank = (status) => status === "pinned" ? 0 : 1;
|
|
24
|
+
const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
|
|
25
|
+
return statusRank(left.status) - statusRank(right.status)
|
|
26
|
+
|| priorityRank(left.priority) - priorityRank(right.priority)
|
|
27
|
+
|| right.updated.localeCompare(left.updated)
|
|
28
|
+
|| left.id.localeCompare(right.id);
|
|
29
|
+
}
|
|
30
|
+
function cardLength(entry, hasPrevious) {
|
|
31
|
+
return codePointLength(renderLocalMemoryIndexCard(entry)) + (hasPrevious ? 1 : 0);
|
|
32
|
+
}
|
|
33
|
+
function overBudgetReason(entries) {
|
|
34
|
+
return entries.some((entry) => entry.status === "pinned")
|
|
35
|
+
? "index_over_budget_pinned"
|
|
36
|
+
: "index_over_budget_active";
|
|
37
|
+
}
|
|
38
|
+
export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
39
|
+
const projectable = entries.filter((entry) => PROJECTABLE_STATUSES.has(entry.status));
|
|
40
|
+
const omittedIds = entries
|
|
41
|
+
.filter((entry) => !PROJECTABLE_STATUSES.has(entry.status))
|
|
42
|
+
.map((entry) => entry.id);
|
|
43
|
+
const protectedIds = options.protectedIds ?? new Set();
|
|
44
|
+
const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
|
|
45
|
+
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
|
|
46
|
+
const selected = [];
|
|
47
|
+
let renderedLength = 0;
|
|
48
|
+
for (const entry of protectedEntries) {
|
|
49
|
+
selected.push(entry);
|
|
50
|
+
renderedLength += cardLength(entry, selected.length > 1);
|
|
51
|
+
}
|
|
52
|
+
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
|
|
53
|
+
return {
|
|
54
|
+
entries: selected,
|
|
55
|
+
omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
|
|
56
|
+
reason: overBudgetReason(selected),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
for (const entry of candidates) {
|
|
60
|
+
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
|
|
61
|
+
omittedIds.push(entry.id);
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
selected.push(entry);
|
|
65
|
+
renderedLength += cardLength(entry, selected.length > 1);
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
entries: selected,
|
|
69
|
+
omittedIds,
|
|
70
|
+
reason: omittedIds.length === entries.filter((item) => !PROJECTABLE_STATUSES.has(item.status)).length
|
|
71
|
+
? null
|
|
72
|
+
: "index_over_budget",
|
|
73
|
+
};
|
|
74
|
+
}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, readdir, realpath, rename, rm, writeFile, readFile } from "node:fs/promises";
|
|
3
|
+
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
const CandidateSchema = z.object({
|
|
6
|
+
operation: z.enum(["create", "update", "supersede"]),
|
|
7
|
+
memoryId: z.string().nullable(),
|
|
8
|
+
baseRevision: z.number().int().nonnegative().nullable(),
|
|
9
|
+
supersedesMemoryId: z.string().nullable(),
|
|
10
|
+
title: z.string().min(1),
|
|
11
|
+
kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
|
|
12
|
+
summary: z.string().min(1),
|
|
13
|
+
triggers: z.array(z.string()).min(1),
|
|
14
|
+
priority: z.enum(["high", "medium", "low"]),
|
|
15
|
+
detailCategory: z.enum(["preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons"]),
|
|
16
|
+
detailBody: z.string().min(1),
|
|
17
|
+
reviewAfter: z.string().nullable(),
|
|
18
|
+
}).strict();
|
|
19
|
+
const PendingSchema = z.object({
|
|
20
|
+
schemaVersion: z.literal(1),
|
|
21
|
+
pendingId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
|
|
22
|
+
createdAt: z.string().min(1),
|
|
23
|
+
reason: z.literal("index_over_budget"),
|
|
24
|
+
source: z.object({
|
|
25
|
+
executionId: z.string().min(1),
|
|
26
|
+
pruneTraceId: z.string().min(1).nullable(),
|
|
27
|
+
}).strict(),
|
|
28
|
+
candidate: CandidateSchema,
|
|
29
|
+
}).strict();
|
|
30
|
+
function within(root, candidate) {
|
|
31
|
+
const path = relative(root, candidate);
|
|
32
|
+
return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
|
|
33
|
+
}
|
|
34
|
+
async function pendingDirectory(agentDir) {
|
|
35
|
+
const agentRoot = await realpath(agentDir);
|
|
36
|
+
const memoryRoot = resolve(agentRoot, ".crew", "memory");
|
|
37
|
+
await mkdir(memoryRoot, { recursive: true });
|
|
38
|
+
const memoryStat = await lstat(memoryRoot);
|
|
39
|
+
if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
40
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
41
|
+
const resolvedMemoryRoot = await realpath(memoryRoot);
|
|
42
|
+
if (resolvedMemoryRoot !== memoryRoot || !within(agentRoot, resolvedMemoryRoot)) {
|
|
43
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
44
|
+
}
|
|
45
|
+
const directory = join(memoryRoot, "pending");
|
|
46
|
+
try {
|
|
47
|
+
const stat = await lstat(directory);
|
|
48
|
+
if (!stat.isDirectory() || stat.isSymbolicLink())
|
|
49
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code !== "ENOENT")
|
|
53
|
+
throw error;
|
|
54
|
+
await mkdir(directory);
|
|
55
|
+
}
|
|
56
|
+
const resolvedDirectory = await realpath(directory);
|
|
57
|
+
if (resolvedDirectory !== directory || !within(agentRoot, resolvedDirectory)) {
|
|
58
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
59
|
+
}
|
|
60
|
+
return directory;
|
|
61
|
+
}
|
|
62
|
+
async function assertRegularFile(path) {
|
|
63
|
+
try {
|
|
64
|
+
const stat = await lstat(path);
|
|
65
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
66
|
+
throw new Error("local_memory_pending_path_invalid");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code !== "ENOENT")
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
async function atomicWrite(path, content) {
|
|
74
|
+
const temporary = join(dirname(path), `.${basename(path)}.tmp-${randomUUID()}`);
|
|
75
|
+
try {
|
|
76
|
+
await writeFile(temporary, content, { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
77
|
+
await rename(temporary, path);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function persistPendingMemoryCandidate(input) {
|
|
85
|
+
const parsed = PendingSchema.safeParse(input.candidate);
|
|
86
|
+
if (!parsed.success)
|
|
87
|
+
throw new Error("local_memory_pending_invalid");
|
|
88
|
+
const directory = await pendingDirectory(input.agentDir);
|
|
89
|
+
const target = join(directory, `${parsed.data.pendingId}.json`);
|
|
90
|
+
await assertRegularFile(target);
|
|
91
|
+
await atomicWrite(target, `${JSON.stringify(parsed.data, null, 2)}\n`);
|
|
92
|
+
return target;
|
|
93
|
+
}
|
|
94
|
+
export async function readPendingMemoryCandidates(input) {
|
|
95
|
+
const directory = await pendingDirectory(input.agentDir);
|
|
96
|
+
const names = (await readdir(directory)).filter((name) => name.endsWith(".json")).sort();
|
|
97
|
+
const result = [];
|
|
98
|
+
for (const name of names) {
|
|
99
|
+
const path = join(directory, name);
|
|
100
|
+
try {
|
|
101
|
+
await assertRegularFile(path);
|
|
102
|
+
const parsed = PendingSchema.safeParse(JSON.parse(await readFile(path, "utf8")));
|
|
103
|
+
if (parsed.success && `${parsed.data.pendingId}.json` === name)
|
|
104
|
+
result.push(parsed.data);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
// A damaged pending item is diagnosable and must not hide other candidates.
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { applyLocalMemoryResult, parseLocalMemoryResult } from "./write.js";
|
|
5
|
+
export function isLocalMemoryV2Eligible(input) {
|
|
6
|
+
return input.enabledForHandle
|
|
7
|
+
&& (input.localMemoryV2Initialized || (!input.agentHomePreexisted && input.memorySeedCreated))
|
|
8
|
+
&& input.memoryPrune;
|
|
9
|
+
}
|
|
10
|
+
const LOCAL_MEMORY_V2_MARKER = "v2-enabled";
|
|
11
|
+
export async function initializeLocalMemoryV2(agentDir) {
|
|
12
|
+
const crewDir = join(agentDir, ".crew");
|
|
13
|
+
const memoryDir = join(crewDir, "memory");
|
|
14
|
+
await mkdir(crewDir, { recursive: true });
|
|
15
|
+
const crewStat = await lstat(crewDir);
|
|
16
|
+
if (!crewStat.isDirectory() || crewStat.isSymbolicLink())
|
|
17
|
+
throw new Error("local_memory_marker_path_invalid");
|
|
18
|
+
await mkdir(memoryDir, { recursive: true });
|
|
19
|
+
const memoryStat = await lstat(memoryDir);
|
|
20
|
+
if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
|
|
21
|
+
throw new Error("local_memory_marker_path_invalid");
|
|
22
|
+
try {
|
|
23
|
+
await writeFile(join(memoryDir, LOCAL_MEMORY_V2_MARKER), "1\n", { encoding: "utf8", mode: 0o600, flag: "wx" });
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (error.code !== "EEXIST")
|
|
27
|
+
throw error;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle) {
|
|
31
|
+
const initialized = workspace.localMemoryV2Initialized === true;
|
|
32
|
+
if (!enabledForHandle || initialized || workspace.localMemoryV2Initialized !== false
|
|
33
|
+
|| workspace.agentHomePreexisted === true || workspace.memorySeedCreated !== true)
|
|
34
|
+
return initialized;
|
|
35
|
+
try {
|
|
36
|
+
await initializeLocalMemoryV2(workspace.dir);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
export function localMemoryResultPath(runDir, executionId) {
|
|
44
|
+
const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
|
|
45
|
+
return join(runDir, `.memory-result.v1-${suffix}.json`);
|
|
46
|
+
}
|
|
47
|
+
export function renderLocalMemoryResultInstructions(resultPath) {
|
|
48
|
+
return `
|
|
49
|
+
|
|
50
|
+
## Structured memory prune (daemon-managed)
|
|
51
|
+
本轮长期记忆收尾只提交候选,不要直接编辑 $CREW_HOME/MEMORY.md 或 $CREW_HOME/notes/。
|
|
52
|
+
将一个合法 JSON 对象写入以下 daemon 提供的文件,写入完成后再结束本轮:
|
|
53
|
+
${resultPath}
|
|
54
|
+
|
|
55
|
+
JSON 顶层必须包含:
|
|
56
|
+
{"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"no_change","changes":[],"statusChanges":[]}
|
|
57
|
+
每个 changes 项使用 operation=create|update|supersede、title、kind、summary、triggers、priority、detailCategory、detailBody、reviewAfter;create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null,update/supersede 按现有索引填写版本和 id。
|
|
58
|
+
只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 中等待后续治理。
|
|
59
|
+
`;
|
|
60
|
+
}
|
|
61
|
+
export async function commitLocalMemoryResultAfterExit(input) {
|
|
62
|
+
if (input.exitCode !== 0)
|
|
63
|
+
return { status: "skipped" };
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = await readFile(input.resultPath, "utf8");
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (error.code === "ENOENT")
|
|
70
|
+
return { status: "missing" };
|
|
71
|
+
return { status: "failed", error: error instanceof Error ? error.message : "read_failed" };
|
|
72
|
+
}
|
|
73
|
+
try {
|
|
74
|
+
const result = parseLocalMemoryResult(raw);
|
|
75
|
+
await applyLocalMemoryResult({
|
|
76
|
+
agentDir: input.agentDir,
|
|
77
|
+
result,
|
|
78
|
+
executionId: input.executionId,
|
|
79
|
+
pruneTraceId: input.pruneTraceId,
|
|
80
|
+
});
|
|
81
|
+
await rm(input.resultPath, { force: true });
|
|
82
|
+
return { status: "applied" };
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
return { status: "failed", error: error instanceof Error ? error.message : "apply_failed" };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|