@tickernelz/paperclip-pro-adapter-utils 2026.1006.0 → 2026.1006.2

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 (181) hide show
  1. package/README.md +13 -0
  2. package/dist/acpx-engine/ephemeral-session-environment.d.ts +4 -0
  3. package/dist/acpx-engine/ephemeral-session-environment.d.ts.map +1 -0
  4. package/dist/acpx-engine/ephemeral-session-environment.js +29 -0
  5. package/dist/acpx-engine/ephemeral-session-environment.js.map +1 -0
  6. package/dist/acpx-engine/ephemeral-session-environment.test.d.ts +2 -0
  7. package/dist/acpx-engine/ephemeral-session-environment.test.d.ts.map +1 -0
  8. package/dist/acpx-engine/ephemeral-session-environment.test.js +177 -0
  9. package/dist/acpx-engine/ephemeral-session-environment.test.js.map +1 -0
  10. package/dist/acpx-engine/execute.d.ts +2 -6
  11. package/dist/acpx-engine/execute.d.ts.map +1 -1
  12. package/dist/acpx-engine/execute.js +125 -62
  13. package/dist/acpx-engine/execute.js.map +1 -1
  14. package/dist/acpx-engine/execute.test.js +299 -3
  15. package/dist/acpx-engine/execute.test.js.map +1 -1
  16. package/dist/acpx-engine/session-codec.d.ts.map +1 -1
  17. package/dist/acpx-engine/session-codec.js +1 -0
  18. package/dist/acpx-engine/session-codec.js.map +1 -1
  19. package/dist/acpx-engine/settlement-characterization.test.js +105 -0
  20. package/dist/acpx-engine/settlement-characterization.test.js.map +1 -1
  21. package/dist/acpx-engine/spawn-smoke.test.js +16 -7
  22. package/dist/acpx-engine/spawn-smoke.test.js.map +1 -1
  23. package/dist/acpx-engine/startup-cancellation.d.ts +1 -0
  24. package/dist/acpx-engine/startup-cancellation.d.ts.map +1 -1
  25. package/dist/acpx-engine/startup-cancellation.js +7 -2
  26. package/dist/acpx-engine/startup-cancellation.js.map +1 -1
  27. package/dist/acpx-engine/terminal-session-failure.d.ts +18 -0
  28. package/dist/acpx-engine/terminal-session-failure.d.ts.map +1 -0
  29. package/dist/acpx-engine/terminal-session-failure.js +106 -0
  30. package/dist/acpx-engine/terminal-session-failure.js.map +1 -0
  31. package/dist/acpx-engine/terminal-session-failure.test.d.ts +2 -0
  32. package/dist/acpx-engine/terminal-session-failure.test.d.ts.map +1 -0
  33. package/dist/acpx-engine/terminal-session-failure.test.js +139 -0
  34. package/dist/acpx-engine/terminal-session-failure.test.js.map +1 -0
  35. package/dist/acpx-engine/turn-characterization.test.js +4 -3
  36. package/dist/acpx-engine/turn-characterization.test.js.map +1 -1
  37. package/dist/command-managed-runtime.d.ts +2 -0
  38. package/dist/command-managed-runtime.d.ts.map +1 -1
  39. package/dist/command-managed-runtime.js +2 -0
  40. package/dist/command-managed-runtime.js.map +1 -1
  41. package/dist/command-redaction.d.ts +2 -0
  42. package/dist/command-redaction.d.ts.map +1 -1
  43. package/dist/command-redaction.js +20 -10
  44. package/dist/command-redaction.js.map +1 -1
  45. package/dist/command-redaction.test.js +20 -1
  46. package/dist/command-redaction.test.js.map +1 -1
  47. package/dist/directory-merge-lock.test.d.ts +2 -0
  48. package/dist/directory-merge-lock.test.d.ts.map +1 -0
  49. package/dist/directory-merge-lock.test.js +166 -0
  50. package/dist/directory-merge-lock.test.js.map +1 -0
  51. package/dist/execution-phase.d.ts +10 -0
  52. package/dist/execution-phase.d.ts.map +1 -0
  53. package/dist/execution-phase.js +27 -0
  54. package/dist/execution-phase.js.map +1 -0
  55. package/dist/execution-phase.test.d.ts +2 -0
  56. package/dist/execution-phase.test.d.ts.map +1 -0
  57. package/dist/execution-phase.test.js +26 -0
  58. package/dist/execution-phase.test.js.map +1 -0
  59. package/dist/execution-target-sandbox.test.js +49 -7
  60. package/dist/execution-target-sandbox.test.js.map +1 -1
  61. package/dist/execution-target-stdin-race.test.js +245 -2
  62. package/dist/execution-target-stdin-race.test.js.map +1 -1
  63. package/dist/execution-target.d.ts +6 -0
  64. package/dist/execution-target.d.ts.map +1 -1
  65. package/dist/execution-target.js +74 -22
  66. package/dist/execution-target.js.map +1 -1
  67. package/dist/git-workspace-sync.d.ts +15 -5
  68. package/dist/git-workspace-sync.d.ts.map +1 -1
  69. package/dist/git-workspace-sync.js +267 -124
  70. package/dist/git-workspace-sync.js.map +1 -1
  71. package/dist/git-workspace-sync.test.js +296 -20
  72. package/dist/git-workspace-sync.test.js.map +1 -1
  73. package/dist/github-launcher.d.ts.map +1 -1
  74. package/dist/github-launcher.js +26 -11
  75. package/dist/github-launcher.js.map +1 -1
  76. package/dist/github-launcher.test.js +34 -1
  77. package/dist/github-launcher.test.js.map +1 -1
  78. package/dist/index.d.ts +1 -1
  79. package/dist/index.d.ts.map +1 -1
  80. package/dist/index.js +1 -1
  81. package/dist/index.js.map +1 -1
  82. package/dist/paperclip-runner-permissions.d.ts +28 -1
  83. package/dist/paperclip-runner-permissions.d.ts.map +1 -1
  84. package/dist/paperclip-runner-permissions.js +9 -1
  85. package/dist/paperclip-runner-permissions.js.map +1 -1
  86. package/dist/prompt-sections.test.d.ts +2 -0
  87. package/dist/prompt-sections.test.d.ts.map +1 -0
  88. package/dist/prompt-sections.test.js +53 -0
  89. package/dist/prompt-sections.test.js.map +1 -0
  90. package/dist/provider-stopped-boundary.d.ts +7 -0
  91. package/dist/provider-stopped-boundary.d.ts.map +1 -0
  92. package/dist/provider-stopped-boundary.js +22 -0
  93. package/dist/provider-stopped-boundary.js.map +1 -0
  94. package/dist/provider-stopped-boundary.test.d.ts +2 -0
  95. package/dist/provider-stopped-boundary.test.d.ts.map +1 -0
  96. package/dist/provider-stopped-boundary.test.js +76 -0
  97. package/dist/provider-stopped-boundary.test.js.map +1 -0
  98. package/dist/remote-managed-runtime.d.ts +2 -0
  99. package/dist/remote-managed-runtime.d.ts.map +1 -1
  100. package/dist/remote-managed-runtime.js +3 -1
  101. package/dist/remote-managed-runtime.js.map +1 -1
  102. package/dist/remote-managed-runtime.test.js +25 -0
  103. package/dist/remote-managed-runtime.test.js.map +1 -1
  104. package/dist/sandbox-callback-bridge.d.ts +2 -0
  105. package/dist/sandbox-callback-bridge.d.ts.map +1 -1
  106. package/dist/sandbox-callback-bridge.js +34 -10
  107. package/dist/sandbox-callback-bridge.js.map +1 -1
  108. package/dist/sandbox-callback-bridge.test.js +106 -3
  109. package/dist/sandbox-callback-bridge.test.js.map +1 -1
  110. package/dist/sandbox-managed-runtime.d.ts +3 -0
  111. package/dist/sandbox-managed-runtime.d.ts.map +1 -1
  112. package/dist/sandbox-managed-runtime.js +749 -718
  113. package/dist/sandbox-managed-runtime.js.map +1 -1
  114. package/dist/sandbox-managed-runtime.test.js +171 -2
  115. package/dist/sandbox-managed-runtime.test.js.map +1 -1
  116. package/dist/server-utils.d.ts +43 -1
  117. package/dist/server-utils.d.ts.map +1 -1
  118. package/dist/server-utils.js +103 -31
  119. package/dist/server-utils.js.map +1 -1
  120. package/dist/server-utils.test.js +235 -45
  121. package/dist/server-utils.test.js.map +1 -1
  122. package/dist/session-compaction.d.ts.map +1 -1
  123. package/dist/session-compaction.js +6 -0
  124. package/dist/session-compaction.js.map +1 -1
  125. package/dist/ssh-fixture.test.js +22 -0
  126. package/dist/ssh-fixture.test.js.map +1 -1
  127. package/dist/ssh.d.ts +2 -0
  128. package/dist/ssh.d.ts.map +1 -1
  129. package/dist/ssh.js +2 -2
  130. package/dist/ssh.js.map +1 -1
  131. package/dist/test-fixtures/prompt-context.d.ts +54 -0
  132. package/dist/test-fixtures/prompt-context.d.ts.map +1 -0
  133. package/dist/test-fixtures/prompt-context.js +55 -0
  134. package/dist/test-fixtures/prompt-context.js.map +1 -0
  135. package/dist/types.d.ts +12 -2
  136. package/dist/types.d.ts.map +1 -1
  137. package/dist/workspace-git-stream.d.ts +19 -0
  138. package/dist/workspace-git-stream.d.ts.map +1 -0
  139. package/dist/workspace-git-stream.js +99 -0
  140. package/dist/workspace-git-stream.js.map +1 -0
  141. package/dist/workspace-git-stream.test.d.ts +2 -0
  142. package/dist/workspace-git-stream.test.d.ts.map +1 -0
  143. package/dist/workspace-git-stream.test.js +71 -0
  144. package/dist/workspace-git-stream.test.js.map +1 -0
  145. package/dist/workspace-manifest.d.ts +61 -0
  146. package/dist/workspace-manifest.d.ts.map +1 -0
  147. package/dist/workspace-manifest.js +257 -0
  148. package/dist/workspace-manifest.js.map +1 -0
  149. package/dist/workspace-manifest.test.d.ts +2 -0
  150. package/dist/workspace-manifest.test.d.ts.map +1 -0
  151. package/dist/workspace-manifest.test.js +160 -0
  152. package/dist/workspace-manifest.test.js.map +1 -0
  153. package/dist/workspace-restore-diagnostics.d.ts +34 -0
  154. package/dist/workspace-restore-diagnostics.d.ts.map +1 -0
  155. package/dist/workspace-restore-diagnostics.js +238 -0
  156. package/dist/workspace-restore-diagnostics.js.map +1 -0
  157. package/dist/workspace-restore-diagnostics.test.d.ts +2 -0
  158. package/dist/workspace-restore-diagnostics.test.d.ts.map +1 -0
  159. package/dist/workspace-restore-diagnostics.test.js +258 -0
  160. package/dist/workspace-restore-diagnostics.test.js.map +1 -0
  161. package/dist/workspace-restore-merge.d.ts +47 -6
  162. package/dist/workspace-restore-merge.d.ts.map +1 -1
  163. package/dist/workspace-restore-merge.js +382 -114
  164. package/dist/workspace-restore-merge.js.map +1 -1
  165. package/dist/workspace-restore-merge.test.js +253 -4
  166. package/dist/workspace-restore-merge.test.js.map +1 -1
  167. package/dist/workspace-restore-result.d.ts +6 -0
  168. package/dist/workspace-restore-result.d.ts.map +1 -0
  169. package/dist/workspace-restore-result.js +72 -0
  170. package/dist/workspace-restore-result.js.map +1 -0
  171. package/dist/workspace-restore-result.test.d.ts +2 -0
  172. package/dist/workspace-restore-result.test.d.ts.map +1 -0
  173. package/dist/workspace-restore-result.test.js +74 -0
  174. package/dist/workspace-restore-result.test.js.map +1 -0
  175. package/dist/workspace-restore-teardown.d.ts.map +1 -1
  176. package/dist/workspace-restore-teardown.js +5 -3
  177. package/dist/workspace-restore-teardown.js.map +1 -1
  178. package/dist/workspace-restore-teardown.test.js +24 -0
  179. package/dist/workspace-restore-teardown.test.js.map +1 -1
  180. package/node_modules/acpx/dist/live-checkpoint-ClPCSdrW.js +4 -3
  181. package/package.json +2 -2
