@nowcrew/daemon 0.6.93-beta.12 → 0.6.93-beta.13

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 (55) hide show
  1. package/dist/cloud-report-upload.js +10 -1
  2. package/dist/execution-journal.js +1 -3
  3. package/dist/execution-protocol.js +1 -1
  4. package/dist/execution-runner.js +1 -4
  5. package/dist/local-execution-prompt.js +3 -8
  6. package/dist/local-executor.js +39 -63
  7. package/dist/local-memory/index-budget.js +3 -9
  8. package/dist/local-memory/inventory-cli.js +8 -49
  9. package/dist/local-memory/inventory-source.js +8 -15
  10. package/dist/local-memory/pending-query.js +2 -5
  11. package/dist/local-memory/pending-schema.js +1 -1
  12. package/dist/local-memory/pending.js +2 -16
  13. package/dist/local-memory/prompt.js +5 -21
  14. package/dist/local-memory/read-cli.js +76 -98
  15. package/dist/local-memory/rollout.js +11 -79
  16. package/dist/local-memory/runtime.js +13 -181
  17. package/dist/local-memory/write.js +21 -189
  18. package/dist/local-memory-diagnostics.js +13 -94
  19. package/dist/local-memory-telemetry.js +2 -19
  20. package/dist/machine-info.js +0 -3
  21. package/dist/marketplace-protocol.js +1 -1
  22. package/dist/marketplace-remote.js +1 -1
  23. package/dist/marketplace-runner.js +1 -1
  24. package/dist/memory-prune-diagnostics.js +4 -60
  25. package/dist/message-file-preview.js +1 -11
  26. package/dist/prompt.js +2 -6
  27. package/dist/runner.js +1 -1
  28. package/dist/runtimes/claude-thread-runner.js +4 -25
  29. package/dist/serve.js +3 -38
  30. package/dist/token.js +0 -1
  31. package/dist/workspace-control-handler.js +3 -16
  32. package/dist/workspace.js +2 -13
  33. package/package.json +1 -1
  34. package/dist/local-memory/commit-telemetry.js +0 -30
  35. package/dist/local-memory/execution-workspace.js +0 -62
  36. package/dist/local-memory/full-index-search.js +0 -47
  37. package/dist/local-memory/initialization-identity.js +0 -36
  38. package/dist/local-memory/inventory-v2.js +0 -361
  39. package/dist/local-memory/legacy-adapter.js +0 -233
  40. package/dist/local-memory/migration-access.js +0 -242
  41. package/dist/local-memory/migration-cli.js +0 -54
  42. package/dist/local-memory/migration-io.js +0 -199
  43. package/dist/local-memory/migration-pending.js +0 -9
  44. package/dist/local-memory/migration-plan.js +0 -317
  45. package/dist/local-memory/migration-schema.js +0 -55
  46. package/dist/local-memory/migration-state-invariants.js +0 -65
  47. package/dist/local-memory/migration-store.js +0 -414
  48. package/dist/local-memory/migration-tool-entry.js +0 -21
  49. package/dist/local-memory/storage-format.js +0 -152
  50. package/dist/local-memory/writer-mode.js +0 -13
  51. package/dist/memory-migration-controller.js +0 -86
  52. package/dist/memory-migration-inspect.js +0 -108
  53. package/dist/memory-migration-journal.js +0 -14
  54. package/dist/memory-migration-protocol.js +0 -14
  55. package/dist/memory-migration-runtime.js +0 -305
@@ -7,7 +7,6 @@ import { dslog } from "./slog.js";
7
7
  const TRACE_PATTERN = /\[memory-prune trace_id=([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})\]/i;
8
8
  export const MEMORY_PRUNE_HASH_LIMIT_BYTES = 10 * 1024 * 1024;
9
9
  export const MEMORY_PRUNE_NOTES_MANIFEST_LIMIT = 128;
10
- export const MEMORY_PRUNE_PENDING_MANIFEST_LIMIT = 128;
11
10
  export const MEMORY_PRUNE_NOTE_CHANGE_LIMIT = 128;
12
11
  function compareCodeUnits(left, right) {
13
12
  return left < right ? -1 : left > right ? 1 : 0;
@@ -193,50 +192,6 @@ export async function inspectMemoryPruneNotes(homeDir, signal) {
193
192
  ...(signal?.aborted ? { error: "ABORT_ERR" } : {}),
194
193
  };
195
194
  }
