ai-whisper 0.7.0 → 0.8.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 CHANGED
@@ -99,7 +99,7 @@ whisper collab mount claude
99
99
  whisper collab mount codex
100
100
  ```
101
101
 
102
- The first `mount` creates the collab and starts the broker daemon for the workspace; the second binds the other agent. From either session, start a workflow against a spec or goal file (`spec-driven-development` for a spec, `ralph-loop` for an open-ended goal). Watch it run with:
102
+ The first `mount` creates the collab and starts the broker daemon for the workspace; the second binds the other agent. From either session, start a workflow against a spec or goal file `spec-driven-development` for a spec, `ralph-loop` for an open-ended goal, plus `complex-bug-fixing` and `deliberation` (see [Workflows](docs/workflows.md)). Watch it run with:
103
103
 
104
104
  ```bash
105
105
  whisper collab dashboard
@@ -121,7 +121,7 @@ For the full mental model, read [Concepts](docs/concepts.md).
121
121
 
122
122
  ## Learn more
123
123
 
124
- - [Workflows](docs/workflows.md) — how to use the workflows well: choosing between `spec-driven-development` and `ralph-loop`, and authoring the spec or goal that drives the run.
124
+ - [Workflows](docs/workflows.md) — how to use the four workflows well: choosing between `spec-driven-development`, `ralph-loop`, `complex-bug-fixing`, and `deliberation`, and authoring the spec, goal, bug report, or seed that drives the run.
125
125
  - [Concepts](docs/concepts.md) — the mental model: baton handoff, real mounted sessions, supervised autonomy, workflow-first execution.
126
126
  - [Relay & handoff flows](docs/relay-handoff-flows.md) — the complete handoff state machine, capture-status table, hotkey reference, per-step verdicts, and troubleshooting.
127
127
  - [Evaluator configuration](docs/evaluator-configuration.md) — required credentials and options for the LLM evaluator that gates workflows.
