@nowcrew/daemon 0.6.101-beta.1 → 0.6.101-beta.3

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/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 +55 -78
  7. package/dist/local-memory/index-budget.js +3 -9
  8. package/dist/local-memory/inventory-cli.js +8 -52
  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 +17 -191
  17. package/dist/local-memory/write.js +21 -190
  18. package/dist/local-memory-diagnostics.js +15 -96
  19. package/dist/local-memory-telemetry.js +3 -20
  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 -74
  25. package/dist/message-file-preview.js +1 -11
  26. package/dist/prompt.js +17 -9
  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 -362
  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 -432
  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/release-artifact-guard.js +0 -55
  50. package/dist/local-memory/source-path.js +0 -13
  51. package/dist/local-memory/storage-format.js +0 -249
  52. package/dist/local-memory/writer-mode.js +0 -13
  53. package/dist/memory-migration-controller.js +0 -89
  54. package/dist/memory-migration-inspect.js +0 -116
  55. package/dist/memory-migration-journal.js +0 -14
  56. package/dist/memory-migration-protocol.js +0 -14
  57. package/dist/memory-migration-runtime.js +0 -305
@@ -2,12 +2,6 @@ import { createHash } from "node:crypto";
2
2
  import { constants } from "node:fs";
3
3
  import { lstat, mkdir, open, readFile, rm, writeFile } from "node:fs/promises";
4
4
  import { join } from "node:path";
5
- import { applyMemoryMigration, prepareMemoryMigration, recoverMemoryMigration } from "./migration-store.js";
6
- import { MigrationStateSchema } from "./migration-schema.js";
7
- import { detectLocalMemoryStorageVersion } from "./storage-format.js";
8
- import { isLocalMemoryInitializationIdentity, localMemoryInitializationIdentity } from "./initialization-identity.js";
9
- import { inventory } from "./migration-io.js";
10
- import { initializeExactWorkspaceSeed } from "./migration-plan.js";
11
5
  import { applyLocalMemoryResult, parseLocalMemoryResult } from "./write.js";
12
6
  import { verifyWorkLogSource } from "./work-log.js";