196
- export async function inspectMemoryPrunePending(homeDir, signal) {
197
- const pendingDir = join(homeDir, ".crew", "memory", "pending");
198
- try {
199
- const metadata = await lstat(pendingDir);
200
- if (metadata.isSymbolicLink())
201
- return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "symbolic_link" };
202
- if (!metadata.isDirectory())
203
- return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: "ENOTDIR" };
204
- }
205
- catch (error) {
206
- if (error.code === "ENOENT")
207
- return { files: [], total_count: 0, omitted_count: 0, truncated: false };
208
- return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
209
- }
210
- let names;
211
- try {
212
- names = (await readdir(pendingDir, { withFileTypes: true }))
213
- .filter(entry => (entry.isFile() || entry.isSymbolicLink()) && entry.name.endsWith(".json"))
214
- .map(entry => entry.name).sort(compareCodeUnits);
215
- }
216
- catch (error) {
217
- return { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
218
- }
219
- const selected = names.slice(0, MEMORY_PRUNE_PENDING_MANIFEST_LIMIT);
220
- const files = [];
221
- for (const name of selected) {
222
- if (signal?.aborted)
223
- break;
224
- const path = join(pendingDir, name);
225
- try {
226
- if ((await lstat(path)).isSymbolicLink()) {
227
- files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: "symbolic_link" });
228
- continue;
229
- }
230
- }
231
- catch (error) {
232
- files.push({ relative_path: `.crew/memory/pending/${name}`, exists: false, error: diagnosticError(error) });
233
- continue;
234
- }
235
- files.push({ relative_path: `.crew/memory/pending/${name}`, ...await inspectFile(path, false, undefined, signal) });
236
- }
237
- return { files, total_count: names.length, omitted_count: Math.max(0, names.length - selected.length),
238
- truncated: names.length > selected.length, ...(signal?.aborted ? { error: "ABORT_ERR" } : {}) };
239
- }
240
195
  async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal) {
241
196
  const cwdIsHome = runDir === homeDir;
242
197
  const [memory, lessons, cwdMemory, cwdLessons, workLog] = await Promise.all([
@@ -255,17 +210,15 @@ async function inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal
255
210
  };
256
211
  }
257
212
  export async function inspectMemoryPruneFiles(homeDir, runDir, workLogPath, signal) {
258
- const [fixedFiles, notesManifest, pendingManifest] = await Promise.all([
213
+ const [fixedFiles, notesManifest] = await Promise.all([
259
214
  inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal),
260
215
  inspectMemoryPruneNotes(homeDir, signal),
261
- inspectMemoryPrunePending(homeDir, signal),
262
216
  ]);
263
- return { ...fixedFiles, notes_manifest: notesManifest, pending_manifest: pendingManifest };
217
+ return { ...fixedFiles, notes_manifest: notesManifest };
264
218
  }
265
219
  export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, workLogPath, timeoutMs) {
266
220
  const fixedFiles = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneFixedFiles(homeDir, runDir, workLogPath, signal), timeoutMs);
267
221
  let notesManifest;
268
- let pendingManifest;
269
222
  try {
270
223
  notesManifest = await withinMemoryPruneDiagnosticsDeadline((signal) => inspectMemoryPruneNotes(homeDir, signal), timeoutMs);
271
224
  }
@@ -278,13 +231,7 @@ export async function inspectMemoryPruneFilesWithinDeadline(homeDir, runDir, wor
278
231
  error: diagnosticError(error),
279
232
  };
280
233
  }
281
- try {
282
- pendingManifest = await withinMemoryPruneDiagnosticsDeadline(signal => inspectMemoryPrunePending(homeDir, signal), timeoutMs);
283
- }
284
- catch (error) {
285
- pendingManifest = { files: [], total_count: 0, omitted_count: 0, truncated: false, error: diagnosticError(error) };
286
- }
287
- return { ...fixedFiles, notes_manifest: notesManifest, pending_manifest: pendingManifest };
234
+ return { ...fixedFiles, notes_manifest: notesManifest };
288
235
  }
289
236
  export async function withinMemoryPruneDiagnosticsDeadline(operation, timeoutMs) {
290
237
  const controller = new AbortController();
@@ -414,7 +361,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
414
361
  && (after.cwd_memory.lessons_reference_count ?? 0) > 0,
415
362
  cwd_lessons_changed: !after.cwd_lessons.same_as_shared_path
416
363
  && fileChanged(before.cwd_lessons, after.cwd_lessons),
417
- pending_changed: diffMemoryPruneNotes(before.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }, after.pending_manifest ?? { files: [], total_count: 0, omitted_count: 0, truncated: false }).change_count > 0,
418
364
  };