@@ -1998,6 +1998,7 @@ function buildCollabSummary(db, collabId2) {
1998
1998
  return {
1999
1999
  collabId: e.collabId,
2000
2000
  label,
2001
+ workspaceRoot: collab2?.workspaceRoot ?? "",
2001
2002
  workflowId: wf?.workflowId ?? null,
2002
2003
  workflowType: wf?.workflowType ?? null,
2003
2004
  workflowStatus: wf?.status ?? null,
@@ -2031,6 +2031,7 @@ function buildCollabSummary(db, collabId) {
2031
2031
  return {
2032
2032
  collabId: e.collabId,
2033
2033
  label,
2034
+ workspaceRoot: collab?.workspaceRoot ?? "",
2034
2035
  workflowId: wf?.workflowId ?? null,
2035
2036
  workflowType: wf?.workflowType ?? null,
2036
2037
  workflowStatus: wf?.status ?? null,
@@ -2001,6 +2001,7 @@ function buildCollabSummary(db, collabId2) {
2001
2001
  return {
2002
2002
  collabId: e.collabId,
2003
2003
  label,
2004
+ workspaceRoot: collab?.workspaceRoot ?? "",
2004
2005
  workflowId: wf?.workflowId ?? null,
2005
2006
  workflowType: wf?.workflowType ?? null,
2006
2007
  workflowStatus: wf?.status ?? null,
@@ -2390,6 +2390,7 @@ function buildCollabSummary(db, collabId) {
2390
2390
  return {
2391
2391
  collabId: e.collabId,
2392
2392
  label,
2393
+ workspaceRoot: collab?.workspaceRoot ?? "",
2393
2394
  workflowId: wf?.workflowId ?? null,
2394
2395
  workflowType: wf?.workflowType ?? null,
2395
2396
  workflowStatus: wf?.status ?? null,
@@ -6831,12 +6832,13 @@ function progressBar(progress) {
6831
6832
  const current = Math.max(0, Math.min(total, progress.current));
6832
6833
  return BAR_FILLED.repeat(current) + BAR_EMPTY.repeat(total - current);
6833
6834
  }
6834
- function midEllipsis(path4, width) {
6835
- if (width <= 1 || path4.length <= width) return path4;
6836
- if (width <= 3) return path4.slice(0, width);
6837
- const keepEnd = Math.ceil((width - 1) / 2);
6838
- const keepStart = width - 1 - keepEnd;
6839
- return path4.slice(0, keepStart) + "\u2026" + path4.slice(path4.length - keepEnd);
6835
+ function keepTail(s, width) {
6836
+ if (width <= 0 || s.length <= width) return s;
6837
+ if (width <= 3) return s.slice(s.length - width);
6838
+ return "\u2026" + s.slice(s.length - (width - 1));
6839
+ }
6840
+ function artifactBasename(p) {
6841
+ return p.slice(p.lastIndexOf("/") + 1) || p;
6840
6842
  }
6841
6843
  function hhmmUTC(iso) {
6842
6844
  const d = new Date(iso);
@@ -6850,6 +6852,14 @@ function dotForHealth(h) {
6850
6852
  function padRight(s, n) {
6851
6853
  return s.length >= n ? s.slice(0, n) : s + " ".repeat(n - s.length);
6852
6854
  }
6855
+ function cwdLine(cwd, width) {
6856
+ const budget = Math.max(8, width - 2 - 2 - 2);
6857
+ return /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: THEME.muted, children: [
6858
+ " ",
6859
+ "\u2302 ",
6860
+ cwd ? keepTail(cwd, budget) : "\u2014"
6861
+ ] });
6862
+ }
6853
6863
  function statusKeyToWorkflowStatus(key) {
6854
6864
  if (key === "idle") return null;
6855
6865
  if (key === "stuck") return "running";
@@ -6881,6 +6891,7 @@ function FullCard(props) {
6881
6891
  typeText
6882
6892
  ] }) : null
6883
6893
  ] }),
6894
+ cwdLine(pane.cwd, props.width),
6884
6895
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: THEME.err, children: [
6885
6896
  " ",
6886
6897
  why.slice(0, splitAt)
@@ -6933,10 +6944,15 @@ function FullCard(props) {
6933
6944
  ]
6934
6945
  }
6935
6946
  ),
6947
+ cwdLine(pane.cwd, props.width),
6936
6948
  artifactText ? /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: THEME.muted, children: [
6937
6949
  " ",
6938
- "\u2192 ",
6939
- midEllipsis(artifactText, artifactBudget),
6950
+ "\u2192",
6951
+ " ",
6952
+ keepTail(
6953
+ artifactBasename(artifactText),
6954
+ artifactBudget
6955
+ ),
6940
6956
  timeTail
6941
6957
  ] }) : null,
6942
6958
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", children: [
@@ -6974,13 +6990,12 @@ function CompactCard(props) {
6974
6990
  });
6975
6991
  const borderColor = pane.statusKey === "stuck" || pane.statusKey === "canceled" ? THEME.err : props.selected ? THEME.select : THEME.muted;
6976
6992
  const chevron = props.selected ? "\u25B8 " : " ";
6977
- const isWide = props.width >= NARROW_PANE_COLS;
6993
+ const isWide = props.width > NARROW_PANE_COLS;
6978
6994
  const typeText = pane.workflowType ? isWide ? pane.workflowType : abbreviateWorkflowType(pane.workflowType) : null;
6979
- const progressText = pane.progress ? `P${pane.progress.current}/${pane.progress.total}` : "\u2014";
6980
- const tail = `${progressText} \xB7 ${statusWord} \xB7 ${pane.elapsed}`;
6981
- const artBudget = Math.max(8, props.width - 2 - 2 - 4 - tail.length);
6995
+ const statusElapsed = `${statusWord} \xB7 ${pane.elapsed}`;
6982
6996
  const compactArtifact = pane.artifact?.trim() || null;
6983
- const artifactPrefix = compactArtifact ? `\u2192 ${midEllipsis(compactArtifact, artBudget)} \xB7 ` : "";
6997
+ const artBudget = Math.max(8, props.width - 2 - 2 - 2);
6998
+ const base = compactArtifact ? artifactBasename(compactArtifact) : null;
6984
6999
  return /* @__PURE__ */ jsxs2(
6985
7000
  Box2,
6986
7001
  {
@@ -7003,18 +7018,19 @@ function CompactCard(props) {
7003
7018
  typeText ? /* @__PURE__ */ jsxs2(Text2, { color: THEME.muted, children: [
7004
7019
  " ",
7005
7020
  typeText
7006
- ] }) : null
7021
+ ] }) : null,
7022
+ /* @__PURE__ */ jsxs2(Text2, { color: THEME.muted, children: [
7023
+ " \xB7 ",
7024
+ statusElapsed
7025
+ ] })
7007
7026
  ]
7008
7027
  }
7009
7028
  ),
