@coreplane/switchboard 1.239.0 → 1.241.0

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 (156) hide show
  1. package/dist/assets/config/config.example.yaml +32 -7
  2. package/dist/assets/deploy/cloudflare/worker.ts +77 -18
  3. package/dist/assets/deploy/cloudflare-memory/worker.ts +115 -1
  4. package/dist/assets/deploy/cloudflare-memory/wrangler.template.jsonc +7 -1
  5. package/dist/assets/deploy/cloudflare-resident/worker.ts +224 -70
  6. package/dist/assets/deploy/secrets.manifest.json +7 -1
  7. package/dist/assets/package-lock.json +207 -3
  8. package/dist/assets/package.json +2 -1
  9. package/dist/assets/source.json +3 -3
  10. package/dist/assets/src/agents/registry.ts +27 -8
  11. package/dist/assets/src/core/authz/actor.ts +38 -2
  12. package/dist/assets/src/core/authz/grants.ts +20 -9
  13. package/dist/assets/src/core/authz/policy.ts +6 -0
  14. package/dist/assets/src/core/coordinator/contract.ts +49 -1
  15. package/dist/assets/src/core/coordinator/driver.ts +30 -1
  16. package/dist/assets/src/core/coordinator/instancesRoute.ts +72 -0
  17. package/dist/assets/src/core/costs.ts +1119 -0
  18. package/dist/assets/src/core/costsSnapshotStore.ts +209 -0
  19. package/dist/assets/src/core/delivery.ts +4 -11
  20. package/dist/assets/src/core/deliverySnapshotStore.ts +12 -28
  21. package/dist/assets/src/core/ingressTokens.ts +27 -13
  22. package/dist/assets/src/core/runLedger/types.ts +3 -0
  23. package/dist/assets/src/core/runRecord.ts +10 -3
  24. package/dist/assets/src/core/schedules.ts +2 -1
  25. package/dist/assets/src/core/ship/coordinator.ts +92 -7
  26. package/dist/assets/src/core/snapshotAge.ts +30 -0
  27. package/dist/assets/src/core/stateWorkerRef.ts +41 -0
  28. package/dist/assets/src/core/trace/attrs.ts +1 -1
  29. package/dist/assets/src/execution/bindingPurge.ts +2 -2
  30. package/dist/assets/src/execution/residentAutoRebuild.ts +73 -0
  31. package/dist/assets/src/execution/residentRefresh.ts +25 -3
  32. package/dist/assets/src/execution/restoreWaiters.ts +61 -0
  33. package/dist/assets/src/mcp/registry.ts +7 -0
  34. package/dist/assets/web/dist/.vite/manifest.json +509 -427
  35. package/dist/assets/web/dist/assets/AppShell-DEy5jRuS.js +1 -0
  36. package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +2 -0
  37. package/dist/assets/web/dist/assets/DeliveryPage-BGpPr5xr.js +1 -0
  38. package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +2 -0
  39. package/dist/assets/web/dist/assets/NotFoundPage-DtmQM2gD.js +1 -0
  40. package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +1 -0
  41. package/dist/assets/web/dist/assets/{ResidentDetailPage-CrfCd8fx.js → ResidentDetailPage-C-48fxat.js} +1 -1
  42. package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +1 -0
  43. package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +1 -0
  44. package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +6 -0
  45. package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +1 -0
  46. package/dist/assets/web/dist/assets/{RunsTabs-XpCqeWu4.js → RunsTabs-CtNGNnhN.js} +1 -1
  47. package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +1 -0
  48. package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +1 -0
  49. package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +1 -0
  50. package/dist/assets/web/dist/assets/{StatusDot-DkXALvKf.js → StatusDot-DPWE1JmO.js} +1 -1
  51. package/dist/assets/web/dist/assets/{Tooltip-DtLIyi5P.js → Tooltip-DKhSRH9t.js} +1 -1
  52. package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +1 -0
  53. package/dist/assets/web/dist/assets/{angular-html-DYtV9uIN.js → angular-html-DgSK1qvr.js} +1 -1
  54. package/dist/assets/web/dist/assets/{angular-ts-DF2P25Zp.js → angular-ts-D3gNdiSG.js} +1 -1
  55. package/dist/assets/web/dist/assets/{apl-Ff96wxko.js → apl-CkHCYM8I.js} +1 -1
  56. package/dist/assets/web/dist/assets/{astro-LbBnZna-.js → astro-DLm45axt.js} +1 -1
  57. package/dist/assets/web/dist/assets/{blade-DltuxeBW.js → blade-BKa-VE-c.js} +1 -1
  58. package/dist/assets/web/dist/assets/{c-DBg7yuPx.js → c-C8NCNWai.js} +1 -1
  59. package/dist/assets/web/dist/assets/{chapel-C83hUAds.js → chapel-DqDQ7IKH.js} +1 -1
  60. package/dist/assets/web/dist/assets/{cobol-DB0yV_2D.js → cobol-j4vCD6AJ.js} +1 -1
  61. package/dist/assets/web/dist/assets/{coffee-Bpih6Kzl.js → coffee-C6oDVH-0.js} +1 -1
  62. package/dist/assets/web/dist/assets/{cpp-CU00ZeRn.js → cpp-Bd3A1Rtc.js} +1 -1
  63. package/dist/assets/web/dist/assets/{crystal-f8ImJkux.js → crystal-CUOgOw0d.js} +1 -1
  64. package/dist/assets/web/dist/assets/{css-4VrC4ISo.js → css-RYljyv7G.js} +1 -1
  65. package/dist/assets/web/dist/assets/delivery-CTdOqpVK.js +1 -0
  66. package/dist/assets/web/dist/assets/{dist-DtA7JiH5.js → dist-BnJXKHCY.js} +2 -2
  67. package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +1 -0
  68. package/dist/assets/web/dist/assets/{edge-BwFDzfS_.js → edge-BJhNdQDc.js} +1 -1
  69. package/dist/assets/web/dist/assets/{elixir-wwIYIpJm.js → elixir-g1AWOAfT.js} +1 -1
  70. package/dist/assets/web/dist/assets/{elm-DyDuM3v3.js → elm-CER5e4Dz.js} +1 -1
  71. package/dist/assets/web/dist/assets/{erb-DFJBEVrh.js → erb-D5aOmMQf.js} +1 -1
  72. package/dist/assets/web/dist/assets/eventSource-tRprhqm7.js +1 -0
  73. package/dist/assets/web/dist/assets/favicon-D1hkyC5j.js +1 -0
  74. package/dist/assets/web/dist/assets/{git-rebase-CuDv-44X.js → git-rebase-D6rfV8jp.js} +1 -1
  75. package/dist/assets/web/dist/assets/{glimmer-js-ZFnrm4J3.js → glimmer-js-BvDH3-mC.js} +1 -1
  76. package/dist/assets/web/dist/assets/{glimmer-ts-XY4Yqshp.js → glimmer-ts-DChtNs9V.js} +1 -1
  77. package/dist/assets/web/dist/assets/{glsl-xnR8efx0.js → glsl-BHlsWrxf.js} +1 -1
  78. package/dist/assets/web/dist/assets/{graphql-D-gO5tnP.js → graphql-BsaBHb53.js} +1 -1
  79. package/dist/assets/web/dist/assets/{hack-CSxbW4ax.js → hack-0lbDosCd.js} +1 -1
  80. package/dist/assets/web/dist/assets/{haml-D4ar42QT.js → haml-BKtco2vr.js} +1 -1
  81. package/dist/assets/web/dist/assets/{handlebars-CMLbDE7o.js → handlebars-BE_cj04z.js} +1 -1
  82. package/dist/assets/web/dist/assets/{html-CVekU4Fs.js → html-BNcr8EVd.js} +1 -1
  83. package/dist/assets/web/dist/assets/{html-derivative-St3HHw_N.js → html-derivative-BsJt2Kei.js} +1 -1
  84. package/dist/assets/web/dist/assets/{http-Mon0DWHS.js → http-BR5P8ER_.js} +1 -1
  85. package/dist/assets/web/dist/assets/{hurl-Cbl5IvBs.js → hurl-CtHalpWS.js} +1 -1
  86. package/dist/assets/web/dist/assets/{localIso-CNA-bhS-.js → indexFormat-B-pd4r7Y.js} +1 -1
  87. package/dist/assets/web/dist/assets/{indexRow-FLJu5mR9.js → indexRow-BD1VT8o8.js} +1 -1
  88. package/dist/assets/web/dist/assets/{java-Bprck1l-.js → java-BXVdhN11.js} +1 -1
  89. package/dist/assets/web/dist/assets/{javascript-C73G1-UR.js → javascript-CyoShgNQ.js} +1 -1
  90. package/dist/assets/web/dist/assets/{jinja-C4DuRxYJ.js → jinja-BQYZzHex.js} +1 -1
  91. package/dist/assets/web/dist/assets/{jison-B2U03GEC.js → jison-DqrUa1Eq.js} +1 -1
  92. package/dist/assets/web/dist/assets/{json-CvTZUDzx.js → json-CQiTF9Jj.js} +1 -1
  93. package/dist/assets/web/dist/assets/{jsx-DMWKCzwo.js → jsx-Y16MkCYQ.js} +1 -1
  94. package/dist/assets/web/dist/assets/{julia-Czn7bV_3.js → julia-zn16YONe.js} +1 -1
  95. package/dist/assets/web/dist/assets/{just-BBxpNfvC.js → just-DcVeN_MN.js} +1 -1
  96. package/dist/assets/web/dist/assets/{latex-Bw9yg5mr.js → latex-BNNoF-WP.js} +1 -1
  97. package/dist/assets/web/dist/assets/{liquid-BRCjaydE.js → liquid-DAi6qHzn.js} +1 -1
  98. package/dist/assets/web/dist/assets/localIso-L06jV29p.js +1 -0
  99. package/dist/assets/web/dist/assets/{lua-DUu2KIC0.js → lua-BswS0axw.js} +1 -1
  100. package/dist/assets/web/dist/assets/main-B3is4sk6.js +28 -0
  101. package/dist/assets/web/dist/assets/main-DP_zSemY.css +1 -0
  102. package/dist/assets/web/dist/assets/{marko-CZJZIlSQ.js → marko-C-4hkmQZ.js} +1 -1
  103. package/dist/assets/web/dist/assets/{mdc-BmClq6fd.js → mdc-0ZaHUkS7.js} +1 -1
  104. package/dist/assets/web/dist/assets/{nginx-Bmt3xSEm.js → nginx-DBDg5tOR.js} +1 -1
  105. package/dist/assets/web/dist/assets/{nim-Da-i0utK.js → nim-BVMz49Yw.js} +1 -1
  106. package/dist/assets/web/dist/assets/{org-BvAre2qP.js → org-1hO_K6ya.js} +1 -1
  107. package/dist/assets/web/dist/assets/{perl-v9WwFFtB.js → perl-CVRQLvVk.js} +1 -1
  108. package/dist/assets/web/dist/assets/{php-DTU4Zyze.js → php-K9nCrB9n.js} +1 -1
  109. package/dist/assets/web/dist/assets/{pug-DhTwEqTO.js → pug-CTxOcmO3.js} +1 -1
  110. package/dist/assets/web/dist/assets/{qml-BUD9AC_c.js → qml-CmCkfG5q.js} +1 -1
  111. package/dist/assets/web/dist/assets/{r-CAnu56n7.js → r-pP47Xn1X.js} +1 -1
  112. package/dist/assets/web/dist/assets/{razor-Dn0P4gAq.js → razor-BpW6r3tC.js} +1 -1
  113. package/dist/assets/web/dist/assets/{regexp-CZyBRaVW.js → regexp-CINgdY4N.js} +1 -1
  114. package/dist/assets/web/dist/assets/{rst-Ckyd5UsY.js → rst-BZIUEq2Q.js} +1 -1
  115. package/dist/assets/web/dist/assets/{ruby-BBQ62fsx.js → ruby-nzAOxz6r.js} +1 -1
  116. package/dist/assets/web/dist/assets/{sas-CW35hMxG.js → sas-QIS1bFth.js} +1 -1
  117. package/dist/assets/web/dist/assets/{scss-CpV8pF4J.js → scss-BuhBBVNt.js} +1 -1
  118. package/dist/assets/web/dist/assets/settingsApi-priOPiwu.js +1 -0
  119. package/dist/assets/web/dist/assets/{shellscript-CRJvsmE1.js → shellscript-CQc1vXbk.js} +1 -1
  120. package/dist/assets/web/dist/assets/{shellsession-CyriUZxf.js → shellsession-CoubCAUv.js} +1 -1
  121. package/dist/assets/web/dist/assets/{soy-D5QzYUeg.js → soy-CPRzlder.js} +1 -1
  122. package/dist/assets/web/dist/assets/{sql-CUBf_Mse.js → sql-W9krb8-9.js} +1 -1
  123. package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +9 -0
  124. package/dist/assets/web/dist/assets/{stata-B-4n4ITz.js → stata-CuISJEC0.js} +1 -1
  125. package/dist/assets/web/dist/assets/{surrealql-RuWZBLtr.js → surrealql-CSet7584.js} +1 -1
  126. package/dist/assets/web/dist/assets/{svelte-CO5YzCsK.js → svelte-ChXTSYwK.js} +1 -1
  127. package/dist/assets/web/dist/assets/{templ-DbkWVaBF.js → templ-Bm55v62k.js} +1 -1
  128. package/dist/assets/web/dist/assets/{tex-IuxoEcId.js → tex-CICMX8Gj.js} +1 -1
  129. package/dist/assets/web/dist/assets/{ts-tags-g6dGQPjd.js → ts-tags-CA1UzWyB.js} +1 -1
  130. package/dist/assets/web/dist/assets/{tsx-WnXwiyH2.js → tsx-Dy04HNbv.js} +1 -1
  131. package/dist/assets/web/dist/assets/{twig-Lx1G7PnY.js → twig-BBHsVnVD.js} +1 -1
  132. package/dist/assets/web/dist/assets/{typescript-BQd7biLF.js → typescript-Dvc-wVBT.js} +1 -1
  133. package/dist/assets/web/dist/assets/{typst-D2lXPHK7.js → typst-Dvlqx3_q.js} +1 -1
  134. package/dist/assets/web/dist/assets/{vue-html-DZPaRte4.js → vue-html-D4YxT3An.js} +1 -1
  135. package/dist/assets/web/dist/assets/{vue-vine-DbmZCmu-.js → vue-vine-0YE7uQlH.js} +1 -1
  136. package/dist/assets/web/dist/assets/{vue-4wVQD9y9.js → vue-zWi1MNU2.js} +1 -1
  137. package/dist/assets/web/dist/assets/{xml-DV16szyo.js → xml-gm-iksZB.js} +1 -1
  138. package/dist/assets/web/dist/assets/{xsl-eo5-1sk0.js → xsl-D_W5BcrY.js} +1 -1
  139. package/dist/assets/web/dist/assets/{yaml-BXdX8HZf.js → yaml-CHmZ21wZ.js} +1 -1
  140. package/dist/cli.js +6014 -2362
  141. package/package.json +1 -1
  142. package/dist/assets/web/dist/assets/AppShell-MYHNxJks.js +0 -1
  143. package/dist/assets/web/dist/assets/CostsPage-DKk9OjB0.js +0 -2
  144. package/dist/assets/web/dist/assets/DeliveryPage-Kz6js49i.js +0 -1
  145. package/dist/assets/web/dist/assets/NotFoundPage-BXLrQQzy.js +0 -1
  146. package/dist/assets/web/dist/assets/ResidentsIndexPage-BdW0k2h3.js +0 -1
  147. package/dist/assets/web/dist/assets/RunFoldRow-CbTd9SiS.js +0 -9
  148. package/dist/assets/web/dist/assets/RunRoutePage-br0H-8Pz.js +0 -6
  149. package/dist/assets/web/dist/assets/RunsIndexPage-Bnzb15zH.js +0 -1
  150. package/dist/assets/web/dist/assets/ScheduledPage-NLhjzHAz.js +0 -1
  151. package/dist/assets/web/dist/assets/SettingsPage-D-i0uVw-.js +0 -1
  152. package/dist/assets/web/dist/assets/UnitRoutePage-DGXukEAH.js +0 -1
  153. package/dist/assets/web/dist/assets/durationTone-CYMux25J.js +0 -1
  154. package/dist/assets/web/dist/assets/favicon-CSt-qDvc.js +0 -1
  155. package/dist/assets/web/dist/assets/main-CFHF2RvQ.css +0 -1
  156. package/dist/assets/web/dist/assets/main-Dht0in4v.js +0 -28
