@ricsam/r5d-worker 0.0.109 → 0.0.111

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.
package/dist/cjs/main.cjs CHANGED
@@ -62,6 +62,7 @@ var import_plan_lint = require("./plan-lint.cjs");
62
62
  var import_node_fs = __toESM(require("node:fs"), 1);
63
63
  var import_recovery_store = require("./recovery-store.cjs");
64
64
  var import_recovery_output_window = require("./recovery-output-window.cjs");
65
+ var import_job_resources = require("./job-resources.cjs");
65
66
  var import_recovery_protocol = require("./recovery-protocol.cjs");
66
67
  var import_node_path = __toESM(require("node:path"), 1);
67
68
  var import_node_crypto = require("node:crypto");
@@ -154,6 +155,7 @@ const PTY_INPUT_BUSY_GRACE_MS = 3e3;
154
155
  const PTY_FOREGROUND_POLL_MS = 1e3;
155
156
  const PTY_FOREGROUND_IDLE_ENABLED = process.env.R5D_PTY_FOREGROUND_IDLE !== "0";
156
157
  const PTY_TMP_PATH_PREFIX = "r5d-worker-tmp://";
158
+ let workerJobResources;
157
159
  const activeProcesses = /* @__PURE__ */ new Map();
158
160
  const credentialBearingProcessGroups = /* @__PURE__ */ new Map();
159
161
  const credentialBearingProcessGroupTargets = /* @__PURE__ */ new Map();