7029
+ cwdLine(pane.cwd, props.width),
7010
7030
  /* @__PURE__ */ jsxs2(Text2, { wrap: "truncate", color: THEME.muted, children: [
7011
7031
  " ",
7012
- artifactPrefix,
7013
- progressText,
7014
- " \xB7 ",
7015
- statusWord,
7016
- " \xB7 ",
7017
- pane.elapsed
7032
+ "\u2192 ",
7033
+ base ? keepTail(base, artBudget) : "\u2014"
7018
7034
  ] })
7019
7035
  ]
7020
7036
  }
@@ -7145,7 +7161,7 @@ function Inspector(props) {
7145
7161
  const oc = outcomeColor(p.outcome);
7146
7162
  return oc ? /* @__PURE__ */ jsx3(Text2, { color: oc, children: p.outcome ?? "\u22EF" }) : /* @__PURE__ */ jsx3(Text2, { children: p.outcome ?? "\u22EF" });
7147
7163
  })()
7148
- ] }, p.phaseIndex)),
7164
+ ] }, p.phaseRunId)),
7149
7165
  /* @__PURE__ */ jsx3(Text2, { wrap: "truncate", bold: true, children: `TOTAL \u2248${s.cost.estInputTokens + s.cost.estOutputTokens} ${fmtDur(
7150
7166
  s.cost.totalMs
7151
7167
  )}` })
@@ -7201,6 +7217,7 @@ var init_dashboard_view = __esm({
7201
7217
  });
7202
7218
 
7203
7219
  // src/runtime/dashboard-state.ts
