@coreplane/switchboard 1.250.0 → 1.252.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 (79) hide show
  1. package/dist/assets/config/config.example.yaml +13 -2
  2. package/dist/assets/deploy/cloudflare/preflight.mjs +19 -21
  3. package/dist/assets/deploy/cloudflare/worker.ts +6 -3
  4. package/dist/assets/deploy/cloudflare-memory/worker.ts +108 -12
  5. package/dist/assets/deploy/cloudflare-resident/memoryGuard.ts +212 -0
  6. package/dist/assets/deploy/cloudflare-resident/refresh.ts +1 -1
  7. package/dist/assets/deploy/cloudflare-resident/worker.ts +317 -56
  8. package/dist/assets/deploy/cloudflare-sandbox/Dockerfile +4 -2
  9. package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.d.mts +31 -0
  10. package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.mjs +119 -0
  11. package/dist/assets/package-lock.json +3 -3
  12. package/dist/assets/package.json +3 -2
  13. package/dist/assets/project.json +13 -9
  14. package/dist/assets/source.json +3 -3
  15. package/dist/assets/src/agents/registry.ts +5 -5
  16. package/dist/assets/src/core/authz/policy.ts +4 -0
  17. package/dist/assets/src/core/authz/resource.ts +6 -2
  18. package/dist/assets/src/core/authz/types.ts +2 -0
  19. package/dist/assets/src/core/budgets.ts +22 -0
  20. package/dist/assets/src/core/coordinator/contract.ts +42 -0
  21. package/dist/assets/src/core/coordinator/driver.ts +150 -12
  22. package/dist/assets/src/core/drain.ts +50 -0
  23. package/dist/assets/src/core/memory/engine.ts +98 -0
  24. package/dist/assets/src/core/memory/scorer.ts +12 -4
  25. package/dist/assets/src/core/memory/types.ts +69 -12
  26. package/dist/assets/src/core/modelCard.ts +51 -7
  27. package/dist/assets/src/core/modelPricing.ts +111 -1
  28. package/dist/assets/src/core/modelProxy/usage.ts +88 -0
  29. package/dist/assets/src/core/modelRegistry.ts +15 -1
  30. package/dist/assets/src/core/provider.ts +49 -0
  31. package/dist/assets/src/core/refusal.ts +6 -6
  32. package/dist/assets/src/core/reviewVerdict.ts +4 -0
  33. package/dist/assets/src/core/runEvents.ts +51 -2
  34. package/dist/assets/src/core/runFriction.ts +7 -2
  35. package/dist/assets/src/core/runLedger/types.ts +23 -4
  36. package/dist/assets/src/core/runRecord.ts +16 -0
  37. package/dist/assets/src/core/runUsage.ts +67 -13
  38. package/dist/assets/src/core/schedules.ts +3 -0
  39. package/dist/assets/src/core/ship/contract.ts +45 -5
  40. package/dist/assets/src/core/ship/coordinator.ts +441 -48
  41. package/dist/assets/src/core/ship/renewal.ts +10 -5
  42. package/dist/assets/src/core/trace/attrs.ts +24 -0
  43. package/dist/assets/src/core/types.ts +5 -5
  44. package/dist/assets/src/core/verbosity.ts +48 -0
  45. package/dist/assets/src/deploy/liveGate.ts +40 -13
  46. package/dist/assets/src/deploy/restart.ts +11 -12
  47. package/dist/assets/src/execution/residentDepCache.ts +50 -1
  48. package/dist/assets/src/execution/residentDepsStore.ts +40 -2
  49. package/dist/assets/src/execution/residentRefresh.ts +55 -3
  50. package/dist/assets/src/execution/residentSteps.ts +4 -0
  51. package/dist/assets/src/execution/sandboxErrors.ts +8 -0
  52. package/dist/assets/web/dist/.vite/manifest.json +58 -52
  53. package/dist/assets/web/dist/assets/DeliveryPage-3ELQWM0r.js +1 -0
  54. package/dist/assets/web/dist/assets/{HomePage-AnycA57D.js → HomePage-BG_ok-K2.js} +2 -2
  55. package/dist/assets/web/dist/assets/{PendingTurnRow-BuRre8it.js → PendingTurnRow-ChCQOLgZ.js} +1 -1
  56. package/dist/assets/web/dist/assets/{ResidentDetailPage-Cb3sFkfj.js → ResidentDetailPage-C9y3nbo8.js} +1 -1
  57. package/dist/assets/web/dist/assets/{ResidentsIndexPage-BZ6n6UxF.js → ResidentsIndexPage-i1RG9e7g.js} +1 -1
  58. package/dist/assets/web/dist/assets/RunFoldRow-D3wVpzBa.js +1 -0
  59. package/dist/assets/web/dist/assets/RunRoutePage-B3IirUVi.js +9 -0
  60. package/dist/assets/web/dist/assets/RunsIndexPage-DiFmtGaJ.js +1 -0
  61. package/dist/assets/web/dist/assets/{ScheduledPage-CBUxbeqN.js → ScheduledPage-DvYwM2TE.js} +1 -1
  62. package/dist/assets/web/dist/assets/{SettingsPage-CBTnZ9Qv.js → SettingsPage-Bo6yCyXZ.js} +1 -1
  63. package/dist/assets/web/dist/assets/{StatusDot-BnRjWzFN.js → StatusDot-CAfS1AUi.js} +1 -1
  64. package/dist/assets/web/dist/assets/{Tooltip-Brge0wnd.js → Tooltip-tZoum_T-.js} +1 -1
  65. package/dist/assets/web/dist/assets/{UnitRoutePage-o6sLju16.js → UnitRoutePage-BmdOHwNn.js} +1 -1
  66. package/dist/assets/web/dist/assets/budgets-CbIyPAER.js +1 -0
  67. package/dist/assets/web/dist/assets/{dist-rgAhsmE-.js → dist-DfbEpHXR.js} +1 -1
  68. package/dist/assets/web/dist/assets/indexRow-BT0cPVRw.js +1 -0
  69. package/dist/assets/web/dist/assets/{main-CeRuGONy.js → main-5Gm_1Gv8.js} +2 -2
  70. package/dist/assets/web/dist/assets/sseReplay-DmyMXfRC.js +11 -0
  71. package/dist/cli.js +3319 -1015
  72. package/package.json +1 -1
  73. package/dist/assets/deploy/cloudflare-sandbox/runtime-supervisor.sh +0 -68
  74. package/dist/assets/web/dist/assets/DeliveryPage-CIfBiINK.js +0 -1
  75. package/dist/assets/web/dist/assets/RunFoldRow-BRXkjkgO.js +0 -1
  76. package/dist/assets/web/dist/assets/RunRoutePage-psSMI3fN.js +0 -9
  77. package/dist/assets/web/dist/assets/RunsIndexPage-68YT_RWt.js +0 -1
  78. package/dist/assets/web/dist/assets/indexRow-BmK74Vp1.js +0 -1
  79. package/dist/assets/web/dist/assets/sseReplay-DXC7kGbN.js +0 -9
