@nowcrew/daemon 0.6.98 → 0.6.99-beta.1
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 +43 -1
- package/dist/automatic-daemon-installer.js +41 -10
- package/dist/automatic-service-migration.js +7 -6
- package/dist/cloud-report-upload.js +1 -10
- package/dist/computer-service.js +40 -42
- package/dist/computer-setup-client.js +43 -9
- package/dist/computer-setup-existing.js +154 -0
- package/dist/computer-setup-orchestration.js +32 -6
- package/dist/computer-setup.js +37 -6
- package/dist/computer-token-setup.js +46 -0
- package/dist/execution-journal.js +3 -1
- package/dist/execution-protocol.js +1 -1
- package/dist/execution-runner.js +4 -1
- package/dist/i18n.js +16 -0
- package/dist/local-execution-prompt.js +8 -3
- package/dist/local-executor.js +64 -39
- package/dist/local-memory/commit-telemetry.js +30 -0
- package/dist/local-memory/execution-workspace.js +62 -0
- package/dist/local-memory/full-index-search.js +47 -0
- package/dist/local-memory/index-budget.js +9 -3
- package/dist/local-memory/initialization-identity.js +36 -0
- package/dist/local-memory/inventory-cli.js +52 -8
- package/dist/local-memory/inventory-source.js +15 -8
- package/dist/local-memory/inventory-v2.js +362 -0
- package/dist/local-memory/legacy-adapter.js +233 -0
- package/dist/local-memory/migration-access.js +242 -0
- package/dist/local-memory/migration-cli.js +54 -0
- package/dist/local-memory/migration-io.js +199 -0
- package/dist/local-memory/migration-pending.js +9 -0
- package/dist/local-memory/migration-plan.js +432 -0
- package/dist/local-memory/migration-schema.js +55 -0
- package/dist/local-memory/migration-state-invariants.js +65 -0
- package/dist/local-memory/migration-store.js +414 -0
- package/dist/local-memory/migration-tool-entry.js +21 -0
- package/dist/local-memory/pending-query.js +5 -2
- package/dist/local-memory/pending-schema.js +1 -1
- package/dist/local-memory/pending.js +16 -2
- package/dist/local-memory/prompt.js +21 -5
- package/dist/local-memory/read-cli.js +98 -76
- package/dist/local-memory/rollout.js +79 -11
- package/dist/local-memory/runtime.js +181 -13
- package/dist/local-memory/source-path.js +13 -0
- package/dist/local-memory/storage-format.js +249 -0
- package/dist/local-memory/write.js +189 -21
- package/dist/local-memory/writer-mode.js +13 -0
- package/dist/local-memory-diagnostics.js +96 -15
- package/dist/local-memory-telemetry.js +20 -3
- package/dist/machine-info.js +6 -3
- package/dist/marketplace-controller.js +8 -21
- package/dist/marketplace-protocol.js +1 -1
- package/dist/marketplace-remote.js +1 -1
- package/dist/marketplace-runner.js +1 -1
- package/dist/marketplace-serve.js +2 -4
- package/dist/memory-migration-controller.js +89 -0
- package/dist/memory-migration-inspect.js +108 -0
- package/dist/memory-migration-journal.js +14 -0
- package/dist/memory-migration-protocol.js +14 -0
- package/dist/memory-migration-runtime.js +305 -0
- package/dist/memory-prune-diagnostics.js +60 -4
- package/dist/message-file-preview.js +11 -1
- package/dist/npm-command.js +5 -5
- package/dist/powershell-command.js +12 -0
- package/dist/project-skills/machine-hello-publisher.js +95 -6
- package/dist/prompt.js +9 -17
- package/dist/runner.js +1 -1
- package/dist/runtimes/claude-thread-runner.js +25 -4
- package/dist/serve.js +92 -10
- package/dist/token.js +1 -0
- package/dist/windows-scheduled-task.js +3 -3
- package/dist/windows-task-principal.js +26 -0
- package/dist/workspace-control-handler.js +16 -3
- package/dist/workspace.js +13 -2
- package/package.json +1 -1
- package/dist/marketplace-maintenance-bridge.js +0 -79
- package/dist/marketplace-maintenance-protocol.js +0 -40
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 {
|
|
9
|
+
import { 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,13 +39,20 @@ 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";
|
|
42
43
|
import { CodexStartupStageParser } from "./codex-startup-stage.js";
|
|
43
44
|
import { formatRuntimeStartupError } from "./runtime-startup-error.js";
|
|
44
45
|
import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
|
|
45
|
-
import { commitLocalMemoryResultAfterExit,
|
|
46
|
+
import { commitLocalMemoryResultAfterExit, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
|
|
46
47
|
import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
|
|
47
|
-
import { reportLocalMemoryV2Deferred
|
|
48
|
+
import { reportLocalMemoryV2Deferred } from "./local-memory/rollout.js";
|
|
49
|
+
import { prepareExecutionMemoryWorkspace } from "./local-memory/execution-workspace.js";
|
|
48
50
|
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";
|
|
49
56
|
export class RuntimeRunningCallbackTimeoutError extends Error {
|
|
50
57
|
constructor(runtime, timeoutMs) {
|
|
51
58
|
super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
|
|
@@ -59,7 +66,6 @@ const STDERR_TAIL_CAP = 2_000;
|
|
|
59
66
|
// the old failure mode where a callback could hold execution slots forever.
|
|
60
67
|
const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
|
|
61
68
|
const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
|
|
62
|
-
const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
|
|
63
69
|
const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
|
|
64
70
|
const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
|
|
65
71
|
function withoutClaudeInstructionFlag(env) {
|
|
@@ -211,37 +217,12 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
211
217
|
: "high"
|
|
212
218
|
: runtime.reasoning;
|
|
213
219
|
const providerFp = providerFingerprint(runtime.name, providerConfig);
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
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 };
|
|
220
|
+
const preparedMemory = await prepareExecutionMemoryWorkspace(input, isolatedMaintenance, dependencies.cancellation);
|
|
221
|
+
let workspace = preparedMemory.workspace;
|
|
222
|
+
const memoryRollout = preparedMemory.mode;
|
|
243
223
|
input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
|
|
244
|
-
const localMemoryV2Initialized =
|
|
224
|
+
const localMemoryV2Initialized = preparedMemory.initialized;
|
|
225
|
+
let localMemoryWriterMode = resolveLocalMemoryWriterMode(workspace, memoryRollout);
|
|
245
226
|
let materialized = null;
|
|
246
227
|
let knownAttachmentDirectory = null;
|
|
247
228
|
const startupReservationState = { current: null };
|
|
@@ -252,6 +233,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
252
233
|
let memoryPruneBeforeSnapshot = null;
|
|
253
234
|
let memoryPruneSharedWriteKey = null;
|
|
254
235
|
let localMemoryResultPath = null;
|
|
236
|
+
let migrationDeferredPending = false;
|
|
255
237
|
let environmentSnapshot;
|
|
256
238
|
let executionWorkspace = workspace;
|
|
257
239
|
let localMemoryTelemetry = null;
|
|
@@ -390,29 +372,41 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
390
372
|
const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
|
|
391
373
|
let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
392
374
|
memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
|
|
393
|
-
|
|
394
|
-
|
|
375
|
+
localMemoryWriterMode = resolveLocalMemoryWriterMode(executionWorkspace, memoryRollout);
|
|
376
|
+
migrationDeferredPending = shouldUseMigrationDeferredPending({ pruneTraceId: memoryPruneTraceId,
|
|
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
|
+
}
|
|
395
385
|
if (memoryPruneTraceId !== null) {
|
|
396
386
|
executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
|
|
397
387
|
promptContext = { ...promptContext, workspace: executionWorkspace };
|
|
398
388
|
baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
|
|
399
389
|
}
|
|
400
|
-
if (
|
|
390
|
+
if ((localMemoryWriterMode === "v2" && memoryPruneTraceId !== null
|
|
401
391
|
&& isLocalMemoryV2Eligible({
|
|
402
392
|
enabledForHandle: true,
|
|
403
393
|
agentHomePreexisted: workspace.agentHomePreexisted === true,
|
|
404
394
|
memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
|
|
405
395
|
localMemoryV2Initialized,
|
|
406
396
|
memoryPrune: true,
|
|
407
|
-
})) {
|
|
397
|
+
})) || migrationDeferredPending) {
|
|
408
398
|
localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
|
|
409
399
|
}
|
|
410
400
|
const memoryPrompt = await prepareLocalMemoryPrompt({
|
|
411
401
|
agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
|
|
412
402
|
enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
|
|
413
403
|
resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
|
|
414
|
-
|
|
404
|
+
storageVersion: detectLocalMemoryStorageVersion(executionWorkspace.memory),
|
|
405
|
+
strictRollout: !isolatedMaintenance && !memoryRollout.legacyFallback && (localMemoryV2Environment(input.launch.serverUrl) === "dev"
|
|
406
|
+
|| detectLocalMemoryStorageVersion(executionWorkspace.memory) === 2),
|
|
415
407
|
deferred: memoryRollout.deferred,
|
|
408
|
+
...(memoryRollout.legacyFallback ? { legacyFallback: true } : {}),
|
|
409
|
+
...(migrationDeferredPending ? { migrationDeferred: true } : {}),
|
|
416
410
|
renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
|
|
417
411
|
});
|
|
418
412
|
localMemoryResultPath = memoryPrompt.resultPath;
|
|
@@ -528,6 +522,7 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
528
522
|
localMemoryTelemetry?.contextPrepared({
|
|
529
523
|
systemPrompt: effectiveSystemPrompt,
|
|
530
524
|
memory: executionWorkspace.memory,
|
|
525
|
+
memoryQuery: baseWakePrompt,
|
|
531
526
|
resumed: resuming,
|
|
532
527
|
...(executionWorkspace.memorySeedCreated === undefined
|
|
533
528
|
? {}
|
|
@@ -909,6 +904,11 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
909
904
|
exitCode,
|
|
910
905
|
workLogPath: executionWorkspace.workLogPath,
|
|
911
906
|
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,6 +922,14 @@ 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
|
+
}
|
|
925
933
|
memoryPruneFailurePhase = "runtime_finalize";
|
|
926
934
|
}
|
|
927
935
|
const errorTail = [
|
|
@@ -1052,6 +1060,23 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
|
|
|
1052
1060
|
after_incomplete_file_count: noteDiff.after_manifest.incomplete_file_count,
|
|
1053
1061
|
changes: serializedChanges.json,
|
|
1054
1062
|
});
|
|
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
|
+
});
|
|
1055
1080
|
const postcondition = evaluateMemoryPrunePostcondition(memoryPruneBeforeSnapshot, snapshot, {
|
|
1056
1081
|
executorCompleted: memoryPruneExecutorCompleted,
|
|
1057
1082
|
...(memoryPruneRuntimeExitCode === undefined
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { dslog } from "../slog.js";
|
|
2
|
+
function boundedIds(ids) {
|
|
3
|
+
return {
|
|
4
|
+
value: (ids ?? []).slice(0, 100).join(","),
|
|
5
|
+
count: ids?.length ?? 0,
|
|
6
|
+
truncated: (ids?.length ?? 0) > 100,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function logLocalMemoryCommitApplied(input) {
|
|
10
|
+
const { committed } = input;
|
|
11
|
+
const created = boundedIds(committed.createdIds);
|
|
12
|
+
const updated = boundedIds(committed.updatedIds);
|
|
13
|
+
const statusChanged = boundedIds(committed.statusChangedIds);
|
|
14
|
+
const pending = boundedIds(committed.pendingIds);
|
|
15
|
+
dslog("local_memory.commit_applied", "结构化本地记忆提交已验证", {
|
|
16
|
+
execution_id: input.executionId, agent_handle: input.agentHandle, prune_trace_id: input.pruneTraceId,
|
|
17
|
+
storage_version_before: committed.storageVersionBefore,
|
|
18
|
+
storage_version_after: committed.storageVersionAfter,
|
|
19
|
+
committed_state: committed.committedState,
|
|
20
|
+
writer_mode: committed.pendingReason === "legacy_requires_migration"
|
|
21
|
+
? "pending_only"
|
|
22
|
+
: committed.storageVersionAfter === 2 && committed.committedState ? "v2" : "legacy",
|
|
23
|
+
created_id_count: created.count, created_ids: created.value, created_ids_truncated: created.truncated,
|
|
24
|
+
updated_id_count: updated.count, updated_ids: updated.value, updated_ids_truncated: updated.truncated,
|
|
25
|
+
status_changed_id_count: statusChanged.count, status_changed_ids: statusChanged.value,
|
|
26
|
+
status_changed_ids_truncated: statusChanged.truncated,
|
|
27
|
+
pending_id_count: pending.count, pending_ids: pending.value, pending_ids_truncated: pending.truncated,
|
|
28
|
+
pending_reason: committed.pendingReason,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { prepareWorkspace } from "../workspace.js";
|
|
2
|
+
import { awaitWithCancellation } from "../runtime-cancellation.js";
|
|
3
|
+
import { logLocalMemoryContextPrepareFailure } from "../local-memory-telemetry.js";
|
|
4
|
+
import { resolveLocalMemoryV2Rollout } from "./rollout.js";
|
|
5
|
+
import { initializeLocalMemoryV2ForWorkspace } from "./runtime.js";
|
|
6
|
+
import { withMemoryAccess } from "./migration-access.js";
|
|
7
|
+
import { join } from "node:path";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
// A fresh Agent may receive the daemon's full ten-wide execution burst. The
|
|
10
|
+
// first turn publishes schema2 durably; the remaining turns must wait to reuse
|
|
11
|
+
// that committed fact instead of timing out on the generic short IO window.
|
|
12
|
+
const EXECUTION_MEMORY_ACCESS_WAIT_MS = 30_000;
|
|
13
|
+
export async function prepareExecutionMemoryWorkspace(input, isolated, cancellation) {
|
|
14
|
+
try {
|
|
15
|
+
const prepare = async (context) => {
|
|
16
|
+
const prepared = await prepareWorkspace({
|
|
17
|
+
agentsRoot: input.launch.agentsRoot, handle: input.handle, cliPath: input.launch.cliPath,
|
|
18
|
+
executionId: input.executionId,
|
|
19
|
+
...(input.keyMode === undefined ? {} : { keyMode: input.keyMode }),
|
|
20
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }),
|
|
21
|
+
...(input.resumeKey === undefined ? {} : { resumeKey: input.resumeKey }),
|
|
22
|
+
...(input.sessionContextFingerprint === undefined ? {} : { sessionContextFingerprint: input.sessionContextFingerprint }),
|
|
23
|
+
...(input.launch.description ? { description: input.launch.description } : {}),
|
|
24
|
+
...(isolated ? { skipLocalMemory: true } : {}),
|
|
25
|
+
});
|
|
26
|
+
const workspace = context?.created ? { ...prepared, agentHomePreexisted: false } : prepared;
|
|
27
|
+
const initialMode = await resolveLocalMemoryV2Rollout({ workspace, handle: input.handle, serverUrl: input.launch.serverUrl,
|
|
28
|
+
agentId: input.launch.agentId, requested: input.localMemoryV2Enabled === true, selfTraining: isolated,
|
|
29
|
+
...(input.launch.description ? { description: input.launch.description } : {}) });
|
|
30
|
+
const initializationBinding = input.launch.workspaceId !== undefined && input.launch.machineId !== undefined
|
|
31
|
+
? { workspaceId: input.launch.workspaceId, machineId: input.launch.machineId }
|
|
32
|
+
: undefined;
|
|
33
|
+
const initialized = await initializeLocalMemoryV2ForWorkspace(workspace, initialMode.enabled, input.launch.agentId, initializationBinding);
|
|
34
|
+
const memory = initialized && !workspace.localMemoryV2Initialized
|
|
35
|
+
? await readFile(join(workspace.dir, "MEMORY.md"), "utf8")
|
|
36
|
+
: workspace.memory;
|
|
37
|
+
const currentWorkspace = { ...workspace, memory };
|
|
38
|
+
const mode = initialized && !workspace.localMemoryV2Initialized
|
|
39
|
+
? await resolveLocalMemoryV2Rollout({ workspace: currentWorkspace, handle: input.handle,
|
|
40
|
+
serverUrl: input.launch.serverUrl, agentId: input.launch.agentId,
|
|
41
|
+
requested: input.localMemoryV2Enabled === true, selfTraining: isolated,
|
|
42
|
+
...(input.launch.description ? { description: input.launch.description } : {}) })
|
|
43
|
+
: initialMode;
|
|
44
|
+
return { workspace: { ...currentWorkspace, localMemoryV2Enabled: mode.enabled }, mode, initialized };
|
|
45
|
+
};
|
|
46
|
+
return await awaitWithCancellation(isolated ? prepare()
|
|
47
|
+
: withMemoryAccess(join(input.launch.agentsRoot, input.handle), prepare, {
|
|
48
|
+
create: true,
|
|
49
|
+
waitMs: EXECUTION_MEMORY_ACCESS_WAIT_MS,
|
|
50
|
+
initializedRecoveryIdentity: {
|
|
51
|
+
agentId: input.launch.agentId,
|
|
52
|
+
...(input.launch.workspaceId === undefined ? {} : { workspaceId: input.launch.workspaceId }),
|
|
53
|
+
...(input.launch.machineId === undefined ? {} : { machineId: input.launch.machineId }),
|
|
54
|
+
},
|
|
55
|
+
}), cancellation);
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
logLocalMemoryContextPrepareFailure({ executionId: input.executionId, agentHandle: input.handle,
|
|
59
|
+
...(input.taskKey === undefined ? {} : { taskKey: input.taskKey }) }, "workspace_prepare", error);
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { parseFullMemoryIndex, LOCAL_MEMORY_GENERATED_INDEX_PATTERN } from "./storage-format.js";
|
|
2
|
+
const excerpt = (text, size) => Array.from(text).slice(0, size).join("");
|
|
3
|
+
/** 查询完整元数据;只在明确的历史入口返回非有效记录。正文仍按引用和哈希分页读取。 */
|
|
4
|
+
export function searchFullMemoryIndex(memory, sha256, input, outputBytes) {
|
|
5
|
+
const records = parseFullMemoryIndex(memory);
|
|
6
|
+
const block = LOCAL_MEMORY_GENERATED_INDEX_PATTERN.exec(memory);
|
|
7
|
+
const prefix = memory.slice(0, block.index);
|
|
8
|
+
let line = (prefix.match(/\n/gu) ?? []).length + 1;
|
|
9
|
+
let offset = Array.from(prefix).length;
|
|
10
|
+
const positions = new Map();
|
|
11
|
+
for (const part of block[0].matchAll(/[^\n]*(?:\n|$)/gu)) {
|
|
12
|
+
const id = /^- id: ([A-Za-z0-9_-]+)\r?\n?$/u.exec(part[0])?.[1];
|
|
13
|
+
if (id)
|
|
14
|
+
positions.set(id, { line, offset });
|
|
15
|
+
line += 1;
|
|
16
|
+
offset += Array.from(part[0]).length;
|
|
17
|
+
}
|
|
18
|
+
const terms = input.query.normalize("NFKC").toLowerCase().split(/\s+/u).filter(Boolean);
|
|
19
|
+
const candidates = records.filter(entry => {
|
|
20
|
+
const active = entry.status === "active" || entry.status === "pinned";
|
|
21
|
+
const content = `${entry.id} ${entry.title} ${entry.summary} ${entry.triggers.join(" ")}`.normalize("NFKC").toLowerCase();
|
|
22
|
+
return active !== input.history && terms.every(term => content.includes(term));
|
|
23
|
+
});
|
|
24
|
+
const matches = [];
|
|
25
|
+
let bytes = 512, hasMore = false;
|
|
26
|
+
for (const entry of candidates) {
|
|
27
|
+
const position = positions.get(entry.id);
|
|
28
|
+
if (!position)
|
|
29
|
+
throw new Error("memory_storage_position_invalid");
|
|
30
|
+
if (position.line <= input.afterLine)
|
|
31
|
+
continue;
|
|
32
|
+
const item = { ...position, id: entry.id, status: entry.status, revision: entry.revision,
|
|
33
|
+
file: entry.file, anchor: entry.anchor, heading: excerpt(entry.title, 96),
|
|
34
|
+
title: excerpt(entry.title, 120), summary: excerpt(entry.summary, 240), text: excerpt(entry.summary, 240) };
|
|
35
|
+
const size = Buffer.byteLength(JSON.stringify(item), "utf8") + 1;
|
|
36
|
+
if (matches.length >= input.limit || bytes + size > outputBytes) {
|
|
37
|
+
hasMore = true;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
matches.push(item);
|
|
41
|
+
bytes += size;
|
|
42
|
+
}
|
|
43
|
+
if (hasMore && matches.length === 0)
|
|
44
|
+
throw new Error("memory_output_too_large");
|
|
45
|
+
return { file: "MEMORY.md", storageVersion: 2, sha256, matches, matchCount: candidates.length,
|
|
46
|
+
hasMore, complete: !hasMore, nextAfterLine: hasMore ? matches.at(-1).line : null };
|
|
47
|
+
}
|
|
@@ -50,14 +50,17 @@ 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(compareEntries);
|
|
53
|
+
const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort((left, right) => (options.relevance?.(right) ?? 0) - (options.relevance?.(left) ?? 0) || compareEntries(left, right));
|
|
54
54
|
const selected = [];
|
|
55
55
|
let renderedLength = 0;
|
|
56
|
+
let renderedBytes = 0;
|
|
56
57
|
for (const entry of protectedEntries) {
|
|
57
58
|
selected.push(entry);
|
|
58
59
|
renderedLength += cardLength(entry, selected.length > 1);
|
|
60
|
+
renderedBytes += Buffer.byteLength(renderLocalMemoryIndexCard(entry), "utf8") + (selected.length > 1 ? 1 : 0);
|
|
59
61
|
}
|
|
60
|
-
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap
|
|
62
|
+
if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap
|
|
63
|
+
|| renderedBytes > (budget.indexByteCap ?? Infinity)) {
|
|
61
64
|
return {
|
|
62
65
|
entries: selected,
|
|
63
66
|
omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
|
|
@@ -65,12 +68,15 @@ export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
|
|
|
65
68
|
};
|
|
66
69
|
}
|
|
67
70
|
for (const entry of candidates) {
|
|
68
|
-
|
|
71
|
+
const bytes = Buffer.byteLength(renderLocalMemoryIndexCard(entry), "utf8") + (selected.length > 0 ? 1 : 0);
|
|
72
|
+
if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap
|
|
73
|
+
|| renderedBytes + bytes > (budget.indexByteCap ?? Infinity)) {
|
|
69
74
|
omittedIds.push(entry.id);
|
|
70
75
|
continue;
|
|
71
76
|
}
|
|
72
77
|
selected.push(entry);
|
|
73
78
|
renderedLength += cardLength(entry, selected.length > 1);
|
|
79
|
+
renderedBytes += bytes;
|
|
74
80
|
}
|
|
75
81
|
return {
|
|
76
82
|
entries: selected,
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { lstat, realpath } from "node:fs/promises";
|
|
3
|
+
export const LEGACY_LOCAL_INITIALIZATION_IDENTITY = "local-initialized";
|
|
4
|
+
export async function localMemoryInitializationIdentity(agentDir, agentId, binding) {
|
|
5
|
+
const root = await realpath(agentDir);
|
|
6
|
+
const rootFingerprint = createHash("sha256").update(root).digest("hex");
|
|
7
|
+
const workspaceId = binding?.workspaceId ?? LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
8
|
+
const machineId = binding?.machineId ?? LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
9
|
+
// Keep the legacy digest byte-for-byte stable so an interrupted pre-upgrade
|
|
10
|
+
// initialization can still be inspected. Every newly authenticated daemon
|
|
11
|
+
// initialization includes both control-plane identities in its operation id.
|
|
12
|
+
const operationSeed = workspaceId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
13
|
+
&& machineId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
14
|
+
? `${agentId}\0${root}`
|
|
15
|
+
: `${workspaceId}\0${machineId}\0${agentId}\0${root}`;
|
|
16
|
+
const operation = createHash("sha256").update(operationSeed, "utf8").digest("hex").slice(0, 24);
|
|
17
|
+
return {
|
|
18
|
+
workspaceId,
|
|
19
|
+
agentId,
|
|
20
|
+
migrationId: `initialize-${operation}`,
|
|
21
|
+
sourceStorageVersion: 0,
|
|
22
|
+
targetStorageVersion: 2,
|
|
23
|
+
rootFingerprint,
|
|
24
|
+
ownerIdentity: String((await lstat(root)).uid),
|
|
25
|
+
machineId,
|
|
26
|
+
executionId: `initialize-${operation}`,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function isLegacyLocalMemoryInitializationIdentity(identity) {
|
|
30
|
+
return identity.workspaceId === LEGACY_LOCAL_INITIALIZATION_IDENTITY
|
|
31
|
+
&& identity.machineId === LEGACY_LOCAL_INITIALIZATION_IDENTITY;
|
|
32
|
+
}
|
|
33
|
+
export function isLocalMemoryInitializationIdentity(actual, expected) {
|
|
34
|
+
return Object.keys(expected)
|
|
35
|
+
.every(key => actual[key] === expected[key]);
|
|
36
|
+
}
|
|
@@ -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 = "只读记忆盘点、schema0/1 转换预演与 schema2 完整性校验,不写文件,不支持 --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,19 +24,60 @@ 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
|
|
27
|
+
const memory = scan.sources.find((source) => source.path === "MEMORY.md")?.content ?? "";
|
|
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);
|
|
28
47
|
const complete = scanComplete && !analysis.issues.some((issue) => issue.code === "missing_detail");
|
|
29
48
|
const issues = [...scan.issues, ...analysis.issues];
|
|
30
|
-
const referenced = new Set(
|
|
31
|
-
|
|
49
|
+
const referenced = new Set([
|
|
50
|
+
...analysis.entries.map((entry) => entry.sourceFields.file),
|
|
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;
|
|
32
59
|
const report = {
|
|
33
60
|
schemaVersion: 1, mode: "dry-run", agentDir: scan.root, applySupported: false,
|
|
34
61
|
complete, scanComplete, requiresHumanReview: true,
|
|
35
|
-
|
|
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"],
|
|
36
76
|
limitations: ["不扫描历史执行目录;result清单仅覆盖Agent根目录。", "两遍摘要比对不等同跨文件原子快照,正式迁移仍需停止写入并重新核对。"],
|
|
37
|
-
limits: inventoryLimits, totalBytes:
|
|
77
|
+
limits: inventoryLimits, totalBytes: reportSources.reduce((total, source) => total + source.bytes, 0), files, ...analysis, issues,
|
|
38
78
|
pending: files.filter((file) => file.path.startsWith(".crew/memory/pending/")).map((file) => file.path),
|
|
39
|
-
|
|
79
|
+
deferredFiles,
|
|
80
|
+
orphanNotes,
|
|
40
81
|
resultFiles: files.filter((file) => file.path.startsWith(".memory-result.v1-")).map((file) => file.path),
|
|
41
82
|
};
|
|
42
83
|
io.out(JSON.stringify(report, null, 2) + "\n");
|
|
@@ -59,3 +100,6 @@ import { parseArgs } from "node:util";
|
|
|
59
100
|
import { z } from "zod";
|
|
60
101
|
import { inventoryLimits, scanMemorySources } from "./inventory-source.js";
|
|
61
102
|
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,7 +73,8 @@ export async function readMemoryInventorySource(root, file, budget) {
|
|
|
73
73
|
await handle.close();
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
-
export async function scanMemorySources(agentDir) {
|
|
76
|
+
export async function scanMemorySources(agentDir, options = {}) {
|
|
77
|
+
const includeOperationalFiles = options.includeOperationalFiles ?? true;
|
|
77
78
|
const root = await realpath(agentDir);
|
|
78
79
|
if ((await lstat(agentDir)).isSymbolicLink())
|
|
79
80
|
throw new Error("agent_root_symlink");
|
|
@@ -124,10 +125,14 @@ export async function scanMemorySources(agentDir) {
|
|
|
124
125
|
}
|
|
125
126
|
await visit("MEMORY.md");
|
|
126
127
|
await visit("notes", true);
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
const resultNames = includeOperationalFiles
|
|
129
|
+
? (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort()
|
|
130
|
+
: [];
|
|
131
|
+
if (includeOperationalFiles) {
|
|
132
|
+
await visit(".crew/memory/pending", true);
|
|
133
|
+
for (const name of resultNames)
|
|
134
|
+
await visit(name);
|
|
135
|
+
}
|
|
131
136
|
// 第二遍比对摘要和目录清单,发现扫描期间的变化就拒绝完整性结论;这不是跨文件事务锁。
|
|
132
137
|
for (const source of sources) {
|
|
133
138
|
try {
|
|
@@ -150,9 +155,11 @@ export async function scanMemorySources(agentDir) {
|
|
|
150
155
|
addIssue("source_changed", file);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
158
|
+
if (includeOperationalFiles) {
|
|
159
|
+
const currentResults = (await readdir(root)).filter((name) => /^\.memory-result\.v1-.*\.json$/u.test(name)).sort();
|
|
160
|
+
if (JSON.stringify(currentResults) !== JSON.stringify(resultNames))
|
|
161
|
+
addIssue("source_changed", ".memory-result.v1-*.json");
|
|
162
|
+
}
|
|
156
163
|
if (await realpath(agentDir) !== root)
|
|
157
164
|
addIssue("source_changed", "agent_root");
|
|
158
165
|
return { root, sources: sources.sort((a, b) => a.path.localeCompare(b.path)), issues, totalBytes };
|