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 +12 -4
- package/dist/bin/broker-daemon.js +107 -82
- package/dist/bin/companion-agent.js +871 -131
- package/dist/bin/relay-monitor.js +111 -83
- package/dist/bin/turn-event-shim.js +18 -6
- package/dist/bin/whisper.js +1445 -318
- package/package.json +4 -3
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
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
|
},
|