@ricsam/r5d-worker 0.0.173 → 0.0.174

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.
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.174",
4
4
  "type": "commonjs"
5
5
  }
@@ -20605,7 +20605,7 @@ function resolveEntrypointPath(entrypoint) {
20605
20605
  }
20606
20606
  }
20607
20607
  function getR5dctlVersion() {
20608
- if (true) return "0.0.173";
20608
+ if (true) return "0.0.174";
20609
20609
  const entrypoint = process.argv[1] ? resolveEntrypointPath(process.argv[1]) : null;
20610
20610
  let current = entrypoint ? import_node_path2.default.dirname(entrypoint) : process.cwd();
20611
20611
  for (let index = 0; index < 12; index += 1) {
package/dist/mjs/main.mjs CHANGED
@@ -7,7 +7,7 @@ import { startManagerRpc } from "./runtime/releases/rpc-main.mjs";
7
7
  import { ManagerRpcConfig } from "./runtime/releases/rpc-protocol.mjs";
8
8
  const args = process.argv.slice(2);
9
9
  if (args.includes("--version")) {
10
- console.log(`r5d-worker ${true ? "0.0.173" : "development"}`);
10
+ console.log(`r5d-worker ${true ? "0.0.174" : "development"}`);
11
11
  } else if (!args.length || args.includes("--help")) {
12
12
  console.log(
13
13
  "Usage: r5d-worker start --label <label> [--root <dir>] [--base-url <url>] [--token <worker-token>]\n r5d-worker executor /absolute/private/config.json\n r5d-worker manager /absolute/private/config.json\n\nRun independently supervised durable runtime services. Provision configuration with r5dinfra."
@@ -15,7 +15,7 @@ if (args.includes("--version")) {
15
15
  } else if (args[0] === "start") {
16
16
  const runtime = await startPersonalWorker(
17
17
  parsePersonalWorkerOptions(args.slice(1)),
18
- true ? "0.0.173" : "development"
18
+ true ? "0.0.174" : "development"
19
19
  );
20
20
  console.log(`Worker connected: ${runtime.resourceId}`);
21
21
  let closing = false;
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.174",
4
4
  "type": "module"
5
5
  }
@@ -264,8 +264,13 @@ class WorkspaceAuthority {
264
264
  const result = [];
265
265
  for (const [operationId, metadata] of Object.entries(b.state.runs)) {
266
266
  if (metadata.state === "rejected_capacity" || (metadata.sessionId ?? b.config.sessionId) !== identity.sessionId) continue;
267
- const poll = await this.poll(identity, { userId: identity.userId, sessionId: identity.sessionId, operationId, fence: route.workerFence }, 0, 0, 1);
268
- result.push({ ...metadata, ...poll.receipt, workerLabel: route.workerFence.resourceId });
267
+ try {
268
+ const poll = await this.poll(identity, { userId: identity.userId, sessionId: identity.sessionId, operationId, fence: route.workerFence }, 0, 0, 1);
269
+ result.push({ ...metadata, ...poll.receipt, workerLabel: route.workerFence.resourceId });
270
+ } catch (error) {
271
+ if (!(error instanceof ExecutorError && error.code === "run_not_found")) throw error;
272
+ result.push({ ...metadata, operationId, userId: identity.userId, sessionId: identity.sessionId, state: "unknown", pid: null, pty: false, lane: "general", stdoutBytes: 0, stderrBytes: 0, stdinClosed: true, exitCode: null, signal: null, reason: "run_not_found", workerLabel: route.workerFence.resourceId });
273
+ }
269
274
  }
270
275
  return result;
271
276
  }
@@ -406,8 +411,20 @@ class WorkspaceAuthority {
406
411
  return { head, treeHash, files: changes, ...patch === void 0 ? {} : { patch } };
407
412
  });
408
413
  }
414
+ /** Bench state is one in-memory object with several writers on different
415
+ * queues (checkout work, run receipts). Writes of its file are chained so
416
+ * they never overlap and the write that lands last always serializes the
417
+ * newest state; the chain covers only the write itself, never the work
418
+ * around it, so a run receipt does not wait for a publication. */
409
419
  save(b) {
410
- return durableJson(path.join(b.directory, "state.json"), b.state);
420
+ const write = (b.saving ?? Promise.resolve()).catch(() => {
421
+ }).then(() => durableJson(path.join(b.directory, "state.json"), b.state));
422
+ b.saving = write;
423
+ void write.finally(() => {
424
+ if (b.saving === write) b.saving = void 0;
425
+ }).catch(() => {
426
+ });
427
+ return write;
411
428
  }
412
429
  async owned(admitted = false) {
413
430
  if (this.closing && !admitted) throw new WorkspaceError("authority_closed", "Workspace authority is closing");
@@ -1846,7 +1863,12 @@ class WorkspaceAuthority {
1846
1863
  }
1847
1864
  const nonmutating = Object.keys(requestedEnv).length === 0 && payload.credentials.length === 0 && b.config.nonmutatingArgv.some((argv) => canonicalJson(argv) === canonicalJson(payload.argv));
1848
1865
  b.state.runs[operation.operationId] = { artifactEnvironment: true, sessionId: identity.sessionId, payloadHash: hash, mutating: !nonmutating, state: "unknown", argv: payload.argv, cwd: payload.cwd, startedAt: (/* @__PURE__ */ new Date()).toISOString(), updatedAt: (/* @__PURE__ */ new Date()).toISOString() };
1849
- await this.save(b);
1866
+ try {
1867
+ await this.save(b);
1868
+ } catch (error) {
1869
+ delete b.state.runs[operation.operationId];
1870
+ throw error;
1871
+ }
1850
1872
  for (let attempt = 0; ; attempt++) {
1851
1873
  try {
1852
1874
  const receipt = await route.client.start(operation);
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import { constants } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { spawn } from "node:child_process";
5
- import { createHash } from "node:crypto";
5
+ import { createHash, randomBytes } from "node:crypto";
6
6
  import { safeTreePath, STORAGE_LIMITS } from "./storage-wire.mjs";
7
7
  import { WorkspaceError } from "./contracts.mjs";
8
8
  const sha256 = (bytes) => createHash("sha256").update(bytes).digest("hex");
@@ -84,14 +84,17 @@ async function ensureAuthorityGitRepositoryLayout(repo) {
84
84
  }
85
85
  }
86
86
  async function durableJson(file, value) {
87
- const temporary = `${file}.next`;
87
+ const temporary = `${file}.${randomBytes(6).toString("hex")}.next`;
88
88
  const handle = await fs.open(temporary, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 384);
89
89
  try {
90
90
  await handle.writeFile(JSON.stringify(value));
91
91
  await handle.sync();
92
- } finally {
92
+ } catch (error) {
93
93
  await handle.close();
94
+ await fs.rm(temporary, { force: true });
95
+ throw error;
94
96
  }
97
+ await handle.close();
95
98
  await fs.rename(temporary, file);
96
99
  const dir = await fs.open(path.dirname(file), constants.O_RDONLY);
97
100
  try {
@@ -57,7 +57,7 @@ export declare class WorkspaceAuthority {
57
57
  content: string;
58
58
  bytes: number;
59
59
  }>;
60
- processes(identity: WorkspaceIdentity): Promise<{
60
+ processes(identity: WorkspaceIdentity): Promise<({
61
61
  workerLabel: string;
62
62
  operationId: string;
63
63
  userId: string;
@@ -80,7 +80,30 @@ export declare class WorkspaceAuthority {
80
80
  startedAt?: string;
81
81
  updatedAt?: string;
82
82
  completedAt?: string;
83
- }[]>;
83
+ } | {
84
+ operationId: string;
85
+ userId: string;
86
+ sessionId: string;
87
+ state: string;
88
+ pid: null;
89
+ pty: boolean;
90
+ lane: string;
91
+ stdoutBytes: number;
92
+ stderrBytes: number;
93
+ stdinClosed: boolean;
94
+ exitCode: null;
95
+ signal: null;
96
+ reason: string;
97
+ workerLabel: string;
98
+ artifactEnvironment?: boolean;
99
+ mutating: boolean;
100
+ payloadHash: string;
101
+ argv?: string[];
102
+ cwd?: string;
103
+ startedAt?: string;
104
+ updatedAt?: string;
105
+ completedAt?: string;
106
+ })[]>;
84
107
  fileHistoryDates(identity: WorkspaceIdentity, file: string): Promise<{
85
108
  createdAt: string | null;
86
109
  updatedAt: string;
@@ -194,6 +217,11 @@ export declare class WorkspaceAuthority {
194
217
  original?: undefined;
195
218
  modified?: undefined;
196
219
  }>;
220
+ /** Bench state is one in-memory object with several writers on different
221
+ * queues (checkout work, run receipts). Writes of its file are chained so
222
+ * they never overlap and the write that lands last always serializes the
223
+ * newest state; the chain covers only the write itself, never the work
224
+ * around it, so a run receipt does not wait for a publication. */
197
225
  private save;
198
226
  private owned;
199
227
  private serial;
@@ -223,11 +251,7 @@ export declare class WorkspaceAuthority {
223
251
  code: string;
224
252
  message: string;
225
253
  operationId?: string;
226
- commit? /** Storage for the account's outer repository, bound to no session. Its
227
- * presence selects the outer-repository model: the workspace root is one
228
- * repository whose files synchronize as a unit and whose project checkouts
229
- * are mirrored, not published, per branch. Without it linked checkouts keep
230
- * publishing per project, which the server workspace service relies on. */: string;
254
+ commit?: string;
231
255
  };
232
256
  mirroredHead?: string | null;
233
257
  runs: Record<string, {
@@ -15,6 +15,10 @@ export declare function readHostRegular(file: string, limit?: number): Promise<B
15
15
  * Restore only that directory skeleton after confirming the authority-owned
16
16
  * repository markers; refs, objects, indexes and configuration are untouched. */
17
17
  export declare function ensureAuthorityGitRepositoryLayout(repo: string): Promise<void>;
18
+ /** Writes `value` atomically: a private temporary file, fsync, rename, directory
19
+ * fsync. Concurrent writers of the same file each get their own temporary name,
20
+ * so neither fails on the other's; the rename that lands last wins, which is why
21
+ * callers that share in-memory state also serialize their writes. */
18
22
  export declare function durableJson(file: string, value: unknown): Promise<void>;
19
23
  /** All Git operates in authority-created repos; NEVER workbench .git/config/index.
20
24
  * hash-object --no-filters and update-index --index-info replace git add/checkout.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.173",
3
+ "version": "0.0.174",
4
4
  "type": "module",
5
5
  "main": "./dist/mjs/main.mjs",
6
6
  "module": "./dist/mjs/main.mjs",
@@ -21,7 +21,7 @@
21
21
  "r5d-worker": "dist/mjs/main.mjs"
22
22
  },
23
23
  "dependencies": {
24
- "@ricsam/r5d-api": "^0.0.173",
24
+ "@ricsam/r5d-api": "^0.0.174",
25
25
  "node-pty": "1.1.0",
26
26
  "zod": "^4.1.13",
27
27
  "picomatch": "^4.0.3"