@coreplane/switchboard 1.260.4 → 1.260.5

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 (50) hide show
  1. package/dist/assets/deploy/cloudflare/package.json +2 -2
  2. package/dist/assets/deploy/cloudflare-docs/package.json +1 -1
  3. package/dist/assets/deploy/cloudflare-memory/package.json +2 -2
  4. package/dist/assets/deploy/cloudflare-memory/worker.ts +205 -8
  5. package/dist/assets/deploy/cloudflare-resident/package.json +2 -2
  6. package/dist/assets/deploy/cloudflare-resident/worker.ts +124 -40
  7. package/dist/assets/deploy/cloudflare-sandbox/package.json +2 -2
  8. package/dist/assets/package-lock.json +712 -97
  9. package/dist/assets/package.json +4 -4
  10. package/dist/assets/source.json +3 -3
  11. package/dist/assets/src/core/budgets.ts +5 -0
  12. package/dist/assets/src/core/coordinator/contract.ts +5 -0
  13. package/dist/assets/src/core/coordinator/driver.ts +114 -25
  14. package/dist/assets/src/core/pipelineStanding.ts +2 -0
  15. package/dist/assets/src/core/plane/decide.ts +19 -2
  16. package/dist/assets/src/core/provider.ts +380 -0
  17. package/dist/assets/src/core/refusal.ts +1 -0
  18. package/dist/assets/src/core/runEvents.ts +8 -0
  19. package/dist/assets/src/core/runLedger/types.ts +10 -1
  20. package/dist/assets/src/core/runRecord.ts +31 -0
  21. package/dist/assets/src/core/ship/contract.ts +2 -0
  22. package/dist/assets/src/core/ship/coordinator.ts +487 -25
  23. package/dist/assets/src/core/types.ts +6 -0
  24. package/dist/assets/src/execution/residentRebind.ts +15 -0
  25. package/dist/assets/src/execution/residentReuse.ts +23 -2
  26. package/dist/assets/web/dist/.vite/manifest.json +67 -67
  27. package/dist/assets/web/dist/assets/{DeliveryPage-BaemWKW8.js → DeliveryPage-Cf1joUyA.js} +1 -1
  28. package/dist/assets/web/dist/assets/{HomePage-fgKQHLaO.js → HomePage-DZEAnCCv.js} +1 -1
  29. package/dist/assets/web/dist/assets/{PendingTurnRow-vuWiDzvz.js → PendingTurnRow-4B8JwETU.js} +1 -1
  30. package/dist/assets/web/dist/assets/{PlanePage-DXMJf8FB.js → PlanePage-CHU2szQs.js} +1 -1
  31. package/dist/assets/web/dist/assets/{ResidentDetailPage-DdC8yU_S.js → ResidentDetailPage-L5SRcN_O.js} +1 -1
  32. package/dist/assets/web/dist/assets/{ResidentsIndexPage-CSM-KODv.js → ResidentsIndexPage-BIFf8qyE.js} +1 -1
  33. package/dist/assets/web/dist/assets/RunFoldRow-DzPIshJx.js +1 -0
  34. package/dist/assets/web/dist/assets/{RunRoutePage-jo6NZxsP.js → RunRoutePage-Cru5IVux.js} +3 -3
  35. package/dist/assets/web/dist/assets/{RunsIndexPage-Dx1Y4sSq.js → RunsIndexPage-CBkdPAdj.js} +1 -1
  36. package/dist/assets/web/dist/assets/{ScheduledPage-BuB_NZyR.js → ScheduledPage-D_R55BDk.js} +1 -1
  37. package/dist/assets/web/dist/assets/{SettingsPage-Cn96jB6Q.js → SettingsPage-DlmZvAvf.js} +1 -1
  38. package/dist/assets/web/dist/assets/{SilentTurn-D2cJfpEq.js → SilentTurn-PWMDMbMG.js} +1 -1
  39. package/dist/assets/web/dist/assets/{StatusDot-DDHUCPVJ.js → StatusDot-Bq0mYMFo.js} +1 -1
  40. package/dist/assets/web/dist/assets/{Tooltip-B4VIHkAI.js → Tooltip-Bs6zd7yE.js} +1 -1
  41. package/dist/assets/web/dist/assets/{UnitRoutePage-C-2DfjLI.js → UnitRoutePage-BMGEZCvn.js} +1 -1
  42. package/dist/assets/web/dist/assets/budgets-0szUBIMv.js +1 -0
  43. package/dist/assets/web/dist/assets/{dist-Cf1CmCSK.js → dist-EkKVbIM-.js} +11 -11
  44. package/dist/assets/web/dist/assets/{indexRow-Dxg7C8s9.js → indexRow-5s3hNzZ9.js} +1 -1
  45. package/dist/assets/web/dist/assets/{main-DxPqaFu9.js → main-Disot0n_.js} +2 -2
  46. package/dist/assets/web/dist/assets/{sseReplay-BzoeVxZB.js → sseReplay-9gcMpylx.js} +1 -1
  47. package/dist/cli.js +1855 -401
  48. package/package.json +4 -4
  49. package/dist/assets/web/dist/assets/RunFoldRow-Csik3wD6.js +0 -1
  50. package/dist/assets/web/dist/assets/budgets-KNNkT4PZ.js +0 -1
package/dist/cli.js CHANGED
@@ -597,7 +597,7 @@ function fit(pipeline) {
597
597
  const need = ALLOWANCES.provision + ASKS.coding + reserveMinutes({ kind: "coding", index: 0 }, pipeline);
598
598
  return { ok: pipeline.maxMinutes >= need, need, have: pipeline.maxMinutes };
599
599
  }