419
365
  let outcome;
420
366
  if (!execution.executorCompleted)
@@ -442,8 +388,6 @@ export function evaluateMemoryPrunePostcondition(before, after, execution) {
442
388
  outcome = "promoted_without_index_change";
443
389
  else if (facts.shared_memory_changed)
444
390
  outcome = "shared_memory_updated";
445
- else if (facts.pending_changed)
446
- outcome = "pending_updated";
447
391
  else
448
392
  outcome = "no_files_changed";
449
393
  return { outcome, ...facts };
@@ -464,7 +408,7 @@ function memoryPruneFileFactFields(fileFacts) {
464
408
  }
465
409
  return fields;
466
410
  }
467
- export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest, pending_manifest: _pendingManifest, ...fileFacts }) {
411
+ export function memoryPruneSnapshotFields({ notes_manifest: _notesManifest, ...fileFacts }) {
468
412
  return memoryPruneFileFactFields(fileFacts);
469
413
  }
470
414
  export function logMemoryPruneDiagnosticsFailure(input, traceId, phase, error) {
@@ -9,7 +9,6 @@ import { z } from "zod";
9
9
  import { isInternalEnvironmentPath } from "./workspace-fs.js";
10
10
  import { opaqueKey, safeKey } from "./workspace.js";
11
11
  import { AgentHandleSchema } from "./execution-protocol.js";
12
- import { withMemoryAccess } from "./local-memory/migration-access.js";
13
12
  const MAX_BYTES = 256 * 1024;
14
13
  const MAX_BINARY_BYTES = 8 * 1024 * 1024;
15
14
  const MAX_CONCURRENT_READS = 4;
@@ -232,16 +231,7 @@ export async function readSharedMessageFile(agentsRoot, rawRequest, registry) {
232
231
  throw new SharedFileError("file_preview_busy");
233
232
  activeReads++;
234
233
  try {
235
- const candidate = await resolveCandidate(agentsRoot, parsed.data, registry);
236
- const agentRoot = await realpath(resolve(agentsRoot, parsed.data.handle)).catch((error) => {
237
- if (error.code === "ENOENT")
238
- return undefined;
239
- throw error;
240
- });
241
- // 记忆保护只用于该Agent根内资料,独立项目文件仍按原执行授权读取。
242
- return await (agentRoot && inside(agentRoot, candidate.path)
243
- ? withMemoryAccess(agentRoot, () => readCandidate(candidate))
244
- : readCandidate(candidate));
234
+ return await readCandidate(await resolveCandidate(agentsRoot, parsed.data, registry));
245
235
  }
246
236
  catch (error) {
247
237
  if (error instanceof SharedFileError)
package/dist/prompt.js CHANGED
@@ -5,7 +5,6 @@
5
5
  * claim-before-work、freshness/draft、"做完所有事再停"、inbox notice 语义、
6
6
  * 分层记忆与压缩安全、协作礼仪。措辞为本项目原创。
7
7
  */
8
- import { detectLocalMemoryStorageVersion, selectLocalMemoryPreview } from "./local-memory/storage-format.js";
9
8
  /** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
10
9
  * 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
11
10
  export const WORKLOG_INJECT_CAP = 8000;
@@ -68,10 +67,7 @@ export function capWorkLogForInject(workLog, cap = WORKLOG_INJECT_CAP) {
68
67
  const tail = workLog.slice(-WORKLOG_TAIL);
69
68
  return `${head}\n…(work-log 过长已截去中段,全文用 Read 读 $CREW_TASK_LOG)\n${tail}`;
70
69
  }
71
- export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY, query = "") {
72
- if (detectLocalMemoryStorageVersion(memory) === 2) {
73
- return selectLocalMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes }).memory;
74
- }
70
+ export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
75
71
  if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
76
72
  return memory;
77
73
  const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
@@ -346,7 +342,7 @@ ${interactiveTaskRules}${freshnessRule}${externalReplyRule}${collaborationEtique
346
342
 
347
343
  ## Workspace 与分层记忆(CRITICAL — 索引+按需,配合上面的并行规则)
348
344
  你的持久记忆在 \`$CREW_HOME\`(跨你所有任务共享),按“常驻索引 + 功能明细 + 当前工作”分层:
349
- 1. **MEMORY.md = 常驻索引 + 角色**:提示词末尾可能只有它的有界预览,预算不足时也可能省略。预览未命中不等于没有记忆。旧版索引可能包含 \`scope\`;V2 规范字段为 \`id/file/type/status/summary/triggers/priority/revision/anchor/updated\`(可选 \`review_after\`),不含 \`scope\`。其中 file 是相对于 \`$CREW_HOME\` 的路径(例如 \`notes/testing.md\`);需要历史约束、经验或偏好时先检索完整索引,再按需分段读取。
345
+ 1. **MEMORY.md = 常驻索引 + 角色**:提示词末尾可能只有它的有界预览,预算不足时也可能省略。预览未命中不等于没有记忆。索引条目包含 \`file/type/scope/summary/triggers/priority/updated\`,其中 file 是相对于 \`$CREW_HOME\` 的路径(例如 \`notes/testing.md\`);需要历史约束、经验或偏好时先检索完整索引,再按需分段读取。
350
346
  2. **notes/<topic>.md = 功能明细**:按用户偏好、项目规则、测试、调试、Git、事故复盘等主题拆分。优先读取与当前任务匹配的文件;\`notes/lessons.md\` 只保留未分类经验和历史兼容内容,不是所有新经验的默认目的地。普通任务默认只读不写,但当前用户明确要求时可以按最小范围更新;任务结束后的专门收尾执行仍按收尾指令复核。
351
347
  - 对旧版 MEMORY.md,如果条目没有完整结构化字段但明确指向 \`notes/<file>.md\`,把它视为 legacy 索引;仍需结合条目标题/链接和当前请求判断相关性,不要因为缺少 summary/triggers 就假定正文已注入。
352
348
  3. **本任务 work-log = \`$CREW_TASK_LOG\`**:每任务一份(别的运行不会碰),记当前阶段/下一步/卡点/关键结论。系统已把它的当前内容注入末尾让你续上;有进展就更新它——这是你**唯一**该在任务中持续写的记忆文件。**work-log 是现状快照,不是流水账**:保持 ≤100 行,更新时重写全文替换过时内容,不要无限追加(过长会被注入截断)。
package/dist/runner.js CHANGED
@@ -80,7 +80,7 @@ async function runAgentWithMachineEnvironment(config, input, onActivity = defaul
80
80
  ? { scheduledExternalNotificationPolicy: input.scheduled.externalNotificationPolicy }
81
81
  : {}),
82
82
  ...(input.wakeOrigin ? { wakeOrigin: input.wakeOrigin } : {}),
83
- memory: capMemoryForInject(workspace.memory, undefined, undefined, baseWake),
83
+ memory: capMemoryForInject(workspace.memory),
84
84
  ...(resuming ? {} : { workLog: capWorkLogForInject(workspace.workLog) }),
85
85
  collaborationToolsEnabled: config.codexCollaborationTools && runtime === "codex",
86
86
  localMemoryV2Enabled: workspace.localMemoryV2Enabled ?? localMemoryV2Enabled,
@@ -12,14 +12,6 @@ const InputSchema = z.object({ bin: z.string().min(1), args: z.array(z.string())
12
12
  wakePrompt: z.string().min(1), sessionId: z.string().min(1).optional(), resume: z.boolean() }).strict();
13
13
  class PreflightError extends Error {
14
14
  }
15
- // Claude waits for every merged SessionStart hook before acknowledging SDK
16
- // initialization. Those hooks may legitimately outlive the old 30s adapter
17
- // deadline. Cap this phase below the daemon's default 120s fail-closed startup
18
- // window so wrapper failure propagation and supervisor-owned tree cleanup retain
19
- // margin. The 100s cap leaves a nominal 20s outside this phase; this runner's
20
- // local TERM/KILL cleanup is itself bounded to 2s.
21
- // Resume and cold recovery share this one budget; hook activity never extends it.
22
- export const CLAUDE_INITIALIZE_TIMEOUT_MS = 100_000;
23
15
  function hookCommand(path, nonce) {
24
16
  const source = import.meta.url.endsWith(".ts");
25
17
  const hook = fileURLToPath(new URL(`./claude-session-start-hook.${source ? "ts" : "js"}`, import.meta.url));
@@ -60,9 +52,6 @@ async function bounded(promise, milliseconds, label) {
60
52
  clearTimeout(timer);
61
53
  }
62
54
  }
63
- export function waitForClaudeInitialize(initialized, timeoutMs = CLAUDE_INITIALIZE_TIMEOUT_MS) {
64
- return bounded(initialized, timeoutMs, "claude_initialize_timeout");
65
- }
66
55
  async function attempt(input, sessionId, resume, options) {
67
56
  const proofDir = await mkdtemp(join(tmpdir(), "nowwork-claude-session-"));
68
57
  const proofPath = join(proofDir, "proof.json");
@@ -199,7 +188,7 @@ async function attempt(input, sessionId, resume, options) {
199
188
  try {
200
189
  try {
201
190
  await send({ type: "control_request", request_id: initId, request: { subtype: "initialize" } });
202
- await waitForClaudeInitialize(initialized.promise, options.initializeTimeoutMs);
191
+ await bounded(initialized.promise, options.initializeTimeoutMs ?? 30_000, "claude_initialize_timeout");
203
192
  const proof = JSON.parse(await readFile(proofPath, "utf8"));
204
193
  if (proof.nonce !== nonce || proof.sessionId !== sessionId || proof.source !== (resume ? "resume" : "startup")) {
205
194
  throw new Error("claude_session_proof_mismatch");
@@ -249,25 +238,15 @@ async function attempt(input, sessionId, resume, options) {
249
238
  }
250
239
  export async function runClaudeThread(input, options = {}) {
251
240
  const sessionId = input.sessionId ?? randomUUID();
252
- const initializeBudgetMs = options.initializeTimeoutMs ?? CLAUDE_INITIALIZE_TIMEOUT_MS;
253
- const now = options.now ?? (() => performance.now());
254
- const initializeDeadline = now() + Math.max(0, initializeBudgetMs);
255
- const runAttempt = (attemptSessionId, resume) => {
256
- const remainingMs = Math.max(0, Math.ceil(initializeDeadline - now()));
257
- if (remainingMs === 0)
258
- return Promise.reject(new PreflightError("claude_native_session_unconfirmed"));
259
- return attempt(input, attemptSessionId, resume, { ...options, initializeTimeoutMs: remainingMs });
260
- };
261
241
  try {
262
- return await runAttempt(sessionId, input.resume);
242
+ return await attempt(input, sessionId, input.resume, options);
263
243
  }
264
244
  catch (error) {
265
245
  // The failed attempt never received a business user turn or entered its
266
- // model/tool loop. Native startup hooks may run again on cold recovery,
267
- // within the original aggregate initialize deadline.
246
+ // model/tool loop. Native startup hooks may run again on cold recovery.
268
247
  if (!input.resume || !(error instanceof PreflightError || error instanceof ThreadDeliveryColdStartRequired))
269
248
  throw error;
270
- return runAttempt(randomUUID(), false);
249
+ return attempt(input, randomUUID(), false, options);
271
250
  }
272
251
  }
273
252
  if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
package/dist/serve.js CHANGED
@@ -14,7 +14,6 @@ import { reconcileExecutionJournal } from "./execution-recovery.js";
14
14
  import { initReleaseSlog, reportDaemonConnectionClose, reportReleaseError } from "./release-channel-guard.js";
15
15
  import { handleWorkspaceControlRequest } from "./workspace-control-handler.js";
16
16
  import { inspectRaftWorkspace, importRaftWorkspace } from "./workspace-import.js";
17
- import { withMemoryAccess } from "./local-memory/migration-access.js";
18
17
  import { normalizeScheduledContext } from "./scheduled-report.js";
19
18
  import { formatDaemonLogLine } from "./log-format.js";
20
19
  import { reportAgentRunComplete } from "./scheduled-run-report.js";
@@ -51,8 +50,6 @@ import { probeRuntimeHealth } from "./runtime-health.js";
51
50
  import { createServeMarketplace } from "./marketplace-serve.js";
52
51
  import { buildControlPlaneUrl } from "./control-plane-url.js";
53
52
  import { resolveDaemonLocalStateRootFromEnv } from "./daemon-local-home.js";
54
- import { createMemoryMigrationController } from "./memory-migration-controller.js";
55
- import { createMemoryMigrationJournal, replayExecutionJournals } from "./memory-migration-journal.js";
56
53
  import { bindStorageServerIdentity, createStorageIdentityAdmission, } from "./storage-serve-adapter.js";
57
54
  export { buildControlPlaneUrl } from "./control-plane-url.js";
58
55
  // normalize.ts 的活动种类 → activity 枚举
@@ -74,8 +71,6 @@ export function serve(config, opts = {}) {
74
71
  let reconnectTimer = null;
75
72
  let stopPromise = null;
76
73
  let executionJournal;
77
- let migrationExecutionJournal;
78
- let migrationJournalReady = false;
79
74
  const injectedExecutionJournal = opts.execution?.journal;
80
75
  const agentMemory = opts.execution?.dependencies?.agentMemory
81
76
  ?? (config.agentMemory === null ? undefined : createAgentMemoryBridge(config.agentMemory));
@@ -90,15 +85,6 @@ export function serve(config, opts = {}) {
90
85
  startupGapMs: config.executionLimits.startupGapMs,
91
86
  });
92
87
  const localSlots = createSharedSlotManager(config.executionLimits);
93
- const memoryMigrations = createMemoryMigrationController({ config, coordinator: hostCoordinator,
94
- isAdmissionClosed: () => stopped || storageIdentityAdmission.isAdmissionClosed(),
95
- journal: () => { if (!migrationJournalReady)
96
- throw new Error("memory_migration_journal_unavailable"); return migrationExecutionJournal; }, send: frame => {
97
- if (frame.type === "execution:completed")
98
- completionRetransmitter.track(frame);
99
- else if (ws?.readyState === WebSocket.OPEN)
100
- ws.send(JSON.stringify(frame));
101
- } });
102
88
  const sharedSlots = hostCoordinatedSlotManager(localSlots, hostCoordinator);
103
89
  const concurrencyTelemetry = createExecutionConcurrencyTelemetry(sharedSlots, {
104
90
  ...(opts.execution?.concurrencySnapshotIntervalMs === undefined
@@ -246,7 +232,7 @@ export function serve(config, opts = {}) {
246
232
  return null;
247
233
  };
248
234
  const sendSnapshot = async (reqId) => {
249
- const entries = (await replayExecutionJournals(executionJournal, migrationExecutionJournal)).map(snapshotEntry).filter((entry) => entry !== null);
235
+ const entries = (await executionJournal.replay()).map(snapshotEntry).filter((entry) => entry !== null);
250
236
  safeExecutionSend(ExecutionSnapshotSchema.parse({
251
237
  type: "execution:snapshot", protocolVersion: 1, reqId, entries,
252
238
  }));
@@ -327,12 +313,6 @@ export function serve(config, opts = {}) {
327
313
  machineRuntime.open(openedSocket, { capabilities: capabilities.machineHello, beforePublish: Promise.resolve(), connectionId });
328
314
  opts.onOpen?.(ws);
329
315
  void sendSnapshot(`reconnect-${randomUUID()}`);
330
- // 普通回执不等待维护日志IO;两类记录分别重放到同一发送器。
331
- void migrationExecutionJournal.replay().then(entries => {
332
- for (const entry of entries)
333
- if (entry.completion && !entry.completionAcknowledged)
334
- completionRetransmitter.track(entry.completion);
335
- }).catch(() => dslog("memory_migration.journal_unavailable", "维护回执重放失败,普通回执继续", { level: "ERROR" }));
336
316
  void executionJournal.replay()
337
317
  .then((entries) => {
338
318
  for (const entry of entries) {
@@ -407,8 +387,7 @@ export function serve(config, opts = {}) {
407
387
  return;
408
388
  if (frame.type === "execution:completion-ack") {
409
389
  try {
410
- const journal = await executionJournal.get(frame.executionId) ? executionJournal : migrationExecutionJournal;
411
- await journal.acknowledgeCompletion(frame.executionId);
390
+ await executionJournal.acknowledgeCompletion(frame.executionId);
412
391
  const attempts = completionRetransmitter.acknowledge(frame.executionId);
413
392
  dslog("execution.completion_acknowledged", "execution completion ACK 已持久化", {
414
393
  execution_id: frame.executionId,
@@ -434,8 +413,6 @@ export function serve(config, opts = {}) {
434
413
  return;
435
414
  }
436
415
  if (frame.type === "execution:cancel") {
437
- if (memoryMigrations.cancel(frame.executionId))
438
- return;
439
416
  if (!knownExecutionHashes.has(frame.executionId)
440
417
  && !executionRuns.has(frame.executionId)
441
418
  && await executionJournal.get(frame.executionId) === null)
@@ -668,7 +645,6 @@ export function serve(config, opts = {}) {
668
645
  const execution = executeProtocol(config, spec, {
669
646
  ...opts.execution?.dependencies,
670
647
  machineEnvironment: machineRuntime.provider,
671
- ...(memoryMigrations.getIdentity() === null ? {} : { controlIdentity: memoryMigrations.getIdentity() }),
672
648
  capabilities: capabilities.executionRunner,
673
649
  projectSkills: opts.execution?.dependencies?.projectSkills ?? initializedProjectSkills,
674
650
  abilityRelease: opts.execution?.dependencies?.abilityRelease ?? agentAbilityRuntime.materializer,
@@ -759,8 +735,6 @@ export function serve(config, opts = {}) {
759
735
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
760
736
  const r = msg;
761
737
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
762
- if (r.workspaceId && r.machineId)
763
- memoryMigrations.setIdentity({ workspaceId: r.workspaceId, machineId: r.machineId });
764
738
  if (opts.storageRuntime !== undefined && typeof r.workspaceId === "string" && typeof r.machineId === "string") {
765
739
  const bindingSocket = ws;
766
740
  bindStorageServerIdentity(opts.storageRuntime, { workspaceId: r.workspaceId, machineId: r.machineId }, {
@@ -827,8 +801,6 @@ export function serve(config, opts = {}) {
827
801
  }
828
802
  catch { /* server 会按请求超时处理 */ } }))