@@ -23,6 +23,17 @@ export interface ModelUsage {
23
23
  outputTokens: number;
24
24
  cacheReadTokens: number;
25
25
  cacheWriteTokens: number;
26
+ /** The model's dollars summed from its turns' own `usd` attrs (the proxy's
27
+ * meter row, model-proxy item 6): a number when every turn that counted
28
+ * tokens carried one, null when such a turn lacked it (unpriced — a sum
29
+ * that left counted tokens out would understate the model; a turn that
30
+ * counted none, an errored call or a retry, leaves nothing out), absent on
31
+ * a record whose turns predate the meter row (the price table then prices
32
+ * the tokens, costs.md item 4b). */
33
+ usd?: number | null;
34
+ /** The distinct `priceSource` words the turns carried, sorted — what the
35
+ * by-model view names as the source. Absent when no turn carried one. */
36
+ priceSources?: string[];
26
37
  }
27
38
 
28
39
  export interface RunUsage {
@@ -46,6 +57,14 @@ export const emptyUsage = (): RunUsage => ({ turns: 0, byModel: {} });
46
57
  /** The run's usage from its events: one `model.turn` span end per provider call. */
47
58
  export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
48
59
  const usage = emptyUsage();
60
+ // The meter row per model (model-proxy item 6): the turns' own dollars and
61
+ // sources. `usd` sums only when every turn that counted tokens carried one —
62
+ // a turn that counted none (an upstream error, a retry the harness's SDK
63
+ // spent, a stream broken before its usage) has nothing a sum could leave
64
+ // out, so it never turns the model unpriced; a model none of whose turns
65
+ // carried price attrs stays without the fields (a record from before the
66
+ // meter row — the table prices it, costs.md item 4b).
67
+ const priced = new Map<string, { usd: number; pricedTurns: number; tokenTurns: number; sources: Set<string> }>();
49
68
  for (const e of events) {
50
69
  if (e.type !== "span_end" || e.name !== MODEL_TURN) continue;
51
70
  const attrs = (e.attrs ?? {}) as Record<string, unknown>;
@@ -57,6 +76,8 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
57
76
  cacheReadTokens: 0,
58
77
  cacheWriteTokens: 0,
59
78
  };
79
+ const counted =
80
+ num(attrs.inputTokens) + num(attrs.outputTokens) + num(attrs.cacheReadTokens) + num(attrs.cacheWriteTokens);
60
81
  m.turns += 1;
61
82
  m.inputTokens += num(attrs.inputTokens);
62
83
  m.outputTokens += num(attrs.outputTokens);
@@ -64,38 +85,71 @@ export function usageOfEvents(events: readonly RunEvent[]): RunUsage {
64
85
  m.cacheWriteTokens += num(attrs.cacheWriteTokens);
65
86
  usage.byModel[model] = m;
66
87
  usage.turns += 1;
88
+ const hasUsd = typeof attrs.usd === "number" && Number.isFinite(attrs.usd);
89
+ const hasSource = typeof attrs.priceSource === "string" && attrs.priceSource !== "";
90
+ const p = priced.get(model) ?? { usd: 0, pricedTurns: 0, tokenTurns: 0, sources: new Set<string>() };
91
+ if (counted > 0) p.tokenTurns += 1;
92
+ if (hasUsd) {
93
+ p.usd += attrs.usd as number;
94
+ p.pricedTurns += 1;
95
+ }
96
+ if (hasSource) p.sources.add(attrs.priceSource as string);
97
+ priced.set(model, p);
98
+ }
99
+ for (const [model, p] of priced) {
100
+ if (p.pricedTurns === 0 && p.sources.size === 0) continue;
101
+ const m = usage.byModel[model]!;
102
+ m.usd = p.pricedTurns >= p.tokenTurns ? p.usd : null;
103
+ if (p.sources.size > 0) m.priceSources = [...p.sources].sort();
67
104
  }
68
105
  return usage;
69
106
  }
