catherd-cli 1.5.0 → 2.0.0-beta.20261022113309
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/README.md +164 -208
- package/THIRD_PARTY_NOTICES.md +217 -35
- package/catalog/jev.json +27 -1
- package/catalog/models.json +56 -247
- package/package.json +6 -8
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/bin/catherd-mcp +1 -1
- package/plugin/commands/catherd.md +2 -2
- package/plugin/skills/catherd/SKILL.md +97 -421
- package/plugin/skills/catherd-setup/SKILL.md +20 -19
- package/src/adapters/all.ts +0 -4
- package/src/adapters/backend.ts +1 -2
- package/src/adapters/cursor/home.ts +1 -1
- package/src/adapters/cursor/index.ts +4 -4
- package/src/cli.ts +22 -27
- package/src/domain/bars.ts +33 -107
- package/src/domain/blocked.ts +72 -0
- package/src/domain/branch.ts +51 -0
- package/src/domain/brief.ts +21 -6
- package/src/domain/catalog.ts +69 -324
- package/src/domain/cost.ts +15 -30
- package/src/domain/errors.ts +2 -2
- package/src/domain/fail-lines.ts +26 -0
- package/src/domain/failover.ts +37 -87
- package/src/domain/findings.ts +47 -0
- package/src/domain/gate-brief.ts +1 -0
- package/src/domain/ids.ts +13 -2
- package/src/domain/jev.ts +47 -2
- package/src/domain/judge.ts +124 -0
- package/src/domain/lane.ts +22 -3
- package/src/domain/milestone-memory.ts +53 -0
- package/src/domain/notice.ts +25 -8
- package/src/domain/notify.ts +32 -0
- package/src/domain/plan-doc.ts +198 -0
- package/src/domain/plan-lint.ts +141 -0
- package/src/domain/profile-rules.ts +101 -160
- package/src/domain/profile.ts +116 -63
- package/src/domain/record.ts +53 -2
- package/src/domain/role-prompts.ts +131 -45
- package/src/domain/role-scope.ts +4 -34
- package/src/domain/role-tools.ts +2 -2
- package/src/domain/roles.ts +2 -0
- package/src/domain/route.ts +17 -0
- package/src/domain/rules.ts +33 -0
- package/src/domain/run-events.ts +298 -0
- package/src/domain/run-machine.ts +668 -0
- package/src/domain/run-state.ts +1310 -0
- package/src/domain/scorecard.ts +197 -0
- package/src/domain/select.ts +151 -146
- package/src/domain/slopalytics.ts +72 -0
- package/src/entry/answer-command.ts +39 -0
- package/src/entry/catalog-command.ts +25 -174
- package/src/entry/check-bin.ts +17 -0
- package/src/entry/cli-kit.ts +1 -1
- package/src/entry/daemon-bin.ts +22 -0
- package/src/entry/glyphs.ts +15 -2
- package/src/entry/init-command.ts +18 -68
- package/src/entry/knowledge-command.ts +45 -4
- package/src/entry/lock-command.ts +6 -7
- package/src/entry/mcp/role-server.ts +18 -0
- package/src/entry/mcp/server.ts +13 -40
- package/src/entry/mcp/session-tools.ts +149 -0
- package/src/entry/panel/openapi.ts +189 -0
- package/src/entry/panel/server.ts +410 -0
- package/src/entry/panel-command.ts +68 -0
- package/src/entry/profile-command.ts +10 -14
- package/src/entry/role-cli-command.ts +47 -3
- package/src/entry/runs-command.ts +3 -19
- package/src/entry/scorecard-command.ts +187 -0
- package/src/entry/supervise-bin.ts +1 -1
- package/src/infra/env.ts +0 -2
- package/src/infra/git.ts +64 -0
- package/src/infra/heavy-lock.ts +45 -7
- package/src/infra/launch.ts +63 -7
- package/src/infra/memory.ts +45 -0
- package/src/infra/ntfy.ts +40 -0
- package/src/infra/open-url.ts +42 -0
- package/src/infra/paths.ts +2 -2
- package/src/infra/slopalytics.ts +30 -0
- package/src/infra/store.ts +6 -1
- package/src/services/admission.ts +24 -9
- package/src/services/backends.ts +4 -6
- package/src/services/capture.ts +1 -22
- package/src/services/catalog-service.ts +155 -262
- package/src/services/credentials.ts +4 -20
- package/src/services/daemon.ts +181 -0
- package/src/services/dispatch-service.ts +5 -1
- package/src/services/doctor-backends.ts +1 -1
- package/src/services/doctor-checks.ts +47 -0
- package/src/services/doctor.ts +4 -9
- package/src/services/failover-rungs.ts +109 -0
- package/src/services/finalize.ts +12 -0
- package/src/services/gate-env.ts +1 -2
- package/src/services/gate-service.ts +21 -6
- package/src/services/jev-service.ts +2 -4
- package/src/services/lane-service.ts +34 -3
- package/src/services/milestone-check.ts +433 -0
- package/src/services/milestone-tree.ts +170 -0
- package/src/services/milestones.ts +35 -8
- package/src/services/notifier.ts +69 -2
- package/src/services/notify-service.ts +26 -0
- package/src/services/panel-setup.ts +116 -0
- package/src/services/plan-input.ts +151 -0
- package/src/services/ports.ts +30 -4
- package/src/services/preflight.ts +82 -8
- package/src/services/profile-service.ts +30 -3
- package/src/services/profile-store.ts +8 -3
- package/src/services/protocol.ts +4 -0
- package/src/services/provenance.ts +31 -90
- package/src/services/questions.ts +9 -0
- package/src/services/reconcile.ts +11 -0
- package/src/services/routing-service.ts +26 -15
- package/src/services/run-briefs.ts +477 -0
- package/src/services/run-client.ts +217 -0
- package/src/services/run-effects.ts +837 -0
- package/src/services/run-events-store.ts +174 -0
- package/src/services/run-evidence.ts +1 -1
- package/src/services/run-read.ts +221 -0
- package/src/services/run-report.ts +176 -0
- package/src/services/run-service.ts +55 -6
- package/src/services/run-store.ts +54 -7
- package/src/services/scorecard-metrics.ts +86 -0
- package/src/services/scorecard.ts +382 -0
- package/src/services/summary.ts +27 -3
- package/src/services/tree-path.ts +27 -0
- package/src/services/workspace-admission.ts +5 -13
- package/src/services/workspace-service.ts +5 -0
- package/web/dist/assets/Brand-PKlUX5TE.js +1 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.br +0 -0
- package/web/dist/assets/Brand-PKlUX5TE.js.gz +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js +1 -0
- package/web/dist/assets/Modal-Dxc5so53.js.br +0 -0
- package/web/dist/assets/Modal-Dxc5so53.js.gz +0 -0
- package/web/dist/assets/Pressable-C8-T6Ed_.js +1 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js +153 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.br +0 -0
- package/web/dist/assets/StatusChip-B5xfI_Mn.js.gz +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js +1 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.br +0 -0
- package/web/dist/assets/_app-Ea5cpyKe.js.gz +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js +1 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.br +0 -0
- package/web/dist/assets/_runId-pTG0fXVo.js.gz +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js +1 -0
- package/web/dist/assets/drawer-Du3cdlok.js.br +0 -0
- package/web/dist/assets/drawer-Du3cdlok.js.gz +0 -0
- package/web/dist/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
- package/web/dist/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
- package/web/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
- package/web/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
- package/web/dist/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
- package/web/dist/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
- package/web/dist/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
- package/web/dist/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
- package/web/dist/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
- package/web/dist/assets/index-B6GiwDkh.js +11 -0
- package/web/dist/assets/index-B6GiwDkh.js.br +0 -0
- package/web/dist/assets/index-B6GiwDkh.js.gz +0 -0
- package/web/dist/assets/index-C5IbylvM.css +2 -0
- package/web/dist/assets/index-C5IbylvM.css.br +0 -0
- package/web/dist/assets/index-C5IbylvM.css.gz +0 -0
- package/web/dist/assets/label-DFSYBc62.js +1 -0
- package/web/dist/assets/label-DFSYBc62.js.br +0 -0
- package/web/dist/assets/label-DFSYBc62.js.gz +0 -0
- package/web/dist/assets/link-D8EcKZUt.js +1 -0
- package/web/dist/assets/link-D8EcKZUt.js.br +0 -0
- package/web/dist/assets/link-D8EcKZUt.js.gz +0 -0
- package/web/dist/assets/login-DaN5HPPW.js +1 -0
- package/web/dist/assets/login-DaN5HPPW.js.br +0 -0
- package/web/dist/assets/login-DaN5HPPW.js.gz +0 -0
- package/web/dist/assets/query-Cu--ccT3.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js +1 -0
- package/web/dist/assets/questions-shoO9OVj.js.br +0 -0
- package/web/dist/assets/questions-shoO9OVj.js.gz +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js +12 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.br +0 -0
- package/web/dist/assets/react-query.gen-Bu2yV-qJ.js.gz +0 -0
- package/web/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/web/dist/assets/run-status-C0DRuV7U.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js +1 -0
- package/web/dist/assets/runs-C3rg2zZc.js.br +0 -0
- package/web/dist/assets/runs-C3rg2zZc.js.gz +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js +1 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.br +0 -0
- package/web/dist/assets/setup-5I4Vl9fR.js.gz +0 -0
- package/web/dist/assets/table-BOxXhqS4.js +1 -0
- package/web/dist/assets/table-BOxXhqS4.js.br +0 -0
- package/web/dist/assets/table-BOxXhqS4.js.gz +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js +1 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.br +0 -0
- package/web/dist/assets/textfield-BzRFkxBF.js.gz +0 -0
- package/web/dist/assets/time--9kSQbbp.js +1 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js +5 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.br +0 -0
- package/web/dist/assets/use-css-variable-CpnN9WtA.js.gz +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js +1 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.br +0 -0
- package/web/dist/assets/useMutation-CVPKlR34.js.gz +0 -0
- package/web/dist/index.html +15 -0
- package/catalog/ATTRIBUTION.md +0 -44
- package/catalog/scores.json +0 -3202
- package/catalog/sources.json +0 -97
- package/src/adapters/antigravity/events.ts +0 -131
- package/src/adapters/antigravity/home.ts +0 -46
- package/src/adapters/antigravity/index.ts +0 -335
- package/src/adapters/antigravity/models.ts +0 -37
- package/src/adapters/grok/events.ts +0 -112
- package/src/adapters/grok/home.ts +0 -187
- package/src/adapters/grok/index.ts +0 -358
- package/src/adapters/grok/models.ts +0 -48
- package/src/domain/calibration.ts +0 -132
- package/src/domain/sources.ts +0 -144
- package/src/entry/mcp/dispatch-tools.ts +0 -52
- package/src/entry/mcp/lane-tools.ts +0 -131
- package/src/entry/mcp/protocol-tools.ts +0 -63
- package/src/entry/mcp/push-tools.ts +0 -16
- package/src/entry/mcp/run-tools.ts +0 -164
- package/src/entry/mcp/setup-tools.ts +0 -109
- package/src/entry/mcp/workspace-tools.ts +0 -142
- package/src/entry/tui/commands.ts +0 -563
- package/src/entry/tui/effects.ts +0 -385
- package/src/entry/tui/fixtures.ts +0 -637
- package/src/entry/tui/profile-edits.ts +0 -146
- package/src/entry/tui/profile-tree.ts +0 -429
- package/src/entry/tui/providers/app.tsx +0 -162
- package/src/entry/tui/providers/boundary.tsx +0 -57
- package/src/entry/tui/providers/data.tsx +0 -160
- package/src/entry/tui/providers/keymap.tsx +0 -166
- package/src/entry/tui/providers/root.tsx +0 -45
- package/src/entry/tui/providers/theme.tsx +0 -17
- package/src/entry/tui/providers/toast.tsx +0 -77
- package/src/entry/tui/run.tsx +0 -114
- package/src/entry/tui/state.ts +0 -400
- package/src/entry/tui/stories.ts +0 -114
- package/src/entry/tui/text.ts +0 -130
- package/src/entry/tui/theme.ts +0 -140
- package/src/entry/tui/views/app.tsx +0 -208
- package/src/entry/tui/views/dialogs.tsx +0 -41
- package/src/entry/tui/views/profile-actions.ts +0 -331
- package/src/entry/tui/views/profiles.tsx +0 -520
- package/src/entry/tui/views/runs.tsx +0 -619
- package/src/entry/tui/views/save-dialog.tsx +0 -344
- package/src/entry/tui/views/status.tsx +0 -213
- package/src/entry/tui/widgets/chrome.tsx +0 -114
- package/src/entry/tui/widgets/dialog-confirm.tsx +0 -58
- package/src/entry/tui/widgets/dialog-prompt.tsx +0 -52
- package/src/entry/tui/widgets/dialog-select.tsx +0 -212
- package/src/entry/tui/widgets/dialog.tsx +0 -68
- package/src/entry/tui/widgets/line.tsx +0 -92
- package/src/entry/tui/widgets/list.tsx +0 -197
- package/src/entry/tui/widgets/spinner.tsx +0 -49
- package/src/entry/tui/widgets/toast.tsx +0 -62
- package/src/infra/sources/arena.ts +0 -95
- package/src/infra/sources/artificial-analysis.ts +0 -119
- package/src/infra/sources/cache.ts +0 -126
- package/src/infra/sources/csv.ts +0 -38
- package/src/infra/sources/epoch.ts +0 -82
- package/src/infra/sources/http.ts +0 -75
- package/src/infra/sources/litellm.ts +0 -58
- package/src/infra/sources/models-dev.ts +0 -59
- package/src/infra/sources/openrouter-endpoints.ts +0 -52
- package/src/infra/sources/openrouter-models.ts +0 -60
- package/src/infra/sources/rows.ts +0 -38
- package/src/infra/sources/vectara.ts +0 -69
- package/src/infra/sources/zip.ts +0 -48
- package/src/services/catalog-refresh.ts +0 -201
- package/src/services/doctor-sources.ts +0 -76
- package/src/services/runs-page.ts +0 -290
- package/src/services/source-derive.ts +0 -397
- package/src/services/source-sync.ts +0 -312
- package/src/services/standins.ts +0 -238
- package/src/services/treat-likes.ts +0 -113
package/src/domain/profile.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import type { OrchestrationHost } from "./host.ts";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import type { Budget } from "./budget.ts";
|
|
4
|
+
import type { Bars } from "./bars.ts";
|
|
4
5
|
import { BILLING_KEYS, type BillingKey } from "./catalog.ts";
|
|
5
6
|
import { BILLING_MODES, type BillingMode, DEFAULT_BILLING } from "./cost.ts";
|
|
6
7
|
import { CatherdError } from "./errors.ts";
|
|
7
|
-
import { ADAPTER_IDS, parseRung, tryParseRung } from "./ids.ts";
|
|
8
|
+
import { ADAPTER_IDS, EFFORT_ORDER, type EffortWord, effortRank, parseRung, tryParseRung } from "./ids.ts";
|
|
9
|
+
import { DIFFICULTIES } from "./lane.ts";
|
|
8
10
|
import { ACCESS, type Access } from "./record.ts";
|
|
9
11
|
import { DEFAULT_ACCESS, ROLES, type Role } from "./roles.ts";
|
|
10
12
|
import { isPlain, slug } from "./util.ts";
|
|
@@ -46,6 +48,8 @@ const RoleDocSchema = z.looseObject({
|
|
|
46
48
|
defaultRung: z.string().optional(),
|
|
47
49
|
network: z.boolean().optional(),
|
|
48
50
|
timeouts: z.looseObject({ idleMin: positive.optional(), wallMin: positive.optional() }).optional(),
|
|
51
|
+
// plan 33: unknown, so a hand edit never makes the file unreadable; resolveProfile reads what it can
|
|
52
|
+
bars: z.unknown().optional(),
|
|
49
53
|
});
|
|
50
54
|
|
|
51
55
|
export const ProfileDocSchema = z.looseObject({
|
|
@@ -57,6 +61,9 @@ export const ProfileDocSchema = z.looseObject({
|
|
|
57
61
|
roles: z.record(z.string(), RoleDocSchema).optional(),
|
|
58
62
|
harness: z.record(z.string(), z.looseObject({ isolated: z.boolean().optional() })).optional(),
|
|
59
63
|
failover: z.record(z.string(), z.string()).optional(),
|
|
64
|
+
// unknown values, like `bars`: an entry that is no effort word never makes the file unreadable
|
|
65
|
+
maxEffort: z.record(z.string(), z.unknown()).optional(),
|
|
66
|
+
bars: z.unknown().optional(),
|
|
60
67
|
budget: z
|
|
61
68
|
.looseObject({ minutes: positive.optional(), tokens: positive.optional(), usd: positive.optional() })
|
|
62
69
|
.optional(),
|
|
@@ -66,9 +73,20 @@ export const ProfileDocSchema = z.looseObject({
|
|
|
66
73
|
.looseObject({ heavy: z.union([z.number().int().positive(), z.literal("cpus/2")]).optional() })
|
|
67
74
|
.optional(),
|
|
68
75
|
notify: z.array(z.string()).optional(),
|
|
76
|
+
// plan 35: unknown, so a hand edit never makes the file unreadable; resolveProfile reads what it can
|
|
77
|
+
limits: z.unknown().optional(),
|
|
69
78
|
});
|
|
70
79
|
export type ProfileDoc = z.infer<typeof ProfileDocSchema>;
|
|
71
80
|
|
|
81
|
+
/** plan 35: the profile's memory numbers for starting milestones side by side (megabytes). */
|
|
82
|
+
const MEMORY_KEYS = ["reserveMb", "perMilestoneMb", "floorMb"] as const;
|
|
83
|
+
type MemoryKey = (typeof MEMORY_KEYS)[number];
|
|
84
|
+
const positiveInt = (v: unknown): v is number => typeof v === "number" && Number.isInteger(v) && v > 0;
|
|
85
|
+
const storedMemory = (doc: ProfileDoc): Record<string, unknown> => {
|
|
86
|
+
const memory = isPlain(doc.limits) ? doc.limits.memory : undefined;
|
|
87
|
+
return isPlain(memory) ? memory : {};
|
|
88
|
+
};
|
|
89
|
+
|
|
72
90
|
const OBJECTIVES = ["cost", "speed"] as const;
|
|
73
91
|
const JEV_USES = ["auto", "off"] as const;
|
|
74
92
|
const known = <T extends string>(values: readonly T[], v: string | undefined): v is T =>
|
|
@@ -86,6 +104,16 @@ const STORED_FALLBACK = {
|
|
|
86
104
|
billing: (key: string): BillingMode => DEFAULT_BILLING[key as BillingKey] ?? "metered",
|
|
87
105
|
} as const;
|
|
88
106
|
|
|
107
|
+
const barOk = (k: string, v: unknown): boolean =>
|
|
108
|
+
(DIFFICULTIES as readonly string[]).includes(k) && typeof v === "number" && v >= 0 && v <= 100;
|
|
109
|
+
|
|
110
|
+
const isEffort = (v: unknown): v is EffortWord => typeof v === "string" && effortRank(v) >= 0;
|
|
111
|
+
|
|
112
|
+
/** The entries of a stored `bars` this catherd can read; anything else is not a bar. */
|
|
113
|
+
function readBars(v: unknown): Bars {
|
|
114
|
+
return isPlain(v) ? (Object.fromEntries(Object.entries(v).filter((e) => barOk(e[0], e[1]))) as Bars) : {};
|
|
115
|
+
}
|
|
116
|
+
|
|
89
117
|
/** A stored value this catherd does not know, with what it is read as. */
|
|
90
118
|
export interface UnknownValue {
|
|
91
119
|
path: string;
|
|
@@ -103,9 +131,25 @@ export function unknownValues(doc: ProfileDoc): UnknownValue[] {
|
|
|
103
131
|
check("jev.use", JEV_USES, doc.jev?.use, STORED_FALLBACK.jevUse);
|
|
104
132
|
for (const [k, v] of Object.entries(doc.billing ?? {}))
|
|
105
133
|
check(`billing.${k}`, BILLING_MODES, v, STORED_FALLBACK.billing(k));
|
|
134
|
+
for (const [k, v] of Object.entries(doc.maxEffort ?? {}))
|
|
135
|
+
if (!isEffort(v)) {
|
|
136
|
+
const value = typeof v === "string" ? v : JSON.stringify(v);
|
|
137
|
+
out.push({ path: `maxEffort.${k}`, value, readAs: "no ceiling" });
|
|
138
|
+
}
|
|
139
|
+
const checkBars = (path: string, v: unknown, readAs: string) => {
|
|
140
|
+
if (v === undefined) return;
|
|
141
|
+
if (!isPlain(v)) return void out.push({ path, value: JSON.stringify(v), readAs });
|
|
142
|
+
for (const [k, x] of Object.entries(v))
|
|
143
|
+
if (!barOk(k, x)) out.push({ path: `${path}.${k}`, value: String(x), readAs });
|
|
144
|
+
};
|
|
145
|
+
checkBars("bars", doc.bars, "the shipped percentile");
|
|
146
|
+
for (const role of ROLES) checkBars(`roles.${role}.bars`, doc.roles?.[role]?.bars, "the profile's bar");
|
|
106
147
|
for (const role of ROLES)
|
|
107
148
|
check(`roles.${role}.access`, ACCESS, doc.roles?.[role]?.access, STORED_FALLBACK.access);
|
|
108
149
|
for (const n of doc.notify ?? []) check("notify", NOTIFY, n, "skipped");
|
|
150
|
+
for (const [k, v] of Object.entries(storedMemory(doc)))
|
|
151
|
+
if (!(MEMORY_KEYS as readonly string[]).includes(k) || !positiveInt(v))
|
|
152
|
+
out.push({ path: `limits.memory.${k}`, value: JSON.stringify(v), readAs: "the default" });
|
|
109
153
|
return out;
|
|
110
154
|
}
|
|
111
155
|
|
|
@@ -120,6 +164,8 @@ export type RoleConfig = {
|
|
|
120
164
|
network?: false;
|
|
121
165
|
/** plan 23: this role's own idle and wall timeouts, over the profile's `timeouts` (a long gate, a slow model) */
|
|
122
166
|
timeouts?: { idleMin?: number; wallMin?: number };
|
|
167
|
+
/** plan 33: this role's own bar percentiles, over the profile's `bars` (decision 11) */
|
|
168
|
+
bars?: Bars;
|
|
123
169
|
};
|
|
124
170
|
|
|
125
171
|
/** The idle and wall timeouts a role runs under: its own `timeouts`, else the profile's. */
|
|
@@ -140,11 +186,17 @@ export interface Profile {
|
|
|
140
186
|
roles: Record<Role, RoleConfig>;
|
|
141
187
|
harness: Record<string, { isolated: boolean }>;
|
|
142
188
|
failover: Record<string, string>;
|
|
189
|
+
/** plan 33: `<backend>:<model>` → the highest effort routing starts it at; a model not named has no ceiling */
|
|
190
|
+
maxEffort: Record<string, EffortWord>;
|
|
191
|
+
/** plan 33: bar percentiles per difficulty, over the shipped ones (`percentilesOf`) */
|
|
192
|
+
bars: Bars;
|
|
143
193
|
budget: Budget;
|
|
144
194
|
timeouts: { idleMin: number; wallMin: number };
|
|
145
195
|
preflight: { confirm: boolean };
|
|
146
196
|
lock: { heavy: number | "cpus/2" };
|
|
147
197
|
notify: NotifyMoment[];
|
|
198
|
+
/** plan 35: only the memory numbers the profile sets; the defaults are `MEMORY_LIMITS` */
|
|
199
|
+
limits: { memory: Partial<Record<MemoryKey, number>> };
|
|
148
200
|
}
|
|
149
201
|
|
|
150
202
|
const LUNA_HIGH = "codex:gpt-6-luna#high";
|
|
@@ -168,14 +220,15 @@ export const BUILTIN_ROLES: Record<Role, RoleConfig> = {
|
|
|
168
220
|
artist: { enabled: true, access: DEFAULT_ACCESS.artist, rungs: [SOL("medium")] },
|
|
169
221
|
writer: { enabled: true, access: DEFAULT_ACCESS.writer, rungs: [LUNA_HIGH] },
|
|
170
222
|
researcher: { enabled: true, access: DEFAULT_ACCESS.researcher, rungs: [LUNA_HIGH] },
|
|
223
|
+
judge: { enabled: true, access: DEFAULT_ACCESS.judge, rungs: ["claude-code:claude-opus-5-5#medium"] },
|
|
171
224
|
};
|
|
172
225
|
|
|
173
226
|
/**
|
|
174
|
-
* Spec 1.1 §11: each shipped worker rung fails over to the best stand-in the
|
|
175
|
-
* (`rankStandIns` over `
|
|
176
|
-
*
|
|
177
|
-
*
|
|
178
|
-
*
|
|
227
|
+
* Spec 1.1 §11: each shipped worker rung fails over to the best stand-in the Slopalytics index offers
|
|
228
|
+
* (`rankStandIns` over `catalogPairs`, pinned by test/domain/failover.test.ts): on another quota, paid from a
|
|
229
|
+
* plan, and at least as intelligent. Go serves GPT-6 Luna itself; Kimi K3 stands on its own index, above Sol
|
|
230
|
+
* medium. Sol high and xhigh have no such stand-in on the owner's plans, so they have none: a limit on them
|
|
231
|
+
* pauses the lane instead of dropping it a tier.
|
|
179
232
|
*/
|
|
180
233
|
export const DEFAULT_FAILOVER: Record<string, string> = {
|
|
181
234
|
[LUNA_HIGH]: "opencode:opencode-go/gpt-6-luna#high",
|
|
@@ -183,63 +236,22 @@ export const DEFAULT_FAILOVER: Record<string, string> = {
|
|
|
183
236
|
};
|
|
184
237
|
|
|
185
238
|
/**
|
|
186
|
-
*
|
|
187
|
-
*
|
|
239
|
+
* Plan 33 Ruling 8: t3herd's effort caps under the shipped catalog's ids, on every backend that lists the model.
|
|
240
|
+
* The default profile writes them, so a failover to any backend can never start a `#max` Opus; a profile without
|
|
241
|
+
* the key has no ceiling.
|
|
188
242
|
*/
|
|
189
|
-
const
|
|
190
|
-
"
|
|
191
|
-
"
|
|
192
|
-
"
|
|
193
|
-
"
|
|
194
|
-
"
|
|
195
|
-
"
|
|
196
|
-
"
|
|
243
|
+
export const DEFAULT_MAX_EFFORT: Readonly<Record<string, EffortWord>> = {
|
|
244
|
+
"claude:claude-opus-5-5": "high",
|
|
245
|
+
"claude-code:claude-opus-5-5": "high",
|
|
246
|
+
"opencode:opencode/claude-opus-5-5": "high",
|
|
247
|
+
"cursor:claude-opus-5-5": "high",
|
|
248
|
+
"claude:claude-sonnet-5-5": "high",
|
|
249
|
+
"claude-code:claude-sonnet-5-5": "high",
|
|
250
|
+
"opencode:opencode/claude-sonnet-5-5": "high",
|
|
251
|
+
"codex:gpt-6.1-sol": "xhigh",
|
|
197
252
|
};
|
|
198
253
|
|
|
199
|
-
/** The
|
|
200
|
-
function cursorPairEffort(model: string, effort: string): string {
|
|
201
|
-
const list = CURSOR_PAIR[model]?.efforts ?? [];
|
|
202
|
-
if (list.length === 0) return "default";
|
|
203
|
-
if (effort !== "default" && list.includes(effort)) return effort;
|
|
204
|
-
return list.includes("high") ? "high" : (list[0] as string);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Spec 1.3 §7.3: the same model through two backends bills two pools, so each stands in for the other on a
|
|
209
|
-
* limit, at the same effort where both list it. Otherwise the stand-in runs at `high`: Grok 4.5 `xhigh` at Cursor
|
|
210
|
-
* `high`, Cursor's Gemini 3.6 `minimal` at agy `high`; Gemini 3.1 Pro's bare Cursor slug pairs with agy `high`.
|
|
211
|
-
* Grok and Gemini are paired with Cursor. Consulted after the profile's own failover and never written into a
|
|
212
|
-
* profile, where a pair whose rung is on no ladder would warn "never runs"; never a stand-in for a shipped Codex,
|
|
213
|
-
* Claude or opencode rung.
|
|
214
|
-
*/
|
|
215
|
-
export const PAIRED_FAILOVER: Record<string, string> = Object.fromEntries(
|
|
216
|
-
(
|
|
217
|
-
[
|
|
218
|
-
["grok", ["grok-4.7", "grok-4.6", "grok-4.5"], ["low", "medium", "high", "xhigh"]],
|
|
219
|
-
[
|
|
220
|
-
"antigravity",
|
|
221
|
-
["gemini-3.8-flash", "gemini-3.7-flash", "gemini-3.6-flash"],
|
|
222
|
-
["low", "medium", "high"],
|
|
223
|
-
],
|
|
224
|
-
["antigravity", ["gemini-3.1-pro"], ["low", "high"]],
|
|
225
|
-
] as const
|
|
226
|
-
).flatMap(([backend, models, efforts]) =>
|
|
227
|
-
models.flatMap((m) => {
|
|
228
|
-
const cursor = CURSOR_PAIR[m] as { id: string; efforts: readonly string[] };
|
|
229
|
-
const back = ["default", ...efforts].map(
|
|
230
|
-
(e) => [`${backend}:${m}#${e}`, `cursor:${cursor.id}#${cursorPairEffort(m, e)}`] as const,
|
|
231
|
-
);
|
|
232
|
-
// `#default` too, as on the other side: a Cursor rung with no effort flag fails over the same way
|
|
233
|
-
const fore = [...new Set(["default", ...cursor.efforts])].map((e) => {
|
|
234
|
-
const there = e !== "default" && (efforts as readonly string[]).includes(e) ? e : "high";
|
|
235
|
-
return [`cursor:${cursor.id}#${e}`, `${backend}:${m}#${there}`] as const;
|
|
236
|
-
});
|
|
237
|
-
return [...back, ...fore];
|
|
238
|
-
}),
|
|
239
|
-
),
|
|
240
|
-
);
|
|
241
|
-
|
|
242
|
-
/** The five billing keys spec §7.1 writes out; cursor and grok arrive with their backends. */
|
|
254
|
+
/** The five billing keys spec §7.1 writes out; the rest default. */
|
|
243
255
|
const WRITTEN_BILLING: BillingKey[] = ["codex", "claude", "claude-code", "opencode-go", "opencode"];
|
|
244
256
|
|
|
245
257
|
/** Stored defaults for init/new; architect/verifier rungs resolve from the current host. */
|
|
@@ -257,6 +269,7 @@ export function defaultProfileDoc(name = "default"): ProfileDoc {
|
|
|
257
269
|
opencode: { isolated: false },
|
|
258
270
|
},
|
|
259
271
|
failover: { ...DEFAULT_FAILOVER },
|
|
272
|
+
maxEffort: { ...DEFAULT_MAX_EFFORT },
|
|
260
273
|
budget: {},
|
|
261
274
|
timeouts: { idleMin: 15, wallMin: 90 },
|
|
262
275
|
preflight: { confirm: false },
|
|
@@ -265,10 +278,10 @@ export function defaultProfileDoc(name = "default"): ProfileDoc {
|
|
|
265
278
|
};
|
|
266
279
|
}
|
|
267
280
|
|
|
268
|
-
/** Remove only the
|
|
281
|
+
/** Remove only the architect, verifier and judge execution choices, preserving other stored fields. */
|
|
269
282
|
export function hostDefaultsDoc(doc: ProfileDoc): ProfileDoc {
|
|
270
283
|
const next = structuredClone(doc);
|
|
271
|
-
for (const role of ["architect", "verifier"] as const) {
|
|
284
|
+
for (const role of ["architect", "verifier", "judge"] as const) {
|
|
272
285
|
const r = next.roles?.[role];
|
|
273
286
|
if (r) {
|
|
274
287
|
delete r.rungs;
|
|
@@ -297,6 +310,9 @@ export function resolveProfile(doc: ProfileDoc, name: string, host: Orchestratio
|
|
|
297
310
|
? []
|
|
298
311
|
: b.rungs;
|
|
299
312
|
}
|
|
313
|
+
// the judge is Opus by default; on a Codex host it follows the host like the architect and the verifier do
|
|
314
|
+
if (role === "judge" && d?.rungs === undefined && host === "codex")
|
|
315
|
+
omitted = ["codex:gpt-6.1-sol#medium"];
|
|
300
316
|
// a role that lists its own rungs never inherits the built-in default rung, which may not be among them
|
|
301
317
|
const defaultRung = d?.defaultRung ?? (d?.rungs === undefined ? b.defaultRung : undefined);
|
|
302
318
|
roles[role] = {
|
|
@@ -318,6 +334,7 @@ export function resolveProfile(doc: ProfileDoc, name: string, host: Orchestratio
|
|
|
318
334
|
},
|
|
319
335
|
}
|
|
320
336
|
: {}),
|
|
337
|
+
...(Object.keys(readBars(d?.bars)).length > 0 ? { bars: readBars(d?.bars) } : {}),
|
|
321
338
|
};
|
|
322
339
|
}
|
|
323
340
|
const harness: Profile["harness"] = {};
|
|
@@ -340,11 +357,23 @@ export function resolveProfile(doc: ProfileDoc, name: string, host: Orchestratio
|
|
|
340
357
|
roles,
|
|
341
358
|
harness,
|
|
342
359
|
failover: { ...doc.failover },
|
|
360
|
+
maxEffort: Object.fromEntries(
|
|
361
|
+
Object.entries(doc.maxEffort ?? {}).filter((e): e is [string, EffortWord] => isEffort(e[1])),
|
|
362
|
+
),
|
|
363
|
+
bars: readBars(doc.bars),
|
|
343
364
|
budget,
|
|
344
365
|
timeouts: { idleMin: doc.timeouts?.idleMin ?? 15, wallMin: doc.timeouts?.wallMin ?? 90 },
|
|
345
366
|
preflight: { confirm: doc.preflight?.confirm ?? false },
|
|
346
367
|
lock: { heavy: doc.lock?.heavy ?? "cpus/2" },
|
|
347
368
|
notify: doc.notify === undefined ? [...NOTIFY] : doc.notify.filter((n) => known(NOTIFY, n)),
|
|
369
|
+
limits: {
|
|
370
|
+
memory: Object.fromEntries(
|
|
371
|
+
MEMORY_KEYS.flatMap((k) => {
|
|
372
|
+
const v = storedMemory(doc)[k];
|
|
373
|
+
return positiveInt(v) ? [[k, v]] : [];
|
|
374
|
+
}),
|
|
375
|
+
),
|
|
376
|
+
},
|
|
348
377
|
};
|
|
349
378
|
}
|
|
350
379
|
|
|
@@ -357,6 +386,10 @@ export function agentName(profile: string, role: Role, rung: string): string {
|
|
|
357
386
|
// Patches: what profile_set and `catherd profile set` accept. Strict at every level, so a misspelt key is
|
|
358
387
|
// refused with E_INPUT_INVALID instead of dropped (plan-2 review m9). `null` removes a map entry or a nullable
|
|
359
388
|
// field (a role's `network` among them: absent means granted).
|
|
389
|
+
const BarsPatchSchema = z
|
|
390
|
+
.partialRecord(z.enum(DIFFICULTIES), z.number().min(0).max(100).nullable())
|
|
391
|
+
.nullable();
|
|
392
|
+
|
|
360
393
|
const RolePatchSchema = z
|
|
361
394
|
.strictObject({
|
|
362
395
|
enabled: z.boolean(),
|
|
@@ -368,6 +401,7 @@ const RolePatchSchema = z
|
|
|
368
401
|
.strictObject({ idleMin: positive.nullable(), wallMin: positive.nullable() })
|
|
369
402
|
.partial()
|
|
370
403
|
.nullable(),
|
|
404
|
+
bars: BarsPatchSchema,
|
|
371
405
|
})
|
|
372
406
|
.partial();
|
|
373
407
|
|
|
@@ -378,6 +412,13 @@ export const ProfilePatchSchema = z.strictObject({
|
|
|
378
412
|
roles: z.partialRecord(z.enum(ROLES), RolePatchSchema).optional(),
|
|
379
413
|
harness: z.partialRecord(z.enum(HARNESS_KEYS), z.strictObject({ isolated: z.boolean() })).optional(),
|
|
380
414
|
failover: z.record(RungSchema, RungSchema.nullable()).optional(),
|
|
415
|
+
maxEffort: z
|
|
416
|
+
.record(
|
|
417
|
+
z.string().refine((k) => tryParseRung(`${k}#x`) !== null, "a key is <backend>:<model>"),
|
|
418
|
+
z.enum(EFFORT_ORDER).nullable(),
|
|
419
|
+
)
|
|
420
|
+
.optional(),
|
|
421
|
+
bars: BarsPatchSchema.optional(),
|
|
381
422
|
budget: z
|
|
382
423
|
.strictObject({ minutes: positive.nullable(), tokens: positive.nullable(), usd: positive.nullable() })
|
|
383
424
|
.partial()
|
|
@@ -386,6 +427,18 @@ export const ProfilePatchSchema = z.strictObject({
|
|
|
386
427
|
preflight: z.strictObject({ confirm: z.boolean() }).optional(),
|
|
387
428
|
lock: z.strictObject({ heavy: z.union([z.number().int().positive(), z.literal("cpus/2")]) }).optional(),
|
|
388
429
|
notify: z.array(z.enum(NOTIFY)).optional(),
|
|
430
|
+
limits: z
|
|
431
|
+
.strictObject({
|
|
432
|
+
memory: z
|
|
433
|
+
.strictObject({
|
|
434
|
+
reserveMb: z.number().int().positive().nullable(),
|
|
435
|
+
perMilestoneMb: z.number().int().positive().nullable(),
|
|
436
|
+
floorMb: z.number().int().positive().nullable(),
|
|
437
|
+
})
|
|
438
|
+
.partial(),
|
|
439
|
+
})
|
|
440
|
+
.partial()
|
|
441
|
+
.optional(),
|
|
389
442
|
});
|
|
390
443
|
export type ProfilePatch = z.infer<typeof ProfilePatchSchema>;
|
|
391
444
|
|
|
@@ -406,7 +459,7 @@ export function applyPatch(doc: ProfileDoc, patch: ProfilePatch): ProfileDoc {
|
|
|
406
459
|
}
|
|
407
460
|
|
|
408
461
|
/** Map fields whose keys are rungs or billing keys, which may hold dots: the rest of the path is one key. */
|
|
409
|
-
const MAP_FIELDS = new Set(["failover", "billing"]);
|
|
462
|
+
const MAP_FIELDS = new Set(["failover", "billing", "maxEffort"]);
|
|
410
463
|
const LIST_FIELDS = (keys: string[]) =>
|
|
411
464
|
(keys[0] === "roles" && keys[2] === "rungs") || (keys[0] === "notify" && keys.length === 1);
|
|
412
465
|
|
package/src/domain/record.ts
CHANGED
|
@@ -3,11 +3,11 @@ import { z } from "zod";
|
|
|
3
3
|
export const ACCESS = ["read-only", "workspace-write", "full"] as const;
|
|
4
4
|
export type Access = (typeof ACCESS)[number];
|
|
5
5
|
|
|
6
|
-
const RUN_STATUSES = ["ok", "failed", "limit", "cli-too-old", "timeout", "cancelled"] as const;
|
|
6
|
+
export const RUN_STATUSES = ["ok", "failed", "limit", "cli-too-old", "timeout", "cancelled"] as const;
|
|
7
7
|
export type RunStatus = (typeof RUN_STATUSES)[number];
|
|
8
8
|
|
|
9
9
|
/** plan 23: `flaky`, a worker's word for a check that failed, then passed when run alone */
|
|
10
|
-
const REPLY_STATUSES = ["complete", "partial", "blocked", "refused", "flaky"] as const;
|
|
10
|
+
export const REPLY_STATUSES = ["complete", "partial", "blocked", "refused", "flaky"] as const;
|
|
11
11
|
export type ReplyStatus = (typeof REPLY_STATUSES)[number];
|
|
12
12
|
|
|
13
13
|
export interface Tokens {
|
|
@@ -70,6 +70,17 @@ export const RunRecordSchema = z.looseObject({
|
|
|
70
70
|
replyWhy: z.string().nullable(),
|
|
71
71
|
/** plan 23: the reply's ENV: line, when the environment stopped the role; absent otherwise */
|
|
72
72
|
environment: z.string().optional(),
|
|
73
|
+
/** a verifier's `HEAD: <sha>` line: the commit it checked (lower-case hex, 7-64 characters) */
|
|
74
|
+
head: z
|
|
75
|
+
.string()
|
|
76
|
+
.regex(/^[0-9a-f]{7,64}$/)
|
|
77
|
+
.optional(),
|
|
78
|
+
/** a worker's `EXTRA: <path> — <why>` lines: files outside its lane it had to change */
|
|
79
|
+
extras: z.array(z.object({ path: z.string(), why: z.string() })).optional(),
|
|
80
|
+
/** a fix-round worker's `REJECTED: <finding> — <why>` lines */
|
|
81
|
+
rejected: z.array(z.object({ finding: z.string(), why: z.string() })).optional(),
|
|
82
|
+
/** a worker's `DESIGN:` lines: where its plan task conflicts with the code */
|
|
83
|
+
design: z.array(z.string()).optional(),
|
|
73
84
|
threadHeavy: z.boolean(),
|
|
74
85
|
access: z.enum(ACCESS),
|
|
75
86
|
isolated: z.boolean(),
|
|
@@ -101,3 +112,43 @@ export function parseReplyStatus(reply: string): { status: ReplyStatus | null; w
|
|
|
101
112
|
const m = STATUS_LINE.exec(last);
|
|
102
113
|
return m ? { status: m[1] as ReplyStatus, why: (m[2] ?? "").trim() } : { status: null, why: null };
|
|
103
114
|
}
|
|
115
|
+
|
|
116
|
+
/** The last `HEAD: <hex>` line of a reply outside code fences, lower-cased: the commit a verifier says it checked. */
|
|
117
|
+
export function parseReplyHead(reply: string): string | null {
|
|
118
|
+
let fenced = false;
|
|
119
|
+
let head: string | null = null;
|
|
120
|
+
for (const line of reply.split("\n")) {
|
|
121
|
+
if (/^\s*(```|~~~)/.test(line)) fenced = !fenced;
|
|
122
|
+
else if (!fenced) {
|
|
123
|
+
const m = /^[ \t]*HEAD:[ \t]*([0-9a-fA-F]{7,64})[ \t]*$/.exec(line);
|
|
124
|
+
if (m) head = (m[1] as string).toLowerCase();
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return head;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** `<LABEL>: <a> — <b>` lines (optional bullet; the separator is `—`, `–` or `-` with whitespace around it). */
|
|
131
|
+
function labelled(reply: string, label: string): [string, string][] {
|
|
132
|
+
const re = new RegExp(
|
|
133
|
+
`^[ \\t]*(?:[-*][ \\t]*)?${label}:[ \\t]*(\\S.*?)(?:[ \\t]+(?:—|–|-)[ \\t]+(.*?))?[ \\t]*$`,
|
|
134
|
+
"gm",
|
|
135
|
+
);
|
|
136
|
+
return [...reply.matchAll(re)].map((m) => [m[1] as string, m[2] ?? ""]);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Every `EXTRA: <path> — <why>` line: a file outside the lane the worker had to change. */
|
|
140
|
+
export function parseReplyExtras(reply: string): { path: string; why: string }[] {
|
|
141
|
+
return labelled(reply, "EXTRA").map(([path, why]) => ({ path: path.replace(/`/g, ""), why }));
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Every `REJECTED: <finding> — <why>` line of a fix round. */
|
|
145
|
+
export function parseReplyRejections(reply: string): { finding: string; why: string }[] {
|
|
146
|
+
return labelled(reply, "REJECTED").map(([finding, why]) => ({ finding, why }));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** The trimmed value of every `DESIGN:` line: a conflict between a plan task and the code. */
|
|
150
|
+
export function parseReplyDesign(reply: string): string[] {
|
|
151
|
+
return [...reply.matchAll(/^[ \t]*(?:[-*][ \t]*)?DESIGN:[ \t]*(\S.*?)[ \t]*$/gm)].map(
|
|
152
|
+
(m) => m[1] as string,
|
|
153
|
+
);
|
|
154
|
+
}
|