@@ -9,7 +9,7 @@ import { githubLauncherSource } from "./github-launcher.js";
9
9
  import { prepareCommandManagedRuntime, } from "./command-managed-runtime.js";
10
10
  import { buildRemoteExecutionSessionIdentity, prepareRemoteManagedRuntime, remoteExecutionSessionMatches, } from "./remote-managed-runtime.js";
11
11
  export { resolveReferencedSourceIgnore } from "./sandbox-managed-runtime.js";
12
- import { createCommandManagedSandboxCallbackBridgeQueueClient, createSandboxCallbackBridgeAsset, createSandboxCallbackBridgeToken, DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES, HTTP2_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST, SANDBOX_CALLBACK_BRIDGE_HTTP2_MODE, sandboxCallbackBridgeDirectories, startSandboxCallbackBridgeServer, startSandboxCallbackBridgeWorker, syncRemoteTextFileWithHashSkip, syncSandboxCallbackBridgeEntrypoint, } from "./sandbox-callback-bridge.js";
12
+ import { createCommandManagedSandboxCallbackBridgeQueueClient, createSandboxCallbackBridgeAsset, createSandboxCallbackBridgeToken, DEFAULT_SANDBOX_CALLBACK_BRIDGE_MAX_BODY_BYTES, HTTP2_SANDBOX_CALLBACK_BRIDGE_ROUTE_ALLOWLIST, runSandboxBridgeControlCommand, SANDBOX_CALLBACK_BRIDGE_HTTP2_MODE, sandboxCallbackBridgeDirectories, startSandboxCallbackBridgeServer, startSandboxCallbackBridgeWorker, syncRemoteTextFileWithHashSkip, syncSandboxCallbackBridgeEntrypoint, } from "./sandbox-callback-bridge.js";
13
13
  import { createHttp2BridgeServer, BridgeProcessCapacityError, } from "./http2-bridge-server.js";
