@nowcrew/daemon 0.6.99-beta.3 → 0.6.99-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 (60) hide show
  1. package/README.md +9 -4
  2. package/dist/cloud-report-upload.js +10 -1
  3. package/dist/computer-main.js +0 -0
  4. package/dist/computer-service.js +30 -5
  5. package/dist/execution-journal.js +1 -3
  6. package/dist/execution-protocol.js +1 -1
  7. package/dist/execution-runner.js +1 -4
  8. package/dist/local-execution-prompt.js +3 -8
  9. package/dist/local-executor.js +39 -64
  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 +13 -181
  20. package/dist/local-memory/write.js +21 -189
  21. package/dist/local-memory-diagnostics.js +15 -96
  22. package/dist/local-memory-telemetry.js +3 -20
  23. package/dist/machine-info.js +0 -3
  24. package/dist/main.js +0 -0
  25. package/dist/marketplace-protocol.js +1 -1
  26. package/dist/marketplace-remote.js +1 -1
  27. package/dist/marketplace-runner.js +1 -1
  28. package/dist/memory-prune-diagnostics.js +4 -60
  29. package/dist/message-file-preview.js +1 -11
  30. package/dist/prompt.js +17 -9
  31. package/dist/runner.js +1 -1
  32. package/dist/runtimes/claude-thread-runner.js +4 -25
  33. package/dist/serve.js +3 -38
  34. package/dist/token.js +0 -1
  35. package/dist/workspace-control-handler.js +3 -16
  36. package/dist/workspace.js +2 -13
  37. package/package.json +10 -8
  38. package/dist/local-memory/commit-telemetry.js +0 -30
  39. package/dist/local-memory/execution-workspace.js +0 -62
  40. package/dist/local-memory/full-index-search.js +0 -47
  41. package/dist/local-memory/initialization-identity.js +0 -36
  42. package/dist/local-memory/inventory-v2.js +0 -362
  43. package/dist/local-memory/legacy-adapter.js +0 -233
  44. package/dist/local-memory/migration-access.js +0 -242
  45. package/dist/local-memory/migration-cli.js +0 -54
  46. package/dist/local-memory/migration-io.js +0 -199
  47. package/dist/local-memory/migration-pending.js +0 -9
  48. package/dist/local-memory/migration-plan.js +0 -432
  49. package/dist/local-memory/migration-schema.js +0 -55
  50. package/dist/local-memory/migration-state-invariants.js +0 -65
  51. package/dist/local-memory/migration-store.js +0 -414
  52. package/dist/local-memory/migration-tool-entry.js +0 -21
  53. package/dist/local-memory/source-path.js +0 -13
  54. package/dist/local-memory/storage-format.js +0 -249
  55. package/dist/local-memory/writer-mode.js +0 -13
  56. package/dist/memory-migration-controller.js +0 -89
  57. package/dist/memory-migration-inspect.js +0 -108
  58. package/dist/memory-migration-journal.js +0 -14
  59. package/dist/memory-migration-protocol.js +0 -14
  60. package/dist/memory-migration-runtime.js +0 -305
@@ -1,5 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
- import { lstat, readFile, readdir, stat } from "node:fs/promises";
2
+ import { readFile, stat } from "node:fs/promises";
3
3
  import { isAbsolute, join, normalize, relative, resolve } from "node:path";
4
4
  import { parseArgs } from "node:util";
5
5
  import { capMemoryForInject, MEMORY_INJECT_CAP } from "./prompt.js";
@@ -8,7 +8,6 @@ const INJECTION_MARKERS = [
8
8
  "## [注入] 你的 MEMORY.md(索引,只读参考)\n",
9
9
  ];
10
10
  const LOCAL_MEMORY_HASH_CAP_BYTES = 10 * 1024 * 1024;