70
107
 
108
+ /** The two sides' `usd` folded: a sum when both carry one, null when either
109
+ * has an unpriced turn, absent when either predates the meter row (the whole
110
+ * cell then prices from the table, never half a figure). */
111
+ const foldUsd = (a: ModelUsage["usd"], b: ModelUsage["usd"]): ModelUsage["usd"] =>
112
+ a === undefined || b === undefined ? undefined : a === null || b === null ? null : a + b;
113
+
71
114
  export function addUsage(a: RunUsage, b: RunUsage): RunUsage {
72
115
  const out: RunUsage = { turns: a.turns + b.turns, byModel: {} };
73
116
  for (const src of [a.byModel, b.byModel]) {
74
117
  for (const [model, m] of Object.entries(src)) {
75
- const acc = out.byModel[model] ?? {
76
- turns: 0,
77
- inputTokens: 0,
78
- outputTokens: 0,
79
- cacheReadTokens: 0,
80
- cacheWriteTokens: 0,
81
- };
118
+ const acc = out.byModel[model];
119
+ if (!acc) {
120
+ out.byModel[model] = { ...m, ...(m.priceSources ? { priceSources: [...m.priceSources] } : {}) };
121
+ continue;
122
+ }
82
123
  acc.turns += m.turns;
83
124
  acc.inputTokens += m.inputTokens;
84
125
  acc.outputTokens += m.outputTokens;
85
126
  acc.cacheReadTokens += m.cacheReadTokens;
86
127
  acc.cacheWriteTokens += m.cacheWriteTokens;
87
- out.byModel[model] = acc;
128
+ const usd = foldUsd(acc.usd, m.usd);
129
+ if (usd === undefined) delete acc.usd;
130
+ else acc.usd = usd;
131
+ const sources = new Set([...(acc.priceSources ?? []), ...(m.priceSources ?? [])]);
132
+ if (sources.size > 0) acc.priceSources = [...sources].sort();
88
133
  }
89
134
  }
90
135
  return out;
91
136
  }
