@whisperr/wizard 0.7.0 → 0.7.2
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 +182 -96
- 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";
|
|
@@ -2172,6 +2230,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
|
|
|
2172
2230
|
const seen = /* @__PURE__ */ new Set();
|
|
2173
2231
|
const rejected = new Set(rejectedCodes);
|
|
2174
2232
|
let summary = "";
|
|
2233
|
+
let finished = false;
|
|
2175
2234
|
const proposeTool = {
|
|
2176
2235
|
name: "propose_event",
|
|
2177
2236
|
description: "Propose one important, concretely-anchored product event. Returns ok:true when accepted.",
|
|
@@ -2233,6 +2292,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
|
|
|
2233
2292
|
},
|
|
2234
2293
|
handler: async (input) => {
|
|
2235
2294
|
summary = String(input.summary ?? "");
|
|
2295
|
+
finished = true;
|
|
2236
2296
|
return { ok: true };
|
|
2237
2297
|
}
|
|
2238
2298
|
};
|
|
@@ -2256,6 +2316,7 @@ async function runSelection(provider, models, repoPath, bootstrap, surveyReport,
|
|
|
2256
2316
|
);
|
|
2257
2317
|
return {
|
|
2258
2318
|
outcome: outcome.kind,
|
|
2319
|
+
finished,
|
|
2259
2320
|
proposed,
|
|
2260
2321
|
summary,
|
|
2261
2322
|
surveyReport,
|
|
@@ -2271,8 +2332,7 @@ async function registerApprovedEvents(runs, runId, approved, sink) {
|
|
|
2271
2332
|
reasoning: event.reasoning,
|
|
2272
2333
|
...Object.keys(event.payloadSchema).length > 0 ? { payloadSchema: event.payloadSchema } : {}
|
|
2273
2334
|
});
|
|
2274
|
-
|
|
2275
|
-
registered.push({ ...event, eventId: item?.id ?? result.id ?? "" });
|
|
2335
|
+
registered.push({ ...event, eventId: result.id || result.item?.id || "" });
|
|
2276
2336
|
sink.emit({ phase: "persisting", file: event.anchorFile, action: `registered ${event.code}` });
|
|
2277
2337
|
}
|
|
2278
2338
|
return registered;
|
|
@@ -2290,7 +2350,7 @@ function placementsFor(registered) {
|
|
|
2290
2350
|
// src/engine/state.ts
|
|
2291
2351
|
import { createHash as createHash3 } from "crypto";
|
|
2292
2352
|
import { mkdirSync, readFileSync as readFileSync2, renameSync, writeFileSync } from "fs";
|
|
2293
|
-
import { homedir } from "os";
|
|
2353
|
+
import { homedir, tmpdir } from "os";
|
|
2294
2354
|
import { join as join4 } from "path";
|
|
2295
2355
|
|
|
2296
2356
|
// src/engine/types.ts
|
|
@@ -2310,10 +2370,16 @@ var ENGINE_PHASES = [
|
|
|
2310
2370
|
// src/engine/state.ts
|
|
2311
2371
|
function stateDirectory() {
|
|
2312
2372
|
const override = process.env.WHISPERR_WIZARD_STATE_DIR?.trim();
|
|
2313
|
-
|
|
2314
|
-
|
|
2373
|
+
const preferred = override || join4(homedir(), ".whisperr", "wizard-runs");
|
|
2374
|
+
if (canCreateStateDirectory(preferred)) {
|
|
2375
|
+
return preferred;
|
|
2315
2376
|
}
|
|
2316
|
-
|
|
2377
|
+
const user = typeof process.getuid === "function" ? process.getuid() : "user";
|
|
2378
|
+
const fallback = join4(tmpdir(), `whisperr-${user}`, "wizard-runs");
|
|
2379
|
+
if (canCreateStateDirectory(fallback)) {
|
|
2380
|
+
return fallback;
|
|
2381
|
+
}
|
|
2382
|
+
throw new Error("Whisperr Wizard could not create a directory for run state");
|
|
2317
2383
|
}
|
|
2318
2384
|
function stateKey(apiBaseUrl, appId, repoFingerprint2) {
|
|
2319
2385
|
return createHash3("sha256").update(`${apiBaseUrl}
|
|
@@ -2324,13 +2390,21 @@ function statePath(apiBaseUrl, appId, repoFingerprint2) {
|
|
|
2324
2390
|
return join4(stateDirectory(), `${stateKey(apiBaseUrl, appId, repoFingerprint2)}.json`);
|
|
2325
2391
|
}
|
|
2326
2392
|
function saveRunState(state) {
|
|
2327
|
-
const
|
|
2328
|
-
|
|
2393
|
+
const directory = stateDirectory();
|
|
2394
|
+
const path = join4(directory, `${stateKey(state.apiBaseUrl, state.appId, state.repoFingerprint)}.json`);
|
|
2329
2395
|
const payload = JSON.stringify({ ...state, updatedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2);
|
|
2330
2396
|
const temporary = `${path}.tmp`;
|
|
2331
2397
|
writeFileSync(temporary, payload, { mode: 384 });
|
|
2332
2398
|
renameSync(temporary, path);
|
|
2333
2399
|
}
|
|
2400
|
+
function canCreateStateDirectory(directory) {
|
|
2401
|
+
try {
|
|
2402
|
+
mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2403
|
+
return true;
|
|
2404
|
+
} catch {
|
|
2405
|
+
return false;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2334
2408
|
function loadRunState(apiBaseUrl, appId, repoFingerprint2) {
|
|
2335
2409
|
const path = statePath(apiBaseUrl, appId, repoFingerprint2);
|
|
2336
2410
|
let raw;
|
|
@@ -2364,7 +2438,7 @@ function isEngineRunState(value) {
|
|
|
2364
2438
|
// src/engine/worktree.ts
|
|
2365
2439
|
import { spawn as spawn2 } from "child_process";
|
|
2366
2440
|
import { mkdtemp, writeFile as writeFile2 } from "fs/promises";
|
|
2367
|
-
import { tmpdir } from "os";
|
|
2441
|
+
import { tmpdir as tmpdir2 } from "os";
|
|
2368
2442
|
import { join as join5 } from "path";
|
|
2369
2443
|
var WorktreeError = class extends Error {
|
|
2370
2444
|
constructor(code, message) {
|
|
@@ -2409,7 +2483,7 @@ async function createWorktree(repoPath, force = false) {
|
|
|
2409
2483
|
);
|
|
2410
2484
|
}
|
|
2411
2485
|
const baseRef = (await must(repoPath, ["rev-parse", "HEAD"])).trim();
|
|
2412
|
-
const path = await mkdtemp(join5(
|
|
2486
|
+
const path = await mkdtemp(join5(tmpdir2(), "whisperr-worktree-"));
|
|
2413
2487
|
await must(repoPath, ["worktree", "add", "--detach", "--force", path, "HEAD"]);
|
|
2414
2488
|
return { path, baseRef, repoPath };
|
|
2415
2489
|
}
|
|
@@ -2485,84 +2559,88 @@ async function runEngine(input) {
|
|
|
2485
2559
|
} catch {
|
|
2486
2560
|
}
|
|
2487
2561
|
};
|
|
2488
|
-
let state
|
|
2489
|
-
|
|
2490
|
-
state = freshState(input, runId);
|
|
2562
|
+
let state;
|
|
2563
|
+
try {
|
|
2564
|
+
state = loadRunState(input.config.apiBaseUrl, input.session.appId, input.repoFingerprint) ?? freshState(input, runId);
|
|
2565
|
+
if (state.runId !== runId) {
|
|
2566
|
+
state = freshState(input, runId);
|
|
2567
|
+
}
|
|
2568
|
+
} catch (error) {
|
|
2569
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
2570
|
+
await failRun(`state initialization failed: ${reason}`);
|
|
2571
|
+
return { kind: "aborted", reason: `state initialization failed: ${reason}` };
|
|
2491
2572
|
}
|
|
2492
2573
|
const persist = () => saveRunState(state);
|
|
2493
2574
|
let registered;
|
|
2494
2575
|
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
2576
|
try {
|
|
2577
|
+
await patchPhase("surveying");
|
|
2578
|
+
const survey = await runSurvey(
|
|
2579
|
+
input.provider,
|
|
2580
|
+
input.models,
|
|
2581
|
+
input.repoPath,
|
|
2582
|
+
bootstrap,
|
|
2583
|
+
input.sink,
|
|
2584
|
+
state.providerSessions.survey
|
|
2585
|
+
);
|
|
2586
|
+
if (survey.outcome.kind !== "completed") {
|
|
2587
|
+
await failRun(`survey ${survey.outcome.kind}`);
|
|
2588
|
+
return { kind: "aborted", reason: `survey ${survey.outcome.kind}` };
|
|
2589
|
+
}
|
|
2590
|
+
state.providerSessions.survey = survey.outcome.sessionId;
|
|
2591
|
+
persist();
|
|
2592
|
+
await patchPhase("designing");
|
|
2593
|
+
const selection = await runSelection(
|
|
2594
|
+
input.provider,
|
|
2595
|
+
input.models,
|
|
2596
|
+
input.repoPath,
|
|
2597
|
+
bootstrap,
|
|
2598
|
+
survey.report,
|
|
2599
|
+
state.tombstonedCodes,
|
|
2600
|
+
input.sink
|
|
2601
|
+
);
|
|
2602
|
+
if (!selection.finished) {
|
|
2603
|
+
const reason = selection.outcome === "completed" ? "selection did not call finish_selection" : `selection ${selection.outcome}`;
|
|
2604
|
+
await failRun(reason);
|
|
2605
|
+
return { kind: "aborted", reason };
|
|
2606
|
+
}
|
|
2607
|
+
if (selection.proposed.length === 0) {
|
|
2608
|
+
await failRun("selection finished with no accepted events");
|
|
2609
|
+
return { kind: "aborted", reason: "selection finished with no accepted events" };
|
|
2610
|
+
}
|
|
2611
|
+
const approved = await input.callbacks.reviewEvents(selection.proposed);
|
|
2612
|
+
if (approved === null) {
|
|
2613
|
+
await failRun("selection rejected by user");
|
|
2614
|
+
return { kind: "aborted", reason: "selection rejected by user" };
|
|
2615
|
+
}
|
|
2616
|
+
if (approved.length === 0) {
|
|
2617
|
+
await failRun("no events accepted for instrumentation");
|
|
2618
|
+
return { kind: "aborted", reason: "no events accepted for instrumentation" };
|
|
2619
|
+
}
|
|
2620
|
+
const approvedCodes = new Set(approved.map((event) => event.code));
|
|
2621
|
+
for (const event of selection.proposed) {
|
|
2622
|
+
if (!approvedCodes.has(event.code) && !state.tombstonedCodes.includes(event.code)) {
|
|
2623
|
+
state.tombstonedCodes.push(event.code);
|
|
2624
|
+
}
|
|
2625
|
+
}
|
|
2626
|
+
persist();
|
|
2627
|
+
await patchPhase("persisting", `registering ${approved.length} events`);
|
|
2538
2628
|
registered = await registerApprovedEvents(runs, runId, approved, input.sink);
|
|
2629
|
+
state.designComplete = true;
|
|
2630
|
+
state.clusters = deriveClusters(placementsFor(registered));
|
|
2631
|
+
persist();
|
|
2539
2632
|
} catch (error) {
|
|
2540
2633
|
const reason = error instanceof Error ? error.message : String(error);
|
|
2541
|
-
await failRun(`
|
|
2542
|
-
|
|
2543
|
-
return { kind: "aborted", reason: `event registration failed: ${reason}` };
|
|
2634
|
+
await failRun(`design failed: ${reason}`);
|
|
2635
|
+
return { kind: "aborted", reason: `design failed: ${reason}` };
|
|
2544
2636
|
}
|
|
2545
|
-
state.designComplete = true;
|
|
2546
|
-
state.clusters = deriveClusters(placementsFor(registered));
|
|
2547
|
-
persist();
|
|
2548
2637
|
} else {
|
|
2549
2638
|
registered = rebuildRegistered(state, bootstrap);
|
|
2550
2639
|
input.sink.emit({ phase: "designing", action: `resumed with ${registered.length} registered events` });
|
|
2551
2640
|
}
|
|
2552
2641
|
if (registered.length === 0) {
|
|
2553
|
-
await
|
|
2554
|
-
|
|
2555
|
-
return {
|
|
2556
|
-
kind: "completed",
|
|
2557
|
-
runId,
|
|
2558
|
-
registered,
|
|
2559
|
-
eventStatuses: /* @__PURE__ */ new Map(),
|
|
2560
|
-
reportEvents: [],
|
|
2561
|
-
changedFiles: [],
|
|
2562
|
-
identifyWired: false,
|
|
2563
|
-
applied: false,
|
|
2564
|
-
summary: "No important events found for this project; nothing was instrumented."
|
|
2565
|
-
};
|
|
2642
|
+
await failRun("no events registered for this project");
|
|
2643
|
+
return { kind: "aborted", reason: "no events registered for this project" };
|
|
2566
2644
|
}
|
|
2567
2645
|
await patchPhase("binding");
|
|
2568
2646
|
let worktree;
|
|
@@ -2713,16 +2791,13 @@ function buildEvidence(registered, statuses, changedFiles2) {
|
|
|
2713
2791
|
});
|
|
2714
2792
|
}
|
|
2715
2793
|
async function completeRun(runs, runId, evidence, _statuses, changedFiles2, identifyWired, verificationStatus, verificationCommand2) {
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
});
|
|
2724
|
-
} catch {
|
|
2725
|
-
}
|
|
2794
|
+
await runs.completeRun(runId, {
|
|
2795
|
+
changedFiles: changedFiles2,
|
|
2796
|
+
identifyWired,
|
|
2797
|
+
verificationStatus,
|
|
2798
|
+
...verificationCommand2 ? { verificationCommand: verificationCommand2 } : {},
|
|
2799
|
+
events: evidence
|
|
2800
|
+
});
|
|
2726
2801
|
}
|
|
2727
2802
|
function buildReportEvents(registered, statuses) {
|
|
2728
2803
|
return registered.map((event) => {
|
|
@@ -3113,6 +3188,12 @@ var WRITE_OUTSIDE_REPO_DENIAL = "blocked: writes must stay inside the target rep
|
|
|
3113
3188
|
var SENSITIVE_WRITE_DENIAL = "blocked: refusing to write CI/git configuration";
|
|
3114
3189
|
var BASH_ALLOWLIST_DENIAL = "blocked: command is outside the Bash allowlist - use package install/add commands, mkdir, or git status/diff/log only";
|
|
3115
3190
|
var CHAINED_COMMAND_DENIAL = "blocked: chained or substituted shell command - run one command at a time";
|
|
3191
|
+
function engineMcpToolName(toolName) {
|
|
3192
|
+
return `mcp__engine__${toolName}`;
|
|
3193
|
+
}
|
|
3194
|
+
function trustedEngineMcpTools(tools) {
|
|
3195
|
+
return new Set(tools.map((tool2) => engineMcpToolName(tool2.name)));
|
|
3196
|
+
}
|
|
3116
3197
|
var ENV_EXAMPLES = /* @__PURE__ */ new Set([".env.example", ".env.sample", ".env.template"]);
|
|
3117
3198
|
var SECRET_DIRECTORIES = /* @__PURE__ */ new Set([".aws", ".ssh", ".gnupg"]);
|
|
3118
3199
|
var SECRET_EXACT_FILES = /* @__PURE__ */ new Set([".netrc", ".npmrc", ".pypirc"]);
|
|
@@ -3137,6 +3218,9 @@ var JS_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["npm", "pnpm", "yarn", "bun"]
|
|
|
3137
3218
|
var PYTHON_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["pip", "pip3", "poetry", "uv"]);
|
|
3138
3219
|
var RUBY_PACKAGE_MANAGERS = /* @__PURE__ */ new Set(["gem", "bundle"]);
|
|
3139
3220
|
function evaluateToolUse(toolName, input, context) {
|
|
3221
|
+
if (context.trustedTools?.has(toolName)) {
|
|
3222
|
+
return { behavior: "allow" };
|
|
3223
|
+
}
|
|
3140
3224
|
switch (toolName) {
|
|
3141
3225
|
case "Read":
|
|
3142
3226
|
return evaluateReadLike(readPathInputs(input), readPathInputs(input), context);
|
|
@@ -4168,7 +4252,7 @@ async function runPass(opts) {
|
|
|
4168
4252
|
}
|
|
4169
4253
|
return { summary, costUsd, ok, maxedOut };
|
|
4170
4254
|
}
|
|
4171
|
-
function buildToolPolicyHooks(repoPath) {
|
|
4255
|
+
function buildToolPolicyHooks(repoPath, trustedTools) {
|
|
4172
4256
|
return {
|
|
4173
4257
|
PreToolUse: [
|
|
4174
4258
|
{
|
|
@@ -4176,7 +4260,8 @@ function buildToolPolicyHooks(repoPath) {
|
|
|
4176
4260
|
async (input) => {
|
|
4177
4261
|
if (input.hook_event_name !== "PreToolUse") return { continue: true };
|
|
4178
4262
|
const decision = evaluateToolUse(input.tool_name, toolInputRecord(input.tool_input), {
|
|
4179
|
-
repoPath
|
|
4263
|
+
repoPath,
|
|
4264
|
+
trustedTools
|
|
4180
4265
|
});
|
|
4181
4266
|
if (decision.behavior === "allow") {
|
|
4182
4267
|
return { continue: true };
|
|
@@ -4194,9 +4279,9 @@ function buildToolPolicyHooks(repoPath) {
|
|
|
4194
4279
|
]
|
|
4195
4280
|
};
|
|
4196
4281
|
}
|
|
4197
|
-
function createToolPermissionCallback(repoPath) {
|
|
4282
|
+
function createToolPermissionCallback(repoPath, trustedTools) {
|
|
4198
4283
|
return async (toolName, input, options) => {
|
|
4199
|
-
const decision = evaluateToolUse(toolName, input, { repoPath });
|
|
4284
|
+
const decision = evaluateToolUse(toolName, input, { repoPath, trustedTools });
|
|
4200
4285
|
return decision.behavior === "allow" ? { behavior: "allow", toolUseID: options.toolUseID } : {
|
|
4201
4286
|
behavior: "deny",
|
|
4202
4287
|
message: decision.message,
|
|
@@ -4275,9 +4360,10 @@ function createClaudeProvider(config, session) {
|
|
|
4275
4360
|
return { content: [{ type: "text", text: JSON.stringify(result) }] };
|
|
4276
4361
|
})
|
|
4277
4362
|
);
|
|
4363
|
+
const trustedTools = trustedEngineMcpTools(invocation.tools);
|
|
4278
4364
|
const allowedTools = [
|
|
4279
4365
|
...invocation.allowRepoWrite ? EDIT_TOOLS : READ_ONLY_TOOLS2,
|
|
4280
|
-
...
|
|
4366
|
+
...trustedTools
|
|
4281
4367
|
];
|
|
4282
4368
|
let sessionId = invocation.resumeSessionId;
|
|
4283
4369
|
let costUsd = 0;
|
|
@@ -4295,8 +4381,8 @@ function createClaudeProvider(config, session) {
|
|
|
4295
4381
|
...roleConfig.effort ? { effort: roleConfig.effort } : {},
|
|
4296
4382
|
tools: [...allowedTools],
|
|
4297
4383
|
...hostTools.length > 0 ? { mcpServers: { engine: createSdkMcpServer({ name: "engine", tools: hostTools }) } } : {},
|
|
4298
|
-
hooks: buildToolPolicyHooks(invocation.cwd),
|
|
4299
|
-
canUseTool: createToolPermissionCallback(invocation.cwd),
|
|
4384
|
+
hooks: buildToolPolicyHooks(invocation.cwd, trustedTools),
|
|
4385
|
+
canUseTool: createToolPermissionCallback(invocation.cwd, trustedTools),
|
|
4300
4386
|
maxTurns: roleConfig.maxTurns,
|
|
4301
4387
|
...invocation.resumeSessionId ? { resume: invocation.resumeSessionId } : {},
|
|
4302
4388
|
settingSources: []
|
|
@@ -4798,8 +4884,8 @@ async function fetchSuggestions(config, session, statuses) {
|
|
|
4798
4884
|
}
|
|
4799
4885
|
function isSuggestionRecord(value) {
|
|
4800
4886
|
if (typeof value !== "object" || value === null) return false;
|
|
4801
|
-
const
|
|
4802
|
-
return typeof
|
|
4887
|
+
const record2 = value;
|
|
4888
|
+
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
4889
|
}
|
|
4804
4890
|
async function markSuggestionIntegrated(config, session, id, target9, repoFingerprint2) {
|
|
4805
4891
|
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.2",
|
|
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",
|