11
- export const LOCAL_MEMORY_DIAGNOSTICS_TIMEOUT_MS = 2_000;
12
11
  function errorCode(error) {
13
12
  const code = error?.code;
14
13
  return typeof code === "string" ? code : error instanceof Error ? error.name : "unknown";
@@ -40,75 +39,30 @@ async function inspectFile(path, signal) {
40
39
  return { ...base, error_code: errorCode(error) };
41
40
  }
42
41
  }
43
- async function inspectPendingDirectory(path, signal) {
44
- let metadata;
45
- try {
46
- metadata = await lstat(path);
47
- }
48
- catch (error) {
49
- const code = errorCode(error);
50
- return code === "ENOENT" ? { exists: false, entry_count: 0 } : { exists: false, error_code: code };
51
- }
52
- const base = { exists: true, mtime_ms: metadata.mtimeMs };
53
- if (metadata.isSymbolicLink() || !metadata.isDirectory())
54
- return { ...base, hash_skipped_reason: "not_regular_file" };
55
- try {
56
- const names = (await readdir(path, { withFileTypes: true }))
57
- .filter(entry => entry.name.endsWith(".json"))
58
- .sort((left, right) => left.name < right.name ? -1 : left.name > right.name ? 1 : 0);
59
- if (names.length > 128)
60
- return { ...base, entry_count: names.length, error_code: "entry_limit" };
61
- const hash = createHash("sha256");
62
- let size = 0;
63
- for (const entry of names) {
64
- if (signal.aborted)
65
- throw signal.reason;
66
- if (!entry.isFile() || entry.isSymbolicLink())
67
- return { ...base, entry_count: names.length, error_code: "invalid_entry" };
68
- const fact = await inspectFile(join(path, entry.name), signal);
69
- if (!fact.exists || fact.sha256 === undefined || fact.error_code !== undefined
70
- || fact.hash_skipped_reason !== undefined)
71
- return { ...base, entry_count: names.length, error_code: "incomplete_entry" };
72
- size += fact.size_bytes ?? 0;
73
- hash.update(entry.name).update("\0").update(fact.sha256).update("\0");
74
- }
75
- return { ...base, size_bytes: size, entry_count: names.length, sha256: hash.digest("hex") };
76
- }
77
- catch (error) {
78
- return { ...base, error_code: errorCode(error) };
79
- }
80
- }
81
- export async function withinLocalMemoryDiagnosticsDeadline(operation, timeoutMs) {
42
+ export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
82
43
  const controller = new AbortController();
83
44
  let timer;
45
+ const inspection = Promise.all([
46
+ inspectFile(join(homeDir, "MEMORY.md"), controller.signal),
47
+ inspectFile(join(homeDir, "notes", "lessons.md"), controller.signal),
48
+ ]).then(([memory, lessons]) => ({ memory, lessons }));
84
49
  const timeout = new Promise((_resolve, reject) => {
85
50
  timer = setTimeout(() => {
86
- const error = Object.assign(new Error("local memory diagnostics timed out"), {
51
+ controller.abort();
52
+ reject(Object.assign(new Error("local memory diagnostics timed out"), {
87
53
  code: "diagnostics_timeout",
88
- });
89
- controller.abort(error);
90
- reject(error);
54
+ }));
91
55
  }, Math.max(1, timeoutMs));
92
56
  timer.unref?.();
93
57
  });
94
58
  try {
95
- return await Promise.race([operation(controller.signal), timeout]);
59
+ return await Promise.race([inspection, timeout]);
96
60
  }
97
61
  finally {
98
62
  if (timer !== undefined)
99
63
  clearTimeout(timer);
100
64
  }
101
65
  }