600
- var SECOND_MS, secondsToMs, MINUTE_MS, minutesToMs, DAY_MS, WEEK_MS, CONFIRMATION_TTL_MS, QUESTION_TTL_MS, AUTHOR_BINDING_TTL_MS, FIRST_ATTACH_WAIT_MS, ATTACH_REQUEST_MIN_MS, HARNESS_PROBE_WAIT_MS, DRAIN, INTAKE_WINDOW_MAX_MS, INTAKE_RECOVERY_WINDOW_MS, LOOP_PRESETS, ASKS, PRESET_FLOORS, FLOORS, PULL_SWEEP, PROVIDER_RETRY_BACKOFFS_MS, MODEL_STREAM_HEARTBEAT_MS, HOSTED_DEADLINE_MARGIN_MINUTES, SHIP_WAIT, RESTART_CLAIM_GRACE_MS, PLANE, PACE_WINDOW_MINUTES, ALLOWANCES, POST_STEP_MINUTES, WRAP_UP_WARNING, SANDBOX_CREDENTIAL_WRITE_TIMEOUT_MS, BASH_COMMAND, RUNAWAY_TURNS_PER_MINUTE, DEFAULT_GRANT, GRANT_RENEWALS_MAX, STRUCTURED_RETRIES_MAX, IDLE_DAYS_DEFAULT, IDLE_DAYS_MAX, IDLE_WAKES_MAX;
600
+ var SECOND_MS, secondsToMs, MINUTE_MS, minutesToMs, DAY_MS, WEEK_MS, CONFIRMATION_TTL_MS, QUESTION_TTL_MS, AUTHOR_BINDING_TTL_MS, FIRST_ATTACH_WAIT_MS, ATTACH_REQUEST_MIN_MS, HARNESS_PROBE_WAIT_MS, DRAIN, INTAKE_WINDOW_MAX_MS, INTAKE_RECOVERY_WINDOW_MS, INTAKE_DELIVERY_CLAIM_MS, LOOP_PRESETS, ASKS, PRESET_FLOORS, FLOORS, PULL_SWEEP, PROVIDER_RETRY_BACKOFFS_MS, MODEL_STREAM_HEARTBEAT_MS, HOSTED_DEADLINE_MARGIN_MINUTES, SHIP_WAIT, RESTART_CLAIM_GRACE_MS, PLANE, PACE_WINDOW_MINUTES, ALLOWANCES, POST_STEP_MINUTES, WRAP_UP_WARNING, SANDBOX_CREDENTIAL_WRITE_TIMEOUT_MS, BASH_COMMAND, RUNAWAY_TURNS_PER_MINUTE, DEFAULT_GRANT, GRANT_RENEWALS_MAX, STRUCTURED_RETRIES_MAX, IDLE_DAYS_DEFAULT, IDLE_DAYS_MAX, IDLE_WAKES_MAX;
601
601
  var init_budgets = __esm({
602
602
  "../../src/core/budgets.ts"() {
603
603
  "use strict";
@@ -633,6 +633,7 @@ var init_budgets = __esm({
633
633
  };
634
634
  INTAKE_WINDOW_MAX_MS = 30 * DAY_MS;
635
635
  INTAKE_RECOVERY_WINDOW_MS = 10 * MINUTE_MS;
636
+ INTAKE_DELIVERY_CLAIM_MS = 5 * MINUTE_MS;
636
637
  LOOP_PRESETS = [
637
638
  "general",
638
639
  "coding",
@@ -1648,13 +1649,13 @@ var init_configDocument = __esm({
1648
1649
  describe() {
1649
1650
  return `state Worker ${this.baseUrl}`;
1650
1651
  }
1651
- async post(path, payload) {
1652
+ async post(path, payload2) {
1652
1653
  let res;
1653
1654
  try {
1654
1655
  res = await this.fetchImpl(`${this.baseUrl}${path}`, {
1655
1656
  method: "POST",
1656
1657
  headers: { authorization: `Bearer ${this.opts.token}`, "content-type": "application/json" },
1657
- body: JSON.stringify(payload),
1658
+ body: JSON.stringify(payload2),
1658
1659
  signal: AbortSignal.timeout(this.timeoutMs)
1659
1660
  });
1660
1661
  } catch (err2) {
@@ -2821,6 +2822,230 @@ var init_effort = __esm({
2821
2822
  });
2822
2823
 
2823
2824
  // ../../src/core/provider.ts
2825
+ function providerFailureDiagnostic(cause, details2) {
2826
+ if (details2.provider !== void 0 && details2.keyVariable !== void 0) {
2827
+ if (cause === "key-absent") return `Provider "${details2.provider}": ${details2.keyVariable} is not set`;
2828
+ if (cause === "key-invalid") return `Provider "${details2.provider}": ${details2.keyVariable} was refused`;
2829
+ }
2830
+ return renderProviderFailure(cause);
2831
+ }
2832
+ function renderProviderFailure(cause, surface = "ended") {
2833
+ switch (cause) {
2834
+ case "transient":
2835
+ return surface === "parked" ? "The model provider is temporarily unavailable; your work is kept and will continue when service recovers." : "The model provider is temporarily unavailable; this request did not start.";
2836
+ case "rate-limited":
2837
+ return surface === "parked" ? "The model provider is rate-limited; your work is kept and will continue when capacity returns." : "The model provider is rate-limited; this request did not start.";
2838
+ case "credit-or-quota-exhausted":
2839
+ return surface === "parked" ? "The model provider's credit or quota is exhausted; your work is kept and will continue when service recovers." : "The model provider's credit or quota is exhausted; this request did not start.";
2840
+ case "key-absent":
2841
+ return "The model provider key is not configured; this request cannot start until the service is restored.";
2842
+ case "key-invalid":
2843
+ return "The model provider key was refused; this request cannot start until the service is restored.";
2844
+ case "model-unknown":
2845
+ return "The configured model is unavailable from its provider; this request cannot start until the service is restored.";
2846
+ case "request-rejected":
2847
+ return "The model provider rejected the request shape; no work was started.";
2848
+ case "permanent":
2849
+ return "The model provider refused the call; the request ended without exposing the provider's response.";
2850
+ }
2851
+ }
2852
+ function providerSchemaRejectionRecord(value) {
2853
+ const row2 = providerFailureRecord(value);
2854
+ return typeof row2?.tool === "string" && row2.tool.length > 0 && typeof row2.keyword === "string" && row2.keyword.length > 0 ? { tool: row2.tool, keyword: row2.keyword } : void 0;
2855
+ }
2856
+ function providerFailureBody(body) {
2857
+ if (typeof body !== "string") return body;
2858
+ const text = body.trim();
2859
+ try {
2860
+ return JSON.parse(text);
2861
+ } catch {
2862
+ const at = text.indexOf("{");
2863
+ if (at < 0) return void 0;
2864
+ try {
2865
+ return JSON.parse(text.slice(at));
2866
+ } catch {
2867
+ return void 0;
2868
+ }
2869
+ }
2870
+ }
2871
+ function providerFailureSignals(value, into = []) {
2872
+ if (Array.isArray(value)) {
2873
+ for (const item of value) providerFailureSignals(item, into);
2874
+ return into;
2875
+ }
2876
+ const row2 = providerFailureRecord(value);
2877
+ if (!row2) return into;
2878
+ for (const [key, item] of Object.entries(row2)) {
2879
+ const normalizedKey = key.toLowerCase();
2880
+ if (typeof item === "string" && ["code", "type", "error_type", "reason", "limit_source"].includes(normalizedKey))
2881
+ into.push(item.toLowerCase());
2882
+ else if (typeof item === "number" && normalizedKey === "code") into.push(String(item));
2883
+ providerFailureSignals(item, into);
2884
+ }
2885
+ return into;
2886
+ }
2887
+ function providerFailureStatus(input, text) {
2888
+ if (input.status !== void 0) return input.status;
2889
+ const match = /(?:^|\b(?:http|status(?: code)?|error|api error)[^0-9]{0,8})([1-5]\d\d)\b/i.exec(text);
2890
+ return match ? Number(match[1]) : void 0;
2891
+ }
2892
+ function providerFailureText(input) {
2893
+ const error = input.error;
2894
+ if (error instanceof Error) return error.message;
2895
+ if (typeof error === "string") return error;
2896
+ if (typeof input.body === "string") return input.body;
2897
+ try {
2898
+ return input.body === void 0 ? "" : JSON.stringify(input.body);
2899
+ } catch {
2900
+ return "";
2901
+ }
2902
+ }
2903
+ function quotedWord(text, word) {
2904
+ return [`'${word}'`, `"${word}"`, `\`${word}\``].some((candidate) => text.includes(candidate));
2905
+ }
2906
+ function toolSchemaKeywords(value, into = /* @__PURE__ */ new Set()) {
2907
+ if (Array.isArray(value)) {
2908
+ for (const item of value) toolSchemaKeywords(item, into);
2909
+ return into;
2910
+ }
2911
+ const schema = providerFailureRecord(value);
2912
+ if (!schema) return into;
2913
+ for (const [keyword, item] of Object.entries(schema)) {
2914
+ into.add(keyword);
2915
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && providerFailureRecord(item)) {
2916
+ for (const child of Object.values(providerFailureRecord(item))) toolSchemaKeywords(child, into);
2917
+ } else {
2918
+ toolSchemaKeywords(item, into);
2919
+ }
2920
+ }
2921
+ return into;
2922
+ }
2923
+ function providerSchemaRejectionOf(error, tools) {
2924
+ if (!tools || tools.length === 0) return void 0;
2925
+ const text = providerFailureText({ error });
2926
+ const parsed = providerFailureBody(providerFailureRecord(error) ?? text);
2927
+ const prose2 = providerFailureProse(parsed ?? text);
2928
+ if (!/(?:invalid|unsupported|refused|rejected)[^.!?]{0,80}(?:json )?schema|(?:json )?schema[^.!?]{0,80}(?:invalid|unsupported|not (?:permitted|supported))/i.test(
2929
+ prose2
2930
+ ))
2931
+ return void 0;
2932
+ const namedTools = tools.filter((tool2) => quotedWord(prose2, tool2.name));
2933
+ if (namedTools.length !== 1) return void 0;
2934
+ const [tool] = namedTools;
2935
+ const namedKeywords = [...toolSchemaKeywords(tool.inputSchema)].filter((keyword) => quotedWord(prose2, keyword));
2936
+ if (namedKeywords.length !== 1) return void 0;
2937
+ return { tool: tool.name, keyword: namedKeywords[0] };
2938
+ }
2939
+ function providerFailureProse(value, into = []) {
2940
+ if (typeof value === "string") {
2941
+ into.push(value);
2942
+ return into.join(" ");
2943
+ }
2944
+ if (Array.isArray(value)) {
2945
+ for (const item of value) providerFailureProse(item, into);
2946
+ return into.join(" ");
2947
+ }
2948
+ const row2 = providerFailureRecord(value);
2949
+ if (!row2) return into.join(" ");
2950
+ for (const [key, item] of Object.entries(row2)) {
2951
+ if (key.toLowerCase() !== "cause") providerFailureProse(item, into);
2952
+ }
2953
+ return into.join(" ");
2954
+ }
2955
+ function signalIncludes(signals, words) {
2956
+ return signals.some((signal2) => words.some((word) => signal2 === word || signal2.includes(word)));
2957
+ }
2958
+ function isProviderFailureCause(value) {
2959
+ return typeof value === "string" && PROVIDER_FAILURE_CAUSES.includes(value);
2960
+ }
2961
+ function trustedProviderFailureEnvelope(value) {
2962
+ if (Array.isArray(value)) {
2963
+ for (const item of value) {
2964
+ const envelope = trustedProviderFailureEnvelope(item);
2965
+ if (envelope !== void 0) return envelope;
2966
+ }
2967
+ return void 0;
2968
+ }
2969
+ const row2 = providerFailureRecord(value);
2970
+ if (!row2) return void 0;
2971
+ if (row2.type === "provider_failure" && isProviderFailureCause(row2.cause)) {
2972
+ const schemaRejection = providerSchemaRejectionRecord(row2.schemaRejection);
2973
+ return { cause: row2.cause, ...schemaRejection !== void 0 ? { schemaRejection } : {} };
2974
+ }
2975
+ for (const item of Object.values(row2)) {
2976
+ const envelope = trustedProviderFailureEnvelope(item);
2977
+ if (envelope !== void 0) return envelope;
2978
+ }
2979
+ return void 0;
2980
+ }
2981
+ function classifyProviderFailure(input) {
2982
+ if (input.error instanceof ProviderFailure) return input.error;
2983
+ const text = providerFailureText(input);
2984
+ const parsed = providerFailureBody(input.body ?? providerFailureRecord(input.error) ?? text);
2985
+ const signals = providerFailureSignals(parsed);
2986
+ const trusted = input.trustedEnvelope === true ? trustedProviderFailureEnvelope(parsed) : void 0;
2987
+ const status3 = providerFailureStatus(input, text);
2988
+ const unstructuredText = input.body !== void 0 && (providerFailureRecord(parsed) !== void 0 || Array.isArray(parsed)) ? providerFailureProse(parsed) : text;
2989
+ const transientTransport = status3 === 408 || status3 === 425 || input.error instanceof Error && (input.error.name === "AbortError" || input.error.name === "TimeoutError") || status3 !== void 0 && status3 >= 500 || /stream ended before message_stop|stream ended without finish_reason|ended before completion/i.test(
2990
+ unstructuredText
2991
+ ) || /^(?:(?:AbortError:\s*)?(?:This|The) operation was aborted|Request aborted)\.?$/i.test(unstructuredText.trim()) || /ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|fetch failed|other side closed|network (?:error|failure)|(?:connection|stream) (?:reset|closed|terminated)|timed? ?out/i.test(
2992
+ unstructuredText
2993
+ ) || /^terminated$/i.test(unstructuredText.trim()) || /\boverloaded\b/i.test(unstructuredText) || /<html[\s>][\s\S]{0,4000}\b(?:bad gateway|service unavailable|gateway timeout)\b/i.test(unstructuredText);
2994
+ let cause;
2995
+ if (trusted !== void 0) cause = trusted.cause;
2996
+ else if (status3 === 402) cause = "credit-or-quota-exhausted";
2997
+ else if (status3 === 429) cause = "rate-limited";
2998
+ else if (transientTransport) cause = "transient";
2999
+ else if (signalIncludes(signals, [
3000
+ "limit_source",
3001
+ "insufficient_quota",
3002
+ "quota_exceeded",
3003
+ "credit_limit",
3004
+ "billing_hard_limit",
3005
+ "payment_required"
3006
+ ]) || providerFailureRecord(parsed)?.metadata !== void 0 && providerFailureRecord(providerFailureRecord(parsed)?.metadata)?.limit_source !== void 0)
3007
+ cause = "credit-or-quota-exhausted";
3008
+ else if (signalIncludes(signals, ["rate_limit", "too_many_requests"]) || /\brate[- ]limit(?:ed)?\b/i.test(unstructuredText))
3009
+ cause = "rate-limited";
3010
+ else if (signalIncludes(signals, ["provider_key_missing", "key_absent", "missing_api_key"])) cause = "key-absent";
3011
+ else if (status3 === 401 || signalIncludes(signals, ["authentication_error", "invalid_api_key", "invalid_key", "unauthorized"]))
3012
+ cause = "key-invalid";
3013
+ else if (signalIncludes(signals, ["model_not_found", "unknown_model", "model_unknown"])) cause = "model-unknown";
3014
+ else if (status3 === 400 || status3 === 422 || signalIncludes(signals, ["invalid_request_error", "invalid_json_schema", "bad_request", "unprocessable_entity"]))
3015
+ cause = "request-rejected";
3016
+ else cause = "permanent";
3017
+ const operatorUrl = /https?:\/\/[^\s"'<>]+/.exec(text)?.[0];
3018
+ return new ProviderFailure(cause, {
3019
+ ...status3 !== void 0 ? { status: status3 } : {},
3020
+ ...input.provider !== void 0 ? { provider: input.provider } : {},
3021
+ ...input.model !== void 0 ? { model: input.model } : {},
3022
+ ...operatorUrl !== void 0 ? { operatorUrl } : {},
3023
+ ...trusted?.schemaRejection !== void 0 ? { schemaRejection: trusted.schemaRejection } : {}
3024
+ });
3025
+ }
3026
+ function typedProviderFailureOf(error, seen = /* @__PURE__ */ new Set()) {
3027
+ if (error instanceof ProviderFailure) return error;
3028
+ const row2 = providerFailureRecord(error);
3029
+ if (!row2 || seen.has(row2)) return void 0;
3030
+ seen.add(row2);
3031
+ if (row2.name === "ProviderFailure" && isProviderFailureCause(row2.cause)) {
3032
+ return new ProviderFailure(row2.cause, {
3033
+ ...typeof row2.status === "number" ? { status: row2.status } : {},
3034
+ ...typeof row2.provider === "string" ? { provider: row2.provider } : {},
3035
+ ...typeof row2.model === "string" ? { model: row2.model } : {},
3036
+ ...typeof row2.operatorUrl === "string" ? { operatorUrl: row2.operatorUrl } : {},
3037
+ ...typeof row2.keyVariable === "string" ? { keyVariable: row2.keyVariable } : {},
3038
+ ...providerSchemaRejectionRecord(row2.schemaRejection) !== void 0 ? { schemaRejection: providerSchemaRejectionRecord(row2.schemaRejection) } : {}
3039
+ });
3040
+ }
3041
+ return typedProviderFailureOf(row2.cause, seen);
3042
+ }
3043
+ function providerFailureOf(error) {
3044
+ return typedProviderFailureOf(error) ?? classifyProviderFailure({ error });
3045
+ }
3046
+ function providerFailureParks(cause) {
3047
+ return cause === "transient" || cause === "rate-limited" || cause === "credit-or-quota-exhausted";
3048
+ }
2824
3049
  function wireOf(block) {
2825
3050
  return block.wire ?? WIRE_ALIASES[block.type] ?? "openai-chat";
2826
3051
  }
@@ -2859,10 +3084,41 @@ function parseModelRef(ref) {
2859
3084
  }
2860
3085
  return { provider: ref.slice(0, i), model: ref.slice(i + 1) };
2861
3086
  }
2862
- var WIRES, WIRE_ALIASES, INVOICE_APIS, BILLER_HARNESS_PROVIDERS, ANTHROPIC_API_KEY_ENV;
3087
+ var PROVIDER_FAILURE_CAUSES, ProviderFailure, providerFailureRecord, WIRES, WIRE_ALIASES, INVOICE_APIS, BILLER_HARNESS_PROVIDERS, ANTHROPIC_API_KEY_ENV;
2863
3088
  var init_provider = __esm({
2864
3089
  "../../src/core/provider.ts"() {
2865
3090
  "use strict";
3091
+ PROVIDER_FAILURE_CAUSES = [
3092
+ "transient",
3093
+ "rate-limited",
3094
+ "credit-or-quota-exhausted",
3095
+ "key-absent",
3096
+ "key-invalid",
3097
+ "model-unknown",
3098
+ "request-rejected",
3099
+ "permanent"
3100
+ ];
3101
+ ProviderFailure = class extends Error {
3102
+ name = "ProviderFailure";
3103
+ cause;
3104
+ status;
3105
+ provider;
3106
+ model;
3107
+ operatorUrl;
3108
+ keyVariable;
3109
+ schemaRejection;
3110
+ constructor(cause, details2 = {}) {
3111
+ super(providerFailureDiagnostic(cause, details2));
3112
+ this.cause = cause;
3113
+ this.status = details2.status;
3114
+ this.provider = details2.provider;
3115
+ this.model = details2.model;
3116
+ this.operatorUrl = details2.operatorUrl;
3117
+ this.keyVariable = details2.keyVariable;
3118
+ this.schemaRejection = details2.schemaRejection;
3119
+ }
3120
+ };
3121
+ providerFailureRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
2866
3122
  WIRES = ["anthropic-messages", "openai-chat", "openai-responses"];
2867
3123
  WIRE_ALIASES = {
2868
3124
  anthropic: "anthropic-messages",
@@ -3285,7 +3541,7 @@ function whereCell(f, target) {
3285
3541
  }
3286
3542
  function verdictMarker(verdict, target) {
3287
3543
  const head = target?.head ?? verdict?.head;
3288
- const payload = {
3544
+ const payload2 = {
3289
3545
  verdict: verdict?.verdict ?? "none",
3290
3546
  ...head ? { head } : {},
3291
3547
  ...verdict?.findings ? {
@@ -3299,7 +3555,7 @@ function verdictMarker(verdict, target) {
3299
3555
  }))
3300
3556
  } : {}
3301
3557
  };
3302
- return `<!-- switchboard:verdict ${JSON.stringify(payload).replace(/-->/g, "--\\u003e")} -->`;
3558
+ return `<!-- switchboard:verdict ${JSON.stringify(payload2).replace(/-->/g, "--\\u003e")} -->`;
3303
3559
  }
3304
3560
  function buildReviewPostBody(answer, verdict, target) {
3305
3561
  const facts2 = [
@@ -4756,11 +5012,11 @@ function analyzeRunFriction(events, opts = {}) {
4756
5012
  if (promised) {
4757
5013
  const line4 = answer.text.slice(0, promised.index).split("\n").pop() + answer.text.slice(promised.index).split("\n")[0];
4758
5014
  const trimmed2 = line4.trim();
4759
- const quoted = trimmed2.length > 120 ? `\u2026${trimmed2.slice(trimmed2.length - 119)}` : trimmed2;
5015
+ const quoted2 = trimmed2.length > 120 ? `\u2026${trimmed2.slice(trimmed2.length - 119)}` : trimmed2;
4760
5016
  findings.push({
4761
5017
  category: "unkept_promise",
4762
5018
  severity: "low",
4763
- summary: `unkept promise: the reply says a file is attached but the run produced none \u2014 "${quoted}"`,
5019
+ summary: `unkept promise: the reply says a file is attached but the run produced none \u2014 "${quoted2}"`,
4764
5020
  eventIndex: answer.index
4765
5021
  });
4766
5022
  }
@@ -5507,6 +5763,7 @@ function isCoordinatorUnit(v) {
5507
5763
  if (r.issue !== void 0 && !isFinite(r.issue)) return false;
5508
5764
  if (r.pr !== void 0 && !isPr(r.pr)) return false;
5509
5765
  if (r.resume !== void 0 && !isResume(r.resume)) return false;
5766
+ if (r.record !== void 0 && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
5510
5767
  if (r.lastPush !== void 0 && !isText(r.lastPush)) return false;
5511
5768
  if (r.segments !== void 0 && (!Array.isArray(r.segments) || !r.segments.every(isSegment))) return false;
5512
5769
  if (r.idle !== void 0 && !isUnitIdle(r.idle)) return false;
@@ -5536,22 +5793,22 @@ async function sendUnitNudge(workflow, key) {
5536
5793
  async function sendChecksSettled(workflow, instance, headSha, settledAt) {
5537
5794
  if (!workflow) return { kind: "no-binding", instance };
5538
5795
  const type = checksSettledEventType(headSha);
5539
- const payload = { headSha, settledAt };
5796
+ const payload2 = { headSha, settledAt };
5540
5797
  try {
5541
5798
  const handle2 = await workflow.get(instance);
5542
- await handle2.sendEvent({ type, payload });
5799
+ await handle2.sendEvent({ type, payload: payload2 });
5543
5800
  return { kind: "sent", instance, type };
5544
5801
  } catch (err2) {
5545
5802
  return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
5546
5803
  }
5547
5804
  }
5548
- async function sendPullMerged(workflow, key, payload) {
5805
+ async function sendPullMerged(workflow, key, payload2) {
5549
5806
  const instance = key.instanceId;
5550
5807
  if (!workflow) return { kind: "no-binding", instance };
5551
5808
  const type = pullMergedEventType(key);
5552
5809
  try {
5553
5810
  const handle2 = await workflow.get(instance);
5554
- await handle2.sendEvent({ type, payload });
5811
+ await handle2.sendEvent({ type, payload: payload2 });
5555
5812
  return { kind: "sent", instance, type };
5556
5813
  } catch (err2) {
5557
5814
  return { kind: "failed", instance, type, reason: err2 instanceof Error ? err2.message : String(err2) };
@@ -5785,6 +6042,7 @@ var init_pipelineStanding = __esm({
5785
6042
  };
5786
6043
  ENDING_STAGE = {
5787
6044
  merged: "merged",
6045
+ closed: "ended",
5788
6046
  already_landed: "merged",
5789
6047
  merge_ready: "merge-ready",
5790
6048
  held: "ended",
@@ -5806,6 +6064,7 @@ var init_pipelineStanding = __esm({
5806
6064
  };
5807
6065
  ENDING_WORDS = {
5808
6066
  merged: "merged",
6067
+ closed: "closed",
5809
6068
  already_landed: "merged",
5810
6069
  merge_ready: "merge-ready",
5811
6070
  held: "held",
@@ -5865,6 +6124,16 @@ function instanceIdOfEvents(events) {
5865
6124
  for (const e of events) if (e.type === "ship_handoff") id = e.instanceId;
5866
6125
  return id;
5867
6126
  }
6127
+ function decisionRecordOfEvents(events) {
6128
+ let reservation;
6129
+ for (const event of events)
6130
+ if (event.type === "run_meta" && event.record !== void 0)
6131
+ reservation = {
6132
+ record: event.record,
6133
+ ...event.recordTaskKey !== void 0 ? { recordTaskKey: event.recordTaskKey } : {}
6134
+ };
6135
+ return reservation;
6136
+ }
5868
6137
  function prOfEvents(events) {
5869
6138
  let pr;
5870
6139
  for (const e of events) {
@@ -5939,6 +6208,7 @@ function operatorOfEvents(events) {
5939
6208
  ...e.request !== void 0 ? { request: e.request } : {},
5940
6209
  ...e.refusalCause !== void 0 ? { refusalCause: e.refusalCause } : {},
5941
6210
  ...e.refusalText !== void 0 ? { refusalText: e.refusalText } : {},
6211
+ ...e.providerFailure !== void 0 ? { providerFailure: e.providerFailure } : {},
5942
6212
  ...e.attempts ? {
5943
6213
  attempts: e.attempts.map((a) => ({
5944
6214
  outcome: a.outcome,
@@ -6150,6 +6420,10 @@ function isRunRecord(v) {
6150
6420
  if (!isOptionalString(r.label) || !isOptionalString(r.agent) || !isOptionalString(r.model) || !isOptionalString(r.repo))
6151
6421
  return false;
6152
6422
  if (!isOptionalString(r.activity) || !isOptionalString(r.sourceUrl) || !isOptionalString(r.userName)) return false;
6423
+ if (r.record !== void 0 && (typeof r.record !== "string" || !/^\d{4}$/.test(r.record))) return false;
6424
+ if (r.recordTaskKey !== void 0 && (typeof r.recordTaskKey !== "string" || !/^[0-9a-f]{16}$/.test(r.recordTaskKey)))
6425
+ return false;
6426
+ if (r.headSha !== void 0 && (typeof r.headSha !== "string" || !REVIEW_HEAD_PATTERN.test(r.headSha))) return false;
6153
6427
  if (r.handoff !== void 0 && !isHandoffShape(r.handoff)) return false;
6154
6428
  if (r.verdict !== void 0 && !isReviewVerdictShape(r.verdict)) return false;
6155
6429
  if (r.reviewHead !== void 0 && (typeof r.reviewHead !== "string" || !REVIEW_HEAD_PATTERN.test(r.reviewHead)))
@@ -8335,13 +8609,13 @@ var init_config2 = __esm({
8335
8609
  describe() {
8336
8610
  return `state Worker ${this.baseUrl} (ConfigDO)`;
8337
8611
  }
8338
- async post(path, payload) {
8612
+ async post(path, payload2) {
8339
8613
  let res;
8340
8614
  try {
8341
8615
  res = await this.fetchImpl(`${this.baseUrl}${path}`, {
8342
8616
  method: "POST",
8343
8617
  headers: { authorization: `Bearer ${this.opts.token}`, "content-type": "application/json" },
8344
- body: JSON.stringify(payload),
8618
+ body: JSON.stringify(payload2),
8345
8619
  signal: AbortSignal.timeout(CONFIG_WORKER_TIMEOUT_MS)
8346
8620
  });
8347
8621
  } catch (err2) {
@@ -10456,11 +10730,11 @@ function resolveImportCandidates(fromFile, specifier) {
10456
10730
  const dir = fromFile.includes("/") ? fromFile.slice(0, fromFile.lastIndexOf("/")) : "";
10457
10731
  const joined = normalizePath(`${dir}/${specifier}`);
10458
10732
  const ext = /\.[a-z]+$/i.exec(joined)?.[0] ?? "";
10459
- const candidates = [];
10460
- if (JS_TO_TS[ext]) candidates.push(joined.slice(0, -ext.length) + JS_TO_TS[ext]);
10461
- if (!ext) candidates.push(`${joined}.ts`);
10462
- candidates.push(joined, `${joined}/index.ts`);
10463
- return candidates;
10733
+ const candidates2 = [];
10734
+ if (JS_TO_TS[ext]) candidates2.push(joined.slice(0, -ext.length) + JS_TO_TS[ext]);
10735
+ if (!ext) candidates2.push(`${joined}.ts`);
10736
+ candidates2.push(joined, `${joined}/index.ts`);
10737
+ return candidates2;
10464
10738
  }
10465
10739
  async function importClosure(entry, read) {
10466
10740
  const entrySource = await read(entry);
@@ -11556,8 +11830,8 @@ var init_runStoreWorker = __esm({
11556
11830
  }
11557
11831
  /** POST a JSON body and classify the outcome. The body is a STRING; the
11558
11832
  * runtime sets its numeric Content-Length (never hand-set — see header). */
11559
- async post(path, payload, span) {
11560
- const body = JSON.stringify(payload);
11833
+ async post(path, payload2, span) {
11834
+ const body = JSON.stringify(payload2);
11561
11835
  let res;
11562
11836
  try {
11563
11837
  res = await tracedFetch(
@@ -11841,8 +12115,8 @@ function rolloutTargetFromDeployOutput(output) {
11841
12115
  const at = text.indexOf("Container application changes");
11842
12116
  if (at < 0) return null;
11843
12117
  const lines = text.slice(at).split("\n").map((l) => l.replace(SECTION_GUTTER, "").trimEnd());
11844
- const imageOn = (candidates) => {
11845
- for (const l of candidates) {
12118
+ const imageOn = (candidates2) => {
12119
+ for (const l of candidates2) {
11846
12120
  const m = /^\+?\s*"image":\s*"([^"]+)"/.exec(l);
11847
12121
  if (m) return m[1];
11848
12122
  }
@@ -12043,7 +12317,7 @@ async function loadProfileOnHost(env = process.env, sourceIO = hostConfigSourceI
12043
12317
  return profileFromJson(read.text, override, "profile");
12044
12318
  }
12045
12319
  }
12046
- const candidates = override ? [
12320
+ const candidates2 = override ? [
12047
12321
  {
12048
12322
  path: override,
12049
12323
  origin: "profile",
@@ -12053,7 +12327,7 @@ async function loadProfileOnHost(env = process.env, sourceIO = hostConfigSourceI
12053
12327
  { path: PROFILE_PATH, origin: "profile", abs: installationPath(OPERATOR_ROOT, PROFILE_PATH) },
12054
12328
  { path: PROFILE_EXAMPLE_PATH, origin: "example", abs: assetPath(OPERATOR_ROOT, PROFILE_EXAMPLE_PATH) }
12055
12329
  ];
12056
- for (const c of candidates) {
12330
+ for (const c of candidates2) {
12057
12331
  const abs = c.abs;
12058
12332
  if (!existsSync6(abs)) {
12059
12333
  if (c.origin === "profile" && override) throw new Error(`${PROFILE_ENV}=${override}: no such file`);
@@ -15182,6 +15456,7 @@ var init_refusal = __esm({
15182
15456
  plan_runner_state_unread: "system",
15183
15457
  plan_units_merged: "request",
15184
15458
  plan_history_unavailable: "system",
15459
+ decision_record_store_unavailable: "system",
15185
15460
  plan_runner_conflict: "system",
15186
15461
  plan_instance_orphaned: "system",
15187
15462
  plan_start_failed: "system",
@@ -15998,10 +16273,11 @@ var init_resident = __esm({
15998
16273
  }
15999
16274
  /** Bind/reuse this thread's worktree. Legible errors for every named
16000
16275
  * refusal the service can answer with. Answers the binding the resident
16001
- * reported: the bound ref — the resident's word, not the hint's: a differing
16002
- * refHint is ignored unless `ownPr` names it as the thread's own pull
16003
- * request and the resident moves a default-bound thread onto it (item 16;
16004
- * `rebound` / `rebindRefused` say which) — and the sha the worktree is at. A
16276
+ * reported: the bound ref — a named `refHint` must be that ref, while the
16277
+ * sticky binding may answer a different ref only when the caller named none
16278
+ * (`refByDefault` or an `ownPr`-derived hint). The Worker also persists a named ref as the new sticky
16279
+ * binding (item 16). `rebound` / `rebindRefused` report the legacy own-PR
16280
+ * movement, and the sha says which commit the worktree is at. A
16005
16281
  * 200 without both fields is a malformed resident (the attach contract
16006
16282
  * always carries them) and is an error, never a half-bound executor. A
16007
16283
  * refusal the Worker typed as the platform's transient (`isTransientRefusal`:
@@ -16142,10 +16418,15 @@ var init_resident = __esm({
16142
16418
  if (typeof data.ref !== "string" || typeof data.sha !== "string") {
16143
16419
  throw new Error(`resident attach: malformed answer for ${this.opts.resource} (missing ref/sha)`);
16144
16420
  }
16421
+ const returned = returnedOf(data.returned);
16422
+ if (this.opts.refHint !== void 0 && !this.opts.refByDefault && this.opts.ownPr === void 0 && data.ref !== this.opts.refHint) {
16423
+ throw new Error(
16424
+ `resident attach: named ref mismatch for ${this.opts.resource} (asked for ${JSON.stringify(this.opts.refHint)}, got ${JSON.stringify(data.ref)})`
16425
+ );
16426
+ }
16145
16427
  const trace = sanitizeGraftedSteps(data.trace);
16146
16428
  const rebound = reboundOf(data.rebound);
16147
16429
  const rebindRefused = rebindRefusedOf(data.rebindRefused);
16148
- const returned = returnedOf(data.returned);
16149
16430
  this.lastBinding = {
16150
16431
  ref: data.ref,
16151
16432
  sha: data.sha,
@@ -16616,11 +16897,11 @@ function matchOf(typed, candidate) {
16616
16897
  const edits = editDistance(typed, candidate);
16617
16898
  return edits <= editBudget(typed) ? { kind: "edit", edits } : void 0;
16618
16899
  }
16619
- function nearMatch(typed, candidates) {
16900
+ function nearMatch(typed, candidates2) {
16620
16901
  const needle = typed.toLowerCase();
16621
- if (candidates.some((c) => c.toLowerCase() === needle)) return {};
16902
+ if (candidates2.some((c) => c.toLowerCase() === needle)) return {};
16622
16903
  const within = [];
16623
- for (const candidate of candidates) {
16904
+ for (const candidate of candidates2) {
16624
16905
  const match = matchOf(needle, candidate.toLowerCase());
16625
16906
  if (match)
16626
16907
  within.push(match.kind === "edit" ? { candidate, edits: match.edits } : { candidate, extended: match.extended });
@@ -16747,12 +17028,12 @@ async function mintCore(scope, span) {
16747
17028
  function appJwt(credentials) {
16748
17029
  const now = Math.floor(systemClock() / 1e3);
16749
17030
  const header4 = b64url(JSON.stringify({ alg: "RS256", typ: "JWT" }));
16750
- const payload = b64url(JSON.stringify({ iat: now - 60, exp: now + 540, iss: credentials.appId.reveal() }));
17031
+ const payload2 = b64url(JSON.stringify({ iat: now - 60, exp: now + 540, iss: credentials.appId.reveal() }));
16751
17032
  const signer = createSign("RSA-SHA256");
16752
- signer.update(`${header4}.${payload}`);
17033
+ signer.update(`${header4}.${payload2}`);
16753
17034
  const pem = credentials.privateKey.reveal().replace(/\\n/g, "\n");
16754
- const signature = signer.sign(pem).toString("base64url");
16755
- return `${header4}.${payload}.${signature}`;
17035
+ const signature2 = signer.sign(pem).toString("base64url");
17036
+ return `${header4}.${payload2}.${signature2}`;
16756
17037
  }
16757
17038
  function b64url(s) {
16758
17039
  return Buffer.from(s).toString("base64url");
@@ -16987,11 +17268,11 @@ async function createBranchRef(repo, branch2, fromRef) {
16987
17268
  if (res.status === 422 && /already exists/i.test(text)) return;
16988
17269
  throw new Error(`branch create failed for ${branch2}: HTTP ${res.status} ${redactAndCap(text, 300)}`);
16989
17270
  }
16990
- function resolveBaseRef(candidates, defaultBranch) {
16991
- return candidates.find((c) => c !== void 0) ?? defaultBranch;
17271
+ function resolveBaseRef(candidates2, defaultBranch) {
17272
+ return candidates2.find((c) => c !== void 0) ?? defaultBranch;
16992
17273
  }
16993
- async function resolveBaseRefLazy(candidates, repo, fetchInfo) {
16994
- const explicit = candidates.find((c) => c !== void 0);
17274
+ async function resolveBaseRefLazy(candidates2, repo, fetchInfo) {
17275
+ const explicit = candidates2.find((c) => c !== void 0);
16995
17276
  if (explicit) return explicit;
16996
17277
  const info = await fetchInfo(repo).catch(() => void 0);
16997
17278
  return info?.defaultBranch;
@@ -17113,7 +17394,22 @@ async function fetchPullRequestFacts(pr) {
17113
17394
  const prSha = typeof data.head?.sha === "string" && /^[0-9a-f]{40}$/.test(data.head.sha) ? data.head.sha : void 0;
17114
17395
  const sameRepoHead = headRepo === pr.repo.toLowerCase();
17115
17396
  const headRef = typeof data.head?.ref === "string" && data.head.ref ? data.head.ref : void 0;
17116
- const sha = sameRepoHead && headRef !== void 0 ? preferRefTip(`${pr.repo}#${pr.number}`, prSha, await headRefTipSha(pr.repo, headRef, headers), headRef) : prSha;
17397
+ const headBranchExists = sameRepoHead && headRef !== void 0 ? await fetchRefExists(pr.repo, headRef) : void 0;
17398
+ const sha = sameRepoHead && headRef !== void 0 && headBranchExists !== false ? preferRefTip(`${pr.repo}#${pr.number}`, prSha, await headRefTipSha(pr.repo, headRef, headers), headRef) : prSha;
17399
+ let closedBy = typeof data.closed_by?.login === "string" ? data.closed_by.login : void 0;
17400
+ if (data.state === "closed" && !(typeof data.merged_at === "string" && data.merged_at) && closedBy === void 0) {
17401
+ try {
17402
+ const issue = await fetch(`https://api.github.com/repos/${pr.repo}/issues/${pr.number}`, {
17403
+ headers,
17404
+ signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
17405
+ });
17406
+ if (issue.ok) {
17407
+ const row2 = await issue.json().catch(() => null);
17408
+ if (typeof row2?.closed_by?.login === "string") closedBy = row2.closed_by.login;
17409
+ }
17410
+ } catch {
17411
+ }
17412
+ }
17117
17413
  return {
17118
17414
  state: data.state,
17119
17415
  ...data.user && (typeof data.user.login === "string" || typeof data.user.id === "number") ? {
@@ -17124,6 +17420,7 @@ async function fetchPullRequestFacts(pr) {
17124
17420
  } : {},
17125
17421
  ...headRef !== void 0 ? { headRef } : {},
17126
17422
  ...sha ? { headSha: sha } : {},
17423
+ ...headBranchExists !== void 0 ? { headBranchExists } : {},
17127
17424
  sameRepoHead,
17128
17425
  ...typeof data.base?.ref === "string" && data.base.ref ? { baseRef: data.base.ref } : {},
17129
17426
  ...typeof data.html_url === "string" ? { htmlUrl: data.html_url } : {},
@@ -17136,8 +17433,10 @@ async function fetchPullRequestFacts(pr) {
17136
17433
  // reports a test-merge sha under `merge_commit_sha`.
17137
17434
  ...typeof data.merged_at === "string" && data.merged_at ? {
17138
17435
  mergedAt: data.merged_at,
17139
- ...typeof data.merge_commit_sha === "string" && /^[0-9a-f]{40}$/.test(data.merge_commit_sha) ? { mergeCommitSha: data.merge_commit_sha } : {}
17140
- } : {}
17436
+ ...typeof data.merge_commit_sha === "string" && /^[0-9a-f]{40}$/.test(data.merge_commit_sha) ? { mergeCommitSha: data.merge_commit_sha } : {},
17437
+ ...typeof data.merged_by?.login === "string" ? { mergedBy: data.merged_by.login } : {}
17438
+ } : {},
17439
+ ...closedBy !== void 0 ? { closedBy } : {}
17141
17440
  };
17142
17441
  }
17143
17442
  async function mergePullRequest(pr, opts) {
@@ -17439,6 +17738,56 @@ async function rerunFailedJobs(repo, sha, names) {
17439
17738
  }
17440
17739
  return true;
17441
17740
  }
17741
+ async function fetchDecisionRecordClaims(repo) {
17742
+ const token2 = await requireToken();
17743
+ const get = async (url, what) => {
17744
+ const res = await fetch(url, { headers: apiHeaders(token2), signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS) });
17745
+ if (!res.ok) {
17746
+ const text = await res.text().catch(() => "");
17747
+ throw new Error(`${what} could not be read: HTTP ${res.status} ${redactAndCap(text, 300)}`);
17748
+ }
17749
+ return res.json().catch(() => null);
17750
+ };
17751
+ const numbers = /* @__PURE__ */ new Set();
17752
+ const claim = (path) => {
17753
+ if (typeof path !== "string") return;
17754
+ const number = /^docs\/decisions\/(\d{4})-[^/]+\.md$/.exec(path)?.[1];
17755
+ if (number !== void 0) numbers.add(number);
17756
+ };
17757
+ const main2 = await get(
17758
+ `https://api.github.com/repos/${repo}/contents/docs/decisions?ref=main&per_page=1000`,
17759
+ `origin/main's decision records in ${repo}`
17760
+ );
17761
+ if (!Array.isArray(main2))
17762
+ throw new Error(`origin/main's decision records in ${repo} could not be read: malformed answer`);
17763
+ for (const row2 of main2) claim(row2.path);
17764
+ const pulls = [];
17765
+ for (let page2 = 1; ; page2 += 1) {
17766
+ const rows = await get(
17767
+ `https://api.github.com/repos/${repo}/pulls?state=open&per_page=100&page=${page2}`,
17768
+ `the open pull requests of ${repo}`
17769
+ );
17770
+ if (!Array.isArray(rows)) throw new Error(`the open pull requests of ${repo} could not be read: malformed answer`);
17771
+ for (const row2 of rows)
17772
+ if (typeof row2.number === "number" && Number.isInteger(row2.number) && row2.number > 0) pulls.push(row2.number);
17773
+ if (rows.length < 100) break;
17774
+ }
17775
+ await Promise.all(
17776
+ pulls.map(async (number) => {
17777
+ for (let page2 = 1; ; page2 += 1) {
17778
+ const rows = await get(
17779
+ `https://api.github.com/repos/${repo}/pulls/${number}/files?per_page=100&page=${page2}`,
17780
+ `the changed files of ${repo}#${number}`
17781
+ );
17782
+ if (!Array.isArray(rows))
17783
+ throw new Error(`the changed files of ${repo}#${number} could not be read: malformed answer`);
17784
+ for (const row2 of rows) claim(row2.filename);
17785
+ if (rows.length < 100) break;
17786
+ }
17787
+ })
17788
+ );
17789
+ return numbers;
17790
+ }
17442
17791
  async function listOpenPullRequests(repo) {
17443
17792
  const token2 = await requireToken();
17444
17793
  const res = await fetch(`https://api.github.com/repos/${repo}/pulls?state=open&per_page=100`, {
@@ -18368,9 +18717,9 @@ function residentSlugsLister(cfg, secrets = processSecrets) {
18368
18717
  }
18369
18718
  async function nearOnboarded(cfg, typed) {
18370
18719
  const list = residentSlugsLister(cfg);
18371
- const candidates = await list?.().catch(() => void 0);
18372
- if (!candidates || candidates.length === 0) return void 0;
18373
- return nearMatch(typed, candidates).guess;
18720
+ const candidates2 = await list?.().catch(() => void 0);
18721
+ if (!candidates2 || candidates2.length === 0) return void 0;
18722
+ return nearMatch(typed, candidates2).guess;
18374
18723
  }
18375
18724
  async function probeResident(cfg, token2, resource, span, stop) {
18376
18725
  if (probeOutage && systemClock() < probeOutage.until) {
@@ -20474,10 +20823,10 @@ var init_costs = __esm({
20474
20823
 
20475
20824
  // ../../src/core/costsBy.ts
20476
20825
  function coverageFrom(range, usage, generatedAt) {
20477
- const candidates = [range.from];
20478
- if (usage.earliestFinishedAt !== void 0) candidates.push(dayOfMs(usage.earliestFinishedAt));
20479
- if (usage.retentionDays > 0) candidates.push(dayOfMs(generatedAt - usage.retentionDays * DAY_MS));
20480
- const from = candidates.reduce((m, d) => d > m ? d : m);
20826
+ const candidates2 = [range.from];
20827
+ if (usage.earliestFinishedAt !== void 0) candidates2.push(dayOfMs(usage.earliestFinishedAt));
20828
+ if (usage.retentionDays > 0) candidates2.push(dayOfMs(generatedAt - usage.retentionDays * DAY_MS));
20829
+ const from = candidates2.reduce((m, d) => d > m ? d : m);
20481
20830
  return from > range.to ? range.to : from;
20482
20831
  }
20483
20832
  function slicesOf(cells, dimension) {
@@ -24728,10 +25077,10 @@ function clusterFriction(records, opts = {}) {
24728
25077
  const minRuns = opts.minRuns ?? DEFAULT_MIN_RUNS;
24729
25078
  const ordered = [...records].sort((a, b) => a.finishedAt - b.finishedAt || a.runId.localeCompare(b.runId));
24730
25079
  const acc = /* @__PURE__ */ new Map();
24731
- const add = (key, kind, signature, rec2, example, durationMs) => {
25080
+ const add = (key, kind, signature2, rec2, example, durationMs) => {
24732
25081
  let a = acc.get(key);
24733
25082
  if (!a) {
24734
- a = { kind, signature, runIds: /* @__PURE__ */ new Set(), occurrences: 0, durationMs: 0, severity: "low", examples: [] };
25083
+ a = { kind, signature: signature2, runIds: /* @__PURE__ */ new Set(), occurrences: 0, durationMs: 0, severity: "low", examples: [] };
24735
25084
  acc.set(key, a);
24736
25085
  }
24737
25086
  a.runIds.add(rec2.runId);
@@ -25057,10 +25406,10 @@ function parseRunEventLines(text) {
25057
25406
  for (const raw of text.split("\n")) {
25058
25407
  const line4 = raw.trim();
25059
25408
  if (line4 === "" || line4.startsWith(":") || /^(retry|event|id):/.test(line4)) continue;
25060
- const payload = line4.startsWith("data:") ? line4.slice("data:".length).trim() : line4;
25409
+ const payload2 = line4.startsWith("data:") ? line4.slice("data:".length).trim() : line4;
25061
25410
  let parsed;
25062
25411
  try {
25063
- parsed = JSON.parse(payload);
25412
+ parsed = JSON.parse(payload2);
25064
25413
  } catch {
25065
25414
  skipped++;
25066
25415
  continue;
@@ -26295,9 +26644,9 @@ function wellKnownCandidates(base, suffix) {
26295
26644
  async function discover(fetchImpl, serverUrl2, hintedMetadataUrl) {
26296
26645
  const server = assertUrlAllowed(serverUrl2);
26297
26646
  const resource = `${server.origin}${server.pathname.replace(/\/+$/, "")}`;
26298
- const candidates = [...hintedMetadataUrl ? [hintedMetadataUrl] : [], ...wellKnownCandidates(server, PRM_SUFFIX)];
26647
+ const candidates2 = [...hintedMetadataUrl ? [hintedMetadataUrl] : [], ...wellKnownCandidates(server, PRM_SUFFIX)];
26299
26648
  let prm;
26300
- for (const url of candidates) {
26649
+ for (const url of candidates2) {
26301
26650
  prm = await getJson(fetchImpl, url, "resource metadata");
26302
26651
  if (prm) break;
26303
26652
  }
@@ -28813,24 +29162,24 @@ function parseReflection(raw, prov, knownIds) {
28813
29162
  const parsed = REFLECTION_ENVELOPE.parse(raw);
28814
29163
  if (!parsed.ok) return { ok: false, error: parsed.failure.observed };
28815
29164
  const obj2 = parsed.value;
28816
- const candidates = [];
29165
+ const candidates2 = [];
28817
29166
  let rejected = 0;
28818
29167
  for (const f of obj2.facts) {
28819
- if (candidates.length >= MAX_REFLECTION_FACTS) break;
29168
+ if (candidates2.length >= MAX_REFLECTION_FACTS) break;
28820
29169
  const fact = parseFact(f, prov, knownIds);
28821
29170
  if (!fact) continue;
28822
29171
  if (rejectionMarkers(fact.text).length > 0) {
28823
29172
  rejected += 1;
28824
29173
  continue;
28825
29174
  }
28826
- candidates.push(fact);
29175
+ candidates2.push(fact);
28827
29176
  }
28828
29177
  const summary = cleanText(obj2.summary);
28829
29178
  if (summary) {
28830
- const audience = SUMMARY_INHERITANCE.find((a) => candidates.some((c) => c.audience === a)) ?? "org";
28831
- candidates.push({ kind: "summary", text: summary, audience, ...prov });
29179
+ const audience = SUMMARY_INHERITANCE.find((a) => candidates2.some((c) => c.audience === a)) ?? "org";
29180
+ candidates2.push({ kind: "summary", text: summary, audience, ...prov });
28832
29181
  }
28833
- return { ok: true, candidates, offered: obj2.facts.length, rejected };
29182
+ return { ok: true, candidates: candidates2, offered: obj2.facts.length, rejected };
28834
29183
  }
28835
29184
  function parseFact(raw, prov, knownIds) {
28836
29185
  if (typeof raw !== "object" || raw === null) return void 0;
@@ -28939,14 +29288,21 @@ async function reflect(deps) {
28939
29288
  const seen = /* @__PURE__ */ new Set();
28940
29289
  const existing = [...await windowP, ...hits].filter((r) => !seen.has(r.id) && (seen.add(r.id), true));
28941
29290
  const text = buildReflectionInput({ history: deps.history, request: deps.request, answer: deps.answer, existing });
28942
- const result = await deps.provider.complete({
28943
- model: deps.model,
28944
- system: REFLECTION_SYSTEM,
28945
- messages: [{ role: "user", content: [{ type: "text", text }] }],
28946
- maxTokens: REFLECTION_MAX_TOKENS,
28947
- ...deps.effort !== void 0 ? { effort: deps.effort } : {},
28948
- ...deps.effortWord !== void 0 ? { effortWord: deps.effortWord } : {}
28949
- });
29291
+ let result;
29292
+ try {
29293
+ result = await deps.provider.complete({
29294
+ model: deps.model,
29295
+ system: REFLECTION_SYSTEM,
29296
+ messages: [{ role: "user", content: [{ type: "text", text }] }],
29297
+ maxTokens: REFLECTION_MAX_TOKENS,
29298
+ ...deps.effort !== void 0 ? { effort: deps.effort } : {},
29299
+ ...deps.effortWord !== void 0 ? { effortWord: deps.effortWord } : {}
29300
+ });
29301
+ } catch (err2) {
29302
+ const failure2 = providerFailureOf(err2);
29303
+ warn(`reflection skipped: ${renderProviderFailure(failure2.cause)}`);
29304
+ return;
29305
+ }
28950
29306
  const reply = result.content.filter((p) => p.type === "text").map((p) => p.text).join("");
28951
29307
  const prov = { sourceThreadKey: deps.sourceThreadKey, sourceRunId: deps.sourceRunId };
28952
29308
  const parsed = parseReflection(reply, prov, new Set(existing.map((r) => r.id)));
@@ -29020,6 +29376,7 @@ var REFLECT_MIN_TURNS, MAX_REFLECTION_FACTS, MIN_REFLECTION_CONFIDENCE, MAX_KEYW
29020
29376
  var init_reflection = __esm({
29021
29377
  "../../src/core/memory/reflection.ts"() {
29022
29378
  "use strict";
29379
+ init_provider();
29023
29380
  init_authz();
29024
29381
  init_runEvents();
29025
29382
  init_llmOutput();
@@ -31344,6 +31701,7 @@ function assembleRunRecord(input) {
31344
31701
  const route2 = input.route ?? routeOfEvents(events);
31345
31702
  const operator = operatorOfEvents(events);
31346
31703
  const instanceId = instanceIdOfEvents(events);
31704
+ const decisionRecord = decisionRecordOfEvents(events);
31347
31705
  const pipeline = input.pipeline ?? pipelineOfEvents(events);
31348
31706
  const fitted = fitRecordToBudget({
31349
31707
  id: run2.id,
@@ -31380,6 +31738,7 @@ function assembleRunRecord(input) {
31380
31738
  ...referencesOfEvents(events).length > 0 ? { references: referencesOfEvents(events) } : {},
31381
31739
  ...msg.sourceUrl !== void 0 ? { sourceUrl: msg.sourceUrl } : {},
31382
31740
  ...msg.userName !== void 0 ? { userName: msg.userName } : {},
31741
+ ...input.headSha !== void 0 ? { headSha: input.headSha } : {},
31383
31742
  ...input.handoff !== void 0 ? { handoff: redactHandoff(input.handoff) } : {},
31384
31743
  ...input.verdict !== void 0 ? { verdict: redactVerdict(input.verdict) } : {},
31385
31744
  ...input.reviewHead !== void 0 ? { reviewHead: input.reviewHead } : {},
@@ -31391,6 +31750,7 @@ function assembleRunRecord(input) {
31391
31750
  ...input.parentRunId !== void 0 ? { parentRunId: input.parentRunId } : {},
31392
31751
  ...coordinatorFields(input.coordinator),
31393
31752
  ...instanceId !== void 0 ? { instanceId } : {},
31753
+ ...decisionRecord !== void 0 ? decisionRecord : {},
31394
31754
  ...pipeline !== void 0 ? { pipeline } : {},
31395
31755
  ...input.hosted ? { hosted: true } : {},
31396
31756
  ...input.seed !== void 0 ? { seed: input.seed } : {},
@@ -31475,6 +31835,7 @@ function registerFinishRecord(deps, ctx) {
31475
31835
  diagnosis,
31476
31836
  root,
31477
31837
  ledgerRun,
31838
+ headSha,
31478
31839
  handoff,
31479
31840
  verdict,
31480
31841
  reviewHead,
@@ -31503,6 +31864,7 @@ function registerFinishRecord(deps, ctx) {
31503
31864
  status: failedAfterFinish && status3 === "completed" ? "failed" : status3,
31504
31865
  diagnosis,
31505
31866
  seal,
31867
+ ...headSha !== void 0 ? { headSha } : {},
31506
31868
  ...handoff !== void 0 ? { handoff } : {},
31507
31869
  ...verdict !== void 0 ? { verdict } : {},
31508
31870
  ...reviewHead !== void 0 ? { reviewHead } : {},
@@ -31845,6 +32207,7 @@ function createRunsService(deps) {
31845
32207
  }
31846
32208
  if (!row2) return {};
31847
32209
  return {
32210
+ ...row2.headSha !== void 0 ? { headSha: row2.headSha } : {},
31848
32211
  ...row2.verdict !== void 0 ? { verdict: row2.verdict } : {},
31849
32212
  ...row2.reviewHead !== void 0 ? { reviewHead: row2.reviewHead } : {},
31850
32213
  ...row2.reviewPost !== void 0 ? { reviewPost: row2.reviewPost } : {},
@@ -33509,6 +33872,9 @@ var init_instanceStore = __esm({
33509
33872
  async listUnits(_instanceId) {
33510
33873
  return [];
33511
33874
  }
33875
+ async reserveDecisionRecord(_repo, _taskKey, _claimed, _existing) {
33876
+ return { ok: false, reason: "unavailable" };
33877
+ }
33512
33878
  async markStopped(_instanceId, _at) {
33513
33879
  return { ok: false, reason: "unavailable" };
33514
33880
  }
@@ -33584,6 +33950,17 @@ var init_instanceStore = __esm({
33584
33950
  throw new Error("coordinator store /runs/coordinator/units/list: the answer is not a list of unit rows");
33585
33951
  return d.units;
33586
33952
  }
33953
+ async reserveDecisionRecord(repo, taskKey, claimed2, existing) {
33954
+ const r = await this.post("/runs/decision-record/reserve", {
33955
+ repo,
33956
+ taskKey,
33957
+ claimed: [...claimed2],
33958
+ ...existing !== void 0 ? { existing } : {}
33959
+ });
33960
+ const d = r.data;
33961
+ if (typeof d.number === "string" && /^\d{4}$/.test(d.number)) return { ok: true, number: d.number };
33962
+ throw new Error(`coordinator store /runs/decision-record/reserve: unexpected answer (HTTP ${r.status})`);
33963
+ }
33587
33964
  async markStopped(instanceId, at) {
33588
33965
  const r = await this.post("/runs/coordinator/stop", { instanceId, at });
33589
33966
  const d = r.data;
@@ -33625,12 +34002,13 @@ var init_instanceStore = __esm({
33625
34002
  function isIntakeReceipt(v) {
33626
34003
  if (typeof v !== "object" || v === null) return false;
33627
34004
  const r = v;
33628
- return (r.verdict === "addressed" || r.verdict === "silent") && typeof r.reason === "string" && (r.source === "model" || r.source === "mode" || r.source === "question" || r.source === "error" || r.source === "timeout") && (r.mode === "mention" || r.mode === "classify") && typeof r.model === "string" && typeof r.gen === "number" && Number.isFinite(r.gen) && typeof r.threadKey === "string" && r.threadKey.length > 0 && r.threadKey.length <= 256 && typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt);
34005
+ return (r.verdict === "addressed" || r.verdict === "silent") && typeof r.reason === "string" && (r.source === "model" || r.source === "mode" || r.source === "question" || r.source === "error" || r.source === "timeout") && (r.providerFailure === void 0 || typeof r.providerFailure === "string" && PROVIDER_FAILURE_CAUSES.includes(r.providerFailure)) && (r.mode === "mention" || r.mode === "classify") && typeof r.model === "string" && typeof r.gen === "number" && Number.isFinite(r.gen) && typeof r.threadKey === "string" && r.threadKey.length > 0 && r.threadKey.length <= 256 && typeof r.decidedAt === "number" && Number.isFinite(r.decidedAt);
33629
34006
  }
33630
34007
  var LEASE_MS, HEARTBEAT_MS, APPEND_FLUSH_MS, APPEND_FLUSH_EVENTS, TRANSCRIPT_REQUEST_BYTES, TRANSCRIPT_PART_BYTES, ATTACHMENT_REF_BYTES, GEN_PATTERN;
33631
34008
  var init_types4 = __esm({
33632
34009
  "../../src/core/runLedger/types.ts"() {
33633
34010
  "use strict";
34011
+ init_provider();
33634
34012
  LEASE_MS = 3e4;
33635
34013
  HEARTBEAT_MS = 1e4;
33636
34014
  APPEND_FLUSH_MS = 500;
@@ -34125,6 +34503,23 @@ var init_runLedgerWorker = __esm({
34125
34503
  const r = await this.post("/runs/intake/read", { storeKey: this.opts.storeKey, key });
34126
34504
  return isIntakeReceipt(r.data.receipt) ? r.data.receipt : void 0;
34127
34505
  }
34506
+ async claimIntakeDelivery(key, poster, claimedAt) {
34507
+ const r = await this.post("/runs/intake/delivery/claim", {
34508
+ storeKey: this.opts.storeKey,
34509
+ key,
34510
+ poster,
34511
+ claimedAt
34512
+ });
34513
+ return r.data.claimed === true;
34514
+ }
34515
+ async finishIntakeDelivery(key, poster, delivered) {
34516
+ await this.post("/runs/intake/delivery/finish", {
34517
+ storeKey: this.opts.storeKey,
34518
+ key,
34519
+ poster,
34520
+ delivered
34521
+ });
34522
+ }
34128
34523
  async listIntake(query) {
34129
34524
  const r = await this.post("/runs/intake/list", {
34130
34525
  storeKey: this.opts.storeKey,
@@ -34924,13 +35319,13 @@ var init_confirmations = __esm({
34924
35319
  describe() {
34925
35320
  return `state Worker ${this.baseUrl} (ConfigDO confirmations)`;
34926
35321
  }
34927
- async post(path, payload) {
35322
+ async post(path, payload2) {
34928
35323
  let res;
34929
35324
  try {
34930
35325
  res = await this.fetchImpl(`${this.baseUrl}${path}`, {
34931
35326
  method: "POST",
34932
35327
  headers: { authorization: `Bearer ${this.opts.token}`, "content-type": "application/json" },
34933
- body: JSON.stringify(payload),
35328
+ body: JSON.stringify(payload2),
34934
35329
  signal: AbortSignal.timeout(CONFIRMATION_WORKER_TIMEOUT_MS)
34935
35330
  });
34936
35331
  } catch (err2) {
@@ -35692,7 +36087,7 @@ async function openPrHeadSha(pr) {
35692
36087
  }
35693
36088
  async function currentPrHeadSha(pr) {
35694
36089
  const head = await prHead(pr).catch(() => void 0);
35695
- return head?.sha;
36090
+ return head?.state === "open" ? head.sha : void 0;
35696
36091
  }
35697
36092
  async function prCommitsSince(input) {
35698
36093
  const sha = normalizeHead(input.sha);
@@ -36312,6 +36707,20 @@ async function runReviewPostStep(input) {
36312
36707
  postTarget = null;
36313
36708
  }
36314
36709
  }
36710
+ if (input.guardTransition === true && postTarget && reviewHead) {
36711
+ const where = `${postTarget.repo}#${postTarget.number}`;
36712
+ const pinned = carried?.current ?? reviewHead;
36713
+ const facts2 = await input.fetchPrFacts({ repo: postTarget.repo, number: postTarget.number }).catch(() => void 0);
36714
+ const current = facts2?.state === "open" && facts2.headBranchExists === true ? normalizeHead(facts2.headSha) : void 0;
36715
+ if (current === void 0 || !sameCommit(current, pinned)) {
36716
+ const reason2 = current === void 0 ? "the pull request is no longer open at a readable head" : `the PR head moved to ${current.slice(0, 7)} after the review of ${pinned.slice(0, 7)}`;
36717
+ console.log(`[review-post] ${logKey} skipped: ${reason2} (${where})`);
36718
+ await input.reply(`\u2139\uFE0F Review not posted to ${where}: ${reason2} \u2014 this verdict is Slack-only.`).catch(() => {
36719
+ });
36720
+ skipReason = reason2;
36721
+ postTarget = null;
36722
+ }
36723
+ }
36315
36724
  if (postTarget && reviewHead) {
36316
36725
  const pinned = carried?.current ?? reviewHead;
36317
36726
  const target = { ...postTarget, commitId: pinned };
@@ -36452,9 +36861,9 @@ function correctedMessage(msg, slug, match) {
36452
36861
  }
36453
36862
  async function onboardedGuess(deps, msg, slug) {
36454
36863
  const list = deps.residentSlugs ?? residentSlugsLister(deps.config.config.execution?.resident);
36455
- const candidates = await list?.().catch(() => void 0);
36456
- if (!candidates || candidates.length === 0) return void 0;
36457
- const near = nearMatch(slug, candidates);
36864
+ const candidates2 = await list?.().catch(() => void 0);
36865
+ if (!candidates2 || candidates2.length === 0) return void 0;
36866
+ const near = nearMatch(slug, candidates2);
36458
36867
  if (!near.guess) return void 0;
36459
36868
  const proposal = correctedMessage(msg, slug, near.guess);
36460
36869
  if (!proposal) return void 0;
@@ -37766,6 +38175,12 @@ var init_statusCardFrame = __esm({
37766
38175
  });
37767
38176
 
37768
38177
  // ../../src/core/dispatch/route.ts
38178
+ function outputCapWithReasoning(visibleAnswerTokens, opts = {}) {
38179
+ return opts.capField !== void 0 && REASONING_COUNTING_CAP_FIELDS.has(opts.capField) ? visibleAnswerTokens + REASONING_OUTPUT_TOKEN_ALLOWANCE : visibleAnswerTokens;
38180
+ }
38181
+ function outputCapRetry(current) {
38182
+ return Math.max(current * 2, REASONING_OUTPUT_TOKEN_ALLOWANCE);
38183
+ }
37769
38184
  function routedRunsAtOnce(def, confirm, input) {
37770
38185
  const radius = input === void 0 ? blastRadius(def) : boundBlastRadius(def, input);
37771
38186
  return radius === "read" || CONFIRM_ORDER[radius] < CONFIRM_ORDER[confirm];
@@ -37827,7 +38242,7 @@ function providerStructuredModel(provider, model, opts = {}) {
37827
38242
  ...opts.effort ? { effort: opts.effort.effort, effortWord: opts.effort.effortWord } : {},
37828
38243
  ...forced ? { tools, toolChoice } : {}
37829
38244
  });
37830
- if (result.stopReason === "max_tokens") throw new Error(`answer cut at the output cap (${call2.maxTokens} tokens)`);
38245
+ if (result.stopReason === "max_tokens") throw new OutputCapError(call2.maxTokens);
37831
38246
  const calls = result.content.filter(
37832
38247
  (p) => p.type === "tool_use"
37833
38248
  );
@@ -37873,13 +38288,13 @@ function redactedInput(input) {
37873
38288
  return v === void 0 ? null : text(JSON.stringify(v) ?? String(v));
37874
38289
  };
37875
38290
  const leafOrList = (v) => Array.isArray(v) ? v.map(leaf) : leaf(v);
37876
- const isRecord10 = (v) => typeof v === "object" && v !== null && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
38291
+ const isRecord11 = (v) => typeof v === "object" && v !== null && (Object.getPrototypeOf(v) === Object.prototype || Object.getPrototypeOf(v) === null);
37877
38292
  const entries = (v, of) => Object.fromEntries(
37878
38293
  Object.entries(v).filter(([, x]) => x !== void 0).map(([k, x]) => [k, of(x)])
37879
38294
  );
37880
38295
  const object1 = (v) => entries(v, leafOrList);
37881
- const object2 = (v) => entries(v, (x) => isRecord10(x) ? object1(x) : leafOrList(x));
37882
- const object3 = (v) => entries(v, (x) => isRecord10(x) ? object2(x) : leafOrList(x));
38296
+ const object2 = (v) => entries(v, (x) => isRecord11(x) ? object1(x) : leafOrList(x));
38297
+ const object3 = (v) => entries(v, (x) => isRecord11(x) ? object2(x) : leafOrList(x));
37883
38298
  return {
37884
38299
  ...input.args ? { args: input.args.map(leaf) } : {},
37885
38300
  ...input.options ? { options: object3(input.options) } : {}
@@ -37902,7 +38317,7 @@ function compoundBrief(text, parts) {
37902
38317
  ...parts.map((p, i) => `${i + 1}. \`${p.preset}\`: ${p.text}`)
37903
38318
  ].join("\n");
37904
38319
  }
37905
- var ROUTE_REASON_CAP, ROUTE_MIN_OUTPUT_TOKENS, ROUTE_TIMEOUT_MS, ROUTE_PART_LINE_CAP, ROUTE_RECEIPT_CAP, ROUTED_RECEIPT_PREFIX, COMPOUND_BRIEF_HEADING, MultiToolCallError;
38320
+ var REASONING_COUNTING_CAP_FIELDS, REASONING_OUTPUT_TOKEN_ALLOWANCE, OutputCapError, ROUTE_REASON_CAP, ROUTE_MIN_OUTPUT_TOKENS, ROUTE_TIMEOUT_MS, ROUTE_PART_LINE_CAP, ROUTE_RECEIPT_CAP, ROUTED_RECEIPT_PREFIX, COMPOUND_BRIEF_HEADING, MultiToolCallError;
37906
38321
  var init_route = __esm({
37907
38322
  "../../src/core/dispatch/route.ts"() {
37908
38323
  "use strict";
@@ -37915,6 +38330,16 @@ var init_route = __esm({
37915
38330
  init_commandSurface();
37916
38331
  init_budgets();
37917
38332
  init_confirmations();
38333
+ REASONING_COUNTING_CAP_FIELDS = /* @__PURE__ */ new Set(["max_tokens", "max_completion_tokens", "max_output_tokens"]);
38334
+ REASONING_OUTPUT_TOKEN_ALLOWANCE = 4096;
38335
+ OutputCapError = class extends Error {
38336
+ constructor(maxTokens) {
38337
+ super(`answer cut at the output cap (${maxTokens} tokens)`);
38338
+ this.maxTokens = maxTokens;
38339
+ this.name = "OutputCapError";
38340
+ }
38341
+ maxTokens;
38342
+ };
37918
38343
  ROUTE_REASON_CAP = 120;
37919
38344
  ROUTE_MIN_OUTPUT_TOKENS = 200;
37920
38345
  ROUTE_TIMEOUT_MS = 8e3;
@@ -39421,11 +39846,12 @@ function resolveTarget2(deps, ctx) {
39421
39846
  )
39422
39847
  );
39423
39848
  }
39424
- if (modelCard.wire === "openai-responses" && (resolved.harness?.name ?? DEFAULT_HARNESS) === "opencode") {
39849
+ const harnessName = resolved.harness?.name ?? DEFAULT_HARNESS;
39850
+ if (modelCard.wire === "openai-responses" && harnessName === "opencode") {
39425
39851
  throw new RefusalError(
39426
39852
  refusalOf(
39427
39853
  "model_card_refused",
39428
- `Model "${resolved.modelRef}" speaks the openai-responses wire, which the "opencode" harness cannot speak yet; this request needs the pi harness or an openai-chat block.`
39854
+ `The "${harnessName}" harness cannot start model "${resolved.modelRef}" from provider "${providerName}": it cannot speak the "${modelCard.wire}" wire; this request needs the pi harness or an openai-chat block.`
39429
39855
  )
39430
39856
  );
39431
39857
  }
@@ -40390,14 +40816,20 @@ function renderOperatorQuestion(decision) {
40390
40816
  ${OPERATOR_QUESTION_MARKER}
40391
40817
  \`${decision.proposal}\`` : decision.text;
40392
40818
  }
40393
- function operatorMaxOutputTokens() {
40819
+ function operatorMaxOutputTokens(opts = {}) {
40394
40820
  const chars = 2 * (ROUTE_RECEIPT_CAP + ROUTE_REASON_CAP + 40) + ROUTE_REASON_CAP + 80;
40395
- return Math.ceil(chars / 3);
40821
+ return outputCapWithReasoning(Math.ceil(chars / 3), opts);
40822
+ }
40823
+ function promptWithoutTool(prompt, omitted) {
40824
+ const kept = [prompt.tool, ...prompt.tools ?? []].filter((candidate) => candidate.name !== omitted);
40825
+ const [tool, ...tools] = kept;
40826
+ if (tool === void 0) return void 0;
40827
+ return { ...prompt, tool, tools: tools.length > 0 ? tools : void 0 };
40396
40828
  }
40397
40829
  async function runOperator(input, model, opts = {}) {
40398
40830
  const now = opts.now ?? Date.now;
40399
40831
  const started = now();
40400
- const prompt = buildOperatorPrompt(input);
40832
+ let prompt = buildOperatorPrompt(input);
40401
40833
  const ctx = {
40402
40834
  requestText: input.text,
40403
40835
  presets: input.projection.presets.map((p) => p.name),
@@ -40412,31 +40844,66 @@ async function runOperator(input, model, opts = {}) {
40412
40844
  };
40413
40845
  let chars = 0;
40414
40846
  const attempts = [];
40415
- const answered = (decision) => ({
40847
+ const answered = (decision, operatorDiagnostic) => ({
40416
40848
  decision,
40417
40849
  latencyMs: now() - started,
40418
40850
  outputTokens: Math.ceil(chars / 3),
40851
+ ...operatorDiagnostic !== void 0 ? { operatorDiagnostic } : {},
40419
40852
  ...attempts.length > 0 ? { attempts } : {}
40420
40853
  });
40421
40854
  const turns = [];
40422
40855
  let reads = 0;
40423
40856
  let violations = 0;
40857
+ let schemaReasks = 0;
40424
40858
  let noCallTurns = 0;
40425
- const generalFloor = () => {
40426
- const floor = parseOperatorTurn(
40427
- { tool: OPERATOR_BIND_TOOL, input: { preset: "general", reason: "no_decision" } },
40428
- ctx
40429
- );
40430
- return floor.kind === "decision" ? floor.decision : { kind: "non_decision", reason: "no_decision" };
40859
+ let outputCapCuts = 0;
40860
+ let maxOutputTokens = opts.maxOutputTokens ?? operatorMaxOutputTokens();
40861
+ const generalFloor = (reason2 = "no_decision") => {
40862
+ const floor = parseOperatorTurn({ tool: OPERATOR_BIND_TOOL, input: { preset: "general", reason: reason2 } }, ctx);
40863
+ return floor.kind === "decision" ? floor.decision : { kind: "non_decision", reason: reason2 };
40431
40864
  };
40432
40865
  const signal2 = AbortSignal.timeout(opts.timeoutMs ?? OPERATOR_TIMEOUT_MS);
40433
40866
  try {
40434
40867
  for (; ; ) {
40435
40868
  let answer;
40436
40869
  try {
40437
- answer = await model({ ...prompt, retries: turns }, { maxTokens: operatorMaxOutputTokens(), signal: signal2 });
40870
+ answer = await model({ ...prompt, retries: turns }, { maxTokens: maxOutputTokens, signal: signal2 });
40438
40871
  } catch (err2) {
40439
- if (!(err2 instanceof MultiToolCallError)) throw err2;
40872
+ if (err2 instanceof OutputCapError) {
40873
+ const violation2 = err2.message;
40874
+ attempts.push({ outcome: "violation", violation: violation2 });
40875
+ if (outputCapCuts++ === 0) {
40876
+ turns.push({ answer: "", violation: `${violation2}; retry with the larger output allowance` });
40877
+ maxOutputTokens = outputCapRetry(maxOutputTokens);
40878
+ continue;
40879
+ }
40880
+ return answered(generalFloor("output_cap"));
40881
+ }
40882
+ if (!(err2 instanceof MultiToolCallError)) {
40883
+ const carried = attemptsOfThrow(err2);
40884
+ if (carried) attempts.push(...carried);
40885
+ const failure2 = providerFailureOf(err2);
40886
+ const rejected = failure2.cause === "request-rejected" && failure2.status === 400 ? failure2.schemaRejection : void 0;
40887
+ const offered = rejected !== void 0 && [prompt.tool, ...prompt.tools ?? []].some((candidate) => candidate.name === rejected.tool);
40888
+ const retry = offered && rejected !== void 0 && schemaReasks < OPERATOR_SCHEMA_REASKS_MAX ? promptWithoutTool(prompt, rejected.tool) : void 0;
40889
+ if (rejected !== void 0 && retry !== void 0) {
40890
+ const violation2 = `provider rejected tool "${rejected.tool}" schema keyword "${rejected.keyword}"; re-asked without that tool`;
40891
+ attempts.push({ outcome: "violation", violation: violation2 });
40892
+ schemaReasks++;
40893
+ prompt = retry;
40894
+ continue;
40895
+ }
40896
+ return answered(
40897
+ {
40898
+ kind: "refusal",
40899
+ cause: "provider",
40900
+ providerFailure: failure2.cause,
40901
+ reason: failure2.cause,
40902
+ text: renderProviderFailure(failure2.cause, "ended")
40903
+ },
40904
+ failure2.message
40905
+ );
40906
+ }
40440
40907
  const calls = JSON.stringify(err2.calls);
40441
40908
  chars += calls.length;
40442
40909
  const violation = `the answer carried ${err2.calls.length} tool calls; one tool call per turn`;
@@ -40517,6 +40984,7 @@ function operatorEventOf(mode, answer, intake) {
40517
40984
  ...d.kind === "question" ? { question: renderOperatorQuestion(d) } : {},
40518
40985
  ...d.kind === "question" && d.proposal !== void 0 ? { proposal: d.proposal } : {},
40519
40986
  ...d.kind === "refusal" ? { refusalCause: d.cause, refusalText: d.text } : {},
40987
+ ...d.kind === "refusal" && d.cause === "provider" ? { providerFailure: d.providerFailure } : {},
40520
40988
  ...answer.attempts ? { attempts: answer.attempts } : {},
40521
40989
  ...intake ? { intake: { verdict: intake.verdict, reason: intake.reason } } : {},
40522
40990
  latencyMs: answer.latencyMs,
@@ -40558,6 +41026,7 @@ async function operatorStage(deps, ctx) {
40558
41026
  const { msg, mode } = ctx;
40559
41027
  const cfg = deps.config.config;
40560
41028
  let model = deps.operatorModel;
41029
+ let maxOutputTokens;
40561
41030
  if (!model) {
40562
41031
  const modelRef3 = cfg.defaults.models["general"];
40563
41032
  if (!modelRef3 || !deps.completions) {
@@ -40568,6 +41037,8 @@ async function operatorStage(deps, ctx) {
40568
41037
  const ref = parseModelRef(modelRef3);
40569
41038
  const effort2 = turnEffort(modelRef3, cfg.defaults.efforts?.["general"], cfg.providers);
40570
41039
  if (effort2.note) console.log(`[operator] ${msg.threadKey} effort: ${effort2.note}`);
41040
+ const card = resolveModelCard(modelRef3, cfg.providers, installedModelRegistry);
41041
+ maxOutputTokens = operatorMaxOutputTokens({ capField: card.capField });
40571
41042
  model = providerStructuredModel(deps.completions.get(ref.provider), ref.model, {
40572
41043
  ...effort2.request ? { effort: effort2.request } : {}
40573
41044
  });
@@ -40605,8 +41076,11 @@ async function operatorStage(deps, ctx) {
40605
41076
  ...pending2 ? { pendingQuestion: pending2.proposal !== void 0 ? { proposal: pending2.proposal } : {} } : {},
40606
41077
  ...ctx.owner ? { owner: ctx.owner } : {}
40607
41078
  },
40608
- model
41079
+ model,
41080
+ maxOutputTokens !== void 0 ? { maxOutputTokens } : {}
40609
41081
  );
41082
+ if (answer.operatorDiagnostic !== void 0)
41083
+ console.log(`[operator] ${msg.threadKey} provider refusal: ${answer.operatorDiagnostic}`);
40610
41084
  const event = operatorEventOf(mode, answer, ctx.intake);
40611
41085
  return event.outcome === "question" ? { ...event, request: redactAndCap(oneLine(msg.text), OPERATOR_REQUEST_CAP) } : event;
40612
41086
  }
@@ -40637,6 +41111,12 @@ async function executeOperatorDecision(deps, ctx) {
40637
41111
  await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
40638
41112
  return answered;
40639
41113
  }
41114
+ if (event.providerFailure !== void 0) {
41115
+ io.requestFailed?.();
41116
+ await io.reply(event.refusalText ?? renderProviderFailure(event.providerFailure, "ended"));
41117
+ await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
41118
+ return answered;
41119
+ }
40640
41120
  if (ctx.owner !== void 0 && !ownedDecisionRuns(event, ctx.owner, deps.commands)) {
40641
41121
  const confirmed = (event.binds ?? []).find((b) => b.confirmed);
40642
41122
  const request2 = confirmed !== void 0 ? presetBindOf(
@@ -40735,7 +41215,7 @@ async function executeOperatorDecision(deps, ctx) {
40735
41215
  if (!carried) await recordOperatorDecision(deps, msg, event, ctx.ending, ctx.trace);
40736
41216
  return answered;
40737
41217
  }
40738
- var OPERATOR_BIND_TOOL, OPERATOR_ASK_TOOL, OPERATOR_READ_TOOLS, OPERATOR_READS_MAX, OPERATOR_TIMEOUT_MS, OPERATOR_QUESTION_MARKER, OPERATOR_REQUEST_CAP;
41218
+ var OPERATOR_BIND_TOOL, OPERATOR_ASK_TOOL, OPERATOR_READ_TOOLS, OPERATOR_READS_MAX, OPERATOR_SCHEMA_REASKS_MAX, OPERATOR_TIMEOUT_MS, OPERATOR_QUESTION_MARKER, OPERATOR_REQUEST_CAP;
40739
41219
  var init_operator = __esm({
40740
41220
  "../../src/core/dispatch/operator.ts"() {
40741
41221
  "use strict";
@@ -40761,6 +41241,8 @@ var init_operator = __esm({
40761
41241
  init_seed();
40762
41242
  init_turnEffort();
40763
41243
  init_thread();
41244
+ init_modelCard();
41245
+ init_installedModelRegistry();
40764
41246
  init_route();
40765
41247
  OPERATOR_BIND_TOOL = "bind_preset";
40766
41248
  OPERATOR_ASK_TOOL = "ask";
@@ -40774,6 +41256,7 @@ var init_operator = __esm({
40774
41256
  providerModels: "provider_models"
40775
41257
  };
40776
41258
  OPERATOR_READS_MAX = 4;
41259
+ OPERATOR_SCHEMA_REASKS_MAX = 4;
40777
41260
  OPERATOR_TIMEOUT_MS = 2e4;
40778
41261
  OPERATOR_QUESTION_MARKER = "Did you mean:";
40779
41262
  OPERATOR_REQUEST_CAP = 600;
@@ -41406,6 +41889,8 @@ async function registerRun(deps, ctx) {
41406
41889
  seed,
41407
41890
  seedTurns,
41408
41891
  agentSource,
41892
+ decisionRecord,
41893
+ decisionRecordTask,
41409
41894
  modelCard,
41410
41895
  cardDecisions,
41411
41896
  route: route2
@@ -41504,6 +41989,8 @@ async function registerRun(deps, ctx) {
41504
41989
  agent: agent.name,
41505
41990
  agentSource,
41506
41991
  model: resolved.modelRef,
41992
+ ...decisionRecord !== void 0 ? { record: decisionRecord } : {},
41993
+ ...decisionRecordTask !== void 0 ? { recordTaskKey: decisionRecordTask } : {},
41507
41994
  traceId: root.traceId,
41508
41995
  // The harness the run is driven by (harness.md items 8 and 10): the
41509
41996
  // object the scopes' word for the preset picks off the roster — the
@@ -41570,6 +42057,8 @@ async function reserveRun(deps, ctx) {
41570
42057
  parentRunId,
41571
42058
  coordinator,
41572
42059
  seed,
42060
+ decisionRecord,
42061
+ decisionRecordTask,
41573
42062
  route: route2
41574
42063
  } = ctx;
41575
42064
  if (!resume && !restart) {
@@ -41593,6 +42082,8 @@ async function reserveRun(deps, ctx) {
41593
42082
  ...msg.authenticatedAs !== void 0 ? { authenticatedAs: msg.authenticatedAs } : {},
41594
42083
  ...msg.postedBy !== void 0 ? { postedBy: msg.postedBy } : {},
41595
42084
  ...resolved.effort !== void 0 ? { effort: resolved.effort } : {},
42085
+ ...decisionRecord !== void 0 ? { record: decisionRecord } : {},
42086
+ ...decisionRecordTask !== void 0 ? { recordTaskKey: decisionRecordTask } : {},
41596
42087
  ...repoCtx.ref !== void 0 ? { ref: repoCtx.ref } : {},
41597
42088
  ...repoCtx.headSha !== void 0 ? { headSha: repoCtx.headSha } : {},
41598
42089
  ...repoCtx.pr !== void 0 ? { pr: repoCtx.pr } : {},
@@ -42149,13 +42640,13 @@ function unquotePath(raw) {
42149
42640
  }
42150
42641
  function riskReasons(entry, paths) {
42151
42642
  const reasons = [];
42152
- const candidates = [...new Set(paths.filter(Boolean))];
42153
- const any = (re) => candidates.some((p) => re.test(p));
42643
+ const candidates2 = [...new Set(paths.filter(Boolean))];
42644
+ const any = (re) => candidates2.some((p) => re.test(p));
42154
42645
  if (any(MIGRATION_RE)) reasons.push("migration/schema");
42155
42646
  if (any(AUTH_RE)) reasons.push("auth/permission-sensitive");
42156
42647
  if (any(INFRA_RE)) reasons.push("infra/deploy config");
42157
42648
  if (entry.status === "deleted") reasons.push("whole-file deletion");
42158
- if (candidates.some((p) => LOCKFILES.has(basename2(p).toLowerCase()))) reasons.push("lockfile");
42649
+ if (candidates2.some((p) => LOCKFILES.has(basename2(p).toLowerCase()))) reasons.push("lockfile");
42159
42650
  const churn = entry.adds + entry.dels;
42160
42651
  if (churn >= LARGE_CHURN) reasons.push(`large change (${churn} lines)`);
42161
42652
  return reasons;
@@ -45202,6 +45693,103 @@ var init_run2 = __esm({
45202
45693
  }
45203
45694
  });
45204
45695
 
45696
+ // ../../src/core/decisionRecordReservation.ts
45697
+ function asksForDecisionRecord(text) {
45698
+ const marker = RECORD_MARKER.exec(text)?.[1]?.toLowerCase();
45699
+ if (marker !== void 0) return marker === "write" || marker === "yes" || marker === "true";
45700
+ for (const instruction of text.matchAll(POSITIVE_RECORD_INSTRUCTION)) {
45701
+ const sentenceStart = Math.max(
45702
+ text.lastIndexOf("\n", instruction.index),
45703
+ text.lastIndexOf(".", instruction.index),
45704
+ text.lastIndexOf("!", instruction.index),
45705
+ text.lastIndexOf("?", instruction.index),
45706
+ text.lastIndexOf(";", instruction.index)
45707
+ );
45708
+ const beforeVerb = text.slice(sentenceStart + 1, instruction.index);
45709
+ if (!NEGATED_INSTRUCTION.test(beforeVerb)) return true;
45710
+ }
45711
+ return false;
45712
+ }
45713
+ function decisionRecordTaskKey(...parts) {
45714
+ let hash = 0xcbf29ce484222325n;
45715
+ for (const byte of new TextEncoder().encode(parts.join("\n"))) {
45716
+ hash ^= BigInt(byte);
45717
+ hash = BigInt.asUintN(64, hash * 0x100000001b3n);
45718
+ }
45719
+ return hash.toString(16).padStart(16, "0");
45720
+ }
45721
+ var DECISION_RECORD_ENV, RECORD_MARKER, POSITIVE_RECORD_INSTRUCTION, NEGATED_INSTRUCTION, DECISION_RECORD_STORE_REFUSAL, DecisionRecordReservationUnavailableError, DecisionRecordAllocator;
45722
+ var init_decisionRecordReservation = __esm({
45723
+ "../../src/core/decisionRecordReservation.ts"() {
45724
+ "use strict";
45725
+ DECISION_RECORD_ENV = "SWITCHBOARD_DECISION_RECORD";
45726
+ RECORD_MARKER = /(?:^|\n)\s*(?:-\s*)?(?:\*\*)?(?:decision[- ]record|record)(?:\*\*)?\s*:\s*(write|yes|true|none|no|false)\b/im;
45727
+ POSITIVE_RECORD_INSTRUCTION = /\b(?:write|draft|create|add|author|produce|document)\b[^\n.!?;]{0,160}\b(?:technical\s+|architectural\s+|architecture\s+)?decision[- ]record\b/gi;
45728
+ NEGATED_INSTRUCTION = /(?:\b(?:do|does|did|must|should|will|can)\s+not(?:\s+\w+){0,3}|\b(?:don't|doesn't|didn't|mustn't|shouldn't|won't|can't)(?:\s+\w+){0,3}|\b(?:never|not|without)(?:\s+\w+){0,3})\s*$/i;
45729
+ DECISION_RECORD_STORE_REFUSAL = "\u26A0\uFE0F A record-writing task needs the durable coordinator store on the state Worker (`runHistory.worker`), but that store is unavailable, so no number was issued and nothing ran.";
45730
+ DecisionRecordReservationUnavailableError = class extends Error {
45731
+ constructor() {
45732
+ super("the durable coordinator store is unavailable");
45733
+ this.name = "DecisionRecordReservationUnavailableError";
45734
+ }
45735
+ };
45736
+ DecisionRecordAllocator = class {
45737
+ constructor(claims, durable) {
45738
+ this.claims = claims;
45739
+ this.durable = durable;
45740
+ }
45741
+ claims;
45742
+ durable;
45743
+ byTask = /* @__PURE__ */ new Map();
45744
+ claimed = /* @__PURE__ */ new Map();
45745
+ tails = /* @__PURE__ */ new Map();
45746
+ async reserve(repo, taskKey, existing) {
45747
+ const cacheKey = `${repo}
45748
+ ${taskKey}`;
45749
+ const cached = this.byTask.get(cacheKey);
45750
+ if (cached !== void 0) return cached;
45751
+ const previous = this.tails.get(repo) ?? Promise.resolve();
45752
+ let release;
45753
+ const tail = new Promise((resolve13) => {
45754
+ release = resolve13;
45755
+ });
45756
+ const queued = previous.then(() => tail);
45757
+ this.tails.set(repo, queued);
45758
+ await previous;
45759
+ try {
45760
+ const repeated = this.byTask.get(cacheKey);
45761
+ if (repeated !== void 0) return repeated;
45762
+ const used = new Set(await this.claims(repo));
45763
+ for (const number2 of this.claimedFor(repo)) used.add(number2);
45764
+ const prior = existing;
45765
+ const durable = await this.durable?.(repo, taskKey, used, prior);
45766
+ if (this.durable !== void 0 && durable === void 0) throw new DecisionRecordReservationUnavailableError();
45767
+ if (durable !== void 0 && !/^\d{4}$/.test(durable))
45768
+ throw new Error(`decision-record reservation store returned invalid number ${JSON.stringify(durable)}`);
45769
+ if (prior !== void 0 && durable !== void 0 && durable !== prior)
45770
+ throw new Error(`decision-record reservation store returned ${durable} for task already carrying ${prior}`);
45771
+ const highest = [...used].reduce((max, value) => /^\d{4}$/.test(value) ? Math.max(max, Number(value)) : max, 0);
45772
+ const number = durable ?? prior ?? String(highest + 1).padStart(4, "0");
45773
+ this.byTask.set(cacheKey, number);
45774
+ this.claimedFor(repo).add(number);
45775
+ return number;
45776
+ } finally {
45777
+ release();
45778
+ if (this.tails.get(repo) === queued) this.tails.delete(repo);
45779
+ }
45780
+ }
45781
+ claimedFor(repo) {
45782
+ let set = this.claimed.get(repo);
45783
+ if (set === void 0) {
45784
+ set = /* @__PURE__ */ new Set();
45785
+ this.claimed.set(repo, set);
45786
+ }
45787
+ return set;
45788
+ }
45789
+ };
45790
+ }
45791
+ });
45792
+
45205
45793
  // ../../src/core/harness/contract.ts
45206
45794
  function harnessFactsOf(value) {
45207
45795
  if (!isRecord4(value)) return void 0;
@@ -45211,7 +45799,7 @@ function harnessFactsOf(value) {
45211
45799
  return void 0;
45212
45800
  }
45213
45801
  function piFactsOf(v) {
45214
- const { harness: _harness, pid, logOffset, sessionFile, root, bearerHash, container, relaunches, ...rest } = v;
45802
+ const { harness: _harness, pid, logOffset, sessionFile, root, bearerHash, wire, container, relaunches, ...rest } = v;
45215
45803
  if (typeof pid !== "number" || typeof logOffset !== "number") return void 0;
45216
45804
  return {
45217
45805
  ...rest,
@@ -45222,6 +45810,7 @@ function piFactsOf(v) {
45222
45810
  ...typeof sessionFile === "string" ? { sessionFile } : {},
45223
45811
  ...typeof bearerHash === "string" ? { bearerHash } : {},
45224
45812
  ...typeof root === "string" ? { root } : {},
45813
+ ...WIRES.includes(wire) ? { wire } : {},
45225
45814
  ...typeof container === "string" ? { container } : {}
45226
45815
  };
45227
45816
  }
@@ -45272,6 +45861,7 @@ var SAID_ONCE_SUFFIX, isRecord4, relaunchesOf, isPiFacts, HarnessInterruptedErro
45272
45861
  var init_contract4 = __esm({
45273
45862
  "../../src/core/harness/contract.ts"() {
45274
45863
  "use strict";
45864
+ init_provider();
45275
45865
  SAID_ONCE_SUFFIX = " (said once: later events of this kind are not noted)";
45276
45866
  isRecord4 = (v) => typeof v === "object" && v !== null;
45277
45867
  relaunchesOf = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 ? v : 0;
@@ -46661,6 +47251,94 @@ var init_runBearers = __esm({
46661
47251
  }
46662
47252
  });
46663
47253
 
47254
+ // ../../src/core/modelProxy/providerFailureAuth.ts
47255
+ import { createHmac, randomBytes as randomBytes5, timingSafeEqual as timingSafeEqual3 } from "node:crypto";
47256
+ function payload(envelope, nonce) {
47257
+ return JSON.stringify([
47258
+ AUTH_VERSION,
47259
+ nonce,
47260
+ envelope.type,
47261
+ envelope.cause,
47262
+ envelope.message,
47263
+ envelope.schemaRejection ?? null
47264
+ ]);
47265
+ }
47266
+ function signature(envelope, nonce) {
47267
+ return createHmac("sha256", AUTH_KEY).update(payload(envelope, nonce)).digest();
47268
+ }
47269
+ function authenticateProxyProviderFailure(envelope) {
47270
+ const nonce = randomBytes5(NONCE_BYTES).toString("base64url");
47271
+ const mac = signature(envelope, nonce).toString("base64url");
47272
+ return { ...envelope, [PROXY_PROVIDER_FAILURE_AUTH_FIELD]: `${AUTH_VERSION}.${nonce}.${mac}` };
47273
+ }
47274
+ function parseBody2(value) {
47275
+ if (typeof value !== "string") return value;
47276
+ const text = value.trim();
47277
+ for (let at = text.indexOf("{"); at >= 0; at = text.indexOf("{", at + 1)) {
47278
+ try {
47279
+ return JSON.parse(text.slice(at));
47280
+ } catch {
47281
+ }
47282
+ }
47283
+ return void 0;
47284
+ }
47285
+ function candidates(value, into = []) {
47286
+ if (Array.isArray(value)) {
47287
+ for (const item of value) candidates(item, into);
47288
+ return into;
47289
+ }
47290
+ const row2 = isRecord5(value) ? value : void 0;
47291
+ if (!row2) return into;
47292
+ if (row2.type === "provider_failure") into.push(row2);
47293
+ for (const item of Object.values(row2)) candidates(item, into);
47294
+ return into;
47295
+ }
47296
+ function proxyProviderFailureIsAuthenticated(value) {
47297
+ const found = candidates(parseBody2(value));
47298
+ if (found.length !== 1) return false;
47299
+ const row2 = found[0];
47300
+ if (row2.type !== "provider_failure" || !isCause(row2.cause) || typeof row2.message !== "string") return false;
47301
+ const rejection = isRecord5(row2.schemaRejection) ? row2.schemaRejection : void 0;
47302
+ if (row2.schemaRejection !== void 0 && (typeof rejection?.tool !== "string" || rejection.tool.length === 0 || typeof rejection.keyword !== "string" || rejection.keyword.length === 0))
47303
+ return false;
47304
+ const schemaRejection = rejection !== void 0 ? { tool: rejection.tool, keyword: rejection.keyword } : void 0;
47305
+ const marker = row2[PROXY_PROVIDER_FAILURE_AUTH_FIELD];
47306
+ if (typeof marker !== "string") return false;
47307
+ const parts = marker.split(".");
47308
+ if (parts.length !== 3 || parts[0] !== AUTH_VERSION) return false;
47309
+ const [, nonce, encodedMac] = parts;
47310
+ if (!/^[A-Za-z0-9_-]{22}$/.test(nonce) || !/^[A-Za-z0-9_-]{43}$/.test(encodedMac)) return false;
47311
+ let offered;
47312
+ try {
47313
+ offered = Buffer.from(encodedMac, "base64url");
47314
+ } catch {
47315
+ return false;
47316
+ }
47317
+ const expected = signature(
47318
+ {
47319
+ type: "provider_failure",
47320
+ cause: row2.cause,
47321
+ message: row2.message,
47322
+ ...schemaRejection !== void 0 ? { schemaRejection } : {}
47323
+ },
47324
+ nonce
47325
+ );
47326
+ return offered.length === expected.length && timingSafeEqual3(offered, expected);
47327
+ }
47328
+ var PROXY_PROVIDER_FAILURE_AUTH_FIELD, AUTH_VERSION, AUTH_KEY, NONCE_BYTES, isRecord5, isCause;
47329
+ var init_providerFailureAuth = __esm({
47330
+ "../../src/core/modelProxy/providerFailureAuth.ts"() {
47331
+ "use strict";
47332
+ init_provider();
47333
+ PROXY_PROVIDER_FAILURE_AUTH_FIELD = "_switchboard_proxy_auth";
47334
+ AUTH_VERSION = "v1";
47335
+ AUTH_KEY = randomBytes5(32);
47336
+ NONCE_BYTES = 16;
47337
+ isRecord5 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
47338
+ isCause = (value) => typeof value === "string" && PROVIDER_FAILURE_CAUSES.includes(value);
47339
+ }
47340
+ });
47341
+
46664
47342
  // ../../src/core/harness/pi/compactionFallback.ts
46665
47343
  function compactionAskOf(body) {
46666
47344
  if (typeof body !== "object" || body === null) return void 0;
@@ -46928,7 +47606,7 @@ export default async function switchboardHarness(pi) {
46928
47606
 
46929
47607
  // ../../src/core/harness/pi/bridge.ts
46930
47608
  function describePiToolCall(tool, args) {
46931
- const input = isRecord5(args) ? args : void 0;
47609
+ const input = isRecord6(args) ? args : void 0;
46932
47610
  if (tool === "bash" && typeof input?.command === "string") return `$ ${input.command}`;
46933
47611
  for (const key of ["path", "name", "url", "query"]) {
46934
47612
  const v = input?.[key];
@@ -46937,8 +47615,8 @@ function describePiToolCall(tool, args) {
46937
47615
  return tool;
46938
47616
  }
46939
47617
  function piResultText(result) {
46940
- const content = isRecord5(result) && Array.isArray(result.content) ? result.content : [];
46941
- return content.filter((p) => isRecord5(p) && p.type === "text" && typeof p.text === "string").map((p) => String(p.text)).join("\n");
47618
+ const content = isRecord6(result) && Array.isArray(result.content) ? result.content : [];
47619
+ return content.filter((p) => isRecord6(p) && p.type === "text" && typeof p.text === "string").map((p) => String(p.text)).join("\n");
46942
47620
  }
46943
47621
  function piBashExit(text, isError) {
46944
47622
  const trailer = /Command exited with code (\d+)\s*$/.exec(text);
@@ -46947,12 +47625,14 @@ function piBashExit(text, isError) {
46947
47625
  if (prefixed.failed) return prefixed;
46948
47626
  return isError ? { failed: true } : { failed: false, exitCode: 0 };
46949
47627
  }
46950
- var PI_EVENT_DISPOSITION, DIALOG_METHODS, BOOKKEEPING_TOOL, POLICY_REFUSAL_STOP_REASONS, isRecord5, str6, PiBridge;
47628
+ var PI_EVENT_DISPOSITION, DIALOG_METHODS, BOOKKEEPING_TOOL, POLICY_REFUSAL_STOP_REASONS, isRecord6, str6, PiBridge;
46951
47629
  var init_bridge2 = __esm({
46952
47630
  "../../src/core/harness/pi/bridge.ts"() {
46953
47631
  "use strict";
46954
47632
  init_formatDuration();
46955
47633
  init_runEvents();
47634
+ init_providerFailureAuth();
47635
+ init_provider();
46956
47636
  init_contract4();
46957
47637
  init_windDown();
46958
47638
  init_compactionFallback();
@@ -46986,7 +47666,7 @@ var init_bridge2 = __esm({
46986
47666
  DIALOG_METHODS = /* @__PURE__ */ new Set(["select", "confirm", "input", "editor"]);
46987
47667
  BOOKKEEPING_TOOL = "update_status";
46988
47668
  POLICY_REFUSAL_STOP_REASONS = /* @__PURE__ */ new Set(["refusal", "sensitive", "content_filter"]);
46989
- isRecord5 = (v) => typeof v === "object" && v !== null;
47669
+ isRecord6 = (v) => typeof v === "object" && v !== null;
46990
47670
  str6 = (v) => typeof v === "string" ? v : String(v ?? "");
46991
47671
  PiBridge = class {
46992
47672
  constructor(deps) {
@@ -47080,10 +47760,10 @@ var init_bridge2 = __esm({
47080
47760
  this.modelCallOpen = true;
47081
47761
  break;
47082
47762
  case "message_start":
47083
- if (isRecord5(event.message) && event.message.role === "assistant") this.assistantStartedAt = this.deps.clock();
47763
+ if (isRecord6(event.message) && event.message.role === "assistant") this.assistantStartedAt = this.deps.clock();
47084
47764
  break;
47085
47765
  case "message_end":
47086
- if (isRecord5(event.message)) {
47766
+ if (isRecord6(event.message)) {
47087
47767
  if (event.message.role === "assistant") {
47088
47768
  const assistant = event.message;
47089
47769
  this.onAssistant(assistant, out);
@@ -47146,14 +47826,19 @@ var init_bridge2 = __esm({
47146
47826
  this.assistantStartedAt = void 0;
47147
47827
  }
47148
47828
  if (message4.stopReason === "error") {
47149
- out.providerError = redactAndCap(message4.errorMessage ?? "the model call failed", 400);
47829
+ const raw = message4.errorMessage ?? "the model call failed";
47830
+ out.providerFailure = classifyProviderFailure({
47831
+ error: raw,
47832
+ trustedEnvelope: proxyProviderFailureIsAuthenticated(raw)
47833
+ });
47834
+ out.providerError = redactAndCap(raw, 400);
47150
47835
  if (typeof message4.rawStopReason === "string" && POLICY_REFUSAL_STOP_REASONS.has(message4.rawStopReason))
47151
47836
  out.policyRefusal = true;
47152
47837
  return;
47153
47838
  }
47154
47839
  const content = Array.isArray(message4.content) ? message4.content : [];
47155
- const toolCalls = content.filter((b) => isRecord5(b) && b.type === "toolCall");
47156
- const text = content.filter((b) => isRecord5(b) && b.type === "text" && typeof b.text === "string").map((b) => String(b.text)).join("\n").trim();
47840
+ const toolCalls = content.filter((b) => isRecord6(b) && b.type === "toolCall");
47841
+ const text = content.filter((b) => isRecord6(b) && b.type === "text" && typeof b.text === "string").map((b) => String(b.text)).join("\n").trim();
47157
47842
  if (toolCalls.length === 0) {
47158
47843
  if (text) this.narrateHeld();
47159
47844
  this.answerText = text || this.heldAnswer || "";
@@ -47173,7 +47858,7 @@ var init_bridge2 = __esm({
47173
47858
  const span = this.agentSpan?.start(`tool.${tool}`);
47174
47859
  this.openTools.set(callId, { span, tool, judged: this.judgeGate });
47175
47860
  this.toolCalls++;
47176
- const input = isRecord5(event.args) ? event.args : void 0;
47861
+ const input = isRecord6(event.args) ? event.args : void 0;
47177
47862
  const command2 = tool === "bash" && typeof input?.command === "string" ? { command: redactAndCap(input.command, COMMAND_CAP) } : {};
47178
47863
  const bound = tool === "bash" && typeof input?.timeout === "number" && Number.isFinite(input.timeout) && input.timeout > 0 ? { boundMs: Math.round(input.timeout * 1e3) } : {};
47179
47864
  this.emit({
@@ -47231,7 +47916,7 @@ var init_bridge2 = __esm({
47231
47916
  }
47232
47917
  }
47233
47918
  onCompactionEnd(event, out) {
47234
- const result = isRecord5(event.result) ? event.result : void 0;
47919
+ const result = isRecord6(event.result) ? event.result : void 0;
47235
47920
  if (event.aborted === true || !result) {
47236
47921
  const why = typeof event.errorMessage === "string" ? event.errorMessage : void 0;
47237
47922
  this.note(
@@ -47750,7 +48435,7 @@ var init_reattach = __esm({
47750
48435
  // ../../src/core/harness/pi/mirror.ts
47751
48436
  import { isDeepStrictEqual } from "node:util";
47752
48437
  function boundOf(input) {
47753
- if (!isRecord6(input)) return {};
48438
+ if (!isRecord7(input)) return {};
47754
48439
  const t = input.timeout;
47755
48440
  return typeof t === "number" && Number.isFinite(t) && t > 0 ? { boundMs: secondsToMs(t) } : {};
47756
48441
  }
@@ -47759,7 +48444,7 @@ function partsOf2(content) {
47759
48444
  if (!Array.isArray(content)) return [];
47760
48445
  const parts = [];
47761
48446
  for (const block of content) {
47762
- if (!isRecord6(block)) continue;
48447
+ if (!isRecord7(block)) continue;
47763
48448
  if (block.type === "text" && typeof block.text === "string") parts.push({ type: "text", text: block.text });
47764
48449
  else if (block.type === "image" && typeof block.data === "string")
47765
48450
  parts.push({ type: "image", mediaType: String(block.mimeType ?? "image/png"), data: block.data });
@@ -47838,7 +48523,7 @@ function piSessionFile(messages, opts, compactions = []) {
47838
48523
  if (p.type === "text") content.push({ type: "text", text: p.text });
47839
48524
  else if (p.type === "tool_use") {
47840
48525
  toolNames.set(p.id, p.name);
47841
- content.push({ type: "toolCall", id: p.id, name: p.name, arguments: isRecord6(p.input) ? p.input : {} });
48526
+ content.push({ type: "toolCall", id: p.id, name: p.name, arguments: isRecord7(p.input) ? p.input : {} });
47842
48527
  }
47843
48528
  }
47844
48529
  const hasTool = content.some((b) => b.type === "toolCall");
@@ -47894,12 +48579,12 @@ function sessionIdOf(at) {
47894
48579
  const hex = at.toString(16).padStart(12, "0").slice(-12);
47895
48580
  return `00000000-0000-4000-8000-${hex}`;
47896
48581
  }
47897
- var isRecord6, PiMirror;
48582
+ var isRecord7, PiMirror;
47898
48583
  var init_mirror = __esm({
47899
48584
  "../../src/core/harness/pi/mirror.ts"() {
47900
48585
  "use strict";
47901
48586
  init_budgets();
47902
- isRecord6 = (v) => typeof v === "object" && v !== null;
48587
+ isRecord7 = (v) => typeof v === "object" && v !== null;
47903
48588
  PiMirror = class {
47904
48589
  constructor(deps) {
47905
48590
  this.deps = deps;
@@ -48108,6 +48793,7 @@ function piLaunchArgs(spec) {
48108
48793
  }
48109
48794
  function piLaunchEnv(spec, bearer) {
48110
48795
  return {
48796
+ ...spec.environment,
48111
48797
  [RUN_BEARER_ENV]: bearer,
48112
48798
  [HARNESS_URL_ENV]: spec.harnessUrl,
48113
48799
  SWITCHBOARD_RUN_ID: spec.runId,
@@ -48277,15 +48963,15 @@ function piStreamOptions(api, req, key) {
48277
48963
  if (api === "openai-responses") return { ...shared, toolChoice: { type: "function", name: req.toolChoice.name } };
48278
48964
  return { ...shared, toolChoice: { type: "function", function: { name: req.toolChoice.name } } };
48279
48965
  }
48280
- function anthropicParallelOff(payload) {
48281
- const p = payload;
48966
+ function anthropicParallelOff(payload2) {
48967
+ const p = payload2;
48282
48968
  return {
48283
- ...payload,
48969
+ ...payload2,
48284
48970
  tool_choice: { ...p.tool_choice, disable_parallel_tool_use: true }
48285
48971
  };
48286
48972
  }
48287
- function openAiParallelOff(payload) {
48288
- return { ...payload, parallel_tool_calls: false };
48973
+ function openAiParallelOff(payload2) {
48974
+ return { ...payload2, parallel_tool_calls: false };
48289
48975
  }
48290
48976
  function toPiContext(req, model, clock4) {
48291
48977
  const callNames = /* @__PURE__ */ new Map();
@@ -48378,9 +49064,18 @@ function visiblePart(part) {
48378
49064
  return void 0;
48379
49065
  }
48380
49066
  }
48381
- function fromPiMessage(message4, provider) {
49067
+ function fromPiMessage(message4, provider, tools) {
48382
49068
  if (message4.stopReason === "error" || message4.stopReason === "aborted") {
48383
- throw new Error(`Provider "${provider}" (${message4.model}): ${message4.errorMessage ?? "the model call failed"}`);
49069
+ const raw = message4.errorMessage ?? "the model call failed";
49070
+ const failure2 = classifyProviderFailure({ error: raw, provider, model: message4.model });
49071
+ const schemaRejection = failure2.cause === "request-rejected" && failure2.status === 400 ? providerSchemaRejectionOf(raw, tools) : void 0;
49072
+ throw new ProviderFailure(failure2.cause, {
49073
+ ...failure2.status !== void 0 ? { status: failure2.status } : {},
49074
+ ...failure2.provider !== void 0 ? { provider: failure2.provider } : {},
49075
+ ...failure2.model !== void 0 ? { model: failure2.model } : {},
49076
+ ...failure2.operatorUrl !== void 0 ? { operatorUrl: failure2.operatorUrl } : {},
49077
+ ...schemaRejection !== void 0 ? { schemaRejection } : {}
49078
+ });
48384
49079
  }
48385
49080
  const content = [];
48386
49081
  for (const part of message4.content) {
@@ -48481,7 +49176,12 @@ var init_piAi = __esm({
48481
49176
  let key;
48482
49177
  if (this.keyEnv !== void 0) {
48483
49178
  const secret2 = this.secrets.named(this.keyEnv);
48484
- if (!secret2) throw new Error(`Provider "${this.name}": ${this.keyEnv} is not set`);
49179
+ if (!secret2)
49180
+ throw new ProviderFailure("key-absent", {
49181
+ provider: this.name,
49182
+ model: req.model,
49183
+ keyVariable: this.keyEnv
49184
+ });
48485
49185
  key = secret2.reveal();
48486
49186
  }
48487
49187
  const model = this.model(req.model, req.maxTokens, req.effort !== void 0);
@@ -48489,8 +49189,21 @@ var init_piAi = __esm({
48489
49189
  ...piStreamOptions(this.api, req, key),
48490
49190
  ...this.fetchImpl ? { fetch: this.fetchImpl } : {}
48491
49191
  };
48492
- const message4 = await this.api$().stream(model, toPiContext(req, model, this.clock), options).result();
48493
- return fromPiMessage(message4, this.name);
49192
+ try {
49193
+ const message4 = await this.api$().stream(model, toPiContext(req, model, this.clock), options).result();
49194
+ return fromPiMessage(message4, this.name, req.tools);
49195
+ } catch (err2) {
49196
+ const failure2 = providerFailureOf(err2);
49197
+ const schemaRejection = failure2.schemaRejection ?? (failure2.cause === "request-rejected" && failure2.status === 400 ? providerSchemaRejectionOf(err2, req.tools) : void 0);
49198
+ throw new ProviderFailure(failure2.cause, {
49199
+ ...failure2.status !== void 0 ? { status: failure2.status } : {},
49200
+ provider: this.name,
49201
+ model: req.model,
49202
+ ...failure2.operatorUrl !== void 0 ? { operatorUrl: failure2.operatorUrl } : {},
49203
+ ...schemaRejection !== void 0 ? { schemaRejection } : {},
49204
+ ...failure2.keyVariable !== void 0 ? { keyVariable: failure2.keyVariable } : failure2.cause === "key-invalid" && this.keyEnv !== void 0 ? { keyVariable: this.keyEnv } : {}
49205
+ });
49206
+ }
48494
49207
  }
48495
49208
  /** pi's implementation of this provider's API, loaded on the first call and
48496
49209
  * kept; a scripted one stands in for it in tests. */
@@ -48593,7 +49306,7 @@ function judgeToolCall(tool, input, ctx) {
48593
49306
  ctx.identity === "none" ? `${tool} is the \`${bundle}\` bundle: a run without a workspace (identity none) has none of pi's own tools` : `${tool} is the \`${bundle}\` bundle, outside the ${ctx.identity} identity's reach`
48594
49307
  );
48595
49308
  }
48596
- const args = isRecord7(input) ? input : {};
49309
+ const args = isRecord8(input) ? input : {};
48597
49310
  if (tool === "bash") return judgeBash(args.command, args.timeout, ctx);
48598
49311
  if (bundle === "files" || bundle === "write-files") return judgePath(args.path, ctx);
48599
49312
  return allowed;
@@ -48671,7 +49384,7 @@ function judgePath(path, ctx) {
48671
49384
  if (rel.startsWith("..") || isAbsolute5(rel)) return refused4(`path \u2014 \`${path}\` resolves outside the checkout`);
48672
49385
  return allowed;
48673
49386
  }
48674
- var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, NONE_REACH, allowed, refused4, outside, isRecord7, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE, OPENCODE_ACTION_TO_TOOL_WORD, REDIRECTION;
49387
+ var PI_TOOL_BUNDLES, CODING_REACH, READ_REACH, NONE_REACH, allowed, refused4, outside, isRecord8, CREDENTIAL_FILE, ENV_DUMP, CREDENTIAL_VAR, MERGE_OR_APPROVE, GIT_THEN_PUSH, GIT_PUSH, ANY_PUSH, GH_WRITE_VERB, WRITE_METHOD, GH_API_WRITE, CURL_GITHUB_WRITE, OPENCODE_ACTION_TO_TOOL_WORD, REDIRECTION;
48675
49388
  var init_toolRules = __esm({
48676
49389
  "../../src/core/harness/pi/toolRules.ts"() {
48677
49390
  "use strict";
@@ -48701,7 +49414,7 @@ var init_toolRules = __esm({
48701
49414
  allowed = { verdict: "allowed" };
48702
49415
  refused4 = (reason2) => ({ verdict: "refused", reason: reason2 });
48703
49416
  outside = (reason2) => ({ verdict: "outside-profile", reason: reason2 });
48704
- isRecord7 = (v) => typeof v === "object" && v !== null;
49417
+ isRecord8 = (v) => typeof v === "object" && v !== null;
48705
49418
  CREDENTIAL_FILE = /(^|[\s/'"`])(\.git\/)?(github-credentials|\.git-credentials)($|[\s'"`|;&])/;
48706
49419
  ENV_DUMP = /(^|[;&|(]\s*)((printenv|env)(\s+-[A-Za-z0-9-]+)*|export\s+-p|declare\s+-[a-zA-Z]*[px][a-zA-Z]*|set)\s*($|[|;&>)])|\/proc\/(self|\d+)\/environ|process\.env(?![.[\w])/;
48707
49420
  CREDENTIAL_VAR = /\$\{?[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b|printenv\s+[A-Z][A-Z0-9_]*(_API_KEY|_TOKEN|_SECRET|_BEARER|_PASSWORD)\b/;
@@ -48987,13 +49700,6 @@ async function locatePi(facts2, container, here) {
48987
49700
  function isAbortedProviderError(message4) {
48988
49701
  return /\baborted\b/i.test(message4);
48989
49702
  }
48990
- function isTransientProviderError(message4) {
48991
- return /stream ended before message_stop|stream ended without finish_reason|ended before completion/i.test(message4) || /^(?:AbortError:\s*)?(?:This|The) operation was aborted\.?$/i.test(message4.trim()) || /ECONNRESET|ETIMEDOUT|EPIPE|socket hang up|fetch failed|other side closed|network (error|failure)|(connection|stream) (reset|closed|terminated)|timed? ?out/i.test(
48992
- message4
48993
- ) || /^terminated$/i.test(message4.trim()) || /overloaded/i.test(message4) || /^\s*(408|425|429|5\d\d)\b/.test(message4) || /<html[\s>][\s\S]{0,4000}\b(?:bad gateway|service unavailable|gateway timeout)\b/i.test(message4) || /(?:\bhttp\b[^a-z0-9]{0,8}|\bstatus(?: code)?\b[^0-9]{0,5}|\berror\b[^0-9]{0,5}|\bapi error\b[^0-9]{0,5})(408|425|429|5\d\d)\b/i.test(
48994
- message4
48995
- );
48996
- }
48997
49703
  function piStartFailureOf(err2) {
48998
49704
  if (!(err2 instanceof HarnessContainerError) || err2.operation !== "send") return void 0;
48999
49705
  const timeout = err2.message.match(/exit 124:.*?(\d+)s command timeout/i);
@@ -49075,6 +49781,7 @@ async function runPiHarnessOpen(deps, run2) {
49075
49781
  const { container, clock: clock4 } = deps;
49076
49782
  const now = () => clock4();
49077
49783
  const recorded = run2.resume?.facts;
49784
+ const runWire = piRunWire({ model: { providerType: run2.model.providerType }, card: run2.card });
49078
49785
  if (recorded !== void 0 && !isPiFacts(recorded)) {
49079
49786
  const mismatch = new HarnessMismatchError("pi", recorded.harness);
49080
49787
  run2.onProgress?.(mismatch.message);
@@ -49092,13 +49799,9 @@ async function runPiHarnessOpen(deps, run2) {
49092
49799
  run2.onProgress?.(summary);
49093
49800
  emit({ type: "run_note", kind, summary, ...mode ? { mode } : {} });
49094
49801
  };
49095
- const policyRefused = (explanation) => {
49096
- emit({
49097
- type: "run_note",
49098
- kind: "policy_refusal",
49099
- summary: `the model refused the call under the provider's usage policy: ${explanation}`
49100
- });
49101
- return new ModelPolicyRefusedError(explanation);
49802
+ const policyRefused = (failure2) => {
49803
+ emit({ type: "run_note", kind: "policy_refusal", summary: renderProviderFailure(failure2.cause, "parked") });
49804
+ return new ModelPolicyRefusedError(failure2);
49102
49805
  };
49103
49806
  const bridge = new PiBridge({
49104
49807
  emit,
@@ -49298,11 +50001,12 @@ async function runPiHarnessOpen(deps, run2) {
49298
50001
  if (located2 === "another-container") {
49299
50002
  elsewhere = `pi is elsewhere: the row's pi (pid ${recorded.pid}) ran in container ${recorded.container}, not the one this run was handed (${here}), so it was neither probed nor ended here`;
49300
50003
  } else if (located2 === "alive-here") {
49301
- if (recorded.root !== void 0 && honoured(recorded.bearerHash)) {
50004
+ const wireMismatch = recorded.wire !== void 0 && recorded.wire !== runWire ? `was configured for ${recorded.wire} while provider ${run2.model.provider} now declares ${runWire}` : void 0;
50005
+ if (recorded.root !== void 0 && wireMismatch === void 0 && honoured(recorded.bearerHash)) {
49302
50006
  reattached = true;
49303
50007
  paths = piRunPathsAt(recorded.root);
49304
50008
  } else {
49305
- ended = recorded.root === void 0 ? "named no directory for its pi" : "carried no bearer this generation could honour for its pi";
50009
+ ended = recorded.root === void 0 ? "named no directory for its pi" : wireMismatch ?? "carried no bearer this generation could honour for its pi";
49306
50010
  await container.kill(recorded.pid).catch(() => {
49307
50011
  });
49308
50012
  }
@@ -49310,7 +50014,11 @@ async function runPiHarnessOpen(deps, run2) {
49310
50014
  }
49311
50015
  if (reattached && recorded && paths !== void 0) {
49312
50016
  pid = recorded.pid;
49313
- facts2 = { ...recorded, ...recorded.container === void 0 && here !== void 0 ? { container: here } : {} };
50017
+ facts2 = {
50018
+ ...recorded,
50019
+ wire: runWire,
50020
+ ...recorded.container === void 0 && here !== void 0 ? { container: here } : {}
50021
+ };
49314
50022
  mirrored = recorded.logOffset;
49315
50023
  transport = new PiRpcTransport({
49316
50024
  container,
@@ -49341,6 +50049,7 @@ async function runPiHarnessOpen(deps, run2) {
49341
50049
  ...run2.effort ? { effort: run2.effort } : {},
49342
50050
  identity: run2.agent.identity,
49343
50051
  system: run2.system,
50052
+ ...run2.environment !== void 0 ? { environment: run2.environment } : {},
49344
50053
  relayTools: run2.tools.map((t) => t.name),
49345
50054
  ...deps.compaction ? { compaction: deps.compaction } : {}
49346
50055
  };
@@ -49389,7 +50098,7 @@ async function runPiHarnessOpen(deps, run2) {
49389
50098
  model: {
49390
50099
  provider: run2.model.provider,
49391
50100
  id: run2.model.id,
49392
- api: piApiFor(piRunWire({ model: { providerType: run2.model.providerType }, card: run2.card }))
50101
+ api: piApiFor(runWire)
49393
50102
  },
49394
50103
  at: now()
49395
50104
  },
@@ -49434,6 +50143,7 @@ async function runPiHarnessOpen(deps, run2) {
49434
50143
  logOffset: 0,
49435
50144
  root: paths.dir,
49436
50145
  ...bearerHash !== void 0 ? { bearerHash } : {},
50146
+ wire: runWire,
49437
50147
  ...here !== void 0 ? { container: here } : {},
49438
50148
  relaunches: recorded?.relaunches ?? 0
49439
50149
  };
@@ -49760,7 +50470,7 @@ ${stagedLine}` : prompt;
49760
50470
  for (const command2 of unsent) sends.send(command2);
49761
50471
  return outcome;
49762
50472
  };
49763
- let providerError;
50473
+ let providerFailure;
49764
50474
  let providerRefusal = false;
49765
50475
  let providerRetries = 0;
49766
50476
  let containerSaid;
@@ -49866,7 +50576,7 @@ ${stagedLine}` : prompt;
49866
50576
  }
49867
50577
  if (obs.message?.role === "user") steerEchoed(obs.message);
49868
50578
  if (obs.message && await mirror.onMessage(obs.message, bridge.turns)) held2();
49869
- if (obs.compactionFailed !== void 0 && !isTransientProviderError(obs.compactionFailed)) {
50579
+ if (obs.compactionFailed !== void 0 && !providerFailureParks(classifyProviderFailure({ error: obs.compactionFailed }).cause)) {
49870
50580
  compactionFailure = obs.compactionFailed;
49871
50581
  if (!catchingUp && run2.onCompactionFailed) {
49872
50582
  try {
@@ -49898,27 +50608,31 @@ ${stagedLine}` : prompt;
49898
50608
  }
49899
50609
  }
49900
50610
  if (obs.providerError !== void 0) {
50611
+ const failure2 = obs.providerFailure ?? providerFailureOf(obs.providerError);
49901
50612
  if (catchingUp)
49902
- note("harness_error", `a model call failed while the bot was away (${obs.providerError}); continuing`);
50613
+ note(
50614
+ "harness_error",
50615
+ `a model call failed while the bot was away (${renderProviderFailure(failure2.cause, "parked")}); continuing`
50616
+ );
49903
50617
  else if (cutAborted && !finaleAborted && isAbortedProviderError(obs.providerError)) {
49904
50618
  cutAborted = false;
49905
- } else if (obs.policyRefusal !== true && !writeUp && !finaleAborted && isTransientProviderError(obs.providerError)) {
49906
- heldTurn = obs.providerError;
50619
+ } else if (obs.policyRefusal !== true && !writeUp && !finaleAborted && providerFailureParks(failure2.cause)) {
50620
+ heldTurn = failure2;
49907
50621
  parkSettled = false;
49908
50622
  const backoff = PROVIDER_RETRY_BACKOFFS_MS[Math.min(providerRetries, PROVIDER_RETRY_BACKOFFS_MS.length - 1)];
49909
50623
  run2.onProgress?.(
49910
50624
  `the model provider did not complete the call; the turn is held and retry ${providerRetries + 1} waits ${backoff / 1e3}s inside this run's lease`
49911
50625
  );
49912
50626
  } else if (writeUp || finaleAborted) {
49913
- writeUpFailed = obs.providerError;
49914
- note("harness_error", windDownFailureNote(obs.providerError));
50627
+ writeUpFailed = renderProviderFailure(failure2.cause, "parked");
50628
+ note("harness_error", windDownFailureNote(writeUpFailed));
49915
50629
  } else {
49916
- providerError = obs.providerError;
50630
+ providerFailure = failure2;
49917
50631
  providerRefusal = obs.policyRefusal === true;
49918
50632
  }
49919
50633
  }
49920
50634
  if (obs.settled && !catchingUp) {
49921
- if (heldTurn !== void 0 && providerError === void 0 && !hardStopped && !writeUp && !finaleAborted) {
50635
+ if (heldTurn !== void 0 && providerFailure === void 0 && !hardStopped && !writeUp && !finaleAborted) {
49922
50636
  parkSettled = true;
49923
50637
  if (providerRetryWait === void 0) {
49924
50638
  const backoff = PROVIDER_RETRY_BACKOFFS_MS[Math.min(providerRetries, PROVIDER_RETRY_BACKOFFS_MS.length - 1)];
@@ -50010,12 +50724,10 @@ ${stagedLine}` : prompt;
50010
50724
  answer = HARD_STOP_MESSAGE;
50011
50725
  } else {
50012
50726
  if (bypass) throw bypass;
50013
- if (providerError !== void 0) {
50014
- if (providerRefusal) throw policyRefused(providerError);
50015
- if (isTransientProviderError(providerError)) throw new ModelTransientFailureError(PROVIDER_RETRY_EXHAUSTED);
50016
- throw new Error(
50017
- providerRetries > 0 ? `the model call failed after ${providerRetries} ${providerRetries === 1 ? "retry" : "retries"}: ${providerError}` : `the model call failed: ${providerError}`
50018
- );
50727
+ if (providerFailure !== void 0) {
50728
+ if (providerRefusal) throw policyRefused(providerFailure);
50729
+ if (providerFailureParks(providerFailure.cause)) throw new ModelTransientFailureError(PROVIDER_RETRY_EXHAUSTED);
50730
+ throw providerFailure;
50019
50731
  }
50020
50732
  const text = bridge.answer() ?? "";
50021
50733
  ending = windDownEndingOf(writeUp ?? (stopMode === "soft" ? { kind: "soft" } : void 0), text, writeUpFailed);
@@ -50050,7 +50762,57 @@ ${stagedLine}` : prompt;
50050
50762
  writeUpFailed = void 0;
50051
50763
  const id = `${ids.prompt}:follow-up:${++followUps}`;
50052
50764
  let turnSettled = false;
50053
- let turnError;
50765
+ let turnFailure;
50766
+ let turnHeld;
50767
+ let turnParkSettled = false;
50768
+ let turnRetryReady = false;
50769
+ let turnRetryWait;
50770
+ let turnRetryGeneration = 0;
50771
+ let turnRetries = 0;
50772
+ let turnRetryInFlight;
50773
+ let turnReissueInFlight;
50774
+ let turnReissues = 0;
50775
+ let turnReissueRows = [];
50776
+ let turnReissueInFlightRows = [];
50777
+ const clearTurnProviderHold = () => {
50778
+ turnHeld = void 0;
50779
+ turnParkSettled = false;
50780
+ turnRetryReady = false;
50781
+ turnRetryWait = void 0;
50782
+ turnRetryGeneration += 1;
50783
+ turnReissueRows = [];
50784
+ turnReissueInFlightRows = [];
50785
+ };
50786
+ const maybeReissueTurn = () => {
50787
+ if (turnHeld === void 0 || !turnParkSettled || turnReissueInFlight !== void 0 || turnRetryInFlight !== void 0 || turnReissueRows.length === 0 || hardStopped || writeUp !== void 0 || finaleAborted)
50788
+ return;
50789
+ turnReissues += 1;
50790
+ turnReissueInFlight = turnReissues === 1 ? `${id}:reissue` : `${id}:reissue:${turnReissues}`;
50791
+ turnReissueInFlightRows = turnReissueRows.splice(0);
50792
+ turnParkSettled = false;
50793
+ turnSettled = false;
50794
+ sends.send({ id: turnReissueInFlight, type: "prompt", message: REISSUE_PROMPT });
50795
+ };
50796
+ const drainTurnReissues = () => {
50797
+ const inputs = run2.inbox?.drain() ?? [];
50798
+ if (inputs.length === 0) {
50799
+ maybeReissueTurn();
50800
+ return;
50801
+ }
50802
+ const reissued = inputs.filter((item) => item.userId === PLANE_ACTOR_ID && isReissueSteerText(item.text));
50803
+ const waiting2 = reissued.length === 0 ? inputs : inputs.filter((item) => !reissued.includes(item));
50804
+ if (waiting2.length > 0) run2.inbox?.requeue(waiting2);
50805
+ for (const item of reissued) {
50806
+ emit({
50807
+ type: "input",
50808
+ text: redactSecrets(item.text),
50809
+ messageId: followUpMessageId(item),
50810
+ ...item.userName ? { source: { user: item.userName } } : {}
50811
+ });
50812
+ }
50813
+ turnReissueRows.push(...reissued);
50814
+ maybeReissueTurn();
50815
+ };
50054
50816
  let turnRefusal = false;
50055
50817
  let turnFailed = false;
50056
50818
  const turnCheck = () => {
@@ -50068,6 +50830,19 @@ ${stagedLine}` : prompt;
50068
50830
  }
50069
50831
  return;
50070
50832
  }
50833
+ drainTurnReissues();
50834
+ if (turnHeld !== void 0) {
50835
+ if (now() >= turnDeadline) throw new ModelTransientFailureError(PROVIDER_RETRY_EXHAUSTED);
50836
+ if (turnParkSettled && turnRetryReady && turnRetryInFlight === void 0 && turnReissueInFlight === void 0) {
50837
+ turnRetryReady = false;
50838
+ turnRetryWait = void 0;
50839
+ turnRetries += 1;
50840
+ turnRetryInFlight = turnRetries === 1 ? `${id}:retry` : `${id}:retry:${turnRetries}`;
50841
+ turnParkSettled = false;
50842
+ turnSettled = false;
50843
+ sends.send({ id: turnRetryInFlight, type: "prompt", message: PROVIDER_RETRY_PROMPT });
50844
+ }
50845
+ }
50071
50846
  if (run2.control?.requested === "soft") {
50072
50847
  note("stopped", softStopNote(), "soft");
50073
50848
  startWriteUp({ kind: "soft" }, SOFT_STOP_INSTRUCTION);
@@ -50135,19 +50910,69 @@ ${stagedLine}` : prompt;
50135
50910
  abortPi();
50136
50911
  break;
50137
50912
  }
50138
- if (obs.response) noteEcho(obs.response);
50913
+ if (obs.response) {
50914
+ noteEcho(obs.response);
50915
+ if (turnReissueInFlight !== void 0 && obs.response.id === turnReissueInFlight) {
50916
+ turnReissueInFlight = void 0;
50917
+ if (obs.response.success === false) {
50918
+ turnReissueRows.unshift(...turnReissueInFlightRows);
50919
+ turnReissueInFlightRows = [];
50920
+ turnParkSettled = false;
50921
+ note(
50922
+ "harness_error",
50923
+ `the follow-up turn's reissue prompt was refused (${String(obs.response.error ?? "no reason")}); the turn stays held for the next settle`
50924
+ );
50925
+ } else {
50926
+ mirror.inboxConsumedSeq = Math.max(
50927
+ mirror.inboxConsumedSeq,
50928
+ ...turnReissueInFlightRows.map((item) => item.ledgerSeq ?? 0)
50929
+ );
50930
+ clearTurnProviderHold();
50931
+ }
50932
+ }
50933
+ if (turnRetryInFlight !== void 0 && obs.response.id === turnRetryInFlight) {
50934
+ turnRetryInFlight = void 0;
50935
+ if (obs.response.success === false) {
50936
+ turnParkSettled = false;
50937
+ note("harness_error", "the follow-up turn's model retry prompt was refused; the failure remains held");
50938
+ } else clearTurnProviderHold();
50939
+ }
50940
+ }
50139
50941
  if (obs.response?.id === id && obs.response.success === false)
50140
50942
  throw new PromptRefused(String(obs.response.error ?? "no reason"));
50141
50943
  if (obs.providerError !== void 0) {
50944
+ const failure2 = obs.providerFailure ?? providerFailureOf(obs.providerError);
50142
50945
  if (writeUp || finaleAborted) {
50143
- writeUpFailed = obs.providerError;
50144
- note("harness_error", windDownFailureNote(obs.providerError, "turn"));
50946
+ writeUpFailed = renderProviderFailure(failure2.cause, "parked");
50947
+ note("harness_error", windDownFailureNote(writeUpFailed, "turn"));
50948
+ } else if (obs.policyRefusal !== true && providerFailureParks(failure2.cause)) {
50949
+ turnHeld = failure2;
50950
+ turnFailure = void 0;
50951
+ turnRefusal = false;
50952
+ turnParkSettled = false;
50953
+ const backoff = PROVIDER_RETRY_BACKOFFS_MS[Math.min(turnRetries, PROVIDER_RETRY_BACKOFFS_MS.length - 1)];
50954
+ run2.onProgress?.(
50955
+ `the model provider did not complete the follow-up call; the turn is held and retry ${turnRetries + 1} waits ${backoff / 1e3}s inside this run's lease`
50956
+ );
50145
50957
  } else {
50146
- turnError = obs.providerError;
50958
+ turnFailure = failure2;
50147
50959
  turnRefusal = obs.policyRefusal === true;
50148
50960
  }
50149
50961
  }
50150
50962
  if (obs.settled) {
50963
+ if (turnHeld !== void 0 && turnFailure === void 0 && !hardStopped && !writeUp && !finaleAborted) {
50964
+ turnParkSettled = true;
50965
+ if (turnRetryWait === void 0) {
50966
+ const backoff = PROVIDER_RETRY_BACKOFFS_MS[Math.min(turnRetries, PROVIDER_RETRY_BACKOFFS_MS.length - 1)];
50967
+ const generation = ++turnRetryGeneration;
50968
+ turnRetryWait = deps.sleep(backoff).then(() => {
50969
+ if (turnRetryGeneration === generation) turnRetryReady = true;
50970
+ });
50971
+ }
50972
+ turnCheck();
50973
+ if (hardStopped) break;
50974
+ continue;
50975
+ }
50151
50976
  turnSettled = true;
50152
50977
  break;
50153
50978
  }
@@ -50205,9 +51030,9 @@ ${stagedLine}` : prompt;
50205
51030
  `pi exited before the turn settled${tail2.trim() ? `: ${redactAndCap(tail2.trim(), 400)}` : ""}`
50206
51031
  );
50207
51032
  }
50208
- if (turnError !== void 0) {
50209
- if (turnRefusal) throw policyRefused(turnError);
50210
- throw new Error(`the model call failed: ${turnError}`);
51033
+ if (turnFailure !== void 0) {
51034
+ if (turnRefusal) throw policyRefused(turnFailure);
51035
+ throw turnFailure;
50211
51036
  }
50212
51037
  const text = bridge.answer() ?? "";
50213
51038
  const turnEnding = windDownEndingOf(writeUp, text, writeUpFailed);
@@ -50217,6 +51042,7 @@ ${stagedLine}` : prompt;
50217
51042
  if (err2 instanceof HarnessControlFileLostError) note("harness_error", err2.message);
50218
51043
  throw err2;
50219
51044
  } finally {
51045
+ turnRetryGeneration += 1;
50220
51046
  deps.bearers?.clearTurn(run2.runId);
50221
51047
  live.toolContext = runContext;
50222
51048
  live.rules = rules;
@@ -50239,6 +51065,7 @@ var CALL_SEEN_WAIT_MS, CALL_SEEN_TICK_MS, PROVIDER_RETRY_PROMPT, REISSUE_PROMPT,
50239
51065
  var init_harness = __esm({
50240
51066
  "../../src/core/harness/pi/harness.ts"() {
50241
51067
  "use strict";
51068
+ init_provider();
50242
51069
  init_contract4();
50243
51070
  init_windDown();
50244
51071
  init_runBearers();
@@ -50286,14 +51113,14 @@ var init_harness = __esm({
50286
51113
  this.name = "PiContainerReplacedError";
50287
51114
  }
50288
51115
  };
50289
- POLICY_REFUSAL_REPLY = "the model refused this request under its usage policy \u2014 rephrase it and the thread continues";
51116
+ POLICY_REFUSAL_REPLY = renderProviderFailure("permanent");
50290
51117
  ModelPolicyRefusedError = class extends Error {
50291
- constructor(providerMessage) {
51118
+ constructor(providerFailure) {
50292
51119
  super(POLICY_REFUSAL_REPLY);
50293
- this.providerMessage = providerMessage;
51120
+ this.providerFailure = providerFailure;
50294
51121
  this.name = "ModelPolicyRefusedError";
50295
51122
  }
50296
- providerMessage;
51123
+ providerFailure;
50297
51124
  };
50298
51125
  ModelTransientFailureError = class extends Error {
50299
51126
  constructor(message4) {
@@ -50498,8 +51325,8 @@ async function postReviewComment(target, body) {
50498
51325
  const clipped = body.length > MAX_COMMENT_CHARS ? `${body.slice(0, MAX_COMMENT_CHARS)}
50499
51326
 
50500
51327
  _(review truncated to fit GitHub's comment size limit)_` : body;
50501
- const payload = { event: "COMMENT", body: clipped };
50502
- if (target.commitId) payload.commit_id = target.commitId;
51328
+ const payload2 = { event: "COMMENT", body: clipped };
51329
+ if (target.commitId) payload2.commit_id = target.commitId;
50503
51330
  const res = await fetch(`https://api.github.com/repos/${target.repo}/pulls/${target.number}/reviews`, {
50504
51331
  method: "POST",
50505
51332
  headers: {
@@ -50508,7 +51335,7 @@ _(review truncated to fit GitHub's comment size limit)_` : body;
50508
51335
  "content-type": "application/json",
50509
51336
  "user-agent": "switchboard"
50510
51337
  },
50511
- body: JSON.stringify(payload),
51338
+ body: JSON.stringify(payload2),
50512
51339
  signal: AbortSignal.timeout(15e3)
50513
51340
  });
50514
51341
  if (!res.ok) {
@@ -50828,8 +51655,8 @@ async function runCodingPrPostStep(input) {
50828
51655
  }
50829
51656
  return void 0;
50830
51657
  }
50831
- const candidates = [target.baseRef, target.bindingRef, target.resolvedRef];
50832
- const base = prDescription ? await resolveBaseRefLazy(candidates, repo, input.fetchRepoInfo) : resolveBaseRef(candidates, void 0);
51658
+ const candidates2 = [target.baseRef, target.bindingRef, target.resolvedRef];
51659
+ const base = prDescription ? await resolveBaseRefLazy(candidates2, repo, input.fetchRepoInfo) : resolveBaseRef(candidates2, void 0);
50833
51660
  const pushedBranch = branch2 !== void 0 && branch2 !== base && pushed;
50834
51661
  if (pushedBranch && branch2 !== void 0 && headSha !== void 0) {
50835
51662
  const clean = observed.uncommittedChanges !== void 0 && observed.unpushedCommits !== void 0 ? observed.uncommittedChanges === 0 && observed.unpushedCommits === 0 : void 0;
@@ -51933,6 +52760,7 @@ async function runLoop(deps, ctx) {
51933
52760
  messages,
51934
52761
  tools: mergeTools(TOOLSETS[agent.toolset] ?? [], mcpForRun?.tools),
51935
52762
  toolContext,
52763
+ ...ctx.decisionRecord !== void 0 ? { environment: { [DECISION_RECORD_ENV]: ctx.decisionRecord } } : {},
51936
52764
  ...session ? { notepad: () => session.readNotepad(), conversation: () => session.readConversation() } : {},
51937
52765
  // A failed compaction is a checkpoint signal (harness-pi.md item
51938
52766
  // 7): the provider can refuse the process's summary for good, and
@@ -52275,6 +53103,10 @@ async function runLoop(deps, ctx) {
52275
53103
  answer,
52276
53104
  carried,
52277
53105
  hardStopped: false,
53106
+ ...coordinator !== void 0 ? {
53107
+ guardTransition: true,
53108
+ fetchPrFacts: deps.fetchPrFacts ?? fetchPullRequestFacts
53109
+ } : { guardTransition: false },
52278
53110
  post: deps.postReviewComment ?? postReviewComment,
52279
53111
  fetchPrHead: deps.fetchPrHead ?? currentPrHeadSha,
52280
53112
  reply: (text) => io.reply(text),
@@ -52358,6 +53190,7 @@ async function runLoop(deps, ctx) {
52358
53190
  diagnosis,
52359
53191
  root,
52360
53192
  ledgerRun,
53193
+ ...observedHead !== void 0 ? { headSha: observedHead } : {},
52361
53194
  ...handoff !== void 0 ? { handoff } : {},
52362
53195
  ...verdict !== void 0 ? { verdict } : {},
52363
53196
  ...reviewHead !== void 0 ? { reviewHead } : {},
@@ -52438,6 +53271,7 @@ var init_runLoop = __esm({
52438
53271
  "use strict";
52439
53272
  init_actor();
52440
53273
  init_predicate();
53274
+ init_decisionRecordReservation();
52441
53275
  init_profile();
52442
53276
  init_provider();
52443
53277
  init_toolsets();
@@ -52521,11 +53355,11 @@ function shipUnitText(requestText, repo) {
52521
53355
  return t.replace(/\s+/g, " ").trim();
52522
53356
  }
52523
53357
  function noRepoGuess(input) {
52524
- const candidates = input.repoCandidates ?? [];
52525
- if (candidates.length === 0) return void 0;
53358
+ const candidates2 = input.repoCandidates ?? [];
53359
+ if (candidates2.length === 0) return void 0;
52526
53360
  const typed = REPO_TOKEN.exec(input.requestText)?.[1];
52527
53361
  if (typed) {
52528
- const near = nearMatch(typed, candidates);
53362
+ const near = nearMatch(typed, candidates2);
52529
53363
  if (near.guess)
52530
53364
  return {
52531
53365
  line: `agent:ship ${input.requestText.replace(typed, near.guess)}`,
@@ -52533,8 +53367,8 @@ function noRepoGuess(input) {
52533
53367
  source: "near"
52534
53368
  };
52535
53369
  }
52536
- if (candidates.length !== 1) return void 0;
52537
- const repo = candidates[0];
53370
+ if (candidates2.length !== 1) return void 0;
53371
+ const repo = candidates2[0];
52538
53372
  return {
52539
53373
  line: `agent:ship in ${repo}: ${input.requestText}`,
52540
53374
  evidence: `\`${repo}\` is the one available repository`,
@@ -52759,7 +53593,8 @@ async function plan(deps, input) {
52759
53593
  ...entry.adopt !== void 0 ? { adopt: entry.adopt } : {},
52760
53594
  ...entry.branch !== void 0 ? { entryBranch: entry.branch } : {},
52761
53595
  ...entry.autoMergeEnabled !== void 0 ? { autoMergeEnabled: entry.autoMergeEnabled } : {},
52762
- ...entry.baseFallback !== void 0 ? { baseFallback: entry.baseFallback } : {}
53596
+ ...entry.baseFallback !== void 0 ? { baseFallback: entry.baseFallback } : {},
53597
+ ...asksForDecisionRecord(text2) ? { recordTasks: { [graph2.units[0].id]: decisionRecordTaskKey(entry.repo, msg.threadKey, text2.trim()) } } : {}
52763
53598
  }
52764
53599
  };
52765
53600
  }
@@ -52815,22 +53650,37 @@ async function plan(deps, input) {
52815
53650
  selected,
52816
53651
  identity: identity2,
52817
53652
  merge: "runner",
52818
- ...entry.baseFallback !== void 0 ? { baseFallback: entry.baseFallback } : {}
53653
+ ...entry.baseFallback !== void 0 ? { baseFallback: entry.baseFallback } : {},
53654
+ recordTasks: Object.fromEntries(
53655
+ graph.units.flatMap((unit) => {
53656
+ const section = parsePlanUnit(text, unit.id)?.section ?? "";
53657
+ return asksForDecisionRecord(section) ? [[unit.id, decisionRecordTaskKey(entry.repo, msg.threadKey, section.trim())]] : [];
53658
+ })
53659
+ )
52819
53660
  }
52820
53661
  };
52821
53662
  }
52822
- function rowsFor(p, selected, instanceId, lastPushOf) {
52823
- return p.graph.units.filter((u) => selected.includes(u.id)).map((u) => ({
52824
- instanceId,
52825
- unit: u.id,
52826
- slug: u.slug,
52827
- title: u.title,
52828
- branch: p.entryBranch ?? u.branch,
52829
- dependsOn: u.dependsOn,
52830
- rounds: [],
52831
- ...p.resume !== void 0 ? { resume: p.resume } : {},
52832
- ...lastPushOf?.has(u.id) ? { lastPush: lastPushOf.get(u.id) } : {}
52833
- }));
53663
+ async function rowsFor(deps, p, selected, instanceId, carried) {
53664
+ const reserve = deps.reserveDecisionRecord ?? (() => Promise.reject(new DecisionRecordReservationUnavailableError()));
53665
+ return Promise.all(
53666
+ p.graph.units.filter((u) => selected.includes(u.id)).map(async (u) => {
53667
+ const previous = carried?.get(u.id);
53668
+ const taskKey = p.recordTasks?.[u.id];
53669
+ const record2 = taskKey !== void 0 ? await reserve(p.identity.repo, taskKey, previous?.record) : previous?.record;
53670
+ return {
53671
+ instanceId,
53672
+ unit: u.id,
53673
+ slug: u.slug,
53674
+ title: u.title,
53675
+ branch: p.entryBranch ?? u.branch,
53676
+ dependsOn: u.dependsOn,
53677
+ rounds: [],
53678
+ ...p.resume !== void 0 ? { resume: p.resume } : {},
53679
+ ...previous?.lastPush !== void 0 ? { lastPush: previous.lastPush } : {},
53680
+ ...record2 !== void 0 ? { record: record2 } : {}
53681
+ };
53682
+ })
53683
+ );
52834
53684
  }
52835
53685
  function planWhere(p, units, mergedBefore, attempt) {
52836
53686
  const at = p.path !== void 0 ? ` (\`${p.path}\` at \`${p.base}\`)` : "";
@@ -52854,6 +53704,15 @@ function planWhere(p, units, mergedBefore, attempt) {
52854
53704
  return lines;
52855
53705
  }
52856
53706
  async function handOffToCoordinator(deps, input) {
53707
+ try {
53708
+ return await handOffToCoordinatorUnchecked(deps, input);
53709
+ } catch (error) {
53710
+ if (error instanceof DecisionRecordReservationUnavailableError)
53711
+ return refused5("decision_record_store_unavailable", DECISION_RECORD_STORE_REFUSAL);
53712
+ throw error;
53713
+ }
53714
+ }
53715
+ async function handOffToCoordinatorUnchecked(deps, input) {
52857
53716
  const log = deps.log ?? console.log;
52858
53717
  const planned = await plan(deps, input);
52859
53718
  if (!planned.ok) return refused5(planned.code, planned.reply);
@@ -52865,7 +53724,7 @@ async function handOffToCoordinator(deps, input) {
52865
53724
  const firstId = planInstanceId(p.planId);
52866
53725
  const first = await deps.instances.get(firstId);
52867
53726
  if (first === null) {
52868
- const units2 = rowsFor(p, p.selected, firstId);
53727
+ const units2 = await rowsFor(deps, p, p.selected, firstId);
52869
53728
  const instance2 = {
52870
53729
  id: firstId,
52871
53730
  ...p.identity,
@@ -52901,11 +53760,16 @@ async function handOffToCoordinator(deps, input) {
52901
53760
  `\u{1F6AB} A runner for ${where} (\`${latest.id}\`) is in a state the bot does not read as ended (${status3.status}) \u2014 a person decides.`
52902
53761
  );
52903
53762
  const merged = /* @__PURE__ */ new Set();
52904
- const lastPushOf = /* @__PURE__ */ new Map();
53763
+ const carried = /* @__PURE__ */ new Map();
52905
53764
  for (let n2 = 1; n2 <= attempt; n2++)
52906
53765
  for (const row2 of await deps.instances.listUnits(planInstanceId(p.planId, n2))) {
52907
53766
  if (row2.ending?.kind === "merged") merged.add(row2.unit);
52908
- if (row2.lastPush !== void 0) lastPushOf.set(row2.unit, row2.lastPush);
53767
+ const prior = carried.get(row2.unit) ?? {};
53768
+ carried.set(row2.unit, {
53769
+ ...prior,
53770
+ ...row2.lastPush !== void 0 ? { lastPush: row2.lastPush } : {},
53771
+ ...row2.record !== void 0 ? { record: row2.record } : {}
53772
+ });
52909
53773
  }
52910
53774
  const remaining = p.selected.filter((u) => !merged.has(u));
52911
53775
  if (remaining.length === 0)
@@ -52915,7 +53779,7 @@ async function handOffToCoordinator(deps, input) {
52915
53779
  );
52916
53780
  const mergedBefore = p.selected.filter((u) => merged.has(u));
52917
53781
  if (status3.kind === "absent") {
52918
- const units2 = rowsFor(p, remaining, latest.id, lastPushOf);
53782
+ const units2 = await rowsFor(deps, p, remaining, latest.id, carried);
52919
53783
  const instance2 = {
52920
53784
  id: latest.id,
52921
53785
  ...p.identity,
@@ -52935,7 +53799,7 @@ async function handOffToCoordinator(deps, input) {
52935
53799
  );
52936
53800
  }
52937
53801
  const nextId = planInstanceId(p.planId, attempt + 1);
52938
- const units = rowsFor(p, remaining, nextId, lastPushOf);
53802
+ const units = await rowsFor(deps, p, remaining, nextId, carried);
52939
53803
  const instance = {
52940
53804
  id: nextId,
52941
53805
  ...p.identity,
@@ -53030,6 +53894,7 @@ var init_handOff = __esm({
53030
53894
  init_budgets();
53031
53895
  init_verbosity();
53032
53896
  init_contract();
53897
+ init_decisionRecordReservation();
53033
53898
  init_preflight();
53034
53899
  init_coordinator();
53035
53900
  refused5 = (code, reply) => ({
@@ -53428,6 +54293,7 @@ async function runShipBranch(deps, msg, io, ctx) {
53428
54293
  {
53429
54294
  readFile: (repo, path, ref, opts) => githubCapabilityFor(deps, chatActorOf(deps.config, msg)).api.readFile(repo, path, ref, opts),
53430
54295
  instances: deps.coordinatorInstances ?? new NullCoordinatorInstanceStore(),
54296
+ ...deps.reserveDecisionRecord !== void 0 ? { reserveDecisionRecord: deps.reserveDecisionRecord } : {},
53431
54297
  create: deps.createCoordinatorInstance ?? ((id) => createInstanceViaShim(shim(), id)),
53432
54298
  status: deps.fetchCoordinatorInstanceStatus ?? ((id) => fetchInstanceStatusViaShim(shim(), id))
53433
54299
  },
@@ -53960,11 +54826,11 @@ function threadArtifactsBlock(records, opts) {
53960
54826
  return { text: parts.join("\n\n"), runs: kept.map((k) => k.id), cut, total: rendered.length };
53961
54827
  }
53962
54828
  async function threadArtifactsFor(input) {
53963
- const candidates = runsSince(input.thread, input.agent).filter(carriesArtifacts);
53964
- if (candidates.length === 0) return { notes: [] };
54829
+ const candidates2 = runsSince(input.thread, input.agent).filter(carriesArtifacts);
54830
+ if (candidates2.length === 0) return { notes: [] };
53965
54831
  const notes = [];
53966
54832
  const reads = await Promise.all(
53967
- candidates.map(async (view) => {
54833
+ candidates2.map(async (view) => {
53968
54834
  try {
53969
54835
  const res = await input.runs.getRun(view.id, { include: "messages" });
53970
54836
  if (res.ok) return res.value;
@@ -54093,6 +54959,25 @@ __export(dispatcher_exports, {
54093
54959
  dispatch: () => dispatch,
54094
54960
  dispatchClick: () => dispatchClick
54095
54961
  });
54962
+ async function priorDecisionRecord(store, threadKey, repo, taskKey) {
54963
+ let before;
54964
+ let beforeId;
54965
+ for (; ; ) {
54966
+ const rows = await store.list({
54967
+ threadKey,
54968
+ agent: "coding",
54969
+ limit: RUN_LIST_MAX_LIMIT,
54970
+ ...before !== void 0 ? { before, beforeId } : {}
54971
+ });
54972
+ const match = rows.find((row2) => row2.repo === repo && row2.recordTaskKey === taskKey && row2.record !== void 0);
54973
+ if (match?.record !== void 0) return match.record;
54974
+ if (rows.length < RUN_LIST_MAX_LIMIT) return void 0;
54975
+ const last = rows.at(-1);
54976
+ if (last.finishedAt === before && last.id === beforeId) return void 0;
54977
+ before = last.finishedAt;
54978
+ beforeId = last.id;
54979
+ }
54980
+ }
54096
54981
  async function answerUnitOwnedThread(deps, ctx) {
54097
54982
  const { msg, io, owner } = ctx;
54098
54983
  const store = deps.coordinatorInstances;
@@ -54235,6 +55120,8 @@ async function dispatch(deps, msg, io, opts = {}) {
54235
55120
  });
54236
55121
  let setupCard;
54237
55122
  let setupShell;
55123
+ let decisionRecord;
55124
+ let decisionRecordTask;
54238
55125
  let setupHeartbeat;
54239
55126
  const admission = deps.admission ?? defaultAdmission;
54240
55127
  const registry = deps.runRegistry ?? defaultRunRegistry;
@@ -54586,6 +55473,36 @@ async function dispatch(deps, msg, io, opts = {}) {
54586
55473
  if (repoGate.kind === "refused") return ended;
54587
55474
  const clip3 = budgetClipLabel(agent, profile, directives.budget, { coordinator: opts.coordinator !== void 0 });
54588
55475
  if (clip3) shell.note("debug", clip3);
55476
+ decisionRecord = opts.contract?.record ?? carriedRow?.meta.record;
55477
+ decisionRecordTask = carriedRow?.meta.recordTaskKey;
55478
+ if (decisionRecord === void 0 && agent.name === "coding" && agentSource === "directive" && repoCtx.repo !== void 0 && asksForDecisionRecord(directives.text)) {
55479
+ decisionRecordTask = decisionRecordTaskKey(
55480
+ repoCtx.repo,
55481
+ msg.threadKey,
55482
+ shipUnitText(directives.text, repoCtx.repo).trim()
55483
+ );
55484
+ const prior = await priorDecisionRecord(deps.runStore, msg.threadKey, repoCtx.repo, decisionRecordTask);
55485
+ const reserve = deps.reserveDecisionRecord ?? (async () => {
55486
+ throw new DecisionRecordReservationUnavailableError();
55487
+ });
55488
+ try {
55489
+ decisionRecord = await reserve(repoCtx.repo, decisionRecordTask, prior);
55490
+ } catch (error) {
55491
+ if (!(error instanceof DecisionRecordReservationUnavailableError)) throw error;
55492
+ await refuse3(
55493
+ refusalOf("decision_record_store_unavailable", DECISION_RECORD_STORE_REFUSAL),
55494
+ () => card.done(
55495
+ shell.close({
55496
+ kind: "not_started",
55497
+ icon: "\u26A0\uFE0F",
55498
+ reason: "durable coordinator store unavailable",
55499
+ ...closeLines(clock4(), false)
55500
+ })
55501
+ )
55502
+ );
55503
+ return ended;
55504
+ }
55505
+ }
54589
55506
  if (agent.name === "ship") {
54590
55507
  setupCard = void 0;
54591
55508
  clearInterval(setupHeartbeat);
@@ -54645,7 +55562,8 @@ async function dispatch(deps, msg, io, opts = {}) {
54645
55562
  const channelBuilt = session ? void 0 : buildConversation(opts.seed ?? history, requestText, msg.images, msg.documents, references.blocks, msg.userId);
54646
55563
  const built = session ? session.messages : channelBuilt.messages;
54647
55564
  const seedActors = session ? session.actors : channelBuilt?.actors;
54648
- const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(built, contractBlock) : built;
55565
+ const withRecord = !resume && decisionRecord !== void 0 && agent.name === "coding" ? withContractInFirstUserTurn(built, `record: ${decisionRecord}`) : built;
55566
+ const messages = resume ? resume.plan.messages : contractBlock !== void 0 && agent.name !== "review" ? withContractInFirstUserTurn(withRecord, contractBlock) : withRecord;
54649
55567
  const headPreflight = await authorizePrHead({
54650
55568
  msg,
54651
55569
  io,
@@ -54686,6 +55604,8 @@ async function dispatch(deps, msg, io, opts = {}) {
54686
55604
  ...opts.restartCarried !== void 0 ? { restartCarried: opts.restartCarried } : {},
54687
55605
  ...seedTurns ? { seedTurns } : {},
54688
55606
  agentSource,
55607
+ ...decisionRecord !== void 0 ? { decisionRecord } : {},
55608
+ ...decisionRecordTask !== void 0 ? { decisionRecordTask } : {},
54689
55609
  modelCard,
54690
55610
  cardDecisions,
54691
55611
  ...operatorEvent ? { operator: operatorEvent } : {},
@@ -54746,6 +55666,8 @@ async function dispatch(deps, msg, io, opts = {}) {
54746
55666
  parentRunId,
54747
55667
  coordinator,
54748
55668
  seed,
55669
+ ...decisionRecord !== void 0 ? { decisionRecord } : {},
55670
+ ...decisionRecordTask !== void 0 ? { decisionRecordTask } : {},
54749
55671
  ...opts.restartOf !== void 0 ? { restartOf: opts.restartOf } : {}
54750
55672
  });
54751
55673
  if (reservation) {
@@ -55068,6 +55990,7 @@ async function dispatch(deps, msg, io, opts = {}) {
55068
55990
  isCodingPrRun,
55069
55991
  reviewHead,
55070
55992
  requestText: directives.text,
55993
+ ...decisionRecord !== void 0 ? { decisionRecord } : {},
55071
55994
  card,
55072
55995
  shell,
55073
55996
  doneLines,
@@ -55409,6 +56332,7 @@ var init_dispatcher = __esm({
55409
56332
  init_authorize2();
55410
56333
  init_messages();
55411
56334
  init_staging();
56335
+ init_runRecord();
55412
56336
  init_provision();
55413
56337
  init_run2();
55414
56338
  init_runLoop();
@@ -55436,7 +56360,9 @@ var init_dispatcher = __esm({
55436
56360
  init_contract2();
55437
56361
  init_runRegistry();
55438
56362
  init_runEnding();
56363
+ init_preflight();
55439
56364
  init_types5();
56365
+ init_decisionRecordReservation();
55440
56366
  activeRuns = 0;
55441
56367
  }
55442
56368
  });
@@ -56139,15 +57065,15 @@ function createLedgerWriteThrough(opts) {
56139
57065
  readNotepad: (key) => ledger.readNotepad(key),
56140
57066
  writeNotepad: (key, text) => ledger.writeNotepad(key, gen, text),
56141
57067
  async handoff() {
56142
- const candidates = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
56143
- if (candidates.length === 0) return { marked: [] };
57068
+ const candidates2 = [...live].filter((r) => r.resumable && r.tracked() && !r.handedOff);
57069
+ if (candidates2.length === 0) return { marked: [] };
56144
57070
  try {
56145
57071
  const { marked } = await ledger.handoff(
56146
57072
  gen,
56147
- candidates.map((r) => r.runId)
57073
+ candidates2.map((r) => r.runId)
56148
57074
  );
56149
57075
  const markedSet = new Set(marked);
56150
- for (const r of candidates) if (markedSet.has(r.runId)) r.markHandedOff();
57076
+ for (const r of candidates2) if (markedSet.has(r.runId)) r.markHandedOff();
56151
57077
  return { marked };
56152
57078
  } catch (err2) {
56153
57079
  return { marked: [], failed: describe9(err2) };
@@ -56830,7 +57756,7 @@ function openCodeToolError(outcome, text) {
56830
57756
  return outcome === "failed" ? { type: "tool.execution", message: text || "The tool call failed" } : { type: "aborted", message: text || "Tool execution interrupted" };
56831
57757
  }
56832
57758
  function openCodeToolContent(toolUse, resultText, at, outcome = "completed") {
56833
- const input = isRecord8(toolUse.input) ? toolUse.input : {};
57759
+ const input = isRecord9(toolUse.input) ? toolUse.input : {};
56834
57760
  const content = [{ type: "text", text: resultText || "(no output)" }];
56835
57761
  return {
56836
57762
  type: "tool",
@@ -56935,11 +57861,11 @@ function openCodeSeedAndRequest(messages) {
56935
57861
  }
56936
57862
  return { seed: messages.slice(), request: "" };
56937
57863
  }
56938
- var isRecord8, SESSION_TOOL_NAME;
57864
+ var isRecord9, SESSION_TOOL_NAME;
56939
57865
  var init_session2 = __esm({
56940
57866
  "../../src/core/harness/opencode/session.ts"() {
56941
57867
  "use strict";
56942
- isRecord8 = (v) => typeof v === "object" && v !== null;
57868
+ isRecord9 = (v) => typeof v === "object" && v !== null;
56943
57869
  SESSION_TOOL_NAME = { bash: "shell", find: "glob" };
56944
57870
  }
56945
57871
  });
@@ -57391,7 +58317,7 @@ main().then(
57391
58317
  });
57392
58318
 
57393
58319
  // ../../src/core/harness/opencode/process.ts
57394
- import { randomBytes as randomBytes5 } from "node:crypto";
58320
+ import { randomBytes as randomBytes6 } from "node:crypto";
57395
58321
  function openCodeRunPaths(runId3) {
57396
58322
  return openCodeRunPathsAt(`${OPENCODE_RUN_ROOT_PREFIX}${runId3}`);
57397
58323
  }
@@ -57574,11 +58500,12 @@ function openCodeLaunchArgs() {
57574
58500
  return ["serve", "--hostname", "127.0.0.1", "--port", PORT_ARG];
57575
58501
  }
57576
58502
  function openCodePassword(bearer) {
57577
- return bearerHashOf(bearer) ?? randomBytes5(24).toString("base64url");
58503
+ return bearerHashOf(bearer) ?? randomBytes6(24).toString("base64url");
57578
58504
  }
57579
58505
  function openCodeLaunchEnv(spec, bearer, password) {
57580
58506
  const { paths } = spec;
57581
58507
  return {
58508
+ ...spec.environment,
57582
58509
  [RUN_BEARER_ENV]: bearer,
57583
58510
  [HARNESS_URL_ENV]: spec.harnessUrl,
57584
58511
  SWITCHBOARD_RUN_ID: spec.runId,
@@ -57797,7 +58724,7 @@ function openCodeToolNameWord(name) {
57797
58724
  function contentText(content) {
57798
58725
  if (typeof content === "string") return content;
57799
58726
  if (!Array.isArray(content)) return "";
57800
- return content.filter((p) => isRecord9(p) && p.type === "text" && typeof p.text === "string").map((p) => String(p.text)).join("\n");
58727
+ return content.filter((p) => isRecord10(p) && p.type === "text" && typeof p.text === "string").map((p) => String(p.text)).join("\n");
57801
58728
  }
57802
58729
  function isBudgetRefusal(error) {
57803
58730
  if (error.status !== void 0 && error.status !== 403) return false;
@@ -57867,8 +58794,8 @@ function toolPartsOf(message4) {
57867
58794
  const content = message4.content;
57868
58795
  const parts = [];
57869
58796
  for (const part of Array.isArray(content) ? content : []) {
57870
- if (!isRecord9(part) || part.type !== "tool") continue;
57871
- const input = isRecord9(part.state) && isRecord9(part.state.input) ? part.state.input : void 0;
58797
+ if (!isRecord10(part) || part.type !== "tool") continue;
58798
+ const input = isRecord10(part.state) && isRecord10(part.state.input) ? part.state.input : void 0;
57872
58799
  parts.push({ id: str7(part.id), name: str7(part.name), ...input ? { input } : {} });
57873
58800
  }
57874
58801
  return parts;
@@ -57889,7 +58816,7 @@ function assistantMessage2(turn) {
57889
58816
  return {
57890
58817
  role: "assistant",
57891
58818
  content: turn.content.map(
57892
- (p) => p.type === "text" ? { type: "text", text: p.text } : p.type === "tool_use" ? { type: "toolCall", id: p.id, name: p.name, arguments: isRecord9(p.input) ? p.input : {} } : {}
58819
+ (p) => p.type === "text" ? { type: "text", text: p.text } : p.type === "tool_use" ? { type: "toolCall", id: p.id, name: p.name, arguments: isRecord10(p.input) ? p.input : {} } : {}
57893
58820
  )
57894
58821
  };
57895
58822
  }
@@ -57918,13 +58845,13 @@ function projectStore(messages, seedLength) {
57918
58845
  const content = [];
57919
58846
  const results = [];
57920
58847
  for (const part of Array.isArray(assistant.content) ? assistant.content : []) {
57921
- if (!isRecord9(part)) continue;
58848
+ if (!isRecord10(part)) continue;
57922
58849
  if (part.type === "text" && typeof part.text === "string") content.push({ type: "text", text: part.text });
57923
58850
  else if (part.type === "tool") {
57924
58851
  const id = str7(part.id);
57925
58852
  const name = openCodeToolNameWord(str7(part.name));
57926
- const state = isRecord9(part.state) ? part.state : {};
57927
- content.push({ type: "tool_use", id, name, input: isRecord9(state.input) ? state.input : {} });
58853
+ const state = isRecord10(part.state) ? part.state : {};
58854
+ content.push({ type: "tool_use", id, name, input: isRecord10(state.input) ? state.input : {} });
57928
58855
  if (state.status === "completed" || state.status === "error")
57929
58856
  results.push({
57930
58857
  type: "tool_result",
@@ -57944,13 +58871,13 @@ function projectStore(messages, seedLength) {
57944
58871
  return turns;
57945
58872
  }
57946
58873
  function errorMessage(error) {
57947
- return isRecord9(error) && typeof error.message === "string" ? error.message : "";
58874
+ return isRecord10(error) && typeof error.message === "string" ? error.message : "";
57948
58875
  }
57949
58876
  function statusOf(error) {
57950
- return isRecord9(error) && typeof error.status === "number" ? error.status : void 0;
58877
+ return isRecord10(error) && typeof error.status === "number" ? error.status : void 0;
57951
58878
  }
57952
58879
  function errorTypeOf(error) {
57953
- return isRecord9(error) && typeof error.type === "string" ? error.type : void 0;
58880
+ return isRecord10(error) && typeof error.type === "string" ? error.type : void 0;
57954
58881
  }
57955
58882
  function owedEndNote(how) {
57956
58883
  return `the interrupted execution ended (${how}) while the loop read in its own mode \u2014 before the interrupt's answer, or after the write-up's own start; set aside \u2014 the end the loop-end cut owed, not the write-up's settle`;
@@ -58626,7 +59553,7 @@ async function driveOpenCode(deps, run2, conn, kind = "loop") {
58626
59553
  const answer = ending ? windDownAnswer(ending, run2.agent.maxMinutes) : unlabelledAnswer(text, void 0);
58627
59554
  return { answer, ...ending ? { ending } : {}, remainingMs: remaining, hardStopped: false, ...handOver() };
58628
59555
  }
58629
- var OpenCodeGateBypassedError, OpenCodeReplyFailedError, OpenCodeRequestRefusedError, OpenCodeWriteUnresolvedError, FIRST_EVENT_BOUND_MS, SILENCE_TAIL_BYTES, OpenCodeSilentError, OpenCodeContainerReplacedError, TOOL_NAME_WORD, RESOURCE_PERMISSIONS, isRecord9, str7, TURN_BUDGET_CODE, TURN_BUDGET_REFUSAL, OpenCodeBridge, StepBoundaries, InboxFate;
59556
+ var OpenCodeGateBypassedError, OpenCodeReplyFailedError, OpenCodeRequestRefusedError, OpenCodeWriteUnresolvedError, FIRST_EVENT_BOUND_MS, SILENCE_TAIL_BYTES, OpenCodeSilentError, OpenCodeContainerReplacedError, TOOL_NAME_WORD, RESOURCE_PERMISSIONS, isRecord10, str7, TURN_BUDGET_CODE, TURN_BUDGET_REFUSAL, OpenCodeBridge, StepBoundaries, InboxFate;
58630
59557
  var init_bridge3 = __esm({
58631
59558
  "../../src/core/harness/opencode/bridge.ts"() {
58632
59559
  "use strict";
@@ -58719,7 +59646,7 @@ var init_bridge3 = __esm({
58719
59646
  };
58720
59647
  TOOL_NAME_WORD = { shell: "bash", glob: "find" };
58721
59648
  RESOURCE_PERMISSIONS = /* @__PURE__ */ new Set(["external_directory", "doom_loop"]);
58722
- isRecord9 = (v) => typeof v === "object" && v !== null;
59649
+ isRecord10 = (v) => typeof v === "object" && v !== null;
58723
59650
  str7 = (v) => typeof v === "string" ? v : String(v ?? "");
58724
59651
  TURN_BUDGET_CODE = "turn_budget_exhausted";
58725
59652
  TURN_BUDGET_REFUSAL = new RegExp(`\\b${TURN_BUDGET_CODE}\\b`);
@@ -59199,7 +60126,7 @@ var init_bridge3 = __esm({
59199
60126
  this.noteKindOnce(event.type, `OpenCode emitted ${event.type}, which this run's configuration turns off`);
59200
60127
  return out;
59201
60128
  }
59202
- const data = isRecord9(event.data) ? event.data : {};
60129
+ const data = isRecord10(event.data) ? event.data : {};
59203
60130
  const earlier2 = this.observing === "earlier";
59204
60131
  if (!earlier2 && event.type !== "session.tool.success" && event.type !== "session.tool.failed" && typeof data.assistantMessageID === "string")
59205
60132
  this.ownSteps.add(data.assistantMessageID);
@@ -59311,7 +60238,7 @@ ${data.text}` : data.text;
59311
60238
  this.openCall(
59312
60239
  callId,
59313
60240
  this.toolNames.get(callId) ?? "tool",
59314
- foldInputs(this.takeHeldAsks(callId), isRecord9(data.input) ? data.input : void 0)
60241
+ foldInputs(this.takeHeldAsks(callId), isRecord10(data.input) ? data.input : void 0)
59315
60242
  );
59316
60243
  }
59317
60244
  /** The call opened on the record — its span, its `tool_call` with the
@@ -59736,6 +60663,7 @@ async function openOpenCodeRun(deps, run2, settings) {
59736
60663
  ...run2.card ? { card: run2.card } : {},
59737
60664
  identity: identity2,
59738
60665
  system: run2.system,
60666
+ ...run2.environment !== void 0 ? { environment: run2.environment } : {},
59739
60667
  relayTools: run2.tools.map((t) => t.name),
59740
60668
  ...settings.compaction ? { compaction: settings.compaction } : {}
59741
60669
  };
@@ -60463,12 +61391,12 @@ var init_requester = __esm({
60463
61391
  });
60464
61392
 
60465
61393
  // ../../src/channels/http.ts
60466
- import { timingSafeEqual as timingSafeEqual3 } from "node:crypto";
61394
+ import { timingSafeEqual as timingSafeEqual4 } from "node:crypto";
60467
61395
  function safeEqual2(a, b) {
60468
61396
  const ab = Buffer.from(a, "utf8");
60469
61397
  const bb = Buffer.from(b, "utf8");
60470
61398
  if (ab.length !== bb.length) return false;
60471
- return timingSafeEqual3(ab, bb);
61399
+ return timingSafeEqual4(ab, bb);
60472
61400
  }
60473
61401
  function authenticate(headers, config) {
60474
61402
  const header4 = headers.authorization;
@@ -60493,7 +61421,7 @@ async function toIncomingMessage(identity2, body, personByEmail) {
60493
61421
  text: body.text
60494
61422
  };
60495
61423
  }
60496
- function parseBody2(raw) {
61424
+ function parseBody3(raw) {
60497
61425
  let parsed;
60498
61426
  try {
60499
61427
  parsed = JSON.parse(raw);
@@ -60560,7 +61488,7 @@ function requireDispatch(identity2, grantsFor) {
60560
61488
  return { status: 403, body: { error: "forbidden", code: "unauthorized" } };
60561
61489
  }
60562
61490
  async function handleAuthorized(identity2, body, deps, options) {
60563
- const parsed = parseBody2(body);
61491
+ const parsed = parseBody3(body);
60564
61492
  if ("error" in parsed) {
60565
61493
  return { status: 400, body: { error: parsed.error } };
60566
61494
  }
@@ -60605,9 +61533,9 @@ async function readBody(req, maxBytes) {
60605
61533
  function createIngressHandler(deps, options) {
60606
61534
  const maxBytes = options.maxBodyBytes ?? MAX_BODY_BYTES2;
60607
61535
  const write = (res, status3, body) => {
60608
- const payload = JSON.stringify(body);
61536
+ const payload2 = JSON.stringify(body);
60609
61537
  res.writeHead(status3, { "content-type": "application/json" });
60610
- res.end(payload);
61538
+ res.end(payload2);
60611
61539
  };
60612
61540
  return (req, res) => {
60613
61541
  void (async () => {
@@ -60721,6 +61649,16 @@ function refusalResponse(shape, status3, code, message4) {
60721
61649
  const body = shape === "anthropic-messages" ? { type: "error", error } : { error };
60722
61650
  return { status: status3, headers: { "content-type": "application/json" }, body: JSON.stringify(body) };
60723
61651
  }
61652
+ function providerFailureResponse(shape, status3, failure2) {
61653
+ const error = authenticateProxyProviderFailure({
61654
+ type: "provider_failure",
61655
+ cause: failure2.cause,
61656
+ message: renderProviderFailure(failure2.cause, providerFailureParks(failure2.cause) ? "parked" : "ended"),
61657
+ ...failure2.schemaRejection !== void 0 ? { schemaRejection: failure2.schemaRejection } : {}
61658
+ });
61659
+ const body = shape === "anthropic-messages" ? { type: "error", error } : { error };
61660
+ return { status: status3, headers: { "content-type": "application/json" }, body: JSON.stringify(body) };
61661
+ }
60724
61662
  function header(headers, name) {
60725
61663
  const raw = headers[name];
60726
61664
  return Array.isArray(raw) ? raw[0] : raw;
@@ -60903,10 +61841,140 @@ function shapeTools(shape, body, marks) {
60903
61841
  const shaped = { ...rest, tools: kept };
60904
61842
  return { body: shaped, toolChoice: toolChoiceWord(shape, void 0, kept.length) };
60905
61843
  }
61844
+ function responsesRejectsPattern(pattern) {
61845
+ return /\(\?(?:[=!]|<[=!])/.test(pattern);
61846
+ }
61847
+ function shapeResponsesSchema(value, tool, degradations) {
61848
+ const schema = record(value);
61849
+ if (!schema) {
61850
+ if (!Array.isArray(value)) return { value, changed: false };
61851
+ let changed2 = false;
61852
+ const items = value.map((item) => {
61853
+ const shaped = shapeResponsesSchema(item, tool, degradations);
61854
+ changed2 ||= shaped.changed;
61855
+ return shaped.value;
61856
+ });
61857
+ return { value: changed2 ? items : value, changed: changed2 };
61858
+ }
61859
+ let changed = false;
61860
+ const out = {};
61861
+ for (const [keyword, item] of Object.entries(schema)) {
61862
+ if (keyword === "pattern" && typeof item === "string" && responsesRejectsPattern(item)) {
61863
+ degradations.push({
61864
+ tool,
61865
+ keyword,
61866
+ why: "the Responses wire does not accept regular-expression lookaround"
61867
+ });
61868
+ changed = true;
61869
+ continue;
61870
+ }
61871
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
61872
+ let mapChanged = false;
61873
+ const mapped = {};
61874
+ for (const [name, child] of Object.entries(record(item))) {
61875
+ const shaped = shapeResponsesSchema(child, tool, degradations);
61876
+ mapChanged ||= shaped.changed;
61877
+ mapped[name] = shaped.value;
61878
+ }
61879
+ out[keyword] = mapChanged ? mapped : item;
61880
+ changed ||= mapChanged;
61881
+ continue;
61882
+ }
61883
+ if (["additionalProperties", "allOf", "anyOf", "if", "items", "not", "oneOf", "then", "else"].includes(keyword) && typeof item === "object" && item !== null) {
61884
+ const shaped = shapeResponsesSchema(item, tool, degradations);
61885
+ out[keyword] = shaped.value;
61886
+ changed ||= shaped.changed;
61887
+ continue;
61888
+ }
61889
+ out[keyword] = item;
61890
+ }
61891
+ return { value: changed ? out : value, changed };
61892
+ }
61893
+ function shapeToolSchemasForWire(shape, body) {
61894
+ if (shape !== "openai-responses" || !Array.isArray(body.tools)) return { body, degradations: [] };
61895
+ const degradations = [];
61896
+ let changed = false;
61897
+ const tools = body.tools.map((item) => {
61898
+ const tool = record(item);
61899
+ if (!tool) return item;
61900
+ const name = typeof tool.name === "string" ? tool.name : "unnamed";
61901
+ const shaped = shapeResponsesSchema(tool.parameters, name, degradations);
61902
+ if (!shaped.changed) return item;
61903
+ changed = true;
61904
+ return { ...tool, parameters: shaped.value };
61905
+ });
61906
+ const unique = degradations.filter(
61907
+ (candidate, index, all) => all.findIndex((other) => other.tool === candidate.tool && other.keyword === candidate.keyword) === index
61908
+ );
61909
+ return { body: changed ? { ...body, tools } : body, degradations: unique };
61910
+ }
60906
61911
  function toolNameOf(shape, tool) {
60907
61912
  if (tool === void 0) return void 0;
60908
61913
  return shape === "openai-chat" ? record(tool.function)?.name : tool.name;
60909
61914
  }
61915
+ function toolSchemaOf(shape, tool) {
61916
+ if (shape === "anthropic-messages") return tool.input_schema;
61917
+ if (shape === "openai-chat") return record(tool.function)?.parameters;
61918
+ return tool.parameters;
61919
+ }
61920
+ function schemaKeywords(value, into = /* @__PURE__ */ new Set()) {
61921
+ if (Array.isArray(value)) {
61922
+ for (const item of value) schemaKeywords(item, into);
61923
+ return into;
61924
+ }
61925
+ const schema = record(value);
61926
+ if (!schema) return into;
61927
+ for (const [keyword, item] of Object.entries(schema)) {
61928
+ into.add(keyword);
61929
+ if ((keyword === "properties" || keyword === "$defs" || keyword === "definitions") && record(item)) {
61930
+ for (const child of Object.values(record(item))) schemaKeywords(child, into);
61931
+ } else {
61932
+ schemaKeywords(item, into);
61933
+ }
61934
+ }
61935
+ return into;
61936
+ }
61937
+ function providerErrorProse(body) {
61938
+ const strings2 = (value, into = []) => {
61939
+ if (typeof value === "string") into.push(value);
61940
+ else if (Array.isArray(value)) for (const item of value) strings2(item, into);
61941
+ else if (record(value)) for (const item of Object.values(record(value))) strings2(item, into);
61942
+ return into;
61943
+ };
61944
+ try {
61945
+ return strings2(JSON.parse(body)).join(" ");
61946
+ } catch {
61947
+ return body;
61948
+ }
61949
+ }
61950
+ function quoted(text, word) {
61951
+ return [`'${word}'`, `"${word}"`, `\`${word}\``].some((candidate) => text.includes(candidate));
61952
+ }
61953
+ function providerSchemaRejectionOf2(shape, body, errorBody) {
61954
+ const prose2 = providerErrorProse(errorBody);
61955
+ if (!/(?:invalid|unsupported|refused|rejected)[^.!?]{0,80}(?:json )?schema|(?:json )?schema[^.!?]{0,80}(?:invalid|unsupported|not (?:permitted|supported))/i.test(
61956
+ prose2
61957
+ ))
61958
+ return void 0;
61959
+ const candidates2 = (Array.isArray(body.tools) ? body.tools : []).map((item) => record(item)).filter((item) => item !== void 0).map((tool) => ({ name: toolNameOf(shape, tool), schema: toolSchemaOf(shape, tool) })).filter(
61960
+ (tool) => typeof tool.name === "string" && quoted(prose2, tool.name)
61961
+ );
61962
+ if (candidates2.length !== 1) return void 0;
61963
+ const [candidate] = candidates2;
61964
+ const keywords = [...schemaKeywords(candidate.schema)].filter((keyword) => quoted(prose2, keyword));
61965
+ if (keywords.length !== 1) return void 0;
61966
+ return { tool: candidate.name, keyword: keywords[0] };
61967
+ }
61968
+ function withSchemaRejection(failure2, schemaRejection) {
61969
+ return new ProviderFailure(failure2.cause, {
61970
+ ...failure2.status !== void 0 ? { status: failure2.status } : {},
61971
+ ...failure2.provider !== void 0 ? { provider: failure2.provider } : {},
61972
+ ...failure2.model !== void 0 ? { model: failure2.model } : {},
61973
+ ...failure2.operatorUrl !== void 0 ? { operatorUrl: failure2.operatorUrl } : {},
61974
+ ...failure2.keyVariable !== void 0 ? { keyVariable: failure2.keyVariable } : {},
61975
+ schemaRejection
61976
+ });
61977
+ }
60910
61978
  function toolsOffered(shape, body) {
60911
61979
  const table = Array.isArray(body.tools) ? body.tools : [];
60912
61980
  const names = [];
@@ -61051,8 +62119,15 @@ async function handleAdmitted(door, req, deps) {
61051
62119
  req.headers
61052
62120
  );
61053
62121
  if (!upstream.ok) {
61054
- log(`[model-proxy] 503 ${upstream.code} run=${grant2.runId}`);
61055
- return refusalResponse(shape, 503, upstream.code, upstream.message);
62122
+ const provider = deps.providers()[grant2.providerName];
62123
+ const keyVariable = provider?.apiKeyEnv ?? (shape === "anthropic-messages" ? ANTHROPIC_API_KEY_ENV : void 0);
62124
+ const failure2 = new ProviderFailure(upstream.code === "provider_key_missing" ? "key-absent" : "permanent", {
62125
+ provider: grant2.providerName,
62126
+ model: grant2.model,
62127
+ ...upstream.code === "provider_key_missing" && keyVariable !== void 0 ? { keyVariable } : {}
62128
+ });
62129
+ log(`[model-proxy] 503 ${upstream.code} run=${grant2.runId} \u2014 ${failure2.message}`);
62130
+ return providerFailureResponse(shape, 503, failure2);
61056
62131
  }
61057
62132
  const turn = deps.bearers.consumeTurn(grant2.runId);
61058
62133
  if (!turn.ok && turn.reason === "ended") {
@@ -61076,7 +62151,21 @@ async function handleAdmitted(door, req, deps) {
61076
62151
  );
61077
62152
  }
61078
62153
  const shaped = shapeTools(shape, body, deps.bearers.marksOf(grant2.runId));
61079
- const payload = JSON.stringify(pinRequest(shape, shaped.body, grant2));
62154
+ const schemaShaped = shapeToolSchemasForWire(shape, shaped.body);
62155
+ for (const degradation of schemaShaped.degradations) {
62156
+ grant2.publish({
62157
+ type: "run_note",
62158
+ kind: "control_degraded",
62159
+ control: "tool_schema",
62160
+ asked: `${degradation.tool}.${degradation.keyword}`,
62161
+ applied: "removed",
62162
+ vouched: true,
62163
+ why: degradation.why,
62164
+ summary: `tool "${degradation.tool}" schema keyword "${degradation.keyword}" removed for ${shape}: ${degradation.why}`,
62165
+ at: deps.clock()
62166
+ });
62167
+ }
62168
+ const payload2 = JSON.stringify(pinRequest(shape, schemaShaped.body, grant2));
61080
62169
  const offered = { ...toolsOffered(shape, body), toolChoice: shaped.toolChoice };
61081
62170
  const startedAt = deps.clock();
61082
62171
  const span = grant2.span.start("model.turn", {
@@ -61098,47 +62187,72 @@ async function handleAdmitted(door, req, deps) {
61098
62187
  meter2.usage,
61099
62188
  deps.prices?.() ?? NO_PRICES
61100
62189
  );
61101
- const outcome = (status3, outBytes) => `[model-proxy] run=${grant2.runId} turn=${turn.turn}/${grant2.maxTurns} ${shape} \u2192 ${status3} in=${Buffer.byteLength(payload)} out=${outBytes} ${Math.max(0, deps.clock() - startedAt)}ms`;
62190
+ const outcome = (status3, outBytes) => `[model-proxy] run=${grant2.runId} turn=${turn.turn}/${grant2.maxTurns} ${shape} \u2192 ${status3} in=${Buffer.byteLength(payload2)} out=${outBytes} ${Math.max(0, deps.clock() - startedAt)}ms`;
61102
62191
  const call2 = () => (deps.fetch ?? fetch)(upstream.url, {
61103
62192
  method: "POST",
61104
62193
  headers: upstream.headers,
61105
- body: payload,
62194
+ body: payload2,
61106
62195
  ...req.signal ? { signal: req.signal } : {}
61107
62196
  });
61108
- const providerDown = () => {
61109
- deps.plane?.level(grant2.providerName, "down");
62197
+ const providerDown = (failure2) => {
62198
+ deps.plane?.level(grant2.providerName, "down", failure2.cause);
61110
62199
  deps.plane?.park(grant2.runId, grant2.providerName);
61111
62200
  };
61112
62201
  const aborted = () => req.signal?.aborted === true;
61113
- const retryable = (answer) => {
61114
- if (answer === void 0) return true;
61115
- if (answer.status === 408 || answer.status === 425 || answer.status === 429 || answer.status >= 500) return true;
61116
- return (answer.headers.get("content-type") ?? "").toLowerCase().includes("text/html");
62202
+ const failureOf2 = async (answer, thrown2) => {
62203
+ if (answer === void 0)
62204
+ return classifyProviderFailure({ error: thrown2 ?? new Error("fetch failed"), provider: grant2.providerName });
62205
+ const contentType2 = (answer.headers.get("content-type") ?? "").toLowerCase();
62206
+ if (answer.ok && !contentType2.includes("text/html")) return void 0;
62207
+ const failureBody = await answer.clone().text().catch(() => "");
62208
+ const failure2 = classifyProviderFailure({
62209
+ status: answer.status,
62210
+ body: failureBody,
62211
+ provider: grant2.providerName,
62212
+ model: grant2.model
62213
+ });
62214
+ const schemaRejection = answer.status === 400 && failure2.cause === "request-rejected" ? providerSchemaRejectionOf2(shape, schemaShaped.body, failureBody) : void 0;
62215
+ return schemaRejection !== void 0 ? withSchemaRejection(failure2, schemaRejection) : failure2;
61117
62216
  };
61118
62217
  let res;
61119
- let failure2;
62218
+ let thrown;
61120
62219
  try {
61121
62220
  res = await call2();
61122
62221
  } catch (err2) {
61123
- failure2 = err2;
62222
+ thrown = err2;
61124
62223
  }
61125
- if (retryable(res) && !aborted()) {
61126
- const retried = await call2().catch(() => void 0);
62224
+ let providerFailure = await failureOf2(res, thrown);
62225
+ if (providerFailure !== void 0 && providerFailureParks(providerFailure.cause) && !aborted()) {
62226
+ let retried;
62227
+ let retryThrown;
62228
+ try {
62229
+ retried = await call2();
62230
+ } catch (err2) {
62231
+ retryThrown = err2;
62232
+ }
61127
62233
  if (retried !== void 0) res = retried;
61128
- }
61129
- if (retryable(res) && !aborted()) providerDown();
61130
- if (res === void 0 || (res.headers.get("content-type") ?? "").toLowerCase().includes("text/html")) {
61131
- span.fail(failure2 ?? new Error("upstream unreachable"));
62234
+ providerFailure = await failureOf2(retried ?? res, retryThrown ?? thrown);
62235
+ }
62236
+ if (providerFailure !== void 0 && providerFailureParks(providerFailure.cause) && !aborted())
62237
+ providerDown(providerFailure);
62238
+ const html = (res?.headers.get("content-type") ?? "").toLowerCase().includes("text/html");
62239
+ if (res === void 0 || html) {
62240
+ const failure2 = providerFailure ?? classifyProviderFailure({ error: thrown, provider: grant2.providerName });
62241
+ span.fail(failure2);
61132
62242
  span.end("error");
61133
62243
  log(`[model-proxy] run=${grant2.runId} turn=${turn.turn}/${grant2.maxTurns} ${shape} \u2192 upstream unreachable`);
61134
- return refusalResponse(shape, 502, "upstream_unreachable", "the model provider did not answer");
62244
+ return providerFailureResponse(shape, 502, failure2);
61135
62245
  }
61136
62246
  const headers = pickResponseHeaders(res.headers);
61137
62247
  if (!res.ok) {
61138
62248
  const text2 = await res.text().catch(() => "");
62249
+ const failure2 = providerFailure ?? classifyProviderFailure({ status: res.status, body: text2, provider: grant2.providerName, model: grant2.model });
61139
62250
  span.end("error", { httpStatus: res.status });
61140
62251
  log(outcome(res.status, text2.length));
61141
- return { status: res.status, headers, body: text2 };
62252
+ return {
62253
+ ...providerFailureResponse(shape, res.status, failure2),
62254
+ headers: { ...headers, "content-type": "application/json" }
62255
+ };
61142
62256
  }
61143
62257
  deps.plane?.level(grant2.providerName, "up");
61144
62258
  const contentType = res.headers.get("content-type") ?? "";
@@ -61161,7 +62275,9 @@ async function handleAdmitted(door, req, deps) {
61161
62275
  log(outcome(res.status, outBytes));
61162
62276
  },
61163
62277
  onError: (err2) => {
61164
- providerDown();
62278
+ providerDown(
62279
+ classifyProviderFailure({ status: 503, error: err2, provider: grant2.providerName, model: grant2.model })
62280
+ );
61165
62281
  const result = meter2.result();
61166
62282
  span.setAttrs(
61167
62283
  turnAttrs(grant2, result, firstAt !== void 0 ? firstAt - startedAt : void 0, offered, priceOf2(result))
@@ -61200,10 +62316,12 @@ function createModelProxyHandler(deps) {
61200
62316
  ...deps,
61201
62317
  plane: {
61202
62318
  ...deps.plane,
61203
- level: (provider, side) => {
61204
- if (lastSide.get(provider) === side) return;
61205
- lastSide.set(provider, side);
61206
- deps.plane?.level(provider, side);
62319
+ level: (provider, side, cause) => {
62320
+ const report2 = side === "down" ? `${side}:${cause ?? "permanent"}` : side;
62321
+ if (lastSide.get(provider) === report2) return;
62322
+ lastSide.set(provider, report2);
62323
+ if (side === "down") deps.plane?.level(provider, side, cause ?? "permanent");
62324
+ else deps.plane?.level(provider, side);
61207
62325
  }
61208
62326
  }
61209
62327
  } : deps;
@@ -61267,6 +62385,7 @@ var init_modelProxy = __esm({
61267
62385
  "../../src/channels/modelProxy.ts"() {
61268
62386
  "use strict";
61269
62387
  init_budgets();
62388
+ init_providerFailureAuth();
61270
62389
  init_usage();
61271
62390
  init_modelPricing();
61272
62391
  init_provider();
@@ -61636,13 +62755,13 @@ var init_secretStore = __esm({
61636
62755
  describe() {
61637
62756
  return `state Worker ${this.baseUrl} (ConfigDO secrets)`;
61638
62757
  }
61639
- async post(path, payload) {
62758
+ async post(path, payload2) {
61640
62759
  let res;
61641
62760
  try {
61642
62761
  res = await this.fetchImpl(`${this.baseUrl}${path}`, {
61643
62762
  method: "POST",
61644
62763
  headers: { authorization: `Bearer ${this.opts.token}`, "content-type": "application/json" },
61645
- body: JSON.stringify(payload),
62764
+ body: JSON.stringify(payload2),
61646
62765
  signal: AbortSignal.timeout(MCP_SECRET_WORKER_TIMEOUT_MS)
61647
62766
  });
61648
62767
  } catch (err2) {
@@ -61880,7 +62999,7 @@ function configRefusalReason(error) {
61880
62999
  return raw.replace(/^SWITCHBOARD_CONFIG=state:\/\/[^:]+:\s*/, "").slice(0, 1e3);
61881
63000
  }
61882
63001
  function createConfigRefusalServer(opts) {
61883
- const payload = {
63002
+ const payload2 = {
61884
63003
  ok: false,
61885
63004
  config: opts.problem,
61886
63005
  inFlight: 0,
@@ -61890,7 +63009,7 @@ function createConfigRefusalServer(opts) {
61890
63009
  return createServer2((req, res) => {
61891
63010
  if (new URL(req.url ?? "/", "http://switchboard.invalid").pathname === "/healthz") {
61892
63011
  res.writeHead(503, { "content-type": "application/json" });
61893
- res.end(JSON.stringify(payload));
63012
+ res.end(JSON.stringify(payload2));
61894
63013
  return;
61895
63014
  }
61896
63015
  res.writeHead(503, { "content-type": "text/plain" });
@@ -61989,7 +63108,14 @@ async function decideIntake(input, deps) {
61989
63108
  } catch (err2) {
61990
63109
  console.warn(`[intake] ${input.key}: receipt read failed \u2014 ${messageOf(err2)}; deciding as if none`);
61991
63110
  }
61992
- if (row3) return { verdict: row3.verdict, reason: row3.reason, source: row3.source, receipt: "existing" };
63111
+ if (row3)
63112
+ return {
63113
+ verdict: row3.verdict,
63114
+ reason: row3.reason,
63115
+ source: row3.source,
63116
+ receipt: "existing",
63117
+ ...row3.providerFailure !== void 0 ? { providerFailure: row3.providerFailure } : {}
63118
+ };
61993
63119
  }
61994
63120
  const decided = input.facts.pendingQuestion === true ? {
61995
63121
  verdict: "addressed",
@@ -62012,7 +63138,13 @@ async function decideIntake(input, deps) {
62012
63138
  try {
62013
63139
  const { inserted, stored } = await deps.ledger.recordIntake(input.key, row2);
62014
63140
  if (inserted) return { ...decided, receipt: "inserted" };
62015
- return { verdict: stored.verdict, reason: stored.reason, source: stored.source, receipt: "existing" };
63141
+ return {
63142
+ verdict: stored.verdict,
63143
+ reason: stored.reason,
63144
+ source: stored.source,
63145
+ receipt: "existing",
63146
+ ...stored.providerFailure !== void 0 ? { providerFailure: stored.providerFailure } : {}
63147
+ };
62016
63148
  } catch (err2) {
62017
63149
  console.warn(`[intake] ${input.key}: receipt write failed \u2014 ${messageOf(err2)}; acting on the verdict anyway`);
62018
63150
  return { ...decided, receipt: "failed" };
@@ -62038,10 +63170,20 @@ async function askModel(input, deps) {
62038
63170
  } catch (err2) {
62039
63171
  const timedOut = err2 instanceof Error && (err2.name === "TimeoutError" || err2.name === "AbortError");
62040
63172
  const attempts = attemptsOfThrow(err2);
63173
+ if (timedOut) {
63174
+ return {
63175
+ verdict: "silent",
63176
+ reason: tidyReason("the intake call timed out"),
63177
+ source: "timeout",
63178
+ ...attempts ? { attempts } : {}
63179
+ };
63180
+ }
63181
+ const failure2 = providerFailureOf(err2);
62041
63182
  return {
62042
63183
  verdict: "silent",
62043
- reason: tidyReason(timedOut ? "the intake call timed out" : `intake model failed: ${messageOf(err2)}`),
62044
- source: timedOut ? "timeout" : "error",
63184
+ reason: renderProviderFailure(failure2.cause, "ended"),
63185
+ source: "error",
63186
+ providerFailure: failure2.cause,
62045
63187
  ...attempts ? { attempts } : {}
62046
63188
  };
62047
63189
  }
@@ -62132,6 +63274,7 @@ var INTAKE_TOOL_NAME, INTAKE_ANSWERS;
62132
63274
  var init_intake = __esm({
62133
63275
  "../../src/core/intake.ts"() {
62134
63276
  "use strict";
63277
+ init_provider();
62135
63278
  init_redact();
62136
63279
  init_structured();
62137
63280
  init_untrusted();
@@ -62367,23 +63510,23 @@ async function fetchImages(files, maxImages, maxTotalBytes = Infinity) {
62367
63510
  const skipped = [];
62368
63511
  const skippedFiles = [];
62369
63512
  let bytes = 0;
62370
- const candidates = [];
63513
+ const candidates2 = [];
62371
63514
  for (const f of files ?? []) {
62372
63515
  const label = fileLabel(f);
62373
63516
  const url = f.url_private_download ?? f.url_private;
62374
- if (!url || !f.mimetype || !IMAGE_TYPES2.has(f.mimetype) || (f.size ?? 0) > MAX_IMAGE_BYTES || candidates.length >= maxImages) {
63517
+ if (!url || !f.mimetype || !IMAGE_TYPES2.has(f.mimetype) || (f.size ?? 0) > MAX_IMAGE_BYTES || candidates2.length >= maxImages) {
62375
63518
  skipped.push(label);
62376
63519
  skippedFiles.push(f);
62377
63520
  continue;
62378
63521
  }
62379
- candidates.push({ f, label, url, mediaType: f.mimetype });
63522
+ candidates2.push({ f, label, url, mediaType: f.mimetype });
62380
63523
  }
62381
63524
  const downloads = await Promise.all(
62382
- candidates.map(
63525
+ candidates2.map(
62383
63526
  ({ url, label }) => downloadSlackFile(url, label, (contentType) => contentType.includes("text/html"))
62384
63527
  )
62385
63528
  );
62386
- candidates.forEach(({ f, label, mediaType }, i) => {
63529
+ candidates2.forEach(({ f, label, mediaType }, i) => {
62387
63530
  const buf2 = downloads[i];
62388
63531
  if (!buf2 || buf2.byteLength > MAX_IMAGE_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
62389
63532
  skipped.push(label);
@@ -62429,24 +63572,24 @@ async function fetchDocuments(files, maxDocs, maxTotalBytes = Infinity) {
62429
63572
  const skipped = [];
62430
63573
  const skippedFiles = [];
62431
63574
  let bytes = 0;
62432
- const candidates = [];
63575
+ const candidates2 = [];
62433
63576
  for (const f of files ?? []) {
62434
63577
  const label = fileLabel(f);
62435
63578
  const url = f.url_private_download ?? f.url_private;
62436
63579
  const kind = classifyDocument(f.mimetype, f.name);
62437
- if (!url || !kind || (f.size ?? 0) > MAX_DOCUMENT_BYTES || candidates.length >= maxDocs) {
63580
+ if (!url || !kind || (f.size ?? 0) > MAX_DOCUMENT_BYTES || candidates2.length >= maxDocs) {
62438
63581
  skipped.push(label);
62439
63582
  skippedFiles.push(f);
62440
63583
  continue;
62441
63584
  }
62442
- candidates.push({ f, label, url, kind });
63585
+ candidates2.push({ f, label, url, kind });
62443
63586
  }
62444
63587
  const downloads = await Promise.all(
62445
- candidates.map(
63588
+ candidates2.map(
62446
63589
  ({ url, label, f }) => downloadSlackFile(url, label, (contentType) => contentType.includes("text/html") && f.mimetype !== "text/html")
62447
63590
  )
62448
63591
  );
62449
- candidates.forEach(({ f, label, kind }, i) => {
63592
+ candidates2.forEach(({ f, label, kind }, i) => {
62450
63593
  const buf2 = downloads[i];
62451
63594
  if (!buf2 || buf2.byteLength > MAX_DOCUMENT_BYTES || bytes + buf2.byteLength > maxTotalBytes) {
62452
63595
  skipped.push(label);
@@ -63133,6 +64276,7 @@ var init_slackSocketStatus = __esm({
63133
64276
  });
63134
64277
 
63135
64278
  // ../../src/channels/slack.ts
64279
+ import { randomUUID as randomUUID5 } from "node:crypto";
63136
64280
  import {
63137
64281
  App,
63138
64282
  SocketModeReceiver,
@@ -63210,6 +64354,17 @@ function createSlackApp(deps, intake) {
63210
64354
  const act = await actOnMissedMessage(m, {
63211
64355
  botUserId: id,
63212
64356
  ...intake ? { intake } : {},
64357
+ reply: async (text) => {
64358
+ await new SlackIO(app.client, {
64359
+ channel: m.channel,
64360
+ user: m.user,
64361
+ text: m.text,
64362
+ ts: m.ts,
64363
+ threadTs: m.threadTs,
64364
+ botUserId: id,
64365
+ trigger: "thread-follow-up"
64366
+ }).reply(text);
64367
+ },
63213
64368
  dispatch: (extra) => {
63214
64369
  void handle(
63215
64370
  deps,
@@ -63318,6 +64473,59 @@ function catchUpDelayNote(messageTs, nowMs) {
63318
64473
  const late = mins < 1 ? "under a minute" : `${mins} min`;
63319
64474
  return `\u23F1 Picked up ${late} after it was posted: the bot was restarting (a deploy or platform move) and Slack does not queue events while it is down. Handling it now \u2014 no need to re-send.`;
63320
64475
  }
64476
+ async function deliverIntakeFailure(opts) {
64477
+ if (opts.reply === void 0 || opts.receipt === "existing" && !opts.allowExisting) return;
64478
+ const postWithoutReceipt = async () => {
64479
+ try {
64480
+ await opts.reply(opts.reason);
64481
+ } catch (err2) {
64482
+ opts.failed(err2);
64483
+ }
64484
+ };
64485
+ if (opts.receipt === "failed" || opts.receipt === "absent") {
64486
+ await postWithoutReceipt();
64487
+ return;
64488
+ }
64489
+ const ledger = opts.ledger;
64490
+ if (ledger?.claimIntakeDelivery === void 0 || ledger.finishIntakeDelivery === void 0) {
64491
+ if (opts.receipt === "existing") return;
64492
+ await postWithoutReceipt();
64493
+ return;
64494
+ }
64495
+ let claimed2;
64496
+ try {
64497
+ claimed2 = await ledger.claimIntakeDelivery(opts.key, opts.poster, opts.claimedAt);
64498
+ } catch (err2) {
64499
+ opts.failed(err2);
64500
+ return;
64501
+ }
64502
+ if (!claimed2) return;
64503
+ const alreadyPosted = opts.botUserId !== void 0 && opts.thread.some((message4) => message4.user === opts.botUserId && message4.text === opts.reason);
64504
+ if (alreadyPosted) {
64505
+ try {
64506
+ await ledger.finishIntakeDelivery(opts.key, opts.poster, true);
64507
+ } catch (err2) {
64508
+ opts.failed(err2);
64509
+ }
64510
+ return;
64511
+ }
64512
+ try {
64513
+ await opts.reply(opts.reason);
64514
+ } catch (err2) {
64515
+ try {
64516
+ await ledger.finishIntakeDelivery(opts.key, opts.poster, false);
64517
+ } catch (finishErr) {
64518
+ opts.failed(finishErr);
64519
+ }
64520
+ opts.failed(err2);
64521
+ return;
64522
+ }
64523
+ try {
64524
+ await ledger.finishIntakeDelivery(opts.key, opts.poster, true);
64525
+ } catch (err2) {
64526
+ opts.failed(err2);
64527
+ }
64528
+ }
63321
64529
  async function actOnMissedMessage(m, opts) {
63322
64530
  const log = opts.log ?? ((line4) => console.log(line4));
63323
64531
  const seen = opts.seen ?? { was: wasHandledHere, mark: markHandledHere };
@@ -63343,6 +64551,20 @@ async function actOnMissedMessage(m, opts) {
63343
64551
  if (row2) {
63344
64552
  decided = true;
63345
64553
  silent = row2.verdict === "silent";
64554
+ if (row2.providerFailure !== void 0)
64555
+ await deliverIntakeFailure({
64556
+ key,
64557
+ receipt: "existing",
64558
+ allowExisting: true,
64559
+ ledger: intake.deps.ledger,
64560
+ claimedAt: intake.deps.now(),
64561
+ poster: `${PLATFORM3}:${opts.botUserId}:${intake.gen}:${randomUUID5()}`,
64562
+ reason: row2.reason,
64563
+ thread: m.thread,
64564
+ botUserId: opts.botUserId,
64565
+ ...opts.reply ? { reply: opts.reply } : {},
64566
+ failed: (err2) => log(`[catch-up] ${key}: provider failure could not be rendered \u2014 ${describeError3(err2)}`)
64567
+ });
63346
64568
  if (silent) log(`[catch-up] ${key}: silenced by its stored receipt (${row2.source}): ${row2.reason}`);
63347
64569
  } else {
63348
64570
  const page2 = m.thread;
@@ -63368,6 +64590,20 @@ async function actOnMissedMessage(m, opts) {
63368
64590
  log(
63369
64591
  `[intake] ${key} ${decision.verdict} (${decision.source}, receipt ${decision.receipt}): ${decision.reason} \u2014 decided at catch-up`
63370
64592
  );
64593
+ if (decision.providerFailure !== void 0)
64594
+ await deliverIntakeFailure({
64595
+ key,
64596
+ receipt: decision.receipt,
64597
+ allowExisting: true,
64598
+ ledger: intake.deps.ledger,
64599
+ claimedAt: intake.deps.now(),
64600
+ poster: `${PLATFORM3}:${opts.botUserId}:${intake.gen}:${randomUUID5()}`,
64601
+ reason: decision.reason,
64602
+ thread: m.thread,
64603
+ botUserId: opts.botUserId,
64604
+ ...opts.reply ? { reply: opts.reply } : {},
64605
+ failed: (err2) => log(`[catch-up] ${key}: provider failure could not be rendered \u2014 ${describeError3(err2)}`)
64606
+ });
63371
64607
  decided = true;
63372
64608
  silent = decision.verdict !== "addressed";
63373
64609
  }
@@ -63585,6 +64821,28 @@ async function gateThreadReply(client, ev, span, intake, mode, threadKey) {
63585
64821
  intake.deps
63586
64822
  );
63587
64823
  span.setAttrs({ intake: decision.verdict, intakeSource: decision.source, intakeReceipt: decision.receipt });
64824
+ if (decision.providerFailure !== void 0) {
64825
+ const key = `${ev.channel}:${ev.ts}`;
64826
+ await deliverIntakeFailure({
64827
+ key,
64828
+ receipt: decision.receipt,
64829
+ allowExisting: false,
64830
+ ledger: intake.deps.ledger,
64831
+ claimedAt: intake.deps.now(),
64832
+ poster: `${PLATFORM3}:${ev.botUserId ?? "unknown"}:${intake.gen}:${randomUUID5()}`,
64833
+ reason: decision.reason,
64834
+ thread: page2,
64835
+ botUserId: ev.botUserId,
64836
+ reply: (text) => new SlackIO(client, ev).reply(text),
64837
+ failed: (err2) => console.error(
64838
+ `[intake] ${key} provider failure could not be rendered \u2014 ${err2 instanceof Error ? err2.message : String(err2)}`
64839
+ )
64840
+ });
64841
+ console.log(
64842
+ `[intake] ${ev.channel}:${ev.ts} ${decision.verdict} (${decision.source}, receipt ${decision.receipt}): ${decision.reason}`
64843
+ );
64844
+ return { proceed: false };
64845
+ }
63588
64846
  if (decision.verdict !== "addressed" || decision.receipt === "existing") {
63589
64847
  console.log(
63590
64848
  `[intake] ${ev.channel}:${ev.ts} ${decision.verdict} (${decision.source}, receipt ${decision.receipt}): ${decision.reason}`
@@ -64616,9 +65874,9 @@ function createMcpHandler(deps, options) {
64616
65874
  res.end();
64617
65875
  return;
64618
65876
  }
64619
- const payload = JSON.stringify(body);
65877
+ const payload2 = JSON.stringify(body);
64620
65878
  res.writeHead(status3, { "content-type": "application/json" });
64621
- res.end(payload);
65879
+ res.end(payload2);
64622
65880
  };
64623
65881
  return (req, res) => {
64624
65882
  void (async () => {
@@ -64835,29 +66093,29 @@ async function verifyAccessJwt(token2, config, deps) {
64835
66093
  if (parts.length !== 3) return null;
64836
66094
  const [rawHeader, rawPayload, rawSignature] = parts;
64837
66095
  const header4 = decodeJsonSegment(rawHeader);
64838
- const payload = decodeJsonSegment(rawPayload);
64839
- if (!header4 || !payload) return null;
66096
+ const payload2 = decodeJsonSegment(rawPayload);
66097
+ if (!header4 || !payload2) return null;
64840
66098
  if (header4.alg !== "RS256") return null;
64841
66099
  if (typeof header4.kid !== "string" || header4.kid === "") return null;
64842
- const signature = decodeBase64Url(rawSignature);
64843
- if (!signature) return null;
66100
+ const signature2 = decodeBase64Url(rawSignature);
66101
+ if (!signature2) return null;
64844
66102
  const jwk = await resolveKey(header4.kid, config, deps);
64845
66103
  if (!jwk) return null;
64846
66104
  let signatureOk;
64847
66105
  try {
64848
66106
  const key = createPublicKey({ format: "jwk", key: jwk });
64849
- signatureOk = cryptoVerify("RSA-SHA256", Buffer.from(`${rawHeader}.${rawPayload}`), key, signature);
66107
+ signatureOk = cryptoVerify("RSA-SHA256", Buffer.from(`${rawHeader}.${rawPayload}`), key, signature2);
64850
66108
  } catch {
64851
66109
  return null;
64852
66110
  }
64853
66111
  if (!signatureOk) return null;
64854
- if (!claimsValid(payload, config, deps.now())) return null;
64855
- if (typeof payload.sub !== "string" || payload.sub === "") {
64856
- return { sub: "", commonName: payload.common_name };
66112
+ if (!claimsValid(payload2, config, deps.now())) return null;
66113
+ if (typeof payload2.sub !== "string" || payload2.sub === "") {
66114
+ return { sub: "", commonName: payload2.common_name };
64857
66115
  }
64858
66116
  return {
64859
- sub: payload.sub,
64860
- email: typeof payload.email === "string" ? payload.email : void 0
66117
+ sub: payload2.sub,
66118
+ email: typeof payload2.email === "string" ? payload2.email : void 0
64861
66119
  };
64862
66120
  }
64863
66121
  function normalizeTeamDomain(raw) {
@@ -66524,7 +67782,7 @@ var init_residentsView = __esm({
66524
67782
  });
66525
67783
 
66526
67784
  // ../../src/channels/web.ts
66527
- import { randomBytes as randomBytes6 } from "node:crypto";
67785
+ import { randomBytes as randomBytes7 } from "node:crypto";
66528
67786
  function parseThreadsRoute(pathname) {
66529
67787
  if (pathname === "/threads" || pathname === "/threads/") return { kind: "new" };
66530
67788
  const m = /^\/threads\/([^/]+)(\/send)?\/?$/.exec(pathname);
@@ -66539,7 +67797,7 @@ function parseThreadsRoute(pathname) {
66539
67797
  return m[2] ? { kind: "send", id } : { kind: "thread", id };
66540
67798
  }
66541
67799
  function mintConversationId() {
66542
- return randomBytes6(CONVERSATION_ID_BYTES).toString("hex");
67800
+ return randomBytes7(CONVERSATION_ID_BYTES).toString("hex");
66543
67801
  }
66544
67802
  function threadKeyFor(sub, id) {
66545
67803
  return id.includes(":") ? id : `${PLATFORM5}:${sub}:${id}`;
@@ -67911,7 +69169,7 @@ var init_installationSettings = __esm({
67911
69169
  });
67912
69170
 
67913
69171
  // ../../src/channels/dashboardAuth.ts
67914
- import { createHash as createHash5, timingSafeEqual as timingSafeEqual4 } from "node:crypto";
69172
+ import { createHash as createHash5, timingSafeEqual as timingSafeEqual5 } from "node:crypto";
67915
69173
  function header3(headers, name) {
67916
69174
  const raw = headers[name];
67917
69175
  return Array.isArray(raw) ? raw[0] : raw;
@@ -67931,7 +69189,7 @@ function accessVerifier(config, deps) {
67931
69189
  function secretsEqual(a, b) {
67932
69190
  const ha = createHash5("sha256").update(a).digest();
67933
69191
  const hb = createHash5("sha256").update(b).digest();
67934
- return timingSafeEqual4(ha, hb);
69192
+ return timingSafeEqual5(ha, hb);
67935
69193
  }
67936
69194
  function tokenVerifier(opts) {
67937
69195
  return {
@@ -68311,41 +69569,41 @@ function readBuildInfo(path, read = (p) => readFileSync19(p, "utf8")) {
68311
69569
  }
68312
69570
  }
68313
69571
  function healthPayload(state) {
68314
- const payload = {
69572
+ const payload2 = {
68315
69573
  ok: true,
68316
69574
  inFlight: state.inFlight,
68317
69575
  draining: state.draining,
68318
69576
  drainDeadlineMs: DRAIN_DEADLINE_MS
68319
69577
  };
68320
69578
  if (state.draining && state.drainStartedAt !== void 0) {
68321
- payload.drainStartedAt = new Date(state.drainStartedAt).toISOString();
69579
+ payload2.drainStartedAt = new Date(state.drainStartedAt).toISOString();
68322
69580
  }
68323
69581
  if (state.draining && state.held && state.held.length > 0) {
68324
- payload.held = state.held.map((r) => ({ id: r.id, why: r.why }));
69582
+ payload2.held = state.held.map((r) => ({ id: r.id, why: r.why }));
68325
69583
  }
68326
69584
  if (state.catchUp) {
68327
69585
  const catchUp = {};
68328
69586
  for (const [k, v] of Object.entries(state.catchUp)) if (v !== void 0) catchUp[k] = v;
68329
- payload.catchUp = catchUp;
69587
+ payload2.catchUp = catchUp;
68330
69588
  }
68331
69589
  if (state.slack) {
68332
- payload.slack = {
69590
+ payload2.slack = {
68333
69591
  connected: state.slack.connected,
68334
69592
  ...state.slack.since !== void 0 ? { since: state.slack.since } : {},
68335
69593
  ...state.slack.connects !== void 0 ? { connects: state.slack.connects } : {}
68336
69594
  };
68337
69595
  }
68338
69596
  if (state.build)
68339
- payload.build = {
69597
+ payload2.build = {
68340
69598
  commit: state.build.commit,
68341
69599
  ...state.build.builtAt !== void 0 ? { builtAt: state.build.builtAt } : {}
68342
69600
  };
68343
- if (state.generation !== void 0) payload.generation = state.generation;
68344
- if (state.startedAt !== void 0) payload.startedAt = new Date(state.startedAt).toISOString();
68345
- if (state.httpListeningAt !== void 0) payload.httpListeningAt = new Date(state.httpListeningAt).toISOString();
68346
- if (state.process) payload.process = { ...state.process };
68347
- if (state.artifacts) payload.artifacts = { bucket: state.artifacts.bucket };
68348
- return payload;
69601
+ if (state.generation !== void 0) payload2.generation = state.generation;
69602
+ if (state.startedAt !== void 0) payload2.startedAt = new Date(state.startedAt).toISOString();
69603
+ if (state.httpListeningAt !== void 0) payload2.httpListeningAt = new Date(state.httpListeningAt).toISOString();
69604
+ if (state.process) payload2.process = { ...state.process };
69605
+ if (state.artifacts) payload2.artifacts = { bucket: state.artifacts.bucket };
69606
+ return payload2;
68349
69607
  }
68350
69608
  var UNKNOWN_BUILD;
68351
69609
  var init_health = __esm({
@@ -69434,13 +70692,14 @@ async function contractFor(instance, unit, readers) {
69434
70692
  break;
69435
70693
  }
69436
70694
  }
69437
- return contractFromPlan({
70695
+ const contract = contractFromPlan({
69438
70696
  ...source2,
69439
70697
  readSpec: (spec) => specs.get(spec),
69440
70698
  ...agentRules ? { agentRules } : {},
69441
70699
  rebase,
69442
70700
  ...issue ? { issue } : {}
69443
70701
  });
70702
+ return unit.record !== void 0 ? { ...contract, record: unit.record } : contract;
69444
70703
  }
69445
70704
  async function continuationPreface(cont, unit, readers) {
69446
70705
  const previous = cont.previousRunId !== void 0 ? await readers.readRunFacts(cont.previousRunId) : void 0;
@@ -69486,9 +70745,12 @@ async function composeChild(brief, instance, unit, readers) {
69486
70745
  case "contract": {
69487
70746
  const contract = await contractFor(instance, unit, readers);
69488
70747
  const task = isGenerated(instance) ? contract.unit.section.split("\n").slice(1).join("\n").trim() : `Implement unit ${contract.unit.id} \u2014 ${contract.unit.title} \u2014 of ${instance.plan?.path ?? "the plan"}: the contract below is the unit. Do its first instruction first, then add every test scenario it lists, update every spec row it names and weaken no guard.`;
69489
- const prompt = brief.continue !== void 0 ? `${await continuationPreface(brief.continue, unit, readers)}
70748
+ const work = unit.record !== void 0 ? `record: ${unit.record}
69490
70749
 
69491
70750
  ${task}` : task;
70751
+ const prompt = brief.continue !== void 0 ? `${await continuationPreface(brief.continue, unit, readers)}
70752
+
70753
+ ${work}` : work;
69492
70754
  return { preset: "coding", prompt, ref: unit.branch, contract };
69493
70755
  }
69494
70756
  case "review": {
@@ -69834,6 +71096,7 @@ function watched(io, on) {
69834
71096
  if (io.attachFile) out.attachFile = (file) => io.attachFile(file);
69835
71097
  if (io.uploadTicket) out.uploadTicket = (file) => io.uploadTicket(file);
69836
71098
  if (io.runFinished) out.runFinished = (receipt) => io.runFinished(receipt);
71099
+ if (io.requestFailed) out.requestFailed = () => io.requestFailed();
69837
71100
  if (io.openThread) out.openThread = (lead) => io.openThread(lead);
69838
71101
  return out;
69839
71102
  }
@@ -70014,12 +71277,18 @@ function coordinatorRunView(view, parentInstanceId, finalReply) {
70014
71277
  parentInstanceId,
70015
71278
  ...view.idempotencyKey !== void 0 ? { idempotencyKey: view.idempotencyKey } : {},
70016
71279
  ...view.ownerGen !== void 0 ? { ownerGen: view.ownerGen } : {},
70017
- ...finalReply !== void 0 ? { finalReply } : {}
71280
+ ...finalReply !== void 0 ? { finalReply } : {},
71281
+ ...view.headSha !== void 0 ? { headSha: view.headSha } : {}
70018
71282
  };
70019
71283
  }
70020
71284
  function prOpenedOf(events) {
70021
71285
  const last = [...events ?? []].reverse().find((e) => e.type === "pr_opened");
70022
- return last && last.type === "pr_opened" ? { number: last.number, url: last.url, created: last.created } : void 0;
71286
+ return last && last.type === "pr_opened" ? {
71287
+ number: last.number,
71288
+ url: last.url,
71289
+ created: last.created,
71290
+ ...last.head !== void 0 ? { head: last.head } : {}
71291
+ } : void 0;
70023
71292
  }
70024
71293
  async function reviewPostedAt(deps, pr, verdict, head) {
70025
71294
  const [reviews, self] = await Promise.all([
@@ -70065,25 +71334,25 @@ async function postedHumanGateAnswer(deps, pr, requester, addressSeverity) {
70065
71334
  if (own === void 0) return void 0;
70066
71335
  const marker = /<!-- switchboard:verdict (\{[^\n]*\}) -->/.exec(own.body);
70067
71336
  if (marker === null) return void 0;
70068
- let payload;
71337
+ let payload2;
70069
71338
  try {
70070
- payload = JSON.parse(marker[1]);
71339
+ payload2 = JSON.parse(marker[1]);
70071
71340
  } catch {
70072
71341
  return void 0;
70073
71342
  }
70074
- if (typeof payload !== "object" || payload === null) return void 0;
70075
- const row2 = payload;
71343
+ if (typeof payload2 !== "object" || payload2 === null) return void 0;
71344
+ const row2 = payload2;
70076
71345
  if (row2.verdict !== "approve" && row2.verdict !== "request_changes") return void 0;
70077
71346
  if (!Array.isArray(row2.findings)) return void 0;
70078
71347
  const postedFindings = row2.findings.map((finding) => legacyPostedFinding(finding, own.body));
70079
71348
  if (!postedFindings.every((finding) => finding !== void 0)) return void 0;
70080
71349
  const findings = row2.verdict === "approve" ? findingsAtOrAbove(postedFindings, addressSeverity) : postedFindings;
70081
71350
  if (findings.length === 0 || !findings.every((finding) => finding.humanGated === true)) return void 0;
70082
- const candidates = comments.filter(
71351
+ const candidates2 = comments.filter(
70083
71352
  (comment) => comment.author.type === "User" && comment.body.trim() !== "" && Number.isFinite(Date.parse(comment.createdAt)) && Date.parse(comment.createdAt) >= githubSecondStart(Date.parse(own.submittedAt))
70084
71353
  ).sort((a, b) => Date.parse(b.createdAt) - Date.parse(a.createdAt));
70085
71354
  let answer;
70086
- for (const candidate of candidates) {
71355
+ for (const candidate of candidates2) {
70087
71356
  const authorized = await deps.commenterAuthorized(requester, candidate.author).catch(() => false);
70088
71357
  if (authorized) {
70089
71358
  answer = candidate;
@@ -70117,6 +71386,44 @@ function reviewPostedByRecord(record2, pr) {
70117
71386
  const same = post.target.repo === pr.repo && post.target.number === pr.number && sameCommit(post.head.toLowerCase(), record2.reviewHead) && post.verdict === record2.verdict.verdict;
70118
71387
  return same ? { reviewPosted: true } : void 0;
70119
71388
  }
71389
+ function headBranchStateUnknown(facts2) {
71390
+ return facts2.state === "open" && facts2.headBranchExists === void 0;
71391
+ }
71392
+ function headBranchStateError(repo, number) {
71393
+ return `could not verify whether ${repo}#${number}'s head branch exists`;
71394
+ }
71395
+ function pullRequestState(number, fallbackUrl, facts2) {
71396
+ if (headBranchStateUnknown(facts2)) return void 0;
71397
+ const url = facts2.htmlUrl ?? fallbackUrl;
71398
+ if (facts2.mergedAt !== void 0 && facts2.mergeCommitSha !== void 0)
71399
+ return {
71400
+ state: "merged",
71401
+ prNumber: number,
71402
+ url,
71403
+ sha: facts2.mergeCommitSha,
71404
+ mergedAt: facts2.mergedAt,
71405
+ ...facts2.mergedBy !== void 0 ? { mergedBy: facts2.mergedBy } : {}
71406
+ };
71407
+ if (facts2.state === "closed")
71408
+ return { state: "closed", prNumber: number, url, closedBy: facts2.closedBy ?? "unknown GitHub user" };
71409
+ return {
71410
+ state: "open",
71411
+ prNumber: number,
71412
+ url,
71413
+ ...facts2.headSha !== void 0 ? { headSha: facts2.headSha } : {},
71414
+ ...facts2.headBranchExists !== void 0 ? { headBranchExists: facts2.headBranchExists } : {}
71415
+ };
71416
+ }
71417
+ async function childPullRequestState(deps, instance, unit) {
71418
+ if (instance == null || unit === void 0) return void 0;
71419
+ const row2 = (await deps.instances.listUnits(instance.id)).find((candidate) => candidate.unit === unit);
71420
+ if (row2?.pr === void 0) return void 0;
71421
+ const facts2 = await deps.fetchPrFacts({ repo: instance.repo, number: row2.pr.number });
71422
+ if (facts2 === void 0) throw new Error(`could not read ${instance.repo}#${row2.pr.number}`);
71423
+ const state = pullRequestState(row2.pr.number, row2.pr.url, facts2);
71424
+ if (state === void 0) throw new Error(headBranchStateError(instance.repo, row2.pr.number));
71425
+ return state;
71426
+ }
70120
71427
  async function readRecord(body, deps) {
70121
71428
  const id = parseInstanceId(body.parentInstanceId);
70122
71429
  if (!id.ok) return json3(400, { ok: false, error: id.error });
@@ -70128,13 +71435,35 @@ async function readRecord(body, deps) {
70128
71435
  const res = await deps.runs.getRun(body.runId);
70129
71436
  if (!res.ok || res.value.parentInstanceId !== id.value) return json3(404, { ok: false, error: "not_found" });
70130
71437
  const view = res.value;
70131
- if (!view.finished) return json3(200, { ok: true, run: coordinatorRunView(view, id.value, void 0), at });
71438
+ const instanceRow = await deps.instances.get(id.value);
71439
+ let pullRequest;
71440
+ try {
71441
+ pullRequest = await childPullRequestState(deps, instanceRow, typeof body.unit === "string" ? body.unit : void 0);
71442
+ } catch (err2) {
71443
+ return json3(502, { ok: false, error: "github_unavailable", message: describe11(err2), at });
71444
+ }
71445
+ if (!view.finished)
71446
+ return json3(200, {
71447
+ ok: true,
71448
+ run: coordinatorRunView(view, id.value, void 0),
71449
+ ...pullRequest !== void 0 ? { pullRequest } : {},
71450
+ at
71451
+ });
70132
71452
  const full = await deps.runs.getRun(body.runId, { include: "messages" });
70133
71453
  const record2 = full.ok ? full.value : view;
70134
71454
  const finalReply = finalReplyOf2(record2.events);
70135
71455
  const reviewAskedAt = reviewAskedAtOf(record2.events);
70136
- const pr = prOpenedOf(record2.events);
70137
- const instanceRow = await deps.instances.get(id.value);
71456
+ const opened = prOpenedOf(record2.events);
71457
+ const pr = opened !== void 0 ? { number: opened.number, url: opened.url, created: opened.created } : void 0;
71458
+ let coordinatorUnit;
71459
+ if (instanceRow !== null && typeof body.unit === "string") {
71460
+ coordinatorUnit = (await deps.instances.listUnits(instanceRow.id)).find((u) => u.unit === body.unit);
71461
+ const pushedPrRef = opened?.head !== void 0 && record2.pushed?.some((pushed) => pushed.ref === opened.head) ? opened.head : void 0;
71462
+ if (coordinatorUnit !== void 0 && pushedPrRef !== void 0 && coordinatorUnit.branch !== pushedPrRef) {
71463
+ coordinatorUnit = { ...coordinatorUnit, branch: pushedPrRef };
71464
+ await deps.instances.putUnits([coordinatorUnit]);
71465
+ }
71466
+ }
70138
71467
  if (view.status === "interrupted") {
70139
71468
  const successor = await restartedChildOf(deps, id.value, view).catch(() => void 0);
70140
71469
  if (successor !== void 0)
@@ -70142,6 +71471,7 @@ async function readRecord(body, deps) {
70142
71471
  ok: true,
70143
71472
  ...instanceRow?.stop !== void 0 ? { stopped: true } : {},
70144
71473
  run: { id: successor, finished: false },
71474
+ ...pullRequest !== void 0 ? { pullRequest } : {},
70145
71475
  restartedAs: successor,
70146
71476
  at
70147
71477
  });
@@ -70150,15 +71480,15 @@ async function readRecord(body, deps) {
70150
71480
  ok: true,
70151
71481
  ...instanceRow?.stop !== void 0 ? { stopped: true } : {},
70152
71482
  run: { id: view.id, finished: false },
71483
+ ...pullRequest !== void 0 ? { pullRequest } : {},
70153
71484
  at
70154
71485
  });
70155
71486
  }
70156
71487
  let posted;
70157
71488
  if (record2.verdict !== void 0 && record2.reviewHead !== void 0 && typeof body.unit === "string") {
70158
71489
  const instance = instanceRow;
70159
- const row2 = instance ? (await deps.instances.listUnits(instance.id)).find((u) => u.unit === body.unit) : void 0;
70160
- if (instance && row2?.pr !== void 0) {
70161
- const unitPr = { repo: instance.repo, number: row2.pr.number };
71490
+ if (instance && coordinatorUnit?.pr !== void 0) {
71491
+ const unitPr = { repo: instance.repo, number: coordinatorUnit.pr.number };
70162
71492
  posted = reviewPostedByRecord(record2, unitPr);
70163
71493
  if (posted === void 0) {
70164
71494
  const seen = await reviewPostedAtPatiently(deps, unitPr, record2.verdict.verdict, record2.reviewHead);
@@ -70169,6 +71499,7 @@ async function readRecord(body, deps) {
70169
71499
  return json3(200, {
70170
71500
  ok: true,
70171
71501
  ...instanceRow?.stop !== void 0 ? { stopped: true } : {},
71502
+ ...pullRequest !== void 0 ? { pullRequest } : {},
70172
71503
  run: {
70173
71504
  ...coordinatorRunView(view, id.value, finalReply),
70174
71505
  ...pr !== void 0 ? { pr } : {},
@@ -70296,6 +71627,24 @@ async function recoverPushedBranch(deps, instance, row2, branch2, runId3) {
70296
71627
  throw err2;
70297
71628
  }
70298
71629
  }
71630
+ async function steerChild(body, deps) {
71631
+ const id = parseInstanceId(body.parentInstanceId);
71632
+ if (!id.ok) return json3(400, { ok: false, error: id.error });
71633
+ if (typeof body.runId !== "string" || !RUN_ID_PATTERN.test(body.runId))
71634
+ return json3(400, { ok: false, error: "runId must be a run id" });
71635
+ if (typeof body.reason !== "string" || !CHILD_SUPERSESSION_REASONS.has(body.reason))
71636
+ return json3(400, { ok: false, error: "reason must name a pull request transition" });
71637
+ const at = (deps.clock ?? systemClock)();
71638
+ const instance = await deps.instances.get(id.value);
71639
+ if (instance === null) return json3(404, { ok: false, error: "unknown_instance", at });
71640
+ const child = await deps.runs.getRun(body.runId);
71641
+ if (!child.ok || child.value.parentInstanceId !== instance.id)
71642
+ return json3(404, { ok: false, error: "not_found", at });
71643
+ const reason2 = body.reason === "merged" ? "The pull request merged while this run was live." : body.reason === "closed" ? "The pull request closed while this run was live." : body.reason === "head_moved" ? "The pull request moved to another head while this review was live." : "The pull request's head branch was deleted while this run was live.";
71644
+ const text = `${reason2} End now without a push or a review post. Record what already happened in the run's final reply.`;
71645
+ const steered = await deps.steerChild?.(body.runId, text, instance) ?? false;
71646
+ return json3(200, { ok: true, outcome: steered ? "steered" : "not_live", at });
71647
+ }
70299
71648
  async function prCheck(body, deps) {
70300
71649
  const id = parseInstanceId(body.parentInstanceId);
70301
71650
  if (!id.ok) return json3(400, { ok: false, error: id.error });
@@ -70317,6 +71666,42 @@ async function prCheck(body, deps) {
70317
71666
  await deps.instances.putUnits([{ ...unit.row, pr }]);
70318
71667
  };
70319
71668
  try {
71669
+ if (follow !== void 0) {
71670
+ const facts2 = await deps.fetchPrFacts({ repo: instance.repo, number: follow });
71671
+ if (facts2 === void 0) throw new Error(`could not read ${instance.repo}#${follow}`);
71672
+ const fallbackUrl = unit.row?.pr?.url ?? `https://github.com/${instance.repo}/pull/${follow}`;
71673
+ const state2 = pullRequestState(follow, fallbackUrl, facts2);
71674
+ if (state2 === void 0) throw new Error(headBranchStateError(instance.repo, follow));
71675
+ await remember({ number: follow, url: String(state2.url) });
71676
+ if (state2.state !== "open") return json3(200, { ok: true, ...state2, at });
71677
+ const prRef = { repo: instance.repo, number: follow };
71678
+ const headSha = facts2.headSha;
71679
+ const approved = entry && headSha !== void 0 ? await reviewPostedAt(deps, prRef, "approve", headSha) : void 0;
71680
+ const humanGate = entry ? await postedHumanGateAnswer(
71681
+ deps,
71682
+ prRef,
71683
+ instance.userId,
71684
+ instance.addressSeverity ?? DEFAULT_ADDRESS_SEVERITY
71685
+ ) : void 0;
71686
+ const entryChecks = entry && headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, headSha).catch(() => void 0) : void 0;
71687
+ const checks = body.checks === true && headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, headSha).catch(() => void 0) : entryChecks;
71688
+ const fixups = body.checks === true ? await deps.fixupCommitSubjects(prRef).catch(() => void 0) : void 0;
71689
+ const queueBase = facts2.baseRef ?? instance.base;
71690
+ const baseHasMergeQueue = body.checks === true && deps.branchHasMergeQueue !== void 0 && queueBase !== void 0 ? await deps.branchHasMergeQueue(instance.repo, queueBase).catch(() => void 0) : void 0;
71691
+ return json3(200, {
71692
+ ok: true,
71693
+ ...state2,
71694
+ ...entry && headSha !== void 0 ? { branchHead: headSha } : {},
71695
+ ...approved !== void 0 ? { approved } : {},
71696
+ ...humanGate !== void 0 ? { humanGate } : {},
71697
+ ...facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {},
71698
+ ...checks !== void 0 ? { checks } : {},
71699
+ ...body.checks === true && facts2.mergeableState !== void 0 ? { mergeableState: facts2.mergeableState } : {},
71700
+ ...fixups !== void 0 ? { fixupCommits: fixups } : {},
71701
+ ...typeof baseHasMergeQueue === "boolean" ? { baseHasMergeQueue } : {},
71702
+ at
71703
+ });
71704
+ }
70320
71705
  const open2 = await deps.findOpenPrByHead(instance.repo, branch2);
70321
71706
  if (open2) {
70322
71707
  await remember({ number: open2.number, url: open2.htmlUrl });
@@ -70324,7 +71709,12 @@ async function prCheck(body, deps) {
70324
71709
  instanceId: instance.id,
70325
71710
  unit: unit.row?.unit ?? body.unit
70326
71711
  });
70327
- const entryFacts = entry ? await deps.fetchPrFacts({ repo: instance.repo, number: open2.number }).catch(() => void 0) : void 0;
71712
+ const liveFacts = await deps.fetchPrFacts({ repo: instance.repo, number: open2.number });
71713
+ if (liveFacts === void 0) throw new Error(`could not read ${instance.repo}#${open2.number}`);
71714
+ const current = pullRequestState(open2.number, open2.htmlUrl, liveFacts);
71715
+ if (current === void 0) throw new Error(headBranchStateError(instance.repo, open2.number));
71716
+ if (current.state !== "open") return json3(200, { ok: true, ...current, at });
71717
+ const entryFacts = entry ? liveFacts : void 0;
70328
71718
  const branchHead = entryFacts?.headSha;
70329
71719
  const entryHead = branchHead ?? open2.headSha;
70330
71720
  const approved = entry && entryHead !== void 0 ? await reviewPostedAt(deps, { repo: instance.repo, number: open2.number }, "approve", entryHead) : void 0;
@@ -70337,7 +71727,7 @@ async function prCheck(body, deps) {
70337
71727
  const entryChecks = entry && entryHead !== void 0 ? await deps.fetchCommitChecks(instance.repo, entryHead).catch(() => void 0) : void 0;
70338
71728
  const checks = body.checks === true && open2.headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, open2.headSha).catch(() => void 0) : entryChecks;
70339
71729
  const prRef = { repo: instance.repo, number: open2.number };
70340
- const facts2 = body.checks === true ? await deps.fetchPrFacts(prRef).catch(() => void 0) : void 0;
71730
+ const facts2 = body.checks === true ? liveFacts : void 0;
70341
71731
  const fixups = body.checks === true ? await deps.fixupCommitSubjects(prRef).catch(() => void 0) : void 0;
70342
71732
  const queueBase = facts2?.baseRef ?? instance.base;
70343
71733
  const baseHasMergeQueue = body.checks === true && deps.branchHasMergeQueue !== void 0 && queueBase !== void 0 ? await deps.branchHasMergeQueue(instance.repo, queueBase).catch(() => void 0) : void 0;
@@ -70346,7 +71736,8 @@ async function prCheck(body, deps) {
70346
71736
  state: "open",
70347
71737
  prNumber: open2.number,
70348
71738
  url: open2.htmlUrl,
70349
- ...open2.headSha !== void 0 ? { headSha: open2.headSha } : {},
71739
+ ...liveFacts.headSha !== void 0 ? { headSha: liveFacts.headSha } : {},
71740
+ ...liveFacts.headBranchExists !== void 0 ? { headBranchExists: liveFacts.headBranchExists } : {},
70350
71741
  ...branchHead !== void 0 ? { branchHead } : {},
70351
71742
  ...approved !== void 0 ? { approved } : {},
70352
71743
  ...humanGate !== void 0 ? { humanGate } : {},
@@ -70362,39 +71753,6 @@ async function prCheck(body, deps) {
70362
71753
  }
70363
71754
  const merged = await deps.findMergedPrByHead(instance.repo, branch2);
70364
71755
  if (!merged) {
70365
- if (follow !== void 0 && recover === void 0) {
70366
- const facts2 = await deps.fetchPrFacts({ repo: instance.repo, number: follow });
70367
- if (facts2 !== void 0) {
70368
- const url = facts2.htmlUrl ?? `https://github.com/${instance.repo}/pull/${follow}`;
70369
- if (facts2.mergedAt !== void 0 && facts2.mergeCommitSha !== void 0) {
70370
- await remember({ number: follow, url });
70371
- return json3(200, {
70372
- ok: true,
70373
- state: "merged",
70374
- prNumber: follow,
70375
- url,
70376
- sha: facts2.mergeCommitSha,
70377
- mergedAt: facts2.mergedAt,
70378
- at
70379
- });
70380
- }
70381
- if (facts2.state === "open") {
70382
- await remember({ number: follow, url });
70383
- const checks = body.checks === true && facts2.headSha !== void 0 ? await deps.fetchCommitChecks(instance.repo, facts2.headSha).catch(() => void 0) : void 0;
70384
- return json3(200, {
70385
- ok: true,
70386
- state: "open",
70387
- prNumber: follow,
70388
- url,
70389
- ...facts2.headSha !== void 0 ? { headSha: facts2.headSha } : {},
70390
- ...facts2.autoMergeEnabled !== void 0 ? { autoMergeEnabled: facts2.autoMergeEnabled } : {},
70391
- ...checks !== void 0 ? { checks } : {},
70392
- at
70393
- });
70394
- }
70395
- return json3(200, { ok: true, state: "none", prClosed: true, at });
70396
- }
70397
- }
70398
71756
  if (recover === void 0) {
70399
71757
  const ahead = instance.base === void 0 ? void 0 : await deps.commitsOverBase(instance.repo, instance.base, branch2).catch((err2) => {
70400
71758
  (deps.log ?? console.log)(
@@ -70407,20 +71765,20 @@ async function prCheck(body, deps) {
70407
71765
  const recovered = await recoverPushedBranch(deps, instance, unit.row, branch2, recover.runId);
70408
71766
  if (recovered.kind === "opened") {
70409
71767
  await remember({ number: recovered.pr.number, url: recovered.pr.htmlUrl });
70410
- return json3(200, { ok: true, state: "open", prNumber: recovered.pr.number, url: recovered.pr.htmlUrl, at });
71768
+ const recoveredFacts = await deps.fetchPrFacts({ repo: instance.repo, number: recovered.pr.number });
71769
+ if (recoveredFacts === void 0) throw new Error(`could not read ${instance.repo}#${recovered.pr.number}`);
71770
+ const recoveredState = pullRequestState(recovered.pr.number, recovered.pr.htmlUrl, recoveredFacts);
71771
+ if (recoveredState === void 0) throw new Error(headBranchStateError(instance.repo, recovered.pr.number));
71772
+ return json3(200, { ok: true, ...recoveredState, at });
70411
71773
  }
70412
71774
  return json3(200, { ok: true, state: "none", unrecovered: recovered.why, at });
70413
71775
  }
70414
71776
  await remember({ number: merged.number, url: merged.htmlUrl });
70415
- return json3(200, {
70416
- ok: true,
70417
- state: "merged",
70418
- prNumber: merged.number,
70419
- url: merged.htmlUrl,
70420
- sha: merged.sha,
70421
- mergedAt: merged.mergedAt,
70422
- at
70423
- });
71777
+ const mergedFacts = await deps.fetchPrFacts({ repo: instance.repo, number: merged.number });
71778
+ if (mergedFacts === void 0) throw new Error(`could not read ${instance.repo}#${merged.number}`);
71779
+ const state = pullRequestState(merged.number, merged.htmlUrl, mergedFacts);
71780
+ if (state?.state !== "merged") throw new Error(`${instance.repo}#${merged.number} no longer reads merged`);
71781
+ return json3(200, { ok: true, ...state, at });
70424
71782
  } catch (err2) {
70425
71783
  return json3(502, { ok: false, error: "github_unavailable", message: describe11(err2), at });
70426
71784
  }
@@ -71124,6 +72482,13 @@ async function merge(body, deps, subject2) {
71124
72482
  }
71125
72483
  if (facts2 === void 0)
71126
72484
  return json3(502, { ok: false, error: "github_unavailable", message: `${where} could not be read`, at });
72485
+ if (headBranchStateUnknown(facts2))
72486
+ return json3(502, {
72487
+ ok: false,
72488
+ error: "github_unavailable",
72489
+ message: headBranchStateError(instance.repo, pr.number),
72490
+ at
72491
+ });
71127
72492
  if (isReleasePullRequest(facts2))
71128
72493
  return refused7(`${where} is the release pull request \u2014 always a person's merge, never the runner's`);
71129
72494
  if (facts2.state !== "open") {
@@ -71134,9 +72499,23 @@ async function merge(body, deps, subject2) {
71134
72499
  by: "other",
71135
72500
  sha: facts2.mergeCommitSha,
71136
72501
  mergedAt: facts2.mergedAt,
72502
+ ...facts2.mergedBy !== void 0 ? { mergedBy: facts2.mergedBy } : {},
71137
72503
  at
71138
72504
  });
71139
- return refused7(`${where} is ${facts2.state}`);
72505
+ const pullRequest = pullRequestState(
72506
+ body.prNumber,
72507
+ row2.pr?.url ?? `https://github.com/${instance.repo}/pull/${body.prNumber}`,
72508
+ facts2
72509
+ );
72510
+ return pullRequest === void 0 ? refused7(`${where} is ${facts2.state}`) : json3(200, { ok: true, outcome: "recheck", pullRequest, at });
72511
+ }
72512
+ if (facts2.headBranchExists === false) {
72513
+ const pullRequest = pullRequestState(
72514
+ body.prNumber,
72515
+ row2.pr?.url ?? `https://github.com/${instance.repo}/pull/${body.prNumber}`,
72516
+ facts2
72517
+ );
72518
+ if (pullRequest !== void 0) return json3(200, { ok: true, outcome: "recheck", pullRequest, at });
71140
72519
  }
71141
72520
  if (body.queued === true) {
71142
72521
  const queue = deps.fetchMergeQueueState !== void 0 ? await deps.fetchMergeQueueState(pr).catch(() => void 0) : void 0;
@@ -71163,10 +72542,14 @@ async function merge(body, deps, subject2) {
71163
72542
  }
71164
72543
  if (facts2.headRef !== row2.branch)
71165
72544
  return refused7(`${where} heads \`${facts2.headRef ?? "?"}\`, not the unit's branch \`${row2.branch}\``);
71166
- if (facts2.headSha === void 0 || !sameCommit(facts2.headSha, headSha))
71167
- return refused7(
71168
- `the head of ${where} moved: \`${facts2.headSha?.slice(0, 7) ?? "?"}\` is not the approved \`${headSha.slice(0, 7)}\``
72545
+ if (facts2.headSha === void 0 || !sameCommit(facts2.headSha, headSha)) {
72546
+ const pullRequest = pullRequestState(
72547
+ body.prNumber,
72548
+ row2.pr?.url ?? `https://github.com/${instance.repo}/pull/${body.prNumber}`,
72549
+ facts2
71169
72550
  );
72551
+ return pullRequest === void 0 ? refused7(`the head of ${where} could not be read`) : json3(200, { ok: true, outcome: "recheck", pullRequest, at });
72552
+ }
71170
72553
  if (facts2.mergeableState === "dirty") {
71171
72554
  const base = facts2.baseRef ?? "its base";
71172
72555
  return json3(200, {
@@ -71267,6 +72650,22 @@ async function checksStep(body, deps) {
71267
72650
  const unit = await unitRowOf(deps, instance, body.unit);
71268
72651
  if (!unit.ok) return unit.response;
71269
72652
  const log = deps.log ?? console.log;
72653
+ const facts2 = await deps.fetchPrFacts({ repo: instance.repo, number: body.prNumber }).catch(() => void 0);
72654
+ if (facts2 === void 0) return json3(502, { ok: false, error: "github_unavailable", at });
72655
+ if (headBranchStateUnknown(facts2))
72656
+ return json3(502, {
72657
+ ok: false,
72658
+ error: "github_unavailable",
72659
+ message: headBranchStateError(instance.repo, body.prNumber),
72660
+ at
72661
+ });
72662
+ const pullRequest = pullRequestState(
72663
+ body.prNumber,
72664
+ unit.row?.pr?.url ?? `https://github.com/${instance.repo}/pull/${body.prNumber}`,
72665
+ facts2
72666
+ );
72667
+ if (pullRequest !== void 0 && (pullRequest.state !== "open" || pullRequest.headBranchExists === false || typeof pullRequest.headSha === "string" && !sameCommit(pullRequest.headSha, headSha)))
72668
+ return json3(200, { ok: true, pullRequest, at });
71270
72669
  if (body.retry !== void 0 && body.refire !== void 0)
71271
72670
  return json3(400, { ok: false, error: "checks recovery must be retry or refire, not both" });
71272
72671
  if (body.retry !== void 0) {
@@ -71286,7 +72685,6 @@ async function checksStep(body, deps) {
71286
72685
  );
71287
72686
  return json3(200, { ok: true, refired, at });
71288
72687
  }
71289
- const facts2 = await deps.fetchPrFacts({ repo: instance.repo, number: body.prNumber }).catch(() => void 0);
71290
72688
  let checks;
71291
72689
  if (deps.fetchRoundChecks !== void 0) {
71292
72690
  checks = await deps.fetchRoundChecks(instance.repo, headSha, body.prNumber, facts2?.baseRef).catch(() => void 0);
@@ -71307,7 +72705,8 @@ async function checksStep(body, deps) {
71307
72705
  return json3(200, {
71308
72706
  ok: true,
71309
72707
  ...checks !== void 0 ? { checks } : {},
71310
- ...facts2?.draft === true ? { draft: true } : {},
72708
+ ...pullRequest !== void 0 ? { pullRequest } : {},
72709
+ ...facts2.draft === true ? { draft: true } : {},
71311
72710
  at
71312
72711
  });
71313
72712
  }
@@ -71458,6 +72857,8 @@ async function answerCoordinatorStep(door, body, deps) {
71458
72857
  return spawn6(parsed.value, deps);
71459
72858
  case "read-record":
71460
72859
  return readRecord(parsed.value, deps);
72860
+ case "steer":
72861
+ return steerChild(parsed.value, deps);
71461
72862
  case "pr-check":
71462
72863
  return prCheck(parsed.value, deps);
71463
72864
  case "round":
@@ -71505,7 +72906,7 @@ function createAdminCoordinatorHandler(deps) {
71505
72906
  })();
71506
72907
  };
71507
72908
  }
71508
- var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, ROUND_OUTCOMES, ENDING_ICON, STEPS;
72909
+ var COORDINATOR_ADMIN_PREFIX, MAX_SPAWN_PROMPT_CHARS, FINISHED_LOOKBACK_PAGES, MAX_ADMIN_BODY_BYTES, UNIT_ID, PRESETS_OF_KIND, invalid, json3, describe11, isGenerated2, EVERY_RUN, REVIEW_POSTED_CHECKS, REVIEW_POSTED_RECHECK_MS, githubSecondStart, CHILD_SUPERSESSION_REASONS, ROUND_OUTCOMES, ENDING_ICON, STEPS;
71509
72910
  var init_adminCoordinator = __esm({
71510
72911
  "../../src/channels/adminCoordinator.ts"() {
71511
72912
  "use strict";
@@ -71558,6 +72959,7 @@ var init_adminCoordinator = __esm({
71558
72959
  REVIEW_POSTED_CHECKS = 3;
71559
72960
  REVIEW_POSTED_RECHECK_MS = 2e3;
71560
72961
  githubSecondStart = (at) => Math.floor(at / SECOND_MS) * SECOND_MS;
72962
+ CHILD_SUPERSESSION_REASONS = /* @__PURE__ */ new Set(["merged", "closed", "head_moved", "branch_deleted"]);
71561
72963
  ROUND_OUTCOMES = [
71562
72964
  "started",
71563
72965
  "pr_opened",
@@ -71589,6 +72991,7 @@ var init_adminCoordinator = __esm({
71589
72991
  "branch",
71590
72992
  "spawn",
71591
72993
  "read-record",
72994
+ "steer",
71592
72995
  "pr-check",
71593
72996
  "round",
71594
72997
  "unit-end",
@@ -71602,15 +73005,15 @@ var init_adminCoordinator = __esm({
71602
73005
  });
71603
73006
 
71604
73007
  // ../../src/core/coordinator/checksIntake.ts
71605
- async function verifyWebhookSignature(secret2, rawBody, signature) {
73008
+ async function verifyWebhookSignature(secret2, rawBody, signature2) {
71606
73009
  const key = await crypto.subtle.importKey("raw", encoder2.encode(secret2), { name: "HMAC", hash: "SHA-256" }, false, [
71607
73010
  "sign"
71608
73011
  ]);
71609
73012
  const digest = new Uint8Array(await crypto.subtle.sign("HMAC", key, encoder2.encode(rawBody)));
71610
73013
  const expected = `sha256=${toHex(digest)}`;
71611
- if (signature.length !== expected.length) return false;
73014
+ if (signature2.length !== expected.length) return false;
71612
73015
  let diff = 0;
71613
- for (let i = 0; i < expected.length; i++) diff |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
73016
+ for (let i = 0; i < expected.length; i++) diff |= expected.charCodeAt(i) ^ signature2.charCodeAt(i);
71614
73017
  return diff === 0;
71615
73018
  }
71616
73019
  function createMergeWaitRegistry(ttlMs = MERGE_WAIT_TTL_MS) {
@@ -71639,14 +73042,14 @@ async function handleCheckRunIntake(headers, rawBody, deps) {
71639
73042
  if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
71640
73043
  return { status: 401, body: { error: "unauthorized" } };
71641
73044
  if (headers.event !== "check_run") return { status: 200, body: { ok: true, ignored: "event" } };
71642
- let payload;
73045
+ let payload2;
71643
73046
  try {
71644
- payload = JSON.parse(rawBody);
73047
+ payload2 = JSON.parse(rawBody);
71645
73048
  } catch {
71646
73049
  return { status: 400, body: { error: "invalid_json" } };
71647
73050
  }
71648
- if (typeof payload !== "object" || payload === null) return { status: 400, body: { error: "invalid_body" } };
71649
- const p = payload;
73051
+ if (typeof payload2 !== "object" || payload2 === null) return { status: 400, body: { error: "invalid_body" } };
73052
+ const p = payload2;
71650
73053
  if (p.action !== "completed") return { status: 200, body: { ok: true, ignored: "action" } };
71651
73054
  const headSha = p.check_run?.head_sha;
71652
73055
  const repo = p.repository?.full_name;
@@ -71666,14 +73069,14 @@ async function handleIssueCommentIntake(headers, rawBody, deps) {
71666
73069
  if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
71667
73070
  return { status: 401, body: { error: "unauthorized" } };
71668
73071
  if (headers.event !== "issue_comment") return { status: 200, body: { ok: true, ignored: "event" } };
71669
- let payload;
73072
+ let payload2;
71670
73073
  try {
71671
- payload = JSON.parse(rawBody);
73074
+ payload2 = JSON.parse(rawBody);
71672
73075
  } catch {
71673
73076
  return { status: 400, body: { error: "invalid_json" } };
71674
73077
  }
71675
- if (typeof payload !== "object" || payload === null) return { status: 400, body: { error: "invalid_body" } };
71676
- const p = payload;
73078
+ if (typeof payload2 !== "object" || payload2 === null) return { status: 400, body: { error: "invalid_body" } };
73079
+ const p = payload2;
71677
73080
  if (p.action !== "created") return { status: 200, body: { ok: true, ignored: "action" } };
71678
73081
  if (p.issue?.pull_request === void 0) return { status: 200, body: { ok: true, ignored: "issue" } };
71679
73082
  const repo = p.repository?.full_name;
@@ -71716,14 +73119,14 @@ async function handlePushIntake(headers, rawBody, deps) {
71716
73119
  if (headers.signature === void 0 || !await verifyWebhookSignature(deps.secret, rawBody, headers.signature))
71717
73120
  return { status: 401, body: { error: "unauthorized" } };
71718
73121
  if (headers.event !== "push") return { status: 200, body: { ok: true, ignored: "event" } };
71719
- let payload;
73122
+ let payload2;
71720
73123
  try {
71721
- payload = JSON.parse(rawBody);
73124
+ payload2 = JSON.parse(rawBody);
71722
73125
  } catch {
71723
73126
  return { status: 400, body: { error: "invalid_json" } };
71724
73127
  }
71725
- if (typeof payload !== "object" || payload === null) return { status: 400, body: { error: "invalid_body" } };
71726
- const p = payload;
73128
+ if (typeof payload2 !== "object" || payload2 === null) return { status: 400, body: { error: "invalid_body" } };
73129
+ const p = payload2;
71727
73130
  const repo = p.repository?.full_name;
71728
73131
  if (typeof p.ref !== "string" || typeof repo !== "string" || repo === "")
71729
73132
  return { status: 400, body: { error: "invalid_body" } };
@@ -72419,6 +73822,14 @@ async function runBot() {
72419
73822
  ...costsCfg?.prices ? { prices: costsCfg.prices } : {}
72420
73823
  }) : null;
72421
73824
  const coordinatorInstances = buildCoordinatorInstanceStore(runHistoryCfg, processSecrets);
73825
+ const decisionRecordAllocator = new DecisionRecordAllocator(
73826
+ fetchDecisionRecordClaims,
73827
+ async (repo, taskKey, claimed2, existing) => {
73828
+ const reserved = await coordinatorInstances.reserveDecisionRecord(repo, taskKey, claimed2, existing);
73829
+ if (!reserved.ok) throw new DecisionRecordReservationUnavailableError();
73830
+ return reserved.number;
73831
+ }
73832
+ );
72422
73833
  let planeAdmitPass;
72423
73834
  const planeEffectExecutor = {
72424
73835
  draining: () => draining,
@@ -72520,6 +73931,7 @@ async function runBot() {
72520
73931
  runHistoryWriter,
72521
73932
  // The coordinator's instance records and unit rows (run-history items 49 and 50): what the ship branch writes when it hands an `agent:ship` request to the plan runner.
72522
73933
  coordinatorInstances,
73934
+ reserveDecisionRecord: decisionRecordAllocator.reserve.bind(decisionRecordAllocator),
72523
73935
  workflow: workflowSender,
72524
73936
  runStore,
72525
73937
  threadsElsewhere,
@@ -72681,7 +74093,9 @@ async function runBot() {
72681
74093
  const out = await runLedger.recordIntake(key, receipt);
72682
74094
  if (out === void 0) throw new Error("the receipt write degraded (the ledger warning names why)");
72683
74095
  return out;
72684
- }
74096
+ },
74097
+ claimIntakeDelivery: (key, poster, claimedAt) => ledgerClient.claimIntakeDelivery(key, poster, claimedAt),
74098
+ finishIntakeDelivery: (key, poster, delivered) => ledgerClient.finishIntakeDelivery(key, poster, delivered)
72685
74099
  } : null;
72686
74100
  slackIntake = wireIntakeGate({
72687
74101
  intakeModeFor: (threadKey, userId, channelId) => config.intakeModeFor(threadKey, userId, channelId),
@@ -72792,7 +74206,12 @@ async function runBot() {
72792
74206
  // and a park land on the write-through's plane routes, fire and forget —
72793
74207
  // the null write-through swallows both where no ledger is configured.
72794
74208
  plane: {
72795
- level: (provider, side) => void runLedger.planeLevel({ provider, name: "provider", side }),
74209
+ level: (provider, side, cause) => void runLedger.planeLevel({
74210
+ provider,
74211
+ name: "provider",
74212
+ side,
74213
+ ...side === "down" && cause !== void 0 ? { cause } : {}
74214
+ }),
72796
74215
  park: (runId3, provider) => void runLedger.planePark(runId3, provider)
72797
74216
  }
72798
74217
  });
@@ -72890,6 +74309,34 @@ async function runBot() {
72890
74309
  registry: defaultRunRegistry,
72891
74310
  ledgerRuns: () => runLedger.liveRuns(),
72892
74311
  dispatch: (msg, io, opts) => dispatch(deps, msg, io, opts),
74312
+ steerChild: async (runId3, text, instance) => {
74313
+ const run2 = defaultRunRegistry.getById(runId3);
74314
+ if (!run2 || run2.finished || run2.threadKey === void 0 || run2.agent === void 0) return false;
74315
+ const outcome = await steerRun2(
74316
+ {
74317
+ // The coordinator already owns this admitted child; revoking its
74318
+ // stale PR authority is not a new request to run the agent.
74319
+ config: { canRunAgent: () => true, grantsFor: (id) => config.grantsFor(id) },
74320
+ runLedger,
74321
+ admission: defaultAdmission
74322
+ },
74323
+ {
74324
+ userId: instance.userId,
74325
+ channelId: instance.channelId,
74326
+ ...instance.userName !== void 0 ? { userName: instance.userName } : {}
74327
+ },
74328
+ {
74329
+ runId: runId3,
74330
+ threadKey: run2.threadKey,
74331
+ agent: run2.agent,
74332
+ ...run2.userId !== void 0 ? { requesterId: run2.userId } : {},
74333
+ ...run2.parentRunId !== void 0 ? { parentRunId: run2.parentRunId } : {},
74334
+ ...run2.parentInstanceId !== void 0 ? { parentInstanceId: run2.parentInstanceId } : {}
74335
+ },
74336
+ text
74337
+ );
74338
+ return outcome.kind === "steered";
74339
+ },
72893
74340
  ioFor: (thread) => threadIoFor(thread),
72894
74341
  findOpenPrByHead,
72895
74342
  findMergedPrByHead,
@@ -73542,6 +74989,7 @@ var init_index = __esm({
73542
74989
  init_authorBinding();
73543
74990
  init_identityRewrite();
73544
74991
  init_run2();
74992
+ init_decisionRecordReservation();
73545
74993
  init_record();
73546
74994
  init_admission();
73547
74995
  init_scheduleStore();
@@ -73785,6 +75233,9 @@ var ConsoleIO = class _ConsoleIO {
73785
75233
  * before that, and forever when no run was started (a config reply such as
73786
75234
  * `help`, a refusal before a run existed). */
73787
75235
  finished;
75236
+ /** A typed request failure that ended before a run existed, such as the
75237
+ * operator model refusing the one-shot ask. */
75238
+ failed = false;
73788
75239
  /** How many child threads this one has opened; the next child's number. */
73789
75240
  children = 0;
73790
75241
  async reply(text) {
@@ -73802,6 +75253,9 @@ var ConsoleIO = class _ConsoleIO {
73802
75253
  runFinished(receipt) {
73803
75254
  this.finished = receipt;
73804
75255
  }
75256
+ requestFailed() {
75257
+ this.failed = true;
75258
+ }
73805
75259
  async status(initial) {
73806
75260
  console.error(this.prefix + initial.title);
73807
75261
  return {
@@ -73827,8 +75281,8 @@ var ConsoleIO = class _ConsoleIO {
73827
75281
  };
73828
75282
  }
73829
75283
  };
73830
- function askExitCode(finished) {
73831
- return finished === void 0 || finished.status === "completed" ? 0 : 1;
75284
+ function askExitCode(finished, requestFailed = false) {
75285
+ return requestFailed || finished !== void 0 && finished.status !== "completed" ? 1 : 0;
73832
75286
  }
73833
75287
  function missingBotConfig(configPath) {
73834
75288
  return new CommandError(
@@ -74001,7 +75455,7 @@ async function main() {
74001
75455
  );
74002
75456
  await runHistoryWriter.settled();
74003
75457
  loopback.close();
74004
- process.exitCode = askExitCode(io.finished);
75458
+ process.exitCode = askExitCode(io.finished, io.failed);
74005
75459
  }
74006
75460
  async function askLoopbackServer(input) {
74007
75461
  const modelProxy = createModelProxyHandler({