@@ -46,6 +46,9 @@ import {
46
46
  type ChildFacts,
47
47
  type MergeReadyFacts,
48
48
  type CoordinatorAction,
49
+ isAddressSeverity,
50
+ type AddressSeverity,
51
+ type AddressSeveritySource,
49
52
  type PlanGraph,
50
53
  type PlanUnitNode,
51
54
  type ShipCaps,
@@ -54,7 +57,7 @@ import {
54
57
  type UnitPipelineInput,
55
58
  type UnitPipelineState,
56
59
  } from "../ship/coordinator.js";
57
- import { isCoordinatorUnit, runFinishedEventType, type CoordinatorUnit } from "./contract.js";
60
+ import { checksSettledEventType, isCoordinatorUnit, runFinishedEventType, type CoordinatorUnit } from "./contract.js";
58
61
 
59
62
  const MIN = 60_000;
60
63
 
@@ -159,6 +162,9 @@ interface PlanFacts {
159
162
  planId?: string;
160
163
  /** Who merges, as the instance's field has it: the plan route answers it, `person` when absent. */
161
164
  merge: "runner" | "person";
165
+ /** The severity to address, beside `merge`: the level an approve's findings are held to, and which layer set it. */
166
+ addressSeverity: AddressSeverity;
167
+ addressSeveritySource: AddressSeveritySource;
162
168
  /** The instance's mark as the plan route answers it: a generated one-unit plan (a `plan` with no `path`). */
163
169
  generated: boolean;
164
170
  repo: string;
@@ -189,6 +195,11 @@ function readPlan(a: BotAnswer): PlanFacts {
189
195
  return {
190
196
  ...(typeof b.planId === "string" ? { planId: b.planId } : {}),
191
197
  merge: b.merge === "runner" ? "runner" : "person",
198
+ addressSeverity: isAddressSeverity(b.addressSeverity) ? b.addressSeverity : "minor",
199
+ addressSeveritySource:
200
+ b.addressSeveritySource === "run" || b.addressSeveritySource === "user" || b.addressSeveritySource === "channel"
201
+ ? b.addressSeveritySource
202
+ : "org",
192
203
  generated: b.generated === true,
193
204
  repo: b.repo,
194
205
  base: b.base,
@@ -418,6 +429,22 @@ async function perform(
418
429
  case "sleep":
419
430
  await step.sleep(action.step, action.ms);
420
431
  return { type: "sleep", step: action.step };
432
+ case "wait-checks": {
433
+ // The intake's checks-settled event at the approved head (http-ingress.md
434
+ // item 12), with the machine's bounded timeout as the fallback: either
435
+ // way the machine re-asks the merge door, which is the guard.
436
+ let outcome: "event" | "timeout";
437
+ try {
438
+ await step.waitForEvent(action.step, {
439
+ type: checksSettledEventType(action.headSha),
440
+ timeout: action.timeoutMs,
441
+ });
442
+ outcome = "event";
443
+ } catch {
444
+ outcome = "timeout";
445
+ }
446
+ return { type: "wait-checks", step: action.step, outcome };
447
+ }
421
448
  case "merge":
422
449
  return mergeReturn(
423
450
  action.step,
@@ -465,6 +492,8 @@ async function runUnit(
465
492
  // seeded plan and `person` on a task, and the door re-checks it — the
466
493
  // branch's name never decides.
467
494
  merge: plan.merge,
495
+ addressSeverity: plan.addressSeverity,
496
+ addressSeveritySource: plan.addressSeveritySource,
468
497
  generated: plan.generated,
469
498
  ...(resume !== undefined ? { resume } : {}),
470
499
  ...(lastPush !== undefined ? { lastPush } : {}),
@@ -131,6 +131,78 @@ export function parseInstanceStatusPath(pathname: string): string | undefined {
131
131
  return INSTANCE_ID_PATTERN.test(id) ? id : undefined;
132
132
  }
133
133
 
134
+ /** `POST /admin/coordinator/instances/<id>/events` — the shim's event relay
135
+ * (docs/reference/specs/http-ingress.md item 12): the bot holds no Workflow
136
+ * binding, so the check-run intake sends `checks-settled-<head>` through this
137
+ * route and the shim's own `SHIP_COORDINATOR.get(id).sendEvent` delivers it. */
138
+ export const COORDINATOR_INSTANCE_EVENTS_SUFFIX = "/events";
139
+
140
+ /** The instance id an event path names, or undefined for any other path. */
141
+ export function parseInstanceEventPath(pathname: string): string | undefined {
142
+ if (!pathname.startsWith(COORDINATOR_INSTANCE_STATUS_PREFIX)) return undefined;
143
+ if (!pathname.endsWith(COORDINATOR_INSTANCE_EVENTS_SUFFIX)) return undefined;
144
+ const id = pathname.slice(
145
+ COORDINATOR_INSTANCE_STATUS_PREFIX.length,
146
+ pathname.length - COORDINATOR_INSTANCE_EVENTS_SUFFIX.length,
147
+ );
148
+ return INSTANCE_ID_PATTERN.test(id) ? id : undefined;
149
+ }
150
+
151
+ export type ParsedSendEvent = { ok: true; type: string; payload: unknown } | { ok: false; reason: string };
152
+
153
+ /** The Workflow event alphabet — what the engine's `sendEvent` accepts as a type. */
154
+ const EVENT_TYPE_PATTERN = /^[A-Za-z0-9_-]{1,100}$/;
155
+
156
+ /** The body: `{ type, payload? }` — the typed event the instance's `waitForEvent` waits on. */
157
+ export function parseSendEventRequest(text: string): ParsedSendEvent {
158
+ let parsed: unknown;
159
+ try {
160
+ parsed = JSON.parse(text);
161
+ } catch {
162
+ return { ok: false, reason: "body is not valid JSON" };
163
+ }
164
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
165
+ return { ok: false, reason: "body must be a JSON object" };
166
+ const b = parsed as Record<string, unknown>;
167
+ if (typeof b.type !== "string" || !EVENT_TYPE_PATTERN.test(b.type))
168
+ return { ok: false, reason: "`type` must be a Workflow event type: letters, digits, `_` and `-`, at most 100" };
169
+ return { ok: true, type: b.type, payload: b.payload };
170
+ }
171
+
172
+ /** How the send ended on the shim: delivered, no such instance (the engine's
173
+ * own `instance.not_found`), or the engine refusing by reason (an instance
174
+ * that already ended is a refusal, not an absence). */
175
+ export type SendEventOutcome =
176
+ { kind: "sent"; id: string } | { kind: "absent"; id: string } | { kind: "failed"; id: string; reason: string };
177
+
178
+ export function sendEventResponse(outcome: SendEventOutcome): { status: number; body: Record<string, unknown> } {
179
+ switch (outcome.kind) {
180
+ case "sent":
181
+ return { status: 200, body: { ok: true, id: outcome.id, sent: true } };
182
+ case "absent":
183
+ return { status: 404, body: { ok: false, error: "no_instance", id: outcome.id } };
184
+ case "failed":
185
+ return { status: 502, body: { ok: false, error: "send_failed", id: outcome.id, message: outcome.reason } };
186
+ }
187
+ }
188
+
189
+ /** The send answer as the bot reads it back: sent, absent, or unanswered by reason. */
190
+ export type SendEventAnswer = { kind: "sent" } | { kind: "absent" } | { kind: "unanswered"; reason: string };
191
+
192
+ export function readSendEventAnswer(status: number, text: string): SendEventAnswer {
193
+ let parsed: unknown;
194
+ try {
195
+ parsed = JSON.parse(text);
196
+ } catch {
197
+ parsed = undefined;
198
+ }
199
+ const body = typeof parsed === "object" && parsed !== null ? (parsed as Record<string, unknown>) : undefined;
200
+ if (status === 200 && body?.ok === true && body.sent === true) return { kind: "sent" };
201
+ if (status === 404 && body?.error === "no_instance") return { kind: "absent" };
202
+ const detail = typeof body?.error === "string" ? body.error : text.slice(0, 200);
203
+ return { kind: "unanswered", reason: `HTTP ${status} — ${detail}` };
204
+ }
205
+
134
206
  /** How the status read ended on the shim: the platform's status word, no such
135
207
  * instance, or the engine failing by reason. */
136
208
  export type InstanceStatusOutcome =