@sagentlab/navarch-runtime 0.1.77 → 0.1.78

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.
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.COMPLETION_TRANSIENT_RETRY_DELAYS_MS = void 0;
4
+ exports.isTransientCompletionError = isTransientCompletionError;
5
+ exports.completeWithTransientRetry = completeWithTransientRetry;
6
+ const api_cjs_1 = require("./api.cjs");
7
+ /**
8
+ * Backoff before each re-send of a completion that failed in transit. About
9
+ * two minutes in total: long enough to ride out a Wi-Fi drop or an edge blip,
10
+ * short enough that the lease is still live when the report finally lands.
11
+ */
12
+ exports.COMPLETION_TRANSIENT_RETRY_DELAYS_MS = [2_000, 5_000, 15_000, 30_000, 60_000];
13
+ /** Statuses the edge or a gateway answers for an unreachable origin; the completion route never chose them. */
14
+ const TRANSIENT_STATUSES = new Set([408, 429, 502, 503, 504, 520, 521, 522, 523, 524]);
15
+ /**
16
+ * A failure that says nothing about the completion itself: the request never
17
+ * got an answer from the completion route. A plain 500 is the route's own
18
+ * answer and keeps the existing crash path.
19
+ */
20
+ function isTransientCompletionError(err) {
21
+ return (err instanceof api_cjs_1.NavarchTransportError ||
22
+ (err instanceof api_cjs_1.NavarchApiError && TRANSIENT_STATUSES.has(err.status)));
23
+ }
24
+ /**
25
+ * Sends a lease completion, re-sending it while the control plane is
26
+ * unreachable. Without this, one dropped connection turned a finished session
27
+ * (pull request already open) into `Session crashed:`, which spent an
28
+ * infrastructure retry and discarded the report and evidence the review loop
29
+ * needed (carova#310). A re-send whose earlier copy did land gets the
30
+ * `already_released` 409, which callers already treat as success.
31
+ */
32
+ async function completeWithTransientRetry(send, options = {}) {
33
+ const delays = options.delaysMs ?? exports.COMPLETION_TRANSIENT_RETRY_DELAYS_MS;
34
+ const sleep = options.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
35
+ for (let attempt = 0;; attempt += 1) {
36
+ try {
37
+ await send();
38
+ return;
39
+ }
40
+ catch (err) {
41
+ const delayMs = delays[attempt];
42
+ if (delayMs === undefined || !isTransientCompletionError(err))
43
+ throw err;
44
+ options.onRetry?.(attempt + 1, delayMs, err);
45
+ await sleep(delayMs);
46
+ }
47
+ }
48
+ }
package/dist/session.cjs CHANGED
@@ -25,6 +25,7 @@ const delivery_cjs_1 = require("./delivery.cjs");
25
25
  const worktree_guard_cjs_1 = require("./worktree-guard.cjs");
26
26
  const adapter_watchdog_cjs_1 = require("./adapter-watchdog.cjs");
27
27
  const lease_heartbeat_cjs_1 = require("./lease-heartbeat.cjs");
28
+ const completion_retry_cjs_1 = require("./completion-retry.cjs");
28
29
  const adapter_capacity_cjs_1 = require("./adapter-capacity.cjs");
29
30
  const agent_auth_cjs_1 = require("./agent-auth.cjs");
30
31
  const browser_evidence_cjs_1 = require("./browser-evidence.cjs");
@@ -730,7 +731,9 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
730
731
  transcript_url: completion.transcript_url,
731
732
  };
732
733
  try {
733
- await api.completeLease(leaseId, completion);
734
+ await (0, completion_retry_cjs_1.completeWithTransientRetry)(() => api.completeLease(leaseId, completion), {
735
+ onRetry: (attempt, delayMs, err) => log.warn(`completion for ${leaseId} did not reach the control plane (${(0, redact_cjs_1.redactText)(String(err), knownSecrets)}); re-sending in ${delayMs / 1000}s (${attempt}).`),
736
+ });
734
737
  if (deliveredPullRequest && completion.status === "completed" && gitWorktree.restoredHostedCheckpoint) {
735
738
  // The pull request carries the hosted attempt's work now; a later
736
739
  // hosted attempt must not resume the stale snapshot.
@@ -766,7 +769,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
766
769
  log.error(`completion for ${leaseId} was rejected as ${rejection.code}, which is not remediable; failing the lease.`);
767
770
  }
768
771
  try {
769
- await api.completeLease(leaseId, {
772
+ await (0, completion_retry_cjs_1.completeWithTransientRetry)(() => api.completeLease(leaseId, {
770
773
  ...completion,
771
774
  status: "failed",
772
775
  report: remediable
@@ -774,7 +777,7 @@ async function runClaimedSession(deps, claimed, sessionId, lifecycle) {
774
777
  : `${redactedRejection}\n\n---\n\n${completion.report}`,
775
778
  failure_summary: redactedRejection,
776
779
  exit_status: "failed",
777
- });
780
+ }));
778
781
  }
779
782
  catch (fallbackErr) {
780
783
  // Same race as the primary completion above: if something else released
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sagentlab/navarch-runtime",
3
- "version": "0.1.77",
3
+ "version": "0.1.78",
4
4
  "description": "Navarch machine-side session manager: claims delivery tasks and runs them through local coding agents.",
5
5
  "type": "commonjs",
6
6
  "license": "MIT",