ai-whisper 0.5.6 → 0.5.8
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 +1 -1
- package/dist/bin/broker-daemon.js +235 -16
- package/dist/bin/companion-agent.js +326 -64
- package/dist/bin/relay-monitor.js +54 -18
- package/dist/bin/whisper.js +466 -116
- package/package.json +6 -6
- package/skills/ai-whisper-plan-execution/SKILL.md +65 -0
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@ pnpm install
|
|
|
80
80
|
pnpm build
|
|
81
81
|
```
|
|
82
82
|
|
|
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:
|
|
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:
|
|
84
84
|
|
|
85
85
|
```bash
|
|
86
86
|
whisper skill install
|
|
@@ -2215,8 +2215,10 @@ Non-blocking risks:
|
|
|
2215
2215
|
- <risk that does NOT block this gate, or "None.">
|
|
2216
2216
|
--- end protocol ---`;
|
|
2217
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.";
|
|
2218
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;
|
|
2219
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;
|
|
2220
2222
|
var SPEC_DRIVEN_DEVELOPMENT = {
|
|
2221
2223
|
type: "spec-driven-development",
|
|
2222
2224
|
displayName: "Spec-Driven Development",
|
|
@@ -2261,9 +2263,9 @@ var SPEC_DRIVEN_DEVELOPMENT = {
|
|
|
2261
2263
|
reviewerRole: null,
|
|
2262
2264
|
maxRounds: 1,
|
|
2263
2265
|
initialHandoffStep: "execute",
|
|
2264
|
-
kickoffTemplate:
|
|
2266
|
+
kickoffTemplate: SDD_PLAN_EXECUTION,
|
|
2265
2267
|
stepTemplates: {
|
|
2266
|
-
execute:
|
|
2268
|
+
execute: SDD_PLAN_EXECUTION
|
|
2267
2269
|
},
|
|
2268
2270
|
evaluatorPromptKey: "execution-gate",
|
|
2269
2271
|
artifactOut: { kind: "commit-range" }
|
|
@@ -2476,6 +2478,25 @@ function derivePlanPath(specPath, dateIso) {
|
|
|
2476
2478
|
return `docs/superpowers/plans/${date}-${slug}.md`;
|
|
2477
2479
|
}
|
|
2478
2480
|
|
|
2481
|
+
// ../broker/dist/storage/repositories/workflow-event-outbox-repository.js
|
|
2482
|
+
function appendWorkflowEvent(db, input) {
|
|
2483
|
+
db.prepare(`INSERT INTO workflow_event_outbox (collab_id, event_name, payload_json, created_at)
|
|
2484
|
+
VALUES (?, ?, ?, ?)`).run(input.collabId, input.eventName, JSON.stringify(input.payload), input.now);
|
|
2485
|
+
}
|
|
2486
|
+
function getMaxWorkflowEventId(db, collabId2) {
|
|
2487
|
+
const row = db.prepare("SELECT MAX(id) AS maxId FROM workflow_event_outbox WHERE collab_id = ?").get(collabId2);
|
|
2488
|
+
return row.maxId ?? 0;
|
|
2489
|
+
}
|
|
2490
|
+
function listWorkflowEventsAfter(db, input) {
|
|
2491
|
+
const rows = db.prepare(`SELECT id, event_name, payload_json FROM workflow_event_outbox
|
|
2492
|
+
WHERE collab_id = ? AND id > ? ORDER BY id ASC`).all(input.collabId, input.afterId);
|
|
2493
|
+
return rows.map((r) => ({
|
|
2494
|
+
id: r.id,
|
|
2495
|
+
eventName: r.event_name,
|
|
2496
|
+
payload: JSON.parse(r.payload_json)
|
|
2497
|
+
}));
|
|
2498
|
+
}
|
|
2499
|
+
|
|
2479
2500
|
// ../broker/dist/control/workflow-control.js
|
|
2480
2501
|
var SHA_REGEX = /^[0-9a-f]{7,40}$/;
|
|
2481
2502
|
function liveReviewCommitRange(ctx) {
|
|
@@ -2512,6 +2533,10 @@ function composeResumeNotice(input) {
|
|
|
2512
2533
|
}
|
|
2513
2534
|
function createWorkflowControl(deps) {
|
|
2514
2535
|
const { db, events } = deps;
|
|
2536
|
+
function emitAndRecord(collabId2, name, payload, now) {
|
|
2537
|
+
events.emit(name, payload);
|
|
2538
|
+
appendWorkflowEvent(db, { collabId: collabId2, eventName: name, payload, now });
|
|
2539
|
+
}
|
|
2515
2540
|
function createWorkflow(input) {
|
|
2516
2541
|
const collab2 = getCollab(db, input.collabId);
|
|
2517
2542
|
if (!collab2) {
|
|
@@ -2550,7 +2575,7 @@ function createWorkflowControl(deps) {
|
|
|
2550
2575
|
});
|
|
2551
2576
|
});
|
|
2552
2577
|
tx.immediate();
|
|
2553
|
-
|
|
2578
|
+
emitAndRecord(input.collabId, "workflow.created", { workflowId }, input.now);
|
|
2554
2579
|
return { workflowId };
|
|
2555
2580
|
}
|
|
2556
2581
|
function getWorkflow(workflowId) {
|
|
@@ -3293,7 +3318,7 @@ ${findingsText}`;
|
|
|
3293
3318
|
});
|
|
3294
3319
|
});
|
|
3295
3320
|
tx.immediate();
|
|
3296
|
-
|
|
3321
|
+
emitAndRecord(workflow.collabId, "workflow.paused", { workflowId: input.workflowId }, input.now);
|
|
3297
3322
|
maybeCaptureQuiesceSnapshot({ workflowId: input.workflowId, now: input.now });
|
|
3298
3323
|
}
|
|
3299
3324
|
function resumeHaltedWorkflow(workflow, now) {
|
|
@@ -3310,10 +3335,7 @@ ${findingsText}`;
|
|
|
3310
3335
|
});
|
|
3311
3336
|
});
|
|
3312
3337
|
tx.immediate();
|
|
3313
|
-
|
|
3314
|
-
workflowId: workflow.workflowId,
|
|
3315
|
-
phaseIndex: workflow.currentPhaseIndex
|
|
3316
|
-
});
|
|
3338
|
+
emitAndRecord(workflow.collabId, "workflow.resumed", { workflowId: workflow.workflowId, phaseIndex: workflow.currentPhaseIndex }, now);
|
|
3317
3339
|
}
|
|
3318
3340
|
function resumeWorkflow(input) {
|
|
3319
3341
|
const workflow = getWorkflowById(db, input.workflowId);
|
|
@@ -3364,10 +3386,7 @@ ${findingsText}`;
|
|
|
3364
3386
|
});
|
|
3365
3387
|
});
|
|
3366
3388
|
tx.immediate();
|
|
3367
|
-
|
|
3368
|
-
workflowId: input.workflowId,
|
|
3369
|
-
phaseIndex: workflow.currentPhaseIndex
|
|
3370
|
-
});
|
|
3389
|
+
emitAndRecord(workflow.collabId, "workflow.resumed", { workflowId: input.workflowId, phaseIndex: workflow.currentPhaseIndex }, input.now);
|
|
3371
3390
|
}
|
|
3372
3391
|
function cancelWorkflow(input) {
|
|
3373
3392
|
const workflow = getWorkflowById(db, input.workflowId);
|
|
@@ -3426,10 +3445,7 @@ ${findingsText}`;
|
|
|
3426
3445
|
});
|
|
3427
3446
|
});
|
|
3428
3447
|
tx.immediate();
|
|
3429
|
-
|
|
3430
|
-
workflowId: input.workflowId,
|
|
3431
|
-
reason: "canceled by operator"
|
|
3432
|
-
});
|
|
3448
|
+
emitAndRecord(workflow.collabId, "workflow.canceled", { workflowId: input.workflowId, reason: "canceled by operator" }, input.now);
|
|
3433
3449
|
}
|
|
3434
3450
|
function getHandoffWithWorkflowMeta(handoffId) {
|
|
3435
3451
|
const meta = getHandoffWithWorkflowMetaById(db, handoffId);
|
|
@@ -4713,6 +4729,19 @@ function ensureBrokerStateRow(db) {
|
|
|
4713
4729
|
schema_version = excluded.schema_version,
|
|
4714
4730
|
migrated = 1`).run(CURRENT_SCHEMA_VERSION);
|
|
4715
4731
|
}
|
|
4732
|
+
function ensureWorkflowEventOutbox(db) {
|
|
4733
|
+
db.exec(`
|
|
4734
|
+
CREATE TABLE IF NOT EXISTS workflow_event_outbox (
|
|
4735
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
4736
|
+
collab_id TEXT NOT NULL,
|
|
4737
|
+
event_name TEXT NOT NULL,
|
|
4738
|
+
payload_json TEXT NOT NULL,
|
|
4739
|
+
created_at TEXT NOT NULL
|
|
4740
|
+
);
|
|
4741
|
+
CREATE INDEX IF NOT EXISTS idx_workflow_event_outbox_collab_id
|
|
4742
|
+
ON workflow_event_outbox (collab_id, id);
|
|
4743
|
+
`);
|
|
4744
|
+
}
|
|
4716
4745
|
function applyMigrations(db) {
|
|
4717
4746
|
const current = db.pragma("user_version", { simple: true });
|
|
4718
4747
|
if (current < CURRENT_SCHEMA_VERSION) {
|
|
@@ -4729,6 +4758,7 @@ function applyMigrations(db) {
|
|
|
4729
4758
|
ensureBrokerStateRow(db);
|
|
4730
4759
|
enforceOneActiveCollabPerWorkspace(db);
|
|
4731
4760
|
sweepStaleCaptureLease(db);
|
|
4761
|
+
ensureWorkflowEventOutbox(db);
|
|
4732
4762
|
}
|
|
4733
4763
|
function runMigrationBody(db) {
|
|
4734
4764
|
db.exec(initMigrationSql);
|
|
@@ -5501,6 +5531,140 @@ function createBrokerRuntime(input) {
|
|
|
5501
5531
|
};
|
|
5502
5532
|
}
|
|
5503
5533
|
|
|
5534
|
+
// ../broker/dist/runtime/event-protocol.js
|
|
5535
|
+
var EVENT_PROTOCOL_VERSION = "1";
|
|
5536
|
+
|
|
5537
|
+
// ../broker/dist/runtime/event-socket-server.js
|
|
5538
|
+
import { createServer } from "node:net";
|
|
5539
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "node:fs";
|
|
5540
|
+
import { dirname as dirname2 } from "node:path";
|
|
5541
|
+
var EVENT_NAME_TABLE = {
|
|
5542
|
+
"chain.resolved": true,
|
|
5543
|
+
"chain.escalated": true,
|
|
5544
|
+
"workflow.created": true,
|
|
5545
|
+
"workflow.phase-started": true,
|
|
5546
|
+
"workflow.round-started": true,
|
|
5547
|
+
"workflow.phase-done": true,
|
|
5548
|
+
"workflow.halted": true,
|
|
5549
|
+
"workflow.canceled": true,
|
|
5550
|
+
"workflow.done": true,
|
|
5551
|
+
"workflow.resumed": true,
|
|
5552
|
+
"workflow.paused": true
|
|
5553
|
+
};
|
|
5554
|
+
var ALL_BROKER_EVENT_NAMES = Object.keys(EVENT_NAME_TABLE);
|
|
5555
|
+
async function createEventSocketServer(input) {
|
|
5556
|
+
const now = input.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
|
|
5557
|
+
const clients = /* @__PURE__ */ new Set();
|
|
5558
|
+
mkdirSync3(dirname2(input.socketPath), { recursive: true });
|
|
5559
|
+
if (existsSync4(input.socketPath)) {
|
|
5560
|
+
try {
|
|
5561
|
+
unlinkSync2(input.socketPath);
|
|
5562
|
+
} catch {
|
|
5563
|
+
}
|
|
5564
|
+
}
|
|
5565
|
+
function dropClient(conn) {
|
|
5566
|
+
clients.delete(conn);
|
|
5567
|
+
try {
|
|
5568
|
+
conn.destroy();
|
|
5569
|
+
} catch {
|
|
5570
|
+
}
|
|
5571
|
+
}
|
|
5572
|
+
function writeLine(conn, line) {
|
|
5573
|
+
try {
|
|
5574
|
+
conn.write(line);
|
|
5575
|
+
} catch {
|
|
5576
|
+
dropClient(conn);
|
|
5577
|
+
}
|
|
5578
|
+
}
|
|
5579
|
+
const server = createServer((conn) => {
|
|
5580
|
+
clients.add(conn);
|
|
5581
|
+
conn.on("error", () => dropClient(conn));
|
|
5582
|
+
conn.on("close", () => clients.delete(conn));
|
|
5583
|
+
const hello = {
|
|
5584
|
+
type: "hello",
|
|
5585
|
+
engineVersion: input.engineVersion,
|
|
5586
|
+
protocolVersion: EVENT_PROTOCOL_VERSION
|
|
5587
|
+
};
|
|
5588
|
+
writeLine(conn, `${JSON.stringify(hello)}
|
|
5589
|
+
`);
|
|
5590
|
+
});
|
|
5591
|
+
const buses = Array.isArray(input.events) ? input.events : [input.events];
|
|
5592
|
+
const unsubscribes = buses.flatMap((bus) => ALL_BROKER_EVENT_NAMES.map((name) => bus.on(name, (payload) => {
|
|
5593
|
+
const frame = { type: "event", name, payload, ts: now() };
|
|
5594
|
+
const line = `${JSON.stringify(frame)}
|
|
5595
|
+
`;
|
|
5596
|
+
for (const conn of [...clients])
|
|
5597
|
+
writeLine(conn, line);
|
|
5598
|
+
})));
|
|
5599
|
+
await new Promise((resolve2, reject) => {
|
|
5600
|
+
server.once("error", reject);
|
|
5601
|
+
server.listen(input.socketPath, () => {
|
|
5602
|
+
server.removeListener("error", reject);
|
|
5603
|
+
resolve2();
|
|
5604
|
+
});
|
|
5605
|
+
});
|
|
5606
|
+
return {
|
|
5607
|
+
socketPath: input.socketPath,
|
|
5608
|
+
close: () => new Promise((resolve2) => {
|
|
5609
|
+
for (const unsub of unsubscribes)
|
|
5610
|
+
unsub();
|
|
5611
|
+
for (const conn of [...clients])
|
|
5612
|
+
dropClient(conn);
|
|
5613
|
+
server.close(() => {
|
|
5614
|
+
try {
|
|
5615
|
+
if (existsSync4(input.socketPath))
|
|
5616
|
+
unlinkSync2(input.socketPath);
|
|
5617
|
+
} catch {
|
|
5618
|
+
}
|
|
5619
|
+
resolve2();
|
|
5620
|
+
});
|
|
5621
|
+
})
|
|
5622
|
+
};
|
|
5623
|
+
}
|
|
5624
|
+
|
|
5625
|
+
// ../broker/dist/runtime/workflow-event-bridge.js
|
|
5626
|
+
function createWorkflowEventBridge(input) {
|
|
5627
|
+
let lastId = 0;
|
|
5628
|
+
let seeded = false;
|
|
5629
|
+
let timer = null;
|
|
5630
|
+
function tick() {
|
|
5631
|
+
if (!seeded) {
|
|
5632
|
+
lastId = getMaxWorkflowEventId(input.db, input.collabId);
|
|
5633
|
+
seeded = true;
|
|
5634
|
+
return;
|
|
5635
|
+
}
|
|
5636
|
+
const rows = listWorkflowEventsAfter(input.db, {
|
|
5637
|
+
collabId: input.collabId,
|
|
5638
|
+
afterId: lastId
|
|
5639
|
+
});
|
|
5640
|
+
for (const row of rows) {
|
|
5641
|
+
input.events.emit(row.eventName, row.payload);
|
|
5642
|
+
lastId = row.id;
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
return {
|
|
5646
|
+
tick,
|
|
5647
|
+
start() {
|
|
5648
|
+
if (timer)
|
|
5649
|
+
return;
|
|
5650
|
+
tick();
|
|
5651
|
+
timer = setInterval(() => {
|
|
5652
|
+
try {
|
|
5653
|
+
tick();
|
|
5654
|
+
} catch {
|
|
5655
|
+
}
|
|
5656
|
+
}, input.intervalMs);
|
|
5657
|
+
timer.unref();
|
|
5658
|
+
},
|
|
5659
|
+
stop() {
|
|
5660
|
+
if (timer) {
|
|
5661
|
+
clearInterval(timer);
|
|
5662
|
+
timer = null;
|
|
5663
|
+
}
|
|
5664
|
+
}
|
|
5665
|
+
};
|
|
5666
|
+
}
|
|
5667
|
+
|
|
5504
5668
|
// src/runtime/relay-orchestrator-evaluator.ts
|
|
5505
5669
|
import * as crypto3 from "node:crypto";
|
|
5506
5670
|
import Anthropic from "@anthropic-ai/sdk";
|
|
@@ -6281,6 +6445,9 @@ import path from "node:path";
|
|
|
6281
6445
|
function getStateRoot() {
|
|
6282
6446
|
return process.env.AI_WHISPER_STATE_ROOT ?? path.join(os.homedir(), ".ai-whisper");
|
|
6283
6447
|
}
|
|
6448
|
+
function getStateSocketsDir() {
|
|
6449
|
+
return path.join(getStateRoot(), "sockets");
|
|
6450
|
+
}
|
|
6284
6451
|
|
|
6285
6452
|
// src/runtime/evaluator-config.ts
|
|
6286
6453
|
function parseDotEnv(text) {
|
|
@@ -6387,6 +6554,31 @@ function recordEvaluatorStatus(db, input) {
|
|
|
6387
6554
|
|
|
6388
6555
|
// src/bin/broker-daemon.ts
|
|
6389
6556
|
import { execFile as execFile2 } from "node:child_process";
|
|
6557
|
+
import { join as join6 } from "node:path";
|
|
6558
|
+
|
|
6559
|
+
// src/runtime/cli-package-info.ts
|
|
6560
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
6561
|
+
import { dirname as dirname3 } from "node:path";
|
|
6562
|
+
import { fileURLToPath } from "node:url";
|
|
6563
|
+
var PKG_CANDIDATES = ["../../package.json", "../package.json"];
|
|
6564
|
+
function readWhisperPackage() {
|
|
6565
|
+
for (const rel of PKG_CANDIDATES) {
|
|
6566
|
+
try {
|
|
6567
|
+
const url = new URL(rel, import.meta.url);
|
|
6568
|
+
const pkg = JSON.parse(readFileSync3(url, "utf8"));
|
|
6569
|
+
if (pkg.name === "ai-whisper" && typeof pkg.version === "string") {
|
|
6570
|
+
return { url, version: pkg.version };
|
|
6571
|
+
}
|
|
6572
|
+
} catch {
|
|
6573
|
+
}
|
|
6574
|
+
}
|
|
6575
|
+
return null;
|
|
6576
|
+
}
|
|
6577
|
+
function resolveCliVersion() {
|
|
6578
|
+
return readWhisperPackage()?.version ?? "0.0.0-dev";
|
|
6579
|
+
}
|
|
6580
|
+
|
|
6581
|
+
// src/bin/broker-daemon.ts
|
|
6390
6582
|
var sqlitePath = process.env.AI_WHISPER_BROKER_SQLITE;
|
|
6391
6583
|
var host = process.env.AI_WHISPER_BROKER_HOST ?? "127.0.0.1";
|
|
6392
6584
|
var port = Number(process.env.AI_WHISPER_BROKER_PORT ?? "4311");
|
|
@@ -6399,6 +6591,31 @@ if (collabId) {
|
|
|
6399
6591
|
});
|
|
6400
6592
|
}
|
|
6401
6593
|
await broker.start();
|
|
6594
|
+
var eventSocket = null;
|
|
6595
|
+
var workflowEventBridge = null;
|
|
6596
|
+
if (collabId) {
|
|
6597
|
+
try {
|
|
6598
|
+
const externalEvents = createBrokerEventBus();
|
|
6599
|
+
workflowEventBridge = createWorkflowEventBridge({
|
|
6600
|
+
db: broker.db,
|
|
6601
|
+
events: externalEvents,
|
|
6602
|
+
collabId,
|
|
6603
|
+
intervalMs: Number(process.env.AI_WHISPER_WORKFLOW_EVENT_BRIDGE_MS ?? 1e3)
|
|
6604
|
+
});
|
|
6605
|
+
workflowEventBridge.start();
|
|
6606
|
+
eventSocket = await createEventSocketServer({
|
|
6607
|
+
socketPath: join6(getStateSocketsDir(), `events-${collabId}.sock`),
|
|
6608
|
+
events: [broker.events, externalEvents],
|
|
6609
|
+
engineVersion: resolveCliVersion()
|
|
6610
|
+
});
|
|
6611
|
+
} catch (err) {
|
|
6612
|
+
workflowEventBridge?.stop();
|
|
6613
|
+
workflowEventBridge = null;
|
|
6614
|
+
console.error(
|
|
6615
|
+
`event-socket fanout unavailable: ${err instanceof Error ? err.message : String(err)}`
|
|
6616
|
+
);
|
|
6617
|
+
}
|
|
6618
|
+
}
|
|
6402
6619
|
var resolved;
|
|
6403
6620
|
var loaderError = null;
|
|
6404
6621
|
try {
|
|
@@ -6460,6 +6677,8 @@ var orchestrator = collab?.orchestratorEnabled && evaluator ? createRelayOrchest
|
|
|
6460
6677
|
orchestrator?.start();
|
|
6461
6678
|
async function shutdown() {
|
|
6462
6679
|
orchestrator?.stop();
|
|
6680
|
+
workflowEventBridge?.stop();
|
|
6681
|
+
await eventSocket?.close();
|
|
6463
6682
|
await broker.stop();
|
|
6464
6683
|
process.exit(0);
|
|
6465
6684
|
}
|