@coreplane/switchboard 1.267.3 → 1.268.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 (39) hide show
  1. package/dist/assets/deploy/cloudflare-memory/worker.ts +23 -1
  2. package/dist/assets/deploy/cloudflare-resident/imageReconcileState.ts +28 -0
  3. package/dist/assets/deploy/cloudflare-resident/shared.ts +2 -3
  4. package/dist/assets/deploy/cloudflare-resident/worker.ts +162 -27
  5. package/dist/assets/package-lock.json +3 -3
  6. package/dist/assets/package.json +1 -1
  7. package/dist/assets/source.json +3 -3
  8. package/dist/assets/src/core/budgets.ts +4 -0
  9. package/dist/assets/src/core/coordinator/contract.ts +14 -8
  10. package/dist/assets/src/core/costs.ts +2 -0
  11. package/dist/assets/src/core/runEvents.ts +1 -0
  12. package/dist/assets/src/core/runLedger/types.ts +2 -0
  13. package/dist/assets/src/core/runRecord.ts +10 -0
  14. package/dist/assets/src/core/runUsage.ts +53 -0
  15. package/dist/assets/src/core/shipSpend.ts +52 -0
  16. package/dist/assets/src/execution/residentPoolSpends.ts +9 -0
  17. package/dist/assets/src/execution/residentRecreateAdmission.ts +28 -0
  18. package/dist/assets/web/dist/.vite/manifest.json +31 -31
  19. package/dist/assets/web/dist/assets/CostsPage-6P9rJ7k_.js +1 -0
  20. package/dist/assets/web/dist/assets/{HomePage-BQmun9kS.js → HomePage-B4LGwXHO.js} +1 -1
  21. package/dist/assets/web/dist/assets/ResidentDetailPage-jpGfsR5n.js +1 -0
  22. package/dist/assets/web/dist/assets/ResidentsIndexPage-C4-5nG9M.js +1 -0
  23. package/dist/assets/web/dist/assets/{RunFoldRow-C1w-mwch.js → RunFoldRow-u4FTh57c.js} +1 -1
  24. package/dist/assets/web/dist/assets/{RunRoutePage-zWMHTzla.js → RunRoutePage-CxOKb7oC.js} +4 -4
  25. package/dist/assets/web/dist/assets/{RunsIndexPage-DF16D2Gg.js → RunsIndexPage-CULSlv_d.js} +1 -1
  26. package/dist/assets/web/dist/assets/{ScheduledPage-CyRs0fXN.js → ScheduledPage-CDjkZuqM.js} +1 -1
  27. package/dist/assets/web/dist/assets/{SettingsPage-BRuMCHSj.js → SettingsPage-BMSomotV.js} +1 -1
  28. package/dist/assets/web/dist/assets/{StatusDot-BIAQN7iD.js → StatusDot-XxLkmRaK.js} +1 -1
  29. package/dist/assets/web/dist/assets/{Tooltip-CxiQ7luL.js → Tooltip-qB73RL4b.js} +1 -1
  30. package/dist/assets/web/dist/assets/{UnitRoutePage-BQB5mAAl.js → UnitRoutePage-DFRWAdOT.js} +1 -1
  31. package/dist/assets/web/dist/assets/{dist-I2OEmPqz.js → dist-BsLBAkQJ.js} +1 -1
  32. package/dist/assets/web/dist/assets/{main-DYwKyLY5.js → main-9ynpo780.js} +10 -10
  33. package/dist/assets/web/dist/assets/main-DSiJ9sHU.css +1 -0
  34. package/dist/cli.js +385 -226
  35. package/package.json +1 -1
  36. package/dist/assets/web/dist/assets/CostsPage-RRggVvIx.js +0 -1
  37. package/dist/assets/web/dist/assets/ResidentDetailPage-CT6v9INM.js +0 -1
  38. package/dist/assets/web/dist/assets/ResidentsIndexPage-CWqPFf40.js +0 -1
  39. package/dist/assets/web/dist/assets/main-DPB0UO1-.css +0 -1
package/dist/cli.js CHANGED
@@ -653,7 +653,7 @@ function fit(pipeline) {
653
653
  const need = ALLOWANCES.provision + ASKS.coding + reserveMinutes({ kind: "coding", index: 0 }, pipeline);
654
654
  return { ok: pipeline.maxMinutes >= need, need, have: pipeline.maxMinutes };
655
655
  }
