ai-whisper 0.8.0 → 0.9.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
@@ -1,6 +1,6 @@
1
1
  # ai-whisper
2
2
 
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.
3
+ ai-whisper pairs two coding agents — mount any two of Claude, Codex, ezio, and agy — 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,11 +52,12 @@ It is **not** for:
52
52
 
53
53
  ## Prerequisites
54
54
 
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.
55
+ You pair any two of four agents — `claude`, `codex`, `ezio`, and `agy`. ai-whisper drives the *real* Claude, Codex, and Antigravity CLIs, so install and authenticate whichever of those you plan to mount first; `ezio` is protocol-native and ships with ai-whisper.
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
59
  - **ezio** *(optional)* — bundled with ai-whisper; mount it with `whisper collab mount ezio`, no separate install.
60
+ - **agy** *(optional)* — the `agy` command (Antigravity CLI), signed in. Mount it with `whisper collab mount agy` (manual-mount parity; no auto-launch via `collab start`).
60
61
  - **Node.js 22+**.
61
62
  - **An LLM evaluator with credentials** — workflows are gated by it and refuse to start without it. See [Evaluator configuration](docs/evaluator-configuration.md).
62
63
  - **tmux** *(optional)* — only for `whisper collab start`, which auto-arranges both agents into panes. The mount flow below does not need it.
@@ -105,6 +106,13 @@ The first `mount` creates the collab and starts the broker daemon for the worksp
105
106
  whisper collab dashboard
106
107
  ```
107
108
 
109
+ - `whisper collab dashboard` — live wall of recently-active collabs + per-run inspector.
110
+ Add `--all` to show every workflow run (no per-collab masking); combine with
111
+ `--window all` for the full run ledger.
112
+ - From the dashboard you can pause/resume/cancel a workflow run in place
113
+ (`p`/`r`/`c`, each confirmed), and a header bar shows live counts of running /
114
+ paused / stuck / done / canceled / idle runs.
115
+
108
116
  > Running from a repo checkout instead of a packaged install? Build first (`pnpm build`) and invoke the CLI as `node packages/cli/dist/bin/whisper.js ...` wherever these examples say `whisper ...`.
109
117
 
110
118
  ## What happens if it fails?
@@ -113,9 +121,9 @@ A run that stops short usually **escalates** — it does not crash. When the eva
113
121
 
114
122
  ## Core concepts
115
123
 
116
- 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.
124
+ 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, ezio, or agy — 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.
117
125
 
118
- 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.
126
+ Claude, Codex, ezio, and agy 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.
119
127
 
120
128
  For the full mental model, read [Concepts](docs/concepts.md).
121
129
 
@@ -63,7 +63,7 @@ var threadStates = [
63
63
  "failed"
64
64
  ];
65
65
  var collabStates = ["active", "stopped"];
66
- var agentTypes = ["codex", "claude", "ezio"];
66
+ var agentTypes = ["codex", "claude", "ezio", "agy"];
67
67
  var sessionRegistrationStates = ["registered"];
68
68
  var workItemStates = [
69
69
  "queued",
@@ -332,7 +332,7 @@ var mockProviderReplySchema = z14.object({
332
332
 
333
333
  // ../shared/dist/relay-host.js
334
334
  import { z as z15 } from "zod";
335
- var relayTargets = ["codex", "claude", "ezio", "pull"];
335
+ var relayTargets = ["codex", "claude", "ezio", "agy", "pull"];
336
336
  var relayDirectiveSchema = z15.object({
337
337
  raw: z15.string().min(1),
338
338
  target: z15.enum(relayTargets),
@@ -1913,7 +1913,8 @@ function listActiveCollabSummaries(db, input) {
1913
1913
  GROUP BY c.collab_id
1914
1914
  HAVING MAX(h.last_activity_at) >= ?
1915
1915
  OR EXISTS (SELECT 1 FROM workflows w
1916
- WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
1916
+ WHERE w.collab_id = c.collab_id
1917
+ AND w.status IN ('running','paused'))`).all(cutoff);
1917
1918
  const out = [];
1918
1919
  const seen = /* @__PURE__ */ new Set();
1919
1920
  for (const e of eligible) {
@@ -1938,87 +1939,105 @@ function listActiveCollabSummaries(db, input) {
1938
1939
  }
1939
1940
  return out;
1940
1941
  }
