@cmmd-center/forge 0.13.77 → 0.13.79

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 (45) hide show
  1. package/dist/{BunHttpServer-DjilmGOM.mjs → BunHttpServer-BpaVqxli.mjs} +6 -6
  2. package/dist/{BunHttpServer-CXndtc1j.cjs → BunHttpServer-DTG5XWSi.cjs} +7 -7
  3. package/dist/{BunPTY-BiTlUj63.cjs → BunPTY-DkR9y_Jg.cjs} +3 -3
  4. package/dist/{BunPTY-Csgwhbi5.mjs → BunPTY-DsRB6_t-.mjs} +3 -3
  5. package/dist/{BunServices-BZKzHdrG.mjs → BunServices-ZpOiNNsP.mjs} +3 -3
  6. package/dist/{BunServices-DsWG7VM1.cjs → BunServices-blx5Lx2N.cjs} +3 -3
  7. package/dist/{Cache-BmGc_2BZ.cjs → Cache-D-JaJz5Q.cjs} +3 -3
  8. package/dist/{Cache-D57GWb9-.mjs → Cache-DOwpGEYJ.mjs} +3 -3
  9. package/dist/{Config-Cdfpphpo.mjs → Config-BnFFyBjD.mjs} +2 -2
  10. package/dist/{Config-CwKCCk14.cjs → Config-pbf0GGHJ.cjs} +2 -2
  11. package/dist/{HttpServer-Bclwvu0I.mjs → HttpServer-5UTabe01.mjs} +3 -3
  12. package/dist/{HttpServer-BBzRBmVt.cjs → HttpServer-DZ6iaxyk.cjs} +12 -12
  13. package/dist/{NodeHttpServer-Ds3Gp1Gp.mjs → NodeHttpServer-DePOzmYy.mjs} +7 -7
  14. package/dist/{NodeHttpServer-B4dCKqYY.cjs → NodeHttpServer-RV5QsVeb.cjs} +7 -7
  15. package/dist/{NodePTY-DgXWLtWp.mjs → NodePTY-Dduv48Vi.mjs} +4 -4
  16. package/dist/{NodePTY-BJ7874MO.cjs → NodePTY-DkaJNgpi.cjs} +4 -4
  17. package/dist/{NodeServices-CtC4a-h5.cjs → NodeServices-BkfWZHfh.cjs} +3 -3
  18. package/dist/{NodeServices-D3IxOzHl.mjs → NodeServices-Bt1GwPBR.mjs} +3 -3
  19. package/dist/{NodeServices-BvdIc9Vh.cjs → NodeServices-CocSeCvl.cjs} +1 -1
  20. package/dist/{NodeSqliteClient-D29w60zZ.mjs → NodeSqliteClient-BpmyZDcj.mjs} +6 -6
  21. package/dist/{NodeSqliteClient-CPsJxxdD.cjs → NodeSqliteClient-VL8Dgl03.cjs} +7 -7
  22. package/dist/{NodeTerminal-9iHZIdt1.mjs → NodeTerminal-CeaWke6-.mjs} +3 -3
  23. package/dist/{NodeTerminal-CO-i-6Je.cjs → NodeTerminal-DQESKR0W.cjs} +3 -3
  24. package/dist/{PTY-BzdbZ0Ym.mjs → PTY-BRT13Y8S.mjs} +2 -2
  25. package/dist/{PTY-B4ZctbwQ.cjs → PTY-C8ypptmz.cjs} +2 -2
  26. package/dist/{Path-CZJ1gPPo.mjs → Path-BIaGSU2M.mjs} +34 -3
  27. package/dist/{Path-BQOH_fWQ.cjs → Path-Dp3btyUm.cjs} +40 -3
  28. package/dist/{Schema-DOBWF8gQ.mjs → Schema-Ay3DUM83.mjs} +82 -11
  29. package/dist/{Schema-CDCJ3xZo.cjs → Schema-DxddW8-V.cjs} +105 -10
  30. package/dist/{SqlError-BMBkezUO.mjs → SqlError-BBR7DPrQ.mjs} +3 -3
  31. package/dist/{SqlError-g4pLzNWR.cjs → SqlError-BqOsoG0V.cjs} +3 -3
  32. package/dist/{SqliteClient-k0pbfeIC.cjs → SqliteClient-BgoGs_ji.cjs} +6 -6
  33. package/dist/{SqliteClient-Bgo_s5sA.mjs → SqliteClient-DQYXFVMu.mjs} +5 -5
  34. package/dist/bin.cjs +458 -49
  35. package/dist/bin.mjs +455 -46
  36. package/dist/client/assets/{DiffPanel-CtO6uWUR.js → DiffPanel-DxiNj12C.js} +2 -2
  37. package/dist/client/assets/{DiffPanel.logic-D0ANxwBg.js → DiffPanel.logic-D7xB9Erg.js} +2 -2
  38. package/dist/client/assets/{DiffPanelShell-C_jUanNz.js → DiffPanelShell-1dMsYoGN.js} +115 -112
  39. package/dist/client/assets/{DiffWorkerPoolProvider-BAShH3us.js → DiffWorkerPoolProvider-BS3XcZ9G.js} +2 -2
  40. package/dist/client/assets/{PullRequestCodePanel-BK_z4V5D.js → PullRequestCodePanel-BXvOtt5U.js} +2 -2
  41. package/dist/client/assets/{Virtualizer-D6ltPf0M.js → Virtualizer-DcohbA3j.js} +2 -2
  42. package/dist/client/assets/{index-Wp8UVHJj.js → index-BGgOXu17.js} +13 -13
  43. package/dist/client/index.html +4 -4
  44. package/dist/forge-build.json +1 -1
  45. package/package.json +1 -1
package/dist/bin.cjs CHANGED
@@ -2,18 +2,18 @@
2
2
 
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
4
4
  const require_chunk = require("./chunk-BhaIgcZK.cjs");
5
- const require_Schema$1 = require("./Schema-CDCJ3xZo.cjs");
6
- const require_Path = require("./Path-BQOH_fWQ.cjs");
7
- const require_NodeTerminal = require("./NodeTerminal-CO-i-6Je.cjs");
8
- const require_NodeServices = require("./NodeServices-CtC4a-h5.cjs");
9
- const require_HttpServer = require("./HttpServer-BBzRBmVt.cjs");
10
- const require_Config = require("./Config-CwKCCk14.cjs");
11
- const require_Cache = require("./Cache-BmGc_2BZ.cjs");
12
- const require_SqlError = require("./SqlError-g4pLzNWR.cjs");
5
+ const require_Schema$1 = require("./Schema-DxddW8-V.cjs");
6
+ const require_Path = require("./Path-Dp3btyUm.cjs");
7
+ const require_NodeTerminal = require("./NodeTerminal-DQESKR0W.cjs");
8
+ const require_NodeServices = require("./NodeServices-BkfWZHfh.cjs");
9
+ const require_HttpServer = require("./HttpServer-DZ6iaxyk.cjs");
10
+ const require_Config = require("./Config-pbf0GGHJ.cjs");
11
+ const require_Cache = require("./Cache-D-JaJz5Q.cjs");
12
+ const require_SqlError = require("./SqlError-BqOsoG0V.cjs");
13
13
  const require_Mime = require("./Mime-Bilnh8zE.cjs");
