@posthog/wizard 2.74.1 → 2.76.0

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.
Files changed (119) hide show
  1. package/README.md +31 -17
  2. package/dist/README.md +10 -0
  3. package/dist/{add-mcp-server-to-clients-CBtInAoH.js → add-mcp-server-to-clients-Exddicp2.js} +5 -5
  4. package/dist/{add-mcp-server-to-clients-CBtInAoH.js.map → add-mcp-server-to-clients-Exddicp2.js.map} +1 -1
  5. package/dist/{agent-interface-lCIi_alS.js → agent-interface-CZWRgZMc.js} +204 -376
  6. package/dist/agent-interface-CZWRgZMc.js.map +1 -0
  7. package/dist/{agent-prompt-loader-CSxm81TM.js → agent-prompt-loader-BobuLDIg.js} +5 -5
  8. package/dist/{agent-prompt-loader-CSxm81TM.js.map → agent-prompt-loader-BobuLDIg.js.map} +1 -1
  9. package/dist/{agent-runner-DVrkOFDA.js → agent-runner-C_R1X_UT.js} +158 -45
  10. package/dist/agent-runner-C_R1X_UT.js.map +1 -0
  11. package/dist/{analytics-CeVMOExk.js → analytics-C_RyE8fz.js} +7 -4
  12. package/dist/analytics-C_RyE8fz.js.map +1 -0
  13. package/dist/analytics-DXvdQqyp.js +2 -0
  14. package/dist/{api-2nAofyKY.js → api-k5aGQpIZ.js} +3 -3
  15. package/dist/{api-2nAofyKY.js.map → api-k5aGQpIZ.js.map} +1 -1
  16. package/dist/bin.js +164 -4087
  17. package/dist/bin.js.map +1 -1
  18. package/dist/{bounded-fs-DmcmFPRq.js → bounded-fs-BHLy83N9.js} +3 -3
  19. package/dist/{bounded-fs-DmcmFPRq.js.map → bounded-fs-BHLy83N9.js.map} +1 -1
  20. package/dist/{ci-install-lwYLQ4Yw.js → ci-install-CXt4c41q.js} +4 -4
  21. package/dist/{ci-install-lwYLQ4Yw.js.map → ci-install-CXt4c41q.js.map} +1 -1
  22. package/dist/{debug-BvheXnxB.js → debug-BxMMpepn.js} +1 -1
  23. package/dist/{debug-GxjVplPx.js → debug-C5Zz8hmm.js} +6 -4
  24. package/dist/debug-C5Zz8hmm.js.map +1 -0
  25. package/dist/{defaults-DI642M3n.js → defaults-DulzMJpN.js} +1 -1
  26. package/dist/{defaults-DI642M3n.js.map → defaults-DulzMJpN.js.map} +1 -1
  27. package/dist/{env-api-key-BMXH7_4u.js → env-api-key-Ca-CrkgV.js} +2 -2
  28. package/dist/{env-api-key-BMXH7_4u.js.map → env-api-key-Ca-CrkgV.js.map} +1 -1
  29. package/dist/{environment-C3nfPBu-.js → environment-B0uTamFf.js} +4 -4
  30. package/dist/{environment-C3nfPBu-.js.map → environment-B0uTamFf.js.map} +1 -1
  31. package/dist/file-CNstcW8F.js +60 -0
  32. package/dist/file-CNstcW8F.js.map +1 -0
  33. package/dist/{file-watcher-BGElIpJi.js → file-watcher-CiT-oZpu.js} +2 -2
  34. package/dist/{file-watcher-BGElIpJi.js.map → file-watcher-CiT-oZpu.js.map} +1 -1
  35. package/dist/gateway-session-BIVN_Xfr.js +2 -0
  36. package/dist/gateway-session-C3Uqwc4R.js +271 -0
  37. package/dist/gateway-session-C3Uqwc4R.js.map +1 -0
  38. package/dist/{headless-ui-jkS2Dm_G.js → headless-ui-TpSp-F43.js} +10 -5
  39. package/dist/headless-ui-TpSp-F43.js.map +1 -0
  40. package/dist/{interactive-CrQWwMr2.js → interactive-B72L-sO5.js} +2 -2
  41. package/dist/{interactive-CrQWwMr2.js.map → interactive-B72L-sO5.js.map} +1 -1
  42. package/dist/{mcp-S3yX8Uz1.js → mcp-CNc6CLK0.js} +2 -2
  43. package/dist/{mcp-S3yX8Uz1.js.map → mcp-CNc6CLK0.js.map} +1 -1
  44. package/dist/{mcp-prompt-streaming-BvvCZBYa.js → mcp-prompt-streaming-BvBNkzjv.js} +6 -5
  45. package/dist/{mcp-prompt-streaming-BvvCZBYa.js.map → mcp-prompt-streaming-BvBNkzjv.js.map} +1 -1
  46. package/dist/mint-failure-CZ5FZ0Jd.js +5739 -0
  47. package/dist/mint-failure-CZ5FZ0Jd.js.map +1 -0
  48. package/dist/{non-interactive-B-MIn3vt.js → non-interactive-UeQlnU0U.js} +2 -2
  49. package/dist/{non-interactive-B-MIn3vt.js.map → non-interactive-UeQlnU0U.js.map} +1 -1
  50. package/dist/{orchestrator-tools-CefodRf2.js → orchestrator-tools-CLADku26.js} +5 -5
  51. package/dist/orchestrator-tools-CLADku26.js.map +1 -0
  52. package/dist/{package-manager-pB6S0gyu.js → package-manager-CppzeIgu.js} +2 -2
  53. package/dist/{package-manager-pB6S0gyu.js.map → package-manager-CppzeIgu.js.map} +1 -1
  54. package/dist/{pi-Dbw8VakI.js → pi-Bs1Tbt0C.js} +35 -24
  55. package/dist/pi-Bs1Tbt0C.js.map +1 -0
  56. package/dist/{playground-C7DwGktN.js → playground-CZgZX4j0.js} +8 -8
  57. package/dist/{playground-C7DwGktN.js.map → playground-CZgZX4j0.js.map} +1 -1
  58. package/dist/{posthog-B3RuLfxH.js → posthog-BZ3NNCyh.js} +2 -2
  59. package/dist/{posthog-B3RuLfxH.js.map → posthog-BZ3NNCyh.js.map} +1 -1
  60. package/dist/{provisioning-B4Lr3-aS.js → provisioning-Ba7GWKzK.js} +3 -3
  61. package/dist/{provisioning-B4Lr3-aS.js.map → provisioning-Ba7GWKzK.js.map} +1 -1
  62. package/dist/{queue-tools-BveMVXQm.js → queue-tools-D09PNS0X.js} +17 -5
  63. package/dist/queue-tools-D09PNS0X.js.map +1 -0
  64. package/dist/{registry-D8MIZ9kJ.js → registry-COcpEsMD.js} +5 -5
  65. package/dist/{registry-D8MIZ9kJ.js.map → registry-COcpEsMD.js.map} +1 -1
  66. package/dist/{security-B_Eo15wD.js → security-CpAT0fZa.js} +5 -5
  67. package/dist/{security-B_Eo15wD.js.map → security-CpAT0fZa.js.map} +1 -1
  68. package/dist/{setup-utils-D3OK2RE_.js → setup-utils-BUAoRjw3.js} +48 -9
  69. package/dist/setup-utils-BUAoRjw3.js.map +1 -0
  70. package/dist/smoke-test-ci.sh +9 -14
  71. package/dist/{start-tui-B7u962uh.js → start-tui-Bz23_yym.js} +848 -127
  72. package/dist/start-tui-Bz23_yym.js.map +1 -0
  73. package/dist/{steps-D4mHkYX4.js → steps-DbsDish5.js} +7 -7
  74. package/dist/{steps-D4mHkYX4.js.map → steps-DbsDish5.js.map} +1 -1
  75. package/dist/{store-Cn1VK6U0.js → store-D5ROpUje.js} +63 -10
  76. package/dist/store-D5ROpUje.js.map +1 -0
  77. package/dist/{subagent-1UEmD7bE.js → subagent-CSBZnhgK.js} +2 -2
  78. package/dist/{subagent-1UEmD7bE.js.map → subagent-CSBZnhgK.js.map} +1 -1
  79. package/dist/{task-DNHNZMO0.js → task-D8sTR7jZ.js} +23 -23
  80. package/dist/task-D8sTR7jZ.js.map +1 -0
  81. package/dist/{task-stream-BJrRzR9K.js → task-stream-Dl0XEsZ9.js} +49 -7
  82. package/dist/task-stream-Dl0XEsZ9.js.map +1 -0
  83. package/dist/{tasks-DSEJ8GZC.js → tasks-BDt427kT.js} +2 -2
  84. package/dist/{tasks-DSEJ8GZC.js.map → tasks-BDt427kT.js.map} +1 -1
  85. package/dist/{telemetry-DWkTy6TJ.js → telemetry-Dp6zjZBj.js} +3 -3
  86. package/dist/{telemetry-DWkTy6TJ.js.map → telemetry-Dp6zjZBj.js.map} +1 -1
  87. package/dist/{terminal-DNnNkvFu.js → terminal-pInlleVD.js} +20 -18
  88. package/dist/{terminal-DNnNkvFu.js.map → terminal-pInlleVD.js.map} +1 -1
  89. package/dist/{tools-HHK4XbnC.js → tools-MkyMOjQI.js} +98 -28
  90. package/dist/tools-MkyMOjQI.js.map +1 -0
  91. package/dist/{wizard-abort-BVmjqUAQ.js → wizard-abort-BCnUgT9z.js} +4 -4
  92. package/dist/{wizard-abort-BVmjqUAQ.js.map → wizard-abort-BCnUgT9z.js.map} +1 -1
  93. package/dist/{wizard-abort-CG_xzEFN.js → wizard-abort-BHLKVKLN.js} +1 -1
  94. package/dist/{wizard-session-BWAQoi9q.js → wizard-session-CYvyYnti.js} +1 -1
  95. package/dist/{wizard-session-GdDN_aMS.js → wizard-session-FSJCkTcP.js} +9 -2
  96. package/dist/wizard-session-FSJCkTcP.js.map +1 -0
  97. package/dist/{yara-hooks-qRkdjYiV.js → yara-hooks-BJWVgTpU.js} +4 -4
  98. package/dist/{yara-hooks-qRkdjYiV.js.map → yara-hooks-BJWVgTpU.js.map} +1 -1
  99. package/dist/yara-hooks-Dp82xJGd.js +2 -0
  100. package/package.json +1 -1
  101. package/dist/agent-interface-lCIi_alS.js.map +0 -1
  102. package/dist/agent-runner-DVrkOFDA.js.map +0 -1
  103. package/dist/analytics-3H_AlflZ.js +0 -2
  104. package/dist/analytics-CeVMOExk.js.map +0 -1
  105. package/dist/debug-GxjVplPx.js.map +0 -1
  106. package/dist/headless-ui-jkS2Dm_G.js.map +0 -1
  107. package/dist/orchestrator-tools-CefodRf2.js.map +0 -1
  108. package/dist/pi-Dbw8VakI.js.map +0 -1
  109. package/dist/posthog-integration-su2LXi3-.js +0 -1475
  110. package/dist/posthog-integration-su2LXi3-.js.map +0 -1
  111. package/dist/queue-tools-BveMVXQm.js.map +0 -1
  112. package/dist/setup-utils-D3OK2RE_.js.map +0 -1
  113. package/dist/start-tui-B7u962uh.js.map +0 -1
  114. package/dist/store-Cn1VK6U0.js.map +0 -1
  115. package/dist/task-DNHNZMO0.js.map +0 -1
  116. package/dist/task-stream-BJrRzR9K.js.map +0 -1
  117. package/dist/tools-HHK4XbnC.js.map +0 -1
  118. package/dist/wizard-session-GdDN_aMS.js.map +0 -1
  119. package/dist/yara-hooks-BOMbtTOr.js +0 -2