13
7
  export function isLocalMemoryV2Eligible(input) {
@@ -18,8 +12,7 @@ export function isLocalMemoryV2Eligible(input) {
18
12
  return input.enabledForHandle && input.memoryPrune;
19
13
  }
20
14
  const LOCAL_MEMORY_V2_MARKER = "v2-enabled";
21
- const DELIVERY_EVIDENCE_PATTERN = /release(?:[-_.]\S+)?\.md|publish(?:[-_.]\S+)?\.md|build(?:[-_.]\S+)?\.md|deploy(?:[-_.]\S+)?\.md|artifact(?:[-_.]\S+)?\.md|registry|manifest|changelog|tarball|sha256|发布|构建|打包|发版|注册表|交付/iu;
22
- async function writeLocalMemoryV2Marker(agentDir) {
15
+ export async function initializeLocalMemoryV2(agentDir) {
23
16
  const crewDir = join(agentDir, ".crew");
24
17
  const memoryDir = join(crewDir, "memory");
25
18
  await mkdir(crewDir, { recursive: true });
@@ -38,151 +31,24 @@ async function writeLocalMemoryV2Marker(agentDir) {
38
31
  throw error;
39
32
  }
40
33
  }
41
- function initializedIntent(identity, state) {
42
- return {
43
- ...identity,
44
- kind: "server_write_intent",
45
- sourceDigest: state.sourceDigest,
46
- planDigest: state.planDigest,
47
- intentVersion: 1,
48
- // New-Agent initialization is derived from the exact pristine seed. Keep
49
- // the receipt deterministic so a crash between intent publication and the
50
- // mutable state update can be retried without creating a conflicting fact.
51
- confirmedAt: "1970-01-01T00:00:00.000Z",
52
- };
53
- }
54
- /**
55
- * Initialize a pristine new Agent through the same journaled schema2
56
- * publication path used by migration. The marker is written only after the
57
- * committed catalog and state have both been verified.
58
- */
59
- async function initializeLocalMemoryV2WithBinding(agentDir, agentId, binding) {
60
- const identity = await localMemoryInitializationIdentity(agentDir, agentId, binding);
61
- let state = await prepareMemoryMigration({
62
- agentDir,
63
- identity,
64
- serverHistory: { kind: "none", isNewAgent: true },
65
- });
66
- if (state.origin !== "initialized")
67
- throw new Error("local_memory_v2_initialization_source_invalid");
68
- if (state.phase === "prepared") {
69
- state = await applyMemoryMigration({
70
- agentDir,
71
- identity,
72
- intentReceipt: initializedIntent(identity, state),
73
- });
74
- }
75
- else if (state.phase === "applying" || state.phase === "repair_required") {
76
- state = await recoverMemoryMigration({
77
- agentDir,
78
- identity,
79
- intentReceipt: state.intentReceipt ?? initializedIntent(identity, state),
80
- stoppedReceipt: {
81
- kind: "migration_process_stopped",
82
- migrationId: identity.migrationId,
83
- machineId: identity.machineId,
84
- executionId: identity.executionId,
85
- ownerIdentity: identity.ownerIdentity,
86
- pid: null,
87
- stoppedAt: new Date().toISOString(),
88
- proof: "journal_reconciled",
89
- },
90
- cancelled: false,
91
- });
92
- }
93
- const memory = await readFile(join(agentDir, "MEMORY.md"), "utf8");
94
- if (state.phase !== "committed" || state.origin !== "initialized" || state.storageVersion !== 2
95
- || detectLocalMemoryStorageVersion(memory) !== 2) {
96
- throw new Error("local_memory_v2_initialization_incomplete");
97
- }
98
- await writeLocalMemoryV2Marker(agentDir);
99
- }
100
- /** Fresh initialization requires the identity from the authenticated ready frame. */
101
- export function initializeLocalMemoryV2(agentDir, agentId, binding) {
102
- return initializeLocalMemoryV2WithBinding(agentDir, agentId, binding);
103
- }
104
- export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle, agentId, binding) {
34
+ export async function initializeLocalMemoryV2ForWorkspace(workspace, enabledForHandle) {
105
35
  const initialized = workspace.localMemoryV2Initialized === true;
106
- if (initialized)
107
- return true;
108
- // Real workspaces always report this boolean. Keep partial/mock workspace
109
- // callers side-effect free instead of probing an unrelated filesystem path.
110
- if (workspace.localMemoryV2Initialized !== false)
111
- return false;
112
- let resumable = false;
113
- let effectiveBinding = binding;
36
+ if (!enabledForHandle || initialized || workspace.localMemoryV2Initialized !== false
37
+ || workspace.agentHomePreexisted === true || workspace.memorySeedCreated !== true)
38
+ return initialized;
114
39
  try {
115
- const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(workspace.dir, ".crew", "memory", "migration.v1.json"), "utf8")));
116
- const identity = await localMemoryInitializationIdentity(workspace.dir, agentId, binding);
117
- const legacyIdentity = binding === undefined
118
- ? identity
119
- : await localMemoryInitializationIdentity(workspace.dir, agentId);
120
- resumable = state.origin === "initialized"
121
- && state.phase !== "rolled_back"
122
- && (isLocalMemoryInitializationIdentity(state.identity, identity)
123
- || isLocalMemoryInitializationIdentity(state.identity, legacyIdentity));
124
- if (resumable && isLocalMemoryInitializationIdentity(state.identity, legacyIdentity))
125
- effectiveBinding = undefined;
126
- }
127
- catch (error) {
128
- if (error.code !== "ENOENT")
129
- throw error;
130
- try {
131
- const migrations = await lstat(join(workspace.dir, ".crew", "memory", "migrations"));
132
- if (!migrations.isDirectory() || migrations.isSymbolicLink()) {
133
- throw new Error("local_memory_v2_initialization_residue_invalid");
134
- }
135
- const identity = await localMemoryInitializationIdentity(workspace.dir, agentId, binding);
136
- const legacyIdentity = binding === undefined
137
- ? identity
138
- : await localMemoryInitializationIdentity(workspace.dir, agentId);
139
- let operationIdentity = identity;
140
- const operation = await lstat(join(workspace.dir, ".crew", "memory", "migrations", identity.migrationId))
141
- .catch(async (error) => {
142
- if (error.code !== "ENOENT" || identity.migrationId === legacyIdentity.migrationId)
143
- throw error;
144
- operationIdentity = legacyIdentity;
145
- return lstat(join(workspace.dir, ".crew", "memory", "migrations", legacyIdentity.migrationId));
146
- });
147
- if (!operation.isDirectory() || operation.isSymbolicLink()) {
148
- throw new Error("local_memory_v2_initialization_residue_invalid");
149
- }
150
- const current = await inventory(workspace.dir);
151
- const memory = current.files.get("MEMORY.md");
152
- resumable = current.files.size === 1 && memory !== undefined
153
- && current.directories.every(path => path === "notes" || path === ".crew/memory/pending")
154
- && initializeExactWorkspaceSeed(memory) !== null;
155
- if (resumable && operationIdentity.migrationId === legacyIdentity.migrationId)
156
- effectiveBinding = undefined;
157
- }
158
- catch (residueError) {
159
- if (residueError.code !== "ENOENT")
160
- throw residueError;
161
- }
40
+ await initializeLocalMemoryV2(workspace.dir);
41
+ return true;
162
42
  }