14
14
  const require_esm = require("./esm-BZhXwX5p.cjs");
15
15
  const require_runtimeControlAuthority = require("./runtimeControlAuthority-DCitZFTk.cjs");
16
- const require_PTY = require("./PTY-B4ZctbwQ.cjs");
16
+ const require_PTY = require("./PTY-C8ypptmz.cjs");
17
17
  const require_settle = require("./settle-B6Rn65UV.cjs");
18
18
  let node_child_process = require("node:child_process");
19
19
  node_child_process = require_chunk.__toESM(node_child_process);
@@ -66275,7 +66275,7 @@ function normalizeNumberish(value) {
66275
66275
  }
66276
66276
  //#endregion
66277
66277
  //#region package.json
66278
- var version$1 = "0.13.77";
66278
+ var version$1 = "0.13.79";
66279
66279
  //#endregion
66280
66280
  //#region src/sentry.ts
66281
66281
  const SERVER_APP_NAME = "forge-server";
@@ -76953,7 +76953,7 @@ const makeNoSerialization$1 = /* @__PURE__ */ require_Schema$1.fnUntraced(functi
76953
76953
  const span = disableTracing ? void 0 : yield* require_Schema$1.makeSpanScoped(`${spanPrefix}.${rpc._tag}`, { attributes: options.spanAttributes });
76954
76954
  const fiber = require_Path.getCurrent();
76955
76955
  const id = generateRequestId();
76956
- const scope = require_Schema$1.getUnsafe(fiber.context, require_Schema$1.Scope);
76956
+ const scope = require_Schema$1.getUnsafe$1(fiber.context, require_Schema$1.Scope);
76957
76957
  yield* require_Schema$1.addFinalizerExit(scope, (exit) => {
76958
76958
  if (!entries.has(id)) return require_Schema$1.void_;
76959
76959
  entries.delete(id);
@@ -77444,7 +77444,7 @@ const makeNoSerialization = /* @__PURE__ */ require_Schema$1.fnUntraced(function
77444
77444
  });
77445
77445
  });
77446
77446
  const applyMiddleware = (context, handler, options) => {
77447
- for (const service of options.rpc.middlewares) handler = require_Schema$1.getUnsafe(context, service)(handler, options);
77447
+ for (const service of options.rpc.middlewares) handler = require_Schema$1.getUnsafe$1(context, service)(handler, options);
77448
77448
  return handler;
77449
77449
  };
77450
77450
  /**
@@ -115412,7 +115412,7 @@ const fromPool = /* @__PURE__ */ require_Schema$1.fnUntraced(function* (options)
115412
115412
  }, client ? require_Schema$1.succeed(client) : reserveRaw);
