tickmarkr 1.30.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 +347 -0
- package/dist/adapters/claude-code.d.ts +4 -0
- package/dist/adapters/claude-code.js +191 -0
- package/dist/adapters/codex.d.ts +8 -0
- package/dist/adapters/codex.js +257 -0
- package/dist/adapters/cursor-agent.d.ts +4 -0
- package/dist/adapters/cursor-agent.js +62 -0
- package/dist/adapters/fake.d.ts +45 -0
- package/dist/adapters/fake.js +115 -0
- package/dist/adapters/grok.d.ts +4 -0
- package/dist/adapters/grok.js +122 -0
- package/dist/adapters/model-lints.d.ts +6 -0
- package/dist/adapters/model-lints.js +130 -0
- package/dist/adapters/opencode.d.ts +3 -0
- package/dist/adapters/opencode.js +108 -0
- package/dist/adapters/pi.d.ts +3 -0
- package/dist/adapters/pi.js +191 -0
- package/dist/adapters/prompt.d.ts +6 -0
- package/dist/adapters/prompt.js +84 -0
- package/dist/adapters/registry.d.ts +31 -0
- package/dist/adapters/registry.js +165 -0
- package/dist/adapters/types.d.ts +98 -0
- package/dist/adapters/types.js +56 -0
- package/dist/cli/commands/approve.d.ts +1 -0
- package/dist/cli/commands/approve.js +72 -0
- package/dist/cli/commands/compile.d.ts +1 -0
- package/dist/cli/commands/compile.js +23 -0
- package/dist/cli/commands/doctor.d.ts +2 -0
- package/dist/cli/commands/doctor.js +103 -0
- package/dist/cli/commands/init.d.ts +1 -0
- package/dist/cli/commands/init.js +32 -0
- package/dist/cli/commands/plan.d.ts +2 -0
- package/dist/cli/commands/plan.js +101 -0
- package/dist/cli/commands/profile.d.ts +1 -0
- package/dist/cli/commands/profile.js +56 -0
- package/dist/cli/commands/report.d.ts +4 -0
- package/dist/cli/commands/report.js +306 -0
- package/dist/cli/commands/resume.d.ts +1 -0
- package/dist/cli/commands/resume.js +11 -0
- package/dist/cli/commands/run.d.ts +1 -0
- package/dist/cli/commands/run.js +35 -0
- package/dist/cli/commands/scope.d.ts +3 -0
- package/dist/cli/commands/scope.js +22 -0
- package/dist/cli/commands/status.d.ts +9 -0
- package/dist/cli/commands/status.js +162 -0
- package/dist/cli/commands/unlock.d.ts +1 -0
- package/dist/cli/commands/unlock.js +11 -0
- package/dist/cli/index.d.ts +8 -0
- package/dist/cli/index.js +65 -0
- package/dist/compile/collateral.d.ts +6 -0
- package/dist/compile/collateral.js +128 -0
- package/dist/compile/common.d.ts +12 -0
- package/dist/compile/common.js +56 -0
- package/dist/compile/gsd.d.ts +3 -0
- package/dist/compile/gsd.js +222 -0
- package/dist/compile/index.d.ts +4 -0
- package/dist/compile/index.js +33 -0
- package/dist/compile/native.d.ts +4 -0
- package/dist/compile/native.js +196 -0
- package/dist/compile/prd.d.ts +2 -0
- package/dist/compile/prd.js +64 -0
- package/dist/compile/speckit.d.ts +2 -0
- package/dist/compile/speckit.js +66 -0
- package/dist/config/config.d.ts +184 -0
- package/dist/config/config.js +329 -0
- package/dist/drivers/herdr.d.ts +44 -0
- package/dist/drivers/herdr.js +394 -0
- package/dist/drivers/index.d.ts +3 -0
- package/dist/drivers/index.js +13 -0
- package/dist/drivers/subprocess.d.ts +25 -0
- package/dist/drivers/subprocess.js +106 -0
- package/dist/drivers/types.d.ts +61 -0
- package/dist/drivers/types.js +77 -0
- package/dist/gates/acceptance.d.ts +19 -0
- package/dist/gates/acceptance.js +92 -0
- package/dist/gates/baseline.d.ts +12 -0
- package/dist/gates/baseline.js +97 -0
- package/dist/gates/evidence.d.ts +4 -0
- package/dist/gates/evidence.js +12 -0
- package/dist/gates/llm.d.ts +25 -0
- package/dist/gates/llm.js +101 -0
- package/dist/gates/review.d.ts +12 -0
- package/dist/gates/review.js +80 -0
- package/dist/gates/run-gates.d.ts +34 -0
- package/dist/gates/run-gates.js +105 -0
- package/dist/gates/scope.d.ts +8 -0
- package/dist/gates/scope.js +32 -0
- package/dist/gates/types.d.ts +6 -0
- package/dist/gates/types.js +1 -0
- package/dist/graph/graph.d.ts +20 -0
- package/dist/graph/graph.js +148 -0
- package/dist/graph/schema.d.ts +185 -0
- package/dist/graph/schema.js +141 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/plan/prompt.d.ts +4 -0
- package/dist/plan/prompt.js +31 -0
- package/dist/plan/scope.d.ts +17 -0
- package/dist/plan/scope.js +141 -0
- package/dist/report/cost.d.ts +24 -0
- package/dist/report/cost.js +89 -0
- package/dist/route/preference.d.ts +27 -0
- package/dist/route/preference.js +49 -0
- package/dist/route/profile.d.ts +65 -0
- package/dist/route/profile.js +197 -0
- package/dist/route/router.d.ts +26 -0
- package/dist/route/router.js +200 -0
- package/dist/run/consult.d.ts +23 -0
- package/dist/run/consult.js +80 -0
- package/dist/run/daemon.d.ts +23 -0
- package/dist/run/daemon.js +728 -0
- package/dist/run/git.d.ts +12 -0
- package/dist/run/git.js +56 -0
- package/dist/run/journal.d.ts +91 -0
- package/dist/run/journal.js +287 -0
- package/dist/run/lock.d.ts +29 -0
- package/dist/run/lock.js +182 -0
- package/dist/run/merge.d.ts +12 -0
- package/dist/run/merge.js +44 -0
- package/dist/run/reconcile.d.ts +4 -0
- package/dist/run/reconcile.js +95 -0
- package/package.json +39 -0
- package/schema/rungraph.schema.json +306 -0
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
import { mkdirSync, readdirSync, readFileSync, realpathSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { probeVersion } from "./claude-code.js";
|
|
5
|
+
import { parseWorkerResult } from "./prompt.js";
|
|
6
|
+
import { channelsFromConfig, MODEL_ID_RE, shq, TokenUsageSchema } from "./types.js";
|
|
7
|
+
// SPEND-07: codex writes per-session JSONL to ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl — date-partitioned,
|
|
8
|
+
// NOT cwd-keyed. session_meta.payload.cwd is FILE-SCOPED (one codex exec per cwd). token_count events carry
|
|
9
|
+
// per-turn DELTAS in payload.info.last_token_usage; we read POST-HOC (never the pane, never the trailer).
|
|
10
|
+
// last_token_usage is the per-turn DELTA; total_token_usage is the session cumulative — reading it is the
|
|
11
|
+
// 3A+2B+C trap (arithmetic-verified 29-RESEARCH.md). Filter PER RECORD by top-level ISO timestamp >= sinceMs
|
|
12
|
+
// (this attempt's dispatch wall-clock). reasoning_output_tokens ⊂ output_tokens — omit reasoning entirely;
|
|
13
|
+
// report.ts total() cross-sums reasoning and populating it double-counts (claude reference impl also omits it).
|
|
14
|
+
// FAIL OPEN everywhere: missing dir / torn line / info:null / bad timestamp / cwd mismatch ⇒ undefined.
|
|
15
|
+
const MAX_SESSION_FILES = 20;
|
|
16
|
+
const MAX_SESSION_BYTES = 8_000_000;
|
|
17
|
+
const DAY_MS = 86_400_000;
|
|
18
|
+
function codexSessionDayDirs(sessions, sinceMs) {
|
|
19
|
+
const out = [];
|
|
20
|
+
let years;
|
|
21
|
+
try {
|
|
22
|
+
years = readdirSync(sessions);
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return [];
|
|
26
|
+
}
|
|
27
|
+
for (const y of years) {
|
|
28
|
+
if (!/^\d{4}$/.test(y))
|
|
29
|
+
continue;
|
|
30
|
+
const yPath = join(sessions, y);
|
|
31
|
+
let months;
|
|
32
|
+
try {
|
|
33
|
+
months = readdirSync(yPath);
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
for (const m of months) {
|
|
39
|
+
if (!/^\d{2}$/.test(m))
|
|
40
|
+
continue;
|
|
41
|
+
const mPath = join(yPath, m);
|
|
42
|
+
let days;
|
|
43
|
+
try {
|
|
44
|
+
days = readdirSync(mPath);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
for (const d of days) {
|
|
50
|
+
if (!/^\d{2}$/.test(d))
|
|
51
|
+
continue;
|
|
52
|
+
if (Date.UTC(Number(y), Number(m) - 1, Number(d)) + 2 * DAY_MS > sinceMs)
|
|
53
|
+
out.push(join(y, m, d));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return out;
|
|
58
|
+
}
|
|
59
|
+
// v1.5 MODEL-01: codex has NO list subcommand (verified 2026-07-10 — `codex models` errors); its
|
|
60
|
+
// own ~/.codex/models_cache.json is a timestamped JSON cache. File read, zero subprocess/network.
|
|
61
|
+
// Fails OPEN to { models: [] } on missing/corrupt cache. Version drift observed (installed CLI
|
|
62
|
+
// 0.143.0 vs cache client_version 0.144.0, 2026-07-10) — hence the defensive try/catch and no shape
|
|
63
|
+
// assumptions. CODEX_HOME is codex's own relocation env. fetchedAt is the honest codex knowledge age.
|
|
64
|
+
export function readCodexModelsCache(path) {
|
|
65
|
+
const p = path ?? join(process.env.CODEX_HOME || join(homedir(), ".codex"), "models_cache.json");
|
|
66
|
+
try {
|
|
67
|
+
const d = JSON.parse(readFileSync(p, "utf8"));
|
|
68
|
+
const models = (d.models ?? [])
|
|
69
|
+
.filter((m) => m?.visibility === "list" && typeof m.slug === "string" && m.slug.length > 0)
|
|
70
|
+
.map((m) => m.slug)
|
|
71
|
+
.filter((id) => MODEL_ID_RE.test(id));
|
|
72
|
+
return { models, fetchedAt: typeof d.fetched_at === "string" ? d.fetched_at : undefined };
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return { models: [] };
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
// drovr worktrees keep their gitdir under the MAIN repo's .git/worktrees/<name> — outside the
|
|
79
|
+
// workspace-write sandbox root — so git commit dies on index.lock (incident run-20260709-104447
|
|
80
|
+
// P87-08: "Unable to create …/.git/worktrees/…/index.lock"). Expanded by the pane shell at the
|
|
81
|
+
// worktree cwd; in a plain repo it resolves to ./.git (already writable, harmless).
|
|
82
|
+
const GITDIR_WRITABLE = `-c "sandbox_workspace_write.writable_roots=[\\"$(git rev-parse --path-format=absolute --git-common-dir)\\"]"`;
|
|
83
|
+
// v1.22 T5 / OBS-16: codex keys trust on absolute path under [projects."<root>"] trust_level="trusted"
|
|
84
|
+
// in ~/.codex/config.toml (CODEX_HOME relocates the dir). Worktrees inherit parent-project trust when
|
|
85
|
+
// the REPO ROOT is trusted — seed the root once, cover every future worktree. Idempotent: a second
|
|
86
|
+
// call that finds the entry returns trusted without rewriting. configPath is the test seam.
|
|
87
|
+
export function seedCodexTrust(repoRoot, configPath) {
|
|
88
|
+
let root;
|
|
89
|
+
try {
|
|
90
|
+
root = realpathSync(repoRoot);
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
root = repoRoot;
|
|
94
|
+
}
|
|
95
|
+
const cfgPath = configPath ?? join(process.env.CODEX_HOME || join(homedir(), ".codex"), "config.toml");
|
|
96
|
+
// TOML quoted-key form matches codex's own writer: [projects."/abs/path"]
|
|
97
|
+
const header = `[projects.${JSON.stringify(root)}]`;
|
|
98
|
+
const entry = `${header}\ntrust_level = "trusted"\n`;
|
|
99
|
+
let text = "";
|
|
100
|
+
try {
|
|
101
|
+
text = readFileSync(cfgPath, "utf8");
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
/* missing file — seed creates it */
|
|
105
|
+
}
|
|
106
|
+
if (hasCodexTrustedProject(text, root))
|
|
107
|
+
return { status: "trusted" };
|
|
108
|
+
// exact one-time operator command if the write fails (readonly home, sandbox, etc.)
|
|
109
|
+
const once = `mkdir -p ${shq(dirname(cfgPath))} && printf '\\n[projects.%s]\\ntrust_level = "trusted"\\n' ${shq(JSON.stringify(root))} >> ${shq(cfgPath)}`;
|
|
110
|
+
try {
|
|
111
|
+
mkdirSync(dirname(cfgPath), { recursive: true });
|
|
112
|
+
const base = text === "" || text.endsWith("\n") ? text : `${text}\n`;
|
|
113
|
+
writeFileSync(cfgPath, `${base}\n${entry}`, { mode: 0o600 });
|
|
114
|
+
return { status: "seeded" };
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
return { status: "action-required", command: once };
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// Match [projects."/path"] or [projects.'/path'] then trust_level = "trusted" before the next section.
|
|
121
|
+
export function hasCodexTrustedProject(text, root) {
|
|
122
|
+
const esc = root.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
123
|
+
const re = new RegExp(String.raw `\[projects\.(?:"${esc}"|'${esc}')\][^\[]*?trust_level\s*=\s*"trusted"`, "s");
|
|
124
|
+
return re.test(text);
|
|
125
|
+
}
|
|
126
|
+
export const codex = {
|
|
127
|
+
id: "codex",
|
|
128
|
+
vendor: "openai",
|
|
129
|
+
probe: async () => probeVersion("codex"),
|
|
130
|
+
channels: (cfg) => channelsFromConfig("codex", cfg),
|
|
131
|
+
// --sandbox workspace-write is the autonomous sandbox mode (codex v0.144.1+)
|
|
132
|
+
headlessCommand: (promptFile, model) => `codex exec --sandbox workspace-write ${GITDIR_WRITABLE} --model ${shq(model)} "$(cat ${shq(promptFile)})"`,
|
|
133
|
+
// TUI uses expanded -a never -s workspace-write (exec-only flags do not apply)
|
|
134
|
+
// (--help 2026-07-09: valid approval policies are untrusted|on-request|never; the previously
|
|
135
|
+
// used `on-failure` is invalid and made codex exit 2 pre-inference)
|
|
136
|
+
interactiveCommand: (promptFile, model) => `codex -a never -s workspace-write ${GITDIR_WRITABLE} --model ${shq(model)} "$(cat ${shq(promptFile)})"`,
|
|
137
|
+
invoke(task, _cwd, a, ctx) {
|
|
138
|
+
return { command: this.headlessCommand(ctx.promptFile, a.model) };
|
|
139
|
+
},
|
|
140
|
+
parse: parseWorkerResult,
|
|
141
|
+
// v1.22 T5: seed [projects."<repoRoot>"] trust_level="trusted" so fresh worktrees never stall on
|
|
142
|
+
// "Do you trust this directory?" (OBS-16). doctor-only side effect.
|
|
143
|
+
trust: (repoRoot) => seedCodexTrust(repoRoot),
|
|
144
|
+
// v1.5 MODEL-01: file read only (no `codex models` subcommand exists, verified 2026-07-10).
|
|
145
|
+
// Already fails OPEN to [] internally — advisory detection, unlike gates' fail-closed.
|
|
146
|
+
listModels: async () => readCodexModelsCache().models,
|
|
147
|
+
// v1.5 MODEL-05: codex reads an offline cache, so "now" would re-stamp an ancient cache fresh and the
|
|
148
|
+
// 30-day staleness lint could never fire. Surface the cache's own fetched_at so doctor stamps the real age.
|
|
149
|
+
listModelsFetchedAt: () => readCodexModelsCache().fetchedAt,
|
|
150
|
+
collectUsage(cwd, sinceMs) {
|
|
151
|
+
try {
|
|
152
|
+
const real = realpathSync(cwd);
|
|
153
|
+
const sessions = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "sessions");
|
|
154
|
+
const dayDirs = codexSessionDayDirs(sessions, sinceMs);
|
|
155
|
+
const files = [];
|
|
156
|
+
for (const dayDir of dayDirs) {
|
|
157
|
+
const fullDay = join(sessions, dayDir);
|
|
158
|
+
let names;
|
|
159
|
+
try {
|
|
160
|
+
names = readdirSync(fullDay);
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
for (const f of names) {
|
|
166
|
+
if (!f.endsWith(".jsonl"))
|
|
167
|
+
continue;
|
|
168
|
+
const p = join(fullDay, f);
|
|
169
|
+
try {
|
|
170
|
+
files.push({ path: p, m: statSync(p).mtimeMs });
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
files.sort((a, b) => b.m - a.m);
|
|
178
|
+
const bounded = files.slice(0, MAX_SESSION_FILES);
|
|
179
|
+
let input = 0, output = 0, kept = false;
|
|
180
|
+
let cacheRead;
|
|
181
|
+
for (const { path: fp } of bounded) {
|
|
182
|
+
let text;
|
|
183
|
+
try {
|
|
184
|
+
text = readFileSync(fp, "utf8").slice(0, MAX_SESSION_BYTES);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const lines = text.split("\n");
|
|
190
|
+
let fileCwd;
|
|
191
|
+
for (const line of lines) {
|
|
192
|
+
if (!line.trim())
|
|
193
|
+
continue;
|
|
194
|
+
let recRaw;
|
|
195
|
+
try {
|
|
196
|
+
recRaw = JSON.parse(line);
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const rec = recRaw;
|
|
202
|
+
if (rec.type === "session_meta") {
|
|
203
|
+
const pcwd = rec.payload?.cwd;
|
|
204
|
+
fileCwd = typeof pcwd === "string" ? pcwd : undefined;
|
|
205
|
+
break;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (fileCwd !== real)
|
|
209
|
+
continue;
|
|
210
|
+
for (const line of lines) {
|
|
211
|
+
if (!line.trim())
|
|
212
|
+
continue;
|
|
213
|
+
let recRaw;
|
|
214
|
+
try {
|
|
215
|
+
recRaw = JSON.parse(line);
|
|
216
|
+
}
|
|
217
|
+
catch {
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
const rec = recRaw;
|
|
221
|
+
if (rec.type === "session_meta")
|
|
222
|
+
continue;
|
|
223
|
+
if (rec.type !== "event_msg")
|
|
224
|
+
continue;
|
|
225
|
+
const payload = rec.payload;
|
|
226
|
+
if (!payload || payload.type !== "token_count")
|
|
227
|
+
continue;
|
|
228
|
+
const info = payload.info;
|
|
229
|
+
if (!info || typeof info !== "object")
|
|
230
|
+
continue;
|
|
231
|
+
const ts = Date.parse(String(rec.timestamp));
|
|
232
|
+
if (!Number.isFinite(ts) || ts < sinceMs)
|
|
233
|
+
continue;
|
|
234
|
+
const last = info.last_token_usage;
|
|
235
|
+
if (!last || typeof last !== "object")
|
|
236
|
+
continue;
|
|
237
|
+
const u = last;
|
|
238
|
+
const n = (k) => (typeof u[k] === "number" ? u[k] : 0);
|
|
239
|
+
const inTok = n("input_tokens"), cached = n("cached_input_tokens");
|
|
240
|
+
input += Math.max(0, inTok - cached);
|
|
241
|
+
output += n("output_tokens");
|
|
242
|
+
if (cached > 0)
|
|
243
|
+
cacheRead = (cacheRead ?? 0) + cached;
|
|
244
|
+
kept = true;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
if (!kept)
|
|
248
|
+
return undefined;
|
|
249
|
+
const out = { input, output, ...(cacheRead !== undefined ? { cacheRead } : {}) };
|
|
250
|
+
const p = TokenUsageSchema.safeParse(out);
|
|
251
|
+
return p.success ? p.data : undefined;
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return undefined;
|
|
255
|
+
}
|
|
256
|
+
},
|
|
257
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { probeVersion } from "./claude-code.js";
|
|
3
|
+
import { parseWorkerResult } from "./prompt.js";
|
|
4
|
+
import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
5
|
+
// v1.22 T5 / OBS-19: cursor's interactive TUI parks on this exact dialog for every fresh worktree
|
|
6
|
+
// (--trust is print-only). No durable trusted-folders store is seedable from doctor; the daemon
|
|
7
|
+
// auto-answers Enter once per slot when the pane matches this fingerprint.
|
|
8
|
+
export const CURSOR_TRUST_DIALOG = {
|
|
9
|
+
fingerprint: "Workspace Trust Required",
|
|
10
|
+
key: "Enter",
|
|
11
|
+
};
|
|
12
|
+
// v1.5 MODEL-01: pure parser for `cursor-agent --list-models` (header "Available models", blank,
|
|
13
|
+
// then "id - Display Name" per line). Live-verified 2026-07-10, cursor-agent 2026.07.08.
|
|
14
|
+
// Token before " - "; `auto` is KEPT (raw persistence — lint-side filtering is Plan 08-02).
|
|
15
|
+
export function parseCursorModels(raw) {
|
|
16
|
+
return raw.trim().split("\n")
|
|
17
|
+
.map((l) => l.trim())
|
|
18
|
+
.filter((l) => l.length > 0 && l !== "Available models")
|
|
19
|
+
.map((l) => l.split(" - ")[0].trim())
|
|
20
|
+
.filter((id) => MODEL_ID_RE.test(id));
|
|
21
|
+
}
|
|
22
|
+
// SPEND-08: collectUsage is DELIBERATELY ABSENT — won't-implement-with-documented-reason,
|
|
23
|
+
// consistent with `.planning/REQUIREMENTS.md` SPEND-08 and `.planning/ROADMAP.md` Phase 29 Success
|
|
24
|
+
// Criterion #2 (operator disposition logged `.overseer/DECISIONS.md` 2026-07-11). Evidence:
|
|
25
|
+
// `.planning/phases/29-fleet-metering/29-RESEARCH.md` (cursor-agent 2026.07.09 exhaustive on-disk probe).
|
|
26
|
+
// NO billing usage in any store — `chats/<md5(cwd)>/store.db` = schema-less protobuf whose token-shaped
|
|
27
|
+
// varints decode to context-window occupancy, not billing tokens; `agent-transcripts/*.jsonl` = text-only
|
|
28
|
+
// (no usage, no per-record timestamp, no cwd); `ai-tracking` = code attribution only. The known
|
|
29
|
+
// `result.usage.*` is stdout-only behind `--print --trust`: a stdout tee is NOT "the adapter's own
|
|
30
|
+
// on-disk session store" (metering honesty invariant, REQUIREMENTS.md) AND is unavailable in the
|
|
31
|
+
// default interactive worker mode; a guessed protobuf parser is banned (SPEND-10 discipline). Consequence:
|
|
32
|
+
// cursor channels report honestly `unmetered`. Revisit if a future cursor-agent version persists usage
|
|
33
|
+
// on disk (re-probe is one ls + one sqlite3 .tables away).
|
|
34
|
+
export const cursorAgent = {
|
|
35
|
+
id: "cursor-agent",
|
|
36
|
+
vendor: "cursor",
|
|
37
|
+
probe: async () => probeVersion("cursor-agent"),
|
|
38
|
+
channels: (cfg) => channelsFromConfig("cursor-agent", cfg),
|
|
39
|
+
headlessCommand: (promptFile, model) => `cursor-agent -p "$(cat ${shq(promptFile)})" --model ${shq(model)} --force --output-format text`,
|
|
40
|
+
// NO --trust here: cursor rejects it outside --print ("--trust can only be used with --print/headless
|
|
41
|
+
// mode", exit 1 — v1.4 phase-1 incident). Fresh worktrees therefore show the trust dialog; v1.22 T5
|
|
42
|
+
// auto-answers it once per slot via trustDialog (OBS-19 burned 4×35m attempts without this).
|
|
43
|
+
interactiveCommand: (promptFile, model) => `cursor-agent --model ${shq(model)} --force "$(cat ${shq(promptFile)})"`,
|
|
44
|
+
invoke(task, _cwd, a, ctx) {
|
|
45
|
+
return { command: this.headlessCommand(ctx.promptFile, a.model) };
|
|
46
|
+
},
|
|
47
|
+
parse: parseWorkerResult,
|
|
48
|
+
// v1.22 T5: no writable durable store for interactive trust (headless --trust is non-persistent and
|
|
49
|
+
// print-only). Doctor names the dialog; the daemon auto-answers it via trustDialog once per slot.
|
|
50
|
+
trust: () => ({
|
|
51
|
+
status: "action-required",
|
|
52
|
+
command: 'accept the cursor-agent "Workspace Trust Required" dialog (Enter) — drovr auto-answers once per slot',
|
|
53
|
+
}),
|
|
54
|
+
trustDialog: CURSOR_TRUST_DIALOG,
|
|
55
|
+
// v1.5 MODEL-01: fail OPEN to [] (advisory detection, unlike gates). --list-models may touch the
|
|
56
|
+
// network (1.35s wall vs 0.34s CPU, RESEARCH A1); 15s timeout + fail-open covers offline.
|
|
57
|
+
// Live-verified 2026-07-10, cursor-agent 2026.07.08.
|
|
58
|
+
listModels: async () => {
|
|
59
|
+
const r = spawnSync("cursor-agent", ["--list-models"], { encoding: "utf8", timeout: 15000 });
|
|
60
|
+
return r.error || r.status !== 0 ? [] : parseCursorModels(r.stdout || "");
|
|
61
|
+
},
|
|
62
|
+
};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { DrovrConfig } from "../config/config.js";
|
|
2
|
+
import type { Task } from "../graph/schema.js";
|
|
3
|
+
import { type Assignment, type AuthHealth, type BillingChannel, type ContextUsage, type Invocation, type SessionRef, type TokenUsage, type WorkerAdapter, type WorkerResult } from "./types.js";
|
|
4
|
+
export interface FakeScript {
|
|
5
|
+
tasks: Record<string, Array<{
|
|
6
|
+
shell: string;
|
|
7
|
+
result?: {
|
|
8
|
+
ok: boolean;
|
|
9
|
+
summary: string;
|
|
10
|
+
deviations?: string[];
|
|
11
|
+
};
|
|
12
|
+
usage?: {
|
|
13
|
+
input: number;
|
|
14
|
+
output: number;
|
|
15
|
+
cacheRead?: number;
|
|
16
|
+
cacheWrite?: number;
|
|
17
|
+
reasoning?: number;
|
|
18
|
+
};
|
|
19
|
+
}>>;
|
|
20
|
+
judge?: unknown;
|
|
21
|
+
review?: unknown;
|
|
22
|
+
consult?: unknown;
|
|
23
|
+
}
|
|
24
|
+
export declare class FakeAdapter implements WorkerAdapter {
|
|
25
|
+
private scriptPath;
|
|
26
|
+
id: string;
|
|
27
|
+
vendor: string;
|
|
28
|
+
private script;
|
|
29
|
+
private attempts;
|
|
30
|
+
constructor(scriptPath: string);
|
|
31
|
+
probe(): Promise<AuthHealth>;
|
|
32
|
+
channels(_cfg: DrovrConfig): BillingChannel[];
|
|
33
|
+
private judgeIdx;
|
|
34
|
+
headlessCommand(promptFile: string, _model: string): string;
|
|
35
|
+
resumeCommand(_sessionId: string, promptFile: string, model: string): string;
|
|
36
|
+
private stepCommand;
|
|
37
|
+
collectUsage(cwd: string, sinceMs: number): TokenUsage | undefined;
|
|
38
|
+
contextUsage(_session: SessionRef): ContextUsage | null;
|
|
39
|
+
private nonceFor;
|
|
40
|
+
invoke(task: Task, _cwd: string, _a: Assignment, ctx: {
|
|
41
|
+
promptFile: string;
|
|
42
|
+
}): Invocation;
|
|
43
|
+
interactiveCommand(promptFile: string, _model: string): string | null;
|
|
44
|
+
parse(output: string, nonce: string): WorkerResult;
|
|
45
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
3
|
+
import { parseWorkerResult } from "./prompt.js";
|
|
4
|
+
import { shq, TokenUsageSchema } from "./types.js";
|
|
5
|
+
// ponytail: deterministic scripted adapter — the whole integration suite runs on it, zero tokens.
|
|
6
|
+
export class FakeAdapter {
|
|
7
|
+
scriptPath;
|
|
8
|
+
id = "fake";
|
|
9
|
+
vendor = "fake-a";
|
|
10
|
+
script;
|
|
11
|
+
attempts = new Map();
|
|
12
|
+
constructor(scriptPath) {
|
|
13
|
+
this.scriptPath = scriptPath;
|
|
14
|
+
this.script = JSON.parse(readFileSync(scriptPath, "utf8"));
|
|
15
|
+
}
|
|
16
|
+
async probe() {
|
|
17
|
+
return { installed: true, authed: true, version: "fake", models: ["fake-1", "fake-2"] };
|
|
18
|
+
}
|
|
19
|
+
channels(_cfg) {
|
|
20
|
+
return [
|
|
21
|
+
{ adapter: "fake", vendor: "fake-a", model: "fake-1", channel: "sub", tier: "frontier" },
|
|
22
|
+
{ adapter: "fake", vendor: "fake-b", model: "fake-2", channel: "api", tier: "frontier" },
|
|
23
|
+
];
|
|
24
|
+
}
|
|
25
|
+
judgeIdx = 0; // Phase 47: per-instance judge verdict cursor — advances only on DROVR-JUDGE prompts
|
|
26
|
+
headlessCommand(promptFile, _model) {
|
|
27
|
+
// Phase 47 (GATE-09): detect role at COMMAND-BUILD time. runHeadless/runViaDriver write the prompt
|
|
28
|
+
// file BEFORE calling headlessCommand, so it exists here. The counter advances ONLY when the prompt
|
|
29
|
+
// contains DROVR-JUDGE — a review/consult prompt never touches it (research Pitfall 3).
|
|
30
|
+
let isJudge = false;
|
|
31
|
+
try {
|
|
32
|
+
isJudge = /DROVR-JUDGE/.test(readFileSync(promptFile, "utf8"));
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// unreadable promptFile: can't detect role; serve static values (legacy headless-call behavior)
|
|
36
|
+
}
|
|
37
|
+
const serve = (key) => {
|
|
38
|
+
let val = this.script[key];
|
|
39
|
+
if (key === "judge" && isJudge && Array.isArray(val)) {
|
|
40
|
+
// array served sequentially per judge prompt, clamped to last (steps[min(n,len-1)] idiom)
|
|
41
|
+
val = val[Math.min(this.judgeIdx, val.length - 1)];
|
|
42
|
+
this.judgeIdx++;
|
|
43
|
+
}
|
|
44
|
+
const p = join(dirname(this.scriptPath), `${key}.json`);
|
|
45
|
+
writeFileSync(p, JSON.stringify(val ?? {}, null, 1));
|
|
46
|
+
return p;
|
|
47
|
+
};
|
|
48
|
+
return `bash -c 'grep -q DROVR-JUDGE ${shq(promptFile)} && cat ${shq(serve("judge"))}; grep -q DROVR-REVIEW ${shq(promptFile)} && cat ${shq(serve("review"))}; grep -q DROVR-CONSULT ${shq(promptFile)} && cat ${shq(serve("consult"))}; true'`;
|
|
49
|
+
}
|
|
50
|
+
resumeCommand(_sessionId, promptFile, model) {
|
|
51
|
+
return this.interactiveCommand(promptFile, model) ?? this.headlessCommand(promptFile, model);
|
|
52
|
+
}
|
|
53
|
+
stepCommand(taskId, n, nonce) {
|
|
54
|
+
const steps = this.script.tasks[taskId] ?? [];
|
|
55
|
+
const step = steps[Math.min(n, steps.length - 1)] ?? {
|
|
56
|
+
shell: "true",
|
|
57
|
+
result: { ok: false, summary: `fake script has no entry for ${taskId}` },
|
|
58
|
+
};
|
|
59
|
+
// SPEND-01: synthetic usage as a TIMESTAMPED disk record the fake's own reader picks up post-hoc —
|
|
60
|
+
// never a trailer, never pane text. node stamps a real ms-precision ISO time (bash 3.2 on darwin has
|
|
61
|
+
// no ms clock) so the record lands at/after this attempt's dispatch and clears the sinceMs cursor.
|
|
62
|
+
// Written AFTER the step's `git add`/commit ⇒ stays untracked ⇒ invisible to the scope/evidence gates.
|
|
63
|
+
const usageWrite = step.usage
|
|
64
|
+
? `; node -e ${shq(`require("fs").writeFileSync(".drovr-usage.json", JSON.stringify({timestamp:new Date().toISOString(), usage:${JSON.stringify(step.usage)}}))`)}`
|
|
65
|
+
: "";
|
|
66
|
+
const base = !step.result
|
|
67
|
+
? `bash -c ${shq(step.shell)}` // no trailer: scripted stall/quota
|
|
68
|
+
: `bash -c ${shq(step.shell)}; echo ${shq(`DROVR_RESULT_${nonce} ` + JSON.stringify({ deviations: [], ...step.result }))}`;
|
|
69
|
+
return base + usageWrite;
|
|
70
|
+
}
|
|
71
|
+
// SPEND-01: read the cwd-keyed usage record from DISK (never a pane), honoring the attempt cursor.
|
|
72
|
+
// A record stamped before sinceMs, an unparseable stamp, or a bad usage shape all fail OPEN to
|
|
73
|
+
// undefined ⇒ unmetered — never 0, never a thrown error that could fail a healthy task.
|
|
74
|
+
collectUsage(cwd, sinceMs) {
|
|
75
|
+
try {
|
|
76
|
+
const rec = JSON.parse(readFileSync(join(cwd, ".drovr-usage.json"), "utf8"));
|
|
77
|
+
const ts = Date.parse(rec?.timestamp);
|
|
78
|
+
if (!Number.isFinite(ts) || ts < sinceMs)
|
|
79
|
+
return undefined; // cursor honored, fail-open
|
|
80
|
+
const p = TokenUsageSchema.safeParse(rec?.usage);
|
|
81
|
+
return p.success ? p.data : undefined;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined; // no record / unreadable ⇒ unmetered
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
// v1.23 T1: fake has no knowable session store — always null (unknown). Callers must NOT coerce
|
|
88
|
+
// null to 0 or treat it as over-threshold (telemetry fail-open).
|
|
89
|
+
contextUsage(_session) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
// the run nonce lives in the prompt writePrompt handed us; echo a matching trailer so
|
|
93
|
+
// parseWorkerResult(output, nonce) succeeds exactly as before the nonce existed
|
|
94
|
+
nonceFor(promptFile) {
|
|
95
|
+
try {
|
|
96
|
+
return /DROVR_RESULT_([0-9a-z]+)/.exec(readFileSync(promptFile, "utf8"))?.[1] ?? "";
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
return "";
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
invoke(task, _cwd, _a, ctx) {
|
|
103
|
+
const n = this.attempts.get(task.id) ?? 0;
|
|
104
|
+
this.attempts.set(task.id, n + 1);
|
|
105
|
+
return { command: this.stepCommand(task.id, n, this.nonceFor(ctx.promptFile)) };
|
|
106
|
+
}
|
|
107
|
+
// task + attempt come from writePrompt's `<taskId>-a<n>.md` contract — interactiveCommand has no task arg
|
|
108
|
+
interactiveCommand(promptFile, _model) {
|
|
109
|
+
const m = /([A-Za-z0-9_-]+)-a(\d+)\.md$/.exec(promptFile);
|
|
110
|
+
return m ? this.stepCommand(m[1], Number(m[2]), this.nonceFor(promptFile)) : null;
|
|
111
|
+
}
|
|
112
|
+
parse(output, nonce) {
|
|
113
|
+
return parseWorkerResult(output, nonce);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { readFileSync } from "node:fs";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { probeVersion } from "./claude-code.js";
|
|
6
|
+
import { parseWorkerResult } from "./prompt.js";
|
|
7
|
+
import { channelsFromConfig, MODEL_ID_RE, shq } from "./types.js";
|
|
8
|
+
// GROK-03: collectUsage is DELIBERATELY ABSENT — documented won't-implement, consistent with the
|
|
9
|
+
// metering-honesty invariant (adapters/types.ts:65-67: "harness-emitted structured usage ONLY, read
|
|
10
|
+
// POST-HOC from the CLI's own cwd-keyed store"). Five-way diagnosis (all [LIVE-RES] 2026-07-11,
|
|
11
|
+
// grok 0.2.93, evidence in .planning/phases/40-native-grok-adapter/40-RESEARCH.md F-4):
|
|
12
|
+
// (1) Session store ~/.grok/sessions/<percent-encoded-realpath>/<uuid>/ (chat_history.jsonl,
|
|
13
|
+
// events.jsonl, summary.json, …) — zero matches for input_tokens|output_tokens|total_tokens|
|
|
14
|
+
// "usage"|"cost" across every file, re-verified on a fresh session.
|
|
15
|
+
// (2) No `grok usage` subcommand (full subcommand list captured: agent, completions, dashboard,
|
|
16
|
+
// export, help, import, inspect, leader, login, logout, mcp, memory, models, plugin, sessions,
|
|
17
|
+
// setup, trace, update, version, worktree, wrap).
|
|
18
|
+
// (3) No newer CLI — `grok update --check --json` → {"updateAvailable":false,…}.
|
|
19
|
+
// (4) `grok trace <id> --local` export and ~/.grok/logs/unified.jsonl (1,447 lines) — zero counters.
|
|
20
|
+
// (5) --debug-file traces DO carry real per-turn input_tokens=… output_tokens=… cache_read_tokens=…
|
|
21
|
+
// — a REAL source, REJECTED for stated reasons:
|
|
22
|
+
// (a) it exists ONLY because drovr would inject --debug-file into every dispatch — a side-channel
|
|
23
|
+
// drovr creates, not a record the CLI keeps (same class as cursor's rejected --print --trust
|
|
24
|
+
// stdout tee, cursor-agent.ts:25-28);
|
|
25
|
+
// (b) DECISIVE: at DEBUG level the log verbatim-dumps the full OIDC bearer JWT and every MCP server
|
|
26
|
+
// env secret — an actual plaintext SUPABASE_ACCESS_TOKEN was observed — drovr forcing a
|
|
27
|
+
// per-attempt secret-spilling file onto disk to meter a sub channel fails its own security
|
|
28
|
+
// posture;
|
|
29
|
+
// (c) it is tracing diagnostics text with no format contract (xAI can reshape it in any patch).
|
|
30
|
+
// Consequence: grok channels report honestly `unmetered` — daemon.ts's optional-chaining on the
|
|
31
|
+
// absent method and report.ts's "unmetered (adapter reports no usage)" line render it by construction
|
|
32
|
+
// with ZERO changes to daemon/journal/report (they already handle absence; SPEND-11 proved a silent
|
|
33
|
+
// zero is worse than an honest nothing). Revisit if grok ships a usage block in the
|
|
34
|
+
// `-p --output-format json` envelope or a counter in the session store — re-probe is one
|
|
35
|
+
// `grok -p` + one grep away. This comment is the defense against a future maintainer "helpfully"
|
|
36
|
+
// wiring up the secret-spilling trace path and writing the operator's credentials to disk.
|
|
37
|
+
// ponytail: grok loads the operator's global Claude-Code-compat config — a 165-char prompt measured
|
|
38
|
+
// input_tokens=82874 (orchestrator research, 2026-07-11); no --strict-mcp-config analog in 0.2.93.
|
|
39
|
+
// 40-02 checks grok --help / ~/.grok/config.toml for a documented compat toggle; re-MEASUREMENT is
|
|
40
|
+
// deferred to an operator-run step (the only token counter is the rejected DEBUG trace — see above).
|
|
41
|
+
// GROK-01. Auth verdict from ~/.grok/auth.json ONLY. NEVER consult `grok models`: its banner is
|
|
42
|
+
// live-proven WRONG in both directions ("You are not authenticated." while authed 2026-07-11 AM
|
|
43
|
+
// [40-CONTEXT D-03]; "You are logged in with grok.com." hours later same token [40-RESEARCH F-1];
|
|
44
|
+
// suspected leader-process staleness via ~/.grok/leader.sock). An entry is authed iff it has a
|
|
45
|
+
// non-empty refresh_token OR an unexpired expires_at — refresh_token alone suffices because the CLI
|
|
46
|
+
// auto-refreshes an expired access key (observed in the DEBUG trace: authenticate method=cached_token
|
|
47
|
+
// → set api_key). Gating on expires_at alone reintroduces the false-negative class GROK-01 exists to
|
|
48
|
+
// kill. Missing/unreadable/empty file ⇒ authed:false (truthful — `grok logout` clears the file).
|
|
49
|
+
// Nested shape { "<issuer>::<client_id>": { refresh_token?, expires_at?, … } } [40-RESEARCH F-2].
|
|
50
|
+
export function grokAuthed(authJsonText, nowMs) {
|
|
51
|
+
try {
|
|
52
|
+
const j = JSON.parse(authJsonText);
|
|
53
|
+
return Object.values(j).some((e) => {
|
|
54
|
+
if (!e || typeof e !== "object")
|
|
55
|
+
return false;
|
|
56
|
+
const { refresh_token, expires_at } = e;
|
|
57
|
+
// refresh_token presence DOMINATES an expired expires_at — the CLI auto-refreshes (GROK-01 trap).
|
|
58
|
+
if (typeof refresh_token === "string" && refresh_token.length > 0)
|
|
59
|
+
return true;
|
|
60
|
+
const exp = Date.parse(String(expires_at));
|
|
61
|
+
return Number.isFinite(exp) && exp > nowMs;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
return false; // missing/unreadable/empty/garbage ⇒ truthfully unauthed (HYG-05 polarity)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// GROK-04 listModels parse (MODEL-05 WR-02): anchor by CONTENT on the exact "Available models:" header
|
|
69
|
+
// — banner lines above it (whatever they claim about auth) never reach the parser BY CONSTRUCTION
|
|
70
|
+
// (40-RESEARCH F-1 verbatim fixture). Strip a leading "* "/"- " prefix and a trailing " (default)"
|
|
71
|
+
// suffix; drop empties; filter through MODEL_ID_RE. No header ⇒ [] (fail-open — listing is advisory).
|
|
72
|
+
export function parseGrokModels(raw) {
|
|
73
|
+
const lines = raw.split("\n");
|
|
74
|
+
const headerIdx = lines.findIndex((l) => l.trim() === "Available models:");
|
|
75
|
+
if (headerIdx === -1)
|
|
76
|
+
return [];
|
|
77
|
+
return lines.slice(headerIdx + 1)
|
|
78
|
+
.map((l) => l.trim().replace(/^[*-]\s+/, "").replace(/\s+\(default\)$/, ""))
|
|
79
|
+
.filter((id) => id.length > 0 && MODEL_ID_RE.test(id));
|
|
80
|
+
}
|
|
81
|
+
export const grok = {
|
|
82
|
+
id: "grok",
|
|
83
|
+
// FLEET-04 cross-vendor honesty: xai is diversity-distinct from anthropic/openai/cursor/zhipu.
|
|
84
|
+
// MUST equal tiers.grok.vendor (a test pins the equality).
|
|
85
|
+
vendor: "xai",
|
|
86
|
+
probe: async () => {
|
|
87
|
+
const h = probeVersion("grok");
|
|
88
|
+
if (!h.installed)
|
|
89
|
+
return h;
|
|
90
|
+
let authed = false;
|
|
91
|
+
try {
|
|
92
|
+
authed = grokAuthed(readFileSync(join(homedir(), ".grok", "auth.json"), "utf8"), Date.now());
|
|
93
|
+
}
|
|
94
|
+
catch { /* missing/unreadable ⇒ truthfully unauthed */ }
|
|
95
|
+
return authed
|
|
96
|
+
? { ...h, note: "auth verified via ~/.grok/auth.json (free, no network; `grok models` banner is state-flappy — never parsed for auth)" }
|
|
97
|
+
: { ...h, authed: false, note: "no valid ~/.grok/auth.json entry (grok login to fix)" };
|
|
98
|
+
},
|
|
99
|
+
channels: (cfg) => channelsFromConfig("grok", cfg),
|
|
100
|
+
// GROK-02 headless. --output-format plain is the default but pin it explicitly; live-verified
|
|
101
|
+
// 2026-07-11 (40-RESEARCH F-3): trailer intact + unwrapped, exit 0. "$(cat file)" matches every
|
|
102
|
+
// other adapter and is already quoting-proven. --permission-mode bypassPermissions is the
|
|
103
|
+
// autonomous-worker analog of claude's --dangerously-skip-permissions. NEVER pass grok's
|
|
104
|
+
// -w/--worktree flag — grok manages its OWN worktrees; a nested worktree inside drovr's worktree
|
|
105
|
+
// is scope-gate chaos (src/run/git.ts owns worktrees).
|
|
106
|
+
headlessCommand: (promptFile, model) => `grok -p "$(cat ${shq(promptFile)})" --model ${shq(model)} --permission-mode bypassPermissions --output-format plain`,
|
|
107
|
+
// GROK-02 interactive. Positional prompt seeds the TUI (40-CONTEXT D-01). Same permission flag;
|
|
108
|
+
// NEVER the -w/--worktree flag (see headless comment above — drovr owns worktrees).
|
|
109
|
+
interactiveCommand: (promptFile, model) => `grok --model ${shq(model)} --permission-mode bypassPermissions "$(cat ${shq(promptFile)})"`,
|
|
110
|
+
invoke(task, _cwd, a, ctx) {
|
|
111
|
+
return { command: this.headlessCommand(ctx.promptFile, a.model) };
|
|
112
|
+
},
|
|
113
|
+
parse: parseWorkerResult, // prompt.ts — last-valid-trailer, hard-wrap tolerant. No grok fork.
|
|
114
|
+
// v1.5 MODEL-01: fail OPEN to [] (advisory detection, unlike gates). Parses the model LIST only —
|
|
115
|
+
// the auth banner on line 1 is ignored BY CONSTRUCTION (parseGrokModels anchors on the header).
|
|
116
|
+
// Advisory/doctor-only — never in the dispatch or auth path (types.ts:56-60).
|
|
117
|
+
// Live-verified 2026-07-11 format, grok 0.2.93, 40-RESEARCH F-1.
|
|
118
|
+
listModels: async () => {
|
|
119
|
+
const r = spawnSync("grok", ["models"], { encoding: "utf8", timeout: 15000 });
|
|
120
|
+
return r.error || r.status !== 0 ? [] : parseGrokModels(r.stdout || "");
|
|
121
|
+
},
|
|
122
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { DrovrConfig } from "../config/config.js";
|
|
2
|
+
import { type AuthHealth, type WorkerAdapter } from "./types.js";
|
|
3
|
+
export declare const SEED_STAMPED = "2026-07-09";
|
|
4
|
+
export declare const MODEL_STALE_DAYS = 30;
|
|
5
|
+
export declare function modelLints(cfg: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[]): string[];
|
|
6
|
+
export declare function suggestOverlay(cfg: DrovrConfig, health: Record<string, AuthHealth>, adapters: WorkerAdapter[]): string;
|