1941
- function buildCollabSummary(db, collabId2) {
1942
- {
1943
- const e = { collabId: collabId2 };
1944
- const collab2 = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
1945
- FROM collab WHERE collab_id = ?`).get(e.collabId);
1946
- const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
1947
- name, status, current_phase_index AS currentPhaseIndex,
1948
- created_at AS createdAt, spec_path AS specPath
1949
- FROM workflows WHERE collab_id = ?
1950
- ORDER BY (status = 'running') DESC, created_at DESC
1951
- LIMIT 1`).get(e.collabId);
1952
- let currentPhaseRunId = null;
1953
- let phaseIndex = null;
1954
- let phaseName = null;
1955
- let chainId = null;
1956
- if (wf) {
1957
- const ph = db.prepare(`SELECT phase_run_id AS phaseRunId, phase_index AS phaseIndex,
1958
- phase_name AS phaseName, chain_id AS chainId
1959
- FROM workflow_phases WHERE workflow_id = ?
1960
- ORDER BY (ended_at IS NULL) DESC, started_at DESC
1961
- LIMIT 1`).get(wf.workflowId);
1962
- if (ph) {
1963
- currentPhaseRunId = ph.phaseRunId;
1964
- phaseIndex = ph.phaseIndex;
1965
- phaseName = ph.phaseName;
1966
- chainId = ph.chainId;
1967
- }
1942
+ function buildWorkflowSummary(db, collabId2, wf) {
1943
+ const collab2 = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
1944
+ FROM collab WHERE collab_id = ?`).get(collabId2);
1945
+ let currentPhaseRunId = null;
1946
+ let phaseIndex = null;
1947
+ let phaseName = null;
1948
+ let chainId = null;
1949
+ if (wf) {
1950
+ const ph = db.prepare(`SELECT phase_run_id AS phaseRunId, phase_index AS phaseIndex,
1951
+ phase_name AS phaseName, chain_id AS chainId
1952
+ FROM workflow_phases WHERE workflow_id = ?
1953
+ ORDER BY (ended_at IS NULL) DESC, started_at DESC
1954
+ LIMIT 1`).get(wf.workflowId);
1955
+ if (ph) {
1956
+ currentPhaseRunId = ph.phaseRunId;
1957
+ phaseIndex = ph.phaseIndex;
1958
+ phaseName = ph.phaseName;
1959
+ chainId = ph.chainId;
1968
1960
  }
1969
- const chain = chainId ? db.prepare(`SELECT status, current_round AS currentRound, max_rounds AS maxRounds
1970
- FROM relay_chains WHERE chain_id = ?`).get(chainId) : void 0;
1971
- const turn = db.prepare(`SELECT turn_owner AS owner, waiting_agent AS waiting, handoff_state AS handoffState
1972
- FROM relay_turn_state WHERE collab_id = ?`).get(e.collabId);
1973
- const sessions = db.prepare(`SELECT agentType, healthState FROM (
1974
- SELECT s.agent_type AS agentType,
1975
- s.health_state AS healthState,
1976
- ROW_NUMBER() OVER (
1977
- PARTITION BY s.agent_type
1978
- ORDER BY CASE WHEN sb.active_session_id = s.session_id
1979
- THEN 0 ELSE 1 END ASC,
1980
- s.registered_at DESC,
1981
- s.rowid DESC
1982
- ) AS rn
1983
- FROM session s
1984
- LEFT JOIN session_binding sb
1985
- ON sb.collab_id = s.collab_id
1986
- AND sb.agent_type = s.agent_type
1987
- WHERE s.collab_id = ?
1988
- ) ranked
1989
- WHERE rn = 1
1990
- ORDER BY agentType ASC`).all(e.collabId);
1991
- const label = wf?.name && wf.name.trim() || collab2?.displayName && collab2.displayName.trim() || (collab2?.workspaceRoot ? basename2(collab2.workspaceRoot) : "") || e.collabId.slice(0, 12);
1992
- const runLastActRow = wf ? db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
1993
- FROM relay_handoff
1994
- WHERE collab_id = ? AND workflow_id = ?`).get(e.collabId, wf.workflowId) : db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
1995
- FROM relay_handoff
1996
- WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
1997
- const runLastAct = runLastActRow?.lastAct ?? "";
1998
- return {
1999
- collabId: e.collabId,
2000
- label,
2001
- workspaceRoot: collab2?.workspaceRoot ?? "",
2002
- workflowId: wf?.workflowId ?? null,
2003
- workflowType: wf?.workflowType ?? null,
2004
- workflowStatus: wf?.status ?? null,
2005
- currentPhaseRunId,
2006
- phaseIndex,
2007
- phaseName,
2008
- currentRound: chain?.currentRound ?? null,
2009
- maxRounds: chain?.maxRounds ?? null,
2010
- chainStatus: chain?.status ?? null,
2011
- turn: {
2012
- owner: turn?.owner ?? "none",
2013
- waiting: turn?.waiting ?? null,
2014
- handoffState: turn?.handoffState ?? "idle"
2015
- },
2016
- sessions,
2017
- workflowCreatedAt: wf?.createdAt ?? null,
2018
- specPath: wf ? displayArtifactPath(wf.specPath, collab2?.workspaceRoot ?? "") : null,
2019
- lastActivityAt: runLastAct
2020
- };
2021
1961
  }
1962
+ const chain = chainId ? db.prepare(`SELECT status, current_round AS currentRound, max_rounds AS maxRounds
1963
+ FROM relay_chains WHERE chain_id = ?`).get(chainId) : void 0;
1964
+ const turn = db.prepare(`SELECT turn_owner AS owner, waiting_agent AS waiting, handoff_state AS handoffState
1965
+ FROM relay_turn_state WHERE collab_id = ?`).get(collabId2);
1966
+ const sessions = db.prepare(`SELECT agentType, healthState FROM (
1967
+ SELECT s.agent_type AS agentType,
1968
+ s.health_state AS healthState,
1969
+ ROW_NUMBER() OVER (
1970
+ PARTITION BY s.agent_type
1971
+ ORDER BY CASE WHEN sb.active_session_id = s.session_id
1972
+ THEN 0 ELSE 1 END ASC,
1973
+ s.registered_at DESC,
1974
+ s.rowid DESC
1975
+ ) AS rn
1976
+ FROM session s
1977
+ LEFT JOIN session_binding sb
1978
+ ON sb.collab_id = s.collab_id
1979
+ AND sb.agent_type = s.agent_type
1980
+ WHERE s.collab_id = ?
1981
+ ) ranked
1982
+ WHERE rn = 1
1983
+ ORDER BY agentType ASC`).all(collabId2);
1984
+ const label = wf?.name && wf.name.trim() || collab2?.displayName && collab2.displayName.trim() || (collab2?.workspaceRoot ? basename2(collab2.workspaceRoot) : "") || collabId2.slice(0, 12);
1985
+ const runLastActRow = wf ? db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
1986
+ FROM relay_handoff
1987
+ WHERE collab_id = ? AND workflow_id = ?`).get(collabId2, wf.workflowId) : db.prepare(`SELECT COALESCE(MAX(last_activity_at), '') AS lastAct
1988
+ FROM relay_handoff
1989
+ WHERE collab_id = ? AND workflow_id IS NULL`).get(collabId2);
1990
+ const runLastAct = runLastActRow?.lastAct ?? "";
1991
+ return {
1992
+ collabId: collabId2,
1993
+ label,
1994
+ workspaceRoot: collab2?.workspaceRoot ?? "",
1995
+ workflowId: wf?.workflowId ?? null,
1996
+ workflowType: wf?.workflowType ?? null,
1997
+ workflowStatus: wf?.status ?? null,
1998
+ currentPhaseRunId,
1999
+ phaseIndex,
2000
+ phaseName,
2001
+ currentRound: chain?.currentRound ?? null,
2002
+ maxRounds: chain?.maxRounds ?? null,
2003
+ chainStatus: chain?.status ?? null,
2004
+ turn: {
2005
+ owner: turn?.owner ?? "none",
2006
+ waiting: turn?.waiting ?? null,
2007
+ handoffState: turn?.handoffState ?? "idle"
2008
+ },
2009
+ sessions,
2010
+ workflowCreatedAt: wf?.createdAt ?? null,
2011
+ specPath: wf ? displayArtifactPath(wf.specPath, collab2?.workspaceRoot ?? "") : null,
2012
+ lastActivityAt: runLastAct
2013
+ };
2014
+ }
2015
+ function buildCollabSummary(db, collabId2) {
2016
+ const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
2017
+ name, status, current_phase_index AS currentPhaseIndex,
2018
+ created_at AS createdAt, spec_path AS specPath
2019
+ FROM workflows WHERE collab_id = ?
2020
+ ORDER BY (status = 'running') DESC, created_at DESC
2021
+ LIMIT 1`).get(collabId2);
2022
+ return buildWorkflowSummary(db, collabId2, wf ?? null);
2023
+ }
2024
+ function listAllWorkflowSummaries(db, input) {
2025
+ const nowMs = Date.parse(input.now ?? (/* @__PURE__ */ new Date()).toISOString());
2026
+ const base = Number.isFinite(nowMs) ? nowMs : Date.now();
2027
+ const cutoff = new Date(Math.max(0, base - input.sinceMs)).toISOString();
2028
+ const rows = db.prepare(`SELECT w.workflow_id AS workflowId, w.workflow_type AS workflowType,
2029
+ w.name AS name, w.status AS status,
2030
+ w.current_phase_index AS currentPhaseIndex,
2031
+ w.created_at AS createdAt, w.spec_path AS specPath,
2032
+ w.collab_id AS collabId
2033
+ FROM workflows w
2034
+ LEFT JOIN relay_handoff h ON h.workflow_id = w.workflow_id
2035
+ GROUP BY w.workflow_id
2036
+ HAVING w.status IN ('running','paused')
2037
+ OR MAX(h.last_activity_at) >= ?
2038
+ OR (MAX(h.last_activity_at) IS NULL AND w.created_at >= ?)
2039
+ ORDER BY w.created_at DESC, w.rowid DESC`).all(cutoff, cutoff);
2040
+ return rows.map((r) => buildWorkflowSummary(db, r.collabId, r));
2022
2041
  }
2023
2042
  function listWorkflowsForCollab(db, collabId2) {
2024
2043
  const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
@@ -4327,6 +4346,12 @@ function createControlService(db, events) {
4327
4346
  ...now !== void 0 ? { now } : {}
4328
4347
  });
4329
4348
  },
4349
+ listAllWorkflowSummaries(sinceMs, now) {
4350
+ return listAllWorkflowSummaries(db, {
4351
+ sinceMs,
4352
+ ...now !== void 0 ? { now } : {}
4353
+ });
4354
+ },
4330
4355
  listRunCostRows(collabId2, workflowId) {
4331
4356
  return listRunCostRows(db, { collabId: collabId2, workflowId });
4332
4357
  },