@nowcrew/daemon 0.6.65 → 0.6.66

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 (57) hide show
  1. package/dist/agent-ability/materializer.js +1 -20
  2. package/dist/agent-ability/resolver.js +10 -7
  3. package/dist/agent-ability/runtime.js +0 -3
  4. package/dist/automatic-install-target.js +10 -15
  5. package/dist/computer-cli.js +1 -10
  6. package/dist/computer-service.js +1 -41
  7. package/dist/config.js +0 -44
  8. package/dist/daemon-migration-controller.js +18 -16
  9. package/dist/execution-runner.js +1 -3
  10. package/dist/local-execution-prompt.js +9 -14
  11. package/dist/local-executor.js +3 -56
  12. package/dist/machine-info.js +2 -3
  13. package/dist/main.js +0 -83
  14. package/dist/managed-service-lifecycle.js +2 -47
  15. package/dist/managed-service-startup.js +0 -2
  16. package/dist/profile-layout.js +4 -10
  17. package/dist/project-skills/controller.js +0 -2
  18. package/dist/prompt.js +1 -19
  19. package/dist/runner.js +0 -3
  20. package/dist/runtimes/codex-app-server-runner.js +14 -54
  21. package/dist/runtimes/codex-contract.js +5 -20
  22. package/dist/runtimes/codex.js +0 -4
  23. package/dist/serve.js +3 -68
  24. package/dist/supervised-runtime.js +0 -3
  25. package/dist/workspace.js +1 -24
  26. package/package.json +9 -10
  27. package/dist/local-memory/index-budget.js +0 -74
  28. package/dist/local-memory/pending.js +0 -111
  29. package/dist/local-memory/runtime.js +0 -87
  30. package/dist/local-memory/types.js +0 -1
  31. package/dist/local-memory/write.js +0 -448
  32. package/dist/runtimes/codex-command-safety.js +0 -75
  33. package/dist/runtimes/codex-turn-retry.js +0 -69
  34. package/dist/storage-config.js +0 -175
  35. package/dist/storage-control.js +0 -193
  36. package/dist/storage-copy.js +0 -609
  37. package/dist/storage-credential-guard.js +0 -21
  38. package/dist/storage-doctor.js +0 -228
  39. package/dist/storage-identity.js +0 -29
  40. package/dist/storage-layout.js +0 -143
  41. package/dist/storage-manifest.js +0 -855
  42. package/dist/storage-migration-controller.js +0 -417
  43. package/dist/storage-migration-manifest.js +0 -105
  44. package/dist/storage-migration-policy.js +0 -100
  45. package/dist/storage-runtime-controller.js +0 -339
  46. package/dist/storage-runtime-readiness.js +0 -184
  47. package/dist/storage-runtime.js +0 -39
  48. package/dist/storage-safe-fs.js +0 -302
  49. package/dist/storage-serve-adapter.js +0 -65
  50. package/dist/storage-status-delivery.js +0 -209
  51. package/dist/storage-status-replay.js +0 -125
  52. package/dist/storage-target-confirmation.js +0 -129
  53. package/dist/storage-worker-command.js +0 -27
  54. package/dist/storage-worker-executor.js +0 -384
  55. package/dist/storage-worker-handoff.js +0 -151
  56. package/dist/storage-worker-operation.js +0 -284
  57. package/dist/storage-worker-status.js +0 -105
package/dist/serve.js CHANGED
@@ -51,7 +51,6 @@ import { handleRuntimeProbeFrame, probeRuntimeHealth } from "./runtime-health.js
51
51
  import { buildControlPlaneUrl } from "./control-plane-url.js";
52
52
  import { createServeMigrationController, handleMigrationControlMessage } from "./daemon-migration-wiring.js";
53
53
  import { createCodexHomeModeController } from "./codex-home-mode-controller.js";
54
- import { attachStorageMigrationRuntime, createServeStorageStatusDelivery, handleStorageMigrationControl, replayStorageMigrationStatus, storageMigrationCapabilities, } from "./storage-serve-adapter.js";
55
54
  export { buildControlPlaneUrl } from "./control-plane-url.js";
