@nowcrew/daemon 0.6.19 → 0.6.21

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 (40) hide show
  1. package/dist/atomic-no-replace-rename.js +91 -0
  2. package/dist/completion-retransmitter-logging.js +16 -0
  3. package/dist/completion-retransmitter.js +39 -4
  4. package/dist/control-plane-url.js +4 -2
  5. package/dist/directory-projection-publication.js +105 -0
  6. package/dist/directory-projection.js +20 -4
  7. package/dist/execution-journal.js +40 -4
  8. package/dist/execution-posix-stop-proof.js +82 -0
  9. package/dist/execution-runner.js +68 -8
  10. package/dist/local-executor.js +67 -52
  11. package/dist/machine-info.js +8 -5
  12. package/dist/project-skills/capability.js +109 -0
  13. package/dist/project-skills/controller-convergence.js +57 -0
  14. package/dist/project-skills/controller.js +80 -24
  15. package/dist/project-skills/initialized-reconciler.js +4 -4
  16. package/dist/project-skills/projection-state-domain.js +19 -2
  17. package/dist/project-skills/projection-state-store.js +3 -2
  18. package/dist/project-skills/projection-state-transaction.js +5 -1
  19. package/dist/project-skills/projection-state.js +1 -1
  20. package/dist/project-skills/reconciler.js +275 -102
  21. package/dist/project-skills/runtime-launch.js +102 -0
  22. package/dist/project-skills/runtime-root-bootstrap.js +47 -0
  23. package/dist/project-skills/runtime-root-domain.js +268 -0
  24. package/dist/project-skills/runtime-root-gc.js +293 -0
  25. package/dist/project-skills/runtime-root-lease-artifact.js +46 -0
  26. package/dist/project-skills/runtime-root-leases.js +487 -0
  27. package/dist/project-skills/runtime-root-source-identity.js +60 -0
  28. package/dist/project-skills/runtime-root-startup.js +49 -0
  29. package/dist/project-skills/runtime-root-state-artifact-domain.js +143 -0
  30. package/dist/project-skills/runtime-root-state-index.js +356 -0
  31. package/dist/project-skills/runtime-root-store.js +722 -0
  32. package/dist/project-skills/serve-capability.js +28 -0
  33. package/dist/project-skills/serve-startup.js +22 -0
  34. package/dist/project-skills/types.js +1 -0
  35. package/dist/runtimes/codex-home-migration-cli.js +26 -0
  36. package/dist/runtimes/codex-home-migration.js +112 -0
  37. package/dist/runtimes/codex-home.js +200 -17
  38. package/dist/serve.js +60 -79
  39. package/dist/supervised-runtime.js +1 -5
  40. package/package.json +2 -1