@@ -1,12 +1,13 @@
1
1
  import { n as __require } from "./rolldown-runtime-B_-DWIq7.js";
2
- import { c as RESUME_INSTRUCTION, d as ErrorCodes, l as classifyAuthFailure, o as AgentSignals, s as REMARK_INSTRUCTION } from "./errors--OqLxmWR.js";
3
- import { E as AWS_SKILLS_BASE_URL, O as CONTEXT_MILL_URL, a as getLogFilePath, bt as runtimeEnv, f as getUI, mt as wizardUserAgentForProgram, o as initLogFile, r as debug, s as logToFile, ut as WIZARD_REMARK_EVENT_NAME, y as checkLlmGatewayHealth } from "./debug-GxjVplPx.js";
4
- import { n as ADDITIONAL_FEATURE_PROMPTS } from "./wizard-session-GdDN_aMS.js";
5
- import { n as analytics } from "./analytics-CeVMOExk.js";
6
- import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-DmcmFPRq.js";
7
- import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-BVmjqUAQ.js";
8
- import { b as coerceAuditChecks, g as AUDIT_CHECKS_FILE, l as prewarmYaraScanner, n as createPostToolUseYaraHooks, p as scanInstalledSkill, r as createPreToolUseYaraHooks } from "./yara-hooks-qRkdjYiV.js";
9
- import { S as writeJsonAtomic, b as triageModelFor, c as buildOrchestratorTools, v as modelCapabilities, x as makeMutex } from "./queue-tools-BveMVXQm.js";
2
+ import { c as RESUME_INSTRUCTION, l as classifyAuthFailure, o as AgentSignals, s as REMARK_INSTRUCTION } from "./errors--OqLxmWR.js";
3
+ import { D as AWS_SKILLS_BASE_URL, a as getLogFilePath, dt as WIZARD_REMARK_EVENT_NAME, ht as wizardUserAgentForProgram, k as CONTEXT_MILL_URL, o as initLogFile, p as getUI, r as debug, s as logToFile, xt as runtimeEnv } from "./debug-C5Zz8hmm.js";
4
+ import { n as ADDITIONAL_FEATURE_PROMPTS } from "./wizard-session-FSJCkTcP.js";
5
+ import { n as analytics } from "./analytics-C_RyE8fz.js";
6
+ import { a as safeReadFile, i as readProjectFile, o as walkProjectFiles } from "./bounded-fs-BHLy83N9.js";
7
+ import { i as wizardAbort, n as registerCleanup, t as WizardError } from "./wizard-abort-BCnUgT9z.js";
8
+ import { o as gatewayAuth, r as buildWizardPropertiesBlob, s as isPastRefresh } from "./gateway-session-C3Uqwc4R.js";
9
+ import { b as coerceAuditChecks, g as AUDIT_CHECKS_FILE, l as prewarmYaraScanner, n as createPostToolUseYaraHooks, p as scanInstalledSkill, r as createPreToolUseYaraHooks } from "./yara-hooks-BJWVgTpU.js";
10
+ import { C as writeJsonAtomic, S as makeMutex, l as buildOrchestratorTools, x as triageModelFor, y as modelCapabilities } from "./queue-tools-D09PNS0X.js";
10
11
  import { createRequire } from "node:module";
11
12
  import * as fs$1 from "fs";
12
13
  import fs from "fs";
@@ -191,303 +192,6 @@ function createCustomHeaders() {
191
192
  };
192
193
  }
193
194
  //#endregion