7220
+ import { homedir as homedir3 } from "node:os";
7204
7221
  function estimateTokens(chars) {
7205
7222
  if (!Number.isFinite(chars) || chars <= 0) return 0;
7206
7223
  return Math.ceil(chars / 4);
@@ -7214,6 +7231,12 @@ function recencyKey(s) {
7214
7231
  function cmpDesc(a, b) {
7215
7232
  return a < b ? 1 : a > b ? -1 : 0;
7216
7233
  }
7234
+ function abbreviateCwd(absPath, home) {
7235
+ let p = absPath;
7236
+ if (p.startsWith("/private/")) p = p.slice("/private".length);
7237
+ if (home && (p === home || p.startsWith(home + "/"))) p = "~" + p.slice(home.length);
7238
+ return p;
7239
+ }
7217
7240
  function partitionWallGroups(summaries) {
7218
7241
  const active = [];
7219
7242
  const idleManual = [];
@@ -7362,6 +7385,7 @@ function buildInspectorState(input) {
7362
7385
  }
7363
7386
  }
7364
7387
  const timeline = input.phaseRuns.slice().sort((a, b) => a.phaseIndex - b.phaseIndex).map((p) => ({
7388
+ phaseRunId: p.phaseRunId,
7365
7389
  phaseIndex: p.phaseIndex,
7366
7390
  phaseName: p.phaseName,
7367
7391
  roundsUsed: roundsByPhase.get(p.phaseRunId) ?? 0,
@@ -7463,7 +7487,7 @@ function parseEventText(text) {
7463
7487
  const verdict = tokens[3] ?? "-";
7464
7488
  return { step, route, verdict };
7465
7489
  }
7466
- function projectPane(s, now, idleThresholdMs, snap) {
7490
+ function projectPane(s, now, idleThresholdMs, snap, home) {
7467
7491
  let wallStep = null;
7468
7492
  if (s.currentPhaseRunId) {
7469
7493
  for (let i = snap.handoffs.length - 1; i >= 0; i--) {
@@ -7526,10 +7550,12 @@ function projectPane(s, now, idleThresholdMs, snap) {
7526
7550
  elapsed,
7527
7551
  startIso: s.workflowCreatedAt,
7528
7552
  artifact: s.specPath,
7553
+ cwd: s.workspaceRoot ? abbreviateCwd(s.workspaceRoot, home) : null,
7529
7554
  cardKind
7530
7555
  };
7531
7556
  }
7532
7557
  function buildWallState(input) {
7558
+ const home = input.home ?? homedir3();
7533
7559
  const groups = partitionWallGroups(input.summaries);
7534
7560
  let cols;
7535
7561
  let rows;
@@ -7552,7 +7578,7 @@ function buildWallState(input) {
7552
7578
  phaseRuns: [],
7553
7579
  totalPhases: 0
7554
7580
  };
7555
- return projectPane(sum, input.now, input.idleThresholdMs, snap);
7581
+ return projectPane(sum, input.now, input.idleThresholdMs, snap, home);
7556
7582
  })
7557
7583
  }));
7558
7584
  const panes = sections.flatMap((sec) => sec.panes);
@@ -7573,7 +7599,7 @@ var init_dashboard_state = __esm({
7573
7599
  init_relay_view_state();
7574
7600
  init_dashboard_glyph();
7575
7601
  MIN_PANE_COLS2 = 40;
7576
- CARD_HEIGHT = { full: 6, compact: 4 };
7602
+ CARD_HEIGHT = { full: 7, compact: 5 };
7577
7603
  HEADER_ROWS = 1;
7578
7604
  GROUP_ORDER = ["active", "idleManual", "halted", "doneCanceled"];
7579
7605
  GROUP_LABEL = {
@@ -12843,9 +12869,9 @@ function compareSemver(a, b) {
12843
12869
  // src/generated/ezio-provenance.ts
12844
12870
  var EZIO_PROVENANCE = {
12845
12871
  ezioCliVersion: "0.2.0-beta.5",
12846
- ezioGitSha: "a701ee7",
12847
- builtAt: "2026-06-22T14:52:17.094Z",
12848
- whisperVersion: "0.7.0"
12872
+ ezioGitSha: "45823b4",
12873
+ builtAt: "2026-06-23T19:09:50.748Z",
12874
+ whisperVersion: "0.8.0"
12849
12875
  };
12850
12876
 
12851
12877
  // src/ezio-provenance-types.ts
@@ -16389,6 +16415,91 @@ import { existsSync as existsSync13 } from "node:fs";
16389
16415
  // src/runtime/evaluator-config.ts
16390
16416
  import { readFileSync as readFileSync9, statSync as statSync4 } from "node:fs";
16391
16417
  import { join as join22 } from "node:path";
16418
+ function parseDotEnv(text) {
16419
+ const out = {};
16420
+ for (const rawLine of text.split(/\r?\n/)) {
16421
+ const line = rawLine.trim();
16422
+ if (line === "" || line.startsWith("#")) continue;
16423
+ const eq = line.indexOf("=");
16424
+ if (eq <= 0) continue;
16425
+ const key = line.slice(0, eq).trim();
16426
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key)) continue;
16427
+ let val = line.slice(eq + 1).trim();
16428
+ if (val.length >= 2 && (val.startsWith('"') && val.endsWith('"') || val.startsWith("'") && val.endsWith("'"))) {
16429
+ val = val.slice(1, -1);
16430
+ }
16431
+ out[key] = val;
16432
+ }
16433
+ return out;
16434
+ }
16435
+ function warnIfLoosePerms(path4) {
16436
+ if (process.platform === "win32") return;
16437
+ let mode;
16438
+ try {
16439
+ mode = statSync4(path4).mode;
16440
+ } catch {
16441
+ return;
16442
+ }
16443
+ if (mode & 63) {
16444
+ console.error(
16445
+ `Warning: ${path4} is accessible by group/world; it holds secrets. Run: chmod 600 ${path4}`
16446
+ );
16447
+ }
16448
+ }
16449
+ function readJsonFile(path4, label) {
16450
+ let text;
16451
+ try {
16452
+ text = readFileSync9(path4, "utf8");
16453
+ } catch (err) {
16454
+ if (err.code === "ENOENT") return null;
16455
+ throw err;
16456
+ }
16457
+ try {
16458
+ return JSON.parse(text);
16459
+ } catch (err) {
16460
+ throw new Error(`Invalid ${label}: ${err.message}`);
16461
+ }
16462
+ }
16463
+ function loadEvaluatorConfig() {
16464
+ const root = getStateRoot();
16465
+ let dotenv = {};
16466
+ try {
16467
+ dotenv = parseDotEnv(readFileSync9(join22(root, ".env"), "utf8"));
16468
+ } catch (err) {
16469
+ if (err.code !== "ENOENT") throw err;
16470
+ }
16471
+ const envGet = (k) => process.env[k] ?? dotenv[k];
16472
+ const authPath = join22(root, "auth.json");
16473
+ warnIfLoosePerms(authPath);
16474
+ const auth = readJsonFile(authPath, "auth.json");
16475
+ const config = readJsonFile(join22(root, "config.json"), "config.json");
16476
+ const evalCfg = config?.evaluator ?? {};
16477
+ const evalOllama = evalCfg.ollama ?? {};
16478
+ const provider = (envGet("AI_WHISPER_EVALUATOR_PROVIDER") ?? evalCfg.provider) === "ollama" ? "ollama" : "anthropic";
16479
+ const rawFallback = envGet("AI_WHISPER_EVALUATOR_FALLBACK") ?? evalCfg.fallback;
16480
+ const fallback = rawFallback === "anthropic" || rawFallback === "ollama" ? rawFallback : null;
16481
+ const apiKey = envGet("ANTHROPIC_API_KEY") ?? auth?.ANTHROPIC_API_KEY ?? null;
16482
+ return {
16483
+ provider,
16484
+ fallback,
16485
+ anthropic: {
16486
+ apiKey: apiKey && apiKey.length > 0 ? apiKey : null,
16487
+ model: evalCfg.anthropicModel ?? null
16488
+ },
16489
+ ollama: {
16490
+ host: envGet("AI_WHISPER_EVALUATOR_OLLAMA_HOST") ?? evalOllama.host ?? null,
16491
+ model: envGet("AI_WHISPER_EVALUATOR_OLLAMA_MODEL") ?? evalOllama.model ?? null
16492
+ }
16493
+ };
16494
+ }
16495
+ function computeEvaluatorStatus(cfg, ctx) {
16496
+ if (ctx.loaderError) return "invalid_config";
16497
+ if (!ctx.orchestratorEnabled) return "disabled";
16498
+ if (cfg.provider === "anthropic" && cfg.anthropic.apiKey === null) {
16499
+ return "missing_anthropic_key";
16500
+ }
16501
+ return "ready";
16502
+ }
16392
16503
  function isEvaluatorReady(status) {
16393
16504
  return status === "ready" || status === "unknown";
16394
16505
  }
@@ -17102,13 +17213,26 @@ function resolveInstallPath() {
17102
17213
  }
17103
17214
 
17104
17215
  // src/commands/env/report.ts
17216
+ function buildEvaluatorReport() {
17217
+ let status;
17218
+ try {
17219
+ status = computeEvaluatorStatus(loadEvaluatorConfig(), {
17220
+ orchestratorEnabled: true,
17221
+ loaderError: null
17222
+ });
17223
+ } catch {
17224
+ status = "invalid_config";
17225
+ }
17226
+ return { status, ready: isEvaluatorReady(status) };
17227
+ }
17105
17228
  function buildEnvReport() {
17106
17229
  return {
17107
17230
  engineVersion: resolveCliVersion(),
17108
17231
  installPath: resolveInstallPath(),
17109
17232
  stateRoot: getStateRoot(),
17110
17233
  dbSchemaVersion: CURRENT_SCHEMA_VERSION,
17111
- protocolVersion: EVENT_PROTOCOL_VERSION
17234
+ protocolVersion: EVENT_PROTOCOL_VERSION,
17235
+ evaluator: buildEvaluatorReport()
17112
17236
  };
17113
17237
  }
17114
17238
  function renderEnvReportText(report) {
@@ -17117,7 +17241,8 @@ function renderEnvReportText(report) {
17117
17241
  `installPath: ${report.installPath}`,
17118
17242
  `stateRoot: ${report.stateRoot}`,
17119
17243
  `dbSchemaVersion: ${report.dbSchemaVersion}`,
17120
- `protocolVersion: ${report.protocolVersion}`
17244
+ `protocolVersion: ${report.protocolVersion}`,
17245
+ `evaluator: ${report.evaluator.status} (ready=${report.evaluator.ready})`
17121
17246
  ].join("\n");
17122
17247
  }
17123
17248
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-whisper",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "Terminal-first relay for paired AI coding agents (Claude + Codex), driven by structured workflows.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,12 +51,12 @@
51
51
  "@types/better-sqlite3": "^7.6.12",
52
52
  "@types/react": "^19.2.14",
53
53
  "ink-testing-library": "^4.0.0",
54
- "@ai-whisper/adapter-claude": "0.0.0",
55
54
  "@ai-whisper/adapter-ai-ezio": "0.0.0",
55
+ "@ai-whisper/adapter-claude": "0.0.0",
56
56
  "@ai-whisper/adapter-codex": "0.0.0",
57
57
  "@ai-whisper/companion-core": "0.0.0",
58
- "@ai-whisper/shared": "0.0.0",
59
- "@ai-whisper/broker": "0.0.0"
58
+ "@ai-whisper/broker": "0.0.0",
59
+ "@ai-whisper/shared": "0.0.0"
60
60
  },
61
61
  "files": [
62
62
  "dist",