92
137
 
93
- const isModelUsage = (v: unknown): v is ModelUsage =>
94
- typeof v === "object" &&
95
- v !== null &&
96
- (["turns", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"] as const).every(
97
- (k) => typeof (v as Record<string, unknown>)[k] === "number",
138
+ const isModelUsage = (v: unknown): v is ModelUsage => {
139
+ if (typeof v !== "object" || v === null) return false;
140
+ const m = v as Record<string, unknown>;
141
+ if (
142
+ !["turns", "inputTokens", "outputTokens", "cacheReadTokens", "cacheWriteTokens"].every(
143
+ (k) => typeof m[k] === "number",
144
+ )
145
+ )
146
+ return false;
147
+ if (m.usd !== undefined && m.usd !== null && typeof m.usd !== "number") return false;
148
+ return (
149
+ m.priceSources === undefined ||
150
+ (Array.isArray(m.priceSources) && m.priceSources.every((s) => typeof s === "string"))
98
151
  );
152
+ };
99
153
 
100
154
  export function isRunUsage(v: unknown): v is RunUsage {
101
155
  if (typeof v !== "object" || v === null) return false;
@@ -379,6 +379,9 @@ export interface WatchdogSummary {
379
379
  action?: unknown;
380
380
  error?: string;
381
381
  disk?: unknown;
382
+ /** The resident's last memory reading (`{usedBytes, capBytes, percent, …}`,
383
+ * resident-repos.md item 70) when it has one. */
384
+ memory?: unknown;
382
385
  /** What the pass did about the resident's refresh Workflow instance: `{id, action, why}`. */
383
386
  instance?: unknown;
384
387
  }>;
@@ -439,17 +439,57 @@ export const TIMEOUT_ON_LONG_COMMANDS =
439
439
  "State a timeout on any command you expect to run longer than a minute: a timeout that reaches past the loop's " +
440
440
  "end is refused before the command runs, never cut midway.";
441
441
 
442
+ /** The fast gates a plan child runs before every push, named one by one and
443
+ * each scoped to the changed set (agent-ship item 13; agent-coding item 9).
444
+ * "Its cheapest proving checks" left the choice to the child, and children
445
+ * chose wrong in both directions: prettier was reported clean while
446
+ * `format:check` was red, hygiene imprints reached CI that `hygiene:check`
447
+ * would have caught locally — and children ran the whole suite and the whole
448
+ * typecheck on the shared resident, minutes each call, time-sliced against
449
+ * every other run. So the gates are the changed-set forms, the full runs are
450
+ * said to be CI's alone in the same breath, and each gate is a receipt — the
451
+ * exit line goes into the PR description's validation table as the row's proof
452
+ * (the handoff has no verified list; parseHandoff carries deviations, followUps,
453
+ * unproven and landed), and a gate the child could not run goes under the
454
+ * handoff's unproven list, never claimed clean. The tests compare against the
455
+ * pull request's own base — `origin/<base>` — which the render substitutes
456
+ * from the rebase's `onto` when the contract knows it. */
457
+ /** The changed-set test command as the contract renders it before the base is
458
+ * known; the render substitutes the unit's base for `<base>` (`renderFirstInstruction`). */
459
+ export const CHANGED_SET_TEST_PLACEHOLDER = "`npx vitest run --changed origin/<base>`";
460
+
461
+ export const FAST_GATES_BEFORE_PUSH =
462
+ "The fast gates, before every push — each scoped to the changed set, never the whole project: " +
463
+ `${CHANGED_SET_TEST_PLACEHOLDER} (the pull request's base; or the touched test files), ` +
464
+ "`tsc --noEmit -p` the touched tsconfig under `NODE_OPTIONS=--max-old-space-size=6144`, " +
465
+ "`npx prettier --check` on the changed files, `npm run hygiene:check` and `npm run specs:check` — " +
466
+ "then your judgement on what else this change needs, not a longer checklist. Every CI pipeline runs the " +
467
+ "tests, the types, the formatting and the full verification on your push, so you never run them again: " +
468
+ "you validate and fix your own change before pushing, at the changed-set scope. Passing the full test suite " +
469
+ "and the full typecheck is NOT part of your criteria: CI is that gate and the only place they run — on a " +
470
+ "shared resident they cost minutes that every other run pays for. " +
471
+ "Paste each command's exit line into the PR description's validation table as the row's proof; a gate you " +
472
+ "could not run goes under the handoff's unproven list and is never claimed clean.";
473
+
442
474
  function renderFirstInstruction(rebase: ChildContract["rebase"]): string {
443
475
  const branch = rebase.branch ? `\`${rebase.branch}\`` : "the unit's branch";
444
476
  const onto = rebase.onto ? `\`${rebase.onto}\`` : "the merged parent";
477
+ // the changed-set test run compares against the unit's own base, which the
478
+ // contract knows as the rebase target; unknown, the placeholder stands
479
+ // split/join, never String.replace: a `$` in a branch name is literal text here
480
+ const gates = rebase.onto
481
+ ? FAST_GATES_BEFORE_PUSH.split(`${CHANGED_SET_TEST_PLACEHOLDER} (the pull request's base; or`).join(
482
+ `\`npx vitest run --changed origin/${rebase.onto}\` (or`,
483
+ )
484
+ : FAST_GATES_BEFORE_PUSH;
445
485
  return (
446
486
  `Rebase ${branch} onto ${onto} before any other work — the parent unit has merged and the base has moved; ` +
447
487
  `the only writes are your own on that branch. A conflict ends the unit: report it as the handoff and stop. ` +
448
- `Push the branch as soon as the change exists and its cheapest proving checks pass — before the project's ` +
449
- `full verification, which runs after that push with any fix as a further commit; an unpushed tree does not ` +
450
- `survive the run's end. Right before each push, fetch ${onto} again and rebase once more if it moved while ` +
451
- `you worked, so the pull request is not born conflicting. At the wind-down note, commit and push what compiles, ` +
452
- `say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
488
+ `Push the branch as soon as the change exists and the fast gates pass — the project's full verification ` +
489
+ `is CI's gate, run there after the push with any fix as a further commit; an unpushed tree does not ` +
490
+ `survive the run's end. ${gates} Right before each push, fetch ${onto} again and rebase ` +
491
+ `once more if it moved while you worked, so the pull request is not born conflicting. At the wind-down note, ` +
492
+ `commit and push what compiles, say what does not, then answer. ${TIMEOUT_ON_LONG_COMMANDS}`
453
493
  );
454
494
  }
455
495