14
14
  import { createSandboxRunLogTailFactory, } from "./sandbox-run-log-stream.js";
15
15
  import { DEFAULT_DUPLEX_BROKER_BUDGETS, DUPLEX_CHANNEL_LOST_ERROR_CODE, isSafeBridgeMethod, } from "./bridge-transport-contract.js";
@@ -34,13 +34,9 @@ export function postedIssueCommentLogMarker(method, requestPath, status, body) {
34
34
  }
35
35
  }
36
36
  export { sanitizeRemoteExecutionEnv } from "./remote-execution-env.js";
37
- // 4-hour wall-clock backstop for sandbox-backed adapter runs. This is a
38
- // last-resort kill switch, not the primary hang detector: genuinely hung runs
39
- // are caught much earlier by the adapters' output-inactivity monitors (e.g.
40
- // codex-local's 7-minute monitor). The value intentionally matches the
41
- // recovery watchdog's ACTIVE_RUN_OUTPUT_CRITICAL_THRESHOLD_MS (4h) in
42
- // server/src/services/recovery/service.ts so healthy long runs are never
43
- // killed by the adapter before the watchdog would even consider them stuck.
37
+ // Four-hour wall-clock backstop for sandbox-backed adapter runs. Keep this
38
+ // execution limit independent of the earlier informational silence warnings
39
+ // and the short deadlines for bridge control operations.
44
40
  export const DEFAULT_REMOTE_SANDBOX_ADAPTER_TIMEOUT_SEC = 14_400;
