@nowcrew/daemon 0.6.87-beta.2 → 0.6.87-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 (36) hide show
  1. package/dist/cloud-report-uploader.js +27 -25
  2. package/dist/execution-runner.js +4 -2
  3. package/dist/external-output.js +154 -68
  4. package/dist/local-executor.js +37 -5
  5. package/dist/local-memory/pending.js +136 -2
  6. package/dist/local-memory/runtime.js +8 -1
  7. package/dist/local-memory/write.js +126 -4
  8. package/dist/main.js +6 -2
  9. package/dist/project-skills/capability.js +109 -0
  10. package/dist/project-skills/controller-convergence.js +57 -0
  11. package/dist/project-skills/projection-set-switch.js +419 -0
  12. package/dist/project-skills/projection-state-domain.js +170 -0
  13. package/dist/project-skills/projection-state-store.js +842 -0
  14. package/dist/project-skills/projection-state-transaction.js +322 -0
  15. package/dist/project-skills/projection-state.js +3 -0
  16. package/dist/project-skills/runtime-root-bootstrap.js +47 -0
  17. package/dist/project-skills/runtime-root-domain.js +268 -0
  18. package/dist/project-skills/runtime-root-gc.js +293 -0
  19. package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
  20. package/dist/project-skills/runtime-root-leases.js +487 -0
  21. package/dist/project-skills/runtime-root-source-identity.js +60 -0
  22. package/dist/project-skills/runtime-root-startup.js +49 -0
  23. package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
  24. package/dist/project-skills/runtime-root-state-index.js +356 -0
  25. package/dist/project-skills/runtime-root-store.js +722 -0
  26. package/dist/project-skills/serve-capability.js +28 -0
  27. package/dist/project-skills/serve-startup.js +37 -0
  28. package/dist/prompt.js +4 -1
  29. package/dist/runner.js +3 -1
  30. package/dist/serve.js +17 -11
  31. package/dist/storage-identity.js +4 -1
  32. package/dist/storage-ownership.js +93 -5
  33. package/dist/storage-runtime-controller.js +40 -20
  34. package/dist/storage-runtime.js +38 -2
  35. package/dist/storage-serve-adapter.js +22 -5
  36. package/package.json +1 -1
@@ -4,7 +4,7 @@ import { dirname, join, relative, resolve, sep } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { containsLikelySecret } from "../agent-memory/policy.js";
6
6
  import { LOCAL_MEMORY_INDEX_CODE_POINT_CAP, LOCAL_MEMORY_INDEX_ENTRY_CAP, LOCAL_MEMORY_TOTAL_CODE_POINT_CAP, renderLocalMemoryIndexCard, selectLocalMemoryIndexEntries, } from "./index-budget.js";
7
- import { persistPendingMemoryCandidate } from "./pending.js";
7
+ import { persistPendingMemoryCandidate, readPendingMemoryCandidates, removePendingMemoryCandidate, } from "./pending.js";
8
8
  const CategorySchema = z.enum([
9
9
  "preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
10
10
  ]);