194
- //#region src/lib/legacy-gateway.ts
195
- let enabled = false;
196
- /** Only the `--ci` runner turns this on. */
197
- function setLegacyGatewayFallback(on) {
198
- enabled = on;
199
- }
200
- /** Legacy auth when the fallback is on and the mint cannot serve this credential (401, or 404 without the endpoint). */
201
- function legacyGatewayAuth(host, accessToken, mintStatus) {
202
- if (!enabled || mintStatus !== 401 && mintStatus !== 404) return void 0;
203
- return {
204
- gatewayUrl: legacyGatewayUrl(host),
205
- token: accessToken,
206
- legacy: true,
207
- refreshAtMs: Number.POSITIVE_INFINITY
208
- };
209
- }
210
- function legacyGatewayUrl(host) {
211
- const { hostname } = new URL(host.apiHost);
212
- if (hostname === "host.docker.internal") return "http://host.docker.internal:3308/wizard";
213
- if (hostname === "localhost" || hostname === "127.0.0.1") return "http://localhost:3308/wizard";
214
- return `https://gateway.${host.region}.posthog.com/wizard`;
215
- }
216
- /** The legacy gateway reads per-key metadata and flag headers and needs an explicit Bedrock opt-in. */
217
- function legacyGatewayHeaders(wizardMetadata, wizardFlags) {
218
- const headers = { "x-posthog-use-bedrock-fallback": "true" };
219
- for (const [key, value] of Object.entries(wizardMetadata)) headers[key.startsWith("X-POSTHOG-PROPERTY-") ? key : `X-POSTHOG-PROPERTY-${key}`] = value;
220
- for (const [flagKey, variant] of Object.entries(wizardFlags)) {
221
- if (!flagKey.toLowerCase().startsWith("wizard")) continue;
222
- headers[`X-POSTHOG-FLAG-${flagKey.toUpperCase()}`] = variant;
223
- }
224
- return headers;
225
- }
226
- //#endregion
227
- //#region src/lib/gateway-session.ts
228
- /**
229
- * Gateway auth for a wizard run: a `phe_` scoped token the backend mints, with
230
- * pinned attribution, a spend cap and an expiry.
231
- *
232
- * Every mint failure throws, since a silent downgrade would spend uncapped,
233
- * unattributed money to hide an outage. The CI-only exception lives in
234
- * legacy-gateway.ts.
235
- */
236
- let cached = null;
237
- /**
238
- * Shared so concurrent callers mint once. The orchestrator starts every runnable
239
- * task at once, and each would otherwise take its own token and its own cap.
240
- */
241
- let inFlight = null;
242
- /**
243
- * Adoption floor. The anthropic subprocess holds its credential until a 401
244
- * forces a re-mint, so a token below this would churn mints.
245
- */
246
- const MIN_USABLE_TTL_MS = 120 * 1e3;
247
- /** Re-resolve at this fraction of the token's life, leaving a usable remainder. */
248
- const REFRESH_AT_FRACTION = .8;
249
- const MINT_TIMEOUT_MS = 2e4;
250
- /** Longer than any refusal the mint writes; a body past this is not a message. */
251
- const MAX_REFUSAL_DETAIL_LENGTH = 500;
252
- /** Outcomes are short snake_case labels; anything longer is not one. */
253
- const MAX_REFUSAL_OUTCOME_LENGTH = 64;
254
- /** Resolve this run's gateway auth, minting and re-minting near expiry. */
255
- async function gatewayAuth(host, accessToken, program) {
256
- const key = `${host.apiHost}\n${accessToken}\n${program ?? ""}`;
257
- if (cached && cached.key === key && Date.now() < cached.staleAtMs) return cached.auth;
258
- if (inFlight && inFlight.key === key) return inFlight.promise;
259
- const promise = resolveGatewayAuth(host, accessToken, key, program);
260
- inFlight = {
261
- key,
262
- promise
263
- };
264
- try {
265
- return await promise;
266
- } finally {
267
- if (inFlight?.promise === promise) inFlight = null;
268
- }
269
- }
270
- async function resolveGatewayAuth(host, accessToken, key, program) {
271
- if (!program) {
272
- logToFile("[gateway] run has no program id; failing the run");
273
- throw new GatewayMintFailed("this run has no program to attribute its spend to");
274
- }
275
- let minted;
276
- try {
277
- minted = await mintGatewayToken(host, accessToken, program);
278
- } catch (e) {
279
- if (!(e instanceof GatewayMintRefused)) throw e;
280
- const legacy = legacyGatewayAuth(host, accessToken, e.status);
281
- if (!legacy) throw e;
282
- logToFile(`[gateway] mint refused this credential (HTTP ${e.status}); CI run staying on the legacy gateway`);
283
- cached = {
284
- key,
285
- auth: legacy,
286
- staleAtMs: legacy.refreshAtMs
287
- };
288
- return legacy;
289
- }
290
- if ((await checkLlmGatewayHealth(minted.gatewayUrl)).status !== "healthy") throw new WizardError("The PostHog AI gateway is unavailable. Please try again later.", void 0, ErrorCodes.EnvServiceOutage);
291
- const expiresAtMs = Date.parse(minted.expiresAt);
292
- const ttlMs = expiresAtMs - Date.now();
293
- if (!Number.isFinite(expiresAtMs) || ttlMs < MIN_USABLE_TTL_MS) {
294
- logToFile(`[gateway] mint returned a token with ${ttlMs}ms of life; failing the run`);
295
- throw new GatewayMintFailed(`the PostHog gateway issued a token with ${ttlMs}ms of life`);
296
- }
297
- const staleAtMs = Date.now() + ttlMs * REFRESH_AT_FRACTION;
298
- logToFile(`[gateway] minted a scoped token: program=${program} team=${minted.teamId ?? "unknown"} ttl=${Math.round(ttlMs / 1e3)}s url=${minted.gatewayUrl}`);
299
- const auth = {
300
- gatewayUrl: minted.gatewayUrl,
301
- token: minted.token,
302
- teamId: minted.teamId,
303
- refreshAtMs: staleAtMs
304
- };
305
- cached = {
306
- key,
307
- auth,
308
- staleAtMs
309
- };
310
- return auth;
311
- }
312
- /** Whether a 401 on this bearer may be age (past its refresh instant) rather than a bad credential. */
313
- function isPastRefresh(auth, now = Date.now()) {
314
- return now >= auth.refreshAtMs;
315
- }
316
- /**
317
- * Whether a server-supplied origin may receive a bearer and prompt content:
318
- * https (loopback excepted), and either a posthog.com host or the one the run
319
- * authenticated against.
320
- */
321
- function isTrustedGatewayUrl(value, apiHost) {
322
- let url;
323
- try {
324
- url = new URL(value);
325
- } catch {
326
- return false;
327
- }
328
- if (url.pathname !== "/" || url.search || url.hash || url.username || url.password) return false;
329
- if (url.hostname === "localhost" || url.hostname === "127.0.0.1" || url.hostname === "host.docker.internal") return true;
330
- if (url.protocol !== "https:") return false;
331
- if (url.hostname.endsWith(".posthog.com")) return true;
332
- try {
333
- return url.hostname === new URL(apiHost).hostname;
334
- } catch {
335
- return false;
336
- }
337
- }
338
- /**
339
- * A deliberate refusal from the mint endpoint, as opposed to the mint being
340
- * unavailable. Thrown so the run stops instead of proceeding without the
341
- * controls the refusal was enforcing. A WizardError, so the runners print its
342
- * message as-is and `wizardAbort` resolves its code.
343
- */
344
- var GatewayMintRefused = class extends WizardError {
345
- status;
346
- /** The backend's refusal outcome (`blocked`, `throttled`, ...), when it sent one. */
347
- outcome;
348
- constructor(status, message, outcome) {
349
- super(message, {
350
- status,
351
- outcome
352
- }, ErrorCodes.GatewayMintRefused);
353
- this.name = "GatewayMintRefused";
354
- this.status = status;
355
- this.outcome = outcome;
356
- }
357
- };
358
- /**
359
- * The mint could not produce a usable credential: unreachable, a 5xx, or a
360
- * response the client cannot use.
361
- */
362
- var GatewayMintFailed = class extends WizardError {
363
- constructor(message) {
364
- super(message, void 0, ErrorCodes.GatewayMintFailed);
365
- this.name = "GatewayMintFailed";
366
- }
367
- };
368
- /**
369
- * Whether a mint status means "refused this run" rather than "not available".
370
- * 429 the daily run limit, 403 revoked project access, 400 a login covering
371
- * more than one project, 401 a credential the mint does not accept, 404 an
372
- * instance without the mint endpoint.
373
- */
374
- function isMintRefusal(status) {
375
- return status === 400 || status === 401 || status === 403 || status === 404 || status === 429;
376
- }
377
- /**
378
- * The server's own reason for a refusal, when it sent one. DRF answers every
379
- * refusal as `{"detail": "...", "code": "<outcome>"}`; the blocklist's detail
380
- * names the contact address, which the fixed messages below cannot. `code` is
381
- * the backend's own label for the refusal (`outcome` on older backends) and
382
- * rides the client event.
383
- */
384
- function cleanRefusalText(value) {
385
- if (typeof value !== "string") return "";
386
- return value.replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").trim();
387
- }
388
- async function readRefusal(resp) {
389
- try {
390
- const body = await resp.json();
391
- const detail = cleanRefusalText(body?.detail);
392
- const outcome = cleanRefusalText(body?.code) || cleanRefusalText(body?.outcome);
393
- return {
394
- detail: detail.length > 0 && detail.length <= MAX_REFUSAL_DETAIL_LENGTH ? detail : void 0,
395
- outcome: outcome.length > 0 && outcome.length <= MAX_REFUSAL_OUTCOME_LENGTH ? outcome : void 0
396
- };
397
- } catch {
398
- return {};
399
- }
400
- }
401
- function mintRefusalMessage(status, detail) {
402
- if (detail) return detail;
403
- switch (status) {
404
- case 429: return "This wizard program has used its daily run limit. Try again tomorrow.";
405
- case 403: return "Your access to this project has changed. Re-authenticate and try again.";
406
- case 400: return "Your PostHog login must cover exactly one project. Re-authenticate and try again.";
407
- case 401: return "PostHog did not accept this login. Re-authenticate with `npx @posthog/wizard@latest`.";
408
- case 404: return "This PostHog instance does not issue gateway tokens. Upgrade with `npx @posthog/wizard@latest` and try again.";
409
- default: return "The PostHog gateway refused this run.";
410
- }
411
- }
412
- async function mintGatewayToken(host, accessToken, program) {
413
- try {
414
- const resp = await fetch(`${host.apiHost}/api/wizard/gateway_token/`, {
415
- method: "POST",
416
- headers: {
417
- Authorization: `Bearer ${accessToken}`,
418
- "Content-Type": "application/json"
419
- },
420
- body: JSON.stringify({
421
- program,
422
- reads_refusal_reason: true
423
- }),
424
- signal: AbortSignal.timeout(MINT_TIMEOUT_MS)
425
- });
426
- if (!resp.ok) {
427
- if (isMintRefusal(resp.status)) {
428
- const refusal = await readRefusal(resp);
429
- logToFile(`[gateway] mint refused with HTTP ${resp.status} (${refusal.outcome ?? "no outcome"}); failing the run`);
430
- analytics.wizardCapture("gateway mint refused", {
431
- status: resp.status,
432
- outcome: refusal.outcome,
433
- program
434
- });
435
- throw new GatewayMintRefused(resp.status, mintRefusalMessage(resp.status, refusal.detail), refusal.outcome);
436
- }
437
- logToFile(`[gateway] mint failed with HTTP ${resp.status}; failing the run`);
438
- throw new GatewayMintFailed(`the PostHog gateway could not issue a token (HTTP ${resp.status})`);
439
- }
440
- const body = await resp.json();
441
- if (!body.token) {
442
- logToFile("[gateway] mint response omitted token; failing the run");
443
- throw new GatewayMintFailed("mint response omitted token");
444
- }
445
- if (!body.expires_at) {
446
- logToFile("[gateway] mint response omitted expires_at; failing the run");
447
- throw new GatewayMintFailed("mint response omitted expires_at");
448
- }
449
- if (!body.gateway_url) {
450
- logToFile("[gateway] mint response omitted gateway_url; failing the run");
451
- throw new GatewayMintFailed("mint response omitted gateway_url");
452
- }
453
- if (!isTrustedGatewayUrl(body.gateway_url, host.apiHost)) {
454
- logToFile("[gateway] mint returned an untrusted gateway url; failing the run");
455
- throw new GatewayMintFailed("mint returned an untrusted gateway url");
456
- }
457
- return {
458
- token: body.token,
459
- expiresAt: body.expires_at,
460
- gatewayUrl: body.gateway_url.replace(/\/+$/, ""),
461
- teamId: body.team_id
462
- };
463
- } catch (e) {
464
- if (e instanceof GatewayMintRefused || e instanceof GatewayMintFailed) throw e;
465
- logToFile(`[gateway] mint transport failure (${String(e)}); failing the run`);
466
- throw new GatewayMintFailed(`could not reach the PostHog gateway (${String(e)})`);
467
- }
468
- }
469
- /**
470
- * The v2 run-metadata carrier: one JSON blob for the `X-PostHog-Properties`
471
- * header. Plain keys only, since the gateway strips `$`-prefixed keys as reserved,
472
- * so feature-flag variants land as `wizard_flag_<key>` instead of the legacy
473
- * `$feature/<key>` (dashboards keying on `$feature/wizard-*` read the new key
474
- * post-cutover).
475
- */
476
- function buildWizardPropertiesBlob(wizardMetadata, wizardFlags, teamId) {
477
- const props = { ai_product: "wizard" };
478
- if (teamId !== void 0) props.team_id = teamId;
479
- for (const [key, value] of Object.entries(wizardMetadata)) props[stripPropertyPrefix(key)] = value;
480
- for (const [flagKey, variant] of Object.entries(wizardFlags)) {
481
- if (!flagKey.toLowerCase().startsWith("wizard")) continue;
482
- props[`wizard_flag_${flagKey.toLowerCase()}`] = variant;
483
- }
484
- return JSON.stringify(props);
485
- }
486
- const LEGACY_PROPERTY_PREFIX = "X-POSTHOG-PROPERTY-";
487
- function stripPropertyPrefix(key) {
488
- return key.toUpperCase().startsWith(LEGACY_PROPERTY_PREFIX) ? key.slice(19).toLowerCase() : key;
489
- }
490
- //#endregion
491
195
  //#region src/lib/safe-tools.ts