102
- export async function inspectLocalMemoryFilesWithinDeadline(homeDir, timeoutMs) {
103
- return withinLocalMemoryDiagnosticsDeadline(async (signal) => {
104
- const [memory, lessons, pending] = await Promise.all([
105
- inspectFile(join(homeDir, "MEMORY.md"), signal),
106
- inspectFile(join(homeDir, "notes", "lessons.md"), signal),
107
- inspectPendingDirectory(join(homeDir, ".crew", "memory", "pending"), signal),
108
- ]);
109
- return { memory, lessons, pending };
110
- }, timeoutMs);
111
- }
112
66
  export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HASH_CAP_BYTES) {
113
67
  const sizeBytes = Buffer.byteLength(content, "utf8");
114
68
  if (sizeBytes > Math.max(0, maxScanBytes)) {
@@ -122,12 +76,12 @@ export function localMemoryContentFacts(content, maxScanBytes = LOCAL_MEMORY_HAS
122
76
  lessons_reference_count: content.match(/\bnotes[\\/]lessons\.md\b/gi)?.length ?? 0,
123
77
  };
124
78
  }
125
- export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory, query = "") {
79
+ export function analyzeLocalMemoryInjection(systemPrompt, sourceMemory) {
126
80
  const sourceBytes = Buffer.byteLength(sourceMemory, "utf8");
127
81
  if (sourceMemory.length === 0) {
128
82
  return { state: "empty_source", source_bytes: 0, bounded_bytes: 0, injected_bytes: 0 };
129
83
  }
130
- const bounded = capMemoryForInject(sourceMemory, undefined, Number.POSITIVE_INFINITY, query);
84
+ const bounded = capMemoryForInject(sourceMemory);
131
85
  const marker = INJECTION_MARKERS.find((candidate) => systemPrompt.includes(candidate));
132
86
  if (marker === undefined) {
133
87
  return {
@@ -263,50 +217,15 @@ function crewMemoryReadAccess(command, paths) {
263
217
  return null;
264
218
  }
265
219
  }
266
- /**
267
- * Decode only the shell wrapper shape emitted by supported runtimes. The body must be one complete
268
- * quoted argument. Double-quoted bodies may escape only a quote or backslash; shell expansion and
269
- * concatenated quoting stay unknown so telemetry never guesses what the shell actually executed.
270
- */
271
- function unwrapLiteralShellCommand(command) {
272
- const trimmed = command.trim();
273
- const wrapper = /^\/bin\/(?:(?:zsh|bash)\s+-lc|sh\s+-c)\s+([\s\S]+)$/u.exec(trimmed);
274
- if (wrapper === null)
275
- return trimmed;
276
- const quoted = wrapper[1];
277
- const quote = quoted[0];
278
- if ((quote !== "'" && quote !== '"') || quoted.at(-1) !== quote || quoted.length < 2)
279
- return null;
280
- const body = quoted.slice(1, -1);
281
- if (quote === "'")
282
- return body.includes("'") ? null : body;
283
- let decoded = "";
284
- for (let index = 0; index < body.length; index += 1) {
285
- const character = body[index];
286
- if (character === '"' || character === "$" || character === "`")
287
- return null;
288
- if (character !== "\\") {
289
- decoded += character;
290
- continue;
291
- }
292
- const escaped = body[index + 1];
293
- if (escaped !== '"' && escaped !== "\\")
294
- return null;
295
- decoded += escaped;
296
- index += 1;
297
- }
298
- return decoded;
299
- }
300
220
  function commandAccesses(command, paths) {
301
- const unwrapped = unwrapLiteralShellCommand(command);
302
- if (unwrapped === null || /[;|&\n]/.test(unwrapped))
221
+ if (/[;|&\n]/.test(command))
303
222
  return [];
304
223
  const accesses = new Map();
305
- const memoryRead = crewMemoryReadAccess(unwrapped, paths);
224
+ const memoryRead = crewMemoryReadAccess(command, paths);
306
225
  if (memoryRead !== null) {
307
226
  accesses.set(`${memoryRead.target}:${memoryRead.relative_path ?? ""}:read`, memoryRead);
308
227
  }
309
- const expanded = unwrapped
228
+ const expanded = command
310
229
  .replaceAll("${CREW_HOME}", paths.homeDir)
311
230
  .replaceAll("$CREW_HOME", paths.homeDir)
312
231
  .replace(/["']/g, "");
@@ -38,7 +38,6 @@ export function logLocalMemoryDiagnosticsFailure(identity, phase, error) {
38
38
  function snapshotFields(snapshot) {
39
39
  if (snapshot === null)
40
40
  return {};
41
- const pending = snapshot.pending ?? { exists: false, entry_count: 0 };
42
41
  return {
43
42
  memory_exists: snapshot.memory.exists,
44
43
  memory_size_bytes: snapshot.memory.size_bytes,
@@ -55,12 +54,6 @@ function snapshotFields(snapshot) {
55
54
  lessons_hash_skipped_reason: snapshot.lessons.hash_skipped_reason,
56
55
  lessons_error_code: snapshot.lessons.error_code,
57
56
  lessons_markdown_heading_count: snapshot.lessons.markdown_heading_count,
58
- pending_exists: pending.exists,
59
- pending_size_bytes: pending.size_bytes,
60
- pending_mtime_ms: pending.mtime_ms,
61
- pending_sha256: pending.sha256,
62
- pending_error_code: pending.error_code,
63
- pending_entry_count: pending.entry_count,
64
57
  };
65
58
  }
66
59
  function promptDelivery(runtime, stableProtocolRuntime) {
@@ -110,7 +103,7 @@ export function createLocalMemoryTelemetry(options) {
110
103
  contextPrepared(context) {
111
104
  try {
112
105
  const sourceFacts = localMemoryContentFacts(context.memory);
113
- const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory, context.memoryQuery);
106
+ const injectionFacts = analyzeLocalMemoryInjection(context.systemPrompt, context.memory);
114
107
  safeLog("local_memory.context_prepared", "本地记忆上下文已准备", {
115
108
  execution_id: options.executionId,
116
109
  agent_handle: options.agentHandle,
@@ -190,13 +183,10 @@ export function createLocalMemoryTelemetry(options) {
190
183
  const lessonsChanged = beforeSnapshot !== null && afterSnapshot !== null
191
184
  ? localMemoryFileChanged(beforeSnapshot.lessons, afterSnapshot.lessons)
192
185
  : undefined;
193
- const pendingChanged = beforeSnapshot !== null && afterSnapshot !== null
194
- ? localMemoryFileChanged(beforeSnapshot.pending ?? { exists: false }, afterSnapshot.pending ?? { exists: false })
195
- : undefined;
196
186
  const accessSummary = accessObserver.summary();
197
- const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined || pendingChanged === undefined
187
+ const sharedFilesChanged = memoryChanged === undefined || lessonsChanged === undefined
198
188
  ? undefined
199
- : memoryChanged || lessonsChanged || pendingChanged;
189
+ : memoryChanged || lessonsChanged;
200
190
  safeLog("local_memory.execution_observed", "本地记忆执行观察已完成", {
201
191
  execution_id: options.executionId,
202
192
  agent_handle: options.agentHandle,
@@ -210,7 +200,6 @@ export function createLocalMemoryTelemetry(options) {
210
200
  executor_outcome: executorCompleted ? "succeeded" : "failed",
211
201
  memory_changed: memoryChanged,
212
202
  lessons_changed: lessonsChanged,
213
- pending_changed: pendingChanged,
214
203
  shared_files_changed_during_execution: sharedFilesChanged,
215
204
  unexpected_shared_write: options.isMemoryPrune
216
205
  ? undefined
@@ -235,12 +224,6 @@ export function createLocalMemoryTelemetry(options) {
235
224
  lessons_after_hash_skipped_reason: afterSnapshot.lessons.hash_skipped_reason,
236
225
  lessons_after_error_code: afterSnapshot.lessons.error_code,
237
226
  lessons_after_markdown_heading_count: afterSnapshot.lessons.markdown_heading_count,
238
- pending_after_exists: afterSnapshot.pending?.exists ?? false,
239
- pending_after_size_bytes: afterSnapshot.pending?.size_bytes,
240
- pending_after_mtime_ms: afterSnapshot.pending?.mtime_ms,
241
- pending_after_sha256: afterSnapshot.pending?.sha256,
242
- pending_after_error_code: afterSnapshot.pending?.error_code,
243
- pending_after_entry_count: afterSnapshot.pending?.entry_count ?? 0,
244
227
  }),
245
228
  });
246
229
  },
@@ -25,7 +25,6 @@ import { RUNTIME_HEALTH_PROBE_CAPABILITY } from "./runtime-health.js";
25
25
  import { probeDeepSeekHarnessAcp, deepSeekHarnessLauncher } from "./runtimes/deepseek-harness.js";
26
26
  import { daemonHome } from "./computer-profile.js";
27
27
  import { daemonReleaseProvenance } from "./release-provenance.js";
28
- import { MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY } from "./memory-migration-protocol.js";
29
28
  export { executableRuntimes } from "./runtime-capabilities.js";
30
29
  const execFileP = promisify(execFile);
31
30
  export const PROJECT_WORKSPACES_CAPABILITY = "project_workspaces_v1";
@@ -41,7 +40,6 @@ export const NATIVE_PROJECT_WORKSPACE_READINESS = Object.freeze({
41
40
  });
42
41
  export const DAEMON_CAPABILITIES = Object.freeze([
43
42
  MARKETPLACE_CAPABILITY,
44
- MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY,
45
43
  "scheduled_job_v1",
46
44
  "reply_origin_v1",
47
45
  "origin_decision_v1",
@@ -77,7 +75,6 @@ export const daemonCapabilities = (runtimePlatform = process.platform, readiness
77
75
  return Object.freeze(DAEMON_CAPABILITIES.filter((capability) => ((capability !== MARKETPLACE_CAPABILITY || nativeWorkspacePlatform)
78
76
  && (capability !== PROJECT_WORKSPACES_CAPABILITY || nativeWorkspaceReady)
79
77
  && (capability !== "message_file_preview_v1" || nativeWorkspacePlatform)
80
- && (nativeWorkspacePlatform || ![MEMORY_MIGRATION_CAPABILITY, MEMORY_STORAGE_V2_CAPABILITY, MEMORY_STATE_GUARD_CAPABILITY].includes(capability))
81
78
  && (nativeWorkspacePlatform
82
79
  || (capability !== "project_skills_v1"
83
80
  && capability !== "project_skill_projection_v2"
package/dist/main.js CHANGED
File without changes
@@ -2,7 +2,7 @@ import { z } from "zod";
2
2
  // 与另一个进程中的 marketplace-protocol.ts 保持一致;跨进程契约测试校验摘要。
3
3
  export const MARKETPLACE_CAPABILITY = "marketplace_update_v1";
4
4
  export const MARKETPLACE = "nowwork-pm";
5
- export const PLUGIN = "aiinterview-plugin";
5
+ export const PLUGIN = "AIinterview-plugin";
6
6
  export const MarketplaceModeSchema = z.enum(["inspect", "refresh", "update"]);
7
7
  export const MarketplaceStatusSchema = z.enum(["queued", "running", "completed", "partial", "failed", "unknown"]);
8
8
  export const MarketplaceTargetSchema = z.object({
@@ -10,7 +10,7 @@ const Target = z.object({
10
10
  version: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._+-]{0,79}$/),
11
11
  source: z.object({ source: z.literal("git-subdir"),
12
12
  url: z.literal("git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git"),
13
- path: z.literal("plugins/aiinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
13
+ path: z.literal("plugins/AIinterview-plugin"), sha: z.string().regex(/^[a-f0-9]{40}$/) }),
14
14
  });
15
15
  export async function readRemoteMarketplace(runtime, registered, env, command) {
16
16
  const source = validateMarketplaceSource(registered.url, registered.ref);
@@ -12,7 +12,7 @@ import { readMarketplaceSource } from "./marketplace-source.js";
12
12
  export { MarketplaceFailure, runMarketplaceCommand } from "./marketplace-command.js";
13
13
  const SELECTOR = `${PLUGIN}@${MARKETPLACE}`;
14
14
  const SOURCE = "git@codeup.aliyun.com:6128d2e757e7cd986dfae5ab/pm/AIinterview-agent-skill.git";
15
- const SOURCE_PATH = "plugins/aiinterview-plugin";
15
+ const SOURCE_PATH = "plugins/AIinterview-plugin";
16
16
  const obj = (x) => x !== null && typeof x === "object" && !Array.isArray(x) ? x : {};
17
17
  const parse = (text) => { try {
18
18
  return JSON.parse(text);
@@ -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, selectLegacyMemoryPreview, selectLocalMemoryPreview, } from "./local-memory/storage-format.js";
9
8
  /** work-log 注入上限(字符):防冷启动注入随文件膨胀。超限保头(目标/结论)+尾(最近进展),
10
9
  * 截中段并提示读全文——全文仍在盘上,只限"默认推送量"不限"可获取量"。纯函数。 */
11
10
  export const WORKLOG_INJECT_CAP = 8000;
@@ -68,14 +67,23 @@ 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
- }
75
- if (memory.length > cap || Buffer.byteLength(memory, "utf8") > maxBytes) {
76
- return selectLegacyMemoryPreview(memory, query, { maxCodePoints: cap, maxBytes });
70
+ export function capMemoryForInject(memory, cap = MEMORY_INJECT_CAP, maxBytes = Number.POSITIVE_INFINITY) {
71
+ if (memory.length <= cap && Buffer.byteLength(memory, "utf8") <= maxBytes)
72
+ return memory;
73
+ const notice = "[MEMORY.md preview truncated; use crew-memory search/read for more.]\n";
74
+ const noticeBytes = Buffer.byteLength(notice, "utf8");
75
+ if (notice.length > cap || noticeBytes > maxBytes)
76
+ return "";
77
+ let end = 0;
78
+ let bytes = noticeBytes;
79
+ for (const character of memory) {
80
+ const size = Buffer.byteLength(character, "utf8");
81
+ if (notice.length + end + character.length > cap || bytes + size > maxBytes)
82
+ break;
83
+ end += character.length;
84
+ bytes += size;
77
85
  }
78
- return memory;
86
+ return notice + memory.slice(0, end);
79
87
  }
80
88
  export function buildSystemPrompt(ctx) {
81
89
  const product = ctx.productName ?? "nowwork";
@@ -334,7 +342,7 @@ ${interactiveTaskRules}${freshnessRule}${externalReplyRule}${collaborationEtique
334
342
 
335
343
  ## Workspace 与分层记忆(CRITICAL — 索引+按需,配合上面的并行规则)
336
344
  你的持久记忆在 \`$CREW_HOME\`(跨你所有任务共享),按“常驻索引 + 功能明细 + 当前工作”分层:
337
- 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\`);需要历史约束、经验或偏好时先检索完整索引,再按需分段读取。
338
346
  2. **notes/<topic>.md = 功能明细**:按用户偏好、项目规则、测试、调试、Git、事故复盘等主题拆分。优先读取与当前任务匹配的文件;\`notes/lessons.md\` 只保留未分类经验和历史兼容内容,不是所有新经验的默认目的地。普通任务默认只读不写,但当前用户明确要求时可以按最小范围更新;任务结束后的专门收尾执行仍按收尾指令复核。
339
347
  - 对旧版 MEMORY.md,如果条目没有完整结构化字段但明确指向 \`notes/<file>.md\`,把它视为 legacy 索引;仍需结合条目标题/链接和当前请求判断相关性,不要因为缺少 summary/triggers 就假定正文已注入。
340
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) {