163
- const pristineFirstInitialization = enabledForHandle
164
- && workspace.localMemoryV2Initialized === false
165
- && workspace.agentHomePreexisted !== true
166
- && workspace.memorySeedCreated === true;
167
- if (!resumable && !pristineFirstInitialization)
168
- return false;
169
- // Never create another unbound sentinel. Undefined is retained solely to
170
- // finish a pre-upgrade journal already proven above.
171
- if (!resumable && effectiveBinding === undefined)
43
+ catch {
172
44
  return false;
173
- await initializeLocalMemoryV2WithBinding(workspace.dir, agentId, effectiveBinding);
174
- return true;
45
+ }
175
46
  }
176
47
  export function localMemoryResultPath(runDir, executionId) {
177
48
  const suffix = createHash("sha256").update(executionId, "utf8").digest("hex").slice(0, 32);
178
49
  return join(runDir, `.memory-result.v1-${suffix}.json`);
179
50
  }
180
- export function renderLocalMemoryResultInstructions(resultPath, storageVersion = 1, migrationDeferred = false) {
181
- const storagePolicy = migrationDeferred
182
- ? "该 Agent 的旧记忆仍在迁移审阅中。本轮只允许提交新的 create 候选;daemon 会把候选原子保存到 .crew/memory/pending/,reason=legacy_requires_migration,并提供 search-pending/read-pending 查询。候选不会进入 active,也不会自动注入普通上下文;更新、替代和 statusChanges 必须留待迁移完成后按当前 V2 ID/revision/hash 重新核对。旧 MEMORY.md、notes 和已有 active/pending 保持不变。"
183
- : storageVersion === 2
184
- ? "完整目录保存在 MEMORY.md,模型只看到有界预览;未展示记录仍然有效。涉及旧规则时先用 crew-memory search 查询完整目录,按当前 ID/revision 更新,不因预览未命中就重复新建。20条预览不是有效记录上限;完整存储超出资源上限会拒绝提交,不得自行删除或降级旧记录。已有 pending 仍是未应用候选,须重新核对授权、重复与版本后通过 changes 申请保存,不因迁移自动生效。"
185
- : "daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。";
51
+ export function renderLocalMemoryResultInstructions(resultPath) {
186
52
  return `
187
53
 
188
54
  ## Structured memory prune (daemon-managed)
@@ -195,18 +61,17 @@ outcome 只允许 no_change、proposed、conflict、failed。新增、更新、
195
61
 
196
62
  新增记忆的完整合法示例(内容须替换为本任务实际候选):
197
63
  \`\`\`json
198
- {"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"proposed","changes":[{"operation":"create","memoryId":null,"baseRevision":null,"supersedesMemoryId":null,"title":"发布验收约定","kind":"procedural","summary":"发布前必须验证回滚步骤。","triggers":["发布","回滚"],"priority":"medium","detailCategory":"project-rules","detailBody":"发布前必须验证回滚步骤,并将验证结果写入发布报告。","reviewAfter":null}],"statusChanges":[],"workLogDisposition":"clear"}
64
+ {"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"proposed","changes":[{"operation":"create","memoryId":null,"baseRevision":null,"supersedesMemoryId":null,"title":"发布验收约定","kind":"procedural","summary":"发布前必须验证回滚步骤。","triggers":["发布","回滚"],"priority":"medium","detailCategory":"project-rules","detailBody":"发布前必须验证回滚步骤,并将验证结果写入发布报告。","reviewAfter":null}],"statusChanges":[]}
199
65
  \`\`\`
200
66
 
201
67
  没有需要变更的记忆时:
202
68
  \`\`\`json
203
- {"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"no_change","changes":[],"statusChanges":[],"workLogDisposition":"clear"}
69
+ {"schemaVersion":1,"purpose":"memory_prune","baseCatalogRevision":0,"outcome":"no_change","changes":[],"statusChanges":[]}
204
70
  \`\`\`
205
71
 
206
72
  每个 changes 项须保留示例中的全部字段。operation=create|update|supersede;kind=procedural|semantic|episodic|preference;priority=high|medium|low;detailCategory=preferences|project-rules|testing|debugging|git|incidents|lessons。create 的 memoryId/baseRevision/supersedesMemoryId 必须为 null;update 按现有索引填写 memoryId/baseRevision,supersedesMemoryId 为 null;supersede 填 supersedesMemoryId/baseRevision,memoryId 为 null。
207
- 只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。${storagePolicy}
208
- 发布、构建、打包、发版和注册表交付证据属于任务产物,不是长期记忆;不要直接创建或更新 notes/release.md 或 notes/release-*.md、notes/publish.md 或 notes/publish-*.md、notes/build.md 或 notes/build-*.md、notes/deploy.md 或 notes/deploy-*.md、notes/artifact.md 或 notes/artifact-*.md,也不要把 release/changelog/manifest 证据复制进 MEMORY.md 或 notes。只有明确授权且内容本身是跨任务可复用的流程规则时,才提交 project-rules 候选。
209
- 写入候选文件只表示提交候选,不能提前宣称已经记住或规则已经更新;daemon 在本轮结束后才处理,候选持久化与进入有效索引也不是同一结果。默认保留 $CREW_TASK_LOG;只有确认没有交付证据且所有后置条件满足时,才在 JSON 中显式填写 workLogDisposition="clear"。缺少该字段或填写 preserve 时,daemon 保留日志;daemon 发现日志包含交付证据关键词时会保守保留,即使结果填写 clear;失败、冲突或证据不完整时不得清理。
73
+ 只提交可复用、与本次任务明确相关的少量候选;不要写入凭证、token、隐私或内部控制标记。daemon 会校验、去重、限制 MEMORY.md 大小,超出索引容量的候选会保存在 pending 归档中。容量暂存的 create 可按需检索参考但不能覆盖现行规则;有空间的后续 proposed 提交可提升。未应用的 update/supersede 不进入普通召回,必须重新核对目标版本后处理。
74
+ 写入候选文件只表示提交候选,不能提前宣称已经记住或规则已经更新;daemon 在本轮结束后才处理,候选持久化与进入有效索引也不是同一结果。本结构化轮次覆盖前文提前清理工作日志的要求:保留 $CREW_TASK_LOG,不要清空或删除;daemon 将在处理成功后清理,失败时保留日志和候选用于排查或重试。
210
75
  `;
211
76
  }
212
77
  /** Prevent ordinary V2 turns from racing the daemon-managed memory writer. */
@@ -216,20 +81,6 @@ export function renderLocalMemoryV2WriteGuard() {
216
81
  ## V2 shared memory write safety
217
82
  本节覆盖前文关于普通任务直接更新共享记忆的例外规则。本 Agent 已启用结构化本地记忆 V2。普通任务不得直接编辑共享的 $CREW_HOME/MEMORY.md 或 $CREW_HOME/notes/ 文件;即使用户明确要求记住内容,也只把候选、用户授权和分类理由记录到本任务的 $CREW_TASK_LOG,留给任务结束后的 memory-prune 结构化收尾应用。只有 memory-prune 收尾提示明确要求写入 daemon 提供的 result 文件时,才按该协议提交候选。`;
218
83
  }
219
- async function localMemoryStorageFacts(agentDir) {
220
- const memory = await readFile(join(agentDir, "MEMORY.md"), "utf8");
221
- const storageVersion = detectLocalMemoryStorageVersion(memory);
222
- let committedState = false;
223
- try {
224
- const state = MigrationStateSchema.parse(JSON.parse(await readFile(join(agentDir, ".crew", "memory", "migration.v1.json"), "utf8")));
225
- committedState = state.phase === "committed" && state.storageVersion === 2;
226
- }
227
- catch (error) {
228
- if (error.code !== "ENOENT" && storageVersion === 2)
229
- throw error;
230
- }
231
- return { storageVersion, committedState };
232
- }
233
84
  function workLogFailure(error) {
234
85
  const reason = error instanceof Error ? error.message : "";
235
86
  return { status: "failed", error: reason === "local_memory_work_log_source_changed"
@@ -247,50 +98,30 @@ export async function commitLocalMemoryResultAfterExit(input) {
247
98
  return { status: "missing" };
248
99
  return { status: "failed", error: error instanceof Error ? error.message : "read_failed" };
249
100
  }
250
- let storageBefore;
251
- let storageAfter;
252
- let applied;
253
- let shouldClearWorkLog = false;
254
101
  try {
255
102
  const result = parseLocalMemoryResult(raw);
256
103
  if (result.outcome === "conflict" || result.outcome === "failed") {
257
104
  return { status: "failed", error: `local_memory_result_${result.outcome}` };
258
105
  }
259
- shouldClearWorkLog = result.workLogDisposition === "clear";
260
106
  if (input.workLogPath !== undefined) {
261
107
  try {
262
108
  await verifyWorkLogSource(input.agentDir, input.workLogPath, input.expectedWorkLogSha256);
263
- if (shouldClearWorkLog)
264
- shouldClearWorkLog = !DELIVERY_EVIDENCE_PATTERN.test(await readFile(input.workLogPath, "utf8"));
265
109
  }
266
110
  catch (error) {
267
111
  return workLogFailure(error);
268
112
  }
269
113
  }
270
- storageBefore = await localMemoryStorageFacts(input.agentDir);
271
- if (input.requiredStorageVersion === 2
272
- && (storageBefore.storageVersion !== 2 || !storageBefore.committedState)) {
273
- return { status: "failed", error: "local_memory_v2_state_invalid_before_commit" };
274
- }
275
- applied = await applyLocalMemoryResult({
114
+ await applyLocalMemoryResult({
276
115
  agentDir: input.agentDir,
277
116
  result,
278
117
  executionId: input.executionId,
279
118
  pruneTraceId: input.pruneTraceId,
280
- ...(input.writerCapabilities === undefined ? {} : { writerCapabilities: input.writerCapabilities }),
281
- ...(input.requiredStorageVersion === undefined ? {} : { requiredStorageVersion: input.requiredStorageVersion }),
282
- ...(input.migrationDeferredPending ? { migrationDeferredPending: true } : {}),
283
119
  });
284
- storageAfter = await localMemoryStorageFacts(input.agentDir);
285
- if (input.requiredStorageVersion === 2
286
- && (storageAfter.storageVersion !== 2 || !storageAfter.committedState)) {
287
- return { status: "failed", error: "local_memory_v2_state_invalid_after_commit" };
288
- }
289
120
  }
290
121
  catch (error) {
291
122
  return { status: "failed", error: error instanceof Error ? error.message : "apply_failed" };
292
123
  }
293
- if (input.workLogPath !== undefined && shouldClearWorkLog) {
124
+ if (input.workLogPath !== undefined) {
294
125
  try {
295
126
  const before = await verifyWorkLogSource(input.agentDir, input.workLogPath, input.expectedWorkLogSha256);
296
127
  const handle = await open(input.workLogPath, constants.O_WRONLY | (constants.O_NOFOLLOW ?? 0) | (constants.O_NONBLOCK ?? 0));
@@ -316,10 +147,5 @@ export async function commitLocalMemoryResultAfterExit(input) {
316
147
  catch {
317
148
  return { status: "failed", error: "local_memory_result_cleanup_failed" };
318
149
  }
319
- return { status: "applied", storageVersionBefore: storageBefore.storageVersion,
320
- storageVersionAfter: storageAfter.storageVersion, committedState: storageAfter.committedState,
321
- createdIds: applied.createdIds, updatedIds: applied.updatedIds,
322
- statusChangedIds: applied.statusChangedIds, pendingIds: applied.pendingIds,
323
- ...(input.migrationDeferredPending
324
- ? { pendingReason: "legacy_requires_migration" } : {}) };
150
+ return { status: "applied" };
325
151
  }