@whisperr/wizard 0.1.11 → 0.1.13
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/dist/index.js +133 -21
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -25,7 +25,13 @@ function resolveConfig(flags = {}) {
|
|
|
25
25
|
llmBaseUrl,
|
|
26
26
|
model: flags.model ?? process.env.WHISPERR_WIZARD_MODEL ?? DEFAULT_MODEL,
|
|
27
27
|
effort: resolveEffort(),
|
|
28
|
-
|
|
28
|
+
// Cost is the real limiter (budgetUsd below). maxTurns is only a high safety
|
|
29
|
+
// backstop against a stuck loop — it should NOT bind on a normal run. (It
|
|
30
|
+
// used to be 55, which guillotined high-effort multi-phase runs mid-work.)
|
|
31
|
+
maxTurns: Number(process.env.WHISPERR_WIZARD_MAX_TURNS) || 200,
|
|
32
|
+
// Hard ceiling on TOTAL spend across all phases. If hit, the run stops
|
|
33
|
+
// cleanly and keeps whatever already landed. Real runs finish well under it.
|
|
34
|
+
budgetUsd: Number(process.env.WHISPERR_WIZARD_BUDGET_USD) || 10,
|
|
29
35
|
directAnthropicKey,
|
|
30
36
|
offline
|
|
31
37
|
};
|
|
@@ -67,7 +73,7 @@ var systemPrompt = `
|
|
|
67
73
|
This SDK is published on pub.dev. Integrate it as follows.
|
|
68
74
|
|
|
69
75
|
1) Dependency. Add to pubspec.yaml under dependencies:
|
|
70
|
-
whisperr: ^0.2.
|
|
76
|
+
whisperr: ^0.2.2
|
|
71
77
|
Then the SDK expects \`flutter pub get\` to be run (run it via bash).
|
|
72
78
|
|
|
73
79
|
2) Initialize once, early, before runApp(), in lib/main.dart:
|
|
@@ -1208,14 +1214,21 @@ async function runIntegrationAgent(opts) {
|
|
|
1208
1214
|
repoPath,
|
|
1209
1215
|
model: config.model,
|
|
1210
1216
|
effort: config.effort,
|
|
1211
|
-
maxTurns:
|
|
1217
|
+
maxTurns: 50,
|
|
1218
|
+
budgetUsd: config.budgetUsd - costUsd,
|
|
1212
1219
|
progress
|
|
1213
1220
|
});
|
|
1214
1221
|
costUsd += core.costUsd;
|
|
1215
1222
|
if (core.summary) summaries.push(`Core setup:
|
|
1216
1223
|
${core.summary}`);
|
|
1224
|
+
const BUDGET_FLOOR = 0.25;
|
|
1217
1225
|
let eventsComplete = true;
|
|
1218
|
-
if (manifest.events.length > 0) {
|
|
1226
|
+
if (manifest.events.length > 0 && config.budgetUsd - costUsd <= BUDGET_FLOOR) {
|
|
1227
|
+
eventsComplete = false;
|
|
1228
|
+
summaries.push(
|
|
1229
|
+
"Events: skipped \u2014 the spend limit was reached during core setup. Re-run with a higher WHISPERR_WIZARD_BUDGET_USD to instrument events."
|
|
1230
|
+
);
|
|
1231
|
+
} else if (manifest.events.length > 0) {
|
|
1219
1232
|
progress?.onPhase?.("Instrumenting your events");
|
|
1220
1233
|
const eventsPrompt = [
|
|
1221
1234
|
`The Whisperr ${playbook.target.displayName} SDK is already installed and`,
|
|
@@ -1241,6 +1254,7 @@ ${core.summary}`);
|
|
|
1241
1254
|
model: config.model,
|
|
1242
1255
|
effort: config.effort,
|
|
1243
1256
|
maxTurns: config.maxTurns,
|
|
1257
|
+
budgetUsd: config.budgetUsd - costUsd,
|
|
1244
1258
|
progress
|
|
1245
1259
|
});
|
|
1246
1260
|
costUsd += events.costUsd;
|
|
@@ -1248,7 +1262,7 @@ ${core.summary}`);
|
|
|
1248
1262
|
if (events.summary) summaries.push(`Events:
|
|
1249
1263
|
${events.summary}`);
|
|
1250
1264
|
}
|
|
1251
|
-
if (core.ok) {
|
|
1265
|
+
if (core.ok && config.budgetUsd - costUsd > BUDGET_FLOOR) {
|
|
1252
1266
|
progress?.onPhase?.("Reviewing & correcting placements");
|
|
1253
1267
|
const reviewPrompt = [
|
|
1254
1268
|
`You wired the Whisperr ${playbook.target.displayName} SDK into this repo.`,
|
|
@@ -1279,7 +1293,8 @@ ${events.summary}`);
|
|
|
1279
1293
|
repoPath,
|
|
1280
1294
|
model: config.model,
|
|
1281
1295
|
effort: config.effort,
|
|
1282
|
-
maxTurns:
|
|
1296
|
+
maxTurns: 40,
|
|
1297
|
+
budgetUsd: config.budgetUsd - costUsd,
|
|
1283
1298
|
progress
|
|
1284
1299
|
});
|
|
1285
1300
|
costUsd += review.costUsd;
|
|
@@ -1294,8 +1309,12 @@ ${review.summary}`);
|
|
|
1294
1309
|
eventsComplete
|
|
1295
1310
|
};
|
|
1296
1311
|
}
|
|
1312
|
+
function isLimitStop(err) {
|
|
1313
|
+
const msg = err?.message ?? "";
|
|
1314
|
+
return /max(imum)?[\s_-]*(turn|budget)|budget.*exceeded|number of turns/i.test(msg);
|
|
1315
|
+
}
|
|
1297
1316
|
async function runPass(opts) {
|
|
1298
|
-
const { prompt, systemPrompt: systemPrompt9, repoPath, model, effort, maxTurns, progress } = opts;
|
|
1317
|
+
const { prompt, systemPrompt: systemPrompt9, repoPath, model, effort, maxTurns, budgetUsd, progress } = opts;
|
|
1299
1318
|
let summary = "";
|
|
1300
1319
|
let costUsd = 0;
|
|
1301
1320
|
let ok = false;
|
|
@@ -1314,23 +1333,36 @@ async function runPass(opts) {
|
|
|
1314
1333
|
allowedTools: ["Read", "Edit", "Write", "Bash", "Glob", "Grep"],
|
|
1315
1334
|
permissionMode: "bypassPermissions",
|
|
1316
1335
|
maxTurns,
|
|
1336
|
+
// Native SDK hard cost cap. Stops this pass with an `error_max_budget_usd`
|
|
1337
|
+
// result once spend exceeds the remaining budget — this, not maxTurns, is
|
|
1338
|
+
// the real limiter. Omitted when there's no positive budget left.
|
|
1339
|
+
...budgetUsd && budgetUsd > 0 ? { maxBudgetUsd: budgetUsd } : {},
|
|
1317
1340
|
settingSources: []
|
|
1318
1341
|
}
|
|
1319
1342
|
});
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1343
|
+
try {
|
|
1344
|
+
for await (const message of response) {
|
|
1345
|
+
if (message.type === "assistant") {
|
|
1346
|
+
for (const block of message.message?.content ?? []) {
|
|
1347
|
+
if (isTextBlock(block) && block.text?.trim()) {
|
|
1348
|
+
progress?.onActivity?.(firstLine(block.text));
|
|
1349
|
+
} else if (isToolUseBlock(block)) {
|
|
1350
|
+
progress?.onActivity?.(describeToolUse(block));
|
|
1351
|
+
}
|
|
1327
1352
|
}
|
|
1353
|
+
} else if (message.type === "result") {
|
|
1354
|
+
ok = message.subtype === "success";
|
|
1355
|
+
const sub = message.subtype ?? "";
|
|
1356
|
+
maxedOut = sub.includes("max_turns") || sub.includes("max_budget");
|
|
1357
|
+
costUsd = message.total_cost_usd ?? 0;
|
|
1358
|
+
summary = message.result ?? extractLastText(message) ?? summary;
|
|
1328
1359
|
}
|
|
1329
|
-
}
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1360
|
+
}
|
|
1361
|
+
} catch (err) {
|
|
1362
|
+
if (isLimitStop(err)) {
|
|
1363
|
+
maxedOut = true;
|
|
1364
|
+
} else {
|
|
1365
|
+
throw err;
|
|
1334
1366
|
}
|
|
1335
1367
|
}
|
|
1336
1368
|
return { summary, costUsd, ok, maxedOut };
|
|
@@ -1507,6 +1539,51 @@ async function pollFirstEvent(config, session, opts = {}) {
|
|
|
1507
1539
|
return { received: false };
|
|
1508
1540
|
}
|
|
1509
1541
|
|
|
1542
|
+
// src/core/postflight.ts
|
|
1543
|
+
import { spawn as spawn2 } from "child_process";
|
|
1544
|
+
var MAX_OUTPUT = 4e3;
|
|
1545
|
+
var DEFAULT_TIMEOUT_MS = 18e4;
|
|
1546
|
+
function runVerifyCommand(repoPath, command, opts = {}) {
|
|
1547
|
+
if (!command || !command.trim()) {
|
|
1548
|
+
return Promise.resolve({ ran: false, ok: true, output: "" });
|
|
1549
|
+
}
|
|
1550
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
1551
|
+
return new Promise((resolve2) => {
|
|
1552
|
+
const child = spawn2(command, { cwd: repoPath, shell: true, env: process.env });
|
|
1553
|
+
let out = "";
|
|
1554
|
+
const append = (d) => {
|
|
1555
|
+
out += d.toString();
|
|
1556
|
+
if (out.length > MAX_OUTPUT * 4) out = out.slice(-MAX_OUTPUT * 2);
|
|
1557
|
+
};
|
|
1558
|
+
child.stdout?.on("data", append);
|
|
1559
|
+
child.stderr?.on("data", append);
|
|
1560
|
+
const timer = setTimeout(() => {
|
|
1561
|
+
child.kill("SIGKILL");
|
|
1562
|
+
resolve2({ ran: true, ok: false, command, output: tail(out), timedOut: true });
|
|
1563
|
+
}, timeoutMs);
|
|
1564
|
+
child.on("close", (code) => {
|
|
1565
|
+
clearTimeout(timer);
|
|
1566
|
+
resolve2({
|
|
1567
|
+
ran: true,
|
|
1568
|
+
ok: code === 0,
|
|
1569
|
+
command,
|
|
1570
|
+
exitCode: code,
|
|
1571
|
+
output: tail(out),
|
|
1572
|
+
// 127 = "command not found": the verify tool isn't on PATH here.
|
|
1573
|
+
toolMissing: code === 127
|
|
1574
|
+
});
|
|
1575
|
+
});
|
|
1576
|
+
child.on("error", () => {
|
|
1577
|
+
clearTimeout(timer);
|
|
1578
|
+
resolve2({ ran: true, ok: false, command, output: tail(out), toolMissing: true });
|
|
1579
|
+
});
|
|
1580
|
+
});
|
|
1581
|
+
}
|
|
1582
|
+
function tail(s) {
|
|
1583
|
+
const t = s.trim();
|
|
1584
|
+
return t.length > MAX_OUTPUT ? `\u2026${t.slice(-MAX_OUTPUT)}` : t;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1510
1587
|
// src/core/report.ts
|
|
1511
1588
|
async function postRunReport(config, session, report) {
|
|
1512
1589
|
if (config.offline) return;
|
|
@@ -1673,6 +1750,40 @@ Flutter is live today; ${theme.bright(
|
|
|
1673
1750
|
return 1;
|
|
1674
1751
|
}
|
|
1675
1752
|
}
|
|
1753
|
+
let verified = null;
|
|
1754
|
+
if (chosen.playbook.verifyCommand && files.length) {
|
|
1755
|
+
const cmd = chosen.playbook.verifyCommand;
|
|
1756
|
+
const vspin = p.spinner();
|
|
1757
|
+
vspin.start(`Verifying the integration \u2014 ${theme.muted(cmd)}`);
|
|
1758
|
+
const verdict = await runVerifyCommand(repoPath, cmd);
|
|
1759
|
+
if (verdict.toolMissing) {
|
|
1760
|
+
vspin.stop(
|
|
1761
|
+
theme.warn("Couldn't verify automatically") + theme.muted(` \u2014 \`${cmd}\` isn't available here. Run it yourself before committing.`)
|
|
1762
|
+
);
|
|
1763
|
+
} else if (verdict.timedOut) {
|
|
1764
|
+
vspin.stop(
|
|
1765
|
+
theme.warn(`Verification timed out \u2014 \`${cmd}\``) + theme.muted(" \u2014 run it yourself before committing.")
|
|
1766
|
+
);
|
|
1767
|
+
} else if (verdict.ok) {
|
|
1768
|
+
verified = true;
|
|
1769
|
+
vspin.stop(theme.success("Verified \u2713") + theme.muted(` (${cmd})`));
|
|
1770
|
+
} else {
|
|
1771
|
+
verified = false;
|
|
1772
|
+
vspin.stop(theme.alert(`Verification failed \u2014 ${cmd}`));
|
|
1773
|
+
if (verdict.output) {
|
|
1774
|
+
p.note(verdict.output, theme.warn("Verifier output"));
|
|
1775
|
+
}
|
|
1776
|
+
const reverted = await maybeRevert(
|
|
1777
|
+
repoPath,
|
|
1778
|
+
checkpoint,
|
|
1779
|
+
"The integration didn't pass its build/lint check, so the edits may be broken."
|
|
1780
|
+
);
|
|
1781
|
+
if (reverted) {
|
|
1782
|
+
p.outro(theme.muted("Reverted \u2014 nothing was left in your working tree."));
|
|
1783
|
+
return 1;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1676
1787
|
const wiredMap = await scanWiredEvents(
|
|
1677
1788
|
repoPath,
|
|
1678
1789
|
files,
|
|
@@ -1687,6 +1798,7 @@ Flutter is live today; ${theme.bright(
|
|
|
1687
1798
|
target: chosen.playbook.target.id,
|
|
1688
1799
|
repo_fingerprint: fingerprint,
|
|
1689
1800
|
identify_wired: outcome.coreOk,
|
|
1801
|
+
verified,
|
|
1690
1802
|
cost_usd: outcome.costUsd,
|
|
1691
1803
|
duration_ms: outcome.durationMs,
|
|
1692
1804
|
summary: outcome.summary.slice(0, 4e3),
|
|
@@ -1694,7 +1806,7 @@ Flutter is live today; ${theme.bright(
|
|
|
1694
1806
|
});
|
|
1695
1807
|
p.log.info(
|
|
1696
1808
|
theme.muted(
|
|
1697
|
-
`${wiredMap.size}/${manifest.events.length} events wired \xB7 ${files.length} file${files.length === 1 ? "" : "s"} changed \xB7
|
|
1809
|
+
`${wiredMap.size}/${manifest.events.length} events wired \xB7 ${files.length} file${files.length === 1 ? "" : "s"} changed \xB7 ` + (verified === true ? "verified \xB7 " : verified === false ? "unverified \xB7 " : "") + `${Math.round(outcome.durationMs / 1e3)}s`
|
|
1698
1810
|
)
|
|
1699
1811
|
);
|
|
1700
1812
|
if (!config.offline) {
|
|
@@ -1890,7 +2002,7 @@ async function main() {
|
|
|
1890
2002
|
return;
|
|
1891
2003
|
}
|
|
1892
2004
|
if ("version" in parsed) {
|
|
1893
|
-
console.log("0.1.
|
|
2005
|
+
console.log("0.1.12");
|
|
1894
2006
|
return;
|
|
1895
2007
|
}
|
|
1896
2008
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whisperr/wizard",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.13",
|
|
4
4
|
"description": "Whisperr Wizard — one command to integrate the Whisperr SDK into your app. Authenticates with your onboarded account and uses an AI coding agent to wire up identify() and your business events automatically.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"private": false,
|