115413
115413
  const reserveRaw = require_Schema$1.callback((resume) => {
115414
115414
  const fiber = require_Path.getCurrent();
115415
- const scope = require_Schema$1.getUnsafe(fiber.context, require_Schema$1.Scope);
115415
+ const scope = require_Schema$1.getUnsafe$1(fiber.context, require_Schema$1.Scope);
115416
115416
  let cause = void 0;
115417
115417
  function onError(cause_) {
115418
115418
  cause = cause_;
@@ -119238,8 +119238,8 @@ require_Schema$1.effectDiscard(runMigrations());
119238
119238
  //#endregion
119239
119239
  //#region src/persistence/Layers/Sqlite.ts
119240
119240
  const defaultSqliteClientLoaders = {
119241
- bun: () => Promise.resolve().then(() => require("./SqliteClient-k0pbfeIC.cjs")),
119242
- node: () => Promise.resolve().then(() => require("./NodeSqliteClient-CPsJxxdD.cjs"))
119241
+ bun: () => Promise.resolve().then(() => require("./SqliteClient-BgoGs_ji.cjs")),
119242
+ node: () => Promise.resolve().then(() => require("./NodeSqliteClient-VL8Dgl03.cjs"))
119243
119243
  };
119244
119244
  const makeRuntimeSqliteLayer = require_Schema$1.fn("makeRuntimeSqliteLayer")(function* (config) {
119245
119245
  const loader = defaultSqliteClientLoaders[process.versions.bun !== void 0 ? "bun" : "node"];
@@ -213922,6 +213922,7 @@ function categorizePublicProviderError(message) {
213922
213922
  if (/\bforge_provider_model_not_allowed\b/.test(m) || /\bnot\s+scoped\s+to\s+the\s+requested\s+model\b/.test(m)) return "The workspace AI credential does not cover the model this turn asked for. Retry, or pick that model for the thread.";
213923
213923
  if (/\bmodel\b[\s\S]*\b(not\s+(found|available|supported)|does\s+not\s+exist|unsupported|invalid|unknown)\b/.test(m) || /\b(unsupported|unknown|invalid)\b[\s\S]*\bmodel\b/.test(m) || /\b(model_not_found|invalid_model|model_not_supported)\b/.test(m)) return "The selected model isn't available right now. Pick a different model and retry.";
213924
213924
  if (isProviderRateOrQuotaError(m)) return "The AI service is rate-limited or out of quota. Wait a moment and retry.";
213925
+ if (/_credential_reconnect_required\b/.test(m)) return "The workspace AI credential needs to be reconnected. Reconnect it in Settings > Providers, then retry.";
213925
213926
  if (/\b(unauthorized|forbidden|authentication\s+(failed|error|required)|invalid\s+api\s+key|expired\s+(token|credential))\b/.test(m) || /\b40[13]\b/.test(m)) return "Workspace AI authentication failed. Reconnect the workspace and retry.";
213926
213927
  if (/\b(timed?\s*out|timeout|overloaded|service\s+unavailable)\b/.test(m) || /\b50[234]\b/.test(m)) return "The AI service is temporarily overloaded. Retry in a moment.";
213927
213928
  return null;
@@ -292537,6 +292538,52 @@ function applyThreadGoalPreamble(goal, input) {
292537
292538
  return trimmedInput.length > 0 ? `${preamble}\n\n${trimmedInput}` : preamble;
292538
292539
  }
292539
292540
  //#endregion
292541
+ //#region src/orchestration/providerRetryableFailure.ts
292542
+ /**
292543
+ * Classifies a provider turn-start failure as a retryable rate-limit or
292544
+ * transient-availability failure (429/503), a terminal failure that names why
292545
+ * it will never clear on its own, or unrecognized (null).
292546
+ *
292547
+ * CMMD-Center/forge#3558: a 429 from CMMD's provider egress used to leave no
292548
+ * trace at all, because nothing in the turn-start path recognized it as a
292549
+ * distinct, retryable failure. `ProviderCommandReactor` uses this to decide
292550
+ * whether to retry a turn start with backoff, and to name the provider,
292551
+ * status and Retry-After in the `provider.turn.start.failed` activity CMMD's
292552
+ * watchdog reads once retries are exhausted.
292553
+ *
292554
+ * Reuses `isProviderRateOrQuotaError` (the same predicate the public-copy
292555
+ * sanitizer uses to recognize a rate-limit message) rather than restating its
292556
+ * word list here.
292557
+ */
292558
+ const SERVICE_UNAVAILABLE_PATTERN = /\b(503|service\s+unavailable)\b/i;
292559
+ const RATE_LIMITED_PAST_TENSE_PATTERN = /\brate[\s-]?limit(?:ed|ing)?\b/i;
292560
+ const QUOTA_OR_USAGE_LIMIT_PATTERN = /\b(usage[ _-]limit|usage_limit_reached|quota|insufficient_quota)\b/i;
292561
+ const EXPLICIT_429_PATTERN = /\b429\b/;
292562
+ const RETRY_AFTER_SECONDS_PATTERN = /retry-after[^0-9]{0,10}(\d+)|(?:try again|retry)\s+in\s+(\d+)\s*(?:s\b|sec(?:onds?)?\b)?/i;
292563
+ function parseRetryAfterSeconds(detail) {
292564
+ const match = detail.match(RETRY_AFTER_SECONDS_PATTERN);
292565
+ if (!match) return null;
292566
+ const raw = match[1] ?? match[2];
292567
+ const seconds = raw ? Number(raw) : NaN;
292568
+ return Number.isFinite(seconds) ? seconds : null;
292569
+ }
292570
+ function classifyRetryableProviderFailure(detail) {
292571
+ if (!detail || detail.trim().length === 0) return null;
292572
+ if (QUOTA_OR_USAGE_LIMIT_PATTERN.test(detail) && !EXPLICIT_429_PATTERN.test(detail)) return {
292573
+ kind: "terminal",
292574
+ reason: "quota",
292575
+ message: detail
292576
+ };
292577
+ const isRateLimited = isProviderRateOrQuotaError(detail) || RATE_LIMITED_PAST_TENSE_PATTERN.test(detail);
292578
+ const isUnavailable = SERVICE_UNAVAILABLE_PATTERN.test(detail);
292579
+ if (!isRateLimited && !isUnavailable) return null;
292580
+ return {
292581
+ kind: "retryable",
292582
+ status: isRateLimited ? 429 : 503,
292583
+ retryAfterSeconds: parseRetryAfterSeconds(detail)
292584
+ };
292585
+ }
292586
+ //#endregion
292540
292587
  //#region src/orchestration/Layers/ProviderCommandReactor.ts
292541
292588
  function toNonEmptyProviderInput(value) {
292542
292589
  const normalized = value?.trim();
@@ -292557,6 +292604,40 @@ const HANDLED_TURN_START_KEY_MAX = 1e4;
292557
292604
  const HANDLED_TURN_START_KEY_TTL = require_Schema$1.minutes(30);
292558
292605
  const DEFAULT_RUNTIME_MODE = "full-access";
292559
292606
  const DEFAULT_THREAD_TITLE = "New thread";
292607
+ /**
292608
+ * Bounds retrying a turn-start ack (`turn/start`) that failed with a
292609
+ * retryable status (429/503). Kept well under `withProviderTurnStartTimeout`'s
292610
+ * 45 s budget: a Retry-After longer than fits here would otherwise blow that
292611
+ * timeout and end the turn as an unreplicated `provider.turn.start.timeout`
292612
+ * instead of the replicated `provider.turn.start.failed` CMMD's watchdog reads
292613
+ * (CMMD-Center/forge#3558).
292614
+ */
292615
+ const PROVIDER_RETRY_MAX_ATTEMPTS = 3;
292616
+ const PROVIDER_RETRY_BASE_DELAY = require_Schema$1.millis(100);
292617
+ /** The longest this loop will ever wait for a single attempt's Retry-After. */
292618
+ const PROVIDER_RETRY_MAX_DELAY_PER_ATTEMPT = require_Schema$1.seconds(20);
292619
+ /**
292620
+ * The longest this loop will ever sleep in total, across every attempt,
292621
+ * counted from the first failure. Two sleeps at the per-attempt cap above
292622
+ * would already reach 40s on their own, leaving nothing for the
292623
+ * `ensureSessionForThread` preamble work and the `sendTurn` round trips that
292624
+ * share `withProviderTurnStartTimeout`'s 45s budget with this loop. 30s keeps
292625
+ * 15s of headroom for that other work. A Retry-After that would need more
292626
+ * than what remains fails the turn immediately and names the real ask,
292627
+ * rather than sleeping a truncated amount and 429ing again anyway once it
292628
+ * wakes up (CMMD-Center/forge#3558 review item 4).
292629
+ */
292630
+ const PROVIDER_RETRY_TOTAL_SLEEP_BUDGET = require_Schema$1.seconds(30);
292631
+ /**
292632
+ * How long the reactor watches for `turn.started` (or a terminal failure)
292633
+ * after a turn-start ack succeeds, before treating silence as a failure.
292634
+ * Codex retries a 429 internally and reports every attempt as a bare `error`
292635
+ * notification with `willRetry`, none of which reach CMMD on their own
292636
+ * (mapped to `runtime.warning`, which does not replicate) — this is the
292637
+ * budget for those internal retries to either land or report a final failure
292638
+ * (CMMD-Center/forge#3558).
292639
+ */
292640
+ const PROVIDER_TURN_START_WATCH_BUDGET = require_Schema$1.minutes(3);
292560
292641
  const isProviderValidationError = require_Schema$1.is(ProviderValidationError);
292561
292642
  const isProviderKind = require_Schema$1.is(ProviderKind);
292562
292643
  function isPathInsideRoot(candidatePath, rootPath) {
@@ -292701,6 +292782,176 @@ function takeValidatedContinuationSession(input) {
292701
292782
  function discardOwnedContinuationSession(staged, threadId) {
292702
292783
  if (staged?.threadId === threadId) takeExactThreadContinuationSession(staged);
292703
292784
  }
292785
+ /** The message text and, when the error carries one, the provider name it named. */
292786
+ function sendTurnFailureDetail(error) {
292787
+ const message = error instanceof Error ? error.message : String(error);
292788
+ const provider = error !== null && typeof error === "object" && "provider" in error && typeof error.provider === "string" ? error.provider : void 0;
292789
+ return provider !== void 0 ? {
292790
+ message,
292791
+ provider
292792
+ } : { message };
292793
+ }
292794
+ /**
292795
+ * The delay before the next `sendTurn` attempt: the provider's own
292796
+ * Retry-After when it gave one, otherwise exponential backoff from
292797
+ * `PROVIDER_RETRY_BASE_DELAY`. Deliberately not capped here --
292798
+ * `sendTurnWithRetry` decides whether the desired delay fits the per-attempt
292799
+ * and total budgets, and fails outright rather than silently sleeping less
292800
+ * than the provider asked for (CMMD-Center/forge#3558 review item 4).
292801
+ */
292802
+ function desiredRetryDelay(input) {
292803
+ if (input.retryAfterSeconds !== null) return require_Schema$1.seconds(input.retryAfterSeconds);
292804
+ return require_Schema$1.times(PROVIDER_RETRY_BASE_DELAY, 2 ** (input.attempt - 1));
292805
+ }
292806
+ /**
292807
+ * The `provider.turn.start.failed` detail once retries are exhausted. Names
292808
+ * the provider, the status every attempt answered, how many attempts ran, and
292809
+ * the Retry-After the provider asked for, so CMMD's watchdog (and whoever
292810
+ * reads the failed Work Session) knows this was a rate limit rather than an
292811
+ * unspecified refusal.
292812
+ */
292813
+ function retryExhaustedFailureDetail(input) {
292814
+ const providerLabel = input.provider ? `The ${input.provider} provider` : "The provider";
292815
+ const retryAfter = input.classified.retryAfterSeconds !== null ? ` It asked to retry after ${input.classified.retryAfterSeconds}s.` : "";
292816
+ return `${providerLabel} responded ${input.classified.status} on every attempt (${input.attempts} of ${input.attempts}).${retryAfter} ${input.underlyingMessage}`;
292817
+ }
292818
+ /**
292819
+ * The `provider.turn.start.failed` detail when honoring the provider's own
292820
+ * Retry-After would need more time than `PROVIDER_RETRY_MAX_DELAY_PER_ATTEMPT`
292821
+ * or the loop's remaining `PROVIDER_RETRY_TOTAL_SLEEP_BUDGET` allow. Fails the
292822
+ * turn immediately and names the real ask, rather than sleeping a truncated
292823
+ * amount and 429ing again once it wakes up (CMMD-Center/forge#3558 review
292824
+ * item 4).
292825
+ */
292826
+ function retryBudgetExceededFailureDetail(input) {
292827
+ return `${input.provider ? `The ${input.provider} provider` : "The provider"} asked to retry after ${input.classified.retryAfterSeconds !== null ? `${input.classified.retryAfterSeconds}s` : "an unspecified time"}, longer than Forge allows for a turn-start retry (stopped after ${input.attempts} of ${PROVIDER_RETRY_MAX_ATTEMPTS} attempts). ${input.underlyingMessage}`;
292828
+ }
292829
+ /**
292830
+ * The `provider.turn.start.failed` detail when the turn-start ack succeeded
292831
+ * but no `turn.started`, no terminal failure, and (past the watch budget) no
292832
+ * event at all ever arrived for it — Codex retrying a 429 with no observable
292833
+ * progress until it either recovers or gives up.
292834
+ */
292835
+ function turnStartWatchTimedOutDetail(lastRetrySignal) {
292836
+ if (lastRetrySignal === null) return "The provider accepted the turn but never reported it starting, and reported no error either.";
292837
+ if (lastRetrySignal.kind === "terminal") return `The provider reported a failure that will not clear by retrying, and never started the turn. ${lastRetrySignal.message}`;
292838
+ const retryAfter = lastRetrySignal.retryAfterSeconds !== null ? ` It last asked to retry after ${lastRetrySignal.retryAfterSeconds}s.` : "";
292839
+ return `The provider kept answering ${lastRetrySignal.status} without ever starting the turn or reporting a final error.${retryAfter}`;
292840
+ }
292841
+ /**
292842
+ * The events that end the watch started after a turn-start ack: the turn
292843
+ * actually starting, that same turn completing (a Codex retry loop can settle
292844
+ * `turn/completed` with no `turn/started` in between), or a runtime error for
292845
+ * THIS turn (Codex's final `error` notification once `willRetry` turns false
292846
+ * — see `mapCodexRuntimeErrorEvent`).
292847
+ *
292848
+ * `runtime.error` matches when it carries no turn id at all (a genuine
292849
+ * session-level error) or carries this exact turn's id. This buffer starts
292850
+ * before the ack (`openTurnStartEventBuffer`), so a `runtime.error` for a
292851
+ * DIFFERENT, still-in-flight turn on the same thread is now visible to this
292852
+ * watch too; without the turn id check, that event would wrongly end this
292853
+ * turn's watch (forge-runtime-supervision's async-hold-and-refresh interleaving
292854
+ * review).
292855
+ */
292856
+ function isTurnStartWatchTerminalEvent(event, turnId) {
292857
+ if (event.type === "turn.started") return event.turnId === turnId;
292858
+ if (event.type === "turn.completed") return event.turnId === turnId;
292859
+ return event.type === "runtime.error" && (event.turnId === void 0 || event.turnId === turnId);
292860
+ }
292861
+ /**
292862
+ * Retries `sendTurn` on a retryable ack failure (429/503), with backoff
292863
+ * bounded by `PROVIDER_RETRY_MAX_ATTEMPTS`, `PROVIDER_RETRY_MAX_DELAY_PER_ATTEMPT`
292864
+ * and `PROVIDER_RETRY_TOTAL_SLEEP_BUDGET`. A non-retryable failure (401, the
292865
+ * #3552 broker-authority miss, quota exhaustion, an unrelated adapter error)
292866
+ * fails on the first attempt.
292867
+ *
292868
+ * Never resubmits after an ack succeeds: codex has already created the turn
292869
+ * by then, and a second `turn/start` would duplicate it and resend the
292870
+ * user's message. Once retries are exhausted — or the provider's own
292871
+ * Retry-After would need more time than this loop allows — the failure this
292872
+ * raises names the provider, the status, and the real Retry-After, rather
292873
+ * than whatever the underlying adapter error happened to say.
292874
+ *
292875
+ * Exported and kept at module scope (out of `ProviderCommandReactor`'s
292876
+ * `make`) so it can be tested directly against a fake clock, without a real
292877
+ * 20-30s wait (CMMD-Center/forge#3558 review item 4).
292878
+ */
292879
+ const sendTurnWithRetry = require_Schema$1.fn("sendTurnWithRetry")(function* (input) {
292880
+ let attempt = 1;
292881
+ let totalSlept = require_Schema$1.zero;
292882
+ while (true) {
292883
+ const outcome = yield* require_Schema$1.result(input.sendTurn(input.turnInput));
292884
+ if (outcome._tag === "Success") return outcome.success;
292885
+ const { message, provider } = sendTurnFailureDetail(outcome.failure);
292886
+ const classified = classifyRetryableProviderFailure(message);
292887
+ if (classified === null || classified.kind === "terminal") return yield* outcome.failure;
292888
+ const desired = desiredRetryDelay({
292889
+ attempt,
292890
+ retryAfterSeconds: classified.retryAfterSeconds
292891
+ });
292892
+ const remainingBudget = require_Schema$1.subtract(PROVIDER_RETRY_TOTAL_SLEEP_BUDGET, totalSlept);
292893
+ const exceedsBudget = require_Schema$1.isGreaterThan$1(desired, PROVIDER_RETRY_MAX_DELAY_PER_ATTEMPT) || require_Schema$1.isGreaterThan$1(desired, remainingBudget);
292894
+ if (attempt >= PROVIDER_RETRY_MAX_ATTEMPTS || exceedsBudget) return yield* new ProviderAdapterRequestError({
292895
+ provider: provider ?? "provider",
292896
+ method: "turn/start",
292897
+ detail: exceedsBudget ? retryBudgetExceededFailureDetail({
292898
+ provider,
292899
+ classified,
292900
+ attempts: attempt,
292901
+ underlyingMessage: message
292902
+ }) : retryExhaustedFailureDetail({
292903
+ provider,
292904
+ classified,
292905
+ attempts: attempt,
292906
+ underlyingMessage: message
292907
+ }),
292908
+ cause: outcome.failure
292909
+ });
292910
+ yield* require_Schema$1.sleep(desired);
292911
+ totalSlept = require_Schema$1.sum(totalSlept, desired);
292912
+ attempt += 1;
292913
+ }
292914
+ });
292915
+ /**
292916
+ * Runs the flow after a turn-start ack attempt. On a failed or skipped ack,
292917
+ * closes the event buffer and reports through `onFailure`/`onSkipped`; on a
292918
+ * successful ack, hands off to `onSent`, which forks the turn-start watch
292919
+ * that then owns the buffer for the rest of its life.
292920
+ *
292921
+ * Wrapped in its own `Effect.onInterrupt` covering `send` through `onSent`:
292922
+ * an interrupt landing anywhere in that span — mid-ack, mid the retry's own
292923
+ * backoff, or in the gap between a successful ack and the watch actually
292924
+ * being forked — still closes the buffer. Once `onSent` returns (the watch
292925
+ * has been forked), this effect has completed normally and the wrapper no
292926
+ * longer applies from there on; the watch's own `Effect.ensuring` owns the
292927
+ * buffer from that point (CodeRabbit on CMMD-Center/forge#3558: the buffer
292928
+ * never closed on a pre-fork interrupt).
292929
+ *
292930
+ * Deliberately does not release the turn-start hold: that stays the
292931
+ * caller's job. `recoverProviderTurnStartTimeout`'s `releaseHold` and
292932
+ * `processDomainEventSafely`'s own `Effect.onInterrupt` already cover every
292933
+ * path that can end this attempt (review items 3 and 7), and releasing here
292934
+ * too would let the hold go before a failure activity this same interrupt
292935
+ * is still writing has landed.
292936
+ *
292937
+ * Extracted to module scope — like `sendTurnWithRetry` above — so this
292938
+ * interrupt-safety claim can be exercised directly with `Deferred` and
292939
+ * `Fiber.interrupt`, without the full reactor test harness.
292940
+ */
292941
+ const runTurnStartAckOutcome = (input) => require_Schema$1.gen(function* () {
292942
+ const sendExit = yield* input.send.pipe(require_Schema$1.exit);
292943
+ if (require_Schema$1.isFailure(sendExit)) {
292944
+ yield* input.close;
292945
+ yield* input.onFailure(sendExit.cause);
292946
+ return;
292947
+ }
292948
+ if (sendExit.value._tag === "skipped") {
292949
+ yield* input.close;
292950
+ yield* input.onSkipped;
292951
+ return;
292952
+ }
292953
+ yield* input.onSent(sendExit.value.turnId);
292954
+ }).pipe(require_Schema$1.onInterrupt(() => input.close));
292704
292955
  function buildGeneratedWorktreeBranchName(raw) {
292705
292956
  const normalized = raw.trim().toLowerCase().replace(/^refs\/heads\//, "").replace(/['"`]/g, "");
292706
292957
  const branchFragment = (normalized.startsWith(`forge/`) ? normalized.slice(`${WORKTREE_BRANCH_PREFIX}/`.length) : normalized).replace(/[^a-z0-9/_-]+/g, "-").replace(/\/+/g, "/").replace(/-+/g, "-").replace(/^[./_-]+|[./_-]+$/g, "").slice(0, 64).replace(/[./_-]+$/g, "");
@@ -292715,12 +292966,48 @@ const make$8 = require_Schema$1.gen(function* () {
292715
292966
  const serverSettingsService = yield* ServerSettingsService;
292716
292967
  const serverConfig = yield* ServerConfig;
292717
292968
  const runtimeDrainState = yield* RuntimeDrainState;
292969
+ const spriteTaskHoldReactor = yield* require_Schema$1.serviceOption(SpriteTaskHoldReactor);
292970
+ const holdTurnStart = (key) => require_Schema$1.isSome(spriteTaskHoldReactor) ? spriteTaskHoldReactor.value.holdTurnStart(key) : require_Schema$1.void_;
292971
+ const releaseTurnStart = (key) => require_Schema$1.isSome(spriteTaskHoldReactor) ? spriteTaskHoldReactor.value.releaseTurnStart(key) : require_Schema$1.void_;
292718
292972
  const handledTurnStartKeys = yield* require_Cache.make({
292719
292973
  capacity: HANDLED_TURN_START_KEY_MAX,
292720
292974
  timeToLive: HANDLED_TURN_START_KEY_TTL,
292721
292975
  lookup: () => require_Schema$1.succeed(true)
292722
292976
  });
292723
292977
  const hasHandledTurnStartRecently = (key) => require_Cache.getOption(handledTurnStartKeys, key).pipe(require_Schema$1.flatMap((cached) => require_Cache.set(handledTurnStartKeys, key, true).pipe(require_Schema$1.as(require_Schema$1.isSome(cached)))));
292978
+ /**
292979
+ * Per-attempt buffers of `providerService.streamEvents`, keyed by the same
292980
+ * turn-start attempt key `holdTurnStart` uses — not by thread id: two
292981
+ * overlapping turn-start attempts on one thread each need their own buffer,
292982
+ * fed from before each one's own ack (see the doc comment on `holdTurnStart`
292983
+ * calls below, and forge-runtime-supervision's interleaving review).
292984
+ * `startTurnStartEventRouter` is the one place that ever reads
292985
+ * `providerService.streamEvents` for this purpose, subscribed once for the
292986
+ * reactor's whole lifetime, exactly like `SpriteTaskHoldReactor.start`, so no
292987
+ * attempt ever races to open its own subscription in time for its ack
292988
+ * (CMMD-Center/forge#3558 review item 1).
292989
+ */
292990
+ const turnStartEventBuffers = /* @__PURE__ */ new Map();
292991
+ const openTurnStartEventBuffer = (key, threadId) => require_Schema$1.gen(function* () {
292992
+ const queue = yield* require_Path.unbounded();
292993
+ turnStartEventBuffers.set(key, {
292994
+ threadId,
292995
+ queue
292996
+ });
292997
+ return queue;
292998
+ });
292999
+ const closeTurnStartEventBuffer = (key, queue) => require_Schema$1.sync(() => {
293000
+ if (turnStartEventBuffers.get(key)?.queue === queue) turnStartEventBuffers.delete(key);
293001
+ }).pipe(require_Schema$1.andThen(require_Path.shutdown(queue)));
293002
+ /**
293003
+ * Started once from `start()`. Fans out every provider event to every
293004
+ * currently-open buffer for its thread — plural, not a single lookup,
293005
+ * because two turn-start attempts can be in flight on the same thread at
293006
+ * once (CMMD-Center/forge#3558 review item 1).
293007
+ */
293008
+ const startTurnStartEventRouter = require_Schema$1.fn("startTurnStartEventRouter")(function* () {
293009
+ yield* require_Schema$1.forkScoped(require_Path.runForEach(providerService.streamEvents, (event) => require_Schema$1.forEach(Array.from(turnStartEventBuffers.values()).filter((entry) => entry.threadId === event.threadId), (entry) => require_Path.offer(entry.queue, event), { discard: true })));
293010
+ });
292724
293011
  const threadModelSelections = /* @__PURE__ */ new Map();
292725
293012
  /**
292726
293013
  * The providerAccountId each LIVE provider session was actually started with.
@@ -292877,9 +293164,87 @@ const make$8 = require_Schema$1.gen(function* () {
292877
293164
  yield* bindSessionToThread(startedSession);
292878
293165
  return startedSession.threadId;
292879
293166
  });
293167
+ /**
293168
+ * Waits for a turn-start ack to either start (or complete) or fail, after
293169
+ * the provider has already accepted it. Codex's own 429 retries surface as
293170
+ * an `error` notification with `willRetry`, mapped to `runtime.warning`
293171
+ * while retries continue and to `runtime.error` only once they stop
293172
+ * (`mapCodexRuntimeErrorEvent`); `runtime.warning` never replicates to CMMD,
293173
+ * so without this a turn that fails this way never told CMMD anything
293174
+ * happened at all (CMMD-Center/forge#3558).
293175
+ *
293176
+ * Reads from `events`, a per-attempt buffer opened by
293177
+ * `openTurnStartEventBuffer` BEFORE the ack was sent, rather than
293178
+ * subscribing to `providerService.streamEvents` directly here: a fresh
293179
+ * subscription opened only now would miss a `turn.started` published in the
293180
+ * gap between the ack and this call (review item 1).
293181
+ */
293182
+ const watchTurnStartOutcome = require_Schema$1.fn("watchTurnStartOutcome")(function* (input) {
293183
+ const lastRetrySignal = yield* require_HttpServer.make$3(null);
293184
+ const terminalEvent = yield* require_Path.fromQueue(input.events).pipe(require_Path.mapEffect((event) => require_Schema$1.gen(function* () {
293185
+ if (event.type === "runtime.warning") {
293186
+ const classified = classifyRetryableProviderFailure(event.payload.message);
293187
+ if (classified) yield* require_HttpServer.set(lastRetrySignal, classified);
293188
+ }
293189
+ return event;
293190
+ })), require_Path.filter((event) => isTurnStartWatchTerminalEvent(event, input.turnId)), require_Path.runHead).pipe(require_Schema$1.timeout(PROVIDER_TURN_START_WATCH_BUDGET), require_Schema$1.catchTag("TimeoutError", () => require_Schema$1.succeed(require_Schema$1.none())));
293191
+ if (require_Schema$1.isNone(terminalEvent)) return {
293192
+ _tag: "TimedOut",
293193
+ lastRetrySignal: yield* require_HttpServer.get$1(lastRetrySignal)
293194
+ };
293195
+ const event = terminalEvent.value;
293196
+ if (event.type === "turn.started") return { _tag: "Started" };
293197
+ if (event.type === "turn.completed") return {
293198
+ _tag: "Failed",
293199
+ detail: event.payload.errorMessage ?? "The provider ended the turn with no error detail."
293200
+ };
293201
+ return {
293202
+ _tag: "Failed",
293203
+ detail: event.payload.message
293204
+ };
293205
+ });
293206
+ /**
293207
+ * Finalizes a turn-start watch: releases the turn-start hold and closes its
293208
+ * event buffer, only AFTER the outcome is durably handled — either the turn
293209
+ * started, or the `provider.turn.start.failed` activity has actually been
293210
+ * persisted. No blanket `Effect.ensuring` for the release: if
293211
+ * `appendProviderFailureActivity` itself fails, the hold stays held rather
293212
+ * than being released with no durable record of why the turn failed
293213
+ * (review item 2). `Effect.onInterrupt` covers the one case that ordering
293214
+ * cannot: a shutdown while this watch is still waiting. A write mid-shutdown
293215
+ * is not reliable, so that path only releases the hold; the turn is left
293216
+ * for CMMD's own sweep rather than ended with an event here (review item 7).
293217
+ * The event buffer itself always closes, via `Effect.ensuring`: it is a
293218
+ * local resource with nothing durable riding on its cleanup order.
293219
+ */
293220
+ const watchTurnStartAndFinalize = (input) => require_Schema$1.gen(function* () {
293221
+ const outcome = yield* watchTurnStartOutcome({
293222
+ threadId: input.threadId,
293223
+ turnId: input.turnId,
293224
+ events: input.events
293225
+ });
293226
+ if (outcome._tag === "Started") {
293227
+ yield* releaseTurnStart(input.holdKey);
293228
+ return;
293229
+ }
293230
+ const detail = outcome._tag === "Failed" ? outcome.detail : turnStartWatchTimedOutDetail(outcome.lastRetrySignal);
293231
+ yield* appendProviderFailureActivity({
293232
+ threadId: input.threadId,
293233
+ kind: "provider.turn.start.failed",
293234
+ summary: "Provider turn start failed",
293235
+ detail: publicProviderFailureDetail(detail),
293236
+ turnId: input.turnId,
293237
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
293238
+ });
293239
+ yield* releaseTurnStart(input.holdKey);
293240
+ }).pipe(require_Schema$1.catchCause((cause) => require_Schema$1.hasInterruptsOnly(cause) ? require_Schema$1.void_ : require_Schema$1.logWarning("provider command reactor turn-start watch failed", {
293241
+ threadId: input.threadId,
293242
+ turnId: input.turnId,
293243
+ cause: require_Schema$1.pretty(cause)
293244
+ })), require_Schema$1.onInterrupt(() => releaseTurnStart(input.holdKey)), require_Schema$1.ensuring(closeTurnStartEventBuffer(input.holdKey, input.events)));
292880
293245
  const sendTurnForThread = require_Schema$1.fn("sendTurnForThread")(function* (input) {
292881
293246
  const thread = yield* resolveThread(input.threadId);
292882
- if (!thread) return;
293247
+ if (!thread) return { _tag: "skipped" };
292883
293248
  if (yield* runtimeDrainState.isDraining) {
292884
293249
  yield* appendProviderFailureActivity({
292885
293250
  threadId: input.threadId,
@@ -292889,7 +293254,7 @@ const make$8 = require_Schema$1.gen(function* () {
292889
293254
  turnId: null,
292890
293255
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
292891
293256
  });
292892
- return;
293257
+ return { _tag: "skipped" };
292893
293258
  }
292894
293259
  yield* ensureSessionForThread(input.threadId, input.createdAt, input.modelSelection !== void 0 ? { modelSelection: input.modelSelection } : {});
292895
293260
  if (input.modelSelection !== void 0) threadModelSelections.set(input.threadId, input.modelSelection);
@@ -292913,13 +293278,19 @@ const make$8 = require_Schema$1.gen(function* () {
292913
293278
  ...requestedModelSelection,
292914
293279
  model: activeSession.model
292915
293280
  } : requestedModelSelection : input.modelSelection;
292916
- yield* providerService.sendTurn({
292917
- threadId: input.threadId,
292918
- ...normalizedInput ? { input: normalizedInput } : {},
292919
- ...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
292920
- ...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
292921
- ...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {}
292922
- });
293281
+ return {
293282
+ _tag: "sent",
293283
+ turnId: (yield* sendTurnWithRetry({
293284
+ sendTurn: providerService.sendTurn,
293285
+ turnInput: {
293286
+ threadId: input.threadId,
293287
+ ...normalizedInput ? { input: normalizedInput } : {},
293288
+ ...normalizedAttachments.length > 0 ? { attachments: normalizedAttachments } : {},
293289
+ ...modelForTurn !== void 0 ? { modelSelection: modelForTurn } : {},
293290
+ ...input.interactionMode !== void 0 ? { interactionMode: input.interactionMode } : {}
293291
+ }
293292
+ })).turnId
293293
+ };
292923
293294
  });
292924
293295
  const maybeGenerateAndRenameWorktreeBranchForFirstTurn = require_Schema$1.fn("maybeGenerateAndRenameWorktreeBranchForFirstTurn")(function* (input) {
292925
293296
  if (!input.branch || !input.worktreePath) return;
@@ -293099,7 +293470,8 @@ const make$8 = require_Schema$1.gen(function* () {
293099
293470
  });
293100
293471
  });
293101
293472
  const processTurnStartRequested = require_Schema$1.fn("processTurnStartRequested")(function* (event) {
293102
- if (yield* hasHandledTurnStartRecently(turnStartKeyForEvent(event))) return;
293473
+ const key = turnStartKeyForEvent(event);
293474
+ if (yield* hasHandledTurnStartRecently(key)) return;
293103
293475
  const thread = yield* resolveThread(event.payload.threadId);
293104
293476
  if (!thread) return;
293105
293477
  const message = thread.messages.find((entry) => entry.id === event.payload.messageId);
@@ -293150,21 +293522,35 @@ const make$8 = require_Schema$1.gen(function* () {
293150
293522
  ...generationInput
293151
293523
  }).pipe(require_Schema$1.forkScoped);
293152
293524
  }
293153
- yield* sendTurnForThread({
293154
- threadId: event.payload.threadId,
293155
- messageText: message.text,
293156
- ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
293157
- ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
293158
- interactionMode: event.payload.interactionMode,
293159
- createdAt: event.payload.createdAt
293160
- }).pipe(require_Schema$1.catchCause((cause) => appendProviderFailureActivity({
293161
- threadId: event.payload.threadId,
293162
- kind: "provider.turn.start.failed",
293163
- summary: "Provider turn start failed",
293164
- detail: publicProviderFailureDetailFromCause(cause),
293165
- turnId: null,
293166
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
293167
- })));
293525
+ const turnStartHoldKey = key;
293526
+ yield* require_Schema$1.uninterruptible(holdTurnStart(turnStartHoldKey));
293527
+ const turnStartEvents = yield* openTurnStartEventBuffer(turnStartHoldKey, event.payload.threadId);
293528
+ yield* runTurnStartAckOutcome({
293529
+ send: sendTurnForThread({
293530
+ threadId: event.payload.threadId,
293531
+ messageText: message.text,
293532
+ ...message.attachments !== void 0 ? { attachments: message.attachments } : {},
293533
+ ...event.payload.modelSelection !== void 0 ? { modelSelection: event.payload.modelSelection } : {},
293534
+ interactionMode: event.payload.interactionMode,
293535
+ createdAt: event.payload.createdAt
293536
+ }),
293537
+ close: closeTurnStartEventBuffer(turnStartHoldKey, turnStartEvents),
293538
+ onFailure: (cause) => appendProviderFailureActivity({
293539
+ threadId: event.payload.threadId,
293540
+ kind: "provider.turn.start.failed",
293541
+ summary: "Provider turn start failed",
293542
+ detail: publicProviderFailureDetailFromCause(cause),
293543
+ turnId: null,
293544
+ createdAt: (/* @__PURE__ */ new Date()).toISOString()
293545
+ }).pipe(require_Schema$1.andThen(releaseTurnStart(turnStartHoldKey))),
293546
+ onSkipped: releaseTurnStart(turnStartHoldKey),
293547
+ onSent: (turnId) => watchTurnStartAndFinalize({
293548
+ threadId: event.payload.threadId,
293549
+ turnId,
293550
+ holdKey: turnStartHoldKey,
293551
+ events: turnStartEvents
293552
+ }).pipe(require_Schema$1.forkScoped)
293553
+ });
293168
293554
  });
293169
293555
  const processTurnInterruptRequested = require_Schema$1.fn("processTurnInterruptRequested")(function* (event) {
293170
293556
  const thread = yield* resolveThread(event.payload.threadId);
@@ -293386,6 +293772,7 @@ const make$8 = require_Schema$1.gen(function* () {
293386
293772
  });
293387
293773
  const recoverTimedOutProviderTurnStart = (event) => recoverProviderTurnStartTimeout({
293388
293774
  cleanup: stopTimedOutProviderSession(event),
293775
+ releaseHold: releaseTurnStart(turnStartKeyForEvent(event)),
293389
293776
  markRecoverable: appendRecoverableTurnStartTimeout(event),
293390
293777
  markCleanupFailed: (cause) => appendProviderFailureActivity({
293391
293778
  threadId: event.payload.threadId,
@@ -293398,7 +293785,7 @@ const make$8 = require_Schema$1.gen(function* () {
293398
293785
  });
293399
293786
  const processDomainEventSafely = (event) => {
293400
293787
  const operation = processDomainEvent(event);
293401
- return (event.type === "thread.turn-start-requested" ? withProviderTurnStartTimeout(operation, recoverTimedOutProviderTurnStart(event)) : operation).pipe(require_Schema$1.catchCause((cause) => {
293788
+ return (event.type === "thread.turn-start-requested" ? withProviderTurnStartTimeout(operation, recoverTimedOutProviderTurnStart(event)).pipe(require_Schema$1.onInterrupt(() => releaseTurnStart(turnStartKeyForEvent(event)))) : operation).pipe(require_Schema$1.catchCause((cause) => {
293402
293789
  if (require_Schema$1.hasInterruptsOnly(cause)) return require_Schema$1.failCause(cause);
293403
293790
  return require_Schema$1.logWarning("provider command reactor failed to process event", {
293404
293791
  eventType: event.type,
@@ -293441,6 +293828,7 @@ const make$8 = require_Schema$1.gen(function* () {
293441
293828
  if (event.type === "thread.meta-updated" && event.payload.regenerateTitle === true) return yield* titleRegenerationWorker.enqueue(event);
293442
293829
  });
293443
293830
  yield* clearInterruptedThreadTitleRegenerations().pipe(require_Schema$1.catchCause((cause) => require_Schema$1.logWarning("provider command reactor failed to sweep interrupted title regenerations", { cause: require_Schema$1.pretty(cause) })));
293831
+ yield* startTurnStartEventRouter();
293444
293832
  yield* require_Schema$1.forkScoped(require_Path.runForEach(orchestrationEngine.streamDomainEvents, processEvent));
293445
293833
  }),
293446
293834
  drain: require_Schema$1.all([worker.drain, titleRegenerationWorker.drain], {
@@ -293457,7 +293845,7 @@ function recoverProviderTurnStartTimeout(input) {
293457
293845
  return input.cleanup.pipe(require_Schema$1.matchEffect({
293458
293846
  onFailure: input.markCleanupFailed,
293459
293847
  onSuccess: () => input.markRecoverable
293460
- }));
293848
+ }), require_Schema$1.ensuring(input.releaseHold));
293461
293849
  }
293462
293850
  (() => {
293463
293851
  try {
@@ -294703,6 +295091,16 @@ const CheckpointReactorLive = require_Schema$1.effect(CheckpointReactor, make$7)
294703
295091
  const LAUNCH_HOLD_MAX_REFRESHES = 10;
294704
295092
  /** The hold id of a pending turn start. The thread names it until turn.started arrives. */
294705
295093
  const launchHoldId = (threadId) => `launch-${threadId}`;
295094
+ /**
295095
+ * Refresh budget for a turn-start hold (`holdTurnStart`): about ten minutes, matching the launch
295096
+ * hold's own budget. `ProviderCommandReactor` bounds its own retry-and-watch window well inside
295097
+ * this; the budget here is the backstop if that watch is itself interrupted (a server restart, a
295098
+ * bug) without releasing the hold.
295099
+ */
295100
+ const TURN_START_HOLD_MAX_REFRESHES = 10;
295101
+ /** The hold id for `holdTurnStart` / `releaseTurnStart`. Distinct from `launchHoldId` on purpose:
295102
+ * see the shape's doc comment on `holdTurnStart`. */
295103
+ const turnStartHoldId = (key) => `turn-start-${key}`;
294706
295104
  const make$6 = require_Schema$1.gen(function* () {
294707
295105
  const providerService = yield* ProviderService;
294708
295106
  const manager = makeSpriteTaskHoldManager(process.env, (message, cause) => {
@@ -294765,10 +295163,21 @@ const make$6 = require_Schema$1.gen(function* () {
294765
295163
  launchCallers.delete(threadId);
294766
295164
  await manager.release(launchHoldId(threadId));
294767
295165
  });
295166
+ /** Takes a turn-start hold. Registered with no thread id: see the shape's doc comment. */
295167
+ const holdTurnStart = (key) => require_Schema$1.promise(async () => {
295168
+ if (manager === null) return;
295169
+ await manager.acquire(turnStartHoldId(key), null, { maxRefreshes: TURN_START_HOLD_MAX_REFRESHES });
295170
+ });
295171
+ const releaseTurnStart = (key) => require_Schema$1.promise(async () => {
295172
+ if (manager === null) return;
295173
+ await manager.release(turnStartHoldId(key));
295174
+ });
294768
295175
  return {
294769
295176
  start,
294770
295177
  holdLaunch,
294771
295178
  releaseLaunch,
295179
+ holdTurnStart,
295180
+ releaseTurnStart,
294772
295181
  releaseAll: onManager((taskHolds) => taskHolds.releaseAll())
294773
295182
  };
294774
295183
  });
@@ -298914,7 +299323,7 @@ function resolveBuildCommitFromEnv(env) {
298914
299323
  * environment descriptor down instead of reporting an honest "unknown".
298915
299324
  */
298916
299325
  function readBakedBuildCommit() {
298917
- return "8a3582256f668ba9a5af98e78fd83977c7fd34c9";
299326
+ return "61b334c338ab47d92fe5f27155bf40ab9c7e3fed";
298918
299327
  }
298919
299328
  async function resolveServerBuildCommit(input) {
298920
299329
  if (isFullCommitSha(input.baked)) return input.baked;
@@ -322653,13 +323062,13 @@ const gitUrlCloneRouteLayer = add$1("POST", "/api/cloud/git/clone-url", require_
322653
323062
  //#endregion
322654
323063
  //#region src/server.ts
322655
323064
  const PtyAdapterLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
322656
- if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-BiTlUj63.cjs")))).layer;
322657
- else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-BJ7874MO.cjs")))).layer;
323065
+ if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunPTY-DkR9y_Jg.cjs")))).layer;
323066
+ else return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodePTY-DkaJNgpi.cjs")))).layer;
322658
323067
  }));
322659
323068
  const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
322660
323069
  const config = yield* ServerConfig;
322661
- if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-CXndtc1j.cjs")))).layer(bunHttpServerOptions(config));
322662
- const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-B4dCKqYY.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
323070
+ if (typeof Bun !== "undefined") return (yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunHttpServer-DTG5XWSi.cjs")))).layer(bunHttpServerOptions(config));
323071
+ const [NodeHttpServer, NodeHttp] = yield* require_Schema$1.all([require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeHttpServer-RV5QsVeb.cjs"))), require_Schema$1.promise(() => import("node:http"))]);
322663
323072
  return NodeHttpServer.layer(NodeHttp.createServer, {
322664
323073
  host: config.host,
322665
323074
  port: config.port
@@ -322667,10 +323076,10 @@ const HttpServerLive = require_Schema$1.unwrap(require_Schema$1.gen(function* ()
322667
323076
  }));
322668
323077
  const PlatformServicesLive = require_Schema$1.unwrap(require_Schema$1.gen(function* () {
322669
323078
  if (typeof Bun !== "undefined") {
322670
- const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-DsWG7VM1.cjs")));
323079
+ const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./BunServices-blx5Lx2N.cjs")));
322671
323080
  return layer;
322672
323081
  } else {
322673
- const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-BvdIc9Vh.cjs")));
323082
+ const { layer } = yield* require_Schema$1.promise(() => Promise.resolve().then(() => require("./NodeServices-CocSeCvl.cjs")));
322674
323083
  return layer;
322675
323084
  }
322676
323085
  }));