829
803
  return;
830
- if (await memoryMigrations.handle(msg))
831
- return;
832
804
  if (await agentAbilityRuntime.tryHandleControlMessage(msg, serverCapabilities, (frame) => ws?.send(JSON.stringify(frame))))
833
805
  return;
834
806
  // 导入 raft agent 工作区:inspect 反填 name/description;import 复制用户内容
@@ -843,7 +815,7 @@ export function serve(config, opts = {}) {
843
815
  try {
844
816
  const data2 = req.type === "raft:inspect"
845
817
  ? await inspectRaftWorkspace(req.path)
846
- : await withMemoryAccess(join(config.agentsRoot, req.handle), () => importRaftWorkspace(req.path, join(config.agentsRoot, req.handle)), { create: true });
818
+ : await importRaftWorkspace(req.path, join(config.agentsRoot, req.handle));
847
819
  if (req.type === "raft:import") {
848
820
  log(`📦 已导入 raft 工作区 → ${req.handle}: 复制 ${data2.copied.length} 项`);
849
821
  }
@@ -1235,7 +1207,6 @@ export function serve(config, opts = {}) {
1235
1207
  return;
1236
1208
  // server 能力随连接失效;下个连接的 ready 帧重新声明(防降级重连后沿用过期能力)。
1237
1209
  serverCapabilities = new Set();
1238
- memoryMigrations.clearIdentity();
1239
1210
  completionRetransmitter.pause();
1240
1211
  // 4001 = 控制面应用级「鉴权失败」关闭码(见 server control-plane.ts)。即使上面的
1241
1212
  // error 帧因 close 抢先而丢失,也能据关闭码识别这是凭证失效——退避拉满,不再每秒热循环。
@@ -1288,9 +1259,6 @@ export function serve(config, opts = {}) {
1288
1259
  writeStderr: (line) => process.stderr.write(line),
1289
1260
  });
1290
1261
  await runProjectSkillStartupCleanup(config.agentsRoot, executionJournal, dslog);
1291
- migrationExecutionJournal = createMemoryMigrationJournal(config.agentsRoot);
1292
- await migrationExecutionJournal.reconcileAfterRestart().then(() => { migrationJournalReady = true; })
1293
- .catch(() => dslog("memory_migration.journal_unavailable", "迁移日志恢复失败,普通执行继续,迁移请求单独报错", { level: "ERROR" }));
1294
1262
  await connect();
1295
1263
  })();
