@nowcrew/daemon 0.6.99-beta.1 → 0.6.99-beta.2
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/dist/cloud-report-upload.js +10 -1
- 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/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 +1 -1
- 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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
2
|
import { homedir, hostname, platform } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
3
4
|
const ALIYUN_MARKER = /alibaba|aliyun|alicloud/iu;
|
|
4
5
|
function readMarker(path) {
|
|
5
6
|
try {
|
|
@@ -28,7 +29,15 @@ export function detectCloudProvider(env = process.env, platformName = platform()
|
|
|
28
29
|
}
|
|
29
30
|
export function findOssUploadScript(env = process.env, exists = existsSync) {
|
|
30
31
|
const explicit = env.CREW_OSS_UPLOAD_SCRIPT?.trim();
|
|
31
|
-
|
|
32
|
+
if (explicit)
|
|
33
|
+
return exists(explicit) ? explicit : undefined;
|
|
34
|
+
const home = env.HOME?.trim() || homedir();
|
|
35
|
+
const candidates = [
|
|
36
|
+
join(home, ".claude", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
37
|
+
join(home, ".codex", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
38
|
+
join(home, ".agents", "skills", "oss-upload", "scripts", "oss_upload.py"),
|
|
39
|
+
];
|
|
40
|
+
return candidates.find(exists);
|
|
32
41
|
}
|
|
33
42
|
export function cloudReportUploadFacts(env = process.env, platformName = platform(), hostName = hostname(), readMarkerValue, exists) {
|
|
34
43
|
const provider = detectCloudProvider(env, platformName, hostName, readMarkerValue);
|
package/dist/computer-service.js
CHANGED
|
@@ -12,6 +12,8 @@ import { powershellCommandArgs } from "./powershell-command.js";
|
|
|
12
12
|
const execFileAsync = promisify(execFile);
|
|
13
13
|
const WINDOWS_STOP_TIMEOUT_MS = 5_000;
|
|
14
14
|
const WINDOWS_STOP_POLL_MS = 100;
|
|
15
|
+
const DARWIN_STOP_TIMEOUT_MS = 30_000;
|
|
16
|
+
const DARWIN_STOP_POLL_MS = 100;
|
|
15
17
|
/** Windows cannot spawn .cmd/.bat shims directly with shell:false; route them through cmd.exe explicitly. */
|
|
16
18
|
export function systemCommandInvocation(command, args, platform = process.platform, comSpec = process.env.ComSpec ?? process.env.COMSPEC ?? "cmd.exe") {
|
|
17
19
|
if (platform === "win32" && /\.(?:cmd|bat)$/iu.test(command)) {
|
|
@@ -186,6 +188,32 @@ async function checked(runner, command, args, action) {
|
|
|
186
188
|
}
|
|
187
189
|
return result;
|
|
188
190
|
}
|
|
191
|
+
async function stopLaunchdService(spec, initial, runner) {
|
|
192
|
+
const assertQueryable = (result) => {
|
|
193
|
+
// launchctl uses 113 for an absent service. Other failures cannot prove it stopped.
|
|
194
|
+
if (result.exitCode !== 0 && result.exitCode !== 113) {
|
|
195
|
+
throw new Error(`launchd status failed: ${result.stderr.trim() || result.stdout.trim() || `exit ${result.exitCode}`}`);
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
assertQueryable(initial);
|
|
199
|
+
if (initial.exitCode === 113)
|
|
200
|
+
return;
|
|
201
|
+
const target = `${spec.managerDomain}/${spec.id}`;
|
|
202
|
+
await checked(runner, "launchctl", ["bootout", target], "launchd stop");
|
|
203
|
+
const deadline = Date.now() + DARWIN_STOP_TIMEOUT_MS;
|
|
204
|
+
while (true) {
|
|
205
|
+
const current = await runner("launchctl", ["print", target]);
|
|
206
|
+
assertQueryable(current);
|
|
207
|
+
if (current.exitCode === 113)
|
|
208
|
+
return;
|
|
209
|
+
// bootout returns while the job is SIGTERMed and still owns its installation lease.
|
|
210
|
+
// Wait for removal, not merely a non-running state, before installing or bootstrapping.
|
|
211
|
+
if (Date.now() >= deadline) {
|
|
212
|
+
throw new Error(`launchd service '${spec.id}' did not unload within ${DARWIN_STOP_TIMEOUT_MS}ms`);
|
|
213
|
+
}
|
|
214
|
+
await new Promise((resolve) => setTimeout(resolve, DARWIN_STOP_POLL_MS));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
189
217
|
function sameWindowsPath(left, right) {
|
|
190
218
|
return left !== undefined
|
|
191
219
|
&& left.replaceAll("/", "\\").toLowerCase() === right.replaceAll("/", "\\").toLowerCase();
|
|
@@ -356,9 +384,7 @@ export async function uninstallService(spec, runner = systemCommandRunner) {
|
|
|
356
384
|
}
|
|
357
385
|
if (spec.platform === "darwin") {
|
|
358
386
|
const status = await serviceStatus(spec, runner);
|
|
359
|
-
|
|
360
|
-
await checked(runner, "launchctl", ["bootout", `${spec.managerDomain}/${spec.id}`], "launchd bootout");
|
|
361
|
-
}
|
|
387
|
+
await stopLaunchdService(spec, status, runner);
|
|
362
388
|
}
|
|
363
389
|
else {
|
|
364
390
|
const disable = await runner("systemctl", ["--user", "disable", "--now", spec.id]);
|
|
@@ -385,8 +411,7 @@ export async function serviceAction(spec, action, runner = systemCommandRunner)
|
|
|
385
411
|
await checked(runner, "launchctl", ["bootstrap", spec.managerDomain, spec.descriptorPath], "launchd start");
|
|
386
412
|
}
|
|
387
413
|
else if (action === "stop") {
|
|
388
|
-
|
|
389
|
-
await checked(runner, "launchctl", ["bootout", target], "launchd stop");
|
|
414
|
+
await stopLaunchdService(spec, status, runner);
|
|
390
415
|
}
|
|
391
416
|
else {
|
|
392
417
|
if (status.loaded)
|
|
@@ -8,7 +8,7 @@ import { provePosixProcessGroupStopped } from "./execution-posix-stop-proof.js";
|
|
|
8
8
|
export { JournalLockedError, JournalLockCorruptionError } from "./execution-journal-lock.js";
|
|
9
9
|
const ExecutionIdSchema = z.string().uuid();
|
|
10
10
|
const TimestampSchema = z.string().datetime({ offset: true });
|
|
11
|
-
const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "
|
|
11
|
+
const RuntimeSchema = z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness", "pi"]);
|
|
12
12
|
const RUNTIME_READY_SUFFIX = ".runtime-ready";
|
|
13
13
|
const RawJournalEntrySchema = z.object({
|
|
14
14
|
executionId: ExecutionIdSchema,
|
|
@@ -523,8 +523,6 @@ export function createExecutionJournal(agentsRoot, options = {}) {
|
|
|
523
523
|
}
|
|
524
524
|
};
|
|
525
525
|
const pruneInternal = async () => {
|
|
526
|
-
if (options.retainCompleted)
|
|
527
|
-
return;
|
|
528
526
|
const protectedExecutionIds = new Set(options.protectedExecutionIds?.() ?? []);
|
|
529
527
|
const entries = await readAll();
|
|
530
528
|
const acknowledged = entries
|
|
@@ -319,7 +319,7 @@ const RawExecutionCompletedSchema = z.object({
|
|
|
319
319
|
providerErrorCategory: ProviderErrorCategorySchema.optional(),
|
|
320
320
|
providerHttpStatus: ProviderHttpStatusSchema.optional(),
|
|
321
321
|
providerErrorCode: ProviderErrorCodeSchema.optional(),
|
|
322
|
-
runtime:
|
|
322
|
+
runtime: RuntimeSchema,
|
|
323
323
|
model: z.string().optional(),
|
|
324
324
|
resumed: z.boolean(),
|
|
325
325
|
finalText: z.string().optional(),
|
package/dist/execution-runner.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { dirname, join, resolve } from "node:path";
|
|
3
3
|
import { isLocalMemoryV2Enabled, localMemoryV2Environment, resolveReposRoot } from "./config.js";
|
|
4
|
-
import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
|
|
5
4
|
import { DaemonToServerExecutionFrameSchema, ExecutionCompletedSchema, ExecutionRejectedSchema, ExecutionStartSchema, } from "./execution-protocol.js";
|
|
6
5
|
import { JournalConflictError } from "./execution-journal.js";
|
|
7
6
|
import { boundExecutionFrame } from "./execution-event-limit.js";
|
|
@@ -847,9 +846,8 @@ export async function runExecution(config, input, dependencies) {
|
|
|
847
846
|
const localMemoryGuardInBase = localMemoryV2Environment(config.serverUrl) === "custom";
|
|
848
847
|
const boundedSystemPrompt = (context) => {
|
|
849
848
|
const enabled = context.workspace.localMemoryV2Enabled ?? localMemoryV2Enabled;
|
|
850
|
-
const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase
|
|
849
|
+
const systemPromptWithLocalFacts = withLocalExecutionFacts(`${spec.instructions.systemPrompt}${enabled && localMemoryGuardInBase ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
|
|
851
850
|
...(selfTraining ? { isolateLocalMemory: true } : {}),
|
|
852
|
-
memoryQuery: spec.instructions.wakePrompt,
|
|
853
851
|
collaborationTools: config.codexCollaborationTools && spec.runtime.name === "codex",
|
|
854
852
|
});
|
|
855
853
|
const base = typeof systemPromptWithLocalFacts === "string"
|
|
@@ -889,7 +887,6 @@ export async function runExecution(config, input, dependencies) {
|
|
|
889
887
|
serverUrl: config.serverUrl,
|
|
890
888
|
token: credential.token,
|
|
891
889
|
agentId: credential.agentId,
|
|
892
|
-
...(dependencies.controlIdentity === undefined ? {} : dependencies.controlIdentity),
|
|
893
890
|
agentsRoot: config.agentsRoot,
|
|
894
891
|
reposRoot: resolveReposRoot(config.agentsRoot, config.reposRoot),
|
|
895
892
|
...(config.qaReportRoot === undefined ? {} : { qaReportRoot: config.qaReportRoot }),
|
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* 本机执行提示词的合成与预算裁剪。
|
|
3
3
|
*
|
|
4
4
|
* server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
|
|
5
|
-
*
|
|
5
|
+
* 只有本机事实(CREW_HOME / work-log / MEMORY 注入)是可选增强,按整段丢弃或按字节截断。
|
|
6
6
|
*/
|
|
7
|
-
import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
|
|
8
7
|
import { buildCollaborationToolsPrompt, buildRuntimeWorkspacePrompt, capMemoryForInject, capWorkLogForInject, } from "./prompt.js";
|
|
9
8
|
const trainingProjectJson = (project) => JSON.stringify({
|
|
10
9
|
repositoryKey: project.repositoryKey,
|
|
@@ -58,14 +57,10 @@ export function withLocalExecutionFacts(serverPrompt, maxBytes, options = {}) {
|
|
|
58
57
|
}
|
|
59
58
|
const remaining = maxBytes - requiredBytes - Math.max(0, reservedPromptBytes);
|
|
60
59
|
const factsBytes = Buffer.byteLength(localFacts, "utf8");
|
|
61
|
-
if (factsBytes > remaining)
|
|
62
|
-
if (!options.isolateLocalMemory && detectLocalMemoryStorageVersion(workspace.memory) === 2) {
|
|
63
|
-
throw new Error("local_memory_identity_budget_exceeded");
|
|
64
|
-
}
|
|
60
|
+
if (factsBytes > remaining)
|
|
65
61
|
return `${serverPrompt}${collaborationPrompt}${mandatoryWorkspacePrompt}`;
|
|
66
|
-
}
|
|
67
62
|
const memoryHeading = "\n\n## Injected MEMORY.md (bounded local context)\n";
|
|
68
|
-
const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8")
|
|
63
|
+
const memory = options.isolateLocalMemory ? "" : capMemoryForInject(workspace.memory, undefined, remaining - factsBytes - Buffer.byteLength(memoryHeading, "utf8"));
|
|
69
64
|
const memorySection = memory === "" ? "" : memoryHeading + memory;
|
|
70
65
|
const workLogSection = workLog === "" ? "" : `\n\n## Injected work-log (bounded local context)\n${workLog}`;
|
|
71
66
|
const workLogBudget = remaining - factsBytes - Buffer.byteLength(memorySection, "utf8");
|
package/dist/local-executor.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createInterface } from "node:readline";
|
|
|
6
6
|
import { readFile, rm, writeFile } from "node:fs/promises";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
import { cleanupSelfTrainingRunDir } from "./self-training-workspace.js";
|
|
9
|
-
import { rotateAgentSession, safeKey, } from "./workspace.js";
|
|
9
|
+
import { prepareWorkspace, rotateAgentSession, safeKey, } from "./workspace.js";
|
|
10
10
|
import { CLAUDE_ADDITIONAL_DIRECTORY_INSTRUCTIONS_ENV, isClaudeAdditionalDirectoryInstructionsSupported, probeClaudeVersion, spawnClaude, } from "./runtimes/claude.js";
|
|
11
11
|
import { spawnCodex } from "./runtimes/codex.js";
|
|
12
12
|
import { DEEPSEEK_CODEX_MODEL, DEEPSEEK_CODEX_REASONING_LEVELS, materializeDeepSeekCodexHome, } from "./runtimes/codex-deepseek-config.js";
|
|
@@ -39,20 +39,13 @@ import { projectSkillRuntimeDirectories, } from "./project-skills/runtime-launch
|
|
|
39
39
|
import { logIdentityRosterRuntimePrepared } from "./identity-roster-telemetry.js";
|
|
40
40
|
import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, logMemoryPruneDiagnosticsFailure, memoryPruneSnapshotFields, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
|
|
41
41
|
import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
|
|
42
|
-
import { LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS } from "./local-memory-diagnostics.js";
|
|
43
42
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
44
43
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
45
44
|
import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
|
|
46
|
-
import { commitLocalMemoryResultAfterExit, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
|
|
45
|
+
import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
|
|
47
46
|
import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
|
|
48
|
-
import { reportLocalMemoryV2Deferred } from "./local-memory/rollout.js";
|
|
49
|
-
import { prepareExecutionMemoryWorkspace } from "./local-memory/execution-workspace.js";
|
|
47
|
+
import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
|
|
50
48
|
import { localMemoryV2Environment } from "./config.js";
|
|
51
|
-
import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
|
|
52
|
-
import { MEMORY_STATE_GUARD_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY } from "./memory-migration-protocol.js";
|
|
53
|
-
import { resolveLocalMemoryWriterMode } from "./local-memory/writer-mode.js";
|
|
54
|
-
import { logLocalMemoryCommitApplied } from "./local-memory/commit-telemetry.js";
|
|
55
|
-
import { shouldUseMigrationDeferredPending } from "./local-memory/migration-pending.js";
|
|
56
49
|
export class RuntimeRunningCallbackTimeoutError extends Error {
|
|
57
50
|
constructor(runtime, timeoutMs) {
|
|
58
51
|
super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
|
|
@@ -66,6 +59,7 @@ const STDERR_TAIL_CAP = 2_000;
|
|
|
66
59
|
// the old failure mode where a callback could hold execution slots forever.
|
|
67
60
|
const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
|
|
68
61
|
const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
62
|
+
const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
|
|
69
63
|
const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
|
|
70
64
|
const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
|
|
71
65
|
function withoutClaudeInstructionFlag(env) {
|
|
@@ -217,12 +211,37 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
217
211
|
: "high"
|
|
218
212
|
: runtime.reasoning;
|
|
219
213
|
const providerFp = providerFingerprint(runtime.name, providerConfig);
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
214
|
+
let workspace;
|
|
215
|
+
try {
|
|
216
|
+
workspace = await awaitWithCancellation(prepareWorkspace({
|
|
217
|
+
agentsRoot: input.launch.agentsRoot,
|
|
218
|
+
handle: input.handle,
|
|
219
|
+
cliPath: input.launch.cliPath,
|
|
220
|
+
executionId: input.executionId,
|
|
221
|
+
...(input.keyMode === undefined ? {} : { keyMode: input.keyMode }),
|
|
222
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
223
|
+
...(input.resumeKey === undefined ? {} : { resumeKey: input.resumeKey }),
|
|
224
|
+
...(input.sessionContextFingerprint === undefined
|
|
225
|
+
? {}
|
|
226
|
+
: { sessionContextFingerprint: input.sessionContextFingerprint }),
|
|
227
|
+
...(input.launch.description ? { description: input.launch.description } : {}),
|
|
228
|
+
...(isolatedMaintenance ? { skipLocalMemory: true } : {}),
|
|
229
|
+
}), dependencies.cancellation);
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
logLocalMemoryContextPrepareFailure({
|
|
233
|
+
executionId: input.executionId,
|
|
234
|
+
agentHandle: input.handle,
|
|
235
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
236
|
+
}, "workspace_prepare", error);
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
const memoryRollout = await resolveLocalMemoryV2Rollout({ workspace, handle: input.handle,
|
|
240
|
+
serverUrl: input.launch.serverUrl, requested: input.localMemoryV2Enabled === true,
|
|
241
|
+
selfTraining: isolatedMaintenance, ...(input.launch.description ? { description: input.launch.description } : {}) });
|
|
242
|
+
workspace = { ...workspace, localMemoryV2Enabled: memoryRollout.enabled };
|
|
223
243
|
input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
|
|
224
|
-
const localMemoryV2Initialized =
|
|
225
|
-
let localMemoryWriterMode = resolveLocalMemoryWriterMode(workspace, memoryRollout);
|
|
244
|
+
const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
|
|
226
245
|
let materialized = null;
|
|
227
246
|
let knownAttachmentDirectory = null;
|
|
228
247
|
const startupReservationState = { current: null };
|
|
@@ -233,7 +252,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
233
252
|
let memoryPruneBeforeSnapshot = null;
|
|
234
253
|
let memoryPruneSharedWriteKey = null;
|
|
235
254
|
let localMemoryResultPath = null;
|
|
236
|
-
let migrationDeferredPending = false;
|
|
237
255
|
let environmentSnapshot;
|
|
238
256
|
let executionWorkspace = workspace;
|
|
239
257
|
let localMemoryTelemetry = null;
|
|
@@ -372,41 +390,29 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
372
390
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
373
391
|
let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
374
392
|
memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
writerMode: localMemoryWriterMode, rolloutEnabled: memoryRollout.enabled,
|
|
378
|
-
legacyFallback: memoryRollout.legacyFallback,
|
|
379
|
-
failure: memoryRollout.failure, memory: executionWorkspace.memory });
|
|
380
|
-
if (memoryRollout.deferred || migrationDeferredPending)
|
|
381
|
-
reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune", memoryRollout.legacyFallback, memoryRollout.failure, migrationDeferredPending);
|
|
382
|
-
if (memoryPruneTraceId !== null && localMemoryWriterMode === "blocked" && !migrationDeferredPending) {
|
|
383
|
-
throw new Error(memoryRollout.failure === "inspection_failed" ? "local_memory_inspection_failed" : "local_memory_v2_requires_migration");
|
|
384
|
-
}
|
|
393
|
+
if (memoryRollout.deferred)
|
|
394
|
+
reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
|
|
385
395
|
if (memoryPruneTraceId !== null) {
|
|
386
396
|
executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
|
|
387
397
|
promptContext = { ...promptContext, workspace: executionWorkspace };
|
|
388
398
|
baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
389
399
|
}
|
|
390
|
-
if (
|
|
400
|
+
if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
|
|
391
401
|
&& isLocalMemoryV2Eligible({
|
|
392
402
|
enabledForHandle: true,
|
|
393
403
|
agentHomePreexisted: workspace.agentHomePreexisted === true,
|
|
394
404
|
memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
|
|
395
405
|
localMemoryV2Initialized,
|
|
396
406
|
memoryPrune: true,
|
|
397
|
-
}))
|
|
407
|
+
})) {
|
|
398
408
|
localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
|
|
399
409
|
}
|
|
400
410
|
const memoryPrompt = await prepareLocalMemoryPrompt({
|
|
401
411
|
agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
|
|
402
412
|
enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
|
|
403
413
|
resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
|
|
404
|
-
|
|
405
|
-
strictRollout: !isolatedMaintenance && !memoryRollout.legacyFallback && (localMemoryV2Environment(input.launch.serverUrl) === "dev"
|
|
406
|
-
|| detectLocalMemoryStorageVersion(executionWorkspace.memory) === 2),
|
|
414
|
+
strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
|
|
407
415
|
deferred: memoryRollout.deferred,
|
|
408
|
-
...(memoryRollout.legacyFallback ? { legacyFallback: true } : {}),
|
|
409
|
-
...(migrationDeferredPending ? { migrationDeferred: true } : {}),
|
|
410
416
|
renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
|
|
411
417
|
});
|
|
412
418
|
localMemoryResultPath = memoryPrompt.resultPath;
|
|
@@ -522,7 +528,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
522
528
|
localMemoryTelemetry?.contextPrepared({
|
|
523
529
|
systemPrompt: effectiveSystemPrompt,
|
|
524
530
|
memory: executionWorkspace.memory,
|
|
525
|
-
memoryQuery: baseWakePrompt,
|
|
526
531
|
resumed: resuming,
|
|
527
532
|
...(executionWorkspace.memorySeedCreated === undefined
|
|
528
533
|
? {}
|
|
@@ -904,11 +909,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
904
909
|
exitCode,
|
|
905
910
|
workLogPath: executionWorkspace.workLogPath,
|
|
906
911
|
expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
|
|
907
|
-
...(localMemoryWriterMode === "v2" ? {
|
|
908
|
-
writerCapabilities: [MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY],
|
|
909
|
-
requiredStorageVersion: 2,
|
|
910
|
-
} : {}),
|
|
911
|
-
...(migrationDeferredPending ? { migrationDeferredPending: true } : {}),
|
|
912
912
|
});
|
|
913
913
|
const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
|
|
914
914
|
if (committed.status === "failed" || committed.status === "missing") {
|
|
@@ -922,14 +922,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
922
922
|
});
|
|
923
923
|
throw new Error(`local_memory_commit_failed: ${reason}`);
|
|
924
924
|
}
|
|
925
|
-
if (committed.status === "applied") {
|
|
926
|
-
if (localMemoryWriterMode === "v2" && (committed.storageVersionBefore !== 2 || committed.storageVersionAfter !== 2
|
|
927
|
-
|| committed.committedState !== true)) {
|
|
928
|
-
throw new Error("local_memory_commit_failed: local_memory_v2_commit_state_invalid");
|
|
929
|
-
}
|
|
930
|
-
logLocalMemoryCommitApplied({ executionId: input.executionId, agentHandle: input.handle,
|
|
931
|
-
pruneTraceId: memoryPruneTraceId, committed });
|
|
932
|
-
}
|
|
933
925
|
memoryPruneFailurePhase = "runtime_finalize";
|
|
934
926
|
}
|
|
935
927
|
const errorTail = [
|
|
@@ -1060,23 +1052,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
1060
1052
|
after_incomplete_file_count: noteDiff.after_manifest.incomplete_file_count,
|
|
1061
1053
|
changes: serializedChanges.json,
|
|
1062
1054
|
});
|
|
1063
|
-
const pendingDiff = diffMemoryPruneNotes(memoryPruneBeforeSnapshot.pending_manifest
|
|
1064
|
-
?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, snapshot.pending_manifest
|
|
1065
|
-
?? { files: [], total_count: 0, omitted_count: 0, truncated: false });
|
|
1066
|
-
const serializedPendingChanges = boundedDiagnosticJsonArray(pendingDiff.changes);
|
|
1067
|
-
const pendingChangesTruncated = pendingDiff.truncated || serializedPendingChanges.truncated;
|
|
1068
|
-
dslog("memory_prune.pending_changed", "长期记忆 pending 候选变化已评估", {
|
|
1069
|
-
execution_id: input.executionId,
|
|
1070
|
-
agent_handle: input.handle,
|
|
1071
|
-
task_key: input.taskKey,
|
|
1072
|
-
prune_trace_id: memoryPruneTraceId,
|
|
1073
|
-
change_count: pendingDiff.change_count,
|
|
1074
|
-
changes_truncated: pendingChangesTruncated,
|
|
1075
|
-
evidence_complete: pendingDiff.evidence_complete && !pendingChangesTruncated,
|
|
1076
|
-
before_total_count: pendingDiff.before_manifest.total_count,
|
|
1077
|
-
after_total_count: pendingDiff.after_manifest.total_count,
|
|
1078
|
-
changes: serializedPendingChanges.json,
|
|
1079
|
-
});
|
|
1080
1055
|
const postcondition = evaluateMemoryPrunePostcondition(memoryPruneBeforeSnapshot, snapshot, {
|
|
1081
1056
|
executorCompleted: memoryPruneExecutorCompleted,
|
|
1082
1057
|
...(memoryPruneRuntimeExitCode === undefined
|
|
@@ -50,17 +50,14 @@ export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
|
50
50
|
.map((entry) => entry.id);
|
|
51
51
|
const protectedIds = options.protectedIds ?? new Set();
|
|
52
52
|
const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
|
|
53
|
-
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(
|
|
53
|
+
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
|
|
54
54
|
const selected = [];
|
|
55
55
|
let renderedLength = 0;
|
|
56
|
-
let renderedBytes = 0;
|
|
57
56
|
for (const entry of protectedEntries) {
|
|
58
57
|
selected.push(entry);
|
|
59
58
|
renderedLength += cardLength(entry, selected.length > 1);
|
|
60
|
-
renderedBytes += Buffer.byteLength(renderLocalMemoryIndexCard(entry), "utf8") + (selected.length > 1 ? 1 : 0);
|
|
61
59
|
}
|
|
62
|
-
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap
|
|
63
|
-
|| renderedBytes > (budget.indexByteCap ?? Infinity)) {
|
|
60
|
+
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
|
|
64
61
|
return {
|
|
65
62
|
entries: selected,
|
|
66
63
|
omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
|
|
@@ -68,15 +65,12 @@ export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
|
68
65
|
};
|
|
69
66
|
}
|
|
70
67
|
for (const entry of candidates) {
|
|
71
|
-
|
|
72
|
-
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap
|
|
73
|
-
|| renderedBytes + bytes > (budget.indexByteCap ?? Infinity)) {
|
|
68
|
+
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
|
|
74
69
|
omittedIds.push(entry.id);
|
|
75
70
|
continue;
|
|
76
71
|
}
|
|
77
72
|
selected.push(entry);
|
|
78
73
|
renderedLength += cardLength(entry, selected.length > 1);
|
|
79
|
-
renderedBytes += bytes;
|
|
80
74
|
}
|
|
81
75
|
return {
|
|
82
76
|
entries: selected,
|
|
@@ -2,9 +2,9 @@ const CommandSchema = z.object({
|
|
|
2
2
|
"agent-dir": z.string().min(1).refine(isAbsolute),
|
|
3
3
|
"dry-run": z.literal(true),
|
|
4
4
|
}).strict();
|
|
5
|
-
const USAGE = "
|
|
5
|
+
const USAGE = "只读记忆盘点与转换预演,不写文件,不支持 --apply。\n"
|
|
6
6
|
+ "pnpm --filter @nowcrew/daemon exec tsx src/local-memory/inventory-cli.ts --agent-dir <单个Agent绝对路径> --dry-run\n"
|
|
7
|
-
+ "报告包含原始记忆,需作为敏感本地资料保存;退出码0
|
|
7
|
+
+ "报告包含原始记忆,需作为敏感本地资料保存;退出码0仅代表盘点及格式预演无阻塞,不代表批准迁移。\n";
|
|
8
8
|
export async function runMemoryInventoryCommand(args, io) {
|
|
9
9
|
if (args.length === 1 && args[0] === "--help") {
|
|
10
10
|
io.out(USAGE);
|
|
@@ -24,60 +24,19 @@ export async function runMemoryInventoryCommand(args, io) {
|
|
|
24
24
|
try {
|
|
25
25
|
const scan = await scanMemorySources(command["agent-dir"]);
|
|
26
26
|
const scanComplete = scan.issues.length === 0;
|
|
27
|
-
const
|
|
28
|
-
let storageVersion = null;
|
|
29
|
-
let detectionIssue = null;
|
|
30
|
-
try {
|
|
31
|
-
storageVersion = detectLocalMemoryStorageVersion(memory);
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
detectionIssue = {
|
|
35
|
-
code: error instanceof Error && /^[a-z0-9_]+$/u.test(error.message)
|
|
36
|
-
? error.message
|
|
37
|
-
: "local_memory_storage_version_invalid",
|
|
38
|
-
path: "MEMORY.md",
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
const v2 = storageVersion === 2
|
|
42
|
-
? await inspectV2MemoryInventory(scan.root, scan.sources, scanComplete)
|
|
43
|
-
: null;
|
|
44
|
-
const analysis = detectionIssue
|
|
45
|
-
? { entries: [], issues: [detectionIssue], preview: null, rawMemory: memory }
|
|
46
|
-
: v2?.analysis ?? previewMemoryInventory(scan.sources, scanComplete);
|
|
27
|
+
const analysis = previewMemoryInventory(scan.sources, scanComplete);
|
|
47
28
|
const complete = scanComplete && !analysis.issues.some((issue) => issue.code === "missing_detail");
|
|
48
29
|
const issues = [...scan.issues, ...analysis.issues];
|
|
49
|
-
const referenced = new Set(
|
|
50
|
-
|
|
51
|
-
...(v2?.preservedSourcePaths ?? []),
|
|
52
|
-
]);
|
|
53
|
-
const reportSources = v2?.stateSource ? [...scan.sources, v2.stateSource] : scan.sources;
|
|
54
|
-
const files = reportSources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
|
|
55
|
-
const deferredFiles = files.filter((file) => isDeferredMemoryArtifactPath(file.path)).map((file) => file.path);
|
|
56
|
-
const orphanNotes = files.filter((file) => file.path.startsWith("notes/") && !isDeferredMemoryArtifactPath(file.path) && !referenced.has(file.path)).map((file) => file.path);
|
|
57
|
-
const indexedEntries = analysis.entries.filter((entry) => entry.format === "managed").length;
|
|
58
|
-
const legacyEntries = analysis.entries.length - indexedEntries;
|
|
30
|
+
const referenced = new Set(analysis.entries.map((entry) => entry.sourceFields.file));
|
|
31
|
+
const files = scan.sources.map(({ path, bytes, sha256 }) => ({ path, bytes, sha256 }));
|
|
59
32
|
const report = {
|
|
60
33
|
schemaVersion: 1, mode: "dry-run", agentDir: scan.root, applySupported: false,
|
|
61
34
|
complete, scanComplete, requiresHumanReview: true,
|
|
62
|
-
|
|
63
|
-
// A dry-run does not create a shadow index or server state. Existing
|
|
64
|
-
// schema1 cards therefore remain legacy; indexedEntries is only coverage
|
|
65
|
-
// information, not proof that the Agent entered shadow mode.
|
|
66
|
-
storageMode: storageVersion === 2 ? (v2?.writeReady ? "v2_write" : "repair_required")
|
|
67
|
-
: detectionIssue ? "repair_required" : "legacy",
|
|
68
|
-
contextMode: storageVersion === 2 ? (v2?.writeReady ? "bounded_preview" : "blocked")
|
|
69
|
-
: detectionIssue ? "blocked" : "legacy_context",
|
|
70
|
-
readerMode: storageVersion === 2 ? "v2" : detectionIssue ? "blocked" : "legacy",
|
|
71
|
-
detectedIndexVersion: storageVersion,
|
|
72
|
-
migrationCoverage: { detectedIndexVersion: storageVersion, storageVersion, indexedEntries, legacyEntries, unindexedFiles: orphanNotes.length,
|
|
73
|
-
totalEntries: analysis.entries.length, complete },
|
|
74
|
-
coverage: ["MEMORY.md", "notes/**", ...(storageVersion === 2 ? [".crew/memory/migration.v1.json"] : []),
|
|
75
|
-
".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
|
|
35
|
+
coverage: ["MEMORY.md", "notes/**", ".crew/memory/pending/**", "根目录的 .memory-result.v1-*.json"],
|
|
76
36
|
limitations: ["不扫描历史执行目录;result清单仅覆盖Agent根目录。", "两遍摘要比对不等同跨文件原子快照,正式迁移仍需停止写入并重新核对。"],
|
|
77
|
-
limits: inventoryLimits, totalBytes:
|
|
37
|
+
limits: inventoryLimits, totalBytes: scan.totalBytes, files, ...analysis, issues,
|
|
78
38
|
pending: files.filter((file) => file.path.startsWith(".crew/memory/pending/")).map((file) => file.path),
|
|
79
|
-
|
|
80
|
-
orphanNotes,
|
|
39
|
+
orphanNotes: files.filter((file) => file.path.startsWith("notes/") && !referenced.has(file.path)).map((file) => file.path),
|
|
81
40
|
resultFiles: files.filter((file) => file.path.startsWith(".memory-result.v1-")).map((file) => file.path),
|
|
82
41
|
};
|
|
83
42
|
io.out(JSON.stringify(report, null, 2) + "\n");
|
|
@@ -100,6 +59,3 @@ import { parseArgs } from "node:util";
|
|
|
100
59
|
import { z } from "zod";
|
|
101
60
|
import { inventoryLimits, scanMemorySources } from "./inventory-source.js";
|
|
102
61
|
import { previewMemoryInventory } from "./inventory-preview.js";
|
|
103
|
-
import { inspectV2MemoryInventory } from "./inventory-v2.js";
|
|
104
|
-
import { detectLocalMemoryStorageVersion } from "./storage-format.js";
|
|
105
|
-
import { isDeferredMemoryArtifactPath } from "./source-path.js";
|
|
@@ -73,8 +73,7 @@ export async function readMemoryInventorySource(root, file, budget) {
|
|
|
73
73
|
await handle.close();
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
export async function scanMemorySources(agentDir
|
|
77
|
-
const includeOperationalFiles = options.includeOperationalFiles ?? true;
|
|
76
|
+
export async function scanMemorySources(agentDir) {
|
|
78
77
|
const root = await realpath(agentDir);
|
|
79
78
|
if ((await lstat(agentDir)).isSymbolicLink())
|
|
80
79
|
throw new Error("agent_root_symlink");
|
|
@@ -125,14 +124,10 @@ export async function scanMemorySources(agentDir, options = {}) {
|
|
|
125
124
|
}
|
|
126
125
|
await visit("MEMORY.md");
|
|
127
126
|
await visit("notes", true);
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
await visit(".crew/memory/pending", true);
|
|
133
|
-
for (const name of resultNames)
|
|
134
|
-
await visit(name);
|
|
135
|
-
}
|
|
127
|
+
await visit(".crew/memory/pending", true);
|
|
128
|
+
const resultNames = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
|
|
129
|
+
for (const name of resultNames)
|
|
130
|
+
await visit(name);
|
|
136
131
|
// 第二遍比对摘要和目录清单,发现扫描期间的变化就拒绝完整性结论;这不是跨文件事务锁。
|
|
137
132
|
for (const source of sources) {
|
|
138
133
|
try {
|
|
@@ -155,11 +150,9 @@ export async function scanMemorySources(agentDir, options = {}) {
|
|
|
155
150
|
addIssue("source_changed", file);
|
|
156
151
|
}
|
|
157
152
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
addIssue("source_changed", ".memory-result.v1-*.json");
|
|
162
|
-
}
|
|
153
|
+
const currentResults = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
|
|
154
|
+
if (JSON.stringify(currentResults) !== JSON.stringify(resultNames))
|
|
155
|
+
addIssue("source_changed", ".memory-result.v1-*.json");
|
|
163
156
|
if (await realpath(agentDir) !== root)
|
|
164
157
|
addIssue("source_changed", "agent_root");
|
|
165
158
|
return { root, sources: sources.sort((a, b) => a.path.localeCompare(b.path)), issues, totalBytes };
|
|
@@ -15,7 +15,6 @@ const SearchSchema = z.object({
|
|
|
15
15
|
cursor: z.string().max(2048).optional(), limit: z.number().int().min(1).max(40).default(10),
|
|
16
16
|
}).strict();
|
|
17
17
|
const excerpt = (text, cap) => Array.from(text).slice(0, cap).join("");
|
|
18
|
-
const pendingState = (reason) => reason === "legacy_requires_migration" ? "migration_deferred" : "capacity_deferred";
|
|
19
18
|
export async function searchPendingMemoryPage(input) {
|
|
20
19
|
const parsed = SearchSchema.safeParse(input);
|
|
21
20
|
if (!parsed.success)
|
|
@@ -62,8 +61,7 @@ export async function searchPendingMemoryPage(input) {
|
|
|
62
61
|
const match = source && isRecallablePending(source.candidate) && pendingRecallScore(source.candidate, args.query) > 0
|
|
63
62
|
? { pendingId: source.candidate.pendingId, sha256: source.sha256, title: source.candidate.candidate.title,
|
|
64
63
|
summary: excerpt(source.candidate.candidate.summary, 180), triggers: source.candidate.candidate.triggers.slice(0, 4),
|
|
65
|
-
operation: "create",
|
|
66
|
-
state: pendingState(source.candidate.reason) }
|
|
64
|
+
operation: "create", state: "capacity_deferred" }
|
|
67
65
|
: null;
|
|
68
66
|
const addition = match ?? warning;
|
|
69
67
|
const size = addition ? Buffer.byteLength(JSON.stringify(addition), "utf8") + 2 : 0;
|
|
@@ -98,6 +96,5 @@ export async function readPendingMemoryBody(input) {
|
|
|
98
96
|
throw new Error("memory_pending_not_recallable");
|
|
99
97
|
const candidate = source.candidate.candidate;
|
|
100
98
|
return { pendingId: source.candidate.pendingId, sha256: source.sha256, title: candidate.title,
|
|
101
|
-
operation: "create",
|
|
102
|
-
state: pendingState(source.candidate.reason), content: candidate.detailBody };
|
|
99
|
+
operation: "create", state: "capacity_deferred", content: candidate.detailBody };
|
|
103
100
|
}
|
|
@@ -8,7 +8,7 @@ const ArchivedMemoryChangeSchema = LocalMemoryChangeSchema.extend({
|
|
|
8
8
|
export const PendingIdSchema = z.string().max(200).regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u);
|
|
9
9
|
export const PendingMemorySchema = z.object({
|
|
10
10
|
schemaVersion: z.literal(1), pendingId: PendingIdSchema,
|
|
11
|
-
createdAt: z.string().min(1).max(100), reason: z.
|
|
11
|
+
createdAt: z.string().min(1).max(100), reason: z.literal("index_over_budget"),
|
|
12
12
|
source: z.object({ executionId: z.string().min(1).max(500), pruneTraceId: z.string().min(1).max(500).nullable() }).strict(),
|
|
13
13
|
candidate: ArchivedMemoryChangeSchema,
|
|
14
14
|
}).strict();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { lstat, mkdir, realpath, rename, rm,
|
|
2
|
+
import { lstat, mkdir, realpath, rename, rm, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname, join, relative, resolve, sep } from "node:path";
|
|
4
4
|
import { isRecallablePending, parsePendingMemoryCandidate } from "./pending-schema.js";
|
|
5
5
|
import { pendingFileNames, pendingReadDirectory, PENDING_FILE_BYTES, readPendingSource } from "./pending-source.js";
|
|
@@ -103,17 +103,6 @@ export async function persistPendingMemoryCandidate(input) {
|
|
|
103
103
|
const directory = await pendingDirectory(input.agentDir);
|
|
104
104
|
const target = join(directory, `${parsed.pendingId}.json`);
|
|
105
105
|
await assertRegularFile(target);
|
|
106
|
-
try {
|
|
107
|
-
const existing = parsePendingMemoryCandidate(JSON.parse(await readFile(target, "utf8")));
|
|
108
|
-
const dedupKey = (value) => value.reason === "legacy_requires_migration"
|
|
109
|
-
? JSON.stringify({ schemaVersion: value.schemaVersion, reason: value.reason, candidate: value.candidate })
|
|
110
|
-
: JSON.stringify({ ...value, createdAt: "" });
|
|
111
|
-
if (dedupKey(existing) === dedupKey(parsed))
|
|
112
|
-
return target;
|
|
113
|
-
}
|
|
114
|
-
catch {
|
|
115
|
-
// A malformed existing candidate is replaced by the validated input below.
|
|
116
|
-
}
|
|
117
106
|
await atomicWrite(target, `${JSON.stringify(parsed, null, 2)}\n`);
|
|
118
107
|
return target;
|
|
119
108
|
}
|
|
@@ -175,10 +164,7 @@ function truncateCodePoints(value, maxCodePoints) {
|
|
|
175
164
|
return Array.from(value).slice(0, Math.max(0, maxCodePoints)).join("");
|
|
176
165
|
}
|
|
177
166
|
export function renderPendingMemoryContext(candidates, maxCodePoints = 2_000) {
|
|
178
|
-
|
|
179
|
-
// be injected into the ordinary prompt before a reviewed migration promotes
|
|
180
|
-
// them to the active catalog.
|
|
181
|
-
const recallable = candidates.filter((candidate) => isRecallablePending(candidate) && candidate.reason === "index_over_budget");
|
|
167
|
+
const recallable = candidates.filter(isRecallablePending);
|
|
182
168
|
if (recallable.length === 0 || maxCodePoints <= 0)
|
|
183
169
|
return "";
|
|
184
170
|
const header = `\n\n## Archived memory candidates (daemon-managed)\n容量暂存的新增资料,仅为部分预览,不代表完整检索;可参考但不能覆盖现行规则,未包含内容请按需查询。\n`;
|