@@ -202,8 +204,23 @@ function renewWorkerExecutionLease(leaseMs) {
202
204
  () => {
203
205
  if (Date.now() < workerExecutionLeaseDeadline) return;
204
206
  workerLeaseExpired = true;
207
+ try {
208
+ workerJobResources?.cancelMatching((id) => id.startsWith("pty:"));
209
+ } catch (error) {
210
+ process.stderr.write(`[r5d-worker] expired PTY resource termination failed: ${error}
211
+ `);
212
+ }
213
+ closeAllPtys();
205
214
  for (const operation of recoveryStore?.operations() ?? []) {
206
- if (operation.sessionId && operation.state !== "completed") recoveryStore?.cancelSession(operation.sessionId);
215
+ if (operation.sessionId && operation.state !== "completed") {
216
+ recoveryStore?.cancelSession(operation.sessionId);
217
+ try {
218
+ workerJobResources?.cancelSession(operation.sessionId);
219
+ } catch (error) {
220
+ process.stderr.write(`[r5d-worker] expired command resource termination failed: ${error}
221
+ `);
222
+ }
223
+ }
207
224
  }
208
225
  for (const [runId, active] of activeProcesses) {
209
226
  if (!active.sessionId) continue;
@@ -1597,7 +1614,7 @@ function credentialReapContractStatus(probe) {
1597
1614
  if (process.platform !== "linux") return "direct";
1598
1615
  try {
1599
1616
  const cgroupText = import_node_fs.default.readFileSync("/proc/self/cgroup", "utf8");
1600
- const unit = cgroupText.match(/(?:^|\/)r5d-worker\.service(?:$|\n)/m)?.[0]?.replace(/^\//, "").trim();
1617
+ const unit = /(?:^|\/)r5d-worker\.service(?:\/|$|\n)/m.test(cgroupText) ? "r5d-worker.service" : void 0;
1601
1618
  if (unit !== "r5d-worker.service") {
1602
1619
  return SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(process.env.INVOCATION_ID ?? "") ? "systemd_unverified" : "direct";
1603
1620
  }
@@ -1631,7 +1648,7 @@ function credentialReapContractStatus(probe) {
1631
1648
  }
1632
1649
  }
1633
1650
  if (resolved.platform !== "linux") return "direct";
1634
- if (!/(?:^|\/)r5d-worker\.service(?:$|\n)/m.test(resolved.cgroupText)) {
1651
+ if (!/(?:^|\/)r5d-worker\.service(?:\/|$|\n)/m.test(resolved.cgroupText)) {
1635
1652
  return SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(resolved.invocationId ?? "") ? "systemd_unverified" : "direct";
1636
1653
  }
1637
1654
  if (!SYSTEMD_INVOCATION_IDENTIFIER_PATTERN.test(resolved.invocationId ?? "")) return "systemd_unverified";
@@ -2013,12 +2030,6 @@ function resolveCommandCwd(branchPath, cwd) {
2013
2030
  }
2014
2031
  return resolved;
2015
2032
  }
2016
- async function collectStream(stream) {
2017
- if (!stream) {
2018
- return "";
2019
- }
2020
- return await new Response(stream).text();
2021
- }
2022
2033
  async function streamCommandOutput(stream, onData) {
2023
2034
  if (!stream) {
2024
2035
  return;
@@ -2739,8 +2750,37 @@ async function prepareShellEnvForTarget(input) {
2739
2750
  ...targetProcessEnv(input.rootDir, input.target)
2740
2751
  };
2741
2752
  }
2753
+ async function releaseWorkerJobResources(resources) {
2754
+ if (!resources) return;
2755
+ try {
2756
+ await resources.release();
2757
+ } catch (error) {
2758
+ process.stderr.write(`[r5d-worker] job resource cleanup failed: ${error instanceof Error ? error.message : String(error)}
2759
+ `);
2760
+ }
2761
+ }
2762
+ async function acquireWorkerCommandResources(ws, message, assertAdmission) {
2763
+ const id = message.type === "pty_open" ? `pty:${message.ptyId}` : message.runId;
2764
+ const operationId = message.type === "pty_open" ? id : `exec:${id}`;
2765
+ sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: true });
2766
+ try {
2767
+ return await workerJobResources.acquire({
2768
+ id,
2769
+ lane: message.type === "exec" && message.workspaceEffect === "none" ? "utility" : "general",
2770
+ ..."sessionId" in message && message.sessionId ? { sessionId: message.sessionId } : {},
2771
+ assertAdmission: () => {
2772
+ assertAdmission();
2773
+ if (cancelledProcessRuns.has(id)) throw new import_job_resources.WorkerJobCancelledError();
2774
+ }
2775
+ });
2776
+ } finally {
2777
+ sendWorkerMessage(ws, { type: "heartbeat_lease", operationId, active: false });
2778
+ }
2779
+ }
2742
2780
  async function executeCommand(input) {
2743
2781
  const startedAt = Date.now();
2782
+ const outputAbort = new AbortController();
2783
+ let outputReaders = [];
2744
2784
  let timeout;
2745
2785
  let spawnedProcess;
2746
2786
  let preparationSettled = false;
@@ -2761,7 +2801,7 @@ async function executeCommand(input) {
2761
2801
  });
2762
2802
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
2763
2803
  input.assertAdmission();
2764
- const subprocess = Bun.spawn(input.message.argv, {
2804
+ const subprocess = Bun.spawn(input.resources.wrap(input.message.argv), {
2765
2805
  cwd,
2766
2806
  stdout: "pipe",
2767
2807
  stderr: "pipe",
@@ -2789,6 +2829,7 @@ async function executeCommand(input) {
2789
2829
  settlePreparation();
2790
2830
  if (input.message.timeoutMs) {
2791
2831
  timeout = setTimeout(() => {
2832
+ input.resources.kill();
2792
2833
  void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
2793
2834
  process.stderr.write(
2794
2835
  `[r5d-worker] failed to terminate timed-out command ${input.message.runId}: ${error instanceof Error ? error.message : String(error)}
@@ -2797,21 +2838,26 @@ async function executeCommand(input) {
2797
2838
  });
2798
2839
  }, input.message.timeoutMs);
2799
2840
  }
2800
- const [stdout, stderr, exitCode] = await Promise.all([
2801
- collectStream(subprocess.stdout),
2802
- collectStream(subprocess.stderr),
2803
- subprocess.exited
2804
- ]);
2841
+ outputReaders = [
2842
+ (0, import_job_resources.collectWorkerInlineOutput)(subprocess.stdout, void 0, outputAbort.signal),
2843
+ (0, import_job_resources.collectWorkerInlineOutput)(subprocess.stderr, void 0, outputAbort.signal)
2844
+ ];
2845
+ const [stdout, stderr, exitCode] = await Promise.all([outputReaders[0], outputReaders[1], subprocess.exited]);
2805
2846
  return {
2806
2847
  type: "exec_result",
2807
2848
  requestId: input.message.requestId,
2808
2849
  stdout,
2809
2850
  stderr,
2810
2851
  exitCode,
2811
- durationMs: Date.now() - startedAt
2852
+ durationMs: Date.now() - startedAt,
2853
+ ...input.resources.memoryLimitExceeded() ? {
2854
+ error: `Command exceeded its worker memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.`
2855
+ } : {}
2812
2856
  };
2813
2857
  } catch (error) {
2858
+ outputAbort.abort(error);
2814
2859
  if (error instanceof StaleWorkerAdmissionError) throw error;
2860
+ input.resources.kill();
2815
2861
  return {
2816
2862
  type: "exec_result",
2817
2863
  requestId: input.message.requestId,
@@ -2829,6 +2875,8 @@ async function executeCommand(input) {
2829
2875
  if (spawnedProcess) {
2830
2876
  await reapCompletedCredentialBearingProcessGroup(input.message.runId, spawnedProcess);
2831
2877
  }
2878
+ outputAbort.abort();
2879
+ await Promise.allSettled(outputReaders);
2832
2880
  activeProcesses.delete(input.message.runId);
2833
2881
  }
2834
2882
  }
@@ -2866,7 +2914,7 @@ async function executeStreamingCommand(input) {
2866
2914
  const cwd = resolveCommandCwd(input.resolvedTarget.rootPath, input.message.cwd);
2867
2915
  const interactive = input.message.interactive === true;
2868
2916
  input.assertAdmission();
2869
- const subprocess = Bun.spawn(input.message.argv, {
2917
+ const subprocess = Bun.spawn(input.resources.wrap(input.message.argv), {
2870
2918
  cwd,
2871
2919
  // Without an explicit stdin the process reads /dev/null and interactive
2872
2920
  // prompts see immediate EOF; "pipe" keeps stdin open for exec_stdin.
@@ -2909,6 +2957,7 @@ async function executeStreamingCommand(input) {
2909
2957
  if (input.message.timeoutMs) {
2910
2958
  timeout = setTimeout(() => {
2911
2959
  timedOut = true;
2960
+ input.resources.kill();
2912
2961
  closeProcessStdin(activeProcesses.get(input.message.runId));
2913
2962
  void (0, import_process_tree.terminateProcessTree)(subprocess).catch((error) => {
2914
2963
  process.stderr.write(
@@ -2937,6 +2986,16 @@ async function executeStreamingCommand(input) {
2937
2986
  });
2938
2987
  })
2939
2988
  ]);
2989
+ if (input.resources.memoryLimitExceeded()) {
2990
+ sendWorkerMessage(input.ws, {
2991
+ type: "exec_output",
2992
+ runId: input.message.runId,
2993
+ stream: "stderr",
2994
+ data: `
2995
+ [r5d-worker] Command exceeded its memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.
2996
+ `
2997
+ });
2998
+ }
2940
2999
  const canonicalWorkspaceHead = canonicalSyncTerminalHead(input.resolvedTarget);
2941
3000
  const terminal = {
2942
3001
  type: "exec_exit",
@@ -3366,6 +3425,7 @@ async function openPty(input) {
3366
3425
  }) : {};
3367
3426
  const targetEnv = targetProcessEnv(input.rootDir, input.resolvedTarget.target);
3368
3427
  const shell = resolveHostShell(input.message.command);
3428
+ const shellArgv = input.resources.wrap([shell.file, ...shell.args]);
3369
3429
  const outputCoalescer = (0, import_pty_output_coalescer.createPtyOutputCoalescer)({
3370
3430
  send: (data) => {
3371
3431
  sendWorkerMessage(input.ws, {
@@ -3385,8 +3445,8 @@ async function openPty(input) {
3385
3445
  };
3386
3446
  try {
3387
3447
  const ptyProcess = createNodePtyBridge({
3388
- file: shell.file,
3389
- args: shell.args,
3448
+ file: shellArgv[0],
3449
+ args: shellArgv.slice(1),
3390
3450
  ptyOptions: {
3391
3451
  name: "xterm-256color",
3392
3452
  cols: Math.max(1, Math.min(Math.floor(input.message.cols || 80), 500)),
@@ -3415,11 +3475,18 @@ async function openPty(input) {
3415
3475
  outputCoalescer.push(data);
3416
3476
  },
3417
3477
  onExit: (event) => {
3478
+ if (input.resources.memoryLimitExceeded())
3479
+ outputCoalescer.push(
3480
+ `\r
3481
+ [r5d-worker] Terminal exceeded its memory limit (${input.resources.memoryBytes} bytes). Reduce parallelism or memory use and retry.\r
3482
+ `
3483
+ );
3418
3484
  outputCoalescer.flush();
3419
3485
  input.releaseWorkspaceMutation?.();
3420
3486
  activePtys.delete(input.message.ptyId);
3421
3487
  cleanupEnvFiles();
3422
3488
  input.onTerminal?.();
3489
+ void releaseWorkerJobResources(input.resources);
3423
3490
  sendWorkerMessage(input.ws, {
3424
3491
  type: "pty_exit",
3425
3492
  ptyId: input.message.ptyId,
@@ -3433,6 +3500,7 @@ async function openPty(input) {
3433
3500
  activePtys.delete(input.message.ptyId);
3434
3501
  cleanupEnvFiles();
3435
3502
  input.onTerminal?.();
3503
+ void releaseWorkerJobResources(input.resources);
3436
3504
  sendWorkerMessage(input.ws, {
3437
3505
  type: "pty_error",
3438
3506
  requestId: input.message.requestId,
@@ -3482,6 +3550,7 @@ function resizePty(message) {
3482
3550
  ptyProcess.resize(Math.max(1, Math.min(Math.floor(message.cols || 80), 500)), Math.max(1, Math.min(Math.floor(message.rows || 24), 500)));
3483
3551
  }
3484
3552
  function closePty(message) {
3553
+ workerJobResources?.cancel(`pty:${message.ptyId}`);
3485
3554
  const ptyProcess = activePtys.get(message.ptyId);
3486
3555
  if (!ptyProcess) {
3487
3556
  return;
@@ -3500,7 +3569,7 @@ function closePtysBestEffort(ptys, reportFailure) {
3500
3569
  function closeAllPtys() {
3501
3570
  closePtysBestEffort(activePtys, (ptyId, error) => {
3502
3571
  process.stderr.write(
3503
- `[r5d-worker] failed to close PTY ${ptyId} after control disconnect: ${error instanceof Error ? error.message : String(error)}
3572
+ `[r5d-worker] failed to close PTY ${ptyId} after execution lease expiry: ${error instanceof Error ? error.message : String(error)}
3504
3573
  `
3505
3574
  );
3506
3575
  });
@@ -3573,6 +3642,11 @@ async function startWorker(options, projectRuntime = {
3573
3642
  initializeCredentialStoreDirectoryOnce();
3574
3643
  const label = requireValue(options.label, "Missing required --label <label>");
3575
3644
  validateLabel(label);
3645
+ if (!workerJobResources) {
3646
+ workerJobResources = (0, import_job_resources.createWorkerJobResources)();
3647
+ process.stdout.write(`[r5d-worker] job resources: ${workerJobResources.isolation}; ${JSON.stringify(workerJobResources.budget)}
3648
+ `);
3649
+ }
3576
3650
  const rootDir = import_node_path.default.resolve(options.rootDir ?? process.env.R5D_ROOT ?? defaultRootDir());
3577
3651
  recoveryStore ??= new import_recovery_store.WorkerRecoveryStore(
3578
3652
  import_node_path.default.join(rootDir, ".worker-recovery", (0, import_node_crypto.createHash)("sha256").update(`${baseUrl}\0${label}`).digest("hex"), "protocol.sqlite")
@@ -5777,6 +5851,7 @@ async function startWorker(options, projectRuntime = {
5777
5851
  }
5778
5852
  if (message.type === "cancel_session") {
5779
5853
  recoveryStore.cancelSession(message.sessionId);
5854
+ workerJobResources?.cancelSession(message.sessionId);
5780
5855
  await Promise.all(
5781
5856
  [...activeProcesses].filter(([, active]) => active.sessionId === message.sessionId).map(async ([runId, active]) => {
5782
5857
  cancelledProcessRuns.add(runId);
@@ -5840,18 +5915,13 @@ async function startWorker(options, projectRuntime = {
5840
5915
  const refreshesDeferredConfiguration = deferredWorkspaceConfiguration !== null && activeWorkspaceIncidentId === null && message.deferWorkspaceSyncForIncidentId === void 0;
5841
5916
  const digest = (0, import_recovery_store.workerOperationDigest)(message);
5842
5917
  const cached = projectRuntime.configuration;
5843
- if (cached?.digest !== digest) advanceWorkerAdmissionGeneration();
5844
- const canReuseConfiguration = cached?.digest === digest && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
5845
- const configured = canReuseConfiguration ? cached.configured : await configureWorkerWorkspace(message, receiptGeneration);
5846
- if (canReuseConfiguration && configured.result.outcome !== "failed") {
5847
- workspaceConfigured = true;
5848
- activeWorkspaceIncidentId = cached.incidentId;
5849
- deferredWorkspaceConfiguration = cached.deferred;
5918
+ const canReuseConfiguration = cached?.digest === digest && cached.workspaceSync?.remoteUrl === message.workspaceRemoteUrl && Boolean(cached.workspaceSync.credentialHelper) && cached.workspaceSync.credentialUsername === workerCredentialUsername && cached.workspaceSync.gitIdentity?.name === message.gitIdentity.name && cached.workspaceSync.gitIdentity?.email === message.gitIdentity.email && !unsafeWorkspaceSyncBouncePending && credentialGenerationFingerprint({ ...message, workerBaseUrl: baseUrl, workerAuthHeader: bearerAuthHeader }) === configuredCredentialGenerationFingerprint;
5919
+ if (!canReuseConfiguration) {
5920
+ advanceWorkerAdmissionGeneration();
5921
+ projectRuntime.configuration = void 0;
5850
5922
  }
5851
- projectRuntime.configuration = configured.result.outcome !== "failed" ? { digest, configured, incidentId: activeWorkspaceIncidentId, deferred: deferredWorkspaceConfiguration } : void 0;
5852
- unsafeWorkspaceSyncBouncePending = false;
5853
- if (configured.result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
5854
- sendWorkerMessageFromCurrentSource(ws, {
5923
+ const configured = canReuseConfiguration ? cached.configured : await configureWorkerWorkspace(message, receiptGeneration);
5924
+ const sendConfigured = () => sendWorkerMessageFromCurrentSource(ws, {
5855
5925
  type: "workspace_configured",
5856
5926
  requestId: message.requestId,
5857
5927
  result: configured.result,
@@ -5859,6 +5929,34 @@ async function startWorker(options, projectRuntime = {
5859
5929
  aheadOfOriginBranches: configured.aheadOfOriginBranches,
5860
5930
  ...configured.deferredWorkspaceSyncForIncidentId ? { deferredWorkspaceSyncForIncidentId: configured.deferredWorkspaceSyncForIncidentId } : {}
5861
5931
  });
5932
+ if (receiptGeneration !== workspaceConfigurationReceiptGeneration || currentWorkerSocket !== ws || ws.readyState !== WebSocket.OPEN) {
5933
+ sendConfigured();
5934
+ return;
5935
+ }
5936
+ if (canReuseConfiguration && configured.result.outcome !== "failed") {
5937
+ workspaceRemoteUrl = cached.workspaceSync.remoteUrl;
5938
+ workspaceCredentialHelper = cached.workspaceSync.credentialHelper;
5939
+ workspaceCredentialUsername = cached.workspaceSync.credentialUsername;
5940
+ workspaceGitIdentity = cached.workspaceSync.gitIdentity;
5941
+ workspaceConfigured = true;
5942
+ activeWorkspaceIncidentId = cached.incidentId;
5943
+ deferredWorkspaceConfiguration = cached.deferred;
5944
+ }
5945
+ projectRuntime.configuration = configured.result.outcome !== "failed" && workspaceRemoteUrl && workspaceCredentialHelper && workspaceCredentialUsername && workspaceGitIdentity ? {
5946
+ digest,
5947
+ workspaceSync: {
5948
+ remoteUrl: workspaceRemoteUrl,
5949
+ credentialHelper: workspaceCredentialHelper,
5950
+ credentialUsername: workspaceCredentialUsername,
5951
+ gitIdentity: workspaceGitIdentity
5952
+ },
5953
+ configured,
5954
+ incidentId: activeWorkspaceIncidentId,
5955
+ deferred: deferredWorkspaceConfiguration
5956
+ } : void 0;
5957
+ unsafeWorkspaceSyncBouncePending = false;
5958
+ if (configured.result.outcome !== "failed") unsafeWorkspaceSyncRecoveryAttempts = 0;
5959
+ sendConfigured();
5862
5960
  sendLifecycleProgress(
5863
5961
  configured.result.outcome === "failed" ? {
5864
5962
  phase: "failed",
@@ -6240,8 +6338,14 @@ async function startWorker(options, projectRuntime = {
6240
6338
  return;
6241
6339
  }
6242
6340
  if (message.type === "cancel") {
6341
+ let resourceCancellationError;
6342
+ try {
6343
+ workerJobResources?.cancel(message.runId);
6344
+ } catch (error) {
6345
+ resourceCancellationError = error instanceof Error ? error.message : String(error);
6346
+ }
6243
6347
  const active = activeProcesses.get(message.runId);
6244
- let cancelled = true;
6348
+ let cancelled = resourceCancellationError === void 0;
6245
6349
  let cancelMessage;
6246
6350
  if (active) {
6247
6351
  try {
@@ -6256,6 +6360,7 @@ async function startWorker(options, projectRuntime = {
6256
6360
  cancelledProcessRuns.add(message.runId);
6257
6361
  cancelMessage = "Cancellation queued before command start";
6258
6362
  }
6363
+ if (resourceCancellationError) cancelMessage += `; job resource termination failed: ${resourceCancellationError}`;
6259
6364
  sendSerializedWorkerMessage(
6260
6365
  ws,
6261
6366
  JSON.stringify({
@@ -6342,13 +6447,16 @@ async function startWorker(options, projectRuntime = {
6342
6447
  });
6343
6448
  return;
6344
6449
  }
6345
- await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
6346
- workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
6347
- if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
6348
- });
6450
+ let resources;
6451
+ let resourcesTransferred = false;
6349
6452
  let releaseWorkspaceMutation;
6350
6453
  let mutationLeaseTransferred = false;
6351
6454
  try {
6455
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6456
+ await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
6457
+ workspaceSyncPriorityPtyTargets.set(message.ptyId, message.target);
6458
+ if (targetMayMutateVisibleWorkspace(message.target)) recordVisibleWorkspaceMutation(message.target);
6459
+ });
6352
6460
  releaseWorkspaceMutation = await (0, import_workspace_command_sync_policy.acquireWorkspaceCommandMutation)(message.target, workspaceSyncSingleFlight);
6353
6461
  if (workspaceOperationsAreFenced(deferredWorkspaceConfiguration, activeWorkspaceIncidentId)) {
6354
6462
  throw new Error(
@@ -6359,6 +6467,7 @@ async function startWorker(options, projectRuntime = {
6359
6467
  process.stdout.write(`[r5d-worker] pty ${message.ptyId}: ${describeWorkerSessionTarget(message.target)}
6360
6468
  `);
6361
6469
  await openPty({
6470
+ resources,
6362
6471
  ws,
6363
6472
  message,
6364
6473
  resolvedTarget,
@@ -6371,6 +6480,7 @@ async function startWorker(options, projectRuntime = {
6371
6480
  }
6372
6481
  } : {}
6373
6482
  });
6483
+ resourcesTransferred = true;
6374
6484
  mutationLeaseTransferred = releaseWorkspaceMutation !== void 0;
6375
6485
  } catch (error) {
6376
6486
  sendWorkerMessageFromCurrentSource(ws, {
@@ -6382,6 +6492,7 @@ async function startWorker(options, projectRuntime = {
6382
6492
  } finally {
6383
6493
  workspaceSyncPriorityPtyTargets.delete(message.ptyId);
6384
6494
  if (!mutationLeaseTransferred) releaseWorkspaceMutation?.();
6495
+ if (!resourcesTransferred) await releaseWorkerJobResources(resources);
6385
6496
  }
6386
6497
  return;
6387
6498
  }
@@ -6440,9 +6551,11 @@ async function startWorker(options, projectRuntime = {
6440
6551
  return;
6441
6552
  }
6442
6553
  let result;
6554
+ let resources;
6443
6555
  let targetReserved = false;
6444
6556
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
6445
6557
  try {
6558
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6446
6559
  if (hasWorkspaceEffect) {
6447
6560
  await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
6448
6561
  workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
@@ -6460,6 +6573,7 @@ async function startWorker(options, projectRuntime = {
6460
6573
  process.stdout.write(`[r5d-worker] exec ${message.runId}: ${message.argv.join(" ")}
6461
6574
  `);
6462
6575
  return await executeCommand({
6576
+ resources,
6463
6577
  message,
6464
6578
  resolvedTarget,
6465
6579
  baseUrl,
@@ -6495,6 +6609,8 @@ async function startWorker(options, projectRuntime = {
6495
6609
  };
6496
6610
  } finally {
6497
6611
  if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
6612
+ await releaseWorkerJobResources(resources);
6613
+ cancelledProcessRuns.delete(message.runId);
6498
6614
  }
6499
6615
  if (infrastructureStoppedRunIds.has(message.runId)) {
6500
6616
  recoveryStore.unknown(message.requestId);
@@ -6526,12 +6642,14 @@ async function startWorker(options, projectRuntime = {
6526
6642
  runId: message.runId
6527
6643
  });
6528
6644
  const hasWorkspaceEffect = workerCommandHasWorkspaceEffect(message);
6645
+ let resources;
6529
6646
  const runCommand = async () => {
6530
6647
  try {
6531
6648
  const resolvedTarget = resolveMessageTarget(message.target);
6532
6649
  process.stdout.write(`[r5d-worker] exec_start ${message.runId}: ${message.argv.join(" ")}
6533
6650
  `);
6534
6651
  await executeStreamingCommand({
6652
+ resources,
6535
6653
  ws,
6536
6654
  message,
6537
6655
  resolvedTarget,
@@ -6565,6 +6683,7 @@ async function startWorker(options, projectRuntime = {
6565
6683
  const execution = (async () => {
6566
6684
  let targetReserved = false;
6567
6685
  try {
6686
+ resources = await acquireWorkerCommandResources(ws, message, assertMessageAdmission);
6568
6687
  if (hasWorkspaceEffect) {
6569
6688
  await (0, import_workspace_command_sync_policy.reserveWorkspaceCommandAfterCurrentSync)(message.target, workspaceSyncSingleFlight, () => {
6570
6689
  workspaceSyncPriorityProcessTargets.set(message.runId, message.target);
@@ -6580,6 +6699,8 @@ async function startWorker(options, projectRuntime = {
6580
6699
  });
6581
6700
  } finally {
6582
6701
  if (targetReserved) workspaceSyncPriorityProcessTargets.delete(message.runId);
6702
+ await releaseWorkerJobResources(resources);
6703
+ cancelledProcessRuns.delete(message.runId);
6583
6704
  }
6584
6705
  })();
6585
6706
  void execution.catch((error) => {
@@ -6701,7 +6822,7 @@ async function startWorker(options, projectRuntime = {
6701
6822
  clearInterval(terminalReplayTimer);
6702
6823
  terminalReplayTimer = void 0;
6703
6824
  }
6704
- if (projectRuntime.configuration) {
6825
+ if (currentWorkerSocket === ws && projectRuntime.configuration) {
6705
6826
  projectRuntime.configuration.incidentId = activeWorkspaceIncidentId;
6706
6827
  projectRuntime.configuration.deferred = deferredWorkspaceConfiguration;
6707
6828
  }
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "name": "@ricsam/r5d-worker",
3
- "version": "0.0.109",
3
+ "version": "0.0.111",
4
4
  "type": "commonjs"
5
5
  }