@coreplane/switchboard 1.243.0 → 1.244.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.
- package/dist/assets/deploy/cloudflare-memory/worker.ts +148 -0
- package/dist/assets/deploy/cloudflare-resident/gc.ts +8 -1
- package/dist/assets/deploy/cloudflare-resident/worker.ts +433 -117
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +13 -2
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +1 -1
- package/dist/assets/src/core/authz/types.ts +10 -0
- package/dist/assets/src/core/budgets.ts +36 -0
- package/dist/assets/src/core/coordinator/contract.ts +32 -0
- package/dist/assets/src/core/coordinator/driver.ts +75 -12
- package/dist/assets/src/core/delivery.ts +1 -1
- package/dist/assets/src/core/runEvents.ts +50 -7
- package/dist/assets/src/core/runFriction.ts +2 -1
- package/dist/assets/src/core/runRecord.ts +31 -0
- package/dist/assets/src/core/ship/contract.ts +2 -1
- package/dist/assets/src/core/ship/coordinator.ts +211 -13
- package/dist/assets/src/core/ship/renewal.ts +155 -0
- package/dist/assets/src/core/trace/attrs.ts +11 -0
- package/dist/assets/src/core/trace/types.ts +4 -3
- package/dist/assets/src/execution/residentRefresh.ts +24 -4
- package/dist/assets/src/execution/residentText.ts +8 -3
- package/dist/assets/src/mcp/registry.ts +3 -0
- package/dist/assets/web/dist/.vite/manifest.json +417 -417
- package/dist/assets/web/dist/assets/{AppShell-IDMGG6yi.js → AppShell-Ck4hSZDi.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-DvhjDMka.js +2 -0
- package/dist/assets/web/dist/assets/DeliveryPage-DBlI_Yq2.js +1 -0
- package/dist/assets/web/dist/assets/HomePage-XFWPxDiJ.js +2 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-C97EhJcQ.js → NotFoundPage-CiGrVOyv.js} +1 -1
- package/dist/assets/web/dist/assets/{PendingTurnRow-BT9RhFZ7.js → PendingTurnRow-BHCSpsPf.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentDetailPage-DACalNVF.js → ResidentDetailPage-C5USV_8P.js} +1 -1
- package/dist/assets/web/dist/assets/{ResidentsIndexPage-SHPdu6uZ.js → ResidentsIndexPage-CSKisWnX.js} +1 -1
- package/dist/assets/web/dist/assets/RunFoldRow-BPMQxTcl.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-CZe9Yodw.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-BfA333Qy.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-BUfdk0lH.js → RunsTabs-BWRHzhtT.js} +1 -1
- package/dist/assets/web/dist/assets/{ScheduledPage-DJ8HiCPt.js → ScheduledPage-DBaChcJZ.js} +1 -1
- package/dist/assets/web/dist/assets/SettingsPage-CqVi9-Ac.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-Dw0T1M-P.js → StatusDot-_Q_bmReh.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-BbLuIAiS.js → Tooltip-Cafsgd4m.js} +1 -1
- package/dist/assets/web/dist/assets/{UnitRoutePage-DicUG96U.js → UnitRoutePage-CdoBZwtf.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-html-oBNfPJR0.js → angular-html-Dail07rr.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-BvNwsyWA.js → angular-ts-BReExio8.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CNUdRlYf.js → apl-DhNO6Yyx.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-Zb0NriSe.js → astro-BYotTY4C.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-qPRVheqq.js → blade-mUxTtD1X.js} +1 -1
- package/dist/assets/web/dist/assets/{c-D8Awx4YO.js → c-DhuiwcSQ.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-Bt72Mhsx.js → chapel-GD2vFtRi.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-BOBacexg.js → cobol-BhFDwkes.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-E4u0liHW.js → coffee-DWUgbE0r.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-q2sLNlul.js → cpp-CO9XFjGW.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-DyWqUnlb.js → crystal-B9pVurpn.js} +1 -1
- package/dist/assets/web/dist/assets/{css-CQY0hFsD.js → css-CFVVV6lI.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-twkFmSUY.js → dist-BtPs2AkQ.js} +2 -2
- package/dist/assets/web/dist/assets/{edge-C1MwhJkX.js → edge-BDhIDQIb.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-Bb3YbHfn.js → elixir-DG3J8WoD.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-DAN9IGQw.js → elm-FiOdiaOE.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-BEB8Xlsj.js → erb-vxbtW503.js} +1 -1
- package/dist/assets/web/dist/assets/{git-rebase-tqpjRfxO.js → git-rebase-DINz8M3H.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-Ccbo65zR.js → glimmer-js-yann6r9i.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-B6WBVMpE.js → glimmer-ts-CXTbrZn_.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-tRec3Fcu.js → glsl-DNYC-hq0.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-P8kbxT4F.js → graphql-CwzhHzi9.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-H9Zhkagy.js → hack-BBXF1cwR.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-DtEnpn7Z.js → haml-oEqBcsTy.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-DkgPfoAz.js → handlebars-YE5thkT1.js} +1 -1
- package/dist/assets/web/dist/assets/{html-D30RXpIs.js → html-Dp4jRQ1j.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-DwozLrEx.js → html-derivative-DCT1GYtQ.js} +1 -1
- package/dist/assets/web/dist/assets/{http-DXuzBAPm.js → http-BPcGbmzJ.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-d1UUJIt_.js → hurl-CQnNHwyd.js} +1 -1
- package/dist/assets/web/dist/assets/{indexRow-B_s5tKyq.js → indexRow-BmK74Vp1.js} +1 -1
- package/dist/assets/web/dist/assets/{java-DL0gWf34.js → java-Dm8Ovj6W.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-Cl7vavnS.js → javascript-Btm9oJK9.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-CfQOWMX9.js → jinja-VZYjWF_U.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-KdYirlqm.js → jison-Dcmjxa9x.js} +1 -1
- package/dist/assets/web/dist/assets/{json-C9cDQ-Qj.js → json-DoWiOGZ8.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-CRx5NItd.js → jsx-Dvmo55ma.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-CmsQsZQl.js → julia-d8AtfV26.js} +1 -1
- package/dist/assets/web/dist/assets/{just-CsM3Q8TE.js → just-Cc5EStA-.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BXCh5YRX.js → latex-B5fKKIH7.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-BF2vwK8p.js → liquid-DUYI9JND.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-DcMBATrl.js → lua-BhMwKWpw.js} +1 -1
- package/dist/assets/web/dist/assets/main-C4366PZF.css +1 -0
- package/dist/assets/web/dist/assets/main-DlTNAC6c.js +28 -0
- package/dist/assets/web/dist/assets/{marko-88MndKvG.js → marko-Cnf2cPAi.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-DJ4kVAd8.js → mdc-FDDXoir9.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-CP6mRgtV.js → nginx-q-8U_1jR.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-QQfj3fpF.js → nim-DIA0TUWs.js} +1 -1
- package/dist/assets/web/dist/assets/{org-Bke3eBzc.js → org-CR88hy7S.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-Bkh0N7Kz.js → perl-Bkd8N7ja.js} +1 -1
- package/dist/assets/web/dist/assets/{php-Dnv1Piya.js → php-BOs84mvy.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-D6peFFI7.js → pug-BbpZBb3w.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-D8PEs-C-.js → qml-D2jQpjFE.js} +1 -1
- package/dist/assets/web/dist/assets/{r-B1EL9b_j.js → r-DKzcAQ22.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BsMTIh2b.js → razor-SOQMWcC9.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-HhvC8spD.js → regexp-B6KK4W8d.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-D5paAxpg.js → rst-kPSg-NPf.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-BrQwhLrl.js → ruby-cKS-JYLH.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-afot2B1o.js → sas-DNxzE5gs.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-Efm-mwuG.js → scss-DGcHQ5OO.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-BK0Vv5fT.js → shellscript-CRZeZBa5.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-BGqlMC7N.js → shellsession-DSNEL0HX.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-M0b4UwGM.js → soy-zisNbkDO.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-sxe6IE9j.js → sql-MG06KMMb.js} +1 -1
- package/dist/assets/web/dist/assets/{sseReplay-C9m_EB8J.js → sseReplay-DaRtc7nI.js} +4 -4
- package/dist/assets/web/dist/assets/{stata-nPF_ddLP.js → stata-CB_wKwv9.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-D_GrC6u7.js → surrealql-DYdSt7re.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-DwL1AtNP.js → svelte-DHO-F7c7.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-7s7LTDkc.js → templ-CUL5L4Lr.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-Bx-5fMxe.js → tex-_JNEKjVW.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-DUMJnke_.js → ts-tags--9EpEV_8.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-BN8biPDe.js → tsx-BcNq2x2u.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-8nIu84TN.js → twig-EsY5Dk_D.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-BooSPq_S.js → typescript-CyA_HiUH.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-CTBiBsem.js → typst-SzaHlDt4.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-C6Ft4Lea.js → vue-D3cn5acV.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-Ba36dD5D.js → vue-html-6MxhUxOa.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-NkFexVo2.js → vue-vine-qJnqhgo9.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-C_THnHXZ.js → xml-DyRpC7xv.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D8G5xqjY.js → xsl-BXMRPAsg.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-jAMIJzge.js → yaml-BNmPCtAN.js} +1 -1
- package/dist/cli.js +2990 -706
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CwXOmkeQ.js +0 -2
- package/dist/assets/web/dist/assets/DeliveryPage-n1tz5I_v.js +0 -1
- package/dist/assets/web/dist/assets/HomePage-PRxjQiGG.js +0 -2
- package/dist/assets/web/dist/assets/RunFoldRow-0SdOmOr5.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-BaFS2p8I.js +0 -9
- package/dist/assets/web/dist/assets/RunsIndexPage-DYI-iALj.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DLiN5IgY.js +0 -1
- package/dist/assets/web/dist/assets/main-B4kEF3Sg.css +0 -1
- package/dist/assets/web/dist/assets/main-d-w-tIKt.js +0 -28
|
@@ -627,6 +627,27 @@ export class ScheduleDO extends DurableObject<Env> {
|
|
|
627
627
|
// for optimistic concurrency: a `put` whose `expectedVersion` is stale is a 409,
|
|
628
628
|
// never a silent clobber (the bot and the CLI both write this document).
|
|
629
629
|
|
|
630
|
+
/** What the bot posts to mint a confirmation: the facts this object judges on
|
|
631
|
+
* and the bot's own row as an opaque body (routing-and-config item 25). */
|
|
632
|
+
interface ConfirmationInput {
|
|
633
|
+
id: string;
|
|
634
|
+
threadKey: string;
|
|
635
|
+
requester: string;
|
|
636
|
+
body: Record<string, unknown>;
|
|
637
|
+
}
|
|
638
|
+
/** A stored confirmation as a consume returns it: the input plus the expiry this object stamped. */
|
|
639
|
+
interface ConfirmationRow extends ConfirmationInput {
|
|
640
|
+
expiresAt: number;
|
|
641
|
+
}
|
|
642
|
+
/** Why a consume or a cancel refused: the row is gone (`used`), past its expiry (`expired`), or someone else's (`foreign`). */
|
|
643
|
+
type ConfirmationRefusal = "used" | "expired" | "foreign";
|
|
644
|
+
type ConfirmationOutcome = { row: ConfirmationRow } | { refused: ConfirmationRefusal };
|
|
645
|
+
type ConfirmationCancelOutcome = { ok: true } | { refused: Exclude<ConfirmationRefusal, "expired"> };
|
|
646
|
+
|
|
647
|
+
function isJsonObject(v: unknown): v is Record<string, unknown> {
|
|
648
|
+
return typeof v === "object" && v !== null && !Array.isArray(v);
|
|
649
|
+
}
|
|
650
|
+
|
|
630
651
|
export class ConfigDO extends DurableObject<Env> {
|
|
631
652
|
private readonly sql: SqlStorage;
|
|
632
653
|
|
|
@@ -650,9 +671,91 @@ export class ConfigDO extends DurableObject<Env> {
|
|
|
650
671
|
expires_at INTEGER NOT NULL,
|
|
651
672
|
ticket TEXT NOT NULL
|
|
652
673
|
);
|
|
674
|
+
CREATE TABLE IF NOT EXISTS confirmations (
|
|
675
|
+
id TEXT PRIMARY KEY,
|
|
676
|
+
thread_key TEXT NOT NULL,
|
|
677
|
+
requester TEXT NOT NULL,
|
|
678
|
+
expires_at INTEGER NOT NULL,
|
|
679
|
+
body TEXT NOT NULL
|
|
680
|
+
);
|
|
653
681
|
`);
|
|
654
682
|
}
|
|
655
683
|
|
|
684
|
+
// ---- Confirmations (docs/reference/specs/routing-and-config.md item 25): the
|
|
685
|
+
// row a routed write is offered as, minted by the bot and consumed once at
|
|
686
|
+
// the click. The body is the bot's row, opaque here; this object owns the
|
|
687
|
+
// three facts the consume decides on — the thread (one pending row per
|
|
688
|
+
// thread), the requester (the click must be theirs) and the expiry, stamped
|
|
689
|
+
// and judged on this object's clock so two bot processes alive during a roll
|
|
690
|
+
// read one answer. Nothing sweeps: expiry is checked on touch.
|
|
691
|
+
|
|
692
|
+
/** Insert the row and drop the thread's older one in one transaction; the
|
|
693
|
+
* expiry is `now + ttlMs`, stamped here and answered to the caller. */
|
|
694
|
+
async putConfirmation(row: ConfirmationInput, ttlMs: number, now: number): Promise<number> {
|
|
695
|
+
const expiresAt = now + ttlMs;
|
|
696
|
+
this.ctx.storage.transactionSync(() => {
|
|
697
|
+
this.sql.exec(`DELETE FROM confirmations WHERE thread_key = ?`, row.threadKey);
|
|
698
|
+
this.sql.exec(
|
|
699
|
+
`INSERT OR REPLACE INTO confirmations (id, thread_key, requester, expires_at, body) VALUES (?, ?, ?, ?, ?)`,
|
|
700
|
+
row.id,
|
|
701
|
+
row.threadKey,
|
|
702
|
+
row.requester,
|
|
703
|
+
expiresAt,
|
|
704
|
+
JSON.stringify(row.body),
|
|
705
|
+
);
|
|
706
|
+
});
|
|
707
|
+
return expiresAt;
|
|
708
|
+
}
|
|
709
|
+
|
|
710
|
+
/** Read, judge and delete in one transaction on the single-threaded object,
|
|
711
|
+
* so a confirmation runs at most once whatever the click timing: a missing
|
|
712
|
+
* row is `used`, a row past its expiry is deleted and `expired`, a requester
|
|
713
|
+
* none of `actorIds` names is `foreign` (the row stays for its requester),
|
|
714
|
+
* and otherwise the row is deleted and returned. */
|
|
715
|
+
async consumeConfirmation(id: string, actorIds: readonly string[], now: number): Promise<ConfirmationOutcome> {
|
|
716
|
+
return this.ctx.storage.transactionSync(() => {
|
|
717
|
+
const stored = this.readConfirmation(id);
|
|
718
|
+
if (!stored) return { refused: "used" };
|
|
719
|
+
if (stored.expiresAt <= now) {
|
|
720
|
+
this.sql.exec(`DELETE FROM confirmations WHERE id = ?`, id);
|
|
721
|
+
return { refused: "expired" };
|
|
722
|
+
}
|
|
723
|
+
if (!actorIds.includes(stored.requester)) return { refused: "foreign" };
|
|
724
|
+
this.sql.exec(`DELETE FROM confirmations WHERE id = ?`, id);
|
|
725
|
+
return { row: stored };
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/** Delete the row under the same requester check as a consume; a missing row
|
|
730
|
+
* is `used`. Expiry plays no part: cancelling an expired offer still leaves
|
|
731
|
+
* nothing pending, which is what the click asked for. */
|
|
732
|
+
async cancelConfirmation(id: string, actorIds: readonly string[]): Promise<ConfirmationCancelOutcome> {
|
|
733
|
+
return this.ctx.storage.transactionSync(() => {
|
|
734
|
+
const stored = this.readConfirmation(id);
|
|
735
|
+
if (!stored) return { refused: "used" };
|
|
736
|
+
if (!actorIds.includes(stored.requester)) return { refused: "foreign" };
|
|
737
|
+
this.sql.exec(`DELETE FROM confirmations WHERE id = ?`, id);
|
|
738
|
+
return { ok: true };
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
private readConfirmation(id: string): ConfirmationRow | undefined {
|
|
743
|
+
const row = this.sql
|
|
744
|
+
.exec<{ thread_key: string; requester: string; expires_at: number; body: string }>(
|
|
745
|
+
`SELECT thread_key, requester, expires_at, body FROM confirmations WHERE id = ?`,
|
|
746
|
+
id,
|
|
747
|
+
)
|
|
748
|
+
.toArray()[0];
|
|
749
|
+
if (!row) return undefined;
|
|
750
|
+
return {
|
|
751
|
+
id,
|
|
752
|
+
threadKey: row.thread_key,
|
|
753
|
+
requester: row.requester,
|
|
754
|
+
expiresAt: row.expires_at,
|
|
755
|
+
body: parseStored(row.body, isJsonObject) ?? {},
|
|
756
|
+
};
|
|
757
|
+
}
|
|
758
|
+
|
|
656
759
|
// ---- MCP sealed credentials + connect tickets (docs/reference/specs/mcp-tools.md items 15–16).
|
|
657
760
|
// Ciphertext the bot sealed — opaque here — and one-time tickets: the two
|
|
658
761
|
// things a config document must never carry, kept beside it on this object.
|
|
@@ -1052,8 +1155,21 @@ const CONFIG_ROUTES = new Set([
|
|
|
1052
1155
|
"/config/tickets/put",
|
|
1053
1156
|
"/config/tickets/get",
|
|
1054
1157
|
"/config/tickets/transition",
|
|
1158
|
+
"/config/confirmations/put",
|
|
1159
|
+
"/config/confirmations/consume",
|
|
1160
|
+
"/config/confirmations/cancel",
|
|
1055
1161
|
]);
|
|
1056
1162
|
const TICKET_STATES: ReadonlySet<string> = new Set<McpTicketState>(MCP_TICKET_STATES);
|
|
1163
|
+
/** A confirmation id as the bot mints it (a UUID) — one token, no whitespace, bounded. */
|
|
1164
|
+
const CONFIRMATION_ID_RE = /^[A-Za-z0-9_-]{8,128}$/;
|
|
1165
|
+
|
|
1166
|
+
/** The `{ id, actorIds }` a consume or a cancel carries, or the 400 that refuses it. */
|
|
1167
|
+
function confirmationClickOf(b: Record<string, unknown>): { id: string; actorIds: string[] } | Response {
|
|
1168
|
+
if (typeof b.id !== "string" || !CONFIRMATION_ID_RE.test(b.id)) return json({ error: "id malformed" }, 400);
|
|
1169
|
+
if (!Array.isArray(b.actorIds) || !b.actorIds.every((a): a is string => typeof a === "string"))
|
|
1170
|
+
return json({ error: "actorIds must be a list of actor ids" }, 400);
|
|
1171
|
+
return { id: b.id, actorIds: b.actorIds };
|
|
1172
|
+
}
|
|
1057
1173
|
|
|
1058
1174
|
async function handleConfig(pathname: string, body: unknown, env: Env): Promise<Response> {
|
|
1059
1175
|
const b = (typeof body === "object" && body !== null ? body : {}) as Record<string, unknown>;
|
|
@@ -1095,6 +1211,38 @@ async function handleConfig(pathname: string, body: unknown, env: Env): Promise<
|
|
|
1095
1211
|
);
|
|
1096
1212
|
return json({ ok: true, applied });
|
|
1097
1213
|
}
|
|
1214
|
+
// Confirmations (routing-and-config item 25): the body is the bot's row,
|
|
1215
|
+
// stored verbatim; the expiry is this object's clock plus the ttl the bot
|
|
1216
|
+
// passed, never a timestamp the bot chose.
|
|
1217
|
+
case "/config/confirmations/put": {
|
|
1218
|
+
if (typeof b.id !== "string" || !CONFIRMATION_ID_RE.test(b.id)) return json({ error: "id malformed" }, 400);
|
|
1219
|
+
if (typeof b.threadKey !== "string" || !b.threadKey) return json({ error: "threadKey required" }, 400);
|
|
1220
|
+
if (typeof b.requester !== "string" || !b.requester) return json({ error: "requester required" }, 400);
|
|
1221
|
+
if (!isJsonObject(b.body)) return json({ error: "body must be a JSON object" }, 400);
|
|
1222
|
+
if (typeof b.ttlMs !== "number" || !Number.isInteger(b.ttlMs) || b.ttlMs < 0)
|
|
1223
|
+
return json({ error: "ttlMs must be a non-negative integer" }, 400);
|
|
1224
|
+
const expiresAt = await dO.putConfirmation(
|
|
1225
|
+
{ id: b.id, threadKey: b.threadKey, requester: b.requester, body: b.body },
|
|
1226
|
+
b.ttlMs,
|
|
1227
|
+
systemClock(),
|
|
1228
|
+
);
|
|
1229
|
+
console.log(`[config/confirmations/put] ${b.threadKey} ${b.id} expires_at=${expiresAt}`);
|
|
1230
|
+
return json({ ok: true, expiresAt });
|
|
1231
|
+
}
|
|
1232
|
+
case "/config/confirmations/consume": {
|
|
1233
|
+
const click = confirmationClickOf(b);
|
|
1234
|
+
if (click instanceof Response) return click;
|
|
1235
|
+
const outcome = await dO.consumeConfirmation(click.id, click.actorIds, systemClock());
|
|
1236
|
+
console.log(`[config/confirmations/consume] ${click.id} ${"row" in outcome ? "consumed" : outcome.refused}`);
|
|
1237
|
+
return json(outcome);
|
|
1238
|
+
}
|
|
1239
|
+
case "/config/confirmations/cancel": {
|
|
1240
|
+
const click = confirmationClickOf(b);
|
|
1241
|
+
if (click instanceof Response) return click;
|
|
1242
|
+
const outcome = await dO.cancelConfirmation(click.id, click.actorIds);
|
|
1243
|
+
console.log(`[config/confirmations/cancel] ${click.id} ${"ok" in outcome ? "cancelled" : outcome.refused}`);
|
|
1244
|
+
return json(outcome);
|
|
1245
|
+
}
|
|
1098
1246
|
default:
|
|
1099
1247
|
break;
|
|
1100
1248
|
}
|
|
@@ -79,6 +79,10 @@ export interface ReclaimInput {
|
|
|
79
79
|
isDefaultRef: boolean;
|
|
80
80
|
/** Thread exec/read/write currently running on this binding. */
|
|
81
81
|
busy: number;
|
|
82
|
+
/** A run registered on the thread — attached and not yet released (item
|
|
83
|
+
* 44): its process lives in the container between the bot's calls, so
|
|
84
|
+
* `busy` reads 0 while its model thinks. Held is kept, whatever the fate. */
|
|
85
|
+
held: boolean;
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
/** The PR that decided the fate (for the audit trail): the open one, else the
|
|
@@ -93,11 +97,13 @@ export type ReclaimWhy =
|
|
|
93
97
|
| "no-pr"
|
|
94
98
|
| "fate-unknown"
|
|
95
99
|
| "busy"
|
|
100
|
+
| "run-held"
|
|
96
101
|
| "re-attached"
|
|
97
102
|
| Extract<RefFate, "gone" | "merged" | "closed">;
|
|
98
103
|
|
|
99
104
|
/** Evict this binding now? A finished ref (gone/merged/closed) is reclaimed
|
|
100
|
-
* when nothing runs on it —
|
|
105
|
+
* when nothing runs on it — no op in flight and no run registered from its
|
|
106
|
+
* attach to its release — and nothing else is asked. What its tree holds
|
|
101
107
|
* is never a reason to keep it: a run starts from a clean tree, so dirt in a
|
|
102
108
|
* tree no run is using protects nothing (the next attach wipes it); the
|
|
103
109
|
* Worker measures and records what it removes instead. Keeps are named so
|
|
@@ -113,6 +119,7 @@ export function reclaimDecision(input: ReclaimInput): { reclaim: boolean; why: R
|
|
|
113
119
|
return { reclaim: false, why: "fate-unknown" };
|
|
114
120
|
}
|
|
115
121
|
if (input.busy > 0) return { reclaim: false, why: "busy" };
|
|
122
|
+
if (input.held) return { reclaim: false, why: "run-held" };
|
|
116
123
|
return { reclaim: true, why: input.fate };
|
|
117
124
|
}
|
|
118
125
|
|