catherd-cli 0.2.1 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +141 -33
- package/THIRD_PARTY_NOTICES.md +37 -0
- package/catalog/jev.json +89 -0
- package/catalog/models.json +236 -0
- package/catalog/scores.json +366 -0
- package/package.json +24 -8
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/.mcp.json +2 -2
- package/plugin/bin/catherd-mcp +30 -0
- package/plugin/commands/catherd.md +2 -2
- package/plugin/skills/catherd/SKILL.md +160 -97
- package/plugin/skills/catherd-setup/SKILL.md +34 -24
- package/src/adapters/access.ts +63 -0
- package/src/adapters/all.ts +8 -0
- package/src/adapters/backend.ts +159 -0
- package/src/adapters/claude-code/events.ts +86 -0
- package/src/adapters/claude-code/index.ts +317 -0
- package/src/adapters/claude-code/models-api.ts +65 -0
- package/src/adapters/claude-code/models.ts +26 -0
- package/src/adapters/cli.ts +74 -0
- package/src/adapters/codex/events.ts +65 -0
- package/src/adapters/codex/home.ts +47 -0
- package/src/adapters/codex/index.ts +282 -0
- package/src/adapters/discovery.ts +87 -0
- package/src/adapters/opencode/agents.ts +84 -0
- package/src/adapters/opencode/events.ts +109 -0
- package/src/adapters/opencode/index.ts +333 -0
- package/src/adapters/registry.ts +14 -0
- package/src/cli.ts +159 -19
- package/src/domain/agents.ts +60 -0
- package/src/domain/budget.ts +45 -0
- package/src/domain/catalog.ts +262 -0
- package/src/domain/changes.ts +52 -0
- package/src/domain/cost.ts +89 -0
- package/src/domain/errors.ts +60 -0
- package/src/domain/failover.ts +119 -0
- package/src/domain/hints.ts +22 -0
- package/src/domain/ids.ts +97 -0
- package/src/domain/jev.ts +283 -0
- package/src/domain/lane.ts +92 -0
- package/src/domain/notice.ts +95 -0
- package/src/domain/profile-rules.ts +239 -0
- package/src/domain/profile.ts +389 -0
- package/src/domain/record.ts +86 -0
- package/src/{profile → domain}/role-prompts.ts +75 -27
- package/src/domain/roles.ts +25 -0
- package/src/domain/route.ts +126 -0
- package/src/domain/runtime.ts +21 -0
- package/src/domain/sanitize.ts +29 -0
- package/src/domain/secrets.ts +57 -0
- package/src/domain/select.ts +136 -0
- package/src/domain/state.ts +49 -0
- package/src/domain/util.ts +18 -0
- package/src/entry/capture-fixtures-command.ts +57 -0
- package/src/entry/catalog-command.ts +106 -0
- package/src/entry/cli-kit.ts +36 -0
- package/src/entry/deps.ts +17 -0
- package/src/entry/doctor-command.ts +51 -0
- package/src/entry/glyphs.ts +34 -0
- package/src/entry/init-command.ts +182 -0
- package/src/entry/lock-command.ts +123 -0
- package/src/{mcp/commands.ts → entry/mcp/command.ts} +5 -1
- package/src/entry/mcp/dispatch-tools.ts +52 -0
- package/src/entry/mcp/handshake.ts +58 -0
- package/src/entry/mcp/lane-tools.ts +84 -0
- package/src/entry/mcp/protocol-tools.ts +57 -0
- package/src/entry/mcp/result.ts +51 -0
- package/src/entry/mcp/run-tools.ts +145 -0
- package/src/entry/mcp/server.ts +88 -0
- package/src/entry/mcp/setup-tools.ts +81 -0
- package/src/entry/profile-command.ts +318 -0
- package/src/entry/prompt.ts +103 -0
- package/src/entry/runs-command.ts +260 -0
- package/src/entry/supervise-bin.ts +18 -0
- package/src/entry/supervise-command.ts +37 -0
- package/src/entry/tui/commands.ts +545 -0
- package/src/entry/tui/effects.ts +312 -0
- package/src/entry/tui/fixtures.ts +585 -0
- package/src/entry/tui/profile-edits.ts +138 -0
- package/src/entry/tui/profile-tree.ts +420 -0
- package/src/entry/tui/providers/app.tsx +162 -0
- package/src/entry/tui/providers/boundary.tsx +57 -0
- package/src/entry/tui/providers/data.tsx +160 -0
- package/src/entry/tui/providers/keymap.tsx +166 -0
- package/src/entry/tui/providers/root.tsx +45 -0
- package/src/entry/tui/providers/theme.tsx +17 -0
- package/src/entry/tui/providers/toast.tsx +77 -0
- package/src/entry/tui/run.tsx +98 -0
- package/src/entry/tui/state.ts +356 -0
- package/src/entry/tui/stories.ts +107 -0
- package/src/entry/tui/text.ts +130 -0
- package/src/entry/tui/theme.ts +140 -0
- package/src/entry/tui/views/app.tsx +208 -0
- package/src/entry/tui/views/dialogs.tsx +41 -0
- package/src/entry/tui/views/profile-actions.ts +321 -0
- package/src/entry/tui/views/profiles.tsx +440 -0
- package/src/entry/tui/views/runs.tsx +573 -0
- package/src/entry/tui/views/save-dialog.tsx +332 -0
- package/src/entry/tui/views/status.tsx +209 -0
- package/src/entry/tui/widgets/chrome.tsx +114 -0
- package/src/entry/tui/widgets/dialog-confirm.tsx +58 -0
- package/src/entry/tui/widgets/dialog-prompt.tsx +52 -0
- package/src/entry/tui/widgets/dialog-select.tsx +212 -0
- package/src/entry/tui/widgets/dialog.tsx +68 -0
- package/src/entry/tui/widgets/line.tsx +92 -0
- package/src/entry/tui/widgets/list.tsx +197 -0
- package/src/entry/tui/widgets/spinner.tsx +49 -0
- package/src/entry/tui/widgets/toast.tsx +62 -0
- package/src/infra/assets.ts +4 -0
- package/src/infra/claude-session.ts +98 -0
- package/src/infra/dispatch-dir.ts +286 -0
- package/src/infra/env.ts +67 -0
- package/src/infra/filelock.ts +159 -0
- package/src/infra/git.ts +91 -0
- package/src/infra/heavy-lock.ts +36 -0
- package/src/infra/jev-client.ts +162 -0
- package/src/infra/launch.ts +25 -0
- package/src/infra/log.ts +84 -0
- package/src/infra/paths.ts +38 -0
- package/src/infra/peer-inbox.ts +92 -0
- package/src/infra/proc.ts +110 -0
- package/src/infra/store.ts +229 -0
- package/src/infra/supervisor.ts +287 -0
- package/src/infra/version.ts +6 -0
- package/src/services/admission.ts +303 -0
- package/src/services/agent-links.ts +139 -0
- package/src/services/backends.ts +44 -0
- package/src/services/budget.ts +47 -0
- package/src/services/capture.ts +233 -0
- package/src/services/catalog-service.ts +382 -0
- package/src/services/dispatch-service.ts +620 -0
- package/src/services/dispatches.ts +174 -0
- package/src/services/doctor-access.ts +231 -0
- package/src/services/doctor-backends.ts +170 -0
- package/src/services/doctor-checks.ts +148 -0
- package/src/services/doctor-push.ts +109 -0
- package/src/services/doctor.ts +338 -0
- package/src/services/finalize.ts +377 -0
- package/src/services/gate-service.ts +288 -0
- package/src/services/global-install.ts +99 -0
- package/src/services/jev-service.ts +183 -0
- package/src/services/lane-service.ts +390 -0
- package/src/services/milestones.ts +208 -0
- package/src/services/notifier.ts +296 -0
- package/src/services/peek.ts +103 -0
- package/src/services/ports.ts +135 -0
- package/src/services/preflight.ts +221 -0
- package/src/services/profile-service.ts +292 -0
- package/src/services/profile-store.ts +141 -0
- package/src/services/protocol.ts +212 -0
- package/src/services/questions.ts +102 -0
- package/src/services/reconcile.ts +130 -0
- package/src/services/reentry.ts +22 -0
- package/src/services/routing-service.ts +190 -0
- package/src/services/run-debug.ts +79 -0
- package/src/services/run-service.ts +194 -0
- package/src/services/run-store.ts +334 -0
- package/src/services/runs-page.ts +254 -0
- package/src/services/session-view.ts +152 -0
- package/src/services/sessions.ts +92 -0
- package/src/services/setup.ts +87 -0
- package/src/services/state.ts +111 -0
- package/src/services/summary.ts +241 -0
- package/catalog/catalog.json +0 -112
- package/catalog/models-dev.json +0 -56453
- package/src/core/codex-events.ts +0 -49
- package/src/core/codex.ts +0 -166
- package/src/core/harness.ts +0 -89
- package/src/core/lanes.ts +0 -67
- package/src/core/lock.ts +0 -111
- package/src/core/opencode.ts +0 -207
- package/src/core/reconcile.ts +0 -26
- package/src/core/reply.ts +0 -54
- package/src/core/runstore.ts +0 -163
- package/src/core/spawn.ts +0 -89
- package/src/core/status.ts +0 -193
- package/src/files.ts +0 -19
- package/src/mcp/dispatch.ts +0 -154
- package/src/mcp/lane-tools.ts +0 -200
- package/src/mcp/out.ts +0 -6
- package/src/mcp/preflight.ts +0 -84
- package/src/mcp/run-tools.ts +0 -121
- package/src/mcp/runs.ts +0 -118
- package/src/mcp/server.ts +0 -26
- package/src/mcp/setup-tools.ts +0 -175
- package/src/paths.ts +0 -20
- package/src/profile/agents.ts +0 -130
- package/src/profile/profile.ts +0 -228
- package/src/routing/catalog.ts +0 -227
- package/src/routing/commands.ts +0 -22
- package/src/routing/jev.ts +0 -185
- package/src/routing/route.ts +0 -155
- package/src/routing/select.ts +0 -90
- package/src/tui/backends.ts +0 -57
- package/src/tui/commands.ts +0 -53
- package/src/tui/dashboard.tsx +0 -245
- package/src/tui/editor.tsx +0 -214
- package/src/tui/init.tsx +0 -208
- package/src/tui/matrix-model.ts +0 -279
- package/src/tui/matrix.tsx +0 -512
- package/src/tui/profiles.ts +0 -38
- package/src/tui/theme.ts +0 -177
- package/src/tui/ui.tsx +0 -267
- package/src/tui/watch-model.ts +0 -83
- package/src/tui/watch.tsx +0 -236
- package/src/types.ts +0 -184
- package/src/version.ts +0 -7
package/src/tui/watch.tsx
DELETED
|
@@ -1,236 +0,0 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import { TextAttributes } from "@opentui/core";
|
|
3
|
-
import { useKeyboard, useRenderer } from "@opentui/react";
|
|
4
|
-
import { useEffect, useRef, useState } from "react";
|
|
5
|
-
import { type BudgetStatus, formatBudget, type RunSummary, summarizeRun } from "../core/status.ts";
|
|
6
|
-
import { listRuns, readJsonl, readRunRecords, type Run } from "../core/runstore.ts";
|
|
7
|
-
import type { RunRecord, RungId } from "../types.ts";
|
|
8
|
-
import { face, glyph, tint, type Ui } from "./theme.ts";
|
|
9
|
-
import { CatSpinner, DetailPane, Frame, gradientLetters, ListLine } from "./ui.tsx";
|
|
10
|
-
import {
|
|
11
|
-
budgetTone,
|
|
12
|
-
climbs,
|
|
13
|
-
jevLine,
|
|
14
|
-
type JevLine,
|
|
15
|
-
liveLine,
|
|
16
|
-
plainBudgetBar,
|
|
17
|
-
runLine,
|
|
18
|
-
} from "./watch-model.ts";
|
|
19
|
-
|
|
20
|
-
export interface WatchDeps {
|
|
21
|
-
listRuns: () => Run[];
|
|
22
|
-
summarizeRun: (run: Run) => RunSummary;
|
|
23
|
-
readRunRecords: (dir: string) => RunRecord[];
|
|
24
|
-
readJev: (dir: string) => JevLine[];
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const watchDeps: WatchDeps = {
|
|
28
|
-
listRuns,
|
|
29
|
-
summarizeRun,
|
|
30
|
-
readRunRecords,
|
|
31
|
-
readJev: (dir) => readJsonl<JevLine>(join(dir, "jev.jsonl")),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// ponytail: only the 20 newest runs are summarized each poll; add paging when someone keeps more than that live
|
|
35
|
-
const MAX_RUNS = 20;
|
|
36
|
-
const LEFT_HEIGHT = 12;
|
|
37
|
-
|
|
38
|
-
interface Item {
|
|
39
|
-
run: Run;
|
|
40
|
-
summary: RunSummary | null;
|
|
41
|
-
error: string | null;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function poll(d: WatchDeps): { items: Item[]; problem: string | null } {
|
|
45
|
-
try {
|
|
46
|
-
const items = d
|
|
47
|
-
.listRuns()
|
|
48
|
-
.slice(0, MAX_RUNS)
|
|
49
|
-
.map((run): Item => {
|
|
50
|
-
try {
|
|
51
|
-
return { run, summary: d.summarizeRun(run), error: null };
|
|
52
|
-
} catch (e) {
|
|
53
|
-
return { run, summary: null, error: (e as Error).message };
|
|
54
|
-
}
|
|
55
|
-
});
|
|
56
|
-
return { items, problem: null };
|
|
57
|
-
} catch (e) {
|
|
58
|
-
return { items: [], problem: (e as Error).message };
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** spec §11b: a bar in the ginger-to-pink gradient, solid ginger from 80% spent and solid pink once
|
|
63
|
-
* the budget is exhausted (the palette has no dedicated warning/error tones, see watch-model.ts). */
|
|
64
|
-
function BudgetBar({ ui, budget }: { ui: Ui; budget: BudgetStatus }) {
|
|
65
|
-
const width = 10;
|
|
66
|
-
if (ui.plain) {
|
|
67
|
-
return (
|
|
68
|
-
<text
|
|
69
|
-
wrapMode="none"
|
|
70
|
-
truncate
|
|
71
|
-
>{`${plainBudgetBar(budget.fraction, width)} ${formatBudget(budget)}`}</text>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
const filled = Math.round(Math.max(0, Math.min(1, budget.fraction)) * width);
|
|
75
|
-
const tone = budgetTone(budget.fraction);
|
|
76
|
-
const solid =
|
|
77
|
-
tone === "error" ? tint("pink", ui.depth) : tone === "warning" ? tint("ginger", ui.depth) : null;
|
|
78
|
-
const cells = solid
|
|
79
|
-
? Array.from({ length: filled }, () => ({ ch: "█", fg: solid }))
|
|
80
|
-
: gradientLetters("█".repeat(filled));
|
|
81
|
-
return (
|
|
82
|
-
<text wrapMode="none" truncate>
|
|
83
|
-
{cells.map((cell, i) => (
|
|
84
|
-
// biome-ignore lint: index key is stable, the bar never reorders
|
|
85
|
-
<span key={i} fg={cell.fg}>
|
|
86
|
-
{cell.ch}
|
|
87
|
-
</span>
|
|
88
|
-
))}
|
|
89
|
-
<span attributes={TextAttributes.DIM}>{"░".repeat(width - filled)}</span>
|
|
90
|
-
{` ${formatBudget(budget)}`}
|
|
91
|
-
</text>
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
/** The right pane for the highlighted run: its live lanes, recent climbs, Jev decisions and the
|
|
96
|
-
* budget bar — the run's records and raw state text stay out (they were noise, spec §item 4). */
|
|
97
|
-
function RunDetail({ ui, it, climbed, deps }: { ui: Ui; it: Item; climbed: string[]; deps: WatchDeps }) {
|
|
98
|
-
const pink = tint("pink", ui.depth);
|
|
99
|
-
if (!it.summary) {
|
|
100
|
-
return (
|
|
101
|
-
<DetailPane ui={ui} title={it.run.id} subtitle="unreadable">
|
|
102
|
-
<text fg={pink} wrapMode="none" truncate>
|
|
103
|
-
{`${face("failed", ui.plain)} ${it.error}`}
|
|
104
|
-
</text>
|
|
105
|
-
</DetailPane>
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
const s = it.summary;
|
|
109
|
-
const jev = deps.readJev(it.run.dir);
|
|
110
|
-
return (
|
|
111
|
-
<DetailPane ui={ui} title={s.title} subtitle={it.run.meta.repo}>
|
|
112
|
-
{s.live.length > 0 ? (
|
|
113
|
-
s.live.map((l) => (
|
|
114
|
-
<text key={l.name} wrapMode="none" truncate>
|
|
115
|
-
{liveLine(l, ui.plain)}
|
|
116
|
-
</text>
|
|
117
|
-
))
|
|
118
|
-
) : (
|
|
119
|
-
<text attributes={TextAttributes.DIM}>no lane is running right now</text>
|
|
120
|
-
)}
|
|
121
|
-
{climbed.length > 0 ? (
|
|
122
|
-
<>
|
|
123
|
-
<text> </text>
|
|
124
|
-
{climbed.map((c, i) => (
|
|
125
|
-
<text key={`c${i}`} fg={pink} wrapMode="none" truncate>
|
|
126
|
-
{c}
|
|
127
|
-
</text>
|
|
128
|
-
))}
|
|
129
|
-
</>
|
|
130
|
-
) : null}
|
|
131
|
-
<text> </text>
|
|
132
|
-
<text attributes={TextAttributes.DIM}>
|
|
133
|
-
{`jev ${glyph("dot", ui.plain)} ${s.jev.decisions} decisions ${glyph("dot", ui.plain)} ${s.jev.fallbacks} fell back`}
|
|
134
|
-
</text>
|
|
135
|
-
{jev.slice(-4).map((e, i) => (
|
|
136
|
-
<text key={`j${i}`} wrapMode="none" truncate>
|
|
137
|
-
{jevLine(e, ui.plain)}
|
|
138
|
-
</text>
|
|
139
|
-
))}
|
|
140
|
-
{s.budget ? (
|
|
141
|
-
<>
|
|
142
|
-
<text> </text>
|
|
143
|
-
<BudgetBar ui={ui} budget={s.budget} />
|
|
144
|
-
</>
|
|
145
|
-
) : null}
|
|
146
|
-
</DetailPane>
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
export function Watch({
|
|
151
|
-
ui,
|
|
152
|
-
deps = {},
|
|
153
|
-
intervalMs = 2000,
|
|
154
|
-
}: {
|
|
155
|
-
ui: Ui;
|
|
156
|
-
deps?: Partial<WatchDeps>;
|
|
157
|
-
intervalMs?: number;
|
|
158
|
-
}) {
|
|
159
|
-
const d = { ...watchDeps, ...deps };
|
|
160
|
-
const renderer = useRenderer();
|
|
161
|
-
const seen = useRef(new Map<string, RungId>());
|
|
162
|
-
const [state, setState] = useState<{ items: Item[]; problem: string | null } | null>(null);
|
|
163
|
-
const [climbed, setClimbed] = useState<string[]>([]);
|
|
164
|
-
const [cursor, setCursor] = useState(0);
|
|
165
|
-
|
|
166
|
-
useEffect(() => {
|
|
167
|
-
const refresh = () => {
|
|
168
|
-
const next = poll(d);
|
|
169
|
-
const fresh = next.items.flatMap((it) =>
|
|
170
|
-
it.summary ? climbs(seen.current, it.summary, ui.plain) : [],
|
|
171
|
-
);
|
|
172
|
-
if (fresh.length > 0) setClimbed((c) => [...fresh, ...c].slice(0, 3));
|
|
173
|
-
setState(next);
|
|
174
|
-
};
|
|
175
|
-
refresh();
|
|
176
|
-
const t = setInterval(refresh, intervalMs);
|
|
177
|
-
return () => clearInterval(t);
|
|
178
|
-
}, []);
|
|
179
|
-
|
|
180
|
-
const items = state?.items ?? [];
|
|
181
|
-
const at = Math.min(cursor, Math.max(0, items.length - 1));
|
|
182
|
-
const leftStart = Math.max(0, Math.min(at - Math.floor(LEFT_HEIGHT / 2), items.length - LEFT_HEIGHT));
|
|
183
|
-
|
|
184
|
-
useKeyboard((key) => {
|
|
185
|
-
if (key.sequence === "q") {
|
|
186
|
-
renderer.destroy();
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
if (key.name === "up") setCursor(Math.max(0, at - 1));
|
|
190
|
-
else if (key.name === "down") setCursor(Math.min(items.length - 1, at + 1));
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
const pink = tint("pink", ui.depth);
|
|
194
|
-
const title = `Watch ${glyph("dot", ui.plain)} ${items.length} runs`;
|
|
195
|
-
const hint = `${glyph("keys", ui.plain)} navigate q quit refreshes every ${intervalMs / 1000} s`;
|
|
196
|
-
|
|
197
|
-
if (state?.problem) {
|
|
198
|
-
return (
|
|
199
|
-
<Frame ui={ui} title={title} hint="q quit">
|
|
200
|
-
<text fg={pink} wrapMode="none" truncate>{`${face("failed", ui.plain)} ${state.problem}`}</text>
|
|
201
|
-
</Frame>
|
|
202
|
-
);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
return (
|
|
206
|
-
<Frame ui={ui} title={title} hint={hint}>
|
|
207
|
-
{state === null ? (
|
|
208
|
-
<CatSpinner ui={ui} label="reading the runs" />
|
|
209
|
-
) : items.length === 0 ? (
|
|
210
|
-
<text>No runs yet. Start one with /catherd in Claude Code.</text>
|
|
211
|
-
) : (
|
|
212
|
-
<box style={{ flexDirection: "row" }}>
|
|
213
|
-
<box style={{ flexDirection: "column", width: 24 }}>
|
|
214
|
-
<text fg={pink} attributes={TextAttributes.BOLD}>
|
|
215
|
-
RUNS
|
|
216
|
-
</text>
|
|
217
|
-
{items.slice(leftStart, leftStart + LEFT_HEIGHT).map((it, i) => (
|
|
218
|
-
<ListLine
|
|
219
|
-
key={it.run.dir}
|
|
220
|
-
ui={ui}
|
|
221
|
-
selected={leftStart + i === at}
|
|
222
|
-
dim={!it.summary}
|
|
223
|
-
text={
|
|
224
|
-
it.summary
|
|
225
|
-
? runLine(it.summary, ui.plain)
|
|
226
|
-
: `${face("failed", ui.plain)} ${it.run.id} ${glyph("dot", ui.plain)} unreadable: ${it.error}`
|
|
227
|
-
}
|
|
228
|
-
/>
|
|
229
|
-
))}
|
|
230
|
-
</box>
|
|
231
|
-
{items[at] ? <RunDetail ui={ui} it={items[at]} climbed={climbed} deps={d} /> : null}
|
|
232
|
-
</box>
|
|
233
|
-
)}
|
|
234
|
-
</Frame>
|
|
235
|
-
);
|
|
236
|
-
}
|
package/src/types.ts
DELETED
|
@@ -1,184 +0,0 @@
|
|
|
1
|
-
export const ROLES = [
|
|
2
|
-
"architect",
|
|
3
|
-
"verifier",
|
|
4
|
-
"worker",
|
|
5
|
-
"reviewer",
|
|
6
|
-
"ui-reviewer",
|
|
7
|
-
"artist",
|
|
8
|
-
"writer",
|
|
9
|
-
"researcher",
|
|
10
|
-
] as const;
|
|
11
|
-
export type Role = (typeof ROLES)[number];
|
|
12
|
-
|
|
13
|
-
export type Backend = "codex" | "opencode" | "claude";
|
|
14
|
-
|
|
15
|
-
/** "<model-id>#<effort>". Model ids may contain "/" (opencode providers); split at the LAST "#". */
|
|
16
|
-
export type RungId = string;
|
|
17
|
-
|
|
18
|
-
export function splitRung(rung: RungId): { model: string; effort: string } {
|
|
19
|
-
const i = rung.lastIndexOf("#");
|
|
20
|
-
if (i < 1 || i === rung.length - 1) throw new Error(`catherd: bad rung "${rung}", expected model#effort`);
|
|
21
|
-
return { model: rung.slice(0, i), effort: rung.slice(i + 1) };
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export const rungOf = (model: string, effort: string): RungId => `${model}#${effort}`;
|
|
25
|
-
|
|
26
|
-
export type Sandbox = "read-only" | "workspace-write" | "danger-full-access";
|
|
27
|
-
|
|
28
|
-
/** Spec §4: the sandbox a Codex or opencode run of each role gets. */
|
|
29
|
-
export const ROLE_SANDBOX: Record<Role, Sandbox> = {
|
|
30
|
-
architect: "read-only",
|
|
31
|
-
verifier: "danger-full-access",
|
|
32
|
-
worker: "workspace-write",
|
|
33
|
-
reviewer: "read-only",
|
|
34
|
-
"ui-reviewer": "danger-full-access",
|
|
35
|
-
artist: "workspace-write",
|
|
36
|
-
writer: "workspace-write",
|
|
37
|
-
researcher: "read-only",
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
export interface Capabilities {
|
|
41
|
-
toolCall: boolean;
|
|
42
|
-
imageIn: boolean;
|
|
43
|
-
imageOut: boolean;
|
|
44
|
-
reasoning: boolean;
|
|
45
|
-
context: number;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Spec §4: what a model must have to be offered for a role. */
|
|
49
|
-
export const ROLE_NEEDS: Record<Role, Partial<Capabilities>> = {
|
|
50
|
-
architect: { toolCall: true },
|
|
51
|
-
verifier: { toolCall: true },
|
|
52
|
-
worker: { toolCall: true },
|
|
53
|
-
reviewer: { toolCall: true },
|
|
54
|
-
"ui-reviewer": { toolCall: true, imageIn: true },
|
|
55
|
-
artist: { imageOut: true },
|
|
56
|
-
writer: { toolCall: true },
|
|
57
|
-
researcher: { toolCall: true },
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
export const KINDS = ["repo_code", "terminal", "ui", "prose", "research"] as const;
|
|
61
|
-
export type Kind = (typeof KINDS)[number];
|
|
62
|
-
|
|
63
|
-
export const DIFFICULTIES = ["copy", "build", "logic", "hard"] as const;
|
|
64
|
-
export type Difficulty = (typeof DIFFICULTIES)[number];
|
|
65
|
-
|
|
66
|
-
export type ScoreDim = "repo_code" | "terminal" | "honesty";
|
|
67
|
-
|
|
68
|
-
export interface Scores {
|
|
69
|
-
repo_code?: number;
|
|
70
|
-
terminal?: number;
|
|
71
|
-
honesty?: number;
|
|
72
|
-
secs_per_task?: number;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export interface CatalogModel {
|
|
76
|
-
id: string;
|
|
77
|
-
backend: Backend;
|
|
78
|
-
efforts: string[];
|
|
79
|
-
capabilities: Capabilities;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
export interface CatalogEntry {
|
|
83
|
-
rung: RungId;
|
|
84
|
-
scores: Scores;
|
|
85
|
-
costRank: number;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export type Bars = Record<Kind, Record<Difficulty, Partial<Record<ScoreDim, number>>>>;
|
|
89
|
-
|
|
90
|
-
export interface Catalog {
|
|
91
|
-
version: string;
|
|
92
|
-
models: CatalogModel[];
|
|
93
|
-
entries: CatalogEntry[];
|
|
94
|
-
bars: Bars;
|
|
95
|
-
/** unscored rung -> scored rung whose scores it borrows */
|
|
96
|
-
treatLike: Record<RungId, RungId>;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export interface RoleConfig {
|
|
100
|
-
enabled: boolean;
|
|
101
|
-
/** model id -> enabled efforts */
|
|
102
|
-
models: Record<string, string[]>;
|
|
103
|
-
defaultRung?: RungId;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export type NotifyMoment = "milestone" | "finish" | "blocked";
|
|
107
|
-
|
|
108
|
-
/** Spec §8.2: false (the default) runs the vendor harness exactly as the user configured it. */
|
|
109
|
-
export interface HarnessConfig {
|
|
110
|
-
isolated: boolean;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface Profile {
|
|
114
|
-
name: string;
|
|
115
|
-
objective: "cost" | "speed";
|
|
116
|
-
roles: Record<Role, RoleConfig>;
|
|
117
|
-
harness: { codex: HarnessConfig; opencode: HarnessConfig };
|
|
118
|
-
lock: { heavy: number | "cpus/2" };
|
|
119
|
-
notify: NotifyMoment[];
|
|
120
|
-
/** spec §11b: a rung's stand-in on another backend, used on a `limit` result */
|
|
121
|
-
failover?: Record<RungId, RungId>;
|
|
122
|
-
/** spec §11b: the run budget; `status` shows spend against it, `route` and `dispatch` react at 80%/100% */
|
|
123
|
-
budget?: { minutes?: number; tokens?: number; usd?: number };
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export interface RouteDecision {
|
|
127
|
-
kind: Kind | null;
|
|
128
|
-
difficulty: Difficulty | null;
|
|
129
|
-
confidence: { kind: number | null; difficulty: number | null };
|
|
130
|
-
source: "jev" | "default";
|
|
131
|
-
rung: RungId;
|
|
132
|
-
ladder: RungId[];
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export type ReplyStatus = "complete" | "partial" | "blocked" | "refused";
|
|
136
|
-
export type RunStatus = "ok" | "failed" | "limit" | "cli-too-old" | "timeout" | "interrupted";
|
|
137
|
-
|
|
138
|
-
export interface Tokens {
|
|
139
|
-
input: number;
|
|
140
|
-
cached: number;
|
|
141
|
-
output: number;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/** One Codex or opencode role run. Appended to <run>/runs.jsonl; the only record of it. */
|
|
145
|
-
export interface RunRecord {
|
|
146
|
-
name: string;
|
|
147
|
-
role: Role;
|
|
148
|
-
backend: "codex" | "opencode";
|
|
149
|
-
rung: RungId;
|
|
150
|
-
thread: string | null;
|
|
151
|
-
status: RunStatus;
|
|
152
|
-
startedAt: string;
|
|
153
|
-
secs: number;
|
|
154
|
-
pid: number | null;
|
|
155
|
-
tokens: Tokens;
|
|
156
|
-
costUsd: number | null;
|
|
157
|
-
changedOwned: string[];
|
|
158
|
-
replyStatus: ReplyStatus | null;
|
|
159
|
-
replyWhy: string | null;
|
|
160
|
-
threadHeavy: boolean;
|
|
161
|
-
/** the harness ran isolated (spec §8.2); lets reports measure what the user's customizations cost */
|
|
162
|
-
isolated: boolean;
|
|
163
|
-
images: string[];
|
|
164
|
-
error: string | null;
|
|
165
|
-
replyPath: string;
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export interface RunMeta {
|
|
169
|
-
id: string;
|
|
170
|
-
repo: string;
|
|
171
|
-
title: string;
|
|
172
|
-
aLines: string[];
|
|
173
|
-
createdAt: string;
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
export interface StateSnapshot {
|
|
177
|
-
head: string;
|
|
178
|
-
dirty: { path: string; owner: string | null }[];
|
|
179
|
-
running: { name: string; rung: RungId; thread: string | null; brief: string; since: string }[];
|
|
180
|
-
lastCheck: string | null;
|
|
181
|
-
next: string;
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
export const THREAD_HEAVY_INPUT = 8_000_000;
|