45
41
  function parseObject(value) {
46
42
  return value && typeof value === "object" && !Array.isArray(value)
@@ -434,6 +430,8 @@ export async function runAdapterExecutionTargetProcess(runId, target, command, a
434
430
  // after the clean process completion cannot latch a false mid-run loss. A
435
431
  // control channel that died before this clean completion still fails the
436
432
  // run closed.
433
+ if (!result.timedOut && typeof result.exitCode === "number" && Number.isInteger(result.exitCode) && result.exitCode >= 0)
434
+ options.onProcessStopped?.();
437
435
  const settled = applyRunDispositionSeam(result, options.settleRunDisposition);
438
436
  if (runLogTail) {
439
437
  await runLogTail.finish({ stdout: result.stdout, stderr: result.stderr });
@@ -450,7 +448,7 @@ export async function runAdapterExecutionTargetProcess(runId, target, command, a
450
448
  const env = target?.kind === "remote" && target.transport === "ssh"
451
449
  ? sanitizeRemoteExecutionEnv(options.env)
452
450
  : options.env;
453
- return await runChildProcess(runId, command, args, {
451
+ const result = await runChildProcess(runId, command, args, {
454
452
  cwd: options.cwd,
455
453
  env,
456
454
  stdin: options.stdin,
@@ -463,6 +461,13 @@ export async function runAdapterExecutionTargetProcess(runId, target, command, a
463
461
  localProcessSandbox: target?.kind === "local" || !target ? options.localProcessSandbox : null,
464
462
  remoteExecution: adapterExecutionTargetToRemoteSpec(target),
465
463
  });
464
+ // Closing an SSH client on timeout/disconnect does not prove the remote
465
+ // provider exited. SSH status 255 is transport failure, never a stop receipt.
466
+ if (!target || target.kind === "local" ||
467
+ (!result.timedOut && !result.signal && result.exitCode !== null && result.exitCode >= 0 && result.exitCode < 255)) {
468
+ options.onProcessStopped?.();
469
+ }
470
+ return result;
466
471
  }
467
472
  export async function runAdapterExecutionTargetShellCommand(runId, target, command, options) {
468
473
  const onLog = options.onLog ?? (async () => { });
@@ -849,6 +854,8 @@ export async function prepareAdapterExecutionTargetRuntime(input) {
849
854
  workspaceLocalDir: input.workspaceLocalDir,
850
855
  workspaceRemoteDir: input.workspaceRemoteDir,
851
856
  syncWorkspace: input.syncWorkspace,
857
+ workspaceFileMode: input.workspaceFileMode,
858
+ workspaceExclude: input.workspaceExclude,
852
859
  assets: input.assets,
853
860
  additionalSources: input.additionalSources,
854
861
  onProgress: input.onProgress,
@@ -886,6 +893,7 @@ export async function prepareAdapterExecutionTargetRuntime(input) {
886
893
  workspaceBaseline: input.workspaceBaseline,
887
894
  workspaceGitSnapshot: input.workspaceGitSnapshot,
888
895
  workspaceExclude: input.workspaceExclude,
896
+ workspaceFileMode: input.workspaceFileMode,
889
897
  preserveAbsentOnRestore: input.preserveAbsentOnRestore,
890
898
  assets: input.assets,
891
899
  additionalSources: input.additionalSources,
@@ -903,6 +911,7 @@ export async function prepareAdapterExecutionTargetRuntime(input) {
903
911
  additionalSourceDirs: prepared.additionalSourceDirs,
904
912
  additionalSourceFailures: prepared.additionalSourceFailures,
905
913
  workspaceSyncSnapshot: prepared.workspaceSyncSnapshot,
914
+ cleanupWorkspaceSnapshot: prepared.cleanupWorkspaceSnapshot,
906
915
  restoreWorkspace: prepared.restoreWorkspace,
907
916
  };
908
917
  }
@@ -1283,6 +1292,11 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1283
1292
  }
1284
1293
  const target = input.target;
1285
1294
  const onLog = input.onLog ?? (async () => { });
1295
+ // Failure diagnostics are best effort: stalled or failed run-log persistence
1296
+ // must not prevent sending shutdown or removing the bridge's session files.
1297
+ const logFailureWithoutWaiting = (message) => {
1298
+ void Promise.resolve().then(() => onLog("stderr", message)).catch(() => undefined);
1299
+ };
1286
1300
  const runner = requireSandboxRunner(target);
1287
1301
  // Run one unit of run-time work under its named wrapper span when a span
1288
1302
  // runner is injected. Without a runner, run the work under the current run
@@ -1347,7 +1361,7 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1347
1361
  else {
1348
1362
  const payloadPath = path.posix.join(sessionDir, "command.b64");
1349
1363
  const runPayloadSetup = async (script) => {
1350
- const result = await runner.execute({
1364
+ const result = await runSandboxBridgeControlCommand(runner, {
1351
1365
  command: shellCommand,
1352
1366
  args: shellCommandArgs(script),
1353
1367
  cwd: target.remoteCwd,
@@ -1376,7 +1390,7 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1376
1390
  // as one foreground session command further down instead, so skip this.
1377
1391
  if (!streamOutput) {
1378
1392
  await onLog("stdout", `[paperclip] Starting ACP process session bridge in sandbox (${target.providerKey ?? "provider"}).\n`);
1379
- const startResult = await runner.execute({
1393
+ const startResult = await runSandboxBridgeControlCommand(runner, {
1380
1394
  command: shellCommand,
1381
1395
  args: shellCommandArgs([
1382
1396
  `mkdir -p ${shellQuote(stdinDir)} ${shellQuote(eventsDir)}`,
@@ -1421,6 +1435,28 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1421
1435
  // a big earlier chunk, so the wrapper reads the stdin bytes out of order and
1422
1436
  // corrupts a large prompt on the stdin path.
1423
1437
  let stdinWriteChain = Promise.resolve();
1438
+ let stdinDeliveryFailed = false;
1439
+ const writeStdinFile = async (filePath, body) => {
1440
+ // Retry the same sequence, never the ACP request or the tool itself. Each
1441
+ // upload uses private temporary paths, and the wrapper drops sequences it
1442
+ // already consumed when a provider loses the final rename's response.
1443
+ for (let attempt = 1;; attempt += 1) {
1444
+ try {
1445
+ await client.writeTextFile(filePath, body);
1446
+ return;
1447
+ }
1448
+ catch (error) {
1449
+ // Plugin RPC preserves provider messages but not HTTP error classes.
1450
+ // Match the Daytona SDK and Cloudflare bridge's gateway diagnostics
1451
+ // exactly; shell failures and auth errors must still fail immediately.
1452
+ const gatewayFailure = error instanceof Error && (/^Request failed with status code (502|503|504)$/.test(error.message) ||
1453
+ /^Cloudflare sandbox bridge request failed with HTTP (502|503|504)\.$/.test(error.message));
1454
+ if (!gatewayFailure || attempt >= 3)
1455
+ throw error;
1456
+ await new Promise((resolve) => setTimeout(resolve, attempt * 250));
1457
+ }
1458
+ }
1459
+ };
1424
1460
  let pollTimer = null;
1425
1461
  const pendingRemoteEvents = [];
1426
1462
  const token = createSandboxCallbackBridgeToken(18);
@@ -1547,15 +1583,24 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1547
1583
  // Chain this write after the previous one, so the atomic rename for
1548
1584
  // file N finishes before the write for file N+1 starts. Keep the
1549
1585
  // per-message `sandbox.agentSession.sendInput` span inside the chain.
1550
- const write = stdinWriteChain.then(() => runRuntimeWork(AGENT_SESSION_SEND_INPUT_SPAN, () => client.writeTextFile(filePath, jsonLine(stdinPayload))));
1551
- // The next message chains after this write on success or failure, so a
1552
- // failed write never blocks the chain. This mirrors the wrapper
1553
- // `writeChain` pattern for its event files.
1554
- stdinWriteChain = write.then(() => undefined, () => undefined);
1555
- // Keep the failure behavior: send one error line, then destroy the socket.
1556
- write.catch((error) => {
1557
- nextSocket.write(jsonLine({ type: "error", message: error instanceof Error ? error.message : String(error) }));
1558
- nextSocket.destroy();
1586
+ stdinWriteChain = stdinWriteChain.then(async () => {
1587
+ if (stdinDeliveryFailed)
1588
+ return;
1589
+ try {
1590
+ await runRuntimeWork(AGENT_SESSION_SEND_INPUT_SPAN, () => writeStdinFile(filePath, jsonLine(stdinPayload)));
1591
+ }
1592
+ catch {
1593
+ stdinDeliveryFailed = true;
1594
+ stopping = true;
1595
+ const message = "ACP process session input delivery failed.";
1596
+ // Flush the diagnostic before closing; destroy() can discard it
1597
+ // and leave only ACP's generic connection_close error. Do not
1598
+ // expose provider error text, which may contain a command payload.
1599
+ nextSocket.end(jsonLine({ type: "error", message }));
1600
+ // stop() awaits this input chain before sending shutdown. Run-log
1601
+ // persistence must not hold teardown open when it stalls or fails.
1602
+ logFailureWithoutWaiting(`[paperclip] ${message}\n`);
1603
+ }
1559
1604
  });
1560
1605
  }
1561
1606
  }
@@ -1818,7 +1863,7 @@ export async function startAdapterExecutionTargetProcessSessionBridge(input) {
1818
1863
  ]);
1819
1864
  stopReadingForShutdownAck = true;
1820
1865
  if (!acknowledgedInTime) {
1821
- await onLog("stderr", `[paperclip] ACP process session wrapper did not acknowledge shutdown within ${DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS}ms; removing the session directory anyway.\n`).catch(() => undefined);
1866
+ logFailureWithoutWaiting(`[paperclip] ACP process session wrapper did not acknowledge shutdown within ${DEFAULT_PROCESS_SESSION_SHUTDOWN_WAIT_MS}ms; removing the session directory anyway.\n`);
1822
1867
  }
1823
1868
  // Unconditional: this removal runs whether or not the wrapper
1824
1869
  // acknowledged, and whether or not any event (real or forged) arrived
@@ -2246,6 +2291,14 @@ async function pollStdin() {
2246
2291
  for (const name of entries) {
2247
2292
  if (shuttingDown) break;
2248
2293
  const entrySeq = Number.parseInt(name, 10);
2294
+ const file = path.posix.join(stdinDir, name);
2295
+ // A successful publication can be retried after its provider response
2296
+ // was lost, even after we consumed it. Never send those bytes twice or
2297
+ // move the expected sequence backwards. This also handles late uploads.
2298
+ if (Number.isFinite(entrySeq) && entrySeq < stdinExpectedSeq) {
2299
+ await fs.rm(file, { force: true }).catch(() => undefined);
2300
+ continue;
2301
+ }
2249
2302
  // Hold the send order when an earlier file has not appeared. Do not consume
2250
2303
  // this later file: wait for the missing file on a later cycle, bounded by
2251
2304
  // the retry budget. After the budget, fail loud and advance past the gap,
@@ -2264,7 +2317,6 @@ async function pollStdin() {
2264
2317
  stdinGapRetries = 0;
2265
2318
  stdinExpectedSeq = entrySeq;
2266
2319
  }
2267
- const file = path.posix.join(stdinDir, name);
2268
2320
  let message;
2269
2321
  try {
2270
2322
  // Hardening (I3): open with O_NOFOLLOW where the platform defines it,