@nowcrew/daemon 0.6.101-beta.2 → 0.6.101-beta.4

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.
Files changed (66) hide show
  1. package/dist/cloud-report-upload.js +10 -1
  2. package/dist/computer-main.js +0 -0
  3. package/dist/execution-journal-lock.js +51 -2
  4. package/dist/execution-journal.js +1 -3
  5. package/dist/execution-protocol.js +1 -1
  6. package/dist/execution-runner.js +1 -4
  7. package/dist/host-execution-coordinator.js +10 -0
  8. package/dist/local-execution-prompt.js +3 -8
  9. package/dist/local-executor.js +55 -78
  10. package/dist/local-memory/index-budget.js +3 -9
  11. package/dist/local-memory/inventory-cli.js +8 -52
  12. package/dist/local-memory/inventory-source.js +8 -15
  13. package/dist/local-memory/pending-query.js +2 -5
  14. package/dist/local-memory/pending-schema.js +1 -1
  15. package/dist/local-memory/pending.js +2 -16
  16. package/dist/local-memory/prompt.js +5 -21
  17. package/dist/local-memory/read-cli.js +76 -98
  18. package/dist/local-memory/rollout.js +11 -79
  19. package/dist/local-memory/runtime.js +17 -191
  20. package/dist/local-memory/write.js +21 -190
  21. package/dist/local-memory-diagnostics.js +15 -96
  22. package/dist/local-memory-telemetry.js +3 -20
  23. package/dist/machine-info.js +3 -4
  24. package/dist/machine-runtime-environment.js +1 -0
  25. package/dist/main.js +0 -0
  26. package/dist/marketplace-protocol.js +1 -1
  27. package/dist/marketplace-remote.js +1 -1
  28. package/dist/marketplace-runner.js +1 -1
  29. package/dist/memory-prune-diagnostics.js +4 -74
  30. package/dist/message-file-preview.js +1 -11
  31. package/dist/prompt.js +17 -9
  32. package/dist/runner.js +1 -1
  33. package/dist/runtime-tool-update-bridge.js +126 -0
  34. package/dist/runtime-tool-update-controller.js +151 -0
  35. package/dist/runtime-tool-update-protocol.js +24 -0
  36. package/dist/runtime-tool-update-serve.js +54 -0
  37. package/dist/runtimes/claude-thread-runner.js +4 -25
  38. package/dist/serve.js +12 -38
  39. package/dist/token.js +0 -1
  40. package/dist/workspace-control-handler.js +3 -16
  41. package/dist/workspace.js +2 -13
  42. package/package.json +10 -9
  43. package/dist/local-memory/commit-telemetry.js +0 -30
  44. package/dist/local-memory/execution-workspace.js +0 -62
  45. package/dist/local-memory/full-index-search.js +0 -47
  46. package/dist/local-memory/initialization-identity.js +0 -36
  47. package/dist/local-memory/inventory-v2.js +0 -362
  48. package/dist/local-memory/legacy-adapter.js +0 -233
  49. package/dist/local-memory/migration-access.js +0 -242
  50. package/dist/local-memory/migration-cli.js +0 -54
  51. package/dist/local-memory/migration-io.js +0 -199
  52. package/dist/local-memory/migration-pending.js +0 -9
  53. package/dist/local-memory/migration-plan.js +0 -432
  54. package/dist/local-memory/migration-schema.js +0 -55
  55. package/dist/local-memory/migration-state-invariants.js +0 -65
  56. package/dist/local-memory/migration-store.js +0 -414
  57. package/dist/local-memory/migration-tool-entry.js +0 -21
  58. package/dist/local-memory/release-artifact-guard.js +0 -55
  59. package/dist/local-memory/source-path.js +0 -13
  60. package/dist/local-memory/storage-format.js +0 -249
  61. package/dist/local-memory/writer-mode.js +0 -13
  62. package/dist/memory-migration-controller.js +0 -89
  63. package/dist/memory-migration-inspect.js +0 -116
  64. package/dist/memory-migration-journal.js +0 -14
  65. package/dist/memory-migration-protocol.js +0 -14
  66. 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
- return explicit && exists(explicit) ? explicit : undefined;
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);
File without changes
@@ -3,10 +3,25 @@ import { renameSync } from "node:fs";
3
3
  import { randomUUID } from "node:crypto";
4
4
  import { join, resolve } from "node:path";
5
5
  import { z } from "zod";
