@nowcrew/daemon 0.6.10 → 0.6.12

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.
@@ -440,8 +440,6 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
440
440
  for (const mount of mounts) {
441
441
  const sources = verified.files.filter((file) => file.path.endsWith(".md")
442
442
  && (file.path === mount.path || file.path.startsWith(`${mount.path}/`)));
443
- if (sources.length === 0)
444
- throw new Error(`ability_memory_mount_empty:${mount.path}`);
445
443
  for (const source of sources) {
446
444
  const relativePath = source.path === mount.path
447
445
  ? source.path.split("/").at(-1)
@@ -467,7 +465,10 @@ const materializeWorkspaceTraining = async (agentsRoot, handle, ability) => {
467
465
  : join(sourceRoot, asset.source);
468
466
  await cp(source, target, { recursive: true, preserveTimestamps: true });
469
467
  }
470
- const evalPaths = [verified.manifest.spec.evals.policy, ...verified.manifest.spec.evals.cases];
468
+ const evalPaths = [
469
+ ...(verified.manifest.spec.evals.policy === undefined ? [] : [verified.manifest.spec.evals.policy]),
470
+ ...verified.manifest.spec.evals.cases,
471
+ ];
471
472
  for (const path of evalPaths) {
472
473
  const target = join(staging, "evals", layerRelativePath(path, "evals"));
473
474
  await mkdir(dirname(target), { recursive: true });
@@ -253,9 +253,19 @@ const scan = (files, manifest) => {
253
253
  if (executable.has(extname(file.path).toLowerCase()))
254
254
  findings.push({ code: "executable_asset", path: file.path });
255
255
  }
256
- const required = new Set(["instructions", "memory-policy", "memory-seed", "workspace", "eval-policy", "eval-cases"]);
256
+ const required = new Set(["instructions"]);
257
257
  if (manifest.spec.skills.length > 0)
258
258
  required.add("skills");
259
+ if (manifest.spec.memory.policy.length > 0)
260
+ required.add("memory-policy");
261
+ if (manifest.spec.memory.seeds.length > 0)
262
+ required.add("memory-seed");
263
+ if (manifest.spec.workspace.assets.length > 0)
264
+ required.add("workspace");
265
+ if (manifest.spec.evals.policy !== undefined)
266
+ required.add("eval-policy");
267
+ if (manifest.spec.evals.cases.length > 0)
268
+ required.add("eval-cases");
259
269
  return { findings, required };
260
270
  };
261
271
  const lockedManifestPaths = (manifest) => [
@@ -264,7 +274,7 @@ const lockedManifestPaths = (manifest) => [
264
274
  ...manifest.spec.memory.policy.map((entry) => entry.path),
265
275
  ...manifest.spec.memory.seeds.map((entry) => entry.path),
266
276
  ...manifest.spec.workspace.assets.map((entry) => entry.source),
267
- manifest.spec.evals.policy,
277
+ ...(manifest.spec.evals.policy === undefined ? [] : [manifest.spec.evals.policy]),
268
278
  ...manifest.spec.evals.cases,
269
279
  ];
270
280
  export const evaluateAbilityCases = (files, manifest, lock, memory) => {
@@ -367,7 +377,7 @@ export async function resolveAbilityRelease(agentsRoot, command) {
367
377
  const assertions = [
368
378
  { name: "manifest_lock_verified", passed: true },
369
379
  { name: "manifest_asset_paths_locked", passed: lockedManifestPaths(manifest).every((path) => lockedPaths.has(path)) },
370
- { name: "all_five_layers_resolvable", passed: [...security.required].every((kind) => kind === "skills" ? kinds.has(kind) || (manifest.spec.skills.length > 0 && lock.dependencies.length > 0) : kinds.has(kind)) },
380
+ { name: "declared_layers_resolvable", passed: [...security.required].every((kind) => kind === "skills" ? kinds.has(kind) || (manifest.spec.skills.length > 0 && lock.dependencies.length > 0) : kinds.has(kind)) },
371
381
  { name: "dependencies_pinned", passed: lock.dependencies.every((dependency) => /^[a-f0-9]{40}$/u.test(dependency.commit)) },
372
382
  { name: "security_scan_clear", passed: security.findings.length === 0 },
373
383
  ...evaluateAbilityCases(files, manifest, lock, memory),
@@ -61,16 +61,20 @@ export const DaemonAbilityManifestSchema = z.object({
61
61
  skills: z.array(z.object({
62
62
  name: z.string().min(1).max(128), source: z.union([LocalSkillSchema, GitSkillSchema]),
63
63
  mount: SafeAbilityPathSchema, mode: ModeSchema, trust: z.string().max(128).optional(),
64
- }).strict()).max(500),
64
+ }).strict()).max(500).default([]),
65
65
  memory: z.object({
66
- policy: z.array(z.object({ path: SafeAbilityPathSchema, mode: ModeSchema, mergeKey: z.string().optional() }).strict()).max(100),
67
- seeds: z.array(z.object({ path: SafeAbilityPathSchema, mode: ModeSchema, mergeKey: z.string().optional() }).strict()).max(1_000),
68
- runtime: z.object({ store: z.literal("nowcrew-managed"), promotion: z.literal("pull-request"), retentionDays: z.number().int().positive().optional() }).strict(),
69
- }).strict(),
66
+ policy: z.array(z.object({ path: SafeAbilityPathSchema, mode: ModeSchema, mergeKey: z.string().optional() }).strict()).max(100).default([]),
67
+ seeds: z.array(z.object({ path: SafeAbilityPathSchema, mode: ModeSchema, mergeKey: z.string().optional() }).strict()).max(1_000).default([]),
68
+ runtime: z.object({ store: z.literal("nowcrew-managed"), promotion: z.literal("pull-request"), retentionDays: z.number().int().positive().optional() }).strict()
69
+ .default({ store: "nowcrew-managed", promotion: "pull-request" }),
70
+ }).strict().default({ policy: [], seeds: [], runtime: { store: "nowcrew-managed", promotion: "pull-request" } }),
70
71
  workspace: z.object({ assets: z.array(z.object({
71
72
  source: SafeAbilityPathSchema, target: WorkspaceAbilityTargetSchema, mode: ModeSchema,
72
- }).strict()).max(1_000) }).strict(),
73
- evals: z.object({ policy: SafeAbilityPathSchema, cases: z.array(SafeAbilityPathSchema).min(1).max(1_000) }).strict(),
73
+ }).strict()).max(1_000).default([]) }).strict().default({ assets: [] }),
74
+ evals: z.object({
75
+ policy: SafeAbilityPathSchema.optional(),
76
+ cases: z.array(SafeAbilityPathSchema).max(1_000).default([]),
77
+ }).strict().default({ cases: [] }),
74
78
  compatibility: z.object({
75
79
  providers: z.array(z.enum(["claude", "codex", "kimi", "hermes", "opencode", "deepseek-harness"])).min(1),
76
80
  minNowCrewVersion: z.string().regex(/^\d+\.\d+\.\d+$/u),
@@ -788,6 +788,16 @@ async function executeLocalUnlocked(input, callbacks, dependencies) {
788
788
  changes_truncated: changesTruncated,
789
789
  evidence_complete: noteDiff.evidence_complete
790
790
  && !changesTruncated,
791
+ before_manifest_error: noteDiff.before_manifest.error,
792
+ after_manifest_error: noteDiff.after_manifest.error,
793
+ before_manifest_truncated: noteDiff.before_manifest.truncated,
794
+ after_manifest_truncated: noteDiff.after_manifest.truncated,
795
+ before_total_count: noteDiff.before_manifest.total_count,
796
+ after_total_count: noteDiff.after_manifest.total_count,
797
+ before_omitted_count: noteDiff.before_manifest.omitted_count,
798
+ after_omitted_count: noteDiff.after_manifest.omitted_count,
799
+ before_incomplete_file_count: noteDiff.before_manifest.incomplete_file_count,
800
+ after_incomplete_file_count: noteDiff.after_manifest.incomplete_file_count,
791
801
  changes: serializedChanges.json,
792
802
  });
793
803
  const postcondition = evaluateMemoryPrunePostcondition(memoryPruneBeforeSnapshot, snapshot, {
@@ -266,45 +266,69 @@ function fileChanged(before, after) {
266
266
  }
267
267
  return before.size !== after.size || before.mtime_ms !== after.mtime_ms;
268
268
  }
269
+ function notePresence(file) {
270
+ if (file === undefined)
271
+ return "absent";
272
+ if (file.exists)
273
+ return "present";
274
+ if (!file.exists && (file.error === undefined || file.error === "ENOENT"))
275
+ return "absent";
276
+ return "unknown";
277
+ }
269
278
  export function diffMemoryPruneNotes(before, after) {
270
- const beforeByPath = new Map(before.files
271
- .filter((file) => file.exists)
272
- .map((file) => [file.relative_path, file]));
273
- const afterByPath = new Map(after.files
274
- .filter((file) => file.exists)
275
- .map((file) => [file.relative_path, file]));
279
+ const manifestDiagnostics = (manifest) => ({
280
+ total_count: manifest.total_count,
281
+ omitted_count: manifest.omitted_count,
282
+ truncated: manifest.truncated,
283
+ incomplete_file_count: manifest.files.filter((file) => !file.exists
284
+ || file.sha256 === undefined
285
+ || file.error !== undefined
286
+ || file.hash_skipped_reason !== undefined).length,
287
+ ...(manifest.error === undefined ? {} : { error: manifest.error }),
288
+ });
289
+ const beforeDiagnostics = manifestDiagnostics(before);
290
+ const afterDiagnostics = manifestDiagnostics(after);
291
+ const beforeByPath = new Map(before.files.map((file) => [file.relative_path, file]));
292
+ const afterByPath = new Map(after.files.map((file) => [file.relative_path, file]));
276
293
  const paths = [...new Set([...beforeByPath.keys(), ...afterByPath.keys()])]
277
294
  .sort(compareCodeUnits);
278
295
  const allChanges = [];
279
- for (const relativePath of paths) {
280
- const beforeFile = beforeByPath.get(relativePath);
281
- const afterFile = afterByPath.get(relativePath);
282
- if (beforeFile === undefined && afterFile !== undefined) {
283
- allChanges.push({
284
- relative_path: relativePath,
285
- change: "created",
286
- ...(afterFile.sha256 === undefined ? {} : { after_sha256: afterFile.sha256 }),
287
- ...(afterFile.size === undefined ? {} : { after_size: afterFile.size }),
288
- });
289
- }
290
- else if (beforeFile !== undefined && afterFile === undefined) {
291
- allChanges.push({
292
- relative_path: relativePath,
293
- change: "deleted",
294
- ...(beforeFile.sha256 === undefined ? {} : { before_sha256: beforeFile.sha256 }),
295
- ...(beforeFile.size === undefined ? {} : { before_size: beforeFile.size }),
296
- });
297
- }
298
- else if (beforeFile !== undefined && afterFile !== undefined
299
- && fileChanged(beforeFile, afterFile)) {
300
- allChanges.push({
301
- relative_path: relativePath,
302
- change: "updated",
303
- ...(beforeFile.sha256 === undefined ? {} : { before_sha256: beforeFile.sha256 }),
304
- ...(afterFile.sha256 === undefined ? {} : { after_sha256: afterFile.sha256 }),
305
- ...(beforeFile.size === undefined ? {} : { before_size: beforeFile.size }),
306
- ...(afterFile.size === undefined ? {} : { after_size: afterFile.size }),
307
- });
296
+ if (before.error === undefined && after.error === undefined) {
297
+ const presenceDiffIsReliable = !before.truncated && !after.truncated;
298
+ for (const relativePath of paths) {
299
+ const beforeFile = beforeByPath.get(relativePath);
300
+ const afterFile = afterByPath.get(relativePath);
301
+ const beforePresence = notePresence(beforeFile);
302
+ const afterPresence = notePresence(afterFile);
303
+ if (presenceDiffIsReliable && beforePresence === "absent" && afterPresence === "present"
304
+ && afterFile !== undefined) {
305
+ allChanges.push({
306
+ relative_path: relativePath,
307
+ change: "created",
308
+ ...(afterFile.sha256 === undefined ? {} : { after_sha256: afterFile.sha256 }),
309
+ ...(afterFile.size === undefined ? {} : { after_size: afterFile.size }),
310
+ });
311
+ }
312
+ else if (presenceDiffIsReliable && beforePresence === "present" && afterPresence === "absent"
313
+ && beforeFile !== undefined) {
314
+ allChanges.push({
315
+ relative_path: relativePath,
316
+ change: "deleted",
317
+ ...(beforeFile.sha256 === undefined ? {} : { before_sha256: beforeFile.sha256 }),
318
+ ...(beforeFile.size === undefined ? {} : { before_size: beforeFile.size }),
319
+ });
320
+ }
321
+ else if (beforePresence === "present" && afterPresence === "present"
322
+ && beforeFile !== undefined && afterFile !== undefined && fileChanged(beforeFile, afterFile)) {
323
+ allChanges.push({
324
+ relative_path: relativePath,
325
+ change: "updated",
326
+ ...(beforeFile.sha256 === undefined ? {} : { before_sha256: beforeFile.sha256 }),
327
+ ...(afterFile.sha256 === undefined ? {} : { after_sha256: afterFile.sha256 }),
328
+ ...(beforeFile.size === undefined ? {} : { before_size: beforeFile.size }),
329
+ ...(afterFile.size === undefined ? {} : { after_size: afterFile.size }),
330
+ });
331
+ }
308
332
  }
309
333
  }
310
334
  return {
@@ -315,10 +339,12 @@ export function diffMemoryPruneNotes(before, after) {
315
339
  && after.error === undefined
316
340
  && !before.truncated
317
341
  && !after.truncated
318
- && [...before.files, ...after.files].every((file) => file.exists
319
- && file.sha256 !== undefined
320
- && file.error === undefined
321
- && file.hash_skipped_reason === undefined),
342
+ && before.omitted_count === 0
343
+ && after.omitted_count === 0
344
+ && beforeDiagnostics.incomplete_file_count === 0
345
+ && afterDiagnostics.incomplete_file_count === 0,
346
+ before_manifest: beforeDiagnostics,
347
+ after_manifest: afterDiagnostics,
322
348
  };
323
349
  }
324
350
  export function evaluateMemoryPrunePostcondition(before, after, execution) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowcrew/daemon",
3
- "version": "0.6.10",
3
+ "version": "0.6.12",
4
4
  "type": "module",
5
5
  "description": "crew daemon — 运行在用户机器:拉起/管理 agent 进程,注入 crew CLI,归一化 runtime 事件",
6
6
  "license": "Apache-2.0",