656
- var SECOND_MS, secondsToMs, MINUTE_MS, minutesToMs, DAY_MS, WEEK_MS, CONFIRMATION_TTL_MS, QUESTION_TTL_MS, AUTHOR_BINDING_TTL_MS, GIT_RECEIVE_PACK_FORWARD_TIMEOUT_MS, GIT_PUBLICATION_SETTLE_TIMEOUT_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, SHIP_RECORD_VISIBILITY, 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;
656
+ var SECOND_MS, secondsToMs, MINUTE_MS, minutesToMs, DAY_MS, WEEK_MS, CONFIRMATION_TTL_MS, QUESTION_TTL_MS, AUTHOR_BINDING_TTL_MS, GIT_RECEIVE_PACK_FORWARD_TIMEOUT_MS, GIT_PUBLICATION_SETTLE_TIMEOUT_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, RESIDENT_LISTING_POLL_MS, HOSTED_DEADLINE_MARGIN_MINUTES, SHIP_WAIT, SHIP_RECORD_VISIBILITY, 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;
657
657
  var init_budgets = __esm({
658
658
  "../../src/core/budgets.ts"() {
659
659
  "use strict";
@@ -732,6 +732,7 @@ var init_budgets = __esm({
732
732
  PULL_SWEEP = { leaseMinutes: 15, spendCapUsd: 5 };
733
733
  PROVIDER_RETRY_BACKOFFS_MS = [5e3, 15e3, 45e3];
734
734
  MODEL_STREAM_HEARTBEAT_MS = 15e3;
735
+ RESIDENT_LISTING_POLL_MS = 3e4;
735
736
  HOSTED_DEADLINE_MARGIN_MINUTES = 60;
736
737
  SHIP_WAIT = { marginMinutes: 5, chunkMinutes: 5, mergeChunkMinutes: 5, busyRetryMinutes: 2 };
737
738
  SHIP_RECORD_VISIBILITY = { retries: 3, retryMs: secondsToMs(1) };
@@ -5388,197 +5389,6 @@ var init_modelPricing = __esm({
5388
5389
  }
5389
5390
  });
5390
5391
 
5391
- // ../../src/core/runUsage.ts
5392
- function usageOfEvents(events) {
5393
- const usage = emptyUsage();
5394
- const priced = /* @__PURE__ */ new Map();
5395
- for (const e of events) {
5396
- if (e.type !== "span_end" || e.name !== MODEL_TURN) continue;
5397
- const attrs = e.attrs ?? {};
5398
- const model = typeof attrs.model === "string" && attrs.model ? attrs.model : UNKNOWN_MODEL;
5399
- const m = usage.byModel[model] ?? {
5400
- turns: 0,
5401
- inputTokens: 0,
5402
- outputTokens: 0,
5403
- cacheReadTokens: 0,
5404
- cacheWriteTokens: 0
5405
- };
5406
- const counted2 = num(attrs.inputTokens) + num(attrs.outputTokens) + num(attrs.cacheReadTokens) + num(attrs.cacheWriteTokens);
5407
- m.turns += 1;
5408
- m.inputTokens += num(attrs.inputTokens);
5409
- m.outputTokens += num(attrs.outputTokens);
5410
- m.cacheReadTokens += num(attrs.cacheReadTokens);
5411
- m.cacheWriteTokens += num(attrs.cacheWriteTokens);
5412
- usage.byModel[model] = m;
5413
- usage.turns += 1;
5414
- const hasUsd = typeof attrs.usd === "number" && Number.isFinite(attrs.usd);
5415
- const hasSource = typeof attrs.priceSource === "string" && attrs.priceSource !== "";
5416
- const p = priced.get(model) ?? {
5417
- usd: 0,
5418
- feeUsd: 0,
5419
- feeTurns: 0,
5420
- pricedTurns: 0,
5421
- unpricedTokenTurns: 0,
5422
- sources: /* @__PURE__ */ new Set()
5423
- };
5424
- if (counted2 > 0 && !hasUsd) p.unpricedTokenTurns += 1;
5425
- if (hasUsd) {
5426
- p.usd += attrs.usd;
5427
- p.pricedTurns += 1;
5428
- }
5429
- if (typeof attrs.feeUsd === "number" && Number.isFinite(attrs.feeUsd)) {
5430
- p.feeUsd += attrs.feeUsd;
5431
- p.feeTurns += 1;
5432
- }
5433
- if (hasSource) p.sources.add(attrs.priceSource);
5434
- priced.set(model, p);
5435
- }
5436
- for (const [model, p] of priced) {
5437
- if (p.pricedTurns === 0 && p.sources.size === 0) continue;
5438
- const m = usage.byModel[model];
5439
- m.usd = p.unpricedTokenTurns > 0 ? null : p.usd;
5440
- if (p.feeTurns > 0) m.feeUsd = p.feeUsd;
5441
- if (p.sources.size > 0) m.priceSources = [...p.sources].sort();
5442
- }
5443
- return usage;
5444
- }
5445
- function addUsage(a, b) {
5446
- const out = { turns: a.turns + b.turns, byModel: {} };
5447
- for (const src of [a.byModel, b.byModel]) {
5448
- for (const [model, m] of Object.entries(src)) {
5449
- const acc = out.byModel[model];
5450
- if (!acc) {
5451
- out.byModel[model] = { ...m, ...m.priceSources ? { priceSources: [...m.priceSources] } : {} };
5452
- continue;
5453
- }
5454
- acc.turns += m.turns;
5455
- acc.inputTokens += m.inputTokens;
5456
- acc.outputTokens += m.outputTokens;
5457
- acc.cacheReadTokens += m.cacheReadTokens;
5458
- acc.cacheWriteTokens += m.cacheWriteTokens;
5459
- const usd2 = foldUsd(acc.usd, m.usd);
5460
- if (usd2 === void 0) delete acc.usd;
5461
- else acc.usd = usd2;
5462
- if (acc.feeUsd !== void 0 || m.feeUsd !== void 0) acc.feeUsd = (acc.feeUsd ?? 0) + (m.feeUsd ?? 0);
5463
- const sources = /* @__PURE__ */ new Set([...acc.priceSources ?? [], ...m.priceSources ?? []]);
5464
- if (sources.size > 0) acc.priceSources = [...sources].sort();
5465
- }
5466
- }
5467
- return out;
5468
- }
5469
- function isRunUsage(v) {
5470
- if (typeof v !== "object" || v === null) return false;
5471
- const u = v;
5472
- if (typeof u.turns !== "number") return false;
5473
- if (typeof u.byModel !== "object" || u.byModel === null || Array.isArray(u.byModel)) return false;
5474
- return Object.values(u.byModel).every(isModelUsage);
5475
- }
5476
- function billedTo(run2, batch, lookupParent) {
5477
- if (!run2.parentRunId) return identityOf(run2);
5478
- const parent = batch.get(run2.parentRunId) ?? lookupParent(run2.parentRunId);
5479
- return identityOf(parent ?? run2);
5480
- }
5481
- function aggregateUsage(runs, lookupParent = () => void 0) {
5482
- const batch = new Map(runs.map((r) => [r.id, r]));
5483
- const rows = /* @__PURE__ */ new Map();
5484
- let pending2 = 0;
5485
- for (const run2 of runs) {
5486
- const who2 = billedTo(run2, batch, lookupParent);
5487
- const day2 = dayOf(run2.finishedAt);
5488
- const agent = run2.agent ?? UNKNOWN_AGENT;
5489
- const key = JSON.stringify([day2, who2.userId, run2.threadKey, run2.channelId, agent]);
5490
- const row2 = rows.get(key) ?? {
5491
- day: day2,
5492
- userId: who2.userId,
5493
- threadKey: run2.threadKey,
5494
- channelId: run2.channelId,
5495
- agent,
5496
- runs: 0,
5497
- wallMs: 0,
5498
- usage: emptyUsage()
5499
- };
5500
- if (who2.userName && !row2.userName) row2.userName = who2.userName;
5501
- row2.runs += 1;
5502
- row2.wallMs += Math.max(0, run2.finishedAt - run2.startedAt);
5503
- if (run2.usage) row2.usage = addUsage(row2.usage, run2.usage);
5504
- else pending2 += 1;
5505
- rows.set(key, row2);
5506
- }
5507
- const sorted = [...rows.values()].sort((a, b) => {
5508
- for (const k of CELL_ORDER) {
5509
- if (a[k] < b[k]) return -1;
5510
- if (a[k] > b[k]) return 1;
5511
- }
5512
- return 0;
5513
- });
5514
- return { rows: sorted, pending: pending2 };
5515
- }
5516
- function reportOfUsageRows(rows) {
5517
- const { rows: cells, pending: pending2 } = aggregateUsage(rows.runs, (id) => rows.parents[id]);
5518
- return {
5519
- rows: cells,
5520
- // The store counts what it could not price; the fold sees the same runs without `usage`.
5521
- pending: Math.max(pending2, rows.pending),
5522
- ...rows.earliestFinishedAt !== void 0 ? { earliestFinishedAt: rows.earliestFinishedAt } : {},
5523
- retentionDays: rows.retentionDays
5524
- };
5525
- }
5526
- function isUsageRun(v) {
5527
- if (!isIdentity(v)) return false;
5528
- const r = v;
5529
- return typeof r.id === "string" && typeof r.threadKey === "string" && typeof r.channelId === "string" && (r.agent === void 0 || typeof r.agent === "string") && (r.parentRunId === void 0 || typeof r.parentRunId === "string") && typeof r.startedAt === "number" && typeof r.finishedAt === "number" && (r.usage === void 0 || isRunUsage(r.usage));
5530
- }
5531
- function hasReportTail(r) {
5532
- if (typeof r.pending !== "number" || typeof r.retentionDays !== "number") return false;
5533
- return r.earliestFinishedAt === void 0 || typeof r.earliestFinishedAt === "number";
5534
- }
5535
- function isRunUsageRows(v) {
5536
- if (typeof v !== "object" || v === null) return false;
5537
- const r = v;
5538
- if (!Array.isArray(r.runs) || !r.runs.every(isUsageRun)) return false;
5539
- if (typeof r.parents !== "object" || r.parents === null || Array.isArray(r.parents)) return false;
5540
- if (!Object.values(r.parents).every(isIdentity)) return false;
5541
- return hasReportTail(r);
5542
- }
5543
- function isRunUsageReport(v) {
5544
- if (typeof v !== "object" || v === null) return false;
5545
- const r = v;
5546
- if (!Array.isArray(r.rows) || !hasReportTail(r)) return false;
5547
- return r.rows.every(
5548
- (row2) => isIdentity(row2) && typeof row2.day === "string" && typeof row2.threadKey === "string" && typeof row2.channelId === "string" && typeof row2.agent === "string" && typeof row2.runs === "number" && typeof row2.wallMs === "number" && isRunUsage(row2.usage)
5549
- );
5550
- }
5551
- var UNKNOWN_MODEL, UNKNOWN_AGENT, MODEL_TURN, num, emptyUsage, foldUsd, isModelUsage, dayOf, identityOf, CELL_ORDER, isIdentity;
5552
- var init_runUsage = __esm({
5553
- "../../src/core/runUsage.ts"() {
5554
- "use strict";
5555
- UNKNOWN_MODEL = "unknown";
5556
- UNKNOWN_AGENT = "unknown";
5557
- MODEL_TURN = "model.turn";
5558
- num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
5559
- emptyUsage = () => ({ turns: 0, byModel: {} });
5560
- foldUsd = (a, b) => a === void 0 || b === void 0 ? void 0 : a === null || b === null ? null : a + b;
5561
- isModelUsage = (v) => {
5562
- if (typeof v !== "object" || v === null) return false;
5563
- const m = v;
5564
- if (!["turns", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"].every(
5565
- (k) => typeof m[k] === "number"
5566
- ))
5567
- return false;
5568
- if (m.usd !== void 0 && m.usd !== null && typeof m.usd !== "number") return false;
5569
- if (m.feeUsd !== void 0 && typeof m.feeUsd !== "number") return false;
5570
- return m.priceSources === void 0 || Array.isArray(m.priceSources) && m.priceSources.every((s) => typeof s === "string");
5571
- };
5572
- dayOf = (epochMs) => new Date(epochMs).toISOString().slice(0, 10);
5573
- identityOf = (who2) => ({
5574
- userId: who2.userId,
5575
- ...who2.userName ? { userName: who2.userName } : {}
5576
- });
5577
- CELL_ORDER = ["day", "userId", "threadKey", "channelId", "agent"];
5578
- isIdentity = (v) => typeof v === "object" && v !== null && typeof v.userId === "string" && (v.userName === void 0 || typeof v.userName === "string");
5579
- }
5580
- });
5581
-
5582
5392
  // ../../src/core/ship/handoff.ts
5583
5393
  function isEmptyHandoff(h) {
5584
5394
  return LISTS.every((k) => listOf(asLists(h), k).length === 0);
@@ -5803,7 +5613,11 @@ function pullMergedEventType(key) {
5803
5613
  return `${PULL_MERGED_EVENT_PREFIX}${key.instanceId.slice(0, room)}${suffix}`;
5804
5614
  }
5805
5615
  function coordinatorFields(tag) {
5806
- return tag ? { parentInstanceId: tag.parentInstanceId, idempotencyKey: tag.idempotencyKey } : {};
5616
+ return tag ? {
5617
+ parentInstanceId: tag.parentInstanceId,
5618
+ idempotencyKey: tag.idempotencyKey,
5619
+ ...tag.costCapUsd !== void 0 ? { costCapUsd: tag.costCapUsd } : {}
5620
+ } : {};
5807
5621
  }
5808
5622
  function isCoordinatorInstance(v) {
5809
5623
  if (!isObject(v)) return false;
@@ -5972,6 +5786,221 @@ var init_contract2 = __esm({
5972
5786
  }
5973
5787
  });
5974
5788
 
5789
+ // ../../src/core/runUsage.ts
5790
+ function usageOfEvents(events) {
5791
+ const usage = emptyUsage();
5792
+ const priced = /* @__PURE__ */ new Map();
5793
+ for (const e of events) {
5794
+ if (e.type !== "span_end" || e.name !== MODEL_TURN) continue;
5795
+ const attrs = e.attrs ?? {};
5796
+ const model = typeof attrs.model === "string" && attrs.model ? attrs.model : UNKNOWN_MODEL;
5797
+ const m = usage.byModel[model] ?? {
5798
+ turns: 0,
5799
+ inputTokens: 0,
5800
+ outputTokens: 0,
5801
+ cacheReadTokens: 0,
5802
+ cacheWriteTokens: 0
5803
+ };
5804
+ const counted2 = num(attrs.inputTokens) + num(attrs.outputTokens) + num(attrs.cacheReadTokens) + num(attrs.cacheWriteTokens);
5805
+ m.turns += 1;
5806
+ m.inputTokens += num(attrs.inputTokens);
5807
+ m.outputTokens += num(attrs.outputTokens);
5808
+ m.cacheReadTokens += num(attrs.cacheReadTokens);
5809
+ m.cacheWriteTokens += num(attrs.cacheWriteTokens);
5810
+ usage.byModel[model] = m;
5811
+ usage.turns += 1;
5812
+ const hasUsd = typeof attrs.usd === "number" && Number.isFinite(attrs.usd);
5813
+ const hasSource = typeof attrs.priceSource === "string" && attrs.priceSource !== "";
5814
+ const p = priced.get(model) ?? {
5815
+ usd: 0,
5816
+ feeUsd: 0,
5817
+ feeTurns: 0,
5818
+ pricedTurns: 0,
5819
+ unpricedTokenTurns: 0,
5820
+ sources: /* @__PURE__ */ new Set()
5821
+ };
5822
+ if (counted2 > 0 && !hasUsd) p.unpricedTokenTurns += 1;
5823
+ if (hasUsd) {
5824
+ p.usd += attrs.usd;
5825
+ p.pricedTurns += 1;
5826
+ }
5827
+ if (typeof attrs.feeUsd === "number" && Number.isFinite(attrs.feeUsd)) {
5828
+ p.feeUsd += attrs.feeUsd;
5829
+ p.feeTurns += 1;
5830
+ }
5831
+ if (hasSource) p.sources.add(attrs.priceSource);
5832
+ priced.set(model, p);
5833
+ }
5834
+ for (const [model, p] of priced) {
5835
+ if (p.pricedTurns === 0 && p.sources.size === 0) continue;
5836
+ const m = usage.byModel[model];
5837
+ m.usd = p.unpricedTokenTurns > 0 ? null : p.usd;
5838
+ if (p.feeTurns > 0) m.feeUsd = p.feeUsd;
5839
+ if (p.sources.size > 0) m.priceSources = [...p.sources].sort();
5840
+ }
5841
+ return usage;
5842
+ }
5843
+ function addUsage(a, b) {
5844
+ const out = { turns: a.turns + b.turns, byModel: {} };
5845
+ for (const src of [a.byModel, b.byModel]) {
5846
+ for (const [model, m] of Object.entries(src)) {
5847
+ const acc = out.byModel[model];
5848
+ if (!acc) {
5849
+ out.byModel[model] = { ...m, ...m.priceSources ? { priceSources: [...m.priceSources] } : {} };
5850
+ continue;
5851
+ }
5852
+ acc.turns += m.turns;
5853
+ acc.inputTokens += m.inputTokens;
5854
+ acc.outputTokens += m.outputTokens;
5855
+ acc.cacheReadTokens += m.cacheReadTokens;
5856
+ acc.cacheWriteTokens += m.cacheWriteTokens;
5857
+ const usd2 = foldUsd(acc.usd, m.usd);
5858
+ if (usd2 === void 0) delete acc.usd;
5859
+ else acc.usd = usd2;
5860
+ if (acc.feeUsd !== void 0 || m.feeUsd !== void 0) acc.feeUsd = (acc.feeUsd ?? 0) + (m.feeUsd ?? 0);
5861
+ const sources = /* @__PURE__ */ new Set([...acc.priceSources ?? [], ...m.priceSources ?? []]);
5862
+ if (sources.size > 0) acc.priceSources = [...sources].sort();
5863
+ }
5864
+ }
5865
+ return out;
5866
+ }
5867
+ function isRunUsage(v) {
5868
+ if (typeof v !== "object" || v === null) return false;
5869
+ const u = v;
5870
+ if (typeof u.turns !== "number") return false;
5871
+ if (typeof u.byModel !== "object" || u.byModel === null || Array.isArray(u.byModel)) return false;
5872
+ return Object.values(u.byModel).every(isModelUsage);
5873
+ }
5874
+ function shipRunsOf(runs) {
5875
+ return runs.flatMap((run2) => {
5876
+ if (!run2.parentInstanceId || !run2.idempotencyKey) return [];
5877
+ const unit = unitOfIdempotencyKey(run2.idempotencyKey);
5878
+ if (!unit) return [];
5879
+ return [
5880
+ {
5881
+ id: run2.id,
5882
+ unitKey: `${run2.parentInstanceId}:${unit}`,
5883
+ finishedAt: run2.finishedAt,
5884
+ ...run2.costCapUsd !== void 0 ? { costCapUsd: run2.costCapUsd } : {},
5885
+ ...run2.usage !== void 0 ? { usage: run2.usage } : {}
5886
+ }
5887
+ ];
5888
+ });
5889
+ }
5890
+ function billedTo(run2, batch, lookupParent) {
5891
+ if (!run2.parentRunId) return identityOf(run2);
5892
+ const parent = batch.get(run2.parentRunId) ?? lookupParent(run2.parentRunId);
5893
+ return identityOf(parent ?? run2);
5894
+ }
5895
+ function aggregateUsage(runs, lookupParent = () => void 0) {
5896
+ const batch = new Map(runs.map((r) => [r.id, r]));
5897
+ const rows = /* @__PURE__ */ new Map();
5898
+ let pending2 = 0;
5899
+ for (const run2 of runs) {
5900
+ const who2 = billedTo(run2, batch, lookupParent);
5901
+ const day2 = dayOf(run2.finishedAt);
5902
+ const agent = run2.agent ?? UNKNOWN_AGENT;
5903
+ const key = JSON.stringify([day2, who2.userId, run2.threadKey, run2.channelId, agent]);
5904
+ const row2 = rows.get(key) ?? {
5905
+ day: day2,
5906
+ userId: who2.userId,
5907
+ threadKey: run2.threadKey,
5908
+ channelId: run2.channelId,
5909
+ agent,
5910
+ runs: 0,
5911
+ wallMs: 0,
5912
+ usage: emptyUsage()
5913
+ };
5914
+ if (who2.userName && !row2.userName) row2.userName = who2.userName;
5915
+ row2.runs += 1;
5916
+ row2.wallMs += Math.max(0, run2.finishedAt - run2.startedAt);
5917
+ if (run2.usage) row2.usage = addUsage(row2.usage, run2.usage);
5918
+ else pending2 += 1;
5919
+ rows.set(key, row2);
5920
+ }
5921
+ const sorted = [...rows.values()].sort((a, b) => {
5922
+ for (const k of CELL_ORDER) {
5923
+ if (a[k] < b[k]) return -1;
5924
+ if (a[k] > b[k]) return 1;
5925
+ }
5926
+ return 0;
5927
+ });
5928
+ return { rows: sorted, pending: pending2 };
5929
+ }
5930
+ function reportOfUsageRows(rows) {
5931
+ const { rows: cells, pending: pending2 } = aggregateUsage(rows.runs, (id) => rows.parents[id]);
5932
+ return {
5933
+ rows: cells,
5934
+ shipRuns: shipRunsOf(rows.runs),
5935
+ // The store counts what it could not price; the fold sees the same runs without `usage`.
5936
+ pending: Math.max(pending2, rows.pending),
5937
+ ...rows.earliestFinishedAt !== void 0 ? { earliestFinishedAt: rows.earliestFinishedAt } : {},
5938
+ retentionDays: rows.retentionDays
5939
+ };
5940
+ }
5941
+ function isUsageRun(v) {
5942
+ if (!isIdentity(v)) return false;
5943
+ const r = v;
5944
+ return typeof r.id === "string" && typeof r.threadKey === "string" && typeof r.channelId === "string" && (r.agent === void 0 || typeof r.agent === "string") && (r.parentRunId === void 0 || typeof r.parentRunId === "string") && (r.parentInstanceId === void 0 || typeof r.parentInstanceId === "string") && (r.idempotencyKey === void 0 || typeof r.idempotencyKey === "string") && (r.costCapUsd === void 0 || typeof r.costCapUsd === "number" && Number.isFinite(r.costCapUsd) && r.costCapUsd > 0) && typeof r.startedAt === "number" && typeof r.finishedAt === "number" && (r.usage === void 0 || isRunUsage(r.usage));
5945
+ }
5946
+ function hasReportTail(r) {
5947
+ if (typeof r.pending !== "number" || typeof r.retentionDays !== "number") return false;
5948
+ return r.earliestFinishedAt === void 0 || typeof r.earliestFinishedAt === "number";
5949
+ }
5950
+ function isRunUsageRows(v) {
5951
+ if (typeof v !== "object" || v === null) return false;
5952
+ const r = v;
5953
+ if (!Array.isArray(r.runs) || !r.runs.every(isUsageRun)) return false;
5954
+ if (typeof r.parents !== "object" || r.parents === null || Array.isArray(r.parents)) return false;
5955
+ if (!Object.values(r.parents).every(isIdentity)) return false;
5956
+ return hasReportTail(r);
5957
+ }
5958
+ function isRunUsageReport(v) {
5959
+ if (typeof v !== "object" || v === null) return false;
5960
+ const r = v;
5961
+ if (!Array.isArray(r.rows) || !hasReportTail(r)) return false;
5962
+ if (r.shipRuns !== void 0 && (!Array.isArray(r.shipRuns) || !r.shipRuns.every(isShipRunUsage))) return false;
5963
+ return r.rows.every(
5964
+ (row2) => isIdentity(row2) && typeof row2.day === "string" && typeof row2.threadKey === "string" && typeof row2.channelId === "string" && typeof row2.agent === "string" && typeof row2.runs === "number" && typeof row2.wallMs === "number" && isRunUsage(row2.usage)
5965
+ );
5966
+ }
5967
+ function isShipRunUsage(v) {
5968
+ if (typeof v !== "object" || v === null) return false;
5969
+ const r = v;
5970
+ return typeof r.id === "string" && typeof r.unitKey === "string" && typeof r.finishedAt === "number" && Number.isFinite(r.finishedAt) && (r.costCapUsd === void 0 || typeof r.costCapUsd === "number" && Number.isFinite(r.costCapUsd) && r.costCapUsd > 0) && (r.usage === void 0 || isRunUsage(r.usage));
5971
+ }
5972
+ var UNKNOWN_MODEL, UNKNOWN_AGENT, MODEL_TURN, num, emptyUsage, foldUsd, isModelUsage, dayOf, identityOf, CELL_ORDER, isIdentity;
5973
+ var init_runUsage = __esm({
5974
+ "../../src/core/runUsage.ts"() {
5975
+ "use strict";
5976
+ init_contract2();
5977
+ UNKNOWN_MODEL = "unknown";
5978
+ UNKNOWN_AGENT = "unknown";
5979
+ MODEL_TURN = "model.turn";
5980
+ num = (v) => typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : 0;
5981
+ emptyUsage = () => ({ turns: 0, byModel: {} });
5982
+ foldUsd = (a, b) => a === void 0 || b === void 0 ? void 0 : a === null || b === null ? null : a + b;
5983
+ isModelUsage = (v) => {
5984
+ if (typeof v !== "object" || v === null) return false;
5985
+ const m = v;
5986
+ if (!["turns", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"].every(
5987
+ (k) => typeof m[k] === "number"
5988
+ ))
5989
+ return false;
5990
+ if (m.usd !== void 0 && m.usd !== null && typeof m.usd !== "number") return false;
5991
+ if (m.feeUsd !== void 0 && typeof m.feeUsd !== "number") return false;
5992
+ return m.priceSources === void 0 || Array.isArray(m.priceSources) && m.priceSources.every((s) => typeof s === "string");
5993
+ };
5994
+ dayOf = (epochMs) => new Date(epochMs).toISOString().slice(0, 10);
5995
+ identityOf = (who2) => ({
5996
+ userId: who2.userId,
5997
+ ...who2.userName ? { userName: who2.userName } : {}
5998
+ });
5999
+ CELL_ORDER = ["day", "userId", "threadKey", "channelId", "agent"];
6000
+ isIdentity = (v) => typeof v === "object" && v !== null && typeof v.userId === "string" && (v.userName === void 0 || typeof v.userName === "string");
6001
+ }
6002
+ });
6003
+
5975
6004
  // ../../src/core/pipelineStanding.ts
5976
6005
  function hostedStageDetail(stage) {
5977
6006
  switch (stage) {
@@ -6589,6 +6618,8 @@ function isRunRecord(v) {
6589
6618
  return false;
6590
6619
  if (r.idempotencyKey !== void 0 && (typeof r.idempotencyKey !== "string" || !IDEMPOTENCY_KEY_PATTERN.test(r.idempotencyKey)))
6591
6620
  return false;
6621
+ if (r.costCapUsd !== void 0 && (r.parentInstanceId === void 0 || typeof r.costCapUsd !== "number" || !Number.isFinite(r.costCapUsd) || r.costCapUsd <= 0))
6622
+ return false;
6592
6623
  if (r.instanceId !== void 0 && (typeof r.instanceId !== "string" || !INSTANCE_ID_PATTERN.test(r.instanceId)))
6593
6624
  return false;
6594
6625
  if (r.pipeline !== void 0 && !isPipelineSummaryShape(r.pipeline)) return false;
@@ -12975,6 +13006,9 @@ async function waitForResident(step, expectedCommit, deadline, io, deps, resourc
12975
13006
  const bearer = RESIDENT_BEARER_ENVS.map((name) => deps.env[name]).find((value) => value?.trim());
12976
13007
  if (resources && !bearer) return `registry unreadable: no ${RESIDENT_BEARER_ENVS.join(" / ")}`;
12977
13008
  let problem = "readiness deadline expired before readback";
13009
+ let lastLoggedProblem = "";
13010
+ let lastLoggedAt = -Infinity;
13011
+ let nextReconcileAt = 0;
12978
13012
  while (deps.now() < deadline) {
12979
13013
  const timeoutMs = Math.min(LIVE_GATE_POLL_MS, deadline - deps.now());
12980
13014
  const [health, registry] = await Promise.all([
@@ -12987,7 +13021,16 @@ async function waitForResident(step, expectedCommit, deadline, io, deps, resourc
12987
13021
  ].filter(Boolean);
12988
13022
  if (problems.length === 0 && deps.now() <= deadline) return void 0;
12989
13023
  problem = problems.join("; ") || "readback arrived after the readiness deadline";
12990
- io.log(`[deploy:all] resident: waiting for readiness \u2014 ${problem}`);
13024
+ if (problem !== lastLoggedProblem || deps.now() - lastLoggedAt >= MINUTE_MS) {
13025
+ io.log(`[deploy:all] resident: waiting for readiness \u2014 ${problem}`);
13026
+ lastLoggedProblem = problem;
13027
+ lastLoggedAt = deps.now();
13028
+ }
13029
+ if (resources && deps.now() >= nextReconcileAt && deadline - deps.now() > 2 * LIVE_GATE_POLL_MS && !residentWorkerProblem(health, expectedCommit)) {
13030
+ const retry = await reconcileFleet(step, io, deps, false);
13031
+ if (retry && !reconciledResources(retry)) io.log(reconcileLine(step.name, retry));
13032
+ nextReconcileAt = deps.now() + MINUTE_MS;
13033
+ }
12991
13034
  const left = deadline - deps.now();
12992
13035
  if (left > 0) await deps.sleep(Math.min(LIVE_GATE_POLL_MS, left));
12993
13036
  }
@@ -13008,11 +13051,11 @@ async function beginDrain(step, expectedCommit, io, deps) {
13008
13051
  io.log(drainBeganLine(step.name, answer2));
13009
13052
  return { attempted: true, drained: drainSet(answer2), until: drainUntil(answer2) };
13010
13053
  }
13011
- async function reconcileFleet(step, io, deps) {
13054
+ async function reconcileFleet(step, io, deps, log = true) {
13012
13055
  const bearer = step.drain ? deps.env[step.drain.tokenEnv] : void 0;
13013
13056
  if (!step.drain || !bearer || !deps.postJson) return;
13014
13057
  const answer2 = await deps.postJson(reconcileUrl(step.drain.url), bearer, {});
13015
- io.log(reconcileLine(step.name, answer2));
13058
+ if (log) io.log(reconcileLine(step.name, answer2));
13016
13059
  return answer2;
13017
13060
  }
13018
13061
  async function endDrain(step, drain, io, deps) {
@@ -15586,15 +15629,15 @@ function sandboxNoAnswerMessage(route2, budgetMs) {
15586
15629
  function sandboxEmptyFailureMessage(route2) {
15587
15630
  return `sandbox worker ${route2}: failure with an empty message (the Worker's failure shape with its text missing)`;
15588
15631
  }
15589
- function waitForSlot(ms, waitedMs, signal2) {
15632
+ function sandboxStopped(route2) {
15633
+ return new ExecInfraError(`sandbox ${route2}: the run was stopped`, "aborted");
15634
+ }
15635
+ function waitForRetry(ms, route2, signal2) {
15590
15636
  return new Promise((resolve13, reject) => {
15591
- const stopped = () => new ExecCapacityError(
15592
- `sandbox fleet busy \u2014 stopped waiting for a free per-thread sandbox after ${Math.round(waitedMs / 1e3)}s: the run was stopped`
15593
- );
15594
- if (signal2?.aborted) return reject(stopped());
15637
+ if (signal2?.aborted) return reject(sandboxStopped(route2));
15595
15638
  const onAbort = () => {
15596
15639
  clearTimeout(timer);
15597
- reject(stopped());
15640
+ reject(sandboxStopped(route2));
15598
15641
  };
15599
15642
  const timer = setTimeout(() => {
15600
15643
  signal2?.removeEventListener("abort", onAbort);
@@ -15629,13 +15672,13 @@ var init_cloudflareSandbox = __esm({
15629
15672
  scrubbed;
15630
15673
  /** The Worker reconnects by thread key, so scrub a reused sandbox before
15631
15674
  * seed, checkout, or any model-visible command can touch its old store. */
15632
- async prepareCredentialBoundary() {
15675
+ async prepareCredentialBoundary(opts) {
15633
15676
  const result = await this.call(
15634
15677
  "/exec",
15635
15678
  { command: legacySandboxCredentialScrub(SANDBOX_CREDENTIAL_FILE) },
15679
+ opts?.signal,
15636
15680
  void 0,
15637
- void 0,
15638
- void 0,
15681
+ opts?.span,
15639
15682
  true
15640
15683
  );
15641
15684
  if (Number(result.exitCode ?? -1) !== 0)
@@ -15691,8 +15734,9 @@ var init_cloudflareSandbox = __esm({
15691
15734
  * each send runs under. Time spent waiting for a slot never eats into a
15692
15735
  * send's deadline: nothing ran during it. */
15693
15736
  async call(route2, body, signal2, budgetMs = BASH_TIMEOUT_MS, span, skipScrub = false) {
15737
+ if (signal2?.aborted) throw sandboxStopped(route2);
15694
15738
  if (this.opts.scrubLegacyCredentials && !skipScrub) {
15695
- this.scrubbed ??= this.prepareCredentialBoundary();
15739
+ this.scrubbed ??= this.prepareCredentialBoundary({ signal: signal2, span });
15696
15740
  await this.scrubbed;
15697
15741
  }
15698
15742
  const envs = await this.opts.resolveEnvs();
@@ -15726,7 +15770,7 @@ var init_cloudflareSandbox = __esm({
15726
15770
  }
15727
15771
  const delay = Math.min(plan3.backoff[Math.min(attempt, plan3.backoff.length - 1)], plan3.budget - waited);
15728
15772
  attempt++;
15729
- await waitForSlot(delay, waited, signal2);
15773
+ await waitForRetry(delay, route2, signal2);
15730
15774
  waited += delay;
15731
15775
  }
15732
15776
  }
@@ -15740,7 +15784,8 @@ var init_cloudflareSandbox = __esm({
15740
15784
  let lastErr = "";
15741
15785
  let lastStatus = 0;
15742
15786
  for (const delay of delays) {
15743
- if (delay > 0) await new Promise((r) => setTimeout(r, delay));
15787
+ if (delay > 0) await waitForRetry(delay, route2, signal2);
15788
+ if (signal2?.aborted) throw sandboxStopped(route2);
15744
15789
  let res;
15745
15790
  let text;
15746
15791
  const deadline = execDeadline(sendDeadlineMs(budgetMs), signal2);
@@ -19077,7 +19122,13 @@ async function makeExecutor(opts, ctx, span) {
19077
19122
  const executor2 = await makePerThreadExecutor(opts, perThreadCheckout(opts, ctx));
19078
19123
  await recheckOwner();
19079
19124
  const handle2 = probe.kind === "status" ? probe.seed : void 0;
19080
- const outcome = handle2 && executor2 instanceof CloudflareSandboxExecutor ? await seedSandbox(executor2, handle2, ctx, () => probeResident(resident, token2, resource, span), span) : void 0;
19125
+ const outcome = handle2 && executor2 instanceof CloudflareSandboxExecutor ? await seedSandbox(
19126
+ executor2,
19127
+ handle2,
19128
+ ctx,
19129
+ () => probeResident(resident, token2, resource, span, ctx.stopSignal),
19130
+ span
19131
+ ) : void 0;
19081
19132
  if (outcome !== void 0) await recheckOwner();
19082
19133
  if (outcome && "seeded" in outcome) {
19083
19134
  return {
@@ -19117,8 +19168,9 @@ async function seedSandbox(executor, handle2, ctx, reprobe, span) {
19117
19168
  for (let retried = false; ; retried = true) {
19118
19169
  let answer2;
19119
19170
  try {
19120
- answer2 = await executor.seed(seed, { span });
19171
+ answer2 = await executor.seed(seed, { span, signal: ctx.stopSignal });
19121
19172
  } catch (err2) {
19173
+ if (isRunStopError(err2)) throw err2;
19122
19174
  return { why: oneLine(`seed failed (${err2 instanceof Error ? err2.message : String(err2)})`) };
19123
19175
  }
19124
19176
  if (answer2.seeded) {
@@ -19134,6 +19186,7 @@ async function seedSandbox(executor, handle2, ctx, reprobe, span) {
19134
19186
  };
19135
19187
  }
19136
19188
  const fresh = answer2.reason === "seed-missing" && !retried ? await reprobe() : void 0;
19189
+ if (fresh?.kind === "unreachable" && fresh.transport && ctx.stopSignal?.aborted) throw wakeStopped("/status");
19137
19190
  const decision = seedRetryDecision({
19138
19191
  answer: answer2,
19139
19192
  attempted: seed,
@@ -21682,6 +21735,9 @@ function usageReportOfRecords(records, query, lookupParent, retentionDays) {
21682
21735
  userId: r.userId,
21683
21736
  ...r.userName ? { userName: r.userName } : {},
21684
21737
  ...r.parentRunId ? { parentRunId: r.parentRunId } : {},
21738
+ ...r.parentInstanceId ? { parentInstanceId: r.parentInstanceId } : {},
21739
+ ...r.idempotencyKey ? { idempotencyKey: r.idempotencyKey } : {},
21740
+ ...r.costCapUsd !== void 0 ? { costCapUsd: r.costCapUsd } : {},
21685
21741
  threadKey: r.threadKey,
21686
21742
  channelId: r.channelId,
21687
21743
  ...r.agent ? { agent: r.agent } : {},
@@ -21694,7 +21750,13 @@ function usageReportOfRecords(records, query, lookupParent, retentionDays) {
21694
21750
  (m, r) => m === void 0 || r.finishedAt < m ? r.finishedAt : m,
21695
21751
  void 0
21696
21752
  );
21697
- return { rows, pending: pending2, ...earliest !== void 0 ? { earliestFinishedAt: earliest } : {}, retentionDays };
21753
+ return {
21754
+ rows,
21755
+ shipRuns: shipRunsOf(runs),
21756
+ pending: pending2,
21757
+ ...earliest !== void 0 ? { earliestFinishedAt: earliest } : {},
21758
+ retentionDays
21759
+ };
21698
21760
  }
21699
21761
  function isValidRunId(id) {
21700
21762
  return typeof id === "string" && RUN_ID_PATTERN.test(id);
@@ -22351,6 +22413,43 @@ var init_costsSnapshotStore = __esm({
22351
22413
  }
22352
22414
  });
22353
22415
 
22416
+ // ../../src/core/shipSpend.ts
22417
+ function recordedUsd(usage) {
22418
+ const models = Object.values(usage?.byModel ?? {});
22419
+ if (!usage || !Number.isSafeInteger(usage.turns) || usage.turns < 0) return null;
22420
+ if (usage.turns === 0 && models.length === 0) return 0;
22421
+ if (models.length === 0 || models.reduce((sum, model) => sum + model.turns, 0) !== usage.turns || models.some((model) => typeof model.usd !== "number" || !Number.isFinite(model.usd) || model.usd < 0))
22422
+ return null;
22423
+ return models.reduce((sum, model) => sum + model.usd, 0);
22424
+ }
22425
+ function buildShipSpendReport(runs, range) {
22426
+ const groups = /* @__PURE__ */ new Map();
22427
+ for (const run2 of runs) {
22428
+ const day2 = new Date(run2.finishedAt).toISOString().slice(0, 10);
22429
+ if (day2 < range.from || day2 > range.to) continue;
22430
+ groups.set(run2.unitKey, [...groups.get(run2.unitKey) ?? [], run2]);
22431
+ }
22432
+ const units = [...groups].map(([key, children]) => {
22433
+ const ordered = children.sort((a, b) => a.finishedAt - b.finishedAt || a.id.localeCompare(b.id));
22434
+ const caps = new Set(ordered.map((r) => r.costCapUsd));
22435
+ const capUsd = caps.size === 1 ? ordered[0]?.costCapUsd ?? null : null;
22436
+ let cumulative = 0;
22437
+ const points = ordered.map((r) => {
22438
+ const usd2 = recordedUsd(r.usage);
22439
+ cumulative = cumulative === null || usd2 === null ? null : cumulative + usd2;
22440
+ return { id: r.id, finishedAt: r.finishedAt, usd: usd2, cumulativeUsd: cumulative };
22441
+ });
22442
+ return { key, capUsd, totalUsd: cumulative, runs: points };
22443
+ });
22444
+ units.sort((a, b) => (b.runs.at(-1)?.finishedAt ?? 0) - (a.runs.at(-1)?.finishedAt ?? 0));
22445
+ return { range, units };
22446
+ }
22447
+ var init_shipSpend = __esm({
22448
+ "../../src/core/shipSpend.ts"() {
22449
+ "use strict";
22450
+ }
22451
+ });
22452
+
22354
22453
  // ../../src/core/costsService.ts
22355
22454
  async function viewerRunUserIds(userIds, viewer, emailOfSlackUser, cache2) {
22356
22455
  const email = viewer?.email?.toLowerCase();
@@ -22384,7 +22483,12 @@ function createCostsService(cfg, snapshots, deps = {}) {
22384
22483
  groups: () => Object.keys(cfg.groups),
22385
22484
  async report(group, daysParam) {
22386
22485
  const g = groupOf(group);
22387
- return reportFromSnapshot(await snapshotOrThrow(), group, g, daysParam, meta, deps.billers);
22486
+ const snapshot = await snapshotOrThrow();
22487
+ const daily = reportFromSnapshot(snapshot, group, g, daysParam, meta, deps.billers);
22488
+ return {
22489
+ ...daily,
22490
+ ...snapshot.runUsage?.shipRuns ? { shipSpend: buildShipSpendReport(snapshot.runUsage.shipRuns, daily.range) } : {}
22491
+ };
22388
22492
  },
22389
22493
  async byReport(group, daysParam, dimension, viewer) {
22390
22494
  const g = groupOf(group);
@@ -22487,6 +22591,7 @@ var init_costsService = __esm({
22487
22591
  init_costs();
22488
22592
  init_costsSnapshot();
22489
22593
  init_costsSnapshotStore();
22594
+ init_shipSpend();
22490
22595
  COSTS_OFF_MESSAGE = "Cost reporting isn't configured \u2014 set costs.cloudflareAccountId + costs.groups in config and the CF_ANALYTICS_TOKEN secret to enable this view.";
22491
22596
  NoCostsSnapshotError = class extends Error {
22492
22597
  name = "NoCostsSnapshotError";
@@ -33009,7 +33114,13 @@ function interruptedRunRecord(summary, snap, finishedAt) {
33009
33114
  repo: summary.repo,
33010
33115
  ...summary.parentRunId !== void 0 ? { parentRunId: summary.parentRunId } : {},
33011
33116
  ...summary.seed !== void 0 ? { seed: summary.seed } : {},
33012
- ...summary.parentInstanceId !== void 0 && summary.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: summary.parentInstanceId, idempotencyKey: summary.idempotencyKey } } : {},
33117
+ ...summary.parentInstanceId !== void 0 && summary.idempotencyKey !== void 0 ? {
33118
+ coordinator: {
33119
+ parentInstanceId: summary.parentInstanceId,
33120
+ idempotencyKey: summary.idempotencyKey,
33121
+ ...summary.costCapUsd !== void 0 ? { costCapUsd: summary.costCapUsd } : {}
33122
+ }
33123
+ } : {},
33013
33124
  ...summary.hosted ? { hosted: true } : {},
33014
33125
  finishedAt,
33015
33126
  status: "interrupted",
@@ -33058,7 +33169,13 @@ function reclaimedRunRecord(input) {
33058
33169
  ...row2.meta.session !== void 0 ? { session: row2.meta.session } : {},
33059
33170
  // A coordinator's child keeps its instance and key on the close (item 48),
33060
33171
  // so the state Worker's finish still sends the parent its event.
33061
- ...row2.meta.parentInstanceId !== void 0 && row2.meta.idempotencyKey !== void 0 ? { coordinator: { parentInstanceId: row2.meta.parentInstanceId, idempotencyKey: row2.meta.idempotencyKey } } : {},
33172
+ ...row2.meta.parentInstanceId !== void 0 && row2.meta.idempotencyKey !== void 0 ? {
33173
+ coordinator: {
33174
+ parentInstanceId: row2.meta.parentInstanceId,
33175
+ idempotencyKey: row2.meta.idempotencyKey,
33176
+ ...row2.meta.costCapUsd !== void 0 ? { costCapUsd: row2.meta.costCapUsd } : {}
33177
+ }
33178
+ } : {},
33062
33179
  ...row2.meta.hosted ? { hosted: true } : {},
33063
33180
  finishedAt,
33064
33181
  status: status3,
@@ -36745,7 +36862,7 @@ function addressedBarePrOf(s, text) {
36745
36862
  function contextCueOf(later2) {
36746
36863
  const clause = later2.before.split(/[;\n]/).at(-1) ?? later2.before;
36747
36864
  const action = [
36748
- ...clause.matchAll(/\b(?:re-?review|review|see|context|example|reference|related|compare|background)\b/gi)
36865
+ ...clause.matchAll(/\b(?:re-?review|review|see|context|example|reference|related|compare|background|packages)\b/gi)
36749
36866
  ].at(-1)?.[0].toLowerCase();
36750
36867
  return action !== void 0 && !/^(?:re-?review|review)$/.test(action) || /^[ \t]*(?:[,:][ \t]*)?(?:for|as)[ \t]+(?:context|reference|background)\b/i.test(later2.after);
36751
36868
  }
@@ -39975,6 +40092,7 @@ function summaryOf(run2, now) {
39975
40092
  ...m?.parentRunId !== void 0 ? { parentRunId: m.parentRunId } : {},
39976
40093
  ...m?.parentInstanceId !== void 0 ? { parentInstanceId: m.parentInstanceId } : {},
39977
40094
  ...m?.idempotencyKey !== void 0 ? { idempotencyKey: m.idempotencyKey } : {},
40095
+ ...m?.costCapUsd !== void 0 ? { costCapUsd: m.costCapUsd } : {},
39978
40096
  ...m?.seed !== void 0 ? { seed: m.seed } : {},
39979
40097
  ...m?.hosted ? { hosted: true } : {},
39980
40098
  ...run2.instanceId !== void 0 ? { instanceId: run2.instanceId } : {},
@@ -47793,6 +47911,7 @@ async function claimRun(deps, ctx) {
47793
47911
  registry.publish(run2.id, {
47794
47912
  type: "coordinator_tag",
47795
47913
  parentInstanceId: coordinator.parentInstanceId,
47914
+ ...coordinator.costCapUsd !== void 0 ? { costCapUsd: coordinator.costCapUsd } : {},
47796
47915
  ...unit !== void 0 ? { unit } : {},
47797
47916
  ...coordinator.branch !== void 0 ? { branch: coordinator.branch } : {},
47798
47917
  ...coordinator.transportWorkflowId !== void 0 ? { transportWorkflowId: coordinator.transportWorkflowId } : {},
@@ -57760,10 +57879,12 @@ function carriedCoordinatorTag(row2, events) {
57760
57879
  const publication = tag?.type === "coordinator_tag" ? tag.publication : void 0;
57761
57880
  const transportWorkflowId = tag?.type === "coordinator_tag" ? tag.transportWorkflowId : void 0;
57762
57881
  const recovery = tag?.type === "coordinator_tag" ? tag.recovery : void 0;
57882
+ const costCapUsd = (tag?.type === "coordinator_tag" ? tag.costCapUsd : void 0) ?? row2.meta.costCapUsd;
57763
57883
  const branch3 = (tag?.type === "coordinator_tag" ? tag.branch : void 0) ?? row2.meta.ref;
57764
57884
  return {
57765
57885
  parentInstanceId,
57766
57886
  idempotencyKey,
57887
+ ...costCapUsd !== void 0 ? { costCapUsd } : {},
57767
57888
  ...branch3 !== void 0 ? { branch: branch3 } : {},
57768
57889
  ...transportWorkflowId !== void 0 ? { transportWorkflowId } : {},
57769
57890
  ...recovery !== void 0 ? { recovery } : {},
@@ -58196,6 +58317,11 @@ __export(dispatcher_exports, {
58196
58317
  dispatch: () => dispatch,
58197
58318
  dispatchClick: () => dispatchClick
58198
58319
  });
58320
+ function stripConnectorFooters(text) {
58321
+ let probe = text.trimEnd();
58322
+ while (CONNECTOR_FOOTER_RE.test(probe)) probe = probe.replace(CONNECTOR_FOOTER_RE, "").trimEnd();
58323
+ return probe;
58324
+ }
58199
58325
  async function priorDecisionRecord(store, threadKey, repo, taskKey) {
58200
58326
  let before;
58201
58327
  let beforeId;
@@ -58468,11 +58594,19 @@ async function dispatch(deps, msg, io, opts = {}) {
58468
58594
  let resumeRowClosed = false;
58469
58595
  let restartRequest;
58470
58596
  let releaseLegacyOwnership;
58597
+ let pendingStop;
58598
+ const relayStop = (mode) => {
58599
+ if (registered) {
58600
+ registered.control.requestStop(mode);
58601
+ return;
58602
+ }
58603
+ if (mode === "hard" || pendingStop === void 0) pendingStop = mode;
58604
+ };
58471
58605
  const reservationHooks = {
58472
- onStop: (mode) => void registered?.control.requestStop(mode),
58606
+ onStop: relayStop,
58473
58607
  onFenced: () => {
58474
58608
  fencedWhileAttaching = true;
58475
- void registered?.control.requestStop("hard");
58609
+ relayStop("hard");
58476
58610
  }
58477
58611
  };
58478
58612
  try {
@@ -58480,7 +58614,7 @@ async function dispatch(deps, msg, io, opts = {}) {
58480
58614
  const typed = parseDirectives(msg.text);
58481
58615
  const typedAgent = typed.agent;
58482
58616
  const explicitPr = explicitPrOf(msg.text);
58483
- const shipTaskProbe = typed.text.replace(/\s+\*Sent using\* ChatGPT Connector \(Local MCP\)\s*$/i, "");
58617
+ const shipTaskProbe = stripConnectorFooters(typed.text);
58484
58618
  const namedShipTask = typedAgent === "ship" && explicitPr ? shipTaskText(shipTaskProbe, explicitPr.repo) : "";
58485
58619
  const freshShipTask = namedShipTask !== "" && !/^(?:please\s+)?(?:continue|resume|retry|keep going|carry on|finish)(?:\s+(?:with|on))?(?:\s+(?:it|this|the task|the plan|(?:the\s+)?work|working|(?:the\s+)?review|reviewing))?(?:\s+(?:of|on|for|with))?[.!]?$/i.test(
58486
58620
  namedShipTask
@@ -59096,8 +59230,8 @@ async function dispatch(deps, msg, io, opts = {}) {
59096
59230
  hooks: {
59097
59231
  reservation: reservationHooks,
59098
59232
  adopt: {
59099
- onStop: (mode) => void registered?.control.requestStop(mode),
59100
- onFenced: () => void registered?.control.requestStop("hard")
59233
+ onStop: relayStop,
59234
+ onFenced: () => relayStop("hard")
59101
59235
  }
59102
59236
  }
59103
59237
  };
@@ -59434,6 +59568,10 @@ async function dispatch(deps, msg, io, opts = {}) {
59434
59568
  });
59435
59569
  const { run: run2, runId: runId3, channelVisibility, liveUrl, events, publishText, publishMeta } = registration;
59436
59570
  registered = run2;
59571
+ if (pendingStop) {
59572
+ run2.control.requestStop(pendingStop);
59573
+ pendingStop = void 0;
59574
+ }
59437
59575
  if (coordinator) io.runStarted?.({ id: runId3 });
59438
59576
  for (const summary of seedNotes)
59439
59577
  events.publish({ type: "run_note", kind: "seed", summary: oneLine(summary), at: clock4() });
@@ -60313,7 +60451,7 @@ async function dispatchClick(deps, click) {
60313
60451
  activeRuns--;
60314
60452
  }
60315
60453
  }
60316
- var activeRuns, FULL_SHA2;
60454
+ var CONNECTOR_FOOTER_RE, activeRuns, FULL_SHA2;
60317
60455
  var init_dispatcher = __esm({
60318
60456
  "../../src/core/dispatcher.ts"() {
60319
60457
  "use strict";
@@ -60379,6 +60517,7 @@ var init_dispatcher = __esm({
60379
60517
  init_preflight();
60380
60518
  init_types5();
60381
60519
  init_decisionRecordReservation();
60520
+ CONNECTOR_FOOTER_RE = /(?:^|\s)(?:(\*{1,3}|_{1,3})Sent using\1|Sent using) ChatGPT Connector \(Local MCP\)\s*$/i;
60382
60521
  activeRuns = 0;
60383
60522
  FULL_SHA2 = /^[0-9a-f]{40}$/i;
60384
60523
  }
@@ -72663,7 +72802,12 @@ function residentStateTone(state) {
72663
72802
  }
72664
72803
  function residentsFleetTone(records) {
72665
72804
  if (records.length === 0) return "grey";
72666
- const tones = new Set(records.map((record4) => residentStateTone(residentLive(rec(record4)).state)));
72805
+ const tones = new Set(
72806
+ records.map((record4) => {
72807
+ const state = residentStateTone(residentLive(rec(record4)).state);
72808
+ return state === "green" && residentImageReport(record4) === "pending" ? "amber" : state;
72809
+ })
72810
+ );
72667
72811
  if (tones.has("red")) return "red";
72668
72812
  if (tones.has("amber")) return "amber";
72669
72813
  return tones.size === 1 && tones.has("green") ? "green" : "grey";
@@ -72676,6 +72820,10 @@ function residentLive(record4) {
72676
72820
  if (typeof live.error === "string") return { ...live, state: "unreachable", reason: live.error };
72677
72821
  return { ...live, state: str8(live.state) || "unknown", reason: str8(live.reason) };
72678
72822
  }
72823
+ function residentImageReport(record4) {
72824
+ const report2 = residentLive(record4).imageReport;
72825
+ return report2 === "current" || report2 === "pending" ? report2 : "unknown";
72826
+ }
72679
72827
  var RESIDENT_SLUG_RE, str8, rec;
72680
72828
  var init_residentsModel = __esm({
72681
72829
  "../../src/channels/residentsModel.ts"() {
@@ -72806,7 +72954,8 @@ function serveResidentsFeed(source2, sink, onLive) {
72806
72954
  reading = true;
72807
72955
  void source2.listing().then((r) => {
72808
72956
  reading = false;
72809
- if (!("error" in r)) send(frame({ type: "residents", cap: r.cap, count: r.count, residents: r.residents }));
72957
+ if (!("error" in r))
72958
+ send(frame({ type: "residents", cap: r.cap, count: r.count, draining: r.draining, residents: r.residents }));
72810
72959
  if (queued) {
72811
72960
  queued = false;
72812
72961
  reread();
@@ -72856,7 +73005,7 @@ function serveResidentsFeed(source2, sink, onLive) {
72856
73005
  for (const off of watched2.values()) off();
72857
73006
  watched2.clear();
72858
73007
  });
72859
- onLive?.();
73008
+ onLive?.(reread);
72860
73009
  }
72861
73010
  var ATTACH_SPAN;
72862
73011
  var init_residentsFeed = __esm({
@@ -72904,7 +73053,7 @@ async function readListing(client) {
72904
73053
  return { status: 502, reason: `resident Worker answered ${r.status} to /residents: ${reason2}` };
72905
73054
  }
72906
73055
  const residents = Array.isArray(r.data.residents) ? r.data.residents : [];
72907
- return { listing: { cap: r.data.cap, count: r.data.count, residents } };
73056
+ return { listing: { cap: r.data.cap, count: r.data.count, draining: r.data.draining, residents } };
72908
73057
  }
72909
73058
  function createResidentsViewHandler(deps) {
72910
73059
  const { client, page: page2, runs } = deps;
@@ -72936,7 +73085,11 @@ function createResidentsViewHandler(deps) {
72936
73085
  }
72937
73086
  },
72938
73087
  nodeSseSink(req, res),
72939
- () => startSseHeartbeat(req, res)
73088
+ (reread) => {
73089
+ startSseHeartbeat(req, res);
73090
+ const timer = setInterval(reread, RESIDENT_LISTING_POLL_MS);
73091
+ req.on("close", () => clearInterval(timer));
73092
+ }
72940
73093
  );
72941
73094
  return true;
72942
73095
  }
@@ -72964,6 +73117,7 @@ function createResidentsViewHandler(deps) {
72964
73117
  page: "residents",
72965
73118
  cap: listing.cap,
72966
73119
  count: listing.count,
73120
+ draining: listing.draining,
72967
73121
  residents: listing.residents,
72968
73122
  now: now(),
72969
73123
  runs: live
@@ -72991,6 +73145,7 @@ var init_residentsView = __esm({
72991
73145
  "../../src/channels/residentsView.ts"() {
72992
73146
  "use strict";
72993
73147
  init_authz();
73148
+ init_budgets();
72994
73149
  init_requestTrace();
72995
73150
  init_sse();
72996
73151
  init_viewer();
@@ -74934,6 +75089,9 @@ async function reclaimRuns(opts) {
74934
75089
  const recorded = row2.state.finalStatus;
74935
75090
  status3 = typeof recorded === "string" && TERMINAL.has(recorded) ? recorded : "completed";
74936
75091
  why = "replied before the previous generation died";
75092
+ } else if (row2.stop === "hard" && !row2.meta.hosted && hostingOf(row2.state) === void 0) {
75093
+ status3 = "stopped_hard";
75094
+ why = "hard stop requested before the previous generation ended";
74937
75095
  } else if (run2.reclaimedFrom === "attaching") {
74938
75096
  if (typeof row2.meta.request === "object" && row2.meta.request !== null) {
74939
75097
  outcome.resumable.push({ kind: "restart", row: row2, reclaimedFrom: "attaching", inbox: run2.inbox });
@@ -76680,6 +76838,7 @@ ${foldThreadEvents(folded)}` };
76680
76838
  const tag = {
76681
76839
  parentInstanceId: instance.id,
76682
76840
  idempotencyKey: key,
76841
+ ...instance.grant?.costCapUsd !== void 0 ? { costCapUsd: instance.grant.costCapUsd } : {},
76683
76842
  ...(row2?.branch ?? instance.branch) !== void 0 ? { branch: row2?.branch ?? instance.branch } : {},
76684
76843
  ...row2?.recovery !== void 0 ? { transportWorkflowId: row2.recovery.workflowId } : {},
76685
76844
  ...row2?.recovery !== void 0 && row2.publication !== void 0 ? {