56
55
  // normalize.ts 的活动种类 → activity 枚举
57
56
  const ACTIVITY_MAP = {
@@ -123,7 +122,6 @@ export function serve(config, opts = {}) {
123
122
  catch { /* reconnect/timeout reconciliation handles a lost status frame */ }
124
123
  },
125
124
  });
126
- let storageMigrationAdmissionClosed = () => false;
127
125
  const migrationController = createServeMigrationController({
128
126
  config,
129
127
  ...(opts.profileName === undefined ? {} : { profileName: opts.profileName }),
@@ -144,7 +142,6 @@ export function serve(config, opts = {}) {
144
142
  const agentAbilityRuntime = createAgentAbilityRuntime(config.agentsRoot, {
145
143
  ...opts.agentAbility,
146
144
  coordinator: projectionCoordinator,
147
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
148
145
  });
149
146
  let projectSkillsController;
150
147
  const projectSkillsReconciler = opts.projectSkills?.reconciler ?? createProjectSkillsReconciler({
@@ -164,7 +161,6 @@ export function serve(config, opts = {}) {
164
161
  }
165
162
  catch { /* 下一次 ready 或项目操作会重新发送完整快照 */ }
166
163
  },
167
- isAdmissionClosed: () => storageMigrationAdmissionClosed(),
168
164
  reconcile: (handle, bindings) => projectSkillsReconciler.reconcile(handle, bindings),
169
165
  ensureSnapshot: (handle, snapshot, options) => projectSkillsReconciler.ensureSnapshot(handle, snapshot, options),
170
166
  });
@@ -179,37 +175,13 @@ export function serve(config, opts = {}) {
179
175
  });
180
176
  const machineHelloPublisher = createProjectSkillMachineHelloPublisher({
181
177
  status: projectSkillControllerReadiness.status,
182
- effective: (hello, status) => {
183
- const effective = withProjectSkillCapabilityStatus(hello, status);
184
- const configuredIdentity = opts.storageMigration?.identity;
185
- const currentIdentity = typeof configuredIdentity === "function"
186
- ? configuredIdentity()
187
- : configuredIdentity;
188
- return currentIdentity === undefined ? effective : { ...effective, ...currentIdentity };
189
- },
178
+ effective: withProjectSkillCapabilityStatus,
190
179
  onSent: (hello) => log(`📤 已上报机器信息: ${hello.hostname} · ${hello.os} · installed=[${hello.runtimes.join(",")}] · executable=[${hello.executionRuntimes.join(",")}] · agents=[${hello.agentHandles.join(",")}]`),
191
180
  });
192
181
  const initializedProjectSkills = initializedProjectSkillsReconciler(projectSkillControllerReadiness.ensure, projectSkillsReconciler);
193
- const configuredCapabilities = opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform);
194
- const storageCapabilities = storageMigrationCapabilities(configuredCapabilities, opts.storageMigration);
195
- const storageStatusDelivery = createServeStorageStatusDelivery(opts.storageMigration, {
196
- onPersistenceError: (error) => dslog("daemon.storage_status_outbox_failed", "存储迁移状态 outbox 持久化失败", { level: "ERROR", error_message: error.message }),
197
- transmit: (frame) => { try {
198
- if (ws?.readyState === WebSocket.OPEN)
199
- ws.send(JSON.stringify(frame));
200
- }
201
- catch { /* retry */ } },
202
- });
203
- const storageMigrationRuntime = attachStorageMigrationRuntime(opts.storageMigration, {
204
- isBusy: () => executionRuns.size > 0 || legacyRuns.size > 0,
205
- waitForExecutionFrames: () => executionFrameQueue,
206
- flushLocalState: flushSlog,
207
- sendStatus: storageStatusDelivery.send,
208
- });
209
- storageMigrationAdmissionClosed = storageMigrationRuntime.isAdmissionClosed;
210
182
  const prepareProjectSkillCapabilities = createProjectSkillConnectionCapabilityProvider({
211
183
  platform: process.platform,
212
- baseCapabilities: storageCapabilities,
184
+ baseCapabilities: opts.machineInfo?.capabilities ?? daemonCapabilities(process.platform),
213
185
  additionalCapabilities: () => managedDaemonCapabilities(updateEligibility),
214
186
  probeRuntimeRootStorePrimitive: projectSkillRuntimeRootStartup.probeNativePublicationPrimitiveReadiness,
215
187
  initializeController: projectSkillControllerReadiness.ensure,
@@ -338,12 +310,6 @@ export function serve(config, opts = {}) {
338
310
  // 连上了才有机会把离线期间(断连原因/退出前)落盘的日志补传上去
339
311
  void drainSpool();
340
312
  // 上报本机信息 (hostname/os/daemon 版本/已装 runtimes)
341
- storageStatusDelivery.replayPending();
342
- const storageReplay = replayStorageMigrationStatus(opts.storageMigration, openedSocket, () => ws === openedSocket, (error) => {
343
- dslog("daemon.storage_status_replay_failed", "存储迁移状态重放失败", {
344
- level: "ERROR", error_message: error.message,
345
- });
346
- }, storageStatusDelivery.send);
347
313
  const { detectInstalled, detectExecutable = detectExecutionRuntimesWithSignal } = opts.machineInfo ?? {};
348
314
  const helloPromise = (opts.machineInfo?.collect ?? collectMachineHello)(config.agentsRoot, config.executionLimits, process.platform, {
349
315
  ...(detectInstalled ? { detectInstalled } : {}),
@@ -359,7 +325,6 @@ export function serve(config, opts = {}) {
359
325
  if (hello.executionProtocol === undefined)
360
326
  return [];
361
327
  const detected = await runtimeProbe.detect(hello.runtimes, detectExecutable);
362
- await storageReplay;
363
328
  if (ws === openedSocket && openedSocket.readyState === WebSocket.OPEN
364
329
  && detected.some((runtime) => !hello.executionRuntimes.includes(runtime))) {
365
330
  detectedExecutionRuntimes = detected;
@@ -374,8 +339,7 @@ export function serve(config, opts = {}) {
374
339
  return [];
375
340
  });
376
341
  void helloPromise
377
- .then(async (hello) => {
378
- await storageReplay;
342
+ .then((hello) => {
379
343
  if (ws !== openedSocket || openedSocket.readyState !== WebSocket.OPEN)
380
344
  return;
381
345
  detectedExecutionRuntimes = hello.executionRuntimes;
@@ -423,8 +387,6 @@ export function serve(config, opts = {}) {
423
387
  if (typeof decoded !== "object" || decoded === null)
424
388
  return;
425
389
  const rawType = "type" in decoded && typeof decoded.type === "string" ? decoded.type : "";
426
- if (storageStatusDelivery.acknowledge(decoded))
427
- return;
428
390
  if (rawType === "daemon:update" || rawType === "daemon:restart") {
429
391
  void updateController.handle(decoded).catch((error) => {
430
392
  dslog("daemon.control_failed", "daemon 控制操作处理失败", {
@@ -441,11 +403,6 @@ export function serve(config, opts = {}) {
441
403
  void codexHomeModeController.handle(decoded).catch((error) => dslog("daemon.codex_home_mode_failed", "Codex Home 模式切换失败", { level: "ERROR", error_message: error.message }));
442
404
  return;
443
405
  }
444
- if (handleStorageMigrationControl(opts.storageMigration, rawType, decoded, storageStatusDelivery.send, (kind, error) => {
445
- dslog(kind === "migration" ? "daemon.storage_migration_failed" : "daemon.storage_migration_confirmation_failed", kind === "migration" ? "存储迁移控制操作失败" : "存储迁移 hello 确认处理失败", { level: "ERROR", error_message: error.message });
446
- })) {
447
- return;
448
- }
449
406
  if (rawType.startsWith("execution:")) {
450
407
  const parsedExecution = ServerToDaemonExecutionFrameSchema.safeParse(decoded);
451
408
  if (!parsedExecution.success) {
@@ -507,14 +464,6 @@ export function serve(config, opts = {}) {
507
464
  return;
508
465
  }
509
466
  const spec = frame;
510
- if (storageMigrationRuntime.isAdmissionClosed()) {
511
- safeExecutionSend(ExecutionRejectedSchema.parse({
512
- type: "execution:rejected", protocolVersion: 1, executionId: spec.executionId,
513
- reason: "resource_limit", message: "Storage migration is quiescing this machine",
514
- at: new Date().toISOString(),
515
- }));
516
- return;
517
- }
518
467
  const workspaceRejection = projectWorkspaceCapabilityRejection(spec, capabilities.executionRunner, new Date().toISOString());
519
468
  if (workspaceRejection !== null)
520
469
  return void safeExecutionSend(workspaceRejection);
@@ -821,17 +770,6 @@ export function serve(config, opts = {}) {
821
770
  // ready 帧带 server 视角的 machineId/workspaceId → 作为后续所有日志的默认关联键
822
771
  const r = msg;
823
772
  setSlogDefaults({ machine_id: r.machineId, workspace_id: r.workspaceId });
824
- if (opts.storageMigration?.ready === true && typeof r.workspaceId === "string") {
825
- try {
826
- opts.storageMigration.bindServerIdentity?.({ workspaceId: r.workspaceId });
827
- machineHelloPublisher.republish();
828
- }
829
- catch (error) {
830
- dslog("daemon.storage_identity_bind_failed", "存储迁移 Workspace 身份绑定失败", {
831
- level: "ERROR", error_message: error.message,
832
- });
833
- }
834
- }
835
773
  // 旧 server 无此字段 → 空集合(daemon 保持全部旧行为)。
836
774
  serverCapabilities = new Set(Array.isArray(r.serverCapabilities)
837
775
  ? r.serverCapabilities.filter((c) => typeof c === "string")
@@ -930,8 +868,6 @@ export function serve(config, opts = {}) {
930
868
  }
931
869
  if (msg.type !== "agent:start")
932
870
  return; // ready/error 等忽略
933
- if (storageMigrationRuntime.isAdmissionClosed())
934
- return;
935
871
  // 任务键:scheduled run 用 runId(每次运行独立 cwd/work-log;overlap 由 server 端 skip_if_running 管,
936
872
  // daemon 的 running 去重只兜底"同一 run 重复投递");普通唤醒仍是 线程锚点 ?? 频道。
937
873
  const scheduled = msg.reason === "scheduled_job" && msg.scheduledRun
@@ -1347,7 +1283,6 @@ export function serve(config, opts = {}) {
1347
1283
  concurrencyTelemetry.stop();
1348
1284
  runtimeProbe.stop();
1349
1285
  completionRetransmitter.stop();
1350
- storageStatusDelivery.stop();
1351
1286
  const deadline = createShutdownDeadline(shutdownTimeoutMs);
1352
1287
  if (reconnectTimer !== null) {
1353
1288
  clearTimeout(reconnectTimer);
@@ -58,9 +58,6 @@ export function supervisorLaunch(request) {
58
58
  ...(request.codexSkillRoots === undefined ? {} : {
59
59
  skillRoots: request.codexSkillRoots,
60
60
  }),
61
- ...(request.codexCollaborationTools === undefined ? {} : {
62
- codexCollaborationTools: request.codexCollaborationTools,
63
- }),
64
61
  resume: request.resume,
65
62
  }),
66
63
  };
package/dist/workspace.js CHANGED
@@ -8,7 +8,7 @@
8
8
  *
9
9
  * spawn runtime 时把 <ws>/.crew 置于 PATH 最前,于是 agent 的 `crew ...` 命中 wrapper。
10
10
  */
11
- import { mkdir, writeFile, readFile, chmod, access, lstat } from "node:fs/promises";
11
+ import { mkdir, writeFile, readFile, chmod, access } from "node:fs/promises";
12
12
  import { createHash, randomUUID } from "node:crypto";
13
13
  import { dirname, join } from "node:path";
14
14
  import { dslog } from "./slog.js";
@@ -34,28 +34,8 @@ async function exists(p) {
34
34
  return false;
35
35
  }
36
36
  }
37
- async function hasLocalMemoryV2Marker(agentDir) {
38
- const crewDir = join(agentDir, ".crew");
39
- const memoryDir = join(crewDir, "memory");
40
- const markerPath = join(agentDir, ".crew", "memory", "v2-enabled");
41
- try {
42
- const crewStat = await lstat(crewDir);
43
- const memoryStat = await lstat(memoryDir);
44
- if (!crewStat.isDirectory() || crewStat.isSymbolicLink()
45
- || !memoryStat.isDirectory() || memoryStat.isSymbolicLink())
46
- return false;
47
- const stat = await lstat(markerPath);
48
- if (!stat.isFile() || stat.isSymbolicLink())
49
- return false;
50
- return (await readFile(markerPath, "utf8")) === "1\n";
51
- }
52
- catch {
53
- return false;
54
- }
55
- }
56
37
  export async function prepareWorkspace(input) {
57
38
  const dir = join(input.agentsRoot, input.handle);
58
- const agentHomePreexisted = await exists(dir);
59
39
  const crewDir = join(dir, ".crew");
60
40
  await mkdir(crewDir, { recursive: true });
61
41
  // 预先创建明细目录,让收尾流程可以按功能创建 notes/<topic>.md。
@@ -63,7 +43,6 @@ export async function prepareWorkspace(input) {
63
43
  // MEMORY.md:首次创建"持久身份 + 知识索引"骨架,之后由 agent 自己维护
64
44
  const memoryPath = join(dir, "MEMORY.md");
65
45
  const memorySeedCreated = !(await exists(memoryPath));
66
- const localMemoryV2Initialized = await hasLocalMemoryV2Marker(dir);
67
46
  if (memorySeedCreated) {
68
47
  try {
69
48
  await writeFile(memoryPath, memorySeed(input.handle, input.description), "utf8");
@@ -145,9 +124,7 @@ export async function prepareWorkspace(input) {
145
124
  crewDir,
146
125
  systemPromptPath,
147
126
  memory,
148
- agentHomePreexisted,
149
127
  memorySeedCreated,
150
- localMemoryV2Initialized,
151
128
  homeDir,
152
129
  runDir,
153
130
  workLogPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.65",
3
+ "version": "0.6.66",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",
@@ -16,14 +16,6 @@
16
16
  "publishConfig": {
17
17
  "access": "public"
18
18
  },
19
- "scripts": {
20
- "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
21
- "build": "tsc -p tsconfig.json",
22
- "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
23
- "prepublishOnly": "pnpm build && node ../scripts/daemon-release-artifact.mjs --strict-registry",
24
- "test": "vitest run",
25
- "typecheck": "tsc --noEmit"
26
- },
27
19
  "dependencies": {
28
20
  "@agentclientprotocol/sdk": "1.2.1",
29
21
  "@nowcrew/cli": "^0.4.13",
@@ -42,5 +34,12 @@
42
34
  "tsx": "^4.19.0",
43
35
  "typescript": "^5.6.0",
44
36
  "vitest": "^2.1.0"
37
+ },
38
+ "scripts": {
39
+ "daemon": "pnpm --filter @nowcrew/cli build && tsx src/main.ts",
40
+ "build": "tsc -p tsconfig.json",
41
+ "codex-home:migrate": "tsx src/runtimes/codex-home-migration-cli.ts",
42
+ "test": "vitest run",
43
+ "typecheck": "tsc --noEmit"
45
44
  }
46
- }
45
+ }
@@ -1,74 +0,0 @@
1
- export const LOCAL_MEMORY_TOTAL_CODE_POINT_CAP = 6_000;
2
- export const LOCAL_MEMORY_INDEX_CODE_POINT_CAP = 3_000;
3
- export const LOCAL_MEMORY_INDEX_ENTRY_CAP = 20;
4
- const PROJECTABLE_STATUSES = new Set(["pinned", "active"]);
5
- function codePointLength(value) {
6
- return Array.from(value).length;
7
- }
8
- export function renderLocalMemoryIndexCard(entry) {
9
- return `### ${entry.title}\n`
10
- + `- id: ${entry.id}\n`
11
- + `- file: ${entry.file}\n`
12
- + `- type: ${entry.type}\n`
13
- + `- status: ${entry.status}\n`
14
- + `- summary: ${entry.summary}\n`
15
- + `- triggers: ${entry.triggers.join(", ")}\n`
16
- + `- priority: ${entry.priority}\n`
17
- + `- revision: ${entry.revision}\n`
18
- + `- anchor: ${entry.anchor ?? ""}\n`
19
- + `- updated: ${entry.updated}\n`
20
- + (entry.reviewAfter === null ? "" : `- review_after: ${entry.reviewAfter}\n`);
21
- }
22
- function compareEntries(left, right) {
23
- const statusRank = (status) => status === "pinned" ? 0 : 1;
24
- const priorityRank = (priority) => (priority === "high" ? 0 : priority === "medium" ? 1 : 2);
25
- return statusRank(left.status) - statusRank(right.status)
26
- || priorityRank(left.priority) - priorityRank(right.priority)
27
- || right.updated.localeCompare(left.updated)
28
- || left.id.localeCompare(right.id);
29
- }
30
- function cardLength(entry, hasPrevious) {
31
- return codePointLength(renderLocalMemoryIndexCard(entry)) + (hasPrevious ? 1 : 0);
32
- }
33
- function overBudgetReason(entries) {
34
- return entries.some((entry) => entry.status === "pinned")
35
- ? "index_over_budget_pinned"
36
- : "index_over_budget_active";
37
- }
38
- export function selectLocalMemoryIndexEntries(entries, budget, options = {}) {
39
- const projectable = entries.filter((entry) => PROJECTABLE_STATUSES.has(entry.status));
40
- const omittedIds = entries
41
- .filter((entry) => !PROJECTABLE_STATUSES.has(entry.status))
42
- .map((entry) => entry.id);
43
- const protectedIds = options.protectedIds ?? new Set();
44
- const protectedEntries = projectable.filter((entry) => protectedIds.has(entry.id)).sort(compareEntries);
45
- const candidates = projectable.filter((entry) => !protectedIds.has(entry.id)).sort(compareEntries);
46
- const selected = [];
47
- let renderedLength = 0;
48
- for (const entry of protectedEntries) {
49
- selected.push(entry);
50
- renderedLength += cardLength(entry, selected.length > 1);
51
- }
52
- if (selected.length > budget.entryCap || renderedLength > budget.indexCodePointCap) {
53
- return {
54
- entries: selected,
55
- omittedIds: [...omittedIds, ...candidates.map((entry) => entry.id)],
56
- reason: overBudgetReason(selected),
57
- };
58
- }
59
- for (const entry of candidates) {
60
- if (selected.length >= budget.entryCap || renderedLength + cardLength(entry, selected.length > 0) > budget.indexCodePointCap) {
61
- omittedIds.push(entry.id);
62
- continue;
63
- }
64
- selected.push(entry);
65
- renderedLength += cardLength(entry, selected.length > 1);
66
- }
67
- return {
68
- entries: selected,
69
- omittedIds,
70
- reason: omittedIds.length === entries.filter((item) => !PROJECTABLE_STATUSES.has(item.status)).length
71
- ? null
72
- : "index_over_budget",
73
- };
74
- }
@@ -1,111 +0,0 @@
1
- import { randomUUID } from "node:crypto";
2
- import { lstat, mkdir, readdir, realpath, rename, rm, writeFile, readFile } from "node:fs/promises";
3
- import { basename, dirname, join, relative, resolve, sep } from "node:path";
4
- import { z } from "zod";
5
- const CandidateSchema = z.object({
6
- operation: z.enum(["create", "update", "supersede"]),
7
- memoryId: z.string().nullable(),
8
- baseRevision: z.number().int().nonnegative().nullable(),
9
- supersedesMemoryId: z.string().nullable(),
10
- title: z.string().min(1),
11
- kind: z.enum(["procedural", "semantic", "episodic", "preference"]),
12
- summary: z.string().min(1),
13
- triggers: z.array(z.string()).min(1),
14
- priority: z.enum(["high", "medium", "low"]),
15
- detailCategory: z.enum(["preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons"]),
16
- detailBody: z.string().min(1),
17
- reviewAfter: z.string().nullable(),
18
- }).strict();
19
- const PendingSchema = z.object({
20
- schemaVersion: z.literal(1),
21
- pendingId: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9._-]*$/u),
22
- createdAt: z.string().min(1),
23
- reason: z.literal("index_over_budget"),
24
- source: z.object({
25
- executionId: z.string().min(1),
26
- pruneTraceId: z.string().min(1).nullable(),
27
- }).strict(),
28
- candidate: CandidateSchema,
29
- }).strict();
30
- function within(root, candidate) {
31
- const path = relative(root, candidate);
32
- return path === "" || (path !== ".." && !path.startsWith(`..${sep}`));
33
- }
34
- async function pendingDirectory(agentDir) {
35
- const agentRoot = await realpath(agentDir);
36
- const memoryRoot = resolve(agentRoot, ".crew", "memory");
37
- await mkdir(memoryRoot, { recursive: true });
38
- const memoryStat = await lstat(memoryRoot);
39
- if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
40
- throw new Error("local_memory_pending_path_invalid");
41
- const resolvedMemoryRoot = await realpath(memoryRoot);
42
- if (resolvedMemoryRoot !== memoryRoot || !within(agentRoot, resolvedMemoryRoot)) {
43
- throw new Error("local_memory_pending_path_invalid");
44
- }
45
- const directory = join(memoryRoot, "pending");
46
- try {
47
- const stat = await lstat(directory);
48
- if (!stat.isDirectory() || stat.isSymbolicLink())
49
- throw new Error("local_memory_pending_path_invalid");
50
- }
51
- catch (error) {
52
- if (error.code !== "ENOENT")
53
- throw error;
54
- await mkdir(directory);
55
- }
56
- const resolvedDirectory = await realpath(directory);
57
- if (resolvedDirectory !== directory || !within(agentRoot, resolvedDirectory)) {
58
- throw new Error("local_memory_pending_path_invalid");
59
- }
60
- return directory;
61
- }
62
- async function assertRegularFile(path) {
63
- try {
64
- const stat = await lstat(path);
65
- if (!stat.isFile() || stat.isSymbolicLink())
66
- throw new Error("local_memory_pending_path_invalid");
67
- }
68
- catch (error) {
69
- if (error.code !== "ENOENT")
70
- throw error;
71
- }
72
- }
73
- async function atomicWrite(path, content) {
74
- const temporary = join(dirname(path), `.${basename(path)}.tmp-${randomUUID()}`);
75
- try {
76
- await writeFile(temporary, content, { encoding: "utf8", mode: 0o600, flag: "wx" });
77
- await rename(temporary, path);
78
- }
79
- catch (error) {
80
- await rm(temporary, { force: true }).catch(() => undefined);
81
- throw error;
82
- }
83
- }
84
- export async function persistPendingMemoryCandidate(input) {
85
- const parsed = PendingSchema.safeParse(input.candidate);
86
- if (!parsed.success)
87
- throw new Error("local_memory_pending_invalid");
88
- const directory = await pendingDirectory(input.agentDir);
89
- const target = join(directory, `${parsed.data.pendingId}.json`);
90
- await assertRegularFile(target);
91
- await atomicWrite(target, `${JSON.stringify(parsed.data, null, 2)}\n`);
92
- return target;
93
- }
94
- export async function readPendingMemoryCandidates(input) {
95
- const directory = await pendingDirectory(input.agentDir);
96
- const names = (await readdir(directory)).filter((name) => name.endsWith(".json")).sort();
97
- const result = [];
98
- for (const name of names) {
99
- const path = join(directory, name);
100
- try {
101
- await assertRegularFile(path);
102
- const parsed = PendingSchema.safeParse(JSON.parse(await readFile(path, "utf8")));
103
- if (parsed.success && `${parsed.data.pendingId}.json` === name)
104
- result.push(parsed.data);
105
- }
106
- catch {
107
- // A damaged pending item is diagnosable and must not hide other candidates.
108
- }
109
- }
110
- return result;
111
- }
@@ -1,87 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { lstat, mkdir, readFile, rm, writeFile } from "node:fs/promises";
3
- import { join } from "node:path";
4
- import { applyLocalMemoryResult, parseLocalMemoryResult } from "./write.js";
5
- export function isLocalMemoryV2Eligible(input) {
6
- return input.enabledForHandle
7
- && (input.localMemoryV2Initialized || (!input.agentHomePreexisted && input.memorySeedCreated))
8
- && input.memoryPrune;
9
- }
10
- const LOCAL_MEMORY_V2_MARKER = "v2-enabled";
11
- export async function initializeLocalMemoryV2(agentDir) {
12
- const crewDir = join(agentDir, ".crew");
13
- const memoryDir = join(crewDir, "memory");
14
- await mkdir(crewDir, { recursive: true });
15
- const crewStat = await lstat(crewDir);
16
- if (!crewStat.isDirectory() || crewStat.isSymbolicLink())
17
- throw new Error("local_memory_marker_path_invalid");
18
- await mkdir(memoryDir, { recursive: true });
19
- const memoryStat = await lstat(memoryDir);
20
- if (!memoryStat.isDirectory() || memoryStat.isSymbolicLink())
21
- throw new Error("local_memory_marker_path_invalid");
22
- try {
23
- await writeFile(join(memoryDir, LOCAL_MEMORY_V2_MARKER), "1\n", { encoding: "utf8", mode: 0o600, flag: "wx" });
24
- }
25
- catch (error) {
26
- if (error.code !== "EEXIST")
27
- throw error;
28
- }
29
- }
30
- export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle) {
31
- const initialized = workspace.localMemoryV2Initialized === true;
32
- if (!enabledForHandle || initialized || workspace.localMemoryV2Initialized !== false
33
- || workspace.agentHomePreexisted === true || workspace.memorySeedCreated !== true)
34
- return initialized;
35
- try {
36
- await initializeLocalMemoryV2(workspace.dir);
37
- return true;
38
- }
39
- catch {
40
- return false;
41
- }
42
- }
43
- export function localMemoryResultPath(runDir, executionId) {
44
- const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
45
- return join(runDir, `.memory-result.v1-${suffix}.json`);
46
- }
47
- export function renderLocalMemoryResultInstructions(resultPath) {
48
- return `
49
-
50
- ## Structured memory prune (daemon-managed)
51
- 本轮长期记忆收尾只提交候选,不要直接编辑 $CREW_HOME/MEMORY.md 或 $CREW_HOME/notes/。
52
- 将一个合法 JSON 对象写入以下 daemon 提供的文件,写入完成后再结束本轮:
53
- ${resultPath}
54
-
55
- JSON 顶层必须包含:
56
- {"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"no_change","changes":[],"statusChanges":[]}
57
- 每个 changes 项使用 operation=create|update|supersede、title、kind、summary、triggers、priority、detailCategory、detailBody、reviewAfter;create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null,update/supersede 按现有索引填写版本和 id。
58
- 只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 中等待后续治理。
59
- `;
60
- }
61
- export async function commitLocalMemoryResultAfterExit(input) {
62
- if (input.exitCode !== 0)
63
- return { status: "skipped" };
64
- let raw;
65
- try {
66
- raw = await readFile(input.resultPath, "utf8");
67
- }
68
- catch (error) {
69
- if (error.code === "ENOENT")
70
- return { status: "missing" };
71
- return { status: "failed", error: error instanceof Error ? error.message : "read_failed" };
72
- }
73
- try {
74
- const result = parseLocalMemoryResult(raw);
75
- await applyLocalMemoryResult({
76
- agentDir: input.agentDir,
77
- result,
78
- executionId: input.executionId,
79
- pruneTraceId: input.pruneTraceId,
80
- });
81
- await rm(input.resultPath, { force: true });
82
- return { status: "applied" };
83
- }
84
- catch (error) {
85
- return { status: "failed", error: error instanceof Error ? error.message : "apply_failed" };
86
- }
87
- }
@@ -1 +0,0 @@
1
- export {};