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 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: "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.",
2266
+ kickoffTemplate: SDD_PLAN_EXECUTION,
2265
2267
  stepTemplates: {
2266
- execute: "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."
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
- events.emit("workflow.created", { workflowId });
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
- events.emit("workflow.paused", { workflowId: input.workflowId });
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
- events.emit("workflow.resumed", {
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
- events.emit("workflow.resumed", {
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
- events.emit("workflow.canceled", {
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
  }