@@ -5,6 +5,7 @@ import { isProjectId, isProjectSkillName, compareProjectSkillRefs, MAX_AGENT_PRO
5
5
  import { ProjectProjectionError, } from "./reconciler.js";
6
6
  import { AgentHandleSchema } from "../execution-protocol.js";
7
7
  import { createPromiseTail } from "../promise-tail.js";
8
+ import { createProjectSkillSnapshotEpoch, MAX_REMEMBERED_PROJECT_SKILL_AGENTS, toPathPrivateConvergenceResults, } from "./controller-convergence.js";
8
9
  const ProjectIdSchema = z.string().refine(isProjectId);
9
10
  const AgentSkillsSyncSchema = z.object({
10
11
  type: z.literal("agent:skills:sync"),
@@ -56,6 +57,13 @@ class ProjectScanTimeoutError extends Error {
56
57
  this.name = "ProjectScanTimeoutError";
57
58
  }
58
59
  }
60
+ class ProjectSkillStaleEpochError extends Error {
61
+ code = "skill_projection_stale";
62
+ constructor() {
63
+ super("skill_projection_stale");
64
+ this.name = "ProjectSkillStaleEpochError";
65
+ }
66
+ }
59
67
  const withinScanDeadline = async (operation, timeoutMs) => {
60
68
  let timer;
61
69
  try {
@@ -79,7 +87,12 @@ export function createProjectSkillsController(deps) {
79
87
  let initializationState = "idle";
80
88
  const scan = deps.scan ?? scanProjects;
81
89
  const scanTimeoutMs = deps.scanTimeoutMs ?? DEFAULT_PROJECT_SCAN_TIMEOUT_MS;
82
- const v2Snapshots = new Map();
90
+ const snapshotEpoch = createProjectSkillSnapshotEpoch();
91
+ let connectionEpochIdentity = Object.freeze({});
92
+ const assertCurrentEpoch = (token) => {
93
+ if (token !== undefined && token !== connectionEpochIdentity)
94
+ throw new ProjectSkillStaleEpochError();
95
+ };
83
96
  const scanCurrent = async (deferredProjectId) => withinScanDeadline(scan(await deps.registry.list(), undefined, [
84
97
  ...scanned
85
98
  .map((project) => project.inventory.projectId)
@@ -97,17 +110,25 @@ export function createProjectSkillsController(deps) {
97
110
  }));
98
111
  };
99
112
  const enqueue = (operation) => operationTail.enqueue(operation);
100
- const applyV2Snapshot = async (handle, snapshot) => {
113
+ const applyV2Snapshot = async (handle, snapshot, epochToken) => {
101
114
  if (deps.ensureSnapshot === undefined)
102
115
  throw new ProjectProjectionError("skill_projection_failed");
103
- await deps.ensureSnapshot(handle, snapshot);
104
- const previous = v2Snapshots.get(handle);
105
- if (previous === undefined || snapshot.generation >= previous.generation) {
106
- v2Snapshots.set(handle, Object.freeze({
107
- bindings: Object.freeze(snapshot.bindings.map((binding) => Object.freeze({ ...binding }))),
108
- generation: snapshot.generation,
109
- }));
116
+ assertCurrentEpoch(epochToken);
117
+ const acceptedEpochIdentity = connectionEpochIdentity;
118
+ const rememberedEntries = snapshotEpoch.entries();
119
+ const firstAcceptedInEpoch = !rememberedEntries.some(([candidate]) => candidate === handle);
120
+ if (firstAcceptedInEpoch && rememberedEntries.length >= MAX_REMEMBERED_PROJECT_SKILL_AGENTS) {
121
+ throw new ProjectProjectionError("skill_projection_failed");
122
+ }
123
+ if (firstAcceptedInEpoch) {
124
+ await deps.ensureSnapshot(handle, snapshot, Object.freeze({ refreshCopyProjection: true }));
125
+ }
126
+ else {
127
+ await deps.ensureSnapshot(handle, snapshot);
110
128
  }
129
+ assertCurrentEpoch(epochToken);
130
+ if (acceptedEpochIdentity === connectionEpochIdentity)
131
+ snapshotEpoch.remember(handle, snapshot);
111
132
  return Object.freeze(projectSkillResolutionRecords(snapshot.bindings, scanned)
112
133
  .map((record) => Object.freeze({
113
134
  projectId: record.projectId,
@@ -115,15 +136,24 @@ export function createProjectSkillsController(deps) {
115
136
  status: record.mode === "resolved" ? "linked" : "unavailable",
116
137
  })));
117
138
  };
118
- const reapplyV2Snapshots = async () => {
119
- if (deps.ensureSnapshot === undefined) {
120
- if (v2Snapshots.size > 0)
121
- throw new ProjectProjectionError("skill_projection_failed");
139
+ const convergeV2Snapshots = async (refreshCopyProjection, epochToken) => {
140
+ assertCurrentEpoch(epochToken);
141
+ const acceptedEpochIdentity = connectionEpochIdentity;
142
+ const entries = snapshotEpoch.entries();
143
+ if (deps.ensureSnapshot === undefined || entries.length === 0)
144
+ return;
145
+ const results = await Promise.allSettled(entries.map(([handle, snapshot]) => refreshCopyProjection
146
+ ? deps.ensureSnapshot(handle, snapshot, Object.freeze({ refreshCopyProjection: true }))
147
+ : deps.ensureSnapshot(handle, snapshot)));
148
+ if (acceptedEpochIdentity !== connectionEpochIdentity) {
149
+ if (epochToken !== undefined)
150
+ throw new ProjectSkillStaleEpochError();
122
151
  return;
123
152
  }
124
- for (const [handle, snapshot] of [...v2Snapshots.entries()].sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0)) {
125
- await deps.ensureSnapshot(handle, snapshot);
153
+ try {
154
+ deps.onConvergence?.(toPathPrivateConvergenceResults(entries, results));
126
155
  }
156
+ catch { /* convergence diagnostics must not change a successful inventory mutation */ }
127
157
  };
128
158
  const initialize = () => {
129
159
  if (initializationState === "ready" && initialization !== null)
@@ -141,24 +171,40 @@ export function createProjectSkillsController(deps) {
141
171
  return attempt;
142
172
  };
143
173
  return {
174
+ beginConnectionEpoch: () => {
175
+ connectionEpochIdentity = Object.freeze({});
176
+ snapshotEpoch.begin();
177
+ return connectionEpochIdentity;
178
+ },
144
179
  initialize,
145
180
  publishCurrent: () => enqueue(publishCurrent),
146
181
  scannedProjects: () => scanned,
147
- async handle(input) {
182
+ async handle(input, epochToken) {
148
183
  const parsed = ProjectCommandSchema.safeParse(input);
149
184
  if (!parsed.success)
150
185
  return { ok: false, error: "invalid_project_command" };
186
+ try {
187
+ assertCurrentEpoch(epochToken);
188
+ }
189
+ catch (error) {
190
+ return { ok: false, error: error.code };
191
+ }
151
192
  if (parsed.data.type === "agent:skills:sync") {
152
193
  try {
153
194
  await initialize();
195
+ assertCurrentEpoch(epochToken);
154
196
  }
155
197
  catch {
198
+ if (epochToken !== undefined && epochToken !== connectionEpochIdentity) {
199
+ return { ok: false, error: "skill_projection_stale" };
200
+ }
156
201
  return { ok: false, error: "project_skills_unavailable" };
157
202
  }
158
203
  }
159
204
  return enqueue(async () => {
160
205
  const command = parsed.data;
161
206
  try {
207
+ assertCurrentEpoch(epochToken);
162
208
  if (command.type === "agent:skills:sync") {
163
209
  if (command.generation !== undefined) {
164
210
  const snapshot = Object.freeze({
@@ -167,13 +213,14 @@ export function createProjectSkillsController(deps) {
167
213
  });
168
214
  return {
169
215
  ok: true,
170
- data: { bindings: await applyV2Snapshot(command.handle, snapshot) },
216
+ data: { bindings: await applyV2Snapshot(command.handle, snapshot, epochToken) },
171
217
  };
172
218
  }
173
219
  if (deps.reconcile === undefined)
174
220
  return { ok: false, error: "skill_projection_failed" };
175
221
  const bindings = await deps.reconcile(command.handle, command.bindings);
176
- v2Snapshots.delete(command.handle);
222
+ assertCurrentEpoch(epochToken);
223
+ snapshotEpoch.forget(command.handle);
177
224
  return {
178
225
  ok: true,
179
226
  data: { bindings },
@@ -182,8 +229,11 @@ export function createProjectSkillsController(deps) {
182
229
  if (command.type === "project:add") {
183
230
  const existed = (await deps.registry.list())
184
231
  .some((project) => project.projectId === command.projectId);
232
+ assertCurrentEpoch(epochToken);
185
233
  await deps.registry.add(command.projectId, command.root);
234
+ assertCurrentEpoch(epochToken);
186
235
  const next = await scanCurrent();
236
+ assertCurrentEpoch(epochToken);
187
237
  const added = next.find((project) => project.inventory.projectId === command.projectId);
188
238
  if (!existed && added?.inventory.errorCode === "machine_project_skill_limit_exceeded") {
189
239
  await deps.registry.remove(command.projectId);
@@ -193,9 +243,11 @@ export function createProjectSkillsController(deps) {
193
243
  }
194
244
  else if (command.type === "project:remove") {
195
245
  await deps.registry.remove(command.projectId);
246
+ assertCurrentEpoch(epochToken);
196
247
  }
197
248
  else {
198
249
  const projects = await deps.registry.list();
250
+ assertCurrentEpoch(epochToken);
199
251
  if (!projects.some((project) => project.projectId === command.projectId)) {
200
252
  return { ok: false, error: "project_not_found" };
201
253
  }
@@ -203,8 +255,10 @@ export function createProjectSkillsController(deps) {
203
255
  if (command.type !== "project:add") {
204
256
  await refresh(command.type === "project:rescan" ? command.projectId : undefined);
205
257
  }
206
- await reapplyV2Snapshots();
258
+ assertCurrentEpoch(epochToken);
207
259
  await publishCurrent();
260
+ assertCurrentEpoch(epochToken);
261
+ await convergeV2Snapshots(command.type === "project:rescan", epochToken);
208
262
  return { ok: true, data: { projectId: command.projectId } };
209
263
  }
210
264
  catch (error) {
@@ -214,11 +268,13 @@ export function createProjectSkillsController(deps) {
214
268
  ? error.code
215
269
  : error.code === "skill_name_conflict"
216
270
  ? "skill_name_conflict"
217
- : error.code === "skill_projection_failed"
218
- ? "skill_projection_failed"
219
- : error.code === "skill_projection_snapshot_corrupt"
220
- ? "skill_projection_snapshot_corrupt"
221
- : "project_operation_failed",
271
+ : error.code === "skill_projection_stale"
272
+ ? "skill_projection_stale"
273
+ : error.code === "skill_projection_failed"
274
+ ? "skill_projection_failed"
275
+ : error.code === "skill_projection_snapshot_corrupt"
276
+ ? "skill_projection_snapshot_corrupt"
277
+ : "project_operation_failed",
222
278
  };
223
279
  }
224
280
  });
@@ -10,11 +10,11 @@ export function initializedProjectSkillsReconciler(ensureInitialized, reconciler
10
10
  async reconcile(handle, bindings) {
11
11
  return (await ready()).reconcile(handle, bindings);
12
12
  },
13
- async ensureSnapshot(handle, snapshot) {
14
- return (await ready()).ensureSnapshot(handle, snapshot);
13
+ async ensureSnapshot(handle, snapshot, options) {
14
+ return (await ready()).ensureSnapshot(handle, snapshot, options);
15
15
  },
16
- async prepareAndLaunch(agentsRoot, handle, projection, launch, onWarning) {
17
- return (await ready()).prepareAndLaunch(agentsRoot, handle, projection, launch, onWarning);
16
+ async prepareAndLaunch(agentsRoot, executionId, handle, projection, launch, onWarning) {
17
+ return (await ready()).prepareAndLaunch(agentsRoot, executionId, handle, projection, launch, onWarning);
18
18
  },
19
19
  };
20
20
  }
@@ -6,6 +6,7 @@ const MANAGED_BY = "nowcrew-project-skills-v2";
6
6
  const MAX_SOURCE_PATH_BYTES = 16 * 1024;
7
7
  const MAX_GENERATION = 2_147_483_647;
8
8
  const DIGEST = /^sha256:[a-f0-9]{64}$/u;
9
+ const CANONICAL_ROOT_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/u;
9
10
  export class ProjectSkillProjectionStateError extends Error {
10
11
  code;
11
12
  constructor(code) {
@@ -106,7 +107,8 @@ export function createAppliedProjectSkillManifest(input) {
106
107
  const bindings = canonicalBindings(input.bindings);
107
108
  const resolutions = normalizeProjectSkillResolutionRecords(input.resolutions, input.platform);
108
109
  if (bindings.length !== resolutions.length
109
- || bindings.some((binding, index) => !sameBinding(binding, resolutions[index]))) {
110
+ || bindings.some((binding, index) => !sameBinding(binding, resolutions[index]))
111
+ || !CANONICAL_ROOT_ID.test(input.rootId)) {
110
112
  return corrupt();
111
113
  }
112
114
  return Object.freeze({
@@ -115,19 +117,26 @@ export function createAppliedProjectSkillManifest(input) {
115
117
  bindings,
116
118
  bindingDigest: digest(bindings),
117
119
  resolutionDigest: digest(resolutions),
120
+ rootId: input.rootId,
118
121
  });
119
122
  }
120
123
  const ProjectSkillRefSchema = z.object({
121
124
  projectId: z.string().min(1).max(MAX_PROJECT_ID_LENGTH),
122
125
  skillName: z.string().min(1).max(MAX_PROJECT_SKILL_NAME_LENGTH),
123
126
  }).strict();
124
- const AppliedManifestSchema = z.object({
127
+ const LegacyAppliedManifestSchema = z.object({
125
128
  managedBy: z.literal(MANAGED_BY),
126
129
  generation: z.number().int().min(0).max(MAX_GENERATION),
127
130
  bindings: z.array(ProjectSkillRefSchema).max(MAX_AGENT_PROJECT_SKILL_BINDINGS),
128
131
  bindingDigest: z.string().regex(DIGEST),
129
132
  resolutionDigest: z.string().regex(DIGEST),
130
133
  }).strict();
134
+ const AppliedManifestSchema = z.union([
135
+ LegacyAppliedManifestSchema.extend({
136
+ rootId: z.string().regex(CANONICAL_ROOT_ID),
137
+ }).strict(),
138
+ LegacyAppliedManifestSchema,
139
+ ]);
131
140
  /** Strict local-store boundary parser; not re-exported from the public projection-state facade. */
132
141
  export const parseAppliedProjectSkillManifest = (candidate) => {
133
142
  if (candidate && typeof candidate === "object" && "managedBy" in candidate
@@ -149,5 +158,13 @@ export const parseAppliedProjectSkillManifest = (candidate) => {
149
158
  bindings,
150
159
  bindingDigest: parsed.data.bindingDigest,
151
160
  resolutionDigest: parsed.data.resolutionDigest,
161
+ rootId: "rootId" in parsed.data ? parsed.data.rootId : null,
152
162
  });
153
163
  };
164
+ /** Publication boundary parser; it must run before creating or settling any filesystem state. */
165
+ export const parsePublishableAppliedProjectSkillManifest = (candidate) => {
166
+ const parsed = parseAppliedProjectSkillManifest(candidate);
167
+ if (parsed.rootId === null)
168
+ return corrupt();
169
+ return Object.freeze({ ...parsed, rootId: parsed.rootId });
170
+ };
@@ -3,7 +3,7 @@ import { chmod, link, lstat, mkdir, open, opendir, rename, rmdir, unlink } from
3
3
  import { basename, dirname, join } from "node:path";
4
4
  import { z } from "zod";
5
5
  import { durableDirectorySync } from "../atomic-private-write.js";
6
- import { parseAppliedProjectSkillManifest, ProjectSkillProjectionStateError, } from "./projection-state-domain.js";
6
+ import { parseAppliedProjectSkillManifest, parsePublishableAppliedProjectSkillManifest, ProjectSkillProjectionStateError, } from "./projection-state-domain.js";
7
7
  const MANIFEST_FILE_NAME = "project-skills-v2.json";
8
8
  const MAX_MANIFEST_BYTES = 256 * 1024;
9
9
  const MAX_CONSISTENT_READ_ATTEMPTS = 6;
@@ -665,7 +665,7 @@ export async function readAppliedProjectSkillManifest(agentRoot, options = {}) {
665
665
  : parseManifestRaw(snapshot.raw);
666
666
  }
667
667
  export async function writeAppliedProjectSkillManifest(agentRoot, manifest, options = {}) {
668
- const parsed = parseAppliedProjectSkillManifest(manifest);
668
+ const parsed = parsePublishableAppliedProjectSkillManifest(manifest);
669
669
  const syncDirectory = projectionDirectorySync(options);
670
670
  await realOwnedDirectory(agentRoot, false);
671
671
  const stateDirectory = join(agentRoot, ".crew");
@@ -827,6 +827,7 @@ export const projectSkillManifestStoreInternals = Object.freeze({
827
827
  isAbsenceMarkerRaw,
828
828
  loadOperation,
829
829
  parseAppliedProjectSkillManifest,
830
+ parsePublishableAppliedProjectSkillManifest,
830
831
  parseManifestRaw,
831
832
  pathUnmanaged,
832
833
  projectionDirectorySync,
@@ -190,11 +190,15 @@ const prepareManifestTransition = async (agentRoot, next, options = {}) => {
190
190
  return Object.freeze({ agentRoot, operationId: operation.intent.nonce, previous, next });
191
191
  };
192
192
  export async function prepareAppliedProjectSkillManifestUpdate(agentRoot, manifest, options = {}) {
193
- return prepareManifestTransition(agentRoot, store.parseAppliedProjectSkillManifest(manifest), options);
193
+ return prepareManifestTransition(agentRoot, store.parsePublishableAppliedProjectSkillManifest(manifest), options);
194
194
  }
195
195
  export async function prepareAppliedProjectSkillManifestRemoval(agentRoot, options = {}) {
196
196
  return prepareManifestTransition(agentRoot, null, options);
197
197
  }
198
+ /** Settles a durable standalone applied-manifest operation before current-state selection. */
199
+ export async function recoverAppliedProjectSkillManifestUpdates(agentRoot, options = {}) {
200
+ await stateContext(agentRoot, options, true);
201
+ }
198
202
  export async function publishPreparedProjectSkillManifestUpdate(update, options = {}) {
199
203
  if (await completedWithoutOperation(update, update.next, options))
200
204
  return;
@@ -1,3 +1,3 @@
1
1
  export { computeProjectSkillBindingDigest, computeProjectSkillResolutionDigest, createAppliedProjectSkillManifest, normalizeProjectSkillResolutionRecords, ProjectSkillProjectionStateError, } from "./projection-state-domain.js";
2
2
  export { appliedProjectSkillManifestPath, readAppliedProjectSkillManifest, writeAppliedProjectSkillManifest, } from "./projection-state-store.js";
3
- export { commitPreparedProjectSkillManifestUpdate, parseProjectSkillManifestUpdateRecord, prepareAppliedProjectSkillManifestRemoval, prepareAppliedProjectSkillManifestUpdate, preparedProjectSkillManifestUpdateFromRecord, projectSkillManifestUpdateRecord, publishPreparedProjectSkillManifestUpdate, rollbackPreparedProjectSkillManifestUpdate, } from "./projection-state-transaction.js";
3
+ export { commitPreparedProjectSkillManifestUpdate, parseProjectSkillManifestUpdateRecord, prepareAppliedProjectSkillManifestRemoval, prepareAppliedProjectSkillManifestUpdate, preparedProjectSkillManifestUpdateFromRecord, projectSkillManifestUpdateRecord, publishPreparedProjectSkillManifestUpdate, recoverAppliedProjectSkillManifestUpdates, rollbackPreparedProjectSkillManifestUpdate, } from "./projection-state-transaction.js";