ai-whisper 0.5.5 → 0.5.7
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 +6 -5
- package/dist/bin/broker-daemon.js +205 -30
- package/dist/bin/companion-agent.js +600 -78
- package/dist/bin/relay-monitor.js +218 -32
- package/dist/bin/turn-event-shim.js +85 -0
- package/dist/bin/whisper.js +1480 -134
- package/package.json +6 -5
- package/skills/ai-whisper-plan-execution/SKILL.md +65 -0
- package/dist/.tsbuildinfo +0 -1
- package/dist/bin/broker-daemon.d.ts +0 -3
- package/dist/bin/broker-daemon.d.ts.map +0 -1
- package/dist/bin/companion-agent.d.ts +0 -3
- package/dist/bin/companion-agent.d.ts.map +0 -1
- package/dist/bin/relay-monitor.d.ts +0 -3
- package/dist/bin/relay-monitor.d.ts.map +0 -1
- package/dist/bin/whisper.d.ts +0 -3
- package/dist/bin/whisper.d.ts.map +0 -1
- package/dist/commands/collab/dashboard.d.ts +0 -18
- package/dist/commands/collab/dashboard.d.ts.map +0 -1
- package/dist/commands/collab/inspect.d.ts +0 -23
- package/dist/commands/collab/inspect.d.ts.map +0 -1
- package/dist/commands/collab/mount.d.ts +0 -55
- package/dist/commands/collab/mount.d.ts.map +0 -1
- package/dist/commands/collab/reconnect.d.ts +0 -45
- package/dist/commands/collab/reconnect.d.ts.map +0 -1
- package/dist/commands/collab/recover.d.ts +0 -33
- package/dist/commands/collab/recover.d.ts.map +0 -1
- package/dist/commands/collab/relay-monitor.d.ts +0 -21
- package/dist/commands/collab/relay-monitor.d.ts.map +0 -1
- package/dist/commands/collab/start.d.ts +0 -53
- package/dist/commands/collab/start.d.ts.map +0 -1
- package/dist/commands/collab/status.d.ts +0 -6
- package/dist/commands/collab/status.d.ts.map +0 -1
- package/dist/commands/collab/stop.d.ts +0 -13
- package/dist/commands/collab/stop.d.ts.map +0 -1
- package/dist/commands/collab/tell.d.ts +0 -27
- package/dist/commands/collab/tell.d.ts.map +0 -1
- package/dist/commands/skill/install.d.ts +0 -13
- package/dist/commands/skill/install.d.ts.map +0 -1
- package/dist/commands/workflow/cancel.d.ts +0 -14
- package/dist/commands/workflow/cancel.d.ts.map +0 -1
- package/dist/commands/workflow/inspect.d.ts +0 -41
- package/dist/commands/workflow/inspect.d.ts.map +0 -1
- package/dist/commands/workflow/list.d.ts +0 -28
- package/dist/commands/workflow/list.d.ts.map +0 -1
- package/dist/commands/workflow/pause.d.ts +0 -14
- package/dist/commands/workflow/pause.d.ts.map +0 -1
- package/dist/commands/workflow/resume.d.ts +0 -17
- package/dist/commands/workflow/resume.d.ts.map +0 -1
- package/dist/commands/workflow/start.d.ts +0 -66
- package/dist/commands/workflow/start.d.ts.map +0 -1
- package/dist/commands/workflow/types.d.ts +0 -2
- package/dist/commands/workflow/types.d.ts.map +0 -1
- package/dist/create-cli.d.ts +0 -4
- package/dist/create-cli.d.ts.map +0 -1
- package/dist/index.d.ts +0 -5
- package/dist/index.d.ts.map +0 -1
- package/dist/runtime/action-inference.d.ts +0 -3
- package/dist/runtime/action-inference.d.ts.map +0 -1
- package/dist/runtime/artifact-input.d.ts +0 -2
- package/dist/runtime/artifact-input.d.ts.map +0 -1
- package/dist/runtime/assistant-turn-capture.d.ts +0 -14
- package/dist/runtime/assistant-turn-capture.d.ts.map +0 -1
- package/dist/runtime/bracketed-paste-detector.d.ts +0 -16
- package/dist/runtime/bracketed-paste-detector.d.ts.map +0 -1
- package/dist/runtime/broker-artifact-service.d.ts +0 -48
- package/dist/runtime/broker-artifact-service.d.ts.map +0 -1
- package/dist/runtime/broker-connect.d.ts +0 -9
- package/dist/runtime/broker-connect.d.ts.map +0 -1
- package/dist/runtime/broker-daemon.d.ts +0 -25
- package/dist/runtime/broker-daemon.d.ts.map +0 -1
- package/dist/runtime/busy-indicator.d.ts +0 -11
- package/dist/runtime/busy-indicator.d.ts.map +0 -1
- package/dist/runtime/capture-handback-text.d.ts +0 -56
- package/dist/runtime/capture-handback-text.d.ts.map +0 -1
- package/dist/runtime/clipboard-change-count.d.ts +0 -12
- package/dist/runtime/clipboard-change-count.d.ts.map +0 -1
- package/dist/runtime/clipboard-handback-capture.d.ts +0 -13
- package/dist/runtime/clipboard-handback-capture.d.ts.map +0 -1
- package/dist/runtime/collab-resolver.d.ts +0 -36
- package/dist/runtime/collab-resolver.d.ts.map +0 -1
- package/dist/runtime/companion-agent-loop.d.ts +0 -13
- package/dist/runtime/companion-agent-loop.d.ts.map +0 -1
- package/dist/runtime/context-injector.d.ts +0 -20
- package/dist/runtime/context-injector.d.ts.map +0 -1
- package/dist/runtime/context-policy.d.ts +0 -3
- package/dist/runtime/context-policy.d.ts.map +0 -1
- package/dist/runtime/current-tty.d.ts +0 -2
- package/dist/runtime/current-tty.d.ts.map +0 -1
- package/dist/runtime/dashboard-glyph.d.ts +0 -12
- package/dist/runtime/dashboard-glyph.d.ts.map +0 -1
- package/dist/runtime/dashboard-state.d.ts +0 -183
- package/dist/runtime/dashboard-state.d.ts.map +0 -1
- package/dist/runtime/dashboard-view.d.ts +0 -34
- package/dist/runtime/dashboard-view.d.ts.map +0 -1
- package/dist/runtime/dashboard.d.ts +0 -46
- package/dist/runtime/dashboard.d.ts.map +0 -1
- package/dist/runtime/evaluator-config.d.ts +0 -21
- package/dist/runtime/evaluator-config.d.ts.map +0 -1
- package/dist/runtime/evaluator-observer.d.ts +0 -8
- package/dist/runtime/evaluator-observer.d.ts.map +0 -1
- package/dist/runtime/id-factory.d.ts +0 -6
- package/dist/runtime/id-factory.d.ts.map +0 -1
- package/dist/runtime/launcher.d.ts +0 -39
- package/dist/runtime/launcher.d.ts.map +0 -1
- package/dist/runtime/live-session-broker-executor.d.ts +0 -8
- package/dist/runtime/live-session-broker-executor.d.ts.map +0 -1
- package/dist/runtime/live-session.d.ts +0 -33
- package/dist/runtime/live-session.d.ts.map +0 -1
- package/dist/runtime/load-dot-env.d.ts +0 -6
- package/dist/runtime/load-dot-env.d.ts.map +0 -1
- package/dist/runtime/local-multiline-composer.d.ts +0 -23
- package/dist/runtime/local-multiline-composer.d.ts.map +0 -1
- package/dist/runtime/mount-session-main.d.ts +0 -67
- package/dist/runtime/mount-session-main.d.ts.map +0 -1
- package/dist/runtime/mounted-turn-owned-relay.d.ts +0 -170
- package/dist/runtime/mounted-turn-owned-relay.d.ts.map +0 -1
- package/dist/runtime/on-demand-processing.d.ts +0 -23
- package/dist/runtime/on-demand-processing.d.ts.map +0 -1
- package/dist/runtime/operator-inspect-captures.d.ts +0 -6
- package/dist/runtime/operator-inspect-captures.d.ts.map +0 -1
- package/dist/runtime/operator-inspect-verdicts.d.ts +0 -6
- package/dist/runtime/operator-inspect-verdicts.d.ts.map +0 -1
- package/dist/runtime/operator-inspect.d.ts +0 -138
- package/dist/runtime/operator-inspect.d.ts.map +0 -1
- package/dist/runtime/paths.d.ts +0 -2
- package/dist/runtime/paths.d.ts.map +0 -1
- package/dist/runtime/port-allocator.d.ts +0 -7
- package/dist/runtime/port-allocator.d.ts.map +0 -1
- package/dist/runtime/port-utils.d.ts +0 -3
- package/dist/runtime/port-utils.d.ts.map +0 -1
- package/dist/runtime/process-start-time.d.ts +0 -7
- package/dist/runtime/process-start-time.d.ts.map +0 -1
- package/dist/runtime/provider-submit-strategy.d.ts +0 -14
- package/dist/runtime/provider-submit-strategy.d.ts.map +0 -1
- package/dist/runtime/providers.d.ts +0 -18
- package/dist/runtime/providers.d.ts.map +0 -1
- package/dist/runtime/record-evaluator-status.d.ts +0 -9
- package/dist/runtime/record-evaluator-status.d.ts.map +0 -1
- package/dist/runtime/recovery-guard.d.ts +0 -12
- package/dist/runtime/recovery-guard.d.ts.map +0 -1
- package/dist/runtime/relay-directive.d.ts +0 -4
- package/dist/runtime/relay-directive.d.ts.map +0 -1
- package/dist/runtime/relay-line-buffer.d.ts +0 -22
- package/dist/runtime/relay-line-buffer.d.ts.map +0 -1
- package/dist/runtime/relay-monitor.d.ts +0 -26
- package/dist/runtime/relay-monitor.d.ts.map +0 -1
- package/dist/runtime/relay-orchestrator-evaluator.d.ts +0 -160
- package/dist/runtime/relay-orchestrator-evaluator.d.ts.map +0 -1
- package/dist/runtime/relay-orchestrator.d.ts +0 -116
- package/dist/runtime/relay-orchestrator.d.ts.map +0 -1
- package/dist/runtime/relay-pane-writer.d.ts +0 -28
- package/dist/runtime/relay-pane-writer.d.ts.map +0 -1
- package/dist/runtime/relay-service.d.ts +0 -76
- package/dist/runtime/relay-service.d.ts.map +0 -1
- package/dist/runtime/relay-view-input.d.ts +0 -17
- package/dist/runtime/relay-view-input.d.ts.map +0 -1
- package/dist/runtime/relay-view-state.d.ts +0 -87
- package/dist/runtime/relay-view-state.d.ts.map +0 -1
- package/dist/runtime/relay-view.d.ts +0 -16
- package/dist/runtime/relay-view.d.ts.map +0 -1
- package/dist/runtime/reply-wait.d.ts +0 -21
- package/dist/runtime/reply-wait.d.ts.map +0 -1
- package/dist/runtime/runtime-debug-log.d.ts +0 -7
- package/dist/runtime/runtime-debug-log.d.ts.map +0 -1
- package/dist/runtime/state-root.d.ts +0 -3
- package/dist/runtime/state-root.d.ts.map +0 -1
- package/dist/runtime/terminal-input-normalizer.d.ts +0 -11
- package/dist/runtime/terminal-input-normalizer.d.ts.map +0 -1
- package/dist/runtime/theme.d.ts +0 -16
- package/dist/runtime/theme.d.ts.map +0 -1
- package/dist/runtime/version-check.d.ts +0 -17
- package/dist/runtime/version-check.d.ts.map +0 -1
- package/dist/runtime/wait-for-broker-ready.d.ts +0 -13
- package/dist/runtime/wait-for-broker-ready.d.ts.map +0 -1
- package/dist/runtime/workspace-id.d.ts +0 -3
- package/dist/runtime/workspace-id.d.ts.map +0 -1
- package/dist/skills/ai-whisper-bugfix/SKILL.md +0 -163
- package/dist/skills/ai-whisper-code-review/SKILL.md +0 -79
- package/dist/skills/ai-whisper-ralph/SKILL.md +0 -163
- package/dist/skills/ai-whisper-sdd/SKILL.md +0 -155
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# ai-whisper
|
|
2
2
|
|
|
3
|
-
ai-whisper
|
|
3
|
+
ai-whisper pairs two coding agents — mount any two of Claude, Codex, and ezio — into a terminal-native pair that hand work back and forth under a single baton, so one agent implements while the other reviews, and a structured workflow drives the loop to a finished, reviewed deliverable without a human babysitting every round.
|
|
4
4
|
|
|
5
5
|
## Magic moment
|
|
6
6
|
|
|
@@ -52,10 +52,11 @@ It is **not** for:
|
|
|
52
52
|
|
|
53
53
|
## Prerequisites
|
|
54
54
|
|
|
55
|
-
ai-whisper drives the *real* Claude and Codex CLIs, so you
|
|
55
|
+
You pair any two of three agents — `claude`, `codex`, and `ezio`. ai-whisper drives the *real* Claude and Codex CLIs, so install and authenticate whichever of those two you plan to mount first; `ezio` is protocol-native and ships with ai-whisper, so it needs no separate CLI.
|
|
56
56
|
|
|
57
57
|
- **[Claude Code CLI](https://claude.com/claude-code)** — the `claude` command, signed in.
|
|
58
58
|
- **[Codex CLI](https://github.com/openai/codex)** — the `codex` command, signed in.
|
|
59
|
+
- **ezio** *(optional)* — bundled with ai-whisper; mount it with `whisper collab mount ezio`, no separate install.
|
|
59
60
|
- **Node.js 22+**.
|
|
60
61
|
- **An LLM evaluator with credentials** — workflows are gated by it and refuse to start without it. See [Evaluator configuration](docs/evaluator-configuration.md).
|
|
61
62
|
- **tmux** *(optional)* — only for `whisper collab start`, which auto-arranges both agents into panes. The mount flow below does not need it.
|
|
@@ -79,7 +80,7 @@ pnpm install
|
|
|
79
80
|
pnpm build
|
|
80
81
|
```
|
|
81
82
|
|
|
82
|
-
Install the bundled agent skills once (they let the agents verify, kick off, and report on workflows). This also installs `ai-whisper-code-review`, the skill workflow code-review handoffs use to evaluate agent-written code:
|
|
83
|
+
Install the bundled agent skills once (they let the agents verify, kick off, and report on workflows). This also installs `ai-whisper-code-review`, the skill workflow code-review handoffs use to evaluate agent-written code, and `ai-whisper-plan-execution`, the skill plan-execution handoffs use to structure how the implementer executes an approved plan:
|
|
83
84
|
|
|
84
85
|
```bash
|
|
85
86
|
whisper skill install
|
|
@@ -110,9 +111,9 @@ A run that stops short usually **escalates** — it does not crash. When the eva
|
|
|
110
111
|
|
|
111
112
|
## Core concepts
|
|
112
113
|
|
|
113
|
-
ai-whisper is **not a swarm**. The agents never type at once — work moves by a single baton, one owner at a time. Mounted sessions are *real*
|
|
114
|
+
ai-whisper is **not a swarm**. The agents never type at once — work moves by a single baton, one owner at a time. Mounted sessions are *real* agent sessions in your terminal — Claude or Codex CLIs, or ezio — and those sessions are the source of truth. Autonomy is supervised: every handoff, verdict, and round is inspectable, and runs are resumable rather than fire-and-forget. Work is organized as structured workflows — explicit loops and state transitions, not a free-form chat.
|
|
114
115
|
|
|
115
|
-
Claude and
|
|
116
|
+
Claude, Codex, and ezio are supported today — you mount any two of them; the architecture is provider-agnostic by design, so other coding-agent CLIs can be added behind the same relay.
|
|
116
117
|
|
|
117
118
|
For the full mental model, read [Concepts](docs/concepts.md).
|
|
118
119
|
|
|
@@ -106,6 +106,26 @@ var attachTargetModes = [
|
|
|
106
106
|
"mount_current_tty"
|
|
107
107
|
];
|
|
108
108
|
|
|
109
|
+
// ../shared/dist/repo-relative-path.js
|
|
110
|
+
import { basename, isAbsolute, relative } from "node:path";
|
|
111
|
+
function repoRelativePath(absPath, root) {
|
|
112
|
+
if (!absPath)
|
|
113
|
+
return "";
|
|
114
|
+
if (!root || !isAbsolute(absPath) || !isAbsolute(root))
|
|
115
|
+
return absPath;
|
|
116
|
+
const rel = relative(root, absPath);
|
|
117
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel))
|
|
118
|
+
return absPath;
|
|
119
|
+
return rel;
|
|
120
|
+
}
|
|
121
|
+
function displayArtifactPath(absPath, root) {
|
|
122
|
+
const trimmed = (absPath ?? "").trim();
|
|
123
|
+
if (!trimmed)
|
|
124
|
+
return null;
|
|
125
|
+
const rel = repoRelativePath(trimmed, root).trim();
|
|
126
|
+
return rel || basename(trimmed).trim() || null;
|
|
127
|
+
}
|
|
128
|
+
|
|
109
129
|
// ../shared/dist/broker-status.js
|
|
110
130
|
import { z as z3 } from "zod";
|
|
111
131
|
var brokerStatusSchema = z3.object({
|
|
@@ -1255,8 +1275,78 @@ function deleteEvaluatorDiagnosticsOlderThan(db, cutoffIso) {
|
|
|
1255
1275
|
return result.changes;
|
|
1256
1276
|
}
|
|
1257
1277
|
|
|
1278
|
+
// ../broker/dist/storage/repositories/relay-turn-event-diagnostics-repository.js
|
|
1279
|
+
var insertSeq = 0;
|
|
1280
|
+
function insertTurnEventDiagnostic(db, input) {
|
|
1281
|
+
const eventId = `tevt_${input.receivedAt.replace(/[^0-9]/g, "")}_${(input.handoffId ?? input.workspaceId).slice(-8)}_${input.action}_${process.pid}_${(insertSeq++).toString(36)}`;
|
|
1282
|
+
db.prepare(`INSERT INTO relay_turn_event_diagnostics (
|
|
1283
|
+
event_id, received_at, provider, workspace_id, cwd,
|
|
1284
|
+
session_or_thread_id, turn_id, workflow_active, collab_id, workflow_id,
|
|
1285
|
+
chain_id, handoff_id, input_correlated, containment_score,
|
|
1286
|
+
fidelity_verdict, defer_count, action, message_len, message_sample
|
|
1287
|
+
) VALUES (
|
|
1288
|
+
@eventId, @receivedAt, @provider, @workspaceId, @cwd,
|
|
1289
|
+
@sessionOrThreadId, @turnId, @workflowActive, @collabId, @workflowId,
|
|
1290
|
+
@chainId, @handoffId, @inputCorrelated, @containmentScore,
|
|
1291
|
+
@fidelityVerdict, @deferCount, @action, @messageLen, @messageSample
|
|
1292
|
+
)`).run({
|
|
1293
|
+
eventId,
|
|
1294
|
+
receivedAt: input.receivedAt,
|
|
1295
|
+
provider: input.provider,
|
|
1296
|
+
workspaceId: input.workspaceId,
|
|
1297
|
+
cwd: input.cwd,
|
|
1298
|
+
sessionOrThreadId: input.sessionOrThreadId,
|
|
1299
|
+
turnId: input.turnId,
|
|
1300
|
+
workflowActive: input.workflowActive ? 1 : 0,
|
|
1301
|
+
collabId: input.collabId,
|
|
1302
|
+
workflowId: input.workflowId,
|
|
1303
|
+
chainId: input.chainId,
|
|
1304
|
+
handoffId: input.handoffId,
|
|
1305
|
+
inputCorrelated: input.inputCorrelated === null ? null : input.inputCorrelated ? 1 : 0,
|
|
1306
|
+
containmentScore: input.containmentScore,
|
|
1307
|
+
fidelityVerdict: input.fidelityVerdict,
|
|
1308
|
+
deferCount: input.deferCount,
|
|
1309
|
+
action: input.action,
|
|
1310
|
+
messageLen: input.messageLen,
|
|
1311
|
+
messageSample: input.messageSample
|
|
1312
|
+
});
|
|
1313
|
+
return { eventId };
|
|
1314
|
+
}
|
|
1315
|
+
function rowToRecord4(r) {
|
|
1316
|
+
return {
|
|
1317
|
+
eventId: r.event_id,
|
|
1318
|
+
receivedAt: r.received_at,
|
|
1319
|
+
provider: r.provider,
|
|
1320
|
+
workspaceId: r.workspace_id,
|
|
1321
|
+
cwd: r.cwd,
|
|
1322
|
+
sessionOrThreadId: r.session_or_thread_id,
|
|
1323
|
+
turnId: r.turn_id,
|
|
1324
|
+
workflowActive: r.workflow_active === 1,
|
|
1325
|
+
collabId: r.collab_id,
|
|
1326
|
+
workflowId: r.workflow_id,
|
|
1327
|
+
chainId: r.chain_id,
|
|
1328
|
+
handoffId: r.handoff_id,
|
|
1329
|
+
inputCorrelated: r.input_correlated === null ? null : r.input_correlated === 1,
|
|
1330
|
+
containmentScore: r.containment_score,
|
|
1331
|
+
fidelityVerdict: r.fidelity_verdict,
|
|
1332
|
+
deferCount: r.defer_count,
|
|
1333
|
+
action: r.action,
|
|
1334
|
+
messageLen: r.message_len,
|
|
1335
|
+
messageSample: r.message_sample
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
function listTurnEventDiagnosticsByCollab(db, collabId2, limit) {
|
|
1339
|
+
const sql = "SELECT * FROM relay_turn_event_diagnostics WHERE collab_id = ? ORDER BY received_at DESC" + (limit !== null ? " LIMIT ?" : "");
|
|
1340
|
+
const rows = limit !== null ? db.prepare(sql).all(collabId2, limit) : db.prepare(sql).all(collabId2);
|
|
1341
|
+
return rows.map(rowToRecord4);
|
|
1342
|
+
}
|
|
1343
|
+
function deleteTurnEventDiagnosticsOlderThan(db, cutoffIso) {
|
|
1344
|
+
const info = db.prepare("DELETE FROM relay_turn_event_diagnostics WHERE received_at < ?").run(cutoffIso);
|
|
1345
|
+
return info.changes;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1258
1348
|
// ../broker/dist/storage/repositories/relay-handoff-repository.js
|
|
1259
|
-
function
|
|
1349
|
+
function rowToRecord5(row) {
|
|
1260
1350
|
return {
|
|
1261
1351
|
handoffId: row.handoff_id,
|
|
1262
1352
|
collabId: row.collab_id,
|
|
@@ -1293,7 +1383,7 @@ function queryRelayHandoff(db, handoffId) {
|
|
|
1293
1383
|
FROM relay_handoff h
|
|
1294
1384
|
JOIN collab c ON c.collab_id = h.collab_id
|
|
1295
1385
|
WHERE h.handoff_id = ?`).get(handoffId);
|
|
1296
|
-
return row ?
|
|
1386
|
+
return row ? rowToRecord5(row) : null;
|
|
1297
1387
|
}
|
|
1298
1388
|
function createRelayHandoffTxn(db, input) {
|
|
1299
1389
|
return db.transaction(() => {
|
|
@@ -1445,7 +1535,7 @@ function queryLatestHandedBackHandoff(db, collabId2) {
|
|
|
1445
1535
|
WHERE h.collab_id = ? AND h.status = 'handed_back'
|
|
1446
1536
|
ORDER BY h.resolved_at DESC
|
|
1447
1537
|
LIMIT 1`).get(collabId2);
|
|
1448
|
-
return row ?
|
|
1538
|
+
return row ? rowToRecord5(row) : null;
|
|
1449
1539
|
}
|
|
1450
1540
|
function failRelayHandoffOnDisconnectTxn(db, input) {
|
|
1451
1541
|
db.transaction(() => {
|
|
@@ -1516,7 +1606,7 @@ function listRelayHandoffsPendingOrchestration(db, collabId2) {
|
|
|
1516
1606
|
AND h.status = 'handed_back'
|
|
1517
1607
|
AND h.orchestrator_status = 'idle'
|
|
1518
1608
|
AND (w.status IS NULL OR w.status <> 'paused')`).all(collabId2);
|
|
1519
|
-
return rows.map(
|
|
1609
|
+
return rows.map(rowToRecord5);
|
|
1520
1610
|
}
|
|
1521
1611
|
function createLoopRelayHandoffTxn(db, input) {
|
|
1522
1612
|
return db.transaction(() => {
|
|
@@ -1712,7 +1802,7 @@ function getHandoffWithWorkflowMetaById(db, handoffId) {
|
|
|
1712
1802
|
WHERE h.handoff_id = ?`).get(handoffId);
|
|
1713
1803
|
if (!row)
|
|
1714
1804
|
return null;
|
|
1715
|
-
const base =
|
|
1805
|
+
const base = rowToRecord5(row);
|
|
1716
1806
|
return {
|
|
1717
1807
|
...base,
|
|
1718
1808
|
handoffStep: row.handoff_step,
|
|
@@ -1811,7 +1901,7 @@ function cleanupOrchestrationOnShutdownTxn(db, input) {
|
|
|
1811
1901
|
}
|
|
1812
1902
|
|
|
1813
1903
|
// ../broker/dist/storage/repositories/dashboard-repository.js
|
|
1814
|
-
import { basename } from "node:path";
|
|
1904
|
+
import { basename as basename2 } from "node:path";
|
|
1815
1905
|
function listActiveCollabSummaries(db, input) {
|
|
1816
1906
|
const nowMs = Date.parse(input.now ?? (/* @__PURE__ */ new Date()).toISOString());
|
|
1817
1907
|
const base = Number.isFinite(nowMs) ? nowMs : Date.now();
|
|
@@ -1855,7 +1945,7 @@ function buildCollabSummary(db, collabId2) {
|
|
|
1855
1945
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1856
1946
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
1857
1947
|
name, status, current_phase_index AS currentPhaseIndex,
|
|
1858
|
-
created_at AS createdAt
|
|
1948
|
+
created_at AS createdAt, spec_path AS specPath
|
|
1859
1949
|
FROM workflows WHERE collab_id = ?
|
|
1860
1950
|
ORDER BY (status = 'running') DESC, created_at DESC
|
|
1861
1951
|
LIMIT 1`).get(e.collabId);
|
|
@@ -1898,7 +1988,7 @@ function buildCollabSummary(db, collabId2) {
|
|
|
1898
1988
|
) ranked
|
|
1899
1989
|
WHERE rn = 1
|
|
1900
1990
|
ORDER BY agentType ASC`).all(e.collabId);
|
|
1901
|
-
const label = wf?.name && wf.name.trim() || collab2?.displayName && collab2.displayName.trim() || (collab2?.workspaceRoot ?
|
|
1991
|
+
const label = wf?.name && wf.name.trim() || collab2?.displayName && collab2.displayName.trim() || (collab2?.workspaceRoot ? basename2(collab2.workspaceRoot) : "") || e.collabId.slice(0, 12);
|
|
1902
1992
|
const runLastActRow = wf ? db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
|
|
1903
1993
|
FROM relay_handoff
|
|
1904
1994
|
WHERE collab_id = ? AND workflow_id = ?`).get(e.collabId, wf.workflowId) : db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
|
|
@@ -1924,6 +2014,7 @@ function buildCollabSummary(db, collabId2) {
|
|
|
1924
2014
|
},
|
|
1925
2015
|
sessions,
|
|
1926
2016
|
workflowCreatedAt: wf?.createdAt ?? null,
|
|
2017
|
+
specPath: wf ? displayArtifactPath(wf.specPath, collab2?.workspaceRoot ?? "") : null,
|
|
1927
2018
|
lastActivityAt: runLastAct
|
|
1928
2019
|
};
|
|
1929
2020
|
}
|
|
@@ -1966,7 +2057,7 @@ function listRunCostRows(db, input) {
|
|
|
1966
2057
|
import { randomUUID } from "node:crypto";
|
|
1967
2058
|
|
|
1968
2059
|
// ../broker/dist/storage/repositories/workflow-phase-repository.js
|
|
1969
|
-
function
|
|
2060
|
+
function rowToRecord6(row) {
|
|
1970
2061
|
return {
|
|
1971
2062
|
phaseRunId: row.phase_run_id,
|
|
1972
2063
|
workflowId: row.workflow_id,
|
|
@@ -1985,7 +2076,7 @@ function insertWorkflowPhaseRun(db, input) {
|
|
|
1985
2076
|
}
|
|
1986
2077
|
function listPhaseRunsForWorkflow(db, workflowId) {
|
|
1987
2078
|
const rows = db.prepare("SELECT * FROM workflow_phases WHERE workflow_id = ? ORDER BY started_at ASC").all(workflowId);
|
|
1988
|
-
return rows.map(
|
|
2079
|
+
return rows.map(rowToRecord6);
|
|
1989
2080
|
}
|
|
1990
2081
|
function closeWorkflowPhaseRun(db, input) {
|
|
1991
2082
|
db.prepare("UPDATE workflow_phases SET ended_at = ?, outcome = ? WHERE phase_run_id = ?").run(input.now, input.outcome, input.phaseRunId);
|
|
@@ -1998,7 +2089,7 @@ function hasOpenPhaseRunForIndex(db, input) {
|
|
|
1998
2089
|
}
|
|
1999
2090
|
|
|
2000
2091
|
// ../broker/dist/storage/repositories/relay-chain-repository.js
|
|
2001
|
-
function
|
|
2092
|
+
function rowToRecord7(row) {
|
|
2002
2093
|
return {
|
|
2003
2094
|
chainId: row.chain_id,
|
|
2004
2095
|
collabId: row.collab_id,
|
|
@@ -2018,7 +2109,7 @@ function insertRelayChain(db, input) {
|
|
|
2018
2109
|
}
|
|
2019
2110
|
function getRelayChain(db, chainId) {
|
|
2020
2111
|
const row = db.prepare("SELECT * FROM relay_chains WHERE chain_id = ?").get(chainId);
|
|
2021
|
-
return row ?
|
|
2112
|
+
return row ? rowToRecord7(row) : null;
|
|
2022
2113
|
}
|
|
2023
2114
|
function incrementChainRound(db, input) {
|
|
2024
2115
|
db.prepare("UPDATE relay_chains SET current_round = current_round + 1, updated_at = ? WHERE chain_id = ?").run(input.now, input.chainId);
|
|
@@ -2124,8 +2215,10 @@ Non-blocking risks:
|
|
|
2124
2215
|
- <risk that does NOT block this gate, or "None.">
|
|
2125
2216
|
--- end protocol ---`;
|
|
2126
2217
|
var CODE_REVIEW_SKILL_GUIDANCE = "Use the ai-whisper-code-review skill to evaluate the delivered code. The workflow review protocol below controls your output format and evaluator semantics; the skill controls how you inspect code and decide which code-quality issues are blocking.\n\n";
|
|
2218
|
+
var PLAN_EXECUTION_SKILL_GUIDANCE = "Use the ai-whisper-plan-execution skill to structure HOW you execute this plan: subagent fan-out with model allocation where your harness supports it, disciplined inline execution otherwise. The handback contract above remains authoritative \u2014 settle all delegated work before handing back, and state which execution mode you used.";
|
|
2127
2219
|
var SDD_SPEC_REVIEW = "Review the spec at {specPath}. This is an autonomous workflow with no human in the loop.\n\n" + WORKFLOW_REVIEW_PROTOCOL;
|
|
2128
2220
|
var SDD_CODE_REVIEW = "Review the implementer's changes for this phase \u2014 the commits in {commitRange}. The upper bound is a LIVE `HEAD`: resolve it against the current repository at review time and INCLUDE any commits added during this review round (e.g. fixes for your prior findings); do not pin the review to an earlier tip. Verify against the spec's acceptance criteria and run the project's verification/tests. This is an autonomous workflow with no human in the loop.\n\n" + CODE_REVIEW_SKILL_GUIDANCE + WORKFLOW_REVIEW_PROTOCOL;
|
|
2221
|
+
var SDD_PLAN_EXECUTION = "Execute the plan at {planPath} now: make all changes it specifies, run the verification command the plan or spec defines and ensure it passes, then commit. This is an autonomous workflow \u2014 no human will respond. Do the work yourself; never ask for confirmation, permission, or clarification. Hand back the commit SHAs and the verification output, plus a 1-2 sentence summary; your reply must be at least two sentences, well over 100 characters \u2014 never hand back only a single word.\n\n" + PLAN_EXECUTION_SKILL_GUIDANCE;
|
|
2129
2222
|
var SPEC_DRIVEN_DEVELOPMENT = {
|
|
2130
2223
|
type: "spec-driven-development",
|
|
2131
2224
|
displayName: "Spec-Driven Development",
|
|
@@ -2170,9 +2263,9 @@ var SPEC_DRIVEN_DEVELOPMENT = {
|
|
|
2170
2263
|
reviewerRole: null,
|
|
2171
2264
|
maxRounds: 1,
|
|
2172
2265
|
initialHandoffStep: "execute",
|
|
2173
|
-
kickoffTemplate:
|
|
2266
|
+
kickoffTemplate: SDD_PLAN_EXECUTION,
|
|
2174
2267
|
stepTemplates: {
|
|
2175
|
-
execute:
|
|
2268
|
+
execute: SDD_PLAN_EXECUTION
|
|
2176
2269
|
},
|
|
2177
2270
|
evaluatorPromptKey: "execution-gate",
|
|
2178
2271
|
artifactOut: { kind: "commit-range" }
|
|
@@ -2375,11 +2468,11 @@ function derivePlanPath(specPath, dateIso) {
|
|
|
2375
2468
|
if (!/^\d{4}-\d{2}-\d{2}/.test(dateIso)) {
|
|
2376
2469
|
throw new Error(`derivePlanPath: dateIso must start with YYYY-MM-DD, got "${dateIso}"`);
|
|
2377
2470
|
}
|
|
2378
|
-
const
|
|
2379
|
-
if (!
|
|
2471
|
+
const basename3 = specPath.split("/").pop() ?? "";
|
|
2472
|
+
if (!basename3.endsWith("-design.md")) {
|
|
2380
2473
|
throw new Error(`derivePlanPath: specPath must end with "-design.md", got "${specPath}"`);
|
|
2381
2474
|
}
|
|
2382
|
-
const withoutExt =
|
|
2475
|
+
const withoutExt = basename3.replace(/\.md$/, "");
|
|
2383
2476
|
const slug = withoutExt.replace(/-design$/, "").replace(/^\d{4}-\d{2}-\d{2}-/, "");
|
|
2384
2477
|
const date = dateIso.slice(0, 10);
|
|
2385
2478
|
return `docs/superpowers/plans/${date}-${slug}.md`;
|
|
@@ -4170,6 +4263,15 @@ function createControlService(db, events) {
|
|
|
4170
4263
|
sweepEvaluatorDiagnostics(input) {
|
|
4171
4264
|
return deleteEvaluatorDiagnosticsOlderThan(db, input.cutoffIso);
|
|
4172
4265
|
},
|
|
4266
|
+
recordTurnEventDiagnostic(input) {
|
|
4267
|
+
return insertTurnEventDiagnostic(db, input);
|
|
4268
|
+
},
|
|
4269
|
+
listTurnEventDiagnosticsByCollab(collabId2, limit) {
|
|
4270
|
+
return listTurnEventDiagnosticsByCollab(db, collabId2, limit);
|
|
4271
|
+
},
|
|
4272
|
+
sweepTurnEventDiagnostics(input) {
|
|
4273
|
+
return deleteTurnEventDiagnosticsOlderThan(db, input.cutoffIso);
|
|
4274
|
+
},
|
|
4173
4275
|
cleanupOrchestration(input) {
|
|
4174
4276
|
return cleanupOrchestrationOnShutdownTxn(db, input);
|
|
4175
4277
|
}
|
|
@@ -4310,7 +4412,7 @@ function sweepStaleCaptureLease(db, options = {}) {
|
|
|
4310
4412
|
}
|
|
4311
4413
|
|
|
4312
4414
|
// ../broker/dist/storage/apply-migrations.js
|
|
4313
|
-
var CURRENT_SCHEMA_VERSION =
|
|
4415
|
+
var CURRENT_SCHEMA_VERSION = 6;
|
|
4314
4416
|
var initMigrationSql = `
|
|
4315
4417
|
CREATE TABLE IF NOT EXISTS broker_state (
|
|
4316
4418
|
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
@@ -4825,6 +4927,37 @@ function runMigrationBody(db) {
|
|
|
4825
4927
|
if (!brokerDaemonColumns.some((column) => column.name === "evaluator_status")) {
|
|
4826
4928
|
db.exec("ALTER TABLE broker_daemon ADD COLUMN evaluator_status TEXT");
|
|
4827
4929
|
}
|
|
4930
|
+
db.exec(`
|
|
4931
|
+
CREATE TABLE IF NOT EXISTS relay_turn_event_diagnostics (
|
|
4932
|
+
event_id TEXT PRIMARY KEY,
|
|
4933
|
+
received_at TEXT NOT NULL,
|
|
4934
|
+
provider TEXT NOT NULL,
|
|
4935
|
+
workspace_id TEXT NOT NULL,
|
|
4936
|
+
cwd TEXT NOT NULL,
|
|
4937
|
+
session_or_thread_id TEXT,
|
|
4938
|
+
turn_id TEXT,
|
|
4939
|
+
workflow_active INTEGER NOT NULL DEFAULT 0,
|
|
4940
|
+
collab_id TEXT,
|
|
4941
|
+
workflow_id TEXT,
|
|
4942
|
+
chain_id TEXT,
|
|
4943
|
+
handoff_id TEXT,
|
|
4944
|
+
input_correlated INTEGER,
|
|
4945
|
+
containment_score REAL,
|
|
4946
|
+
fidelity_verdict TEXT NOT NULL DEFAULT 'n/a',
|
|
4947
|
+
defer_count INTEGER NOT NULL DEFAULT 0,
|
|
4948
|
+
action TEXT NOT NULL,
|
|
4949
|
+
message_len INTEGER NOT NULL DEFAULT 0,
|
|
4950
|
+
message_sample TEXT
|
|
4951
|
+
);
|
|
4952
|
+
CREATE INDEX IF NOT EXISTS idx_relay_turn_event_diag_collab_received
|
|
4953
|
+
ON relay_turn_event_diagnostics (collab_id, received_at DESC);
|
|
4954
|
+
CREATE INDEX IF NOT EXISTS idx_relay_turn_event_diag_handoff
|
|
4955
|
+
ON relay_turn_event_diagnostics (handoff_id);
|
|
4956
|
+
CREATE INDEX IF NOT EXISTS idx_relay_turn_event_diag_action
|
|
4957
|
+
ON relay_turn_event_diagnostics (action);
|
|
4958
|
+
CREATE INDEX IF NOT EXISTS idx_relay_turn_event_diag_received
|
|
4959
|
+
ON relay_turn_event_diagnostics (received_at);
|
|
4960
|
+
`);
|
|
4828
4961
|
}
|
|
4829
4962
|
|
|
4830
4963
|
// ../broker/dist/storage/open-database.js
|
|
@@ -4975,8 +5108,12 @@ function createDaemonHeartbeat(input) {
|
|
|
4975
5108
|
}
|
|
4976
5109
|
|
|
4977
5110
|
// ../broker/dist/runtime/diagnostics-sweep.js
|
|
5111
|
+
import { readdirSync, unlinkSync, statSync as statSync2 } from "node:fs";
|
|
5112
|
+
import { join as join2 } from "node:path";
|
|
4978
5113
|
var DEFAULT_INTERVAL_MS = 60 * 60 * 1e3;
|
|
4979
5114
|
var DEFAULT_RETENTION_DAYS = 30;
|
|
5115
|
+
var DEFAULT_EVENT_LOG_RETENTION_DAYS = 3;
|
|
5116
|
+
var TURN_EVENT_FILE_RE = /^turn-events-(\d{4}-\d{2}-\d{2})\.jsonl$/;
|
|
4980
5117
|
function resolveIntervalMs(override) {
|
|
4981
5118
|
if (override !== void 0)
|
|
4982
5119
|
return override;
|
|
@@ -4993,6 +5130,38 @@ function resolveRetentionDays(override) {
|
|
|
4993
5130
|
return Number(envVal);
|
|
4994
5131
|
return DEFAULT_RETENTION_DAYS;
|
|
4995
5132
|
}
|
|
5133
|
+
function resolveEventLogRetentionDays(override) {
|
|
5134
|
+
if (override !== void 0)
|
|
5135
|
+
return override;
|
|
5136
|
+
const raw = process.env["AI_WHISPER_EVENT_LOG_RETENTION_DAYS"];
|
|
5137
|
+
if (raw && Number.isFinite(Number(raw)) && Number(raw) > 0)
|
|
5138
|
+
return Number(raw);
|
|
5139
|
+
return DEFAULT_EVENT_LOG_RETENTION_DAYS;
|
|
5140
|
+
}
|
|
5141
|
+
function sweepTurnEventLogs(logsDir, cutoffDate) {
|
|
5142
|
+
let entries;
|
|
5143
|
+
try {
|
|
5144
|
+
entries = readdirSync(logsDir);
|
|
5145
|
+
} catch {
|
|
5146
|
+
return [];
|
|
5147
|
+
}
|
|
5148
|
+
const removed = [];
|
|
5149
|
+
for (const name of entries) {
|
|
5150
|
+
const m = TURN_EVENT_FILE_RE.exec(name);
|
|
5151
|
+
if (!m)
|
|
5152
|
+
continue;
|
|
5153
|
+
if (m[1] < cutoffDate) {
|
|
5154
|
+
const full = join2(logsDir, name);
|
|
5155
|
+
try {
|
|
5156
|
+
statSync2(full);
|
|
5157
|
+
unlinkSync(full);
|
|
5158
|
+
removed.push(full);
|
|
5159
|
+
} catch {
|
|
5160
|
+
}
|
|
5161
|
+
}
|
|
5162
|
+
}
|
|
5163
|
+
return removed;
|
|
5164
|
+
}
|
|
4996
5165
|
function createDiagnosticsSweep(deps) {
|
|
4997
5166
|
const intervalMs = resolveIntervalMs(deps.intervalMs);
|
|
4998
5167
|
const retentionDays = resolveRetentionDays(deps.retentionDays);
|
|
@@ -5002,6 +5171,12 @@ function createDiagnosticsSweep(deps) {
|
|
|
5002
5171
|
const cutoffIso = cutoff.toISOString();
|
|
5003
5172
|
deps.broker.control.sweepCaptureDiagnostics({ cutoffIso });
|
|
5004
5173
|
deps.broker.control.sweepEvaluatorDiagnostics({ cutoffIso });
|
|
5174
|
+
const eventRetentionDays = resolveEventLogRetentionDays(deps.eventLogRetentionDays);
|
|
5175
|
+
const eventCutoff = new Date(Date.now() - eventRetentionDays * 864e5);
|
|
5176
|
+
deps.broker.control.sweepTurnEventDiagnostics({ cutoffIso: eventCutoff.toISOString() });
|
|
5177
|
+
if (deps.logsDir) {
|
|
5178
|
+
sweepTurnEventLogs(deps.logsDir, eventCutoff.toISOString().slice(0, 10));
|
|
5179
|
+
}
|
|
5005
5180
|
}
|
|
5006
5181
|
return {
|
|
5007
5182
|
start() {
|
|
@@ -5020,15 +5195,15 @@ function createDiagnosticsSweep(deps) {
|
|
|
5020
5195
|
|
|
5021
5196
|
// ../broker/dist/runtime/ralph-setup.js
|
|
5022
5197
|
import { mkdirSync, writeFileSync, existsSync as existsSync2 } from "node:fs";
|
|
5023
|
-
import { join as
|
|
5198
|
+
import { join as join3 } from "node:path";
|
|
5024
5199
|
function ensureRalphWorkspace(workspaceRoot, workflowId) {
|
|
5025
5200
|
const dir = ralphRunDir(workspaceRoot, workflowId);
|
|
5026
5201
|
mkdirSync(dir, { recursive: true });
|
|
5027
|
-
const ignorePath =
|
|
5202
|
+
const ignorePath = join3(workspaceRoot, ".ai-whisper", ".gitignore");
|
|
5028
5203
|
if (!existsSync2(ignorePath)) {
|
|
5029
5204
|
writeFileSync(ignorePath, "*\n");
|
|
5030
5205
|
}
|
|
5031
|
-
const ralphIgnorePath =
|
|
5206
|
+
const ralphIgnorePath = join3(workspaceRoot, ".ai-whisper", "ralph", ".gitignore");
|
|
5032
5207
|
if (!existsSync2(ralphIgnorePath)) {
|
|
5033
5208
|
writeFileSync(ralphIgnorePath, "*\n");
|
|
5034
5209
|
}
|
|
@@ -5037,15 +5212,15 @@ function ensureRalphWorkspace(workspaceRoot, workflowId) {
|
|
|
5037
5212
|
|
|
5038
5213
|
// ../broker/dist/runtime/bugfix-setup.js
|
|
5039
5214
|
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2, existsSync as existsSync3 } from "node:fs";
|
|
5040
|
-
import { join as
|
|
5215
|
+
import { join as join4 } from "node:path";
|
|
5041
5216
|
function ensureBugfixWorkspace(workspaceRoot, workflowId) {
|
|
5042
5217
|
const dir = bugfixRunDir(workspaceRoot, workflowId);
|
|
5043
5218
|
mkdirSync2(dir, { recursive: true });
|
|
5044
|
-
const ignorePath =
|
|
5219
|
+
const ignorePath = join4(workspaceRoot, ".ai-whisper", ".gitignore");
|
|
5045
5220
|
if (!existsSync3(ignorePath)) {
|
|
5046
5221
|
writeFileSync2(ignorePath, "*\n");
|
|
5047
5222
|
}
|
|
5048
|
-
const bugfixIgnorePath =
|
|
5223
|
+
const bugfixIgnorePath = join4(workspaceRoot, ".ai-whisper", "bugfix", ".gitignore");
|
|
5049
5224
|
if (!existsSync3(bugfixIgnorePath)) {
|
|
5050
5225
|
writeFileSync2(bugfixIgnorePath, "*\n");
|
|
5051
5226
|
}
|
|
@@ -6099,8 +6274,8 @@ function writeOwnPidToBrokerDaemon(db, input) {
|
|
|
6099
6274
|
}
|
|
6100
6275
|
|
|
6101
6276
|
// src/runtime/evaluator-config.ts
|
|
6102
|
-
import { readFileSync as readFileSync2, statSync as
|
|
6103
|
-
import { join as
|
|
6277
|
+
import { readFileSync as readFileSync2, statSync as statSync3 } from "node:fs";
|
|
6278
|
+
import { join as join5 } from "node:path";
|
|
6104
6279
|
|
|
6105
6280
|
// src/runtime/state-root.ts
|
|
6106
6281
|
import os from "node:os";
|
|
@@ -6131,7 +6306,7 @@ function warnIfLoosePerms(path2) {
|
|
|
6131
6306
|
if (process.platform === "win32") return;
|
|
6132
6307
|
let mode;
|
|
6133
6308
|
try {
|
|
6134
|
-
mode =
|
|
6309
|
+
mode = statSync3(path2).mode;
|
|
6135
6310
|
} catch {
|
|
6136
6311
|
return;
|
|
6137
6312
|
}
|
|
@@ -6159,15 +6334,15 @@ function loadEvaluatorConfig() {
|
|
|
6159
6334
|
const root = getStateRoot();
|
|
6160
6335
|
let dotenv = {};
|
|
6161
6336
|
try {
|
|
6162
|
-
dotenv = parseDotEnv(readFileSync2(
|
|
6337
|
+
dotenv = parseDotEnv(readFileSync2(join5(root, ".env"), "utf8"));
|
|
6163
6338
|
} catch (err) {
|
|
6164
6339
|
if (err.code !== "ENOENT") throw err;
|
|
6165
6340
|
}
|
|
6166
6341
|
const envGet = (k) => process.env[k] ?? dotenv[k];
|
|
6167
|
-
const authPath =
|
|
6342
|
+
const authPath = join5(root, "auth.json");
|
|
6168
6343
|
warnIfLoosePerms(authPath);
|
|
6169
6344
|
const auth = readJsonFile(authPath, "auth.json");
|
|
6170
|
-
const config = readJsonFile(
|
|
6345
|
+
const config = readJsonFile(join5(root, "config.json"), "config.json");
|
|
6171
6346
|
const evalCfg = config?.evaluator ?? {};
|
|
6172
6347
|
const evalOllama = evalCfg.ollama ?? {};
|
|
6173
6348
|
const provider = (envGet("AI_WHISPER_EVALUATOR_PROVIDER") ?? evalCfg.provider) === "ollama" ? "ollama" : "anthropic";
|