@coreplane/switchboard 1.241.0 → 1.243.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/config/config.example.yaml +45 -13
- package/dist/assets/deploy/cloudflare-memory/worker.ts +86 -29
- package/dist/assets/deploy/cloudflare-resident/worker.ts +298 -24
- package/dist/assets/deploy/cloudflare-sandbox/worker.ts +38 -5
- package/dist/assets/package-lock.json +3 -3
- package/dist/assets/package.json +1 -1
- package/dist/assets/project.json +5 -5
- package/dist/assets/source.json +3 -3
- package/dist/assets/src/agents/registry.ts +61 -48
- package/dist/assets/src/config/profile.ts +68 -3
- package/dist/assets/src/core/authz/actor.ts +12 -9
- package/dist/assets/src/core/authz/authorize.ts +17 -4
- package/dist/assets/src/core/authz/policy.ts +4 -0
- package/dist/assets/src/core/authz/types.ts +12 -0
- package/dist/assets/src/core/budgets.ts +313 -0
- package/dist/assets/src/core/coordinator/contract.ts +3 -1
- package/dist/assets/src/core/coordinator/driver.ts +0 -10
- package/dist/assets/src/core/costs.ts +21 -80
- package/dist/assets/src/core/harness/scope.ts +21 -0
- package/dist/assets/src/core/modelPricing.ts +212 -0
- package/dist/assets/src/core/prDescriptionTypes.ts +29 -24
- package/dist/assets/src/core/reviewVerdict.ts +7 -0
- package/dist/assets/src/core/runEvents.ts +48 -5
- package/dist/assets/src/core/runFriction.ts +2 -1
- package/dist/assets/src/core/runLedger/types.ts +2 -0
- package/dist/assets/src/core/runRecord.ts +51 -0
- package/dist/assets/src/core/runUsage.ts +158 -47
- package/dist/assets/src/core/schedules.ts +3 -0
- package/dist/assets/src/core/ship/contract.ts +4 -2
- package/dist/assets/src/core/ship/coordinator.ts +101 -84
- package/dist/assets/src/core/ship/handoff.ts +9 -0
- package/dist/assets/src/execution/bashTimeout.ts +8 -5
- package/dist/assets/src/execution/residentAutoRebuild.ts +4 -2
- package/dist/assets/src/execution/residentHead.ts +23 -10
- package/dist/assets/src/execution/residentInfraStreak.ts +89 -0
- package/dist/assets/src/execution/residentRefresh.ts +43 -4
- package/dist/assets/src/execution/residentSteps.ts +1 -0
- package/dist/assets/src/execution/sandboxErrors.ts +6 -0
- package/dist/assets/src/execution/seedPlan.ts +17 -0
- package/dist/assets/web/dist/.vite/manifest.json +443 -446
- package/dist/assets/web/dist/assets/{AppShell-DEy5jRuS.js → AppShell-IDMGG6yi.js} +1 -1
- package/dist/assets/web/dist/assets/CostsPage-CwXOmkeQ.js +2 -0
- package/dist/assets/web/dist/assets/{DeliveryPage-BGpPr5xr.js → DeliveryPage-n1tz5I_v.js} +1 -1
- package/dist/assets/web/dist/assets/HomePage-PRxjQiGG.js +2 -0
- package/dist/assets/web/dist/assets/{NotFoundPage-DtmQM2gD.js → NotFoundPage-C97EhJcQ.js} +1 -1
- package/dist/assets/web/dist/assets/PendingTurnRow-BT9RhFZ7.js +1 -0
- package/dist/assets/web/dist/assets/{ResidentDetailPage-C-48fxat.js → ResidentDetailPage-DACalNVF.js} +1 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-SHPdu6uZ.js +1 -0
- package/dist/assets/web/dist/assets/RunFoldRow-0SdOmOr5.js +1 -0
- package/dist/assets/web/dist/assets/RunRoutePage-BaFS2p8I.js +9 -0
- package/dist/assets/web/dist/assets/RunsIndexPage-DYI-iALj.js +1 -0
- package/dist/assets/web/dist/assets/{RunsTabs-CtNGNnhN.js → RunsTabs-BUfdk0lH.js} +1 -1
- package/dist/assets/web/dist/assets/ScheduledPage-DJ8HiCPt.js +1 -0
- package/dist/assets/web/dist/assets/SettingsPage-DLiN5IgY.js +1 -0
- package/dist/assets/web/dist/assets/{StatusDot-DPWE1JmO.js → StatusDot-Dw0T1M-P.js} +1 -1
- package/dist/assets/web/dist/assets/{Tooltip-DKhSRH9t.js → Tooltip-BbLuIAiS.js} +1 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DicUG96U.js +1 -0
- package/dist/assets/web/dist/assets/{angular-html-DgSK1qvr.js → angular-html-oBNfPJR0.js} +1 -1
- package/dist/assets/web/dist/assets/{angular-ts-D3gNdiSG.js → angular-ts-BvNwsyWA.js} +1 -1
- package/dist/assets/web/dist/assets/{apl-CkHCYM8I.js → apl-CNUdRlYf.js} +1 -1
- package/dist/assets/web/dist/assets/{astro-DLm45axt.js → astro-Zb0NriSe.js} +1 -1
- package/dist/assets/web/dist/assets/{blade-BKa-VE-c.js → blade-qPRVheqq.js} +1 -1
- package/dist/assets/web/dist/assets/{c-C8NCNWai.js → c-D8Awx4YO.js} +1 -1
- package/dist/assets/web/dist/assets/{chapel-DqDQ7IKH.js → chapel-Bt72Mhsx.js} +1 -1
- package/dist/assets/web/dist/assets/{cobol-j4vCD6AJ.js → cobol-BOBacexg.js} +1 -1
- package/dist/assets/web/dist/assets/{coffee-C6oDVH-0.js → coffee-E4u0liHW.js} +1 -1
- package/dist/assets/web/dist/assets/{cpp-Bd3A1Rtc.js → cpp-q2sLNlul.js} +1 -1
- package/dist/assets/web/dist/assets/{crystal-CUOgOw0d.js → crystal-DyWqUnlb.js} +1 -1
- package/dist/assets/web/dist/assets/{css-RYljyv7G.js → css-CQY0hFsD.js} +1 -1
- package/dist/assets/web/dist/assets/{dist-BnJXKHCY.js → dist-twkFmSUY.js} +2 -2
- package/dist/assets/web/dist/assets/durationTone-BobbycC-.js +1 -0
- package/dist/assets/web/dist/assets/{edge-BJhNdQDc.js → edge-C1MwhJkX.js} +1 -1
- package/dist/assets/web/dist/assets/{elixir-g1AWOAfT.js → elixir-Bb3YbHfn.js} +1 -1
- package/dist/assets/web/dist/assets/{elm-CER5e4Dz.js → elm-DAN9IGQw.js} +1 -1
- package/dist/assets/web/dist/assets/{erb-D5aOmMQf.js → erb-BEB8Xlsj.js} +1 -1
- package/dist/assets/web/dist/assets/format-BldUwl_R.js +1 -0
- package/dist/assets/web/dist/assets/{git-rebase-D6rfV8jp.js → git-rebase-tqpjRfxO.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-js-BvDH3-mC.js → glimmer-js-Ccbo65zR.js} +1 -1
- package/dist/assets/web/dist/assets/{glimmer-ts-DChtNs9V.js → glimmer-ts-B6WBVMpE.js} +1 -1
- package/dist/assets/web/dist/assets/{glsl-BHlsWrxf.js → glsl-tRec3Fcu.js} +1 -1
- package/dist/assets/web/dist/assets/{graphql-BsaBHb53.js → graphql-P8kbxT4F.js} +1 -1
- package/dist/assets/web/dist/assets/{hack-0lbDosCd.js → hack-H9Zhkagy.js} +1 -1
- package/dist/assets/web/dist/assets/{haml-BKtco2vr.js → haml-DtEnpn7Z.js} +1 -1
- package/dist/assets/web/dist/assets/{handlebars-BE_cj04z.js → handlebars-DkgPfoAz.js} +1 -1
- package/dist/assets/web/dist/assets/{html-BNcr8EVd.js → html-D30RXpIs.js} +1 -1
- package/dist/assets/web/dist/assets/{html-derivative-BsJt2Kei.js → html-derivative-DwozLrEx.js} +1 -1
- package/dist/assets/web/dist/assets/{http-BR5P8ER_.js → http-DXuzBAPm.js} +1 -1
- package/dist/assets/web/dist/assets/{hurl-CtHalpWS.js → hurl-d1UUJIt_.js} +1 -1
- package/dist/assets/web/dist/assets/indexRow-B_s5tKyq.js +1 -0
- package/dist/assets/web/dist/assets/{java-BXVdhN11.js → java-DL0gWf34.js} +1 -1
- package/dist/assets/web/dist/assets/{javascript-CyoShgNQ.js → javascript-Cl7vavnS.js} +1 -1
- package/dist/assets/web/dist/assets/{jinja-BQYZzHex.js → jinja-CfQOWMX9.js} +1 -1
- package/dist/assets/web/dist/assets/{jison-DqrUa1Eq.js → jison-KdYirlqm.js} +1 -1
- package/dist/assets/web/dist/assets/{json-CQiTF9Jj.js → json-C9cDQ-Qj.js} +1 -1
- package/dist/assets/web/dist/assets/{jsx-Y16MkCYQ.js → jsx-CRx5NItd.js} +1 -1
- package/dist/assets/web/dist/assets/{julia-zn16YONe.js → julia-CmsQsZQl.js} +1 -1
- package/dist/assets/web/dist/assets/{just-DcVeN_MN.js → just-CsM3Q8TE.js} +1 -1
- package/dist/assets/web/dist/assets/{latex-BNNoF-WP.js → latex-BXCh5YRX.js} +1 -1
- package/dist/assets/web/dist/assets/{liquid-DAi6qHzn.js → liquid-BF2vwK8p.js} +1 -1
- package/dist/assets/web/dist/assets/{lua-BswS0axw.js → lua-DcMBATrl.js} +1 -1
- package/dist/assets/web/dist/assets/main-B4kEF3Sg.css +1 -0
- package/dist/assets/web/dist/assets/{main-B3is4sk6.js → main-d-w-tIKt.js} +2 -2
- package/dist/assets/web/dist/assets/{marko-C-4hkmQZ.js → marko-88MndKvG.js} +1 -1
- package/dist/assets/web/dist/assets/{mdc-0ZaHUkS7.js → mdc-DJ4kVAd8.js} +1 -1
- package/dist/assets/web/dist/assets/{nginx-DBDg5tOR.js → nginx-CP6mRgtV.js} +1 -1
- package/dist/assets/web/dist/assets/{nim-BVMz49Yw.js → nim-QQfj3fpF.js} +1 -1
- package/dist/assets/web/dist/assets/{org-1hO_K6ya.js → org-Bke3eBzc.js} +1 -1
- package/dist/assets/web/dist/assets/{perl-CVRQLvVk.js → perl-Bkh0N7Kz.js} +1 -1
- package/dist/assets/web/dist/assets/{php-K9nCrB9n.js → php-Dnv1Piya.js} +1 -1
- package/dist/assets/web/dist/assets/{pug-CTxOcmO3.js → pug-D6peFFI7.js} +1 -1
- package/dist/assets/web/dist/assets/{qml-CmCkfG5q.js → qml-D8PEs-C-.js} +1 -1
- package/dist/assets/web/dist/assets/{r-pP47Xn1X.js → r-B1EL9b_j.js} +1 -1
- package/dist/assets/web/dist/assets/{razor-BpW6r3tC.js → razor-BsMTIh2b.js} +1 -1
- package/dist/assets/web/dist/assets/{regexp-CINgdY4N.js → regexp-HhvC8spD.js} +1 -1
- package/dist/assets/web/dist/assets/{rst-BZIUEq2Q.js → rst-D5paAxpg.js} +1 -1
- package/dist/assets/web/dist/assets/{ruby-nzAOxz6r.js → ruby-BrQwhLrl.js} +1 -1
- package/dist/assets/web/dist/assets/{sas-QIS1bFth.js → sas-afot2B1o.js} +1 -1
- package/dist/assets/web/dist/assets/{scss-BuhBBVNt.js → scss-Efm-mwuG.js} +1 -1
- package/dist/assets/web/dist/assets/{shellscript-CQc1vXbk.js → shellscript-BK0Vv5fT.js} +1 -1
- package/dist/assets/web/dist/assets/{shellsession-CoubCAUv.js → shellsession-BGqlMC7N.js} +1 -1
- package/dist/assets/web/dist/assets/{soy-CPRzlder.js → soy-M0b4UwGM.js} +1 -1
- package/dist/assets/web/dist/assets/{sql-W9krb8-9.js → sql-sxe6IE9j.js} +1 -1
- package/dist/assets/web/dist/assets/sseReplay-C9m_EB8J.js +9 -0
- package/dist/assets/web/dist/assets/{stata-CuISJEC0.js → stata-nPF_ddLP.js} +1 -1
- package/dist/assets/web/dist/assets/{surrealql-CSet7584.js → surrealql-D_GrC6u7.js} +1 -1
- package/dist/assets/web/dist/assets/{svelte-ChXTSYwK.js → svelte-DwL1AtNP.js} +1 -1
- package/dist/assets/web/dist/assets/{templ-Bm55v62k.js → templ-7s7LTDkc.js} +1 -1
- package/dist/assets/web/dist/assets/{tex-CICMX8Gj.js → tex-Bx-5fMxe.js} +1 -1
- package/dist/assets/web/dist/assets/{ts-tags-CA1UzWyB.js → ts-tags-DUMJnke_.js} +1 -1
- package/dist/assets/web/dist/assets/{tsx-Dy04HNbv.js → tsx-BN8biPDe.js} +1 -1
- package/dist/assets/web/dist/assets/{twig-BBHsVnVD.js → twig-8nIu84TN.js} +1 -1
- package/dist/assets/web/dist/assets/{typescript-Dvc-wVBT.js → typescript-BooSPq_S.js} +1 -1
- package/dist/assets/web/dist/assets/{typst-Dvlqx3_q.js → typst-CTBiBsem.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-zWi1MNU2.js → vue-C6Ft4Lea.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-html-D4YxT3An.js → vue-html-Ba36dD5D.js} +1 -1
- package/dist/assets/web/dist/assets/{vue-vine-0YE7uQlH.js → vue-vine-NkFexVo2.js} +1 -1
- package/dist/assets/web/dist/assets/{xml-gm-iksZB.js → xml-C_THnHXZ.js} +1 -1
- package/dist/assets/web/dist/assets/{xsl-D_W5BcrY.js → xsl-D8G5xqjY.js} +1 -1
- package/dist/assets/web/dist/assets/{yaml-CHmZ21wZ.js → yaml-jAMIJzge.js} +1 -1
- package/dist/cli.js +4121 -1902
- package/package.json +1 -1
- package/dist/assets/web/dist/assets/CostsPage-BuHD-Bv7.js +0 -2
- package/dist/assets/web/dist/assets/HomePage-dKBvWh-E.js +0 -2
- package/dist/assets/web/dist/assets/PendingTurnRow-CrEAWx5b.js +0 -1
- package/dist/assets/web/dist/assets/ResidentsIndexPage-CBBIjPOa.js +0 -1
- package/dist/assets/web/dist/assets/RunFoldRow-B6vxjcBI.js +0 -1
- package/dist/assets/web/dist/assets/RunRoutePage-CQjd3Cz-.js +0 -6
- package/dist/assets/web/dist/assets/RunsIndexPage-Cy9G6D0p.js +0 -1
- package/dist/assets/web/dist/assets/ScheduledPage-Ci1Ygqnc.js +0 -1
- package/dist/assets/web/dist/assets/SettingsPage-DxmJvAmT.js +0 -1
- package/dist/assets/web/dist/assets/SlackMark-VDNs7Vjh.js +0 -1
- package/dist/assets/web/dist/assets/UnitRoutePage-DEN5bSQf.js +0 -1
- package/dist/assets/web/dist/assets/durationTone-DXG-3R7_.js +0 -1
- package/dist/assets/web/dist/assets/indexRow-BD1VT8o8.js +0 -1
- package/dist/assets/web/dist/assets/localIso-L06jV29p.js +0 -1
- package/dist/assets/web/dist/assets/main-DP_zSemY.css +0 -1
- package/dist/assets/web/dist/assets/sseReplay-yji8a1wM.js +0 -9
|
@@ -11,7 +11,11 @@ providers:
|
|
|
11
11
|
anthropic:
|
|
12
12
|
type: anthropic
|
|
13
13
|
apiKeyEnv: ANTHROPIC_API_KEY
|
|
14
|
-
#
|
|
14
|
+
# OpenAI itself runs on the same compatible adapter: any preset's model may
|
|
15
|
+
# be openai/<model-id> anywhere a model is accepted. The adapter speaks Chat
|
|
16
|
+
# Completions, not the Responses API, sends no reasoning control (our effort
|
|
17
|
+
# tiers do not reach these models), and meters tokens without pricing them.
|
|
18
|
+
# A first-class OpenAI block is a plan's business, not this file's.
|
|
15
19
|
openai:
|
|
16
20
|
type: openai-compatible
|
|
17
21
|
baseUrl: https://api.openai.com/v1
|
|
@@ -65,10 +69,19 @@ defaults:
|
|
|
65
69
|
# clipped (the card and the run record say so); a preset whose identity or
|
|
66
70
|
# class is above a cap is refused by name before any executor exists. Absent →
|
|
67
71
|
# nothing is capped and every preset runs exactly its declared profile.
|
|
72
|
+
# A fourth field, `confirm`, is the front door's and caps no run: the first
|
|
73
|
+
# blast-radius class a command the router bound from prose is handed back at
|
|
74
|
+
# (`To run this: …`) instead of run — `write` (every write is handed back; a
|
|
75
|
+
# read or a repository's own test or build runs) or `destructive` (a write
|
|
76
|
+
# runs at once; only a destructive one is handed back). It intersects toward
|
|
77
|
+
# caution like the run axes — a channel or a user can only ask MORE than what
|
|
78
|
+
# is set here — and unset everywhere it is the built-in `write`. `exec` and
|
|
79
|
+
# `never` are refused by name.
|
|
68
80
|
# boundary:
|
|
69
81
|
# maxMinutes: 120
|
|
70
82
|
# maxIdentity: write
|
|
71
83
|
# machines: [none, blank, repo-cold, repo-resident]
|
|
84
|
+
# confirm: write
|
|
72
85
|
|
|
73
86
|
# PR reviews and the reading diff (docs/reference/specs/reading-diff.md). Every
|
|
74
87
|
# review records its full `git diff`; the ABRIDGED version (meat.dev, run on
|
|
@@ -100,10 +113,16 @@ defaults:
|
|
|
100
113
|
# maxMinutes: 45
|
|
101
114
|
# maxIdentity: read
|
|
102
115
|
|
|
103
|
-
# Static per-user defaults (user ID -> scope).
|
|
116
|
+
# Static per-user defaults (user ID -> scope). A user's `harness` puts that
|
|
117
|
+
# person's runs of a preset on another harness (docs/reference/specs/harness.md
|
|
118
|
+
# item 8) — every run of it they request, in any channel, and nobody else's; the
|
|
119
|
+
# same words as the top-level `harness` block, which they override (a channel's
|
|
120
|
+
# `harness` sits between). From chat: `config set me --harness.coding opencode`.
|
|
104
121
|
# users:
|
|
105
122
|
# slack:U012345:
|
|
106
123
|
# model: openai/gpt-5
|
|
124
|
+
# harness:
|
|
125
|
+
# coding: opencode
|
|
107
126
|
|
|
108
127
|
# Authorization (docs/reference/specs/authorization.md item 9, docs/reference/authorization.md):
|
|
109
128
|
# two blocks. `restrict` names what is CLOSED unless granted — an agent listed
|
|
@@ -327,17 +346,22 @@ workspaceDir: ./workspaces
|
|
|
327
346
|
# # a finding at or above it sends the round into a fix step instead of merge-ready.
|
|
328
347
|
# # Per channel/user: config set … --ship.addressSeverity; per run: severity:<level>.
|
|
329
348
|
|
|
330
|
-
# Which harness each preset's runs are driven by
|
|
331
|
-
# item 8): a mapping of preset to a harness's
|
|
332
|
-
# coding agent, the default for every preset)
|
|
333
|
-
# `opencode serve`) — each started inside
|
|
334
|
-
# as a child of the bot for a preset
|
|
335
|
-
# model-proxy bearer as its only key, every
|
|
336
|
-
# event on the run's record.
|
|
337
|
-
#
|
|
338
|
-
#
|
|
339
|
-
#
|
|
340
|
-
#
|
|
349
|
+
# Which harness each preset's runs are driven by, deployment-wide
|
|
350
|
+
# (docs/reference/specs/harness.md item 8): a mapping of preset to a harness's
|
|
351
|
+
# name. Two exist — `pi` (the pi coding agent, the default for every preset)
|
|
352
|
+
# and `opencode` (OpenCode's server, `opencode serve`) — each started inside
|
|
353
|
+
# the run's own execution container (or as a child of the bot for a preset
|
|
354
|
+
# without a workspace) with the run's model-proxy bearer as its only key, every
|
|
355
|
+
# tool call decided in the bot, every event on the run's record. This block is
|
|
356
|
+
# the defaults layer of the per-scope `harness` setting: a channel's or a
|
|
357
|
+
# user's `harness` (above, or `config set channel|me --harness.<preset> …`)
|
|
358
|
+
# overrides it for the runs in that scope, user over channel over here. A
|
|
359
|
+
# preset no layer names runs on pi; nothing defaults to OpenCode. A run keeps
|
|
360
|
+
# the harness it started on, so changing a word moves the next run, never one
|
|
361
|
+
# in flight. Any other word (`native`, the deleted loop; `codex`) fails the
|
|
362
|
+
# load naming the two, as does an unknown preset, in every layer; a
|
|
363
|
+
# `defaults.harness` key is refused pointing here. How-to:
|
|
364
|
+
# docs/how-to/put-a-preset-on-opencode.md.
|
|
341
365
|
# harness:
|
|
342
366
|
# coding: opencode # coding runs on OpenCode
|
|
343
367
|
# review: pi # the default, written out
|
|
@@ -411,6 +435,8 @@ workspaceDir: ./workspaces
|
|
|
411
435
|
# snapshot: # the page serves a snapshot of both billing sources, never a live read;
|
|
412
436
|
# everyHours: 24 # how often the bot reads them (1–168, default 24). `costs snapshot`
|
|
413
437
|
# # takes one now. Kept on the state Worker any `*.worker` block names
|
|
438
|
+
# alertChannel: slack:C0123ABCD # optional: a line there after three failed takes in a row, and one
|
|
439
|
+
# # when a take lands again; otherwise only the page's status line says
|
|
414
440
|
# groups:
|
|
415
441
|
# myapp:
|
|
416
442
|
# label: My App
|
|
@@ -423,6 +449,12 @@ workspaceDir: ./workspaces
|
|
|
423
449
|
# r2Buckets: # optional: bucket name → label for buckets not named <worker>-cache
|
|
424
450
|
# some-other-bucket: uploads
|
|
425
451
|
# anthropicWorkspaceId: wrkspc_... # this app's Anthropic workspace
|
|
452
|
+
# prices: # optional: USD per million tokens by <provider>/<model>, the ref a run's
|
|
453
|
+
# openai/gpt-5: # spans name — for a model the Anthropic list does not know, or to
|
|
454
|
+
# input: 1.25 # override the list; all four kinds named (a provider with no
|
|
455
|
+
# output: 10 # cache-write charge says 0). A model neither knows is "unpriced",
|
|
456
|
+
# cacheRead: 0.125 # never $0, on the costs page and on a run's own page
|
|
457
|
+
# cacheWrite: 0
|
|
426
458
|
|
|
427
459
|
# Delivery indicators: GET /delivery and `delivery report` (docs/reference/specs/delivery.md).
|
|
428
460
|
# Optional. Needs a GitHub credential (the App triple or GH_TOKEN); read-only on GitHub.
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
isRunSession,
|
|
26
26
|
isRunVisibilityFilter,
|
|
27
27
|
normalizeStored,
|
|
28
|
+
pullRequestNumberOf,
|
|
28
29
|
RETENTION_BOUNDS,
|
|
29
30
|
RUN_EVENTS_DEFAULT_PAGE,
|
|
30
31
|
RUN_EVENTS_MAX_PAGE,
|
|
@@ -59,11 +60,11 @@ import {
|
|
|
59
60
|
} from "../../src/core/runLedger/sessionLog.ts";
|
|
60
61
|
import type { RunEvent } from "../../src/core/runEvents.ts";
|
|
61
62
|
import {
|
|
62
|
-
aggregateUsageByUser,
|
|
63
63
|
isRunUsage,
|
|
64
64
|
usageOfEvents,
|
|
65
65
|
type RunUsage,
|
|
66
|
-
type
|
|
66
|
+
type RunUsageRows,
|
|
67
|
+
type UsageIdentity,
|
|
67
68
|
type UsageRun,
|
|
68
69
|
} from "../../src/core/runUsage.ts";
|
|
69
70
|
import {
|
|
@@ -1265,13 +1266,15 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1265
1266
|
this.migrateRunsTable();
|
|
1266
1267
|
// The indexes the visibility predicate's leaves walk (`channel_id IN`,
|
|
1267
1268
|
// `channel_visibility IN`, `user_id =`), each ordered like the page; the
|
|
1268
|
-
// session the sweep asks about; the parent a children listing filters on
|
|
1269
|
+
// session the sweep asks about; the parent a children listing filters on;
|
|
1270
|
+
// the pull request a findings listing filters on.
|
|
1269
1271
|
this.sql.exec(`
|
|
1270
1272
|
CREATE INDEX IF NOT EXISTS runs_channel_finished ON runs(channel_id, finished_at DESC, run_id DESC);
|
|
1271
1273
|
CREATE INDEX IF NOT EXISTS runs_visibility_finished ON runs(channel_visibility, finished_at DESC, run_id DESC);
|
|
1272
1274
|
CREATE INDEX IF NOT EXISTS runs_user_finished ON runs(user_id, finished_at DESC, run_id DESC);
|
|
1273
1275
|
CREATE INDEX IF NOT EXISTS runs_session ON runs(session_key);
|
|
1274
1276
|
CREATE INDEX IF NOT EXISTS runs_parent ON runs(parent_run_id, finished_at DESC, run_id DESC);
|
|
1277
|
+
CREATE INDEX IF NOT EXISTS runs_pr ON runs(repo, pr_number, finished_at DESC, run_id DESC);
|
|
1275
1278
|
`);
|
|
1276
1279
|
// The sessions registry (session-log item 7): every session log a run of
|
|
1277
1280
|
// this store claimed, with its thread — the sweep cannot enumerate the
|
|
@@ -1438,7 +1441,11 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1438
1441
|
* aggregate fills it from the run's stored events. The parent a child names
|
|
1439
1442
|
* (run-history item 46), the column a children listing filters on: the
|
|
1440
1443
|
* record already carries it in `summary_json`, so existing rows are filled
|
|
1441
|
-
* from there once, and every later `put` writes it beside the row.
|
|
1444
|
+
* from there once, and every later `put` writes it beside the row. The pull
|
|
1445
|
+
* request a run names (run-history item 58; `pullRequestNumberOf`), the
|
|
1446
|
+
* column a findings listing filters on: the same one-time fill from
|
|
1447
|
+
* `summary_json` — the coding post-step's `pr`, else a posted review's
|
|
1448
|
+
* target — and every later `put` writes it beside the row. */
|
|
1442
1449
|
migrateRunsTable(): void {
|
|
1443
1450
|
const columns = new Set(
|
|
1444
1451
|
this.sql
|
|
@@ -1457,6 +1464,19 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1457
1464
|
WHERE json_type(summary_json, '$.parentRunId') = 'text'`,
|
|
1458
1465
|
);
|
|
1459
1466
|
}
|
|
1467
|
+
if (!columns.has("pr_number")) {
|
|
1468
|
+
this.sql.exec(`ALTER TABLE runs ADD COLUMN pr_number INTEGER`);
|
|
1469
|
+
// `pullRequestNumberOf` in SQL: the coding post-step's pull request, else
|
|
1470
|
+
// the one a posted review targeted (a skipped post has no target).
|
|
1471
|
+
this.sql.exec(
|
|
1472
|
+
`UPDATE runs SET pr_number = COALESCE(
|
|
1473
|
+
CASE WHEN json_type(summary_json, '$.pr.number') = 'integer' THEN json_extract(summary_json, '$.pr.number') END,
|
|
1474
|
+
CASE WHEN json_extract(summary_json, '$.reviewPost.posted') = 1
|
|
1475
|
+
AND json_type(summary_json, '$.reviewPost.target.number') = 'integer'
|
|
1476
|
+
THEN json_extract(summary_json, '$.reviewPost.target.number') END
|
|
1477
|
+
)`,
|
|
1478
|
+
);
|
|
1479
|
+
}
|
|
1460
1480
|
}
|
|
1461
1481
|
|
|
1462
1482
|
private liveByThread(threadKey: string): LiveRunRow | undefined {
|
|
@@ -1970,8 +1990,8 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1970
1990
|
);
|
|
1971
1991
|
this.sql.exec(
|
|
1972
1992
|
`INSERT INTO runs (run_id, label, agent, model, channel_id, user_id, thread_key, channel_visibility, repo, started_at, finished_at, stored_at, status,
|
|
1973
|
-
event_count, stored_event_count, truncated, bytes, diagnosis_json, summary_json, session_key, usage_json, parent_run_id)
|
|
1974
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1993
|
+
event_count, stored_event_count, truncated, bytes, diagnosis_json, summary_json, session_key, usage_json, parent_run_id, pr_number)
|
|
1994
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
1975
1995
|
ON CONFLICT(run_id) DO UPDATE SET
|
|
1976
1996
|
label = excluded.label, agent = excluded.agent, model = excluded.model, channel_id = excluded.channel_id,
|
|
1977
1997
|
user_id = excluded.user_id, thread_key = excluded.thread_key, channel_visibility = excluded.channel_visibility,
|
|
@@ -1981,7 +2001,8 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
1981
2001
|
bytes = excluded.bytes, diagnosis_json = excluded.diagnosis_json, summary_json = excluded.summary_json,
|
|
1982
2002
|
session_key = excluded.session_key,
|
|
1983
2003
|
usage_json = COALESCE(excluded.usage_json, runs.usage_json),
|
|
1984
|
-
parent_run_id = excluded.parent_run_id
|
|
2004
|
+
parent_run_id = excluded.parent_run_id,
|
|
2005
|
+
pr_number = excluded.pr_number`,
|
|
1985
2006
|
stored.id,
|
|
1986
2007
|
stored.label ?? null,
|
|
1987
2008
|
stored.agent ?? null,
|
|
@@ -2004,6 +2025,7 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2004
2025
|
stored.session?.key ?? null,
|
|
2005
2026
|
stored.usage ? JSON.stringify(stored.usage) : null,
|
|
2006
2027
|
stored.parentRunId ?? null,
|
|
2028
|
+
pullRequestNumberOf(stored) ?? null,
|
|
2007
2029
|
);
|
|
2008
2030
|
// The session's registry row learns its newest finish (session-log item
|
|
2009
2031
|
// 7); a record that reaches the store without a claim (the plain put
|
|
@@ -2208,28 +2230,32 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2208
2230
|
}
|
|
2209
2231
|
|
|
2210
2232
|
/**
|
|
2211
|
-
*
|
|
2212
|
-
*
|
|
2213
|
-
*
|
|
2214
|
-
*
|
|
2215
|
-
*
|
|
2233
|
+
* What the runs that finished in [sinceMs, untilMs) and are inside the
|
|
2234
|
+
* retention window cost (costs.md items 10–10a; run-history item 56): one row
|
|
2235
|
+
* per run — its requester, thread, channel, agent and usage — plus the
|
|
2236
|
+
* identity of every parent a child names that is outside the batch, so the
|
|
2237
|
+
* bot bills the child without a second read. The arithmetic is the bot's; the
|
|
2238
|
+
* Worker only reads its rows. A row written before `usage_json` existed is
|
|
2239
|
+
* filled in here from its stored `model.turn` events — up to
|
|
2240
|
+
* USAGE_BACKFILL_PER_CALL a call, the rest answered without `usage` and
|
|
2241
|
+
* counted in `pending` — so the history heals as it is read, with no
|
|
2216
2242
|
* operator step; a run without turns is written back as the zero usage so it
|
|
2217
|
-
* is not re-read.
|
|
2218
|
-
* outside the range is looked up by id.
|
|
2243
|
+
* is not re-read.
|
|
2219
2244
|
*/
|
|
2220
|
-
|
|
2245
|
+
usage(sinceMs: number, untilMs: number): RunUsageRows {
|
|
2221
2246
|
const now = systemClock();
|
|
2222
2247
|
const { policy } = this.policyState();
|
|
2223
2248
|
const cutoff = now - policy.retentionDays * 86_400_000;
|
|
2224
2249
|
const rows = this.sql
|
|
2225
2250
|
.exec<UsageRow>(
|
|
2226
|
-
`SELECT run_id, user_id, started_at, finished_at, usage_json, summary_json FROM runs
|
|
2251
|
+
`SELECT run_id, user_id, agent, channel_id, thread_key, started_at, finished_at, usage_json, summary_json FROM runs
|
|
2227
2252
|
WHERE finished_at >= ? AND finished_at < ? ORDER BY finished_at ASC, run_id ASC`,
|
|
2228
2253
|
Math.max(sinceMs, cutoff),
|
|
2229
2254
|
untilMs,
|
|
2230
2255
|
)
|
|
2231
2256
|
.toArray();
|
|
2232
2257
|
let backfilled = 0;
|
|
2258
|
+
let pending = 0;
|
|
2233
2259
|
const runs: UsageRun[] = rows.map((row) => {
|
|
2234
2260
|
let usage = parseUsageJson(row.usage_json);
|
|
2235
2261
|
if (usage === undefined && backfilled < USAGE_BACKFILL_PER_CALL) {
|
|
@@ -2237,31 +2263,38 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2237
2263
|
this.sql.exec(`UPDATE runs SET usage_json = ? WHERE run_id = ?`, JSON.stringify(usage), row.run_id);
|
|
2238
2264
|
backfilled += 1;
|
|
2239
2265
|
}
|
|
2266
|
+
if (usage === undefined) pending += 1;
|
|
2240
2267
|
const who = identityOfSummary(row.summary_json);
|
|
2241
2268
|
return {
|
|
2242
2269
|
id: row.run_id,
|
|
2243
2270
|
userId: row.user_id,
|
|
2244
2271
|
...(who.userName ? { userName: who.userName } : {}),
|
|
2245
2272
|
...(who.parentRunId ? { parentRunId: who.parentRunId } : {}),
|
|
2273
|
+
threadKey: row.thread_key,
|
|
2274
|
+
channelId: row.channel_id,
|
|
2275
|
+
...(row.agent ? { agent: row.agent } : {}),
|
|
2246
2276
|
startedAt: row.started_at,
|
|
2247
2277
|
finishedAt: row.finished_at,
|
|
2248
2278
|
...(usage ? { usage } : {}),
|
|
2249
2279
|
};
|
|
2250
2280
|
});
|
|
2251
|
-
|
|
2281
|
+
// The parents a child names that are not in the batch: looked up once each, known or not.
|
|
2282
|
+
const inBatch = new Set(runs.map((r) => r.id));
|
|
2283
|
+
const parents: Record<string, UsageIdentity> = {};
|
|
2284
|
+
for (const id of new Set(runs.map((r) => r.parentRunId).filter((p): p is string => !!p && !inBatch.has(p)))) {
|
|
2252
2285
|
const p = this.sql
|
|
2253
2286
|
.exec<{ user_id: string; summary_json: string }>(`SELECT user_id, summary_json FROM runs WHERE run_id = ?`, id)
|
|
2254
2287
|
.toArray()[0];
|
|
2255
|
-
if (!p)
|
|
2288
|
+
if (!p) continue;
|
|
2256
2289
|
const who = identityOfSummary(p.summary_json);
|
|
2257
|
-
|
|
2258
|
-
}
|
|
2259
|
-
const { rows: out, pending } = aggregateUsageByUser(runs, lookupParent);
|
|
2290
|
+
parents[id] = { userId: p.user_id, ...(who.userName ? { userName: who.userName } : {}) };
|
|
2291
|
+
}
|
|
2260
2292
|
const earliest = this.sql
|
|
2261
2293
|
.exec<{ m: number | null }>(`SELECT MIN(finished_at) AS m FROM runs WHERE finished_at >= ?`, cutoff)
|
|
2262
2294
|
.one().m;
|
|
2263
2295
|
return {
|
|
2264
|
-
|
|
2296
|
+
runs,
|
|
2297
|
+
parents,
|
|
2265
2298
|
pending,
|
|
2266
2299
|
...(earliest !== null ? { earliestFinishedAt: earliest } : {}),
|
|
2267
2300
|
retentionDays: policy.retentionDays,
|
|
@@ -2343,6 +2376,11 @@ export class RunHistoryDO extends DurableObject<Env> {
|
|
|
2343
2376
|
where.push(`parent_run_id = ?`);
|
|
2344
2377
|
params.push(q.parentRunId);
|
|
2345
2378
|
}
|
|
2379
|
+
if (q.pr !== undefined) {
|
|
2380
|
+
// `namesPullRequest` in SQL: the row's repository and the number it names.
|
|
2381
|
+
where.push(`repo = ?`, `pr_number = ?`);
|
|
2382
|
+
params.push(q.pr.repo, q.pr.number);
|
|
2383
|
+
}
|
|
2346
2384
|
if (q.visibleTo !== undefined && q.visibleTo.kind !== "all") where.push(visibilitySql(q.visibleTo, params));
|
|
2347
2385
|
const select = `SELECT run_id, agent, channel_id, finished_at, bytes, event_count, summary_json FROM runs WHERE ${where.join(" AND ")} ORDER BY finished_at DESC, run_id DESC`;
|
|
2348
2386
|
// `LIMIT` holds on the over-bound path too: the kept set is the newest
|
|
@@ -2423,6 +2461,9 @@ const USAGE_BACKFILL_PER_CALL = 200;
|
|
|
2423
2461
|
type UsageRow = {
|
|
2424
2462
|
run_id: string;
|
|
2425
2463
|
user_id: string;
|
|
2464
|
+
agent: string | null;
|
|
2465
|
+
channel_id: string;
|
|
2466
|
+
thread_key: string;
|
|
2426
2467
|
started_at: number;
|
|
2427
2468
|
finished_at: number;
|
|
2428
2469
|
usage_json: string | null;
|
|
@@ -2590,12 +2631,26 @@ function parseRunList(body: unknown): Validated<{ storeKey: string; query: RunLi
|
|
|
2590
2631
|
if (!id.ok) return invalid("parentRunId must match ^[A-Za-z0-9_-]{1,64}$");
|
|
2591
2632
|
query.parentRunId = id.value;
|
|
2592
2633
|
}
|
|
2634
|
+
if (b.pr !== undefined) {
|
|
2635
|
+
const pr = b.pr as Record<string, unknown> | null;
|
|
2636
|
+
if (
|
|
2637
|
+
typeof pr !== "object" ||
|
|
2638
|
+
pr === null ||
|
|
2639
|
+
typeof pr.repo !== "string" ||
|
|
2640
|
+
!REPO_SLUG.test(pr.repo) ||
|
|
2641
|
+
typeof pr.number !== "number" ||
|
|
2642
|
+
!Number.isInteger(pr.number) ||
|
|
2643
|
+
pr.number < 1
|
|
2644
|
+
)
|
|
2645
|
+
return invalid("pr must be { repo: owner/name, number: a positive integer }");
|
|
2646
|
+
query.pr = { repo: pr.repo, number: pr.number };
|
|
2647
|
+
}
|
|
2593
2648
|
if (b.visibleTo !== undefined) {
|
|
2594
2649
|
// A malformed filter is a 400, never "all": the bot degrades to live rows
|
|
2595
2650
|
// rather than the DO widening what an actor may see.
|
|
2596
2651
|
if (!isRunVisibilityFilter(b.visibleTo)) return invalid("visibleTo must be a run visibility filter");
|
|
2597
|
-
|
|
2598
|
-
|
|
2652
|
+
const headroom = DO_MAX_BOUND_PARAMETERS - RUN_LIST_BASE_PARAMETERS - (query.pr ? RUN_LIST_PR_PARAMETERS : 0);
|
|
2653
|
+
if (boundParameters(b.visibleTo) > headroom) return invalid(`visibleTo names more than ${headroom} ids`);
|
|
2599
2654
|
query.visibleTo = b.visibleTo;
|
|
2600
2655
|
}
|
|
2601
2656
|
return { ok: true, value: { storeKey: key.value, query } };
|
|
@@ -2604,6 +2659,8 @@ function parseRunList(body: unknown): Validated<{ storeKey: string; query: RunLi
|
|
|
2604
2659
|
/** Parameters the page query binds before any filter: the cursor pair (3) and the age floor (1),
|
|
2605
2660
|
* plus `agent`, `channel`, `threadKey`, `parentRunId`, and the LIMIT at most — the headroom `visibleTo` must fit under. */
|
|
2606
2661
|
const RUN_LIST_BASE_PARAMETERS = 9;
|
|
2662
|
+
/** The two more a `pr` filter binds (`repo`, `pr_number`), taken from the same headroom only when asked. */
|
|
2663
|
+
const RUN_LIST_PR_PARAMETERS = 2;
|
|
2607
2664
|
|
|
2608
2665
|
/** How many `?` a filter binds (one per id, one per user). */
|
|
2609
2666
|
function boundParameters(f: RunVisibilityFilter): number {
|
|
@@ -3800,13 +3857,13 @@ async function handleRuns(pathname: string, body: unknown, env: Env): Promise<Re
|
|
|
3800
3857
|
);
|
|
3801
3858
|
return json(result ?? { events: null });
|
|
3802
3859
|
}
|
|
3803
|
-
if (pathname === "/runs/usage
|
|
3860
|
+
if (pathname === "/runs/usage") {
|
|
3804
3861
|
const parsed = parseRunUsageQuery(body);
|
|
3805
3862
|
if (!parsed.ok) return json({ error: parsed.error }, 400);
|
|
3806
3863
|
const { storeKey, sinceMs, untilMs } = parsed.value;
|
|
3807
|
-
const
|
|
3808
|
-
console.log(`[runs/usage
|
|
3809
|
-
return json(
|
|
3864
|
+
const rows = await stub(storeKey).usage(sinceMs, untilMs);
|
|
3865
|
+
console.log(`[runs/usage] ${storeKey} -> ${rows.runs.length} runs, ${rows.pending} pending`);
|
|
3866
|
+
return json(rows);
|
|
3810
3867
|
}
|
|
3811
3868
|
// /runs/delete
|
|
3812
3869
|
const parsed = parseRunTarget(body);
|
|
@@ -3831,7 +3888,7 @@ const ROUTES = new Set([
|
|
|
3831
3888
|
"/runs/summary",
|
|
3832
3889
|
"/runs/list",
|
|
3833
3890
|
"/runs/events",
|
|
3834
|
-
"/runs/usage
|
|
3891
|
+
"/runs/usage",
|
|
3835
3892
|
"/runs/delete",
|
|
3836
3893
|
...LEDGER_ROUTES,
|
|
3837
3894
|
]);
|