ai-whisper 0.1.1 → 0.1.3
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 +2 -1
- package/dist/bin/broker-daemon.js +27 -3
- package/dist/bin/companion-agent.js +47 -15
- package/dist/bin/relay-monitor.js +30 -4
- package/dist/bin/whisper.js +110 -25
- package/dist/skills/ai-whisper-ralph/SKILL.md +1 -1
- package/dist/skills/ai-whisper-sdd/SKILL.md +1 -1
- package/package.json +5 -5
- package/skills/ai-whisper-ralph/SKILL.md +1 -1
- package/skills/ai-whisper-sdd/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -21,7 +21,7 @@ Run spec-driven-development using docs/spec.md
|
|
|
21
21
|
|
|
22
22
|
From there ai-whisper runs the workflow autonomously:
|
|
23
23
|
|
|
24
|
-
- **Implementer / reviewer assignment** —
|
|
24
|
+
- **Implementer / reviewer assignment** — the agent you trigger the workflow from becomes the implementer and the other agent becomes the reviewer; pass `--implementer` / `--reviewer` to choose explicitly. (Started outside a mounted session with no flags, it falls back to a default pairing and warns.) The baton passes between them; only one owns the turn at a time.
|
|
25
25
|
- **Autonomous execution** — the implementer does each step in its real session and hands the result back. An LLM evaluator judges whether the deliverable meets the request.
|
|
26
26
|
- **Review loops** — when work isn't good enough yet, the reviewer's findings are composed into a follow-up handoff and the implementer iterates. The loop repeats until the work is approved or the round budget is exhausted.
|
|
27
27
|
- **Resumability** — workflow and chain state is durable. If the broker restarts or you stop for the day, you recover and reconnect rather than starting over.
|
|
@@ -100,6 +100,7 @@ For the full mental model, read [Concepts](docs/concepts.md).
|
|
|
100
100
|
|
|
101
101
|
## Learn more
|
|
102
102
|
|
|
103
|
+
- [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.
|
|
103
104
|
- [Concepts](docs/concepts.md) — the mental model: baton handoff, real mounted sessions, supervised autonomy, workflow-first execution.
|
|
104
105
|
- [Relay & handoff flows](docs/relay-handoff-flows.md) — the complete handoff state machine, capture-status table, hotkey reference, per-step verdicts, and troubleshooting.
|
|
105
106
|
- [Evaluator configuration](docs/evaluator-configuration.md) — required credentials and options for the LLM evaluator that gates workflows.
|
|
@@ -1702,7 +1702,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1702
1702
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1703
1703
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1704
1704
|
const out = [];
|
|
1705
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1705
1706
|
for (const e of eligible) {
|
|
1707
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1708
|
+
seen.add(e.collabId);
|
|
1709
|
+
}
|
|
1710
|
+
const minResults = input.minResults ?? 3;
|
|
1711
|
+
if (out.length < minResults) {
|
|
1712
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1713
|
+
FROM workflows
|
|
1714
|
+
WHERE status IN ('done','halted','canceled')
|
|
1715
|
+
GROUP BY collab_id
|
|
1716
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1717
|
+
for (const f of finished) {
|
|
1718
|
+
if (out.length >= minResults)
|
|
1719
|
+
break;
|
|
1720
|
+
if (seen.has(f.collabId))
|
|
1721
|
+
continue;
|
|
1722
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1723
|
+
seen.add(f.collabId);
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
return out;
|
|
1727
|
+
}
|
|
1728
|
+
function buildCollabSummary(db, collabId2) {
|
|
1729
|
+
{
|
|
1730
|
+
const e = { collabId: collabId2 };
|
|
1706
1731
|
const collab2 = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1707
1732
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1708
1733
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1756,7 +1781,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1756
1781
|
FROM relay_handoff
|
|
1757
1782
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1758
1783
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1759
|
-
|
|
1784
|
+
return {
|
|
1760
1785
|
collabId: e.collabId,
|
|
1761
1786
|
label,
|
|
1762
1787
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1775,9 +1800,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1775
1800
|
},
|
|
1776
1801
|
sessions,
|
|
1777
1802
|
lastActivityAt: runLastAct
|
|
1778
|
-
}
|
|
1803
|
+
};
|
|
1779
1804
|
}
|
|
1780
|
-
return out;
|
|
1781
1805
|
}
|
|
1782
1806
|
function listWorkflowsForCollab(db, collabId2) {
|
|
1783
1807
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1735,7 +1735,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1735
1735
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1736
1736
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1737
1737
|
const out = [];
|
|
1738
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1738
1739
|
for (const e of eligible) {
|
|
1740
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1741
|
+
seen.add(e.collabId);
|
|
1742
|
+
}
|
|
1743
|
+
const minResults = input.minResults ?? 3;
|
|
1744
|
+
if (out.length < minResults) {
|
|
1745
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1746
|
+
FROM workflows
|
|
1747
|
+
WHERE status IN ('done','halted','canceled')
|
|
1748
|
+
GROUP BY collab_id
|
|
1749
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1750
|
+
for (const f of finished) {
|
|
1751
|
+
if (out.length >= minResults)
|
|
1752
|
+
break;
|
|
1753
|
+
if (seen.has(f.collabId))
|
|
1754
|
+
continue;
|
|
1755
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1756
|
+
seen.add(f.collabId);
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
return out;
|
|
1760
|
+
}
|
|
1761
|
+
function buildCollabSummary(db, collabId) {
|
|
1762
|
+
{
|
|
1763
|
+
const e = { collabId };
|
|
1739
1764
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1740
1765
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1741
1766
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1789,7 +1814,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1789
1814
|
FROM relay_handoff
|
|
1790
1815
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1791
1816
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1792
|
-
|
|
1817
|
+
return {
|
|
1793
1818
|
collabId: e.collabId,
|
|
1794
1819
|
label,
|
|
1795
1820
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1808,9 +1833,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1808
1833
|
},
|
|
1809
1834
|
sessions,
|
|
1810
1835
|
lastActivityAt: runLastAct
|
|
1811
|
-
}
|
|
1836
|
+
};
|
|
1812
1837
|
}
|
|
1813
|
-
return out;
|
|
1814
1838
|
}
|
|
1815
1839
|
function listWorkflowsForCollab(db, collabId) {
|
|
1816
1840
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -5866,12 +5890,16 @@ function createNodePty(input) {
|
|
|
5866
5890
|
ensureNodePtySpawnHelperExecutable({
|
|
5867
5891
|
unixTerminalPath: nodePtyUnixTerminalPath
|
|
5868
5892
|
});
|
|
5869
|
-
return spawn2(input.config.executable, input.config.execArgs, {
|
|
5870
|
-
|
|
5871
|
-
|
|
5872
|
-
|
|
5873
|
-
|
|
5874
|
-
|
|
5893
|
+
return spawn2(input.config.executable, input.config.execArgs, buildClaudePtySpawnOptions({ cols: input.cols, rows: input.rows, cwd: input.cwd }));
|
|
5894
|
+
}
|
|
5895
|
+
function buildClaudePtySpawnOptions(input) {
|
|
5896
|
+
const env = {};
|
|
5897
|
+
for (const [key, value] of Object.entries(input.baseEnv ?? process.env)) {
|
|
5898
|
+
if (typeof value === "string")
|
|
5899
|
+
env[key] = value;
|
|
5900
|
+
}
|
|
5901
|
+
env.AI_WHISPER_AGENT = "claude";
|
|
5902
|
+
return { name: "xterm-256color", cols: input.cols, rows: input.rows, cwd: input.cwd, env };
|
|
5875
5903
|
}
|
|
5876
5904
|
function createClaudeLiveSession(input) {
|
|
5877
5905
|
let pty = null;
|
|
@@ -6169,12 +6197,16 @@ function createNodePty2(input) {
|
|
|
6169
6197
|
ensureNodePtySpawnHelperExecutable({
|
|
6170
6198
|
unixTerminalPath: nodePtyUnixTerminalPath2
|
|
6171
6199
|
});
|
|
6172
|
-
return spawn4(input.config.executable, input.config.execArgs, {
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6200
|
+
return spawn4(input.config.executable, input.config.execArgs, buildCodexPtySpawnOptions({ cols: input.cols, rows: input.rows, cwd: input.cwd }));
|
|
6201
|
+
}
|
|
6202
|
+
function buildCodexPtySpawnOptions(input) {
|
|
6203
|
+
const env = {};
|
|
6204
|
+
for (const [key, value] of Object.entries(input.baseEnv ?? process.env)) {
|
|
6205
|
+
if (typeof value === "string")
|
|
6206
|
+
env[key] = value;
|
|
6207
|
+
}
|
|
6208
|
+
env.AI_WHISPER_AGENT = "codex";
|
|
6209
|
+
return { name: "xterm-256color", cols: input.cols, rows: input.rows, cwd: input.cwd, env };
|
|
6178
6210
|
}
|
|
6179
6211
|
function createCodexLiveSession(input) {
|
|
6180
6212
|
let pty = null;
|
|
@@ -1705,7 +1705,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1705
1705
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1706
1706
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1707
1707
|
const out = [];
|
|
1708
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1708
1709
|
for (const e of eligible) {
|
|
1710
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1711
|
+
seen.add(e.collabId);
|
|
1712
|
+
}
|
|
1713
|
+
const minResults = input.minResults ?? 3;
|
|
1714
|
+
if (out.length < minResults) {
|
|
1715
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1716
|
+
FROM workflows
|
|
1717
|
+
WHERE status IN ('done','halted','canceled')
|
|
1718
|
+
GROUP BY collab_id
|
|
1719
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1720
|
+
for (const f of finished) {
|
|
1721
|
+
if (out.length >= minResults)
|
|
1722
|
+
break;
|
|
1723
|
+
if (seen.has(f.collabId))
|
|
1724
|
+
continue;
|
|
1725
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1726
|
+
seen.add(f.collabId);
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return out;
|
|
1730
|
+
}
|
|
1731
|
+
function buildCollabSummary(db, collabId2) {
|
|
1732
|
+
{
|
|
1733
|
+
const e = { collabId: collabId2 };
|
|
1709
1734
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1710
1735
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1711
1736
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1759,7 +1784,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1759
1784
|
FROM relay_handoff
|
|
1760
1785
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1761
1786
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1762
|
-
|
|
1787
|
+
return {
|
|
1763
1788
|
collabId: e.collabId,
|
|
1764
1789
|
label,
|
|
1765
1790
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1778,9 +1803,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1778
1803
|
},
|
|
1779
1804
|
sessions,
|
|
1780
1805
|
lastActivityAt: runLastAct
|
|
1781
|
-
}
|
|
1806
|
+
};
|
|
1782
1807
|
}
|
|
1783
|
-
return out;
|
|
1784
1808
|
}
|
|
1785
1809
|
function listWorkflowsForCollab(db, collabId2) {
|
|
1786
1810
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -5076,7 +5100,9 @@ function computeLiveness(snap) {
|
|
|
5076
5100
|
let why = null;
|
|
5077
5101
|
let stuck = false;
|
|
5078
5102
|
let liveOverride = null;
|
|
5079
|
-
if (terminal === "
|
|
5103
|
+
if (terminal === "done") {
|
|
5104
|
+
return { stuck: false, why: null, liveText: "done" };
|
|
5105
|
+
} else if (terminal === "halted" || terminal === "canceled") {
|
|
5080
5106
|
stuck = true;
|
|
5081
5107
|
why = snap.workflow?.haltReason ? `${terminal}: ${snap.workflow.haltReason}` : `workflow ${terminal}`;
|
|
5082
5108
|
} else if (chainStatus === "escalated" || chainStatus === "abandoned") {
|
package/dist/bin/whisper.js
CHANGED
|
@@ -1775,7 +1775,32 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1775
1775
|
OR EXISTS (SELECT 1 FROM workflows w
|
|
1776
1776
|
WHERE w.collab_id = c.collab_id AND w.status = 'running')`).all(cutoff);
|
|
1777
1777
|
const out = [];
|
|
1778
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1778
1779
|
for (const e of eligible) {
|
|
1780
|
+
out.push(buildCollabSummary(db, e.collabId));
|
|
1781
|
+
seen.add(e.collabId);
|
|
1782
|
+
}
|
|
1783
|
+
const minResults = input.minResults ?? 3;
|
|
1784
|
+
if (out.length < minResults) {
|
|
1785
|
+
const finished = db.prepare(`SELECT collab_id AS collabId, MAX(created_at) AS lastCreated
|
|
1786
|
+
FROM workflows
|
|
1787
|
+
WHERE status IN ('done','halted','canceled')
|
|
1788
|
+
GROUP BY collab_id
|
|
1789
|
+
ORDER BY lastCreated DESC, collab_id DESC`).all();
|
|
1790
|
+
for (const f of finished) {
|
|
1791
|
+
if (out.length >= minResults)
|
|
1792
|
+
break;
|
|
1793
|
+
if (seen.has(f.collabId))
|
|
1794
|
+
continue;
|
|
1795
|
+
out.push(buildCollabSummary(db, f.collabId));
|
|
1796
|
+
seen.add(f.collabId);
|
|
1797
|
+
}
|
|
1798
|
+
}
|
|
1799
|
+
return out;
|
|
1800
|
+
}
|
|
1801
|
+
function buildCollabSummary(db, collabId) {
|
|
1802
|
+
{
|
|
1803
|
+
const e = { collabId };
|
|
1779
1804
|
const collab = db.prepare(`SELECT display_name AS displayName, workspace_root AS workspaceRoot
|
|
1780
1805
|
FROM collab WHERE collab_id = ?`).get(e.collabId);
|
|
1781
1806
|
const wf = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -1829,7 +1854,7 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1829
1854
|
FROM relay_handoff
|
|
1830
1855
|
WHERE collab_id = ? AND workflow_id IS NULL`).get(e.collabId);
|
|
1831
1856
|
const runLastAct = runLastActRow?.lastAct ?? "";
|
|
1832
|
-
|
|
1857
|
+
return {
|
|
1833
1858
|
collabId: e.collabId,
|
|
1834
1859
|
label,
|
|
1835
1860
|
workflowId: wf?.workflowId ?? null,
|
|
@@ -1848,9 +1873,8 @@ function listActiveCollabSummaries(db, input) {
|
|
|
1848
1873
|
},
|
|
1849
1874
|
sessions,
|
|
1850
1875
|
lastActivityAt: runLastAct
|
|
1851
|
-
}
|
|
1876
|
+
};
|
|
1852
1877
|
}
|
|
1853
|
-
return out;
|
|
1854
1878
|
}
|
|
1855
1879
|
function listWorkflowsForCollab(db, collabId) {
|
|
1856
1880
|
const rows = db.prepare(`SELECT workflow_id AS workflowId, workflow_type AS workflowType,
|
|
@@ -6168,12 +6192,16 @@ function createNodePty(input) {
|
|
|
6168
6192
|
ensureNodePtySpawnHelperExecutable({
|
|
6169
6193
|
unixTerminalPath: nodePtyUnixTerminalPath
|
|
6170
6194
|
});
|
|
6171
|
-
return spawn3(input.config.executable, input.config.execArgs, {
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6195
|
+
return spawn3(input.config.executable, input.config.execArgs, buildClaudePtySpawnOptions({ cols: input.cols, rows: input.rows, cwd: input.cwd }));
|
|
6196
|
+
}
|
|
6197
|
+
function buildClaudePtySpawnOptions(input) {
|
|
6198
|
+
const env = {};
|
|
6199
|
+
for (const [key, value] of Object.entries(input.baseEnv ?? process.env)) {
|
|
6200
|
+
if (typeof value === "string")
|
|
6201
|
+
env[key] = value;
|
|
6202
|
+
}
|
|
6203
|
+
env.AI_WHISPER_AGENT = "claude";
|
|
6204
|
+
return { name: "xterm-256color", cols: input.cols, rows: input.rows, cwd: input.cwd, env };
|
|
6177
6205
|
}
|
|
6178
6206
|
function createClaudeLiveSession(input) {
|
|
6179
6207
|
let pty = null;
|
|
@@ -6471,12 +6499,16 @@ function createNodePty2(input) {
|
|
|
6471
6499
|
ensureNodePtySpawnHelperExecutable({
|
|
6472
6500
|
unixTerminalPath: nodePtyUnixTerminalPath2
|
|
6473
6501
|
});
|
|
6474
|
-
return spawn5(input.config.executable, input.config.execArgs, {
|
|
6475
|
-
|
|
6476
|
-
|
|
6477
|
-
|
|
6478
|
-
|
|
6479
|
-
|
|
6502
|
+
return spawn5(input.config.executable, input.config.execArgs, buildCodexPtySpawnOptions({ cols: input.cols, rows: input.rows, cwd: input.cwd }));
|
|
6503
|
+
}
|
|
6504
|
+
function buildCodexPtySpawnOptions(input) {
|
|
6505
|
+
const env = {};
|
|
6506
|
+
for (const [key, value] of Object.entries(input.baseEnv ?? process.env)) {
|
|
6507
|
+
if (typeof value === "string")
|
|
6508
|
+
env[key] = value;
|
|
6509
|
+
}
|
|
6510
|
+
env.AI_WHISPER_AGENT = "codex";
|
|
6511
|
+
return { name: "xterm-256color", cols: input.cols, rows: input.rows, cwd: input.cwd, env };
|
|
6480
6512
|
}
|
|
6481
6513
|
function createCodexLiveSession(input) {
|
|
6482
6514
|
let pty = null;
|
|
@@ -9157,7 +9189,9 @@ function computeLiveness(snap) {
|
|
|
9157
9189
|
let why = null;
|
|
9158
9190
|
let stuck = false;
|
|
9159
9191
|
let liveOverride = null;
|
|
9160
|
-
if (terminal === "
|
|
9192
|
+
if (terminal === "done") {
|
|
9193
|
+
return { stuck: false, why: null, liveText: "done" };
|
|
9194
|
+
} else if (terminal === "halted" || terminal === "canceled") {
|
|
9161
9195
|
stuck = true;
|
|
9162
9196
|
why = snap.workflow?.haltReason ? `${terminal}: ${snap.workflow.haltReason}` : `workflow ${terminal}`;
|
|
9163
9197
|
} else if (chainStatus === "escalated" || chainStatus === "abandoned") {
|
|
@@ -9938,6 +9972,8 @@ function createDashboardRuntime(input) {
|
|
|
9938
9972
|
let lastSig = null;
|
|
9939
9973
|
let renderCount = 0;
|
|
9940
9974
|
let recycles = 0;
|
|
9975
|
+
let lastRenderedMode = "wall";
|
|
9976
|
+
let clearCount = 0;
|
|
9941
9977
|
const recycleEvery = Math.max(1, input.__recycleEveryRenders ?? 750);
|
|
9942
9978
|
function toPhaseRuns(raw) {
|
|
9943
9979
|
return raw.map((p) => ({
|
|
@@ -10143,6 +10179,11 @@ function createDashboardRuntime(input) {
|
|
|
10143
10179
|
lastSig = pendingSig;
|
|
10144
10180
|
renderCount += 1;
|
|
10145
10181
|
const full = createElement2(DashboardApp, { node: el, onKey: handleKey });
|
|
10182
|
+
if (mode !== lastRenderedMode) {
|
|
10183
|
+
ink.clear();
|
|
10184
|
+
clearCount += 1;
|
|
10185
|
+
lastRenderedMode = mode;
|
|
10186
|
+
}
|
|
10146
10187
|
if (renderCount % recycleEvery === 0) {
|
|
10147
10188
|
ink.unmount();
|
|
10148
10189
|
ink = render2(full, inkOptions);
|
|
@@ -10252,7 +10293,8 @@ function createDashboardRuntime(input) {
|
|
|
10252
10293
|
lastError: lastPollError
|
|
10253
10294
|
}),
|
|
10254
10295
|
__renderCount: () => renderCount,
|
|
10255
|
-
__recycles: () => recycles
|
|
10296
|
+
__recycles: () => recycles,
|
|
10297
|
+
__clears: () => clearCount
|
|
10256
10298
|
};
|
|
10257
10299
|
}
|
|
10258
10300
|
|
|
@@ -10802,21 +10844,62 @@ async function runWorkflowStart(deps) {
|
|
|
10802
10844
|
}
|
|
10803
10845
|
}
|
|
10804
10846
|
const def = getWorkflowDefinition(deps.workflowType);
|
|
10805
|
-
|
|
10806
|
-
|
|
10807
|
-
|
|
10808
|
-
|
|
10809
|
-
|
|
10810
|
-
|
|
10847
|
+
let resolved;
|
|
10848
|
+
try {
|
|
10849
|
+
resolved = resolveRoleBindings({
|
|
10850
|
+
explicitImplementer: deps.implementer,
|
|
10851
|
+
explicitReviewer: deps.reviewer,
|
|
10852
|
+
callerAgent: deps.callerAgent ?? null,
|
|
10853
|
+
def
|
|
10854
|
+
});
|
|
10855
|
+
} catch (e) {
|
|
10856
|
+
if (e instanceof Error && /no default role bindings/.test(e.message)) {
|
|
10857
|
+
throw new Error(
|
|
10858
|
+
`Workflow type "${deps.workflowType}" has no default role bindings. Pass --implementer and --reviewer explicitly.`
|
|
10859
|
+
);
|
|
10860
|
+
}
|
|
10861
|
+
throw e;
|
|
10811
10862
|
}
|
|
10812
|
-
|
|
10863
|
+
const { workflowId } = deps.broker.control.createWorkflow({
|
|
10813
10864
|
collabId: deps.collabId,
|
|
10814
10865
|
workflowType: deps.workflowType,
|
|
10815
10866
|
specPath: deps.specPath,
|
|
10816
|
-
roleBindings: { implementer, reviewer },
|
|
10867
|
+
roleBindings: { implementer: resolved.implementer, reviewer: resolved.reviewer },
|
|
10817
10868
|
...deps.name ? { name: deps.name } : {},
|
|
10818
10869
|
now: deps.now
|
|
10819
10870
|
});
|
|
10871
|
+
return { workflowId, ...resolved.warning ? { roleWarning: resolved.warning } : {} };
|
|
10872
|
+
}
|
|
10873
|
+
var otherAgent = (a) => a === "claude" ? "codex" : "claude";
|
|
10874
|
+
function resolveRoleBindings(input) {
|
|
10875
|
+
const { explicitImplementer, explicitReviewer, callerAgent, def } = input;
|
|
10876
|
+
if (explicitImplementer || explicitReviewer) {
|
|
10877
|
+
const implementer2 = explicitImplementer ?? (explicitReviewer ? otherAgent(explicitReviewer) : void 0);
|
|
10878
|
+
const reviewer2 = explicitReviewer ?? (explicitImplementer ? otherAgent(explicitImplementer) : void 0);
|
|
10879
|
+
if (implementer2 && reviewer2) {
|
|
10880
|
+
if (implementer2 === reviewer2) {
|
|
10881
|
+
throw new Error("implementer and reviewer cannot be the same agent");
|
|
10882
|
+
}
|
|
10883
|
+
return { implementer: implementer2, reviewer: reviewer2, source: "explicit" };
|
|
10884
|
+
}
|
|
10885
|
+
}
|
|
10886
|
+
if (callerAgent) {
|
|
10887
|
+
return { implementer: callerAgent, reviewer: otherAgent(callerAgent), source: "caller" };
|
|
10888
|
+
}
|
|
10889
|
+
const implementer = def?.defaultImplementer;
|
|
10890
|
+
const reviewer = def?.defaultReviewer;
|
|
10891
|
+
if (!implementer || !reviewer) {
|
|
10892
|
+
throw new Error("no default role bindings");
|
|
10893
|
+
}
|
|
10894
|
+
return {
|
|
10895
|
+
implementer,
|
|
10896
|
+
reviewer,
|
|
10897
|
+
source: "default",
|
|
10898
|
+
warning: `No triggering agent detected; defaulted to implementer=${implementer} / reviewer=${reviewer}. Pass --implementer / --reviewer to choose explicitly.`
|
|
10899
|
+
};
|
|
10900
|
+
}
|
|
10901
|
+
function parseCallerAgent(raw) {
|
|
10902
|
+
return raw === "claude" || raw === "codex" ? raw : null;
|
|
10820
10903
|
}
|
|
10821
10904
|
|
|
10822
10905
|
// src/commands/workflow/list.ts
|
|
@@ -11184,8 +11267,10 @@ Not attached (stdin/stdout is not a TTY). To view the tmux session, run:
|
|
|
11184
11267
|
...opts.implementer ? { implementer: opts.implementer } : {},
|
|
11185
11268
|
...opts.reviewer ? { reviewer: opts.reviewer } : {},
|
|
11186
11269
|
...opts.name ? { name: opts.name } : {},
|
|
11270
|
+
callerAgent: parseCallerAgent(process.env.AI_WHISPER_AGENT),
|
|
11187
11271
|
now: (/* @__PURE__ */ new Date()).toISOString()
|
|
11188
11272
|
});
|
|
11273
|
+
if (result.roleWarning) console.error(result.roleWarning);
|
|
11189
11274
|
console.log(`Workflow started: ${result.workflowId}`);
|
|
11190
11275
|
} finally {
|
|
11191
11276
|
await broker.stop();
|
|
@@ -97,7 +97,7 @@ Run:
|
|
|
97
97
|
whisper workflow start --type=ralph-loop --spec=<resolved-absolute-path>
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
(No `--implementer` / `--reviewer` — the
|
|
100
|
+
(No `--implementer` / `--reviewer` needed — the agent triggering this skill becomes the implementer and the other agent the reviewer. Pass `--implementer <agent> --reviewer <agent>` only to override. `--spec` names the goal file.)
|
|
101
101
|
|
|
102
102
|
Parse the workflowId from stdout (format: `Workflow started: <workflowId>`).
|
|
103
103
|
|
|
@@ -94,7 +94,7 @@ Run:
|
|
|
94
94
|
whisper workflow start --type=spec-driven-development --spec=<resolved-absolute-path>
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
(No `--implementer` / `--reviewer` — the
|
|
97
|
+
(No `--implementer` / `--reviewer` needed — the agent triggering this skill becomes the implementer and the other agent the reviewer. Pass `--implementer <agent> --reviewer <agent>` only to override.)
|
|
98
98
|
|
|
99
99
|
Parse the workflowId from stdout (format: `Workflow started: <workflowId>`).
|
|
100
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ai-whisper",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
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": {
|
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"@types/better-sqlite3": "^7.6.12",
|
|
30
30
|
"@types/react": "^19.2.14",
|
|
31
31
|
"ink-testing-library": "^4.0.0",
|
|
32
|
-
"@ai-whisper/adapter-codex": "0.0.0",
|
|
33
|
-
"@ai-whisper/broker": "0.0.0",
|
|
34
|
-
"@ai-whisper/shared": "0.0.0",
|
|
35
32
|
"@ai-whisper/adapter-claude": "0.0.0",
|
|
36
|
-
"@ai-whisper/
|
|
33
|
+
"@ai-whisper/shared": "0.0.0",
|
|
34
|
+
"@ai-whisper/adapter-codex": "0.0.0",
|
|
35
|
+
"@ai-whisper/companion-core": "0.0.0",
|
|
36
|
+
"@ai-whisper/broker": "0.0.0"
|
|
37
37
|
},
|
|
38
38
|
"files": [
|
|
39
39
|
"dist",
|
|
@@ -97,7 +97,7 @@ Run:
|
|
|
97
97
|
whisper workflow start --type=ralph-loop --spec=<resolved-absolute-path>
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
-
(No `--implementer` / `--reviewer` — the
|
|
100
|
+
(No `--implementer` / `--reviewer` needed — the agent triggering this skill becomes the implementer and the other agent the reviewer. Pass `--implementer <agent> --reviewer <agent>` only to override. `--spec` names the goal file.)
|
|
101
101
|
|
|
102
102
|
Parse the workflowId from stdout (format: `Workflow started: <workflowId>`).
|
|
103
103
|
|
|
@@ -94,7 +94,7 @@ Run:
|
|
|
94
94
|
whisper workflow start --type=spec-driven-development --spec=<resolved-absolute-path>
|
|
95
95
|
```
|
|
96
96
|
|
|
97
|
-
(No `--implementer` / `--reviewer` — the
|
|
97
|
+
(No `--implementer` / `--reviewer` needed — the agent triggering this skill becomes the implementer and the other agent the reviewer. Pass `--implementer <agent> --reviewer <agent>` only to override.)
|
|
98
98
|
|
|
99
99
|
Parse the workflowId from stdout (format: `Workflow started: <workflowId>`).
|
|
100
100
|
|