6
+ const ProtectedGroupSchema = z.object({ pid: z.number().int().positive(), processIdentity: z.string().min(1) }).strict();
7
+ export async function inspectProtectedProcessGroup(pid) {
8
+ try {
9
+ process.kill(-pid, 0);
10
+ return true;
11
+ }
12
+ catch (error) {
13
+ if (error.code === "ESRCH")
14
+ return false;
15
+ if (error.code === "EPERM")
16
+ return true;
17
+ throw error;
18
+ }
19
+ }
6
20
  const OwnerSchema = z.object({
7
21
  pid: z.number().int().positive(),
8
22
  processIdentity: z.string().min(1),
9
23
  token: z.string().uuid(),
24
+ protectedGroup: ProtectedGroupSchema.optional(),
10
25
  }).strict();
11
26
  export class JournalLockedError extends Error {
12
27
  journalPath;
@@ -144,7 +159,8 @@ export async function inspectJournalLock(options) {
144
159
  let inspection;
145
160
  try {
146
161
  const identity = await options.inspectIdentity(owner.pid);
147
- inspection = identity === owner.processIdentity
162
+ const protectedAlive = owner.protectedGroup !== undefined && await (options.inspectProcessGroup ?? inspectProtectedProcessGroup)(owner.protectedGroup.pid);
163
+ inspection = identity === owner.processIdentity || protectedAlive
148
164
  ? {
149
165
  status: "owned",
150
166
  ownerPid: owner.pid,
@@ -333,7 +349,9 @@ export function createJournalLease(options) {
333
349
  if (observed === null)
334
350
  continue;
335
351
  const identity = await options.inspectIdentity(observed.owner.pid);
336
- if (identity === observed.owner.processIdentity) {
352
+ const protectedAlive = observed.owner.protectedGroup !== undefined
353
+ && await (options.inspectProcessGroup ?? inspectProtectedProcessGroup)(observed.owner.protectedGroup.pid);
354
+ if (identity === observed.owner.processIdentity || protectedAlive) {
337
355
  throw new JournalLockedError(`Execution journal is locked by process ${observed.owner.pid}`, { journalPath, ownerPid: observed.owner.pid });
338
356
  }
339
357
  await options.hooks?.beforeRemoveObservedOwner?.(observed.owner);
@@ -408,6 +426,9 @@ export function createJournalLease(options) {
408
426
  status = "closed";
409
427
  return;
410
428
  }
429
+ if (lease.owner.protectedGroup && await (options.inspectProcessGroup ?? inspectProtectedProcessGroup)(lease.owner.protectedGroup.pid)) {
430
+ throw new JournalLockedError("Protected maintenance process group is still alive", { journalPath, ownerPid: lease.owner.protectedGroup.pid });
431
+ }
411
432
  status = "closing";
412
433
  lease.releasing = true;
413
434
  await awaitAbortable((async () => {
@@ -426,8 +447,36 @@ export function createJournalLease(options) {
426
447
  status = "closed";
427
448
  void cleanupReleasedDirectory(releasedDirectory);
428
449
  };
450
+ const protectProcessGroup = async (raw) => {
451
+ const group = ProtectedGroupSchema.parse(raw);
452
+ const lease = registry.get(options.directory);
453
+ if (!attached || status !== "open" || !lease || lease.releasing || lease.refs !== 1)
454
+ throw new JournalLockedError("Lease cannot protect a worker", { journalPath });
455
+ await validateInstalledOwner(lease);
456
+ if (await options.inspectIdentity(group.pid) !== group.processIdentity
457
+ || !await (options.inspectProcessGroup ?? inspectProtectedProcessGroup)(group.pid))
458
+ throw new JournalLockedError("Worker identity is absent", { journalPath });
459
+ if (lease.owner.protectedGroup) {
460
+ if (JSON.stringify(lease.owner.protectedGroup) === JSON.stringify(group))
461
+ return;
462
+ throw new JournalLockedError("Lease already protects a worker", { journalPath });
463
+ }
464
+ const owner = { ...lease.owner, protectedGroup: group };
465
+ const temp = join(options.directory, `.journal-owner.${lease.owner.token}.${randomUUID()}.tmp`);
466
+ try {
467
+ await fileSystem.writeDurableFile(temp, `${JSON.stringify(owner)}\n`);
468
+ await fileSystem.rename(temp, join(lockDirectory, lease.ownerFile));
469
+ lease.owner = owner;
470
+ await options.syncDirectory(lockDirectory);
471
+ await options.syncDirectory(options.directory);
472
+ }
473
+ finally {
474
+ await fileSystem.rm(temp, { force: true }).catch(() => undefined);
475
+ }
476
+ };
429
477
  return {
430
478
  acquire,
479
+ protectProcessGroup,
431
480
  assertUsable: () => {
432
481
  if (status !== "open") {
433
482
  throw new JournalLockedError(`Execution journal lease is ${status}`, { journalPath });
@@ -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", "memory_migration", "pi"]);
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: z.union([RuntimeSchema, z.literal("memory_migration")]),
322
+ runtime: RuntimeSchema,
323
323
  model: z.string().optional(),
324
324
  resumed: z.boolean(),
325
325
  finalText: z.string().optional(),
@@ -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 && detectLocalMemoryStorageVersion(context.workspace.memory) !== 2 ? renderLocalMemoryV2WriteGuard() : ""}`, baseSystemPromptBudget, {
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 }),
@@ -302,6 +302,16 @@ export function createHostExecutionCoordinator(options = {}) {
302
302
  }
303
303
  let releasePromise = null;
304
304
  return {
305
+ protectProcessGroup: async (pid) => {
306
+ const processIdentity = await defaultProcessController.inspectIdentity(pid);
307
+ if (processIdentity === null)
308
+ throw new Error("maintenance worker identity is absent");
309
+ for (const lease of leases) {
310
+ if (!lease.protectProcessGroup)
311
+ throw new Error("lease process-group protection is unavailable");
312
+ await lease.protectProcessGroup({ pid, processIdentity });
313
+ }
314
+ },
305
315
  release: () => {
306
316
  if (releasePromise !== null)
307
317
  return releasePromise;
@@ -2,9 +2,8 @@
2
2
  * 本机执行提示词的合成与预算裁剪。
3
3
  *
4
4
  * server 提示词是权威部分,绝不裁剪;项目工作区提示词是绑定执行的强制部分,放不下就直接失败;
5
- * 本机事实与旧格式预览可按预算省略;schema2身份和置顶规则必须保留,放不下则拒绝执行。
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"), options.memoryQuery);
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");
@@ -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";
@@ -38,22 +38,14 @@ import { formatProjectSkillRuntimeWarning } from "./project-skills/runtime-warni
38
38
  import { projectSkillRuntimeDirectories, } from "./project-skills/runtime-launch.js";
39
39
  import { logIdentityRosterRuntimePrepared } from "./identity-roster-telemetry.js";
40
40
  import { diffMemoryPruneNotes, evaluateMemoryPrunePostcondition, inspectMemoryPruneFilesWithinDeadline, logMemoryPruneDiagnosticsFailure, memoryPruneSnapshotFields, parseMemoryPruneTraceId, } from "./memory-prune-diagnostics.js";
41
- import { checkReleaseArtifactGuardBeforeCommit, throwReleaseArtifactGuardFailure } from "./local-memory/release-artifact-guard.js";
42
41
  import { createLocalMemoryTelemetry, logLocalMemoryContextPrepareFailure, logLocalMemoryDiagnosticsFailure, } from "./local-memory-telemetry.js";
43
- import { LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS } from "./local-memory-diagnostics.js";
44
42
  import { CodexStartupStageParser } from "./codex-startup-stage.js";
45
43
  import { formatRuntimeStartupError } from "./runtime-startup-error.js";
46
44
  import { logRuntimeEnvironmentContract } from "./runtime-environment-telemetry.js";
47
- import { commitLocalMemoryResultAfterExit, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
45
+ import { commitLocalMemoryResultAfterExit, initializeLocalMemoryV2ForWorkspace, isLocalMemoryV2Eligible, localMemoryResultPath as makeLocalMemoryResultPath, } from "./local-memory/runtime.js";
48
46
  import { prepareLocalMemoryPrompt } from "./local-memory/prompt.js";
49
- import { reportLocalMemoryV2Deferred } from "./local-memory/rollout.js";
50
- import { prepareExecutionMemoryWorkspace } from "./local-memory/execution-workspace.js";
47
+ import { reportLocalMemoryV2Deferred, resolveLocalMemoryV2Rollout } from "./local-memory/rollout.js";
51
48
  import { localMemoryV2Environment } from "./config.js";
52
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
53
- import { MEMORY_STATE_GUARD_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY } from "./memory-migration-protocol.js";
54
- import { resolveLocalMemoryWriterMode } from "./local-memory/writer-mode.js";
55
- import { logLocalMemoryCommitApplied } from "./local-memory/commit-telemetry.js";
56
- import { shouldUseMigrationDeferredPending } from "./local-memory/migration-pending.js";
57
49
  export class RuntimeRunningCallbackTimeoutError extends Error {
58
50
  constructor(runtime, timeoutMs) {
59
51
  super(`${runtime} runtime-running callback timed out after ${timeoutMs}ms`);
@@ -67,6 +59,7 @@ const STDERR_TAIL_CAP = 2_000;
67
59
  // the old failure mode where a callback could hold execution slots forever.
68
60
  const RUNTIME_RUNNING_CALLBACK_TIMEOUT_MS = 30_000;
69
61
  const MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS = 2_000;
62
+ const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 250;
70
63
  const CLAUDE_INSTRUCTION_WARNING = "claude_additional_directory_instructions_unverified";
71
64
  const CODEX_COLLABORATION_EXEC_WARNING = "codex_collaboration_disabled_on_exec";
72
65
  function withoutClaudeInstructionFlag(env) {
@@ -218,12 +211,37 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
218
211
  : "high"
219
212
  : runtime.reasoning;
220
213
  const providerFp = providerFingerprint(runtime.name, providerConfig);
221
- const preparedMemory = await prepareExecutionMemoryWorkspace(input, isolatedMaintenance, dependencies.cancellation);
222
- let workspace = preparedMemory.workspace;
223
- const memoryRollout = preparedMemory.mode;
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 };
224
243
  input = { ...input, localMemoryV2Enabled: memoryRollout.enabled };
225
- const localMemoryV2Initialized = preparedMemory.initialized;
226
- let localMemoryWriterMode = resolveLocalMemoryWriterMode(workspace, memoryRollout);
244
+ const localMemoryV2Initialized = await initializeLocalMemoryV2ForWorkspace(workspace, memoryRollout.enabled);
227
245
  let materialized = null;
228
246
  let knownAttachmentDirectory = null;
229
247
  const startupReservationState = { current: null };
@@ -234,7 +252,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
234
252
  let memoryPruneBeforeSnapshot = null;
235
253
  let memoryPruneSharedWriteKey = null;
236
254
  let localMemoryResultPath = null;
237
- let migrationDeferredPending = false;
238
255
  let environmentSnapshot;
239
256
  let executionWorkspace = workspace;
240
257
  let localMemoryTelemetry = null;
@@ -373,41 +390,29 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
373
390
  const attachmentPlan = routeRuntimeAttachments(runtime.name, materialized?.attachments ?? []);
374
391
  let baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
375
392
  memoryPruneTraceId = isolatedMaintenance ? null : parseMemoryPruneTraceId(baseWakePrompt);
376
- localMemoryWriterMode = resolveLocalMemoryWriterMode(executionWorkspace, memoryRollout);
377
- migrationDeferredPending = shouldUseMigrationDeferredPending({ pruneTraceId: memoryPruneTraceId,
378
- writerMode: localMemoryWriterMode, rolloutEnabled: memoryRollout.enabled,
379
- legacyFallback: memoryRollout.legacyFallback,
380
- failure: memoryRollout.failure, memory: executionWorkspace.memory });
381
- if (memoryRollout.deferred || migrationDeferredPending)
382
- reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune", memoryRollout.legacyFallback, memoryRollout.failure, migrationDeferredPending);
383
- if (memoryPruneTraceId !== null && localMemoryWriterMode === "blocked" && !migrationDeferredPending) {
384
- throw new Error(memoryRollout.failure === "inspection_failed" ? "local_memory_inspection_failed" : "local_memory_v2_requires_migration");
385
- }
393
+ if (memoryRollout.deferred)
394
+ reportLocalMemoryV2Deferred(input.executionId, input.handle, memoryPruneTraceId !== null || input.maintenancePurpose === "memory_prune");
386
395
  if (memoryPruneTraceId !== null) {
387
396
  executionWorkspace = await resolveMemoryPruneWorkLog(executionWorkspace, input);
388
397
  promptContext = { ...promptContext, workspace: executionWorkspace };
389
398
  baseWakePrompt = `${resolvePrompt(input.wakePrompt, promptContext)}${attachmentPlan.promptSuffix}`;
390
399
  }
391
- if ((localMemoryWriterMode === "v2" && memoryPruneTraceId !== null
400
+ if (input.localMemoryV2Enabled === true && memoryPruneTraceId !== null
392
401
  && isLocalMemoryV2Eligible({
393
402
  enabledForHandle: true,
394
403
  agentHomePreexisted: workspace.agentHomePreexisted === true,
395
404
  memorySeedCreated: executionWorkspace.memorySeedCreated === true && (localMemoryV2Initialized || workspace.localMemoryV2Initialized !== false),
396
405
  localMemoryV2Initialized,
397
406
  memoryPrune: true,
398
- })) || migrationDeferredPending) {
407
+ })) {
399
408
  localMemoryResultPath = makeLocalMemoryResultPath(executionWorkspace.runDir, input.executionId);
400
409
  }
401
410
  const memoryPrompt = await prepareLocalMemoryPrompt({
402
411
  agentDir: executionWorkspace.dir, executionId: input.executionId, agentHandle: input.handle,
403
412
  enabled: input.localMemoryV2Enabled === true, query: baseWakePrompt,
404
413
  resultPath: localMemoryResultPath, maxBytes: input.maxSystemPromptBytes,
405
- storageVersion: detectLocalMemoryStorageVersion(executionWorkspace.memory),
406
- strictRollout: !isolatedMaintenance && !memoryRollout.legacyFallback && (localMemoryV2Environment(input.launch.serverUrl) === "dev"
407
- || detectLocalMemoryStorageVersion(executionWorkspace.memory) === 2),
414
+ strictRollout: !isolatedMaintenance && localMemoryV2Environment(input.launch.serverUrl) === "dev",
408
415
  deferred: memoryRollout.deferred,
409
- ...(memoryRollout.legacyFallback ? { legacyFallback: true } : {}),
410
- ...(migrationDeferredPending ? { migrationDeferred: true } : {}),
411
416
  renderBase: (reservedPromptBytes) => resolvePrompt(input.systemPrompt, { ...promptContext, reservedPromptBytes }),
412
417
  });
413
418
  localMemoryResultPath = memoryPrompt.resultPath;
@@ -523,7 +528,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
523
528
  localMemoryTelemetry?.contextPrepared({
524
529
  systemPrompt: effectiveSystemPrompt,
525
530
  memory: executionWorkspace.memory,
526
- memoryQuery: baseWakePrompt,
527
531
  resumed: resuming,
528
532
  ...(executionWorkspace.memorySeedCreated === undefined
529
533
  ? {}
@@ -897,21 +901,15 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
897
901
  if (localMemoryResultPath !== null) {
898
902
  memoryPruneFailurePhase = "memory_commit";
899
903
  const resultPath = localMemoryResultPath;
900
- const commit = async () => {
901
- if (exitCode === 0) {
902
- const releaseGuard = await checkReleaseArtifactGuardBeforeCommit({ runDir: executionWorkspace.runDir, scopeKey: memoryPruneTraceId, before: memoryPruneBeforeSnapshot, inspectAfter: () => inspectMemoryPruneFilesWithinDeadline(executionWorkspace.dir, executionWorkspace.runDir, executionWorkspace.workLogPath, dependencies.memoryPruneDiagnosticsTimeoutMs ?? MEMORY_PRUNE_DIAGNOSTICS_TIMEOUT_MS) });
903
- if (releaseGuard.blocked) {
904
- memoryPruneFailurePhase = "memory_artifact_guard";
905
- throwReleaseArtifactGuardFailure({ result: releaseGuard, executionId: input.executionId,
906
- agentHandle: input.handle, taskKey: input.taskKey, pruneTraceId: memoryPruneTraceId });
907
- }
908
- }
909
- return commitLocalMemoryResultAfterExit({ resultPath, agentDir: executionWorkspace.dir, executionId: input.executionId,
910
- pruneTraceId: memoryPruneTraceId, exitCode, workLogPath: executionWorkspace.workLogPath,
911
- expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
912
- ...(localMemoryWriterMode === "v2" ? { writerCapabilities: [MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY], requiredStorageVersion: 2 } : {}),
913
- ...(migrationDeferredPending ? { migrationDeferredPending: true } : {}) });
914
- };
904
+ const commit = () => commitLocalMemoryResultAfterExit({
905
+ resultPath,
906
+ agentDir: executionWorkspace.dir,
907
+ executionId: input.executionId,
908
+ pruneTraceId: memoryPruneTraceId,
909
+ exitCode,
910
+ workLogPath: executionWorkspace.workLogPath,
911
+ expectedWorkLogSha256: workLogSha256(executionWorkspace.workLog),
912
+ });
915
913
  const committed = exitCode !== 0 ? await commit() : await withWorkLogCommit(workLogExecutionKey(input.launch.agentsRoot, input.handle, input.taskKey), commit, dependencies.cancellation);
916
914
  if (committed.status === "failed" || committed.status === "missing") {
917
915
  const reason = committed.error ?? "local_memory_result_missing";
@@ -924,14 +922,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
924
922
  });
925
923
  throw new Error(`local_memory_commit_failed: ${reason}`);
926
924
  }
927
- if (committed.status === "applied") {
928
- if (localMemoryWriterMode === "v2" && (committed.storageVersionBefore !== 2 || committed.storageVersionAfter !== 2
929
- || committed.committedState !== true)) {
930
- throw new Error("local_memory_commit_failed: local_memory_v2_commit_state_invalid");
931
- }
932
- logLocalMemoryCommitApplied({ executionId: input.executionId, agentHandle: input.handle,
933
- pruneTraceId: memoryPruneTraceId, committed });
934
- }
935
925
  memoryPruneFailurePhase = "runtime_finalize";
936
926
  }
937
927
  const errorTail = [
@@ -972,9 +962,13 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
972
962
  await writeFile(legacyCwdMarker, "1", "utf8");
973
963
  }
974
964
  }
975
- const externalAnswer = runtime.name === "pi" ? externalOutput.lastClosedBlock : externalOutput.lastClosedBlock
976
- ?? (finalText === null ? null : extractExternalAnswer(finalText));
977
- const reportFinalText = runtime.name === "pi" && externalAnswer === null ? null : finalText;
965
+ const externalAnswer = runtime.name === "pi"
966
+ ? externalOutput.lastClosedBlock
967
+ : externalOutput.lastClosedBlock
968
+ ?? (finalText === null ? null : extractExternalAnswer(finalText));
969
+ const reportFinalText = runtime.name === "pi" && externalAnswer === null
970
+ ? null
971
+ : finalText;
978
972
  const cloudReportResult = exitCode === 0
979
973
  ? await deliverCloudReports({
980
974
  facts: cloudReportUpload, ...reportRoots, before: reportSnapshot, finalText: reportFinalText,
@@ -1058,23 +1052,6 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
1058
1052
  after_incomplete_file_count: noteDiff.after_manifest.incomplete_file_count,
1059
1053
  changes: serializedChanges.json,
1060
1054
  });
1061
- const pendingDiff = diffMemoryPruneNotes(memoryPruneBeforeSnapshot.pending_manifest
1062
- ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, snapshot.pending_manifest
1063
- ?? { files: [], total_count: 0, omitted_count: 0, truncated: false });
1064
- const serializedPendingChanges = boundedDiagnosticJsonArray(pendingDiff.changes);
1065
- const pendingChangesTruncated = pendingDiff.truncated || serializedPendingChanges.truncated;
1066
- dslog("memory_prune.pending_changed", "长期记忆 pending 候选变化已评估", {
1067
- execution_id: input.executionId,
1068
- agent_handle: input.handle,
1069
- task_key: input.taskKey,
1070
- prune_trace_id: memoryPruneTraceId,
1071
- change_count: pendingDiff.change_count,
1072
- changes_truncated: pendingChangesTruncated,
1073
- evidence_complete: pendingDiff.evidence_complete && !pendingChangesTruncated,
1074
- before_total_count: pendingDiff.before_manifest.total_count,
1075
- after_total_count: pendingDiff.after_manifest.total_count,
1076
- changes: serializedPendingChanges.json,
1077
- });
1078
1055
  const postcondition = evaluateMemoryPrunePostcondition(memoryPruneBeforeSnapshot, snapshot, {
1079
1056
  executorCompleted: memoryPruneExecutorCompleted,
1080
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((left, right) => (options.relevance?.(right) ?? 0) - (options.relevance?.(left) ?? 0) || compareEntries(left, right));
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
- 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)) {
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 = "只读记忆盘点、schema0/1 转换预演与 schema2 完整性校验,不写文件,不支持 --apply。\n"
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仅代表盘点、格式预演或完整性校验无阻塞,不代表批准迁移。\n";
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 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);
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
- ...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;
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: reportSources.reduce((total, source) => total + source.bytes, 0), files, ...analysis, issues,
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
- deferredFiles,
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, options = {}) {
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
- 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
- }
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
- 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
- }
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 };