1296
1264
  const stop = () => {
@@ -1308,7 +1276,6 @@ export function serve(config, opts = {}) {
1308
1276
  const webSocketClosed = closeWebSocketWithinDeadline(ws, deadline.signal);
1309
1277
  const pending = [
1310
1278
  machineRuntime.stop(),
1311
- memoryMigrations.stop(),
1312
1279
  webSocketClosed,
1313
1280
  updateController.drain(),
1314
1281
  marketplace.stop(),
@@ -1327,8 +1294,6 @@ export function serve(config, opts = {}) {
1327
1294
  if (executionJournal !== undefined) {
1328
1295
  await executionJournal.close({ signal: deadline.signal });
1329
1296
  }
1330
- if (migrationExecutionJournal !== undefined)
1331
- await migrationExecutionJournal.close({ signal: deadline.signal });
1332
1297
  }
1333
1298
  finally {
1334
1299
  deadline.dispose();
package/dist/token.js CHANGED
@@ -7,7 +7,6 @@ export async function mintAgentToken(serverUrl, machineToken, handle, displayNam
7
7
  },
8
8
  // 把本轮唤醒的线程根钉到 per-launch 令牌:服务端据此让 task/消息默认绑/回当前线程。
9
9
  body: JSON.stringify({
10
- memoryStateGuardVersion: 1,
11
10
  handle,
12
11
  ...(displayName ? { displayName } : {}),
13
12
  ...(options.wakeThreadRoot ? { wakeThreadRoot: options.wakeThreadRoot } : {}),
@@ -1,22 +1,9 @@
1
1
  /** 控制面文件/技能查看的按需处理,独立于执行生命周期。 */
2
- import { lstat } from "node:fs/promises";
3
- import { join, resolve } from "node:path";
2
+ import { join } from "node:path";
4
3
  import { readSharedMessageFile } from "./message-file-preview.js";
5
4
  import { listWorkspace, readWorkspaceFile } from "./workspace-fs.js";
6
5
  import { listSkills } from "./skills.js";
7
6
  import { listRuntimeModels } from "./list-models.js";
8
- import { withMemoryAccess } from "./local-memory/migration-access.js";
9
- async function listProtectedWorkspace(root, path) {
10
- try {
11
- await lstat(root);
12
- }
13
- catch (error) {
14
- if (error.code !== "ENOENT")
15
- throw error;
16
- return { root: resolve(root), path: path.replace(/^\/+/, ""), entries: [] };
17
- }
18
- return withMemoryAccess(root, () => listWorkspace(root, path));
19
- }
20
7
  export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, models = listRuntimeModels) {
21
8
  if (!raw || typeof raw !== "object")
22
9
  return null;
@@ -28,8 +15,8 @@ export async function handleWorkspaceControlRequest(raw, agentsRoot, registry, m
28
15
  try {
29
16
  const root = join(agentsRoot, request.handle);
30
17
  const data = request.type === "fs:read-shared" ? await readSharedMessageFile(agentsRoot, raw, registry)
31
- : request.type === "fs:list" ? await listProtectedWorkspace(root, request.path)
32
- : request.type === "fs:read" ? await withMemoryAccess(root, () => readWorkspaceFile(root, request.path))
18
+ : request.type === "fs:list" ? await listWorkspace(root, request.path)
19
+ : request.type === "fs:read" ? await readWorkspaceFile(root, request.path)
33
20
  : request.type === "skills:list" ? await listSkills(request.runtime)
34
21
  : { models: await models(request.runtime ?? "") };
35
22
  return { type: "fs:result", reqId: request.reqId, ok: true, data };
package/dist/workspace.js CHANGED
@@ -8,15 +8,12 @@
8
8
  *
9
9
  * spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
10
10
  */
11
- import { mkdir, writeFile, readFile, chmod, access, lstat, realpath } from "node:fs/promises";
11
+ import { mkdir, writeFile, readFile, chmod, access, lstat } from "node:fs/promises";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { dirname, join } from "node:path";
14
14
  import { fileURLToPath, pathToFileURL } from "node:url";
15
15
  import { createRequire } from "node:module";
16
16
  import { dslog } from "./slog.js";
17
- import { MigrationStateSchema } from "./local-memory/migration-schema.js";
18
- import { assertMigrationStateIntegrity } from "./local-memory/migration-state-invariants.js";
19
- import { detectLocalMemoryStorageVersion } from "./local-memory/storage-format.js";
20
17
  /** 文件系统安全的 taskKey:仅留 [\w.-],其余转 _,截断,避免路径穿越/超长。 */
21
18
  export function safeKey(key) {
22
19
  return key.replace(/[^\w.-]+/g, "_").replace(/^[-.]+/, "").slice(0, 80) || "default";
@@ -52,15 +49,7 @@ async function hasLocalMemoryV2Marker(agentDir) {
52
49
  const stat = await lstat(markerPath);
53
50
  if (!stat.isFile() || stat.isSymbolicLink())
54
51
  return false;
55
- if ((await readFile(markerPath, "utf8")) !== "1\n")
56
- return false;
57
- const root = await realpath(agentDir);
58
- const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(memoryDir, "migration.v1.json"), "utf8")));
59
- assertMigrationStateIntegrity(state, state.identity);
60
- return state.phase === "committed" && state.storageVersion === 2
61
- && state.identity.rootFingerprint === createHash("sha256").update(root).digest("hex")
62
- && state.identity.ownerIdentity === String((await lstat(root)).uid)
63
- && detectLocalMemoryStorageVersion(await readFile(join(root, "MEMORY.md"), "utf8")) === 2;
52
+ return (await readFile(markerPath, "utf8")) === "1\n";
64
53
  }
65
54
  catch {
66
55
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.93-beta.12",
3
+ "version": "0.6.93-beta.13",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -1,30 +0,0 @@
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
- }
@@ -1,62 +0,0 @@
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
- }
@@ -1,47 +0,0 @@
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
- }