492
196
  const LINTING_TOOLS = [
493
197
  "codespell",
@@ -999,6 +703,13 @@ function evaluateBashCommand(rawCommand) {
999
703
  */
1000
704
  /** Sentinel returned for unanswered fields on cancellation or timeout. */
1001
705
  const CANCELLED_SENTINEL = "__cancelled__";
706
+ /**
707
+ * The longer per-question timeout, for asks that send the user on an errand —
708
+ * open a database console, mint a restricted API key. The default above is
709
+ * sized for a question answerable from memory and expires long before an
710
+ * errand is done.
711
+ */
712
+ const LONGER_ASK_TIMEOUT_MS = 1200 * 1e3;
1002
713
  function buildCancelledAnswers(questions) {
1003
714
  const out = {};
1004
715
  for (const q of questions) out[q.id] = CANCELLED_SENTINEL;
@@ -1021,8 +732,10 @@ function createWizardAskBridge(opts) {
1021
732
  };
1022
733
  const startedAt = Date.now();
1023
734
  let timer;
735
+ let timedOut = false;
1024
736
  const timeoutPromise = new Promise((resolve) => {
1025
737
  timer = setTimeout(() => {
738
+ timedOut = true;
1026
739
  opts.cancelQuestion?.();
1027
740
  resolve(buildCancelledAnswers(questions));
1028
741
  }, timeoutMs);
@@ -1035,7 +748,7 @@ function createWizardAskBridge(opts) {
1035
748
  subject,
1036
749
  question_count: questions.length,
1037
750
  duration_ms: durationMs,
1038
- timed_out: durationMs >= timeoutMs
751
+ timed_out: timedOut
1039
752
  });
1040
753
  else analytics.wizardCapture("wizard_ask answered", {
1041
754
  source: pending.source,
@@ -1043,7 +756,10 @@ function createWizardAskBridge(opts) {
1043
756
  question_count: questions.length,
1044
757
  duration_ms: durationMs
1045
758
  });
1046
- return answers;
759
+ return {
760
+ answers,
761
+ timedOut
762
+ };
1047
763
  } finally {
1048
764
  if (timer) clearTimeout(timer);
1049
765
  }
@@ -1327,6 +1043,31 @@ function normaliseAskSubject(subject) {
1327
1043
  return trimmed.slice(0, ASK_SUBJECT_MAX_LENGTH);
1328
1044
  }
1329
1045
  /**
1046
+ * The `wizard_ask` `kind` field description, shared by both harness facades so
1047
+ * the inferred default cannot drift between them.
1048
+ */
1049
+ const WIZARD_ASK_KIND_DESCRIPTION = "'single' = pick one option, 'multi' = pick any, 'text' = free-form single-line answer. Optional: omit it and the kind follows the question — 'single' when you pass options, 'text' when you do not, which is what a credential question wants.";
1050
+ /**
1051
+ * Fill in the `kind` of every question that arrived without one.
1052
+ *
1053
+ * The field used to be required, and both harnesses validate a call before this
1054
+ * handler sees it — pi against the typebox schema, the MCP SDK against the zod
1055
+ * one — so a call that omitted it was rejected upstream and the agent spent a
1056
+ * turn recovering from a validation error rather than asking its question.
1057
+ * Accepting the omission costs nothing, because `options` already says which
1058
+ * kind was meant, and a question carrying none is free text — which is what a
1059
+ * credential question wants, and the case an agent is most likely to send bare.
1060
+ *
1061
+ * Options imply `single`, never `multi`: omitting `kind` expresses no intent to
1062
+ * accept more than one answer.
1063
+ */
1064
+ function resolveAskQuestionKinds(questions) {
1065
+ return questions.map((q) => ({
1066
+ ...q,
1067
+ kind: q.kind ?? (q.options && q.options.length > 0 ? "single" : "text")
1068
+ }));
1069
+ }
1070
+ /**
1330
1071
  * The `wizard_ask` `sensitive` field description, shared by both harness facades
1331
1072
  * (the zod schema in `./mcp` and the typebox mirror in `harness/pi/tools.ts`) so
1332
1073
  * the secret-handling guidance cannot drift between them — the same discipline
@@ -1355,7 +1096,43 @@ const WIZARD_ASK_SUBJECT_DESCRIPTION = "Short, stable tag naming what this call
1355
1096
  * server in `./mcp` and the pi-native mirror in `harness/pi/tools.ts`) so the
1356
1097
  * batching and cancellation contract reads identically in both harnesses.
1357
1098
  */
1358
- const WIZARD_ASK_TOOL_DESCRIPTION = "Ask the user one or more structured questions and wait for their answers. Use this whenever you would otherwise inline a question in your text output. Batch every question about one subject into a single call (up to 8) rather than asking one at a time, and tag the call with `subject`. Walking a list — one call per data-warehouse source, one call per integration step — is expected and is never blocked, because the batching guard counts consecutive calls per subject. A fully cancelled or timed-out response does NOT count against the per-run cap — treat it as \"the user declined\" and fall back gracefully (e.g. hand over a deep link) without worrying about a wasted call.";
1099
+ const WIZARD_ASK_TOOL_DESCRIPTION = "Ask the user one or more structured questions and wait for their answers. Use this whenever you would otherwise inline a question in your text output. Batch every question about one subject into a single call (up to 8) rather than asking one at a time, and tag the call with `subject`. Walking a list — one call per data-warehouse source, one call per integration step — is expected and is never blocked, because the batching guard counts consecutive calls per subject. A fully cancelled or timed-out response does NOT count against the per-run cap, so nothing is wasted. When a field is not collected the result carries a `cancelled` object naming those question ids, whether the user dismissed the prompt or it timed out, and what to do next: read that instead of inspecting the answer values, and fall back gracefully (e.g. hand over a deep link) rather than re-asking.";
1100
+ /**
1101
+ * Guidance returned with a `wizard_ask` result when the user dismissed the
1102
+ * prompt. Shared by both harness facades, like the descriptions above.
1103
+ *
1104
+ * Deliberately says nothing about how the caller reports its own outcome:
1105
+ * `wizard_ask` serves programs with no task queue as well as the orchestrator's
1106
+ * seeded ones, so the note covers the ask and only the ask.
1107
+ */
1108
+ const ASK_CANCELLED_NOTE = "The user dismissed this prompt, so none of these fields were collected. Read it as a decline for this subject: do not re-ask the same questions. Fall back to a route that needs no answer from them (for example, hand them a link to finish it themselves) and carry on with the rest of your work. Asking about a different subject is still fine.";
1109
+ /**
1110
+ * Guidance returned with a `wizard_ask` result when the prompt timed out.
1111
+ *
1112
+ * A timeout is not one decline: it says nobody is reading the terminal, and
1113
+ * every later prompt in the run will end the same way after the same wait.
1114
+ * Naming that is the difference between falling back once and stopping the run
1115
+ * behind one unattended prompt per remaining item.
1116
+ */
1117
+ const ASK_TIMED_OUT_NOTE = "This prompt timed out with no answer, so the user is most likely away from the terminal. Read it as a decline, and expect any further prompt in this run to time out the same way after the same wait: stop asking and finish without them — hand over links for everything still outstanding — rather than opening another prompt.";
1118
+ /**
1119
+ * Describe an ask's cancelled fields, or `undefined` when every question was
1120
+ * answered. `timedOut` comes from the ask bridge — it is the only thing that
1121
+ * tells a dismissed prompt from an unattended one.
1122
+ */
1123
+ function describeAskCancellation(answers, timedOut) {
1124
+ const questionIds = Object.entries(answers).filter(([, value]) => value === CANCELLED_SENTINEL).map(([id]) => id);
1125
+ if (questionIds.length === 0) return void 0;
1126
+ return timedOut ? {
1127
+ reason: "timed-out",
1128
+ questionIds,
1129
+ note: ASK_TIMED_OUT_NOTE
1130
+ } : {
1131
+ reason: "user-cancelled",
1132
+ questionIds,
1133
+ note: ASK_CANCELLED_NOTE
1134
+ };
1135
+ }
1359
1136
  /**
1360
1137
  * Pure decision function for the wizard_ask caps. Returns whether the
1361
1138
  * upcoming call should proceed and, if not, the message to surface to the
@@ -1538,6 +1315,34 @@ function templateEnvWriteRefusal(resolvedPath) {
1538
1315
  if (!isTemplateEnvFileName(name)) return null;
1539
1316
  return `Error: "${name}" is a committed template that documents key names, so it is not a valid target for set_env_values — a credential written there would be published with the repository. Write to .env or .env.local instead (it is created if missing). If you only mean to document the key name, edit the template directly.`;
1540
1317
  }
1318
+ /** Whole-word, so `NEXT_PUBLIC_POSTHOG_KEY` (`_` is a word character) does not count. */
1319
+ const LEGACY_KEY_USE = /\bPOSTHOG_KEY\b/;
1320
+ /** Files where a project reads or defines an env var: source, config, env, shell and build files. */
1321
+ const LEGACY_KEY_SCAN_FILE = /^(\.env.*|Dockerfile.*|Makefile|Procfile|package\.json|app\.json|eas\.json|.*\.[cm]?[jt]sx?|.*\.(vue|svelte|astro|py|rb|php|go|rs|exs?|java|kts?|swift|dart|cs|ya?ml|toml|sh|bash|gradle|properties|xcconfig|plist))$/;
1322
+ function projectReadsLegacyKey(workingDirectory) {
1323
+ let found = false;
1324
+ walkProjectFiles(workingDirectory, (name, fullPath) => {
1325
+ if (found || !LEGACY_KEY_SCAN_FILE.test(name)) return;
1326
+ const content = readProjectFile(fullPath);
1327
+ if (content !== null && LEGACY_KEY_USE.test(content)) found = true;
1328
+ }, 6);
1329
+ return found;
1330
+ }
1331
+ /**
1332
+ * `set_env_values`' refusal for the legacy `POSTHOG_KEY` name, or null. Shared
1333
+ * by both facades so the two cannot disagree about it.
1334
+ *
1335
+ * `POSTHOG_KEY` is the old name for the project token, so a new project gets
1336
+ * the canonical one (e.g. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN). A project whose
1337
+ * code already reads `POSTHOG_KEY` keeps it: refusing there forces a rename of
1338
+ * working code, and every deploy step and CI secret that still passes the old
1339
+ * name then starts the app with an empty token.
1340
+ */
1341
+ function legacyKeyNameRefusal(workingDirectory, keys) {
1342
+ const key = keys.find((k) => k.toUpperCase() === "POSTHOG_KEY");
1343
+ if (!key || projectReadsLegacyKey(workingDirectory)) return null;
1344
+ return `Error: "${key}" is not a valid PostHog env var name. Use the key name from your framework's integration guide (e.g. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN). POSTHOG_KEY is accepted only when the project already reads it.`;
1345
+ }
1541
1346
  /**
1542
1347
  * Escape a key before it is interpolated into the match regex below. The key
1543
1348
  * comes from the agent, and a stray metacharacter would otherwise build a
@@ -1685,6 +1490,47 @@ function readLedger(targetPath) {
1685
1490
  return [];
1686
1491
  }
1687
1492
  }
1493
+ const AUDIT_SEED_CHECKS_DESCRIPTION = "Seed the audit ledger at .posthog-audit-checks.json with the full set of pending checks. Call this once at the start of the audit. Atomically replaces any existing ledger.";
1494
+ const AUDIT_SEED_CHECKS_PARAM_DESCRIPTION = "Full pending checklist to write to the ledger";
1495
+ const AUDIT_ADD_CHECKS_DESCRIPTION = "Append one or more pending checks to the existing audit ledger at .posthog-audit-checks.json. Call audit_seed_checks first. Atomically rejects duplicate ids without changing the ledger.";
1496
+ const AUDIT_ADD_CHECKS_PARAM_DESCRIPTION = "Additional checks to append to the existing ledger";
1497
+ const AUDIT_RESOLVE_CHECKS_DESCRIPTION = "Resolve one or more audit checks by id. Patches each entry's status (and optional file/details) and writes the ledger back atomically. Concurrent calls serialize.";
1498
+ const AUDIT_RESOLVE_CHECKS_PARAM_DESCRIPTION = "Patches to apply, keyed by check id";
1499
+ /** The three ledger mutations. The caller owns serialization (a mutex). */
1500
+ function seedAuditChecks(targetPath, checks) {
1501
+ writeLedgerAtomic(targetPath, checks);
1502
+ return {
1503
+ ok: true,
1504
+ message: `Seeded ${checks.length} audit checks.`
1505
+ };
1506
+ }
1507
+ function addAuditChecks(targetPath, checks) {
1508
+ const result = appendAuditChecksToLedger(targetPath, checks);
1509
+ if (result.ok) return {
1510
+ ok: true,
1511
+ message: `Added ${result.added} audit check(s).`
1512
+ };
1513
+ if (result.reason === "missing-ledger") return {
1514
+ ok: false,
1515
+ message: "Error: audit ledger does not exist. Run audit_seed_checks first."
1516
+ };
1517
+ return {
1518
+ ok: false,
1519
+ message: `Error: duplicate check id(s): ${result.ids.join(", ")}. Check ids must be unique.`
1520
+ };
1521
+ }
1522
+ function resolveAuditChecks(targetPath, updates) {
1523
+ const { next, unknown } = applyAuditUpdates(readLedger(targetPath), updates);
1524
+ if (unknown.length > 0) return {
1525
+ ok: false,
1526
+ message: `Error: unknown check id(s): ${unknown.join(", ")}. Run audit_seed_checks first or check the id.`
1527
+ };
1528
+ writeLedgerAtomic(targetPath, next);
1529
+ return {
1530
+ ok: true,
1531
+ message: `Resolved ${updates.length} check(s).`
1532
+ };
1533
+ }
1688
1534
  function appendAuditChecksToLedger(targetPath, additions) {
1689
1535
  if (!fs.existsSync(targetPath)) return {
1690
1536
  ok: false,
@@ -1935,11 +1781,11 @@ async function createWizardToolsServer(options) {
1935
1781
  filePath: z.string().describe(ENV_FILE_PATH_DESCRIPTION),
1936
1782
  values: z.record(z.string(), z.union([z.string(), z.object({ secretRef: z.string() })])).describe("Key → (literal string OR { secretRef } pointing to a vaulted secret)")
1937
1783
  }, (args) => {
1938
- const forbidden = Object.keys(args.values).find((k) => k.toUpperCase() === "POSTHOG_KEY");
1939
- if (forbidden) return {
1784
+ const keyRefusal = legacyKeyNameRefusal(workingDirectory, Object.keys(args.values));
1785
+ if (keyRefusal) return {
1940
1786
  content: [{
1941
1787
  type: "text",
1942
- text: `Error: "${forbidden}" is not a valid PostHog env var name. Use the project-specific key name from your framework's integration guide (e.g. NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN).`
1788
+ text: keyRefusal
1943
1789
  }],
1944
1790
  isError: true
1945
1791
  };
@@ -2049,58 +1895,40 @@ async function createWizardToolsServer(options) {
2049
1895
  });
2050
1896
  const auditLedgerPath = path.join(workingDirectory, AUDIT_CHECKS_FILE);
2051
1897
  const auditMutex = makeMutex();
2052
- const auditSeedChecks = tool("audit_seed_checks", "Seed the audit ledger at .posthog-audit-checks.json with the full set of pending checks. Call this once at the start of the audit. Atomically replaces any existing ledger.", { checks: z.array(auditCheckSchema).describe("Full pending checklist to write to the ledger") }, async (args) => {
1898
+ const auditSeedChecks = tool("audit_seed_checks", AUDIT_SEED_CHECKS_DESCRIPTION, { checks: z.array(auditCheckSchema).describe(AUDIT_SEED_CHECKS_PARAM_DESCRIPTION) }, async (args) => {
2053
1899
  return auditMutex(() => {
2054
- writeLedgerAtomic(auditLedgerPath, args.checks);
1900
+ const result = seedAuditChecks(auditLedgerPath, args.checks);
2055
1901
  logToFile(`audit_seed_checks: wrote ${args.checks.length} entries`);
2056
1902
  return { content: [{
2057
1903
  type: "text",
2058
- text: `Seeded ${args.checks.length} audit checks.`
1904
+ text: result.message
2059
1905
  }] };
2060
1906
  });
2061
1907
  });
2062
- const auditAddChecks = tool("audit_add_checks", "Append one or more pending checks to the existing audit ledger at .posthog-audit-checks.json. Call audit_seed_checks first. Atomically rejects duplicate ids without changing the ledger.", { checks: z.array(auditCheckSchema).min(1).describe("Additional checks to append to the existing ledger") }, async (args) => {
1908
+ const auditAddChecks = tool("audit_add_checks", AUDIT_ADD_CHECKS_DESCRIPTION, { checks: z.array(auditCheckSchema).min(1).describe(AUDIT_ADD_CHECKS_PARAM_DESCRIPTION) }, async (args) => {
2063
1909
  return auditMutex(() => {
2064
- const result = appendAuditChecksToLedger(auditLedgerPath, args.checks);
2065
- if (!result.ok) {
2066
- if (result.reason === "missing-ledger") return {
2067
- content: [{
2068
- type: "text",
2069
- text: "Error: audit ledger does not exist. Run audit_seed_checks first."
2070
- }],
2071
- isError: true
2072
- };
2073
- return {
2074
- content: [{
2075
- type: "text",
2076
- text: `Error: duplicate check id(s): ${result.ids.join(", ")}. Check ids must be unique.`
2077
- }],
2078
- isError: true
2079
- };
2080
- }
2081
- logToFile(`audit_add_checks: added ${result.added} entries`);
2082
- return { content: [{
2083
- type: "text",
2084
- text: `Added ${result.added} audit check(s).`
2085
- }] };
1910
+ const result = addAuditChecks(auditLedgerPath, args.checks);
1911
+ logToFile(`audit_add_checks: ${result.message}`);
1912
+ return {
1913
+ content: [{
1914
+ type: "text",
1915
+ text: result.message
1916
+ }],
1917
+ ...result.ok ? {} : { isError: true }
1918
+ };
2086
1919
  });
2087
1920
  });
2088
- const auditResolveChecks = tool("audit_resolve_checks", "Resolve one or more audit checks by id. Patches each entry's status (and optional file/details) and writes the ledger back atomically. Concurrent calls serialize.", { updates: z.array(auditUpdateSchema).min(1).describe("Patches to apply, keyed by check id") }, async (args) => {
1921
+ const auditResolveChecks = tool("audit_resolve_checks", AUDIT_RESOLVE_CHECKS_DESCRIPTION, { updates: z.array(auditUpdateSchema).min(1).describe(AUDIT_RESOLVE_CHECKS_PARAM_DESCRIPTION) }, async (args) => {
2089
1922
  return auditMutex(() => {
2090
- const { next, unknown } = applyAuditUpdates(readLedger(auditLedgerPath), args.updates);
2091
- if (unknown.length > 0) return {
1923
+ const result = resolveAuditChecks(auditLedgerPath, args.updates);
1924
+ logToFile(`audit_resolve_checks: ${result.message}`);
1925
+ return {
2092
1926
  content: [{
2093
1927
  type: "text",
2094
- text: `Error: unknown check id(s): ${unknown.join(", ")}. Run audit_seed_checks first or check the id.`
1928
+ text: result.message
2095
1929
  }],
2096
- isError: true
1930
+ ...result.ok ? {} : { isError: true }
2097
1931
  };
2098
- writeLedgerAtomic(auditLedgerPath, next);
2099
- logToFile(`audit_resolve_checks: applied ${args.updates.length} update(s)`);
2100
- return { content: [{
2101
- type: "text",
2102
- text: `Resolved ${args.updates.length} check(s).`
2103
- }] };
2104
1932
  });
2105
1933
  });
2106
1934
  const askQuestionSchema = z.object({
@@ -2110,7 +1938,7 @@ async function createWizardToolsServer(options) {
2110
1938
  "single",
2111
1939
  "multi",
2112
1940
  "text"
2113
- ]).describe("'single' = pick one option, 'multi' = pick any, 'text' = free-form single-line answer"),
1941
+ ]).optional().describe(WIZARD_ASK_KIND_DESCRIPTION),
2114
1942
  options: z.array(z.object({
2115
1943
  label: z.string(),
2116
1944
  value: z.string(),
@@ -2159,7 +1987,8 @@ async function createWizardToolsServer(options) {
2159
1987
  isError: capDecision.reason !== "adjacency"
2160
1988
  };
2161
1989
  }
2162
- for (const q of args.questions) {
1990
+ const questions = resolveAskQuestionKinds(args.questions);
1991
+ for (const q of questions) {
2163
1992
  if ((q.kind === "single" || q.kind === "multi") && (!q.options || q.options.length === 0)) return {
2164
1993
  content: [{
2165
1994
  type: "text",
@@ -2176,7 +2005,7 @@ async function createWizardToolsServer(options) {
2176
2005
  };
2177
2006
  }
2178
2007
  const ids = /* @__PURE__ */ new Set();
2179
- for (const q of args.questions) {
2008
+ for (const q of questions) {
2180
2009
  if (ids.has(q.id)) return {
2181
2010
  content: [{
2182
2011
  type: "text",
@@ -2188,16 +2017,20 @@ async function createWizardToolsServer(options) {
2188
2017
  }
2189
2018
  askAccounting.record(args.subject);
2190
2019
  try {
2191
- const answers = await askBridge.request({
2192
- questions: args.questions,
2020
+ const { answers, timedOut } = await askBridge.request({
2021
+ questions,
2193
2022
  subject: normaliseAskSubject(args.subject)
2194
2023
  });
2195
2024
  if (isFullyCancelled(answers)) askAccounting.refund(args.subject);
2196
- const sanitised = vaultSensitiveAnswers(args.questions, answers, secretVault);
2197
- logToFile(`wizard_ask: resolved ${Object.keys(answers).length} answer(s) for ${args.questions.length} question(s)`);
2025
+ const sanitised = vaultSensitiveAnswers(questions, answers, secretVault);
2026
+ const cancelled = describeAskCancellation(sanitised, timedOut);
2027
+ logToFile(`wizard_ask: resolved ${Object.keys(answers).length} answer(s) for ${args.questions.length} question(s)${cancelled ? `, cancelled: ${cancelled.reason}` : ""}`);
2198
2028
  return { content: [{
2199
2029
  type: "text",
2200
- text: JSON.stringify({ answers: sanitised }, null, 2)
2030
+ text: JSON.stringify({
2031
+ answers: sanitised,
2032
+ ...cancelled ? { cancelled } : {}
2033
+ }, null, 2)
2201
2034
  }] };
2202
2035
  } catch (err) {
2203
2036
  askAccounting.refund(args.subject);
@@ -2247,9 +2080,8 @@ const GATEWAY_PROVIDER = "posthog-gateway";
2247
2080
  * OpenAI models take the Responses API because OpenAI rejects function tools
2248
2081
  * combined with `reasoning_effort` on chat completions and every task sends both.
2249
2082
  */
2250
- function gatewayApiFor(modelId, legacy) {
2251
- if (!modelId.startsWith("openai/")) return "anthropic-messages";
2252
- return legacy ? "openai-completions" : "openai-responses";
2083
+ function gatewayApiFor(modelId) {
2084
+ return modelId.startsWith("openai/") ? "openai-responses" : "anthropic-messages";
2253
2085
  }
2254
2086
  /**
2255
2087
  * Gateway HTTP headers, mirroring `buildAgentEnv` on the anthropic path: one
@@ -2257,10 +2089,10 @@ function gatewayApiFor(modelId, legacy) {
2257
2089
  * plus the 1M context beta, since pi otherwise runs at 200k and overflows on
2258
2090
  * larger projects (the post-run compaction failures).
2259
2091
  */
2260
- function buildGatewayHeaders(wizardMetadata, wizardFlags, teamId, legacy) {
2092
+ function buildGatewayHeaders(wizardMetadata, wizardFlags, teamId) {
2261
2093
  return {
2262
2094
  "anthropic-beta": "context-1m-2025-08-07",
2263
- ...legacy ? legacyGatewayHeaders(wizardMetadata, wizardFlags) : { "X-PostHog-Properties": buildWizardPropertiesBlob(wizardMetadata, wizardFlags, teamId) }
2095
+ "X-PostHog-Properties": buildWizardPropertiesBlob(wizardMetadata, wizardFlags, teamId)
2264
2096
  };
2265
2097
  }
2266
2098
  /**
@@ -2269,8 +2101,8 @@ function buildGatewayHeaders(wizardMetadata, wizardFlags, teamId, legacy) {
2269
2101
  * callers (scan triage) hand it straight to `completeSimple`.
2270
2102
  */
2271
2103
  function buildGatewayModel(inputs) {
2272
- const { gatewayUrl, wizardMetadata, wizardFlags, modelId, teamId, legacy } = inputs;
2273
- const api = gatewayApiFor(modelId, legacy);
2104
+ const { gatewayUrl, wizardMetadata, wizardFlags, modelId, teamId } = inputs;
2105
+ const api = gatewayApiFor(modelId);
2274
2106
  return {
2275
2107
  id: modelId,
2276
2108
  name: `${modelId} (PostHog Gateway)`,
@@ -2287,7 +2119,7 @@ function buildGatewayModel(inputs) {
2287
2119
  },
2288
2120
  contextWindow: 1e6,
2289
2121
  maxTokens: 64e3,
2290
- headers: buildGatewayHeaders(wizardMetadata, wizardFlags, teamId, legacy)
2122
+ headers: buildGatewayHeaders(wizardMetadata, wizardFlags, teamId)
2291
2123
  };
2292
2124
  }
2293
2125
  /**
@@ -2295,8 +2127,8 @@ function buildGatewayModel(inputs) {
2295
2127
  * plus the derived traits the session setup needs (`caps.thinkingLevel`).
2296
2128
  */
2297
2129
  function buildGatewayProvider(inputs) {
2298
- const { gatewayUrl, accessToken, modelId, effort, legacy } = inputs;
2299
- const api = gatewayApiFor(modelId, legacy);
2130
+ const { gatewayUrl, accessToken, modelId, effort } = inputs;
2131
+ const api = gatewayApiFor(modelId);
2300
2132
  const tableCaps = modelCapabilities(modelId);
2301
2133
  const caps = effort && effort !== "off" && tableCaps.reasoning ? modelCapabilities(modelId, effort) : effort === "off" && tableCaps.reasoning ? {
2302
2134
  ...tableCaps,
@@ -2391,7 +2223,6 @@ function createTriageLLMProvider(auth, harness) {
2391
2223
  gatewayUrl: current.baseURL,
2392
2224
  accessToken: authToken,
2393
2225
  teamId: current.teamId,
2394
- legacy: current.legacy,
2395
2226
  wizardMetadata: current.wizardMetadata ?? {},
2396
2227
  wizardFlags: current.wizardFlags ?? {},
2397
2228
  modelId
@@ -3260,13 +3091,11 @@ function isWarlockDisabled() {
3260
3091
  /**
3261
3092
  * Build ANTHROPIC_CUSTOM_HEADERS for the SDK subprocess: the run's metadata and
3262
3093
  * flags as one `X-PostHog-Properties` JSON blob. Bedrock fallback is native to
3263
- * the gateway, so there is no opt-in header. The CI fallback sends the legacy
3264
- * gateway's shape instead.
3094
+ * the gateway, so there is no opt-in header.
3265
3095
  */
3266
- function buildAgentEnv(wizardMetadata, wizardFlags, auth) {
3096
+ function buildAgentEnv(wizardMetadata, wizardFlags, teamId) {
3267
3097
  const headers = createCustomHeaders();
3268
- const shaped = auth.legacy ? legacyGatewayHeaders(wizardMetadata, wizardFlags) : { "X-PostHog-Properties": buildWizardPropertiesBlob(wizardMetadata, wizardFlags, auth.teamId) };
3269
- for (const [key, value] of Object.entries(shaped)) headers.add(key, value);
3098
+ headers.add("X-PostHog-Properties", buildWizardPropertiesBlob(wizardMetadata, wizardFlags, teamId));
3270
3099
  const encoded = headers.encode();
3271
3100
  logToFile("ANTHROPIC_CUSTOM_HEADERS", encoded);
3272
3101
  return encoded;
@@ -3376,7 +3205,6 @@ async function initializeAgent(config, options) {
3376
3205
  baseURL: current.gatewayUrl,
3377
3206
  authToken: current.token,
3378
3207
  teamId: current.teamId,
3379
- legacy: current.legacy,
3380
3208
  wizardMetadata: triageMetadata,
3381
3209
  wizardFlags: config.wizardFlags ?? {}
3382
3210
  };
@@ -3622,7 +3450,7 @@ async function runAgent(agentConfig, prompt, options, spinner, config, middlewar
3622
3450
  CLAUDE_CODE_DISABLE_EXPERIMENTAL_BETAS: "true",
3623
3451
  POSTHOG_MCP_TOKEN: agentConfig.posthogApiKey,
3624
3452
  MCP_CONNECTION_NONBLOCKING: "0",
3625
- ANTHROPIC_CUSTOM_HEADERS: buildAgentEnv(agentConfig.wizardMetadata ?? {}, agentConfig.wizardFlags ?? {}, agentConfig.gatewayAuth)
3453
+ ANTHROPIC_CUSTOM_HEADERS: buildAgentEnv(agentConfig.wizardMetadata ?? {}, agentConfig.wizardFlags ?? {}, agentConfig.gatewayAuth.teamId)
3626
3454
  },
3627
3455
  canUseTool: (toolName, input) => {
3628
3456
  logToFile("canUseTool called:", {
@@ -4146,6 +3974,6 @@ function handleSDKMessage(message, options, spinner, signals, receivedSuccessRes
4146
3974
  }
4147
3975
  }
4148
3976
  //#endregion
4149
- export { WIZARD_TOOL_NAMES as A, templateEnvWriteRefusal as B, publishHandoff as C, WIZARD_ASK_SENSITIVE_DESCRIPTION as D, ENV_FILE_PATH_DESCRIPTION as E, installSkillById as F, gatewayAuth as G, fetchWithRetry as H, mergeEnvValues as I, isEnvFileName as J, setLegacyGatewayFallback as K, normaliseAskSubject as L, createAskAccounting as M, downloadSkill as N, WIZARD_ASK_SUBJECT_DESCRIPTION as O, fetchSkillMenu as P, resolveEnvPath as R, PUBLISH_HANDOFF_TOOL_NAME as S, CHECK_ENV_KEYS_FILE_PATH_DESCRIPTION as T, createWizardAskBridge as U, vaultSensitiveAnswers as V, isFullyCancelled as W, toPromptSafeRelativePath as X, parseEnvKeyNames as Y, markGrantRevoked as Z, buildGatewayProvider as _, wizardCanUseTool as a, PUBLISH_HANDOFF_CONTENT_DESCRIPTION as b, checkAllSettingsConflicts as c, restoreClaudeSettings as d, sanitizeAgentSubprocessEnv as f, GATEWAY_PROVIDER as g, createTriageLLMProvider as h, runAgent as i, checkEnvKeys as j, WIZARD_ASK_TOOL_DESCRIPTION as k, classifySettingsConflicts as l, assembleCommandments as m, buildRunTags as n, createIsolatedAgentConfigDir as o, AgentOutputSignals as p, MAX_ENV_KEY_SET as q, initializeAgent as r, backupAndFixClaudeSettings as s, buildAgentEnv as t, recoverOrphanedSettingsBackups as u, withGatewayRemint as v, CHECK_ENV_KEYS_DESCRIPTION as w, PUBLISH_HANDOFF_DESCRIPTION as x, createSecretVault as y, resolveEnvSecretRefs as z };
3977
+ export { resolveEnvSecretRefs as $, AUDIT_STATUSES as A, checkEnvKeys as B, publishHandoff as C, AUDIT_RESOLVE_CHECKS_PARAM_DESCRIPTION as D, AUDIT_RESOLVE_CHECKS_DESCRIPTION as E, WIZARD_ASK_SENSITIVE_DESCRIPTION as F, fetchSkillMenu as G, describeAskCancellation as H, WIZARD_ASK_SUBJECT_DESCRIPTION as I, mergeEnvValues as J, installSkillById as K, WIZARD_ASK_TOOL_DESCRIPTION as L, CHECK_ENV_KEYS_FILE_PATH_DESCRIPTION as M, ENV_FILE_PATH_DESCRIPTION as N, AUDIT_SEED_CHECKS_DESCRIPTION as O, WIZARD_ASK_KIND_DESCRIPTION as P, resolveEnvPath as Q, WIZARD_TOOL_NAMES as R, PUBLISH_HANDOFF_TOOL_NAME as S, AUDIT_ADD_CHECKS_PARAM_DESCRIPTION as T, downloadSkill as U, createAskAccounting as V, ensureGitignoreCoverage as W, resolveAskQuestionKinds as X, normaliseAskSubject as Y, resolveAuditChecks as Z, buildGatewayProvider as _, wizardCanUseTool as a, createWizardAskBridge as at, PUBLISH_HANDOFF_CONTENT_DESCRIPTION as b, checkAllSettingsConflicts as c, isEnvFileName as ct, restoreClaudeSettings as d, markGrantRevoked as dt, seedAuditChecks as et, sanitizeAgentSubprocessEnv as f, GATEWAY_PROVIDER as g, createTriageLLMProvider as h, runAgent as i, LONGER_ASK_TIMEOUT_MS as it, CHECK_ENV_KEYS_DESCRIPTION as j, AUDIT_SEED_CHECKS_PARAM_DESCRIPTION as k, classifySettingsConflicts as l, parseEnvKeyNames as lt, assembleCommandments as m, buildRunTags as n, vaultSensitiveAnswers as nt, createIsolatedAgentConfigDir as o, isFullyCancelled as ot, AgentOutputSignals as p, legacyKeyNameRefusal as q, initializeAgent as r, fetchWithRetry as rt, backupAndFixClaudeSettings as s, MAX_ENV_KEY_SET as st, buildAgentEnv as t, templateEnvWriteRefusal as tt, recoverOrphanedSettingsBackups as u, toPromptSafeRelativePath as ut, withGatewayRemint as v, AUDIT_ADD_CHECKS_DESCRIPTION as w, PUBLISH_HANDOFF_DESCRIPTION as x, createSecretVault as y, addAuditChecks as z };
4150
3978
 
4151
- //# sourceMappingURL=agent-interface-lCIi_alS.js.map
3979
+ //# sourceMappingURL=agent-interface-CZWRgZMc.js.map