@whisperr/wizard 0.7.0 → 0.7.1
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 +157 -77
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1969,10 +1969,10 @@ var RunsClient = class {
|
|
|
1969
1969
|
return payload;
|
|
1970
1970
|
}
|
|
1971
1971
|
createOrResumeRun(input) {
|
|
1972
|
-
return this.request("POST", "/wizard/runs", { ...input });
|
|
1972
|
+
return this.request("POST", "/wizard/runs", { ...input }).then(normalizeRunBootstrap);
|
|
1973
1973
|
}
|
|
1974
1974
|
getRun(runId) {
|
|
1975
|
-
return this.request("GET", `/wizard/runs/${runId}`);
|
|
1975
|
+
return this.request("GET", `/wizard/runs/${runId}`).then(normalizeRunBootstrap);
|
|
1976
1976
|
}
|
|
1977
1977
|
patchRun(runId, patch) {
|
|
1978
1978
|
return this.request("PATCH", `/wizard/runs/${runId}`, { ...patch });
|
|
@@ -1983,7 +1983,7 @@ var RunsClient = class {
|
|
|
1983
1983
|
idempotencyKey: itemIdempotencyKey(kind, code, idempotencyExtra),
|
|
1984
1984
|
kind,
|
|
1985
1985
|
payload
|
|
1986
|
-
});
|
|
1986
|
+
}).then(normalizeItemWriteResult);
|
|
1987
1987
|
}
|
|
1988
1988
|
completeRun(runId, completion) {
|
|
1989
1989
|
return this.request(
|
|
@@ -1993,6 +1993,64 @@ var RunsClient = class {
|
|
|
1993
1993
|
);
|
|
1994
1994
|
}
|
|
1995
1995
|
};
|
|
1996
|
+
function normalizeItemWriteResult(payload) {
|
|
1997
|
+
const value = record(payload);
|
|
1998
|
+
const item = record(value?.item);
|
|
1999
|
+
const id = stringValue(value?.id) || stringValue(item?.id);
|
|
2000
|
+
if (!value || value.kind !== "event" || typeof value.created !== "boolean" || !id) {
|
|
2001
|
+
throw new RunsApiError("runs API returned an invalid item write response");
|
|
2002
|
+
}
|
|
2003
|
+
return {
|
|
2004
|
+
kind: "event",
|
|
2005
|
+
created: value.created,
|
|
2006
|
+
id,
|
|
2007
|
+
...typeof value.code === "string" ? { code: value.code } : {},
|
|
2008
|
+
...item && typeof item.id === "string" ? { item: { id: item.id, ...typeof item.code === "string" ? { code: item.code } : {} } } : {}
|
|
2009
|
+
};
|
|
2010
|
+
}
|
|
2011
|
+
function normalizeRunBootstrap(payload) {
|
|
2012
|
+
const value = record(payload);
|
|
2013
|
+
if (!value) {
|
|
2014
|
+
throw new RunsApiError("runs API returned an invalid run bootstrap response");
|
|
2015
|
+
}
|
|
2016
|
+
const snapshotSource = record(value.snapshot) ?? value;
|
|
2017
|
+
const events = Array.isArray(snapshotSource.events) ? snapshotSource.events : void 0;
|
|
2018
|
+
const run3 = record(value.run);
|
|
2019
|
+
const project = record(value.project);
|
|
2020
|
+
const ingestion = record(value.ingestion);
|
|
2021
|
+
if (!run3 || typeof run3.id !== "string" || !project || typeof project.id !== "string" || !events || events.some((event) => !isSnapshotEvent(event)) || !ingestion || typeof ingestion.apiKey !== "string" || typeof ingestion.baseUrl !== "string") {
|
|
2022
|
+
throw new RunsApiError("runs API returned an invalid run bootstrap response");
|
|
2023
|
+
}
|
|
2024
|
+
return {
|
|
2025
|
+
project: {
|
|
2026
|
+
id: project.id,
|
|
2027
|
+
repoFingerprint: stringValue(project.repoFingerprint),
|
|
2028
|
+
displayName: stringValue(project.displayName),
|
|
2029
|
+
target: stringValue(project.target),
|
|
2030
|
+
kind: stringValue(project.kind)
|
|
2031
|
+
},
|
|
2032
|
+
run: { id: run3.id, status: stringValue(run3.status), phase: stringValue(run3.phase) },
|
|
2033
|
+
resumed: value.resumed === true,
|
|
2034
|
+
universeMode: stringValue(value.universeMode),
|
|
2035
|
+
...record(value.onboardingContext) ? { onboardingContext: record(value.onboardingContext) } : {},
|
|
2036
|
+
app: record(value.app) ?? {},
|
|
2037
|
+
snapshot: {
|
|
2038
|
+
setupStatus: stringValue(snapshotSource.setupStatus),
|
|
2039
|
+
events
|
|
2040
|
+
},
|
|
2041
|
+
ingestion: { apiKey: ingestion.apiKey, baseUrl: ingestion.baseUrl }
|
|
2042
|
+
};
|
|
2043
|
+
}
|
|
2044
|
+
function record(value) {
|
|
2045
|
+
return typeof value === "object" && value !== null ? value : void 0;
|
|
2046
|
+
}
|
|
2047
|
+
function stringValue(value) {
|
|
2048
|
+
return typeof value === "string" ? value : "";
|
|
2049
|
+
}
|
|
2050
|
+
function isSnapshotEvent(value) {
|
|
2051
|
+
const event = record(value);
|
|
2052
|
+
return !!event && typeof event.id === "string" && typeof event.code === "string" && typeof event.name === "string" && typeof event.reasoning === "string" && typeof event.projectId === "string" && !!record(event.payloadSchema);
|
|
2053
|
+
}
|
|
1996
2054
|
|
|
1997
2055
|
// src/engine/selection.ts
|
|
1998
2056
|
import { z } from "zod";
|
|
@@ -2271,8 +2329,7 @@ async function registerApprovedEvents(runs, runId, approved, sink) {
|
|
|
2271
2329
|
reasoning: event.reasoning,
|
|
2272
2330
|
...Object.keys(event.payloadSchema).length > 0 ? { payloadSchema: event.payloadSchema } : {}
|
|
2273
2331
|
});
|
|
2274
|
-
|
|
2275
|
-
registered.push({ ...event, eventId: item?.id ?? result.id ?? "" });
|
|
2332
|
+
registered.push({ ...event, eventId: result.id || result.item?.id || "" });
|
|
2276
2333
|
sink.emit({ phase: "persisting", file: event.anchorFile, action: `registered ${event.code}` });
|
|
2277
2334
|
}
|
|
2278
2335
|
return registered;
|
|
@@ -2290,7 +2347,7 @@ function placementsFor(registered) {
|
|
|
2290
2347
|
// src/engine/state.ts
|
|
2291
2348
|
import { createHash as createHash3 } from "crypto";
|
|
2292
2349
|
import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
2293
|
-
import { homedir } from "os";
|
|
2350
|
+
import { homedir, tmpdir } from "os";
|
|
2294
2351
|
import { join as join4 } from "path";
|
|
2295
2352
|
|
|
2296
2353
|
// src/engine/types.ts
|
|
@@ -2310,10 +2367,16 @@ var ENGINE_PHASES = [
|
|
|
2310
2367
|
// src/engine/state.ts
|
|
2311
2368
|
function stateDirectory() {
|
|
2312
2369
|
const override = process.env.WHISPERR_WIZARD_STATE_DIR?.trim();
|
|
2313
|
-
|
|
2314
|
-
|
|
2370
|
+
const preferred = override || join4(homedir(), ".whisperr", "wizard-runs");
|
|
2371
|
+
if (canCreateStateDirectory(preferred)) {
|
|
2372
|
+
return preferred;
|
|
2315
2373
|
}
|
|
2316
|
-
|
|
2374
|
+
const user = typeof process.getuid === "function" ? process.getuid() : "user";
|
|
2375
|
+
const fallback = join4(tmpdir(), `whisperr-${user}`, "wizard-runs");
|
|
2376
|
+
if (canCreateStateDirectory(fallback)) {
|
|
2377
|
+
return fallback;
|
|
2378
|
+
}
|
|
2379
|
+
throw new Error("Whisperr Wizard could not create a directory for run state");
|
|
2317
2380
|
}
|
|
2318
2381
|
function stateKey(apiBaseUrl, appId, repoFingerprint2) {
|
|
2319
2382
|
return createHash3("sha256").update(`${apiBaseUrl}
|
|
@@ -2324,13 +2387,21 @@ function statePath(apiBaseUrl, appId, repoFingerprint2) {
|
|
|
2324
2387
|
return join4(stateDirectory(), `${stateKey(apiBaseUrl, appId, repoFingerprint2)}.json`);
|
|
2325
2388
|
}
|
|
2326
2389
|
function saveRunState(state) {
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2390
|
+
const directory = stateDirectory();
|
|
2391
|
+
const path = join4(directory, `${stateKey(state.apiBaseUrl, state.appId, state.repoFingerprint)}.json`);
|
|
2329
2392
|
const payload = JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2);
|
|
2330
2393
|
const temporary = `${path}.tmp`;
|
|
2331
2394
|
writeFileSync(temporary, payload, { mode: 384 });
|
|
2332
2395
|
renameSync(temporary, path);
|
|
2333
2396
|
}
|
|
2397
|
+
function canCreateStateDirectory(directory) {
|
|
2398
|
+
try {
|
|
2399
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2400
|
+
return true;
|
|
2401
|
+
} catch {
|
|
2402
|
+
return false;
|
|
2403
|
+
}
|
|
2404
|
+
}
|
|
2334
2405
|
function loadRunState(apiBaseUrl, appId, repoFingerprint2) {
|
|
2335
2406
|
const path = statePath(apiBaseUrl, appId, repoFingerprint2);
|
|
2336
2407
|
let raw;
|
|
@@ -2364,7 +2435,7 @@ function isEngineRunState(value) {
|
|
|
2364
2435
|
// src/engine/worktree.ts
|
|
2365
2436
|
import { spawn as spawn2 } from "child_process";
|
|
2366
2437
|
import { mkdtemp, writeFile as writeFile2 } from "fs/promises";
|
|
2367
|
-
import { tmpdir } from "os";
|
|
2438
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2368
2439
|
import { join as join5 } from "path";
|
|
2369
2440
|
var WorktreeError = class extends Error {
|
|
2370
2441
|
constructor(code, message) {
|
|
@@ -2409,7 +2480,7 @@ async function createWorktree(repoPath, force = false) {
|
|
|
2409
2480
|
);
|
|
2410
2481
|
}
|
|
2411
2482
|
const baseRef = (await must(repoPath, ["rev-parse", "HEAD"])).trim();
|
|
2412
|
-
const path = await mkdtemp(join5(
|
|
2483
|
+
const path = await mkdtemp(join5(tmpdir2(), "whisperr-worktree-"));
|
|
2413
2484
|
await must(repoPath, ["worktree", "add", "--detach", "--force", path, "HEAD"]);
|
|
2414
2485
|
return { path, baseRef, repoPath };
|
|
2415
2486
|
}
|
|
@@ -2485,73 +2556,85 @@ async function runEngine(input) {
|
|
|
2485
2556
|
} catch {
|
|
2486
2557
|
}
|
|
2487
2558
|
};
|
|
2488
|
-
let state
|
|
2489
|
-
|
|
2490
|
-
state = freshState(input, runId);
|
|
2559
|
+
let state;
|
|
2560
|
+
try {
|
|
2561
|
+
state = loadRunState(input.config.apiBaseUrl, input.session.appId, input.repoFingerprint) ?? freshState(input, runId);
|
|
2562
|
+
if (state.runId !== runId) {
|
|
2563
|
+
state = freshState(input, runId);
|
|
2564
|
+
}
|
|
2565
|
+
} catch (error) {
|
|
2566
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2567
|
+
await failRun(`state initialization failed: ${reason}`);
|
|
2568
|
+
return { kind: "aborted", reason: `state initialization failed: ${reason}` };
|
|
2491
2569
|
}
|
|
2492
2570
|
const persist = () => saveRunState(state);
|
|
2493
2571
|
let registered;
|
|
2494
2572
|
if (!state.designComplete) {
|
|
2495
|
-
await patchPhase("surveying");
|
|
2496
|
-
const survey = await runSurvey(
|
|
2497
|
-
input.provider,
|
|
2498
|
-
input.models,
|
|
2499
|
-
input.repoPath,
|
|
2500
|
-
bootstrap,
|
|
2501
|
-
input.sink,
|
|
2502
|
-
state.providerSessions.survey
|
|
2503
|
-
);
|
|
2504
|
-
if (survey.outcome.kind !== "completed") {
|
|
2505
|
-
await failRun(`survey ${survey.outcome.kind}`);
|
|
2506
|
-
return { kind: "aborted", reason: `survey ${survey.outcome.kind}` };
|
|
2507
|
-
}
|
|
2508
|
-
state.providerSessions.survey = survey.outcome.sessionId;
|
|
2509
|
-
persist();
|
|
2510
|
-
await patchPhase("designing");
|
|
2511
|
-
const selection = await runSelection(
|
|
2512
|
-
input.provider,
|
|
2513
|
-
input.models,
|
|
2514
|
-
input.repoPath,
|
|
2515
|
-
bootstrap,
|
|
2516
|
-
survey.report,
|
|
2517
|
-
state.tombstonedCodes,
|
|
2518
|
-
input.sink
|
|
2519
|
-
);
|
|
2520
|
-
if (selection.outcome !== "completed" && selection.proposed.length === 0) {
|
|
2521
|
-
await failRun(`selection ${selection.outcome}`);
|
|
2522
|
-
return { kind: "aborted", reason: `selection ${selection.outcome}` };
|
|
2523
|
-
}
|
|
2524
|
-
const approved = await input.callbacks.reviewEvents(selection.proposed);
|
|
2525
|
-
if (approved === null) {
|
|
2526
|
-
await failRun("selection rejected by user");
|
|
2527
|
-
return { kind: "aborted", reason: "selection rejected by user" };
|
|
2528
|
-
}
|
|
2529
|
-
const approvedCodes = new Set(approved.map((event) => event.code));
|
|
2530
|
-
for (const event of selection.proposed) {
|
|
2531
|
-
if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
|
|
2532
|
-
state.tombstonedCodes.push(event.code);
|
|
2533
|
-
}
|
|
2534
|
-
}
|
|
2535
|
-
persist();
|
|
2536
|
-
await patchPhase("persisting", `registering ${approved.length} events`);
|
|
2537
2573
|
try {
|
|
2574
|
+
await patchPhase("surveying");
|
|
2575
|
+
const survey = await runSurvey(
|
|
2576
|
+
input.provider,
|
|
2577
|
+
input.models,
|
|
2578
|
+
input.repoPath,
|
|
2579
|
+
bootstrap,
|
|
2580
|
+
input.sink,
|
|
2581
|
+
state.providerSessions.survey
|
|
2582
|
+
);
|
|
2583
|
+
if (survey.outcome.kind !== "completed") {
|
|
2584
|
+
await failRun(`survey ${survey.outcome.kind}`);
|
|
2585
|
+
return { kind: "aborted", reason: `survey ${survey.outcome.kind}` };
|
|
2586
|
+
}
|
|
2587
|
+
state.providerSessions.survey = survey.outcome.sessionId;
|
|
2588
|
+
persist();
|
|
2589
|
+
await patchPhase("designing");
|
|
2590
|
+
const selection = await runSelection(
|
|
2591
|
+
input.provider,
|
|
2592
|
+
input.models,
|
|
2593
|
+
input.repoPath,
|
|
2594
|
+
bootstrap,
|
|
2595
|
+
survey.report,
|
|
2596
|
+
state.tombstonedCodes,
|
|
2597
|
+
input.sink
|
|
2598
|
+
);
|
|
2599
|
+
if (selection.outcome !== "completed" && selection.proposed.length === 0) {
|
|
2600
|
+
await failRun(`selection ${selection.outcome}`);
|
|
2601
|
+
return { kind: "aborted", reason: `selection ${selection.outcome}` };
|
|
2602
|
+
}
|
|
2603
|
+
const approved = await input.callbacks.reviewEvents(selection.proposed);
|
|
2604
|
+
if (approved === null) {
|
|
2605
|
+
await failRun("selection rejected by user");
|
|
2606
|
+
return { kind: "aborted", reason: "selection rejected by user" };
|
|
2607
|
+
}
|
|
2608
|
+
const approvedCodes = new Set(approved.map((event) => event.code));
|
|
2609
|
+
for (const event of selection.proposed) {
|
|
2610
|
+
if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
|
|
2611
|
+
state.tombstonedCodes.push(event.code);
|
|
2612
|
+
}
|
|
2613
|
+
}
|
|
2614
|
+
persist();
|
|
2615
|
+
await patchPhase("persisting", `registering ${approved.length} events`);
|
|
2538
2616
|
registered = await registerApprovedEvents(runs, runId, approved, input.sink);
|
|
2617
|
+
state.designComplete = true;
|
|
2618
|
+
state.clusters = deriveClusters(placementsFor(registered));
|
|
2619
|
+
persist();
|
|
2539
2620
|
} catch (error) {
|
|
2540
2621
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2541
|
-
await failRun(`
|
|
2542
|
-
|
|
2543
|
-
return { kind: "aborted", reason: `event registration failed: ${reason}` };
|
|
2622
|
+
await failRun(`design failed: ${reason}`);
|
|
2623
|
+
return { kind: "aborted", reason: `design failed: ${reason}` };
|
|
2544
2624
|
}
|
|
2545
|
-
state.designComplete = true;
|
|
2546
|
-
state.clusters = deriveClusters(placementsFor(registered));
|
|
2547
|
-
persist();
|
|
2548
2625
|
} else {
|
|
2549
2626
|
registered = rebuildRegistered(state, bootstrap);
|
|
2550
2627
|
input.sink.emit({ phase: "designing", action: `resumed with ${registered.length} registered events` });
|
|
2551
2628
|
}
|
|
2552
2629
|
if (registered.length === 0) {
|
|
2553
2630
|
await patchPhase("reporting", "no events registered for this project");
|
|
2554
|
-
|
|
2631
|
+
try {
|
|
2632
|
+
await completeRun(runs, runId, [], /* @__PURE__ */ new Map(), [], true, "unavailable", void 0);
|
|
2633
|
+
} catch (error) {
|
|
2634
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2635
|
+
await failRun(`completion failed: ${reason}`);
|
|
2636
|
+
return { kind: "aborted", reason: `completion failed: ${reason}` };
|
|
2637
|
+
}
|
|
2555
2638
|
return {
|
|
2556
2639
|
kind: "completed",
|
|
2557
2640
|
runId,
|
|
@@ -2713,16 +2796,13 @@ function buildEvidence(registered, statuses, changedFiles2) {
|
|
|
2713
2796
|
});
|
|
2714
2797
|
}
|
|
2715
2798
|
async function completeRun(runs, runId, evidence, _statuses, changedFiles2, identifyWired, verificationStatus, verificationCommand2) {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
});
|
|
2724
|
-
} catch {
|
|
2725
|
-
}
|
|
2799
|
+
await runs.completeRun(runId, {
|
|
2800
|
+
changedFiles: changedFiles2,
|
|
2801
|
+
identifyWired,
|
|
2802
|
+
verificationStatus,
|
|
2803
|
+
...verificationCommand2 ? { verificationCommand: verificationCommand2 } : {},
|
|
2804
|
+
events: evidence
|
|
2805
|
+
});
|
|
2726
2806
|
}
|
|
2727
2807
|
function buildReportEvents(registered, statuses) {
|
|
2728
2808
|
return registered.map((event) => {
|
|
@@ -4798,8 +4878,8 @@ async function fetchSuggestions(config, session, statuses) {
|
|
|
4798
4878
|
}
|
|
4799
4879
|
function isSuggestionRecord(value) {
|
|
4800
4880
|
if (typeof value !== "object" || value === null) return false;
|
|
4801
|
-
const
|
|
4802
|
-
return typeof
|
|
4881
|
+
const record2 = value;
|
|
4882
|
+
return typeof record2.id === "string" && (record2.kind === "event" || record2.kind === "intervention") && typeof record2.code === "string" && (record2.status === "proposed" || record2.status === "approved" || record2.status === "rejected" || record2.status === "integrated") && typeof record2.payload === "object" && record2.payload !== null;
|
|
4803
4883
|
}
|
|
4804
4884
|
async function markSuggestionIntegrated(config, session, id, target9, repoFingerprint2) {
|
|
4805
4885
|
if (config.offline) return false;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@whisperr/wizard",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.1",
|
|
4
4
|
"description": "Whisperr Wizard \u2014 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
|
"repository": {
|
|
6
6
|
"type": "git",
|