@@ -228,6 +228,47 @@ function normalize(value) {
228
228
  function activeOrPinnedIds(entries) {
229
229
  return new Set(entries.filter((entry) => entry.status === "active" || entry.status === "pinned").map((entry) => entry.id));
230
230
  }
231
+ function pinnedIds(entries) {
232
+ return new Set(entries.filter((entry) => entry.status === "pinned").map((entry) => entry.id));
233
+ }
234
+ const DETAIL_CATEGORIES = new Set([
235
+ "preferences", "project-rules", "testing", "debugging", "git", "incidents", "lessons",
236
+ ]);
237
+ function detailCategoryFromFile(file) {
238
+ if (!file.startsWith("notes/") || !file.endsWith(".md"))
239
+ return null;
240
+ const category = file.slice("notes/".length, -".md".length);
241
+ return DETAIL_CATEGORIES.has(category) ? category : null;
242
+ }
243
+ function readNoteBody(content, id) {
244
+ const begin = content.indexOf(`<!-- NOWWORK:MEMORY_ENTRY:BEGIN id=${id}`);
245
+ if (begin < 0)
246
+ return null;
247
+ const bodyStart = content.indexOf("\n\n", begin);
248
+ const bodyEnd = content.indexOf(`\n<!-- NOWWORK:MEMORY_ENTRY:END id=${id}`, bodyStart + 2);
249
+ if (bodyStart < 0 || bodyEnd < 0)
250
+ return null;
251
+ return content.slice(bodyStart + 2, bodyEnd).trim();
252
+ }
253
+ function archivedChange(entry, detailBody) {
254
+ const detailCategory = detailCategoryFromFile(entry.file);
255
+ if (detailCategory === null)
256
+ return null;
257
+ return {
258
+ operation: "create",
259
+ memoryId: null,
260
+ baseRevision: null,
261
+ supersedesMemoryId: null,
262
+ title: entry.title,
263
+ kind: entry.type,
264
+ summary: entry.summary,
265
+ triggers: entry.triggers,
266
+ priority: entry.priority,
267
+ detailCategory,
268
+ detailBody,
269
+ reviewAfter: entry.reviewAfter,
270
+ };
271
+ }
231
272
  function emptyResult() {
232
273
  return { createdIds: [], updatedIds: [], statusChangedIds: [], deferredIds: [], pendingIds: [] };
233
274
  }
@@ -341,7 +382,9 @@ export async function applyLocalMemoryResult(input) {
341
382
  const statusChangedIds = input.result.statusChanges.map((change) => change.memoryId);
342
383
  const deferredIds = [];
343
384
  const pendingCandidates = [];
385
+ const resolvedPendingIds = new Set();
344
386
  const noteUpdates = new Map();
387
+ let allowActiveEviction = false;
345
388
  const randomId = input.randomId ?? randomUUID;
346
389
  const executionId = input.executionId ?? "unknown";
347
390
  const defer = (change, index) => {
@@ -399,7 +442,10 @@ export async function applyLocalMemoryResult(input) {
399
442
  : item),
400
443
  entry,
401
444
  ];
402
- const protectedIds = new Set(activeOrPinnedIds(entries));
445
+ const allowEvictionForChange = change.operation === "create" && change.priority === "high";
446
+ const protectedIds = new Set(allowEvictionForChange ? pinnedIds(entries) : activeOrPinnedIds(entries));
447
+ if (allowEvictionForChange)
448
+ allowActiveEviction = true;
403
449
  if (change.supersedesMemoryId !== null)
404
450
  protectedIds.delete(change.supersedesMemoryId);
405
451
  const projection = projectIndex(memory, candidateEntries, protectedIds);
@@ -428,15 +474,91 @@ export async function applyLocalMemoryResult(input) {
428
474
  else if (existingCreate === undefined)
429
475
  createdIds.push(id);
430
476
  }
431
- const finalProjection = projectIndex(memory, entries, activeOrPinnedIds(entries));
432
- if (finalProjection.reason !== null)
477
+ // Pending create candidates are durable overflow, not dead ends. Promote only
478
+ // when the current catalog has room; supersede/update candidates stay pending
479
+ // until a fresh revision-aware decision can safely replay them.
480
+ const pendingCandidatesOnDisk = await readPendingMemoryCandidates({ agentDir: input.agentDir });
481
+ for (const pending of pendingCandidatesOnDisk) {
482
+ const change = pending.candidate;
483
+ if (change.operation !== "create")
484
+ continue;
485
+ const id = stableId(`pending:${pending.pendingId}`, 0, change);
486
+ const existing = entries.find((entry) => entry.id === id);
487
+ if (existing !== undefined) {
488
+ resolvedPendingIds.add(pending.pendingId);
489
+ continue;
490
+ }
491
+ const duplicate = entries.find((entry) => (entry.status === "active" || entry.status === "pinned")
492
+ && (normalize(entry.title) === normalize(change.title)
493
+ || normalize(entry.summary) === normalize(change.summary)));
494
+ if (duplicate !== undefined) {
495
+ const duplicatePath = resolve(input.agentDir, duplicate.file);
496
+ const duplicateBody = readNoteBody(noteUpdates.get(duplicatePath) ?? await readNote(duplicatePath), duplicate.id);
497
+ if (duplicateBody === change.detailBody)
498
+ resolvedPendingIds.add(pending.pendingId);
499
+ continue;
500
+ }
501
+ const entry = entryFromChange(change, id, 1, "active");
502
+ const candidateEntries = [...entries, entry];
503
+ const allowEvictionForPending = change.priority === "high";
504
+ const projection = projectIndex(memory, candidateEntries, allowEvictionForPending ? pinnedIds(entries) : activeOrPinnedIds(entries));
505
+ if (allowEvictionForPending)
506
+ allowActiveEviction = true;
507
+ if (projection.omittedIds.includes(id))
508
+ continue;
509
+ const notePath = resolve(input.agentDir, entry.file);
510
+ if (!isWithin(resolve(input.agentDir, "notes"), notePath))
511
+ throw new Error("local_memory_note_path_invalid");
512
+ const previous = noteUpdates.get(notePath) ?? await readNote(notePath);
513
+ const replacement = noteSection(id, 1, change.title, change.detailBody);
514
+ const existingSection = replaceNote(previous, id, replacement);
515
+ const next = existingSection ?? `${previous.trimEnd()}${previous.trimEnd() ? "\n\n" : ""}${replacement}`;
516
+ noteUpdates.set(notePath, next);
517
+ entries = candidateEntries;
518
+ createdIds.push(id);
519
+ resolvedPendingIds.add(pending.pendingId);
520
+ }
521
+ let finalProjection = projectIndex(memory, entries, allowActiveEviction ? pinnedIds(entries) : activeOrPinnedIds(entries));
522
+ if (finalProjection.reason !== null
523
+ && (!allowActiveEviction || finalProjection.reason === "index_over_budget_pinned")) {
433
524
  throw new Error(finalProjection.reason);
525
+ }
526
+ if (allowActiveEviction) {
527
+ const omittedActiveEntries = finalProjection.omittedIds
528
+ .map((id) => entries.find((entry) => entry.id === id))
529
+ .filter((entry) => entry?.status === "active");
530
+ for (const entry of omittedActiveEntries) {
531
+ const notePath = resolve(input.agentDir, entry.file);
532
+ const content = noteUpdates.get(notePath) ?? await readNote(notePath);
533
+ const detailBody = readNoteBody(content, entry.id);
534
+ const change = detailBody === null ? null : archivedChange(entry, detailBody);
535
+ if (change === null)
536
+ throw new Error("local_memory_note_anchor_missing");
537
+ const pendingId = `pending-${stableId(`evicted:${executionId}`, 0, change).slice("mem_".length)}`;
538
+ pendingCandidates.push({
539
+ schemaVersion: 1,
540
+ pendingId,
541
+ createdAt: new Date().toISOString(),
542
+ reason: "index_over_budget",
543
+ source: { executionId, pruneTraceId: input.pruneTraceId ?? null },
544
+ candidate: change,
545
+ });
546
+ deferredIds.push(pendingId);
547
+ entries = entries.map((item) => item.id === entry.id ? { ...item, status: "archived" } : item);
548
+ }
549
+ finalProjection = projectIndex(memory, entries, pinnedIds(entries));
550
+ if (finalProjection.reason !== null)
551
+ throw new Error(finalProjection.reason);
552
+ }
434
553
  for (const candidate of pendingCandidates)
435
554
  await persistPendingMemoryCandidate({ agentDir: input.agentDir, candidate });
436
555
  for (const [path, content] of noteUpdates)
437
556
  await atomicWrite(path, content);
438
557
  if (noteUpdates.size > 0 || statusChangedIds.length > 0)
439
558
  await atomicWrite(memoryPath, finalProjection.memory);
559
+ for (const pendingId of resolvedPendingIds) {
560
+ await removePendingMemoryCandidate({ agentDir: input.agentDir, pendingId });
561
+ }
440
562
  return {
441
563
  createdIds,
442
564
  updatedIds,
package/dist/main.js CHANGED
@@ -24,7 +24,7 @@ import { formatDaemonStartupError } from "./daemon-startup-error.js";
24
24
  import { assertManagedServiceStartup } from "./managed-service-startup.js";
25
25
  import { daemonGlobalInstallation } from "./daemon-installation.js";
26
26
  import { acquireDaemonInstallationLease, runWithDaemonInstallationLease, } from "./daemon-installation-lease.js";
27
- import { loadStorageRuntime } from "./storage-runtime.js";
27
+ import { createStorageRuntimeOwnershipBinding, loadStorageRuntime } from "./storage-runtime.js";
28
28
  import { readStorageMigrationReceipt } from "./storage-migration-controller.js";
29
29
  import { storageMigrationStatusReplay } from "./storage-status-replay.js";
30
30
  import { runStorageWorkerCommand } from "./storage-worker-command.js";
@@ -160,6 +160,9 @@ async function main() {
160
160
  ...(process.getuid === undefined ? {} : { uid: process.getuid() }),
161
161
  environment: process.env,
162
162
  });
163
+ const targetStorageBinding = storageRuntime === null
164
+ ? null
165
+ : createStorageRuntimeOwnershipBinding(storageRuntime);
163
166
  const targetConfirmation = storageRuntime?.identity.storageMigrationId === undefined
164
167
  ? null
165
168
  : createStorageTargetConfirmationHandler({
@@ -172,7 +175,8 @@ async function main() {
172
175
  ? {
173
176
  ready: false,
174
177
  statusOutboxPath: join(storageRuntime.local.stateRoot, "storage-status-outbox.json"),
175
- identity: storageRuntime.identity,
178
+ identity: targetStorageBinding.identity,
179
+ bindServerIdentity: targetStorageBinding.bind,
176
180
  ...(targetConfirmation === null ? {} : { confirm: targetConfirmation.handle }),
177
181
  ...(storageRuntime.identity.storageMigrationId === undefined ? {} : {
178
182
  replayStatus: async (send) => {
@@ -0,0 +1,109 @@
1
+ import { PROJECT_SKILLS_CAPABILITY, PROJECT_SKILL_PROJECTION_V2_CAPABILITY, } from "./types.js";
2
+ export function projectSkillV2RolloutReady(platform, readiness) {
3
+ const platformReady = platform === "darwin"
4
+ || platform === "linux"
5
+ || (platform === "win32" && readiness.nativeWindowsSmokePassed);
6
+ return platformReady
7
+ && readiness.runtimeRootStorePrimitiveReady
8
+ && readiness.runtimeRootStoreRecoveryReady
9
+ && readiness.leaseRecoveryReady
10
+ && readiness.controllerReady
11
+ && readiness.executionHandlerReady
12
+ && readiness.codexAdapterReady
13
+ && readiness.claudeAdapterReady;
14
+ }
15
+ export function effectiveProjectSkillCapabilities(capabilities, projectSkillsReady, projectSkillV2Ready) {
16
+ const projectSkillCapabilitiesReady = projectSkillsReady && projectSkillV2Ready;
17
+ if (!projectSkillCapabilitiesReady
18
+ && !capabilities.includes(PROJECT_SKILLS_CAPABILITY)
19
+ && !capabilities.includes(PROJECT_SKILL_PROJECTION_V2_CAPABILITY)
20
+ && Object.isFrozen(capabilities))
21
+ return capabilities;
22
+ const filtered = capabilities.filter((capability) => capability !== PROJECT_SKILL_PROJECTION_V2_CAPABILITY
23
+ && (projectSkillCapabilitiesReady || capability !== PROJECT_SKILLS_CAPABILITY));
24
+ if (!projectSkillCapabilitiesReady || !filtered.includes(PROJECT_SKILLS_CAPABILITY)) {
25
+ return Object.freeze(filtered);
26
+ }
27
+ const v1Index = filtered.indexOf(PROJECT_SKILLS_CAPABILITY);
28
+ return Object.freeze([
29
+ ...filtered.slice(0, v1Index + 1),
30
+ PROJECT_SKILL_PROJECTION_V2_CAPABILITY,
31
+ ...filtered.slice(v1Index + 1),
32
+ ]);
33
+ }
34
+ export function withProjectSkillCapabilityStatus(value, status) {
35
+ return {
36
+ ...value,
37
+ projectSkillsStatus: status,
38
+ capabilities: status === "ready"
39
+ ? value.capabilities
40
+ : effectiveProjectSkillCapabilities(value.capabilities, false, false),
41
+ };
42
+ }
43
+ const settleReadinessGate = async (operation) => {
44
+ try {
45
+ return await operation() === true;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ };
51
+ export async function createProjectSkillConnectionCapabilitySnapshot(input) {
52
+ const runtimeRootStorePrimitiveReady = await settleReadinessGate(input.probeRuntimeRootStorePrimitive);
53
+ const controllerReady = await settleReadinessGate(input.initializeController);
54
+ const recoveryReady = await settleReadinessGate(input.recoverRuntimeRootsAndLeases);
55
+ const readiness = Object.freeze({
56
+ runtimeRootStorePrimitiveReady,
57
+ runtimeRootStoreRecoveryReady: recoveryReady,
58
+ leaseRecoveryReady: recoveryReady,
59
+ controllerReady,
60
+ executionHandlerReady: true,
61
+ codexAdapterReady: true,
62
+ claudeAdapterReady: true,
63
+ nativeWindowsSmokePassed: false,
64
+ });
65
+ const projectSkillsReady = projectSkillV2RolloutReady(input.platform, readiness);
66
+ return Object.freeze({
67
+ capabilities: effectiveProjectSkillCapabilities(input.baseCapabilities, projectSkillsReady, projectSkillsReady),
68
+ projectSkillsReady,
69
+ readiness,
70
+ });
71
+ }
72
+ export async function prepareProjectSkillConnectionCapabilities(input) {
73
+ const snapshot = await createProjectSkillConnectionCapabilitySnapshot({
74
+ ...input,
75
+ baseCapabilities: [...input.baseCapabilities, ...await input.additionalCapabilities()],
76
+ });
77
+ input.applyReadiness(snapshot.projectSkillsReady);
78
+ return snapshot.capabilities;
79
+ }
80
+ export function createProjectSkillControllerReadiness(input) {
81
+ let status = "initializing";
82
+ let initialization = null;
83
+ const ensure = () => {
84
+ if (status === "ready")
85
+ return Promise.resolve(true);
86
+ if (initialization !== null)
87
+ return initialization;
88
+ status = "initializing";
89
+ const attempt = input.initialize().then(() => {
90
+ status = "ready";
91
+ return true;
92
+ }, (error) => {
93
+ status = "unavailable";
94
+ input.onUnavailable(error);
95
+ return false;
96
+ }).finally(() => {
97
+ if (initialization === attempt)
98
+ initialization = null;
99
+ input.onSettled();
100
+ });
101
+ initialization = attempt;
102
+ return attempt;
103
+ };
104
+ return Object.freeze({
105
+ status: () => status,
106
+ ensure,
107
+ applyPreflight: (ready) => { status = ready ? "ready" : "unavailable"; },
108
+ });
109
+ }
@@ -0,0 +1,57 @@
1
+ import { ProjectProjectionError, } from "./reconciler.js";
2
+ export const MAX_REMEMBERED_PROJECT_SKILL_AGENTS = 64;
3
+ const compareBinary = (left, right) => {
4
+ if (left < right)
5
+ return -1;
6
+ if (left > right)
7
+ return 1;
8
+ return 0;
9
+ };
10
+ const copySnapshot = (snapshot) => Object.freeze({
11
+ bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
12
+ generation: snapshot.generation,
13
+ });
14
+ export function createProjectSkillSnapshotEpoch(maxEntries = MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
15
+ if (!Number.isSafeInteger(maxEntries) || maxEntries < 1) {
16
+ throw new ProjectProjectionError("skill_projection_failed");
17
+ }
18
+ const snapshots = new Map();
19
+ return Object.freeze({
20
+ begin() {
21
+ snapshots.clear();
22
+ },
23
+ remember(handle, snapshot) {
24
+ const previous = snapshots.get(handle);
25
+ if (previous !== undefined && snapshot.generation < previous.generation)
26
+ return;
27
+ if (previous === undefined && snapshots.size >= maxEntries) {
28
+ throw new ProjectProjectionError("skill_projection_failed");
29
+ }
30
+ snapshots.set(handle, copySnapshot(snapshot));
31
+ },
32
+ forget(handle) {
33
+ snapshots.delete(handle);
34
+ },
35
+ entries() {
36
+ return Object.freeze([...snapshots.entries()]
37
+ .sort(([left], [right]) => compareBinary(left, right))
38
+ .map(([handle, snapshot]) => Object.freeze([handle, copySnapshot(snapshot)])));
39
+ },
40
+ });
41
+ }
42
+ const stableErrorCode = (reason) => reason?.code === "skill_projection_snapshot_corrupt"
43
+ ? "skill_projection_snapshot_corrupt"
44
+ : "skill_projection_failed";
45
+ export function toPathPrivateConvergenceResults(entries, results) {
46
+ return Object.freeze(entries.map(([handle], index) => {
47
+ const result = results[index];
48
+ if (result?.status === "fulfilled") {
49
+ return Object.freeze({ handle, status: "fulfilled" });
50
+ }
51
+ return Object.freeze({
52
+ handle,
53
+ status: "rejected",
54
+ errorCode: stableErrorCode(result?.reason),
55
+ });
56
+ }));
57
+ }