@algolia/wizard 0.83.0 → 0.84.0-rc.202.644

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/main.js CHANGED
@@ -884,20 +884,28 @@ function useScrollWindow({
884
884
  revealIndex
885
885
  };
886
886
  }
887
- function ScrollView({ scroll, children }) {
888
- return /* @__PURE__ */ jsxs5(Box6, { ref: scroll.viewportRef, flexDirection: "column", flexGrow: 1, children: [
889
- scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
890
- "\u2191 ",
891
- scroll.hiddenAbove,
892
- " more"
893
- ] }),
894
- children,
895
- scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
896
- "\u2193 ",
897
- scroll.hiddenBelow,
898
- " more"
899
- ] })
900
- ] });
887
+ function ScrollView({ scroll, grow = true, children }) {
888
+ return /* @__PURE__ */ jsxs5(
889
+ Box6,
890
+ {
891
+ ref: scroll.viewportRef,
892
+ flexDirection: "column",
893
+ flexGrow: grow ? 1 : 0,
894
+ children: [
895
+ scroll.hiddenAbove > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
896
+ "\u2191 ",
897
+ scroll.hiddenAbove,
898
+ " more"
899
+ ] }),
900
+ children,
901
+ scroll.hiddenBelow > 0 && /* @__PURE__ */ jsxs5(Text6, { color: COLORS.dim, children: [
902
+ "\u2193 ",
903
+ scroll.hiddenBelow,
904
+ " more"
905
+ ] })
906
+ ]
907
+ }
908
+ );
901
909
  }
902
910
 
903
911
  // src/ui/Review.tsx
@@ -1082,6 +1090,7 @@ function CommandApproval({
1082
1090
  if (scroll.measured) setWidth(scroll.width);
1083
1091
  }, [scroll.measured, scroll.width]);
1084
1092
  const scrollable = scroll.maxOffset > 0;
1093
+ const grow = width === 0 || scrollable;
1085
1094
  useInput3(
1086
1095
  (input) => {
1087
1096
  if (input === "k") scroll.scrollBy(-1);
@@ -1104,10 +1113,10 @@ function CommandApproval({
1104
1113
  borderBottom: false,
1105
1114
  borderRight: false,
1106
1115
  width: "100%",
1107
- flexGrow: 1,
1116
+ flexGrow: grow ? 1 : 0,
1108
1117
  flexShrink: 1,
1109
1118
  minHeight: 3,
1110
- children: /* @__PURE__ */ jsx6(ScrollView, { scroll, children: visible.map((line, i) => /* @__PURE__ */ jsxs7(Text8, { wrap: "truncate", children: [
1119
+ children: /* @__PURE__ */ jsx6(ScrollView, { scroll, grow, children: visible.map((line, i) => /* @__PURE__ */ jsxs7(Text8, { wrap: "truncate", children: [
1111
1120
  line.prefix ? /* @__PURE__ */ jsx6(Text8, { color: COLORS.muted, children: line.prefix }) : null,
1112
1121
  /* @__PURE__ */ jsx6(Text8, { color: line.color, children: line.text })
1113
1122
  ] }, `cmd-${scroll.offset + i}`)) })
@@ -1483,7 +1492,7 @@ function PromptInput() {
1483
1492
  }
1484
1493
 
1485
1494
  // src/workflows/default.ts
1486
- import { z as z29 } from "zod";
1495
+ import { z as z30 } from "zod";
1487
1496
 
1488
1497
  // src/core/orchestrator.ts
1489
1498
  import "zod";
@@ -2306,7 +2315,7 @@ async function ensureApplication(resumeAppId) {
2306
2315
  // package.json
2307
2316
  var package_default = {
2308
2317
  name: "@algolia/wizard",
2309
- version: "0.83.0",
2318
+ version: "0.84.0-rc.202.644",
2310
2319
  description: "Magically implement Algolia functionality in your codebase",
2311
2320
  type: "module",
2312
2321
  engines: {
@@ -2797,6 +2806,22 @@ async function listIndices() {
2797
2806
 
2798
2807
  // src/actions/selectIndex.ts
2799
2808
  var CREATE_NEW_INDEX = "Create a new index\u2026";
2809
+ var MAX_INDEX_NAME_LENGTH = 255;
2810
+ function indexNameError(name) {
2811
+ if (!name) return "Index name cannot be empty.";
2812
+ if (name.length > MAX_INDEX_NAME_LENGTH)
2813
+ return `Index name cannot be longer than ${MAX_INDEX_NAME_LENGTH} characters.`;
2814
+ if (name.startsWith(".")) return "Index name cannot start with '.'.";
2815
+ if (/[/~,\\`&|;$*]/.test(name))
2816
+ return "Index name cannot contain any of these characters: / ~ , \\ ` & | ; $ *";
2817
+ if (/[\p{Cc}]/u.test(name))
2818
+ return "Index name cannot contain control characters.";
2819
+ if (/[\u{10000}-\u{10FFFF}]/u.test(name))
2820
+ return "Index name cannot contain characters outside the Basic Multilingual Plane.";
2821
+ if (/\.(txt|bin|idx)$/.test(name) && !name.endsWith(".indice.bin"))
2822
+ return "Index name cannot end with .txt, .bin, or .idx.";
2823
+ return void 0;
2824
+ }
2800
2825
  var selectIndexStep = async (ctx) => {
2801
2826
  await requireApplication(ctx.appId);
2802
2827
  const indices = await listIndices();
@@ -2824,6 +2849,7 @@ var selectIndexStep = async (ctx) => {
2824
2849
  throw new Error("selectIndex received an unexpected non-text result");
2825
2850
  }
2826
2851
  let candidate;
2852
+ let isNewName = true;
2827
2853
  if (!hasIndices) {
2828
2854
  candidate = selection.trim();
2829
2855
  } else if (selection === CREATE_NEW_INDEX) {
@@ -2838,9 +2864,11 @@ var selectIndexStep = async (ctx) => {
2838
2864
  candidate = name.trim();
2839
2865
  } else {
2840
2866
  candidate = selection;
2867
+ isNewName = false;
2841
2868
  }
2842
- if (!candidate) {
2843
- error = "Index name cannot be empty.";
2869
+ const nameError = isNewName ? indexNameError(candidate) : void 0;
2870
+ if (nameError) {
2871
+ error = nameError;
2844
2872
  continue;
2845
2873
  }
2846
2874
  chosen = candidate;
@@ -2856,6 +2884,7 @@ import {
2856
2884
  Output as Output3,
2857
2885
  APICallError,
2858
2886
  NoOutputGeneratedError,
2887
+ RetryError,
2859
2888
  stepCountIs
2860
2889
  } from "ai";
2861
2890
  import { createAnthropic as createAnthropic3 } from "@ai-sdk/anthropic";
@@ -2869,6 +2898,58 @@ import { tool } from "ai";
2869
2898
  import z6 from "zod";
2870
2899
  import { readdir } from "node:fs/promises";
2871
2900
 
2901
+ // src/lib/tools/context.ts
2902
+ var DEFAULT_TOOL_LIMITS = {
2903
+ list: 10,
2904
+ search: 20,
2905
+ read: 20,
2906
+ match: 100,
2907
+ shell: 50
2908
+ };
2909
+ var EMPTY_TOOL_COUNTS = {
2910
+ list: 0,
2911
+ search: 0,
2912
+ read: 0,
2913
+ shell: 0
2914
+ };
2915
+ var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
2916
+ async function refuseByDefault() {
2917
+ return "reject";
2918
+ }
2919
+ async function emptyShellEnv() {
2920
+ await Promise.resolve();
2921
+ return {};
2922
+ }
2923
+ function createShellContext(overrides = {}) {
2924
+ return {
2925
+ env: emptyShellEnv,
2926
+ secretEnv: emptyShellEnv,
2927
+ timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
2928
+ executions: [],
2929
+ approve: refuseByDefault,
2930
+ ...overrides
2931
+ };
2932
+ }
2933
+ function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
2934
+ return {
2935
+ root: cwd,
2936
+ cwd,
2937
+ limits: { ...limits },
2938
+ counts: { ...EMPTY_TOOL_COUNTS },
2939
+ shell: shell2,
2940
+ reviewed: []
2941
+ };
2942
+ }
2943
+ function readLimitRefusal(kind, limit) {
2944
+ return `Refused: ${kind} limit (${limit}) reached. This limit is not a failure, so do not report fail because of it. Finish the task now with the information you already have. If the task changes files, write or edit every file that the task still needs. Then report success, and name any gap in the summary.`;
2945
+ }
2946
+ function readLimitReached(ctx) {
2947
+ return ctx.counts.read > ctx.limits.read || ctx.counts.search > ctx.limits.search || ctx.counts.list > ctx.limits.list;
2948
+ }
2949
+ function resetToolCounts(ctx) {
2950
+ Object.assign(ctx.counts, EMPTY_TOOL_COUNTS);
2951
+ }
2952
+
2872
2953
  // src/lib/tools/path.ts
2873
2954
  import { constants } from "node:fs";
2874
2955
  import { lstat, mkdir as mkdir3, open } from "node:fs/promises";
@@ -2950,7 +3031,7 @@ function listFilesTool(ctx) {
2950
3031
  execute: async ({ path = "." }) => {
2951
3032
  logger.info({ path }, "called listFiles tool");
2952
3033
  if (++ctx.counts.list > ctx.limits.list) {
2953
- return `Refused: list limit (${ctx.limits.list}) reached. Stop listing and proceed with the information you already have.`;
3034
+ return readLimitRefusal("list", ctx.limits.list);
2954
3035
  }
2955
3036
  const resolved2 = resolveInRoot(ctx, path);
2956
3037
  if (!resolved2.ok) return resolved2.error;
@@ -3026,6 +3107,9 @@ function isSecretEnvFile(filePath) {
3026
3107
  const name = basename(filePath);
3027
3108
  return !/\.(example|sample|template)$/.test(name);
3028
3109
  }
3110
+ function secretEnvWriteRefusal(filePath) {
3111
+ return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
3112
+ }
3029
3113
 
3030
3114
  // src/lib/tools/utils/truncateText.ts
3031
3115
  var HEAD_CHARS = 4e3;
@@ -3095,7 +3179,7 @@ function readFileTool(ctx) {
3095
3179
  }),
3096
3180
  execute: async ({ filePath, readLines }) => {
3097
3181
  if (++ctx.counts.read > ctx.limits.read) {
3098
- return `Refused: read limit (${ctx.limits.read}) reached. Stop reading and proceed with the information you already have.`;
3182
+ return readLimitRefusal("read", ctx.limits.read);
3099
3183
  }
3100
3184
  logger.info({ filePath, readLines }, "called readFile tool");
3101
3185
  const resolved2 = resolveInRoot(ctx, filePath);
@@ -3116,7 +3200,7 @@ import { tool as tool5 } from "ai";
3116
3200
  import z10 from "zod";
3117
3201
  function writeFileTool(ctx) {
3118
3202
  return tool5({
3119
- description: "Write content to a file at the given path, overwriting it. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
3203
+ description: "Write content to a file at the given path, overwriting it. Use it to create a file or to replace all of its content; to change part of an existing file, use editFile. Writes to secret env files (.env, .env.local, etc.) are refused by this tool \u2014 call it anyway and the tool will tell you how to proceed.",
3120
3204
  inputSchema: z10.object({
3121
3205
  filePath: z10.string().describe("Path to the file to write"),
3122
3206
  content: z10.string().describe("Content to write to the file")
@@ -3126,7 +3210,7 @@ function writeFileTool(ctx) {
3126
3210
  const resolved2 = resolveInRoot(ctx, filePath);
3127
3211
  if (resolved2.ok === false) return resolved2.error;
3128
3212
  if (isSecretEnvFile(resolved2.target)) {
3129
- return `Refused: ${filePath} holds secrets and cannot be written directly. If a writeCredentials tool is available, use it to set Algolia credentials. If not, tell the user to set the value manually \u2014 do not reproduce the secret value in your response.`;
3213
+ return secretEnvWriteRefusal(filePath);
3130
3214
  }
3131
3215
  try {
3132
3216
  const writeError = await safeWriteFile(ctx, resolved2.target, content);
@@ -3140,22 +3224,107 @@ function writeFileTool(ctx) {
3140
3224
  });
3141
3225
  }
3142
3226
 
3143
- // src/lib/tools/writeAlgoliaCredentials.ts
3227
+ // src/lib/tools/editFile.ts
3144
3228
  import { tool as tool6 } from "ai";
3145
- import z13 from "zod";
3146
- import { readFile as readFile6 } from "node:fs/promises";
3229
+ import z11 from "zod";
3230
+ import { readFile as readFile5 } from "node:fs/promises";
3231
+ var SNIPPET_CONTEXT_LINES = 3;
3232
+ var SNIPPET_MAX_LINES = 40;
3233
+ function countOccurrences(content, search2) {
3234
+ let count = 0;
3235
+ let index = content.indexOf(search2);
3236
+ while (index !== -1) {
3237
+ count += 1;
3238
+ index = content.indexOf(search2, index + search2.length);
3239
+ }
3240
+ return count;
3241
+ }
3242
+ function replaceFirst(content, search2, replacement) {
3243
+ const index = content.indexOf(search2);
3244
+ return content.slice(0, index) + replacement + content.slice(index + search2.length);
3245
+ }
3246
+ function editedSnippet(updated, editIndex, newString) {
3247
+ const lines = updated.split("\n");
3248
+ const editStart = updated.slice(0, editIndex).split("\n").length - 1;
3249
+ const editEnd = updated.slice(0, editIndex + newString.length).split("\n").length - (newString.endsWith("\n") ? 1 : 0);
3250
+ const from = Math.max(0, editStart - SNIPPET_CONTEXT_LINES);
3251
+ const to = Math.min(
3252
+ lines.length,
3253
+ editEnd + SNIPPET_CONTEXT_LINES,
3254
+ from + SNIPPET_MAX_LINES
3255
+ );
3256
+ return `readLines [${from}, ${to}] now contains:
3257
+ ${truncateText(lines.slice(from, to).join("\n"))}`;
3258
+ }
3259
+ function editFileTool(ctx) {
3260
+ return tool6({
3261
+ description: "Edit an existing file by replacing an exact string with a new string. Prefer this over writeFile for any change to an existing file: send only the changed part, not the whole file. oldString must match the file content exactly, including whitespace and indentation, and must be unique unless replaceAll is true. Include enough surrounding lines to make it unique. Read the file first so the match is exact. A single edit returns the edited lines with some context, so do not read the file again to check the edit.",
3262
+ inputSchema: z11.object({
3263
+ filePath: z11.string().describe("Path to the file to edit"),
3264
+ oldString: z11.string().describe("Exact text to replace"),
3265
+ newString: z11.string().describe("Text to put in place of oldString"),
3266
+ replaceAll: z11.boolean().optional().describe("Replace every occurrence of oldString (default false)")
3267
+ }),
3268
+ execute: async ({ filePath, oldString, newString, replaceAll }) => {
3269
+ logger.info({ filePath, replaceAll }, "called editFile tool");
3270
+ const resolved2 = resolveInRoot(ctx, filePath);
3271
+ if (resolved2.ok === false) return resolved2.error;
3272
+ if (isSecretEnvFile(resolved2.target)) {
3273
+ return secretEnvWriteRefusal(filePath);
3274
+ }
3275
+ if (oldString === "") {
3276
+ return "Error: oldString is empty. To create a file, use writeFile.";
3277
+ }
3278
+ if (oldString === newString) {
3279
+ return "Error: oldString and newString are identical. No change made.";
3280
+ }
3281
+ try {
3282
+ const unsafe = await validateSafeWriteTarget(ctx, resolved2.target);
3283
+ if (unsafe) return unsafe;
3284
+ let content;
3285
+ try {
3286
+ content = await readFile5(resolved2.target, "utf8");
3287
+ } catch (err) {
3288
+ if (err.code === "ENOENT") {
3289
+ return `Error: ${filePath} does not exist. To create it, use writeFile.`;
3290
+ }
3291
+ throw err;
3292
+ }
3293
+ const matches = countOccurrences(content, oldString);
3294
+ if (matches === 0) {
3295
+ return `Error: oldString not found in ${filePath}. Read the file again and copy the exact text, including whitespace.`;
3296
+ }
3297
+ if (matches > 1 && !replaceAll) {
3298
+ return `Error: oldString matches ${matches} places in ${filePath}. Add surrounding lines to make it unique, or set replaceAll to true.`;
3299
+ }
3300
+ const updated = replaceAll ? content.split(oldString).join(newString) : replaceFirst(content, oldString, newString);
3301
+ const writeError = await safeWriteFile(ctx, resolved2.target, updated);
3302
+ if (writeError) return writeError;
3303
+ useWizard.getState().recordWrittenFile(resolved2.target);
3304
+ return matches > 1 ? `Edited ${filePath} (${matches} replacements)` : `Edited ${filePath}. ${editedSnippet(updated, content.indexOf(oldString), newString)}`;
3305
+ } catch (err) {
3306
+ return `Error editing ${filePath}: ${err.message}`;
3307
+ }
3308
+ }
3309
+ });
3310
+ }
3311
+
3312
+ // src/lib/tools/writeAlgoliaCredentials.ts
3313
+ import { tool as tool7 } from "ai";
3314
+ import z14 from "zod";
3315
+ import { readFile as readFile7 } from "node:fs/promises";
3147
3316
  import { relative as relative3 } from "node:path";
3148
3317
 
3149
3318
  // src/lib/algoliaApiKey.ts
3150
- import { z as z12 } from "zod";
3319
+ import { z as z13 } from "zod";
3151
3320
 
3152
3321
  // src/lib/keychain.ts
3153
3322
  import { createHash } from "node:crypto";
3154
3323
  import { deletePassword, getPassword, setPassword } from "cross-keychain";
3155
- import { z as z11 } from "zod";
3324
+ import { z as z12 } from "zod";
3156
3325
  var SERVICE = "algolia-wizard";
3157
3326
  var account = (userId) => `api-keys-${createHash("sha256").update(userId).digest("hex")}`;
3158
- var storedKeysSchema = z11.record(z11.string(), z11.string());
3327
+ var storedKeysSchema = z12.record(z12.string(), z12.string());
3159
3328
  function entryId(kind, index, appId) {
3160
3329
  return `${kind}:${appId}:${index}`;
3161
3330
  }
@@ -3249,9 +3418,9 @@ var WRITE_ACLS = [
3249
3418
  "editSettings",
3250
3419
  "listIndexes"
3251
3420
  ];
3252
- var createdKeySchema = z12.object({
3253
- key: z12.string().min(1).optional(),
3254
- value: z12.string().min(1).optional()
3421
+ var createdKeySchema = z13.object({
3422
+ key: z13.string().min(1).optional(),
3423
+ value: z13.string().min(1).optional()
3255
3424
  }).transform((o) => o.key ?? o.value);
3256
3425
  async function createKey(index, acls, description) {
3257
3426
  logger.info({ index, acls }, "creating an API key");
@@ -3347,7 +3516,7 @@ async function resolveSearchOnlyKey(index, appId) {
3347
3516
 
3348
3517
  // src/lib/gitignore.ts
3349
3518
  import { execFile } from "node:child_process";
3350
- import { lstat as lstat2, readFile as readFile5, writeFile as writeFile3 } from "node:fs/promises";
3519
+ import { lstat as lstat2, readFile as readFile6, writeFile as writeFile3 } from "node:fs/promises";
3351
3520
  import { join as join7, relative as relative2 } from "node:path";
3352
3521
  var GIT_ENV_OVERRIDES = [
3353
3522
  "GIT_DIR",
@@ -3410,7 +3579,7 @@ async function ensureGitIgnored(root, target) {
3410
3579
  );
3411
3580
  return "unknown";
3412
3581
  }
3413
- const existing = link ? await readFile5(gitIgnore, "utf8") : "";
3582
+ const existing = link ? await readFile6(gitIgnore, "utf8") : "";
3414
3583
  const prefix = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
3415
3584
  await writeFile3(gitIgnore, `${existing}${prefix}${pattern}
3416
3585
  `, "utf8");
@@ -3457,10 +3626,10 @@ function upsertEnv(content, name, value) {
3457
3626
  );
3458
3627
  }
3459
3628
  function writeCredentialsTool(ctx) {
3460
- return tool6({
3629
+ return tool7({
3461
3630
  description: `Write the active Algolia credentials (${APP_ID_VAR} and ${API_KEY_VAR}) and the target index name (${INDEX_NAME_VAR}) into the given env file. The credentials come from the selected Algolia application, with a write key scoped to the target index; you only pass the path to the env file. ${INDEX_NAME_VAR} is always set to this run's target index, replacing any value already there. An ${APP_ID_VAR} or ${API_KEY_VAR} the file already gives a value is left untouched; a missing or blank one is filled in when it can be paired with the selected application. The env file is added to .gitignore automatically; do not edit .gitignore yourself. If the script or app that reads these credentials lives in a subdirectory (e.g. a package in a monorepo), an env file at the repo root is the wrong default \u2014 a script only loads env vars from its own directory (or one it's explicitly configured to read), so check every directory from the script's own up to the repo root, not just those two: its own directory, each ancestor in between (a shared workspace-level directory above the immediate package is common), and the root. Use whichever of those already holds real credentials; only fall back to the repo root when none of them do. Never invent a brand-new file in one of those directories when a real one already exists in another \u2014 that leaves the real one stale and the new one wrong. Listing just the script's own directory and the very top-level root is not enough to find a workspace-level file in between; check the intermediate ones too. If instructions describe a location that doesn't match the project you actually find (e.g. a path outside the repo, or a convention the project doesn't follow), don't stop and ask before doing anything \u2014 call this tool on the real, in-repo file the script actually reads (that's always the safe default), then note the mismatch afterward. Ending your turn with only a question and no call to this tool leaves the project unconfigured.`,
3462
- inputSchema: z13.object({
3463
- filePath: z13.string().describe(
3631
+ inputSchema: z14.object({
3632
+ filePath: z14.string().describe(
3464
3633
  'Path to the env file to write credentials into, relative to the repo root (e.g. ".env", or "packages/api/.env" when the consuming script lives in that package)'
3465
3634
  )
3466
3635
  }),
@@ -3478,7 +3647,7 @@ function writeCredentialsTool(ctx) {
3478
3647
  const targetError = await validateSafeWriteTarget(ctx, resolved2.target);
3479
3648
  if (targetError) return targetError;
3480
3649
  try {
3481
- existing = await readFile6(resolved2.target, "utf8");
3650
+ existing = await readFile7(resolved2.target, "utf8");
3482
3651
  } catch (err) {
3483
3652
  if (err.code !== "ENOENT") throw err;
3484
3653
  }
@@ -3567,9 +3736,9 @@ async function gitIgnoreOutcome(ctx, target) {
3567
3736
 
3568
3737
  // src/lib/tools/searchFiles.ts
3569
3738
  import { execFile as execFile2 } from "node:child_process";
3570
- import { tool as tool7 } from "ai";
3571
- import z14 from "zod";
3572
- import { lstat as lstat3, readdir as readdir2, readFile as readFile7, realpath, stat as stat2 } from "node:fs/promises";
3739
+ import { tool as tool8 } from "ai";
3740
+ import z15 from "zod";
3741
+ import { lstat as lstat3, readdir as readdir2, readFile as readFile8, realpath, stat as stat2 } from "node:fs/promises";
3573
3742
  import { isAbsolute as isAbsolute2, join as join8, relative as relative4, sep as sep2 } from "node:path";
3574
3743
  import { performance } from "node:perf_hooks";
3575
3744
  import { Worker } from "node:worker_threads";
@@ -3940,16 +4109,16 @@ function formatSearchResult(matches, skippedFiles, deadlineReached, workerFailed
3940
4109
  return lines.length > 0 ? lines.join("\n") : "No matches";
3941
4110
  }
3942
4111
  function searchFilesTool(ctx) {
3943
- return tool7({
4112
+ return tool8({
3944
4113
  description: "Search file contents for a JavaScript regular expression (RegExp syntax, not grep/PCRE). Returns matching lines as file:line:text.",
3945
- inputSchema: z14.object({
3946
- query: z14.string().describe("JavaScript RegExp pattern to search for"),
3947
- path: z14.string().optional().describe("Directory to search in (default: cwd)")
4114
+ inputSchema: z15.object({
4115
+ query: z15.string().describe("JavaScript RegExp pattern to search for"),
4116
+ path: z15.string().optional().describe("Directory to search in (default: cwd)")
3948
4117
  }),
3949
4118
  execute: async ({ query, path = "." }) => {
3950
4119
  logger.info({ query, path }, "called searchFiles tool");
3951
4120
  if (++ctx.counts.search > ctx.limits.search) {
3952
- return `Refused: search limit (${ctx.limits.search}) reached. Stop searching and proceed with the information you already have.`;
4121
+ return readLimitRefusal("search", ctx.limits.search);
3953
4122
  }
3954
4123
  if (query.length > MAX_QUERY_LENGTH) {
3955
4124
  return `Refused: query exceeds ${MAX_QUERY_LENGTH} characters. Use a shorter pattern.`;
@@ -3992,7 +4161,7 @@ function searchFilesTool(ctx) {
3992
4161
  try {
3993
4162
  const filePath = await safeSearchPath(rootPath, file);
3994
4163
  if (filePath === null) continue;
3995
- content = await readFile7(filePath, "utf8");
4164
+ content = await readFile8(filePath, "utf8");
3996
4165
  } catch {
3997
4166
  continue;
3998
4167
  }
@@ -4079,16 +4248,16 @@ function searchFilesTool(ctx) {
4079
4248
  }
4080
4249
 
4081
4250
  // src/lib/tools/runShell.ts
4082
- import { tool as tool9, generateText, Output } from "ai";
4251
+ import { tool as tool10, generateText, Output } from "ai";
4083
4252
  import { createAnthropic } from "@ai-sdk/anthropic";
4084
- import z16 from "zod";
4253
+ import z17 from "zod";
4085
4254
  import { relative as relative6 } from "node:path";
4086
4255
 
4087
4256
  // src/lib/tools/reviewScript.ts
4088
- import { tool as tool8 } from "ai";
4089
- import z15 from "zod";
4257
+ import { tool as tool9 } from "ai";
4258
+ import z16 from "zod";
4090
4259
  import { createHash as createHash2 } from "node:crypto";
4091
- import { readFile as readFile8, stat as stat3 } from "node:fs/promises";
4260
+ import { readFile as readFile9, stat as stat3 } from "node:fs/promises";
4092
4261
  import { basename as basename2, relative as relative5, resolve as resolve3 } from "node:path";
4093
4262
 
4094
4263
  // src/lib/tools/utils/prompt.ts
@@ -4103,7 +4272,7 @@ function serializePrompt(work) {
4103
4272
  // src/lib/tools/reviewScript.ts
4104
4273
  var reviewedCommandApprovals = /* @__PURE__ */ new WeakMap();
4105
4274
  async function fileDigest(filePath) {
4106
- const content = await readFile8(filePath);
4275
+ const content = await readFile9(filePath);
4107
4276
  return createHash2("sha256").update(content).digest("hex");
4108
4277
  }
4109
4278
  function grantReviewedCommandApproval(ctx, approval) {
@@ -4207,18 +4376,18 @@ async function reviewedCommandApprovalIsCurrent(approval) {
4207
4376
  return currentDigest === approval.digest;
4208
4377
  }
4209
4378
  function reviewScriptTool(ctx) {
4210
- return tool8({
4379
+ return tool9({
4211
4380
  description: "Call this as soon as the ingestion script is finished and before you run it. It summarizes the records that will be written, then asks the user to review the script and approve the exact command that will run it, so a matching runShell call needs no second approval.",
4212
- inputSchema: z15.object({
4213
- filePath: z15.string().describe(
4381
+ inputSchema: z16.object({
4382
+ filePath: z16.string().describe(
4214
4383
  "Path to the script to review, relative to the project root."
4215
4384
  ),
4216
- command: z15.string().min(1).describe("The exact command that runShell will use to run the script."),
4217
- cwd: z15.string().describe(
4385
+ command: z16.string().min(1).describe("The exact command that runShell will use to run the script."),
4386
+ cwd: z16.string().describe(
4218
4387
  "The exact working directory that runShell will use, relative to the current tool working directory."
4219
4388
  ),
4220
- entityName: z15.string().trim().min(1).max(100).regex(/^[^\r\n]+$/).describe("The exact entity name selected by the user."),
4221
- usesGeneratedRecords: z15.boolean().describe(
4389
+ entityName: z16.string().trim().min(1).max(100).regex(/^[^\r\n]+$/).describe("The exact entity name selected by the user."),
4390
+ usesGeneratedRecords: z16.boolean().describe(
4222
4391
  "Whether the script reads generated sample records instead of the selected real data source."
4223
4392
  )
4224
4393
  }),
@@ -4309,54 +4478,6 @@ function redactSecrets(output, secrets) {
4309
4478
 
4310
4479
  // src/lib/tools/utils/runShell.ts
4311
4480
  import { spawn as spawn2 } from "node:child_process";
4312
-
4313
- // src/lib/tools/context.ts
4314
- var DEFAULT_TOOL_LIMITS = {
4315
- list: 10,
4316
- search: 20,
4317
- read: 20,
4318
- match: 100,
4319
- shell: 50
4320
- };
4321
- var EMPTY_TOOL_COUNTS = {
4322
- list: 0,
4323
- search: 0,
4324
- read: 0,
4325
- shell: 0
4326
- };
4327
- var DEFAULT_SHELL_TIMEOUT_MS = 10 * 60 * 1e3;
4328
- async function refuseByDefault() {
4329
- return "reject";
4330
- }
4331
- async function emptyShellEnv() {
4332
- await Promise.resolve();
4333
- return {};
4334
- }
4335
- function createShellContext(overrides = {}) {
4336
- return {
4337
- env: emptyShellEnv,
4338
- secretEnv: emptyShellEnv,
4339
- timeoutMs: DEFAULT_SHELL_TIMEOUT_MS,
4340
- executions: [],
4341
- approve: refuseByDefault,
4342
- ...overrides
4343
- };
4344
- }
4345
- function createToolContext(limits = DEFAULT_TOOL_LIMITS, cwd = process.cwd(), shell2 = createShellContext()) {
4346
- return {
4347
- root: cwd,
4348
- cwd,
4349
- limits: { ...limits },
4350
- counts: { ...EMPTY_TOOL_COUNTS },
4351
- shell: shell2,
4352
- reviewed: []
4353
- };
4354
- }
4355
- function resetToolCounts(ctx) {
4356
- Object.assign(ctx.counts, EMPTY_TOOL_COUNTS);
4357
- }
4358
-
4359
- // src/lib/tools/utils/runShell.ts
4360
4481
  var SIGKILL_DELAY_MS = 5e3;
4361
4482
  function runShell(command, opts) {
4362
4483
  const timeoutMs = opts.timeoutMs ?? DEFAULT_SHELL_TIMEOUT_MS;
@@ -4618,9 +4739,9 @@ function fastPathSafety(command) {
4618
4739
  if (results.every((r) => r === true)) return true;
4619
4740
  return void 0;
4620
4741
  }
4621
- var commandSafetySchema = z16.object({
4622
- safe: z16.boolean(),
4623
- reason: z16.string().describe("One short sentence explaining the verdict.")
4742
+ var commandSafetySchema = z17.object({
4743
+ safe: z17.boolean(),
4744
+ reason: z17.string().describe("One short sentence explaining the verdict.")
4624
4745
  });
4625
4746
  function defaultCreateModel() {
4626
4747
  const token = getAuthToken();
@@ -4661,17 +4782,17 @@ async function classifyCommandSafety(createModel, command, cwd, explanation, app
4661
4782
  prompt: [
4662
4783
  "A coding agent wants to run this shell command in a user's project without asking for approval first. The command may be in any programming language or ecosystem.",
4663
4784
  "Judge it SAFE only if it cannot modify, delete, move, rename, or install anything the project cares about, cannot push/publish/deploy/commit anything, and cannot make a network request that changes remote state.",
4664
- "Never judge a command SAFE if it runs, imports, requires, or otherwise references an ingestion script or any path under `.algolia-wizard/`. This rule includes shell wrappers, inline code, variable indirection, and dry-run or check descriptions. Such a command always requires explicit user approval.",
4785
+ "Never judge a command SAFE if it runs, imports, requires, or otherwise executes an ingestion script or any code under `.algolia-wizard/`. This rule includes shell wrappers, inline code, variable indirection, and dry-run or check descriptions, and no earlier approval overrides it. Type-checking, parsing, or compiling such a file without running it (e.g. `tsc --noEmit <file>`, `node --check <file>`) does not execute it: judge that check unsafe the first time, and SAFE when the precedent rule at the end applies.",
4665
4786
  "Three broad categories are safe, and most commands you will see fall into one of them:",
4666
4787
  "(1) Reporting or listing existing state, with no side effect \u2014 e.g. `git status`, `npm ls`, `npm outdated`, `pip show`, `pip freeze`, `cargo tree`, `docker ps`, `docker images`, `kubectl get pods`, `terraform state list`. This includes a read-only GET-style query to a remote registry or API that only fetches public metadata and changes nothing remote (e.g. `npm view <package>`, `pip index versions <package>`, `curl` with no -X/--request other than GET and no -d/--data) \u2014 safe because nothing changes, not because it stays local.",
4667
4788
  '(2) A "preview" or "check" mode that reports what a change WOULD do, or reports a problem, without making the change. This is a general pattern, not a fixed list \u2014 ANY command that takes a dry-run/check/plan/validate/diff/list-only flag is safe when that flag is present, regardless of whether the same flag would normally appear on a "safe" kind of command: `terraform plan`, `terraform validate`, `terraform fmt -check`, `black --check`, `prettier --check`, `isort --check`, `gofmt -l` (list-only), `stylelint` with no `--fix`, and equally `git push --dry-run`, `npm publish --dry-run`, `kubectl apply --dry-run=client` \u2014 these last three are otherwise-unsafe operations (push, publish, cluster changes) that the dry-run flag turns into a report. The corresponding command WITHOUT that flag (e.g. `terraform apply`, `black .`, `gofmt -w`, `git push`) is a different, unsafe command \u2014 the flag is what makes the difference, for any tool, not just the ones named here. This cuts both ways, so do not assume a bare invocation is the safe one: `cargo fmt`, `black`, `prettier`, `isort`, `rustfmt`, and `terraform fmt` all REWRITE FILES by default and need an explicit check/diff/dry-run flag to become safe, while `gofmt`, `eslint`, `stylelint`, `rubocop`, and `ruff check` default to a safe check-only mode and need an explicit fix/write flag to become unsafe \u2014 the same-looking bare command is safe for one group and unsafe for the other, so judge each by what its own flags actually say, not by resemblance to a tool you already judged.',
4668
4789
  "(3) Running the project's own tests, type checker, linter, or build/compile step (e.g. `npm run build`, `yarn build`, `vite build`, `webpack`, `tsc`, `cargo build`, `go build`, `mvn compile`), as long as it is not passed an autofix/write/update flag (e.g. --fix, -u, --write, rubocop -a) \u2014 this holds even though the point of a build step is to write compiled output to a build/dist/target directory inside the project (e.g. a target/, build/, dist/, or __pycache__ directory): writing that output does not make the command unsafe. It stops being safe the moment the command chain goes past building \u2014 a step that also deploys, publishes, uploads, or pushes the build (e.g. `next build && vercel deploy`, `npm run build && npm publish`) is unsafe for that later segment even though the build segment itself is fine; judge each chained segment on its own, same as elsewhere in this list.",
4669
4790
  "Deleting or removing anything is unsafe, even something in a cache or build directory (e.g. `rm -rf __pycache__`, `cargo clean`, `git clean`), and even if the command otherwise fits one of the three categories above. Installing, uninstalling, or upgrading a dependency, changing a database schema, or writing to a path outside the project is also unsafe. A command chained with &&, ;, a pipe, or a line break (LF or CRLF) is only safe if every command segment is safe on its own. Treat each line as a separate command segment, even when the first line is safe.",
4670
- "One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe.",
4791
+ "One specific, narrow exception to all of the above: when the command literally invokes one of these runner programs BY NAME as the leading command \u2014 npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx \u2014 it is unsafe regardless of what it then runs, even something that looks like a harmless test/lint/typecheck command (e.g. `npx tsc --noEmit`, `uvx ruff check .`), because that runner can fetch and execute a different, unreviewed version of a package from a registry each time. This exception is about that specific syntax, not a general doubt about whether a tool is installed: a plain `ruff check .`, `pytest`, or any other bare command name is NOT this exception merely because you cannot verify from the string alone that it is installed \u2014 judge those the same as any other already-installed project tool, per the categories above. If you are unsure, judge it unsafe. This runner rule yields to the precedent rule at the end: a later invocation of an approved runner and package (e.g. `npx tsc ...`) is SAFE only when it has the same action and side-effect profile as the approved one.",
4671
4792
  ...approvedHistory.length > 0 ? [
4672
4793
  "The user has already explicitly approved these exact commands earlier in this session (working directory in parentheses, then the command):",
4673
4794
  approvedHistory.map((h) => `- (${h.cwd}) ${h.command}`).join("\n"),
4674
- "If the new command performs the same action and side-effect profile as one of these \u2014 differing only in a trivial way, such as a different file path, package name, or argument value that does not change what kind of action it is \u2014 judge it SAFE on that precedent, even if it would not otherwise fit categories (1)-(3) above. Do not stretch this to a command that merely shares a binary name, or superficially resembles one on the list while doing something riskier or of a different kind (e.g. an approved `rm build/tmp.log` does not license a new `rm -rf src/`, and an approved `git push origin feature-x` does not license `git push --force`)."
4795
+ "Precedent rule: if the new command performs the same action and side-effect profile as one of these \u2014 differing only in a trivial way, such as a different file path, flag, or argument value that does not change what kind of action it is \u2014 judge it SAFE on that precedent. A different package name is NOT trivial for a dependency install or a runner (npx, bunx, pnpm dlx, yarn dlx, pipx run, uvx): approving `npm install algoliasearch` does not approve `npm install <other-pkg>`, because installing runs lifecycle scripts. This overrides categories (1)-(3), the dependency install and upgrade rule, the runner rule, and the `.algolia-wizard/` check rule above. Precedent never makes SAFE a command that executes code under `.algolia-wizard/`. Do not stretch this to a command that merely shares a binary name, or superficially resembles one on the list while doing something riskier or of a different kind (e.g. an approved `rm build/tmp.log` does not license a new `rm -rf src/`, and an approved `git push origin feature-x` does not license `git push --force`)."
4675
4796
  ] : [],
4676
4797
  `Command: ${command}`,
4677
4798
  `Working directory: ${cwd}`,
@@ -4716,7 +4837,9 @@ async function resolveSecretShellEnv(ctx, command) {
4716
4837
  });
4717
4838
  }
4718
4839
  async function runAndRecord(ctx, command, environment) {
4719
- useWizard.getState().pushNotice({ messages: [`Running: ${command}`] });
4840
+ useWizard.getState().pushNotice({
4841
+ messages: [`Running: ${command.replace(/\s*\n\s*/g, " ").trim()}`]
4842
+ });
4720
4843
  const run2 = await (ctx.shell.run ?? runShell)(command, {
4721
4844
  cwd: environment.cwd,
4722
4845
  env: environment.values,
@@ -4789,16 +4912,16 @@ async function runUnreviewedCommand(ctx, command, cwd) {
4789
4912
  return runAndRecord(ctx, command, { cwd, values: env, secrets });
4790
4913
  }
4791
4914
  function runShellTool(ctx, createModel = defaultCreateModel) {
4792
- return tool9({
4915
+ return tool10({
4793
4916
  description: "Run a shell command in the project. Use this for anything the project needs done in its own ecosystem: installing dependencies, running a script you wrote, running the project's lint/typecheck/test commands. Do not use this to find or read files \u2014 use listFiles, searchFiles, and readFile instead of shell file-listing, reading, or search commands (ls/find/tree/cat/head/tail/grep/rg and similar). This tool refuses all of those outright: the dedicated tools enforce the project-root boundary and secret redaction that a raw shell read would bypass, and they won't count against this tool's command budget. The user approves any command that could change the project before it runs, so write a clear `explanation`. A command judged read-only (inspection, or running tests/typecheck/lint without an autofix flag, in any language) runs immediately without approval. If the user rejects a command, do not retry it \u2014 propose a different approach.",
4794
- inputSchema: z16.object({
4795
- command: z16.string().describe(
4917
+ inputSchema: z17.object({
4918
+ command: z17.string().describe(
4796
4919
  "The command to run, exactly as it would be typed in a shell. Pipes, && and redirects are allowed."
4797
4920
  ),
4798
- cwd: z16.string().optional().describe(
4921
+ cwd: z17.string().optional().describe(
4799
4922
  "Directory to run in, relative to the project root. Defaults to the project root."
4800
4923
  ),
4801
- explanation: z16.string().describe(
4924
+ explanation: z17.string().describe(
4802
4925
  "One short line telling the user what this command does and why, including any side effect (e.g. writes records to Algolia). This is what they approve against."
4803
4926
  )
4804
4927
  }),
@@ -4854,10 +4977,10 @@ function runShellTool(ctx, createModel = defaultCreateModel) {
4854
4977
  }
4855
4978
 
4856
4979
  // src/lib/tools/generateRecord.ts
4857
- import { tool as tool10, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
4980
+ import { tool as tool11, generateText as generateText2, Output as Output2, NoObjectGeneratedError } from "ai";
4858
4981
  import { createAnthropic as createAnthropic2 } from "@ai-sdk/anthropic";
4859
4982
  import { nanoid as nanoid2 } from "nanoid";
4860
- import z17 from "zod";
4983
+ import z18 from "zod";
4861
4984
  var DATA_DIR = ".algolia-wizard/data";
4862
4985
  var MAX_RECORDS = 100;
4863
4986
  var BATCH_SIZE = 10;
@@ -4874,20 +4997,20 @@ function defaultCreateModel2() {
4874
4997
  });
4875
4998
  }
4876
4999
  function generateRecordTool(ctx, createModel = defaultCreateModel2) {
4877
- return tool10({
5000
+ return tool11({
4878
5001
  description: "Generate realistic sample records for an entity and write them to a JSON file. Provide the entity name and its attributes; this tool asks a model to invent varied, realistic values, each with a unique objectID, and returns the file path to read them from at runtime. Do not invent the record values or objectIDs yourself, and do not inline the returned records into the script \u2014 call this tool and read the file it writes.",
4879
- inputSchema: z17.object({
4880
- entityName: z17.string().describe("Name of the entity to generate records for."),
4881
- attributes: z17.array(z17.string()).describe("Attribute names each record must contain."),
4882
- count: z17.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
4883
- hint: z17.string().optional().describe("Optional context to steer realistic values.")
5002
+ inputSchema: z18.object({
5003
+ entityName: z18.string().describe("Name of the entity to generate records for."),
5004
+ attributes: z18.array(z18.string()).describe("Attribute names each record must contain."),
5005
+ count: z18.number().int().min(1).max(MAX_RECORDS).default(10).describe(`How many records to generate (max ${MAX_RECORDS}).`),
5006
+ hint: z18.string().optional().describe("Optional context to steer realistic values.")
4884
5007
  }),
4885
5008
  execute: async ({ entityName, attributes, count, hint }) => {
4886
5009
  logger.info({ entityName, count }, "called generateRecord tool");
4887
5010
  try {
4888
5011
  const anthropic = createModel();
4889
- const value = z17.union([z17.string(), z17.number(), z17.boolean(), z17.null()]);
4890
- const recordSchema2 = z17.object(
5012
+ const value = z18.union([z18.string(), z18.number(), z18.boolean(), z18.null()]);
5013
+ const recordSchema2 = z18.object(
4891
5014
  Object.fromEntries(attributes.map((attr) => [attr, value]))
4892
5015
  );
4893
5016
  const generateBatch = async (batchCount) => {
@@ -4909,8 +5032,8 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
4909
5032
  }
4910
5033
  },
4911
5034
  output: Output2.object({
4912
- schema: z17.object({
4913
- records: z17.array(recordSchema2).length(batchCount)
5035
+ schema: z18.object({
5036
+ records: z18.array(recordSchema2).length(batchCount)
4914
5037
  })
4915
5038
  }),
4916
5039
  prompt: [
@@ -4981,13 +5104,13 @@ function generateRecordTool(ctx, createModel = defaultCreateModel2) {
4981
5104
  }
4982
5105
 
4983
5106
  // src/lib/tools/notifyUser.ts
4984
- import { tool as tool11 } from "ai";
4985
- import z18 from "zod";
5107
+ import { tool as tool12 } from "ai";
5108
+ import z19 from "zod";
4986
5109
  function notifyUserTool() {
4987
- return tool11({
5110
+ return tool12({
4988
5111
  description: `Give the user a brief, high-level update on what you are currently doing or about to do next. This is for the big picture (e.g. "Reading through your data models", "Writing the search UI") \u2014 not granular detail like individual tool calls, which are already logged separately. Call it when you start a new phase of work or your focus shifts, just not on every step, enough to keep the user engaged. Don't say things like "starting", just describe what you are doing. Don't mention tool calls themselves, just general direction of the work. Only the latest update is shown on screen, in one line, so keep it under 140 characters.`,
4989
- inputSchema: z18.object({
4990
- message: z18.string().max(140).describe(
5112
+ inputSchema: z19.object({
5113
+ message: z19.string().max(140).describe(
4991
5114
  "Short, plain-language description of what you are doing now. Under 140 characters \u2014 only the latest update is shown, on one line."
4992
5115
  )
4993
5116
  }),
@@ -5025,6 +5148,7 @@ function createTools(ctx, { output, tools }) {
5025
5148
  reportStatus: withLogging("reportStatus", reportStatusTool(output)),
5026
5149
  readFile: withLogging("readFile", readFileTool(ctx)),
5027
5150
  writeFile: withLogging("writeFile", writeFileTool(ctx)),
5151
+ editFile: withLogging("editFile", editFileTool(ctx)),
5028
5152
  writeCredentials: withLogging(
5029
5153
  "writeCredentials",
5030
5154
  writeCredentialsTool(ctx)
@@ -5052,9 +5176,19 @@ var FS_READ_TOOLS = [
5052
5176
  var MISSING_REPORT_STATUS_ERROR_MESSAGE = "Agent finished without calling reportStatus";
5053
5177
  var MISSING_REPORT_USER_MESSAGE = "This step ran into a problem finishing. Run the wizard again to retry it.";
5054
5178
  var REPORT_STATUS_RETRIES = 2;
5179
+ var STEP_LIMIT_INSTRUCTION = "When reportStatus is the only tool available, you reached the step limit. This limit is not a failure, so do not report fail because of it: report status=success with the work done so far, and name the remaining work in the summary.";
5180
+ var RECENT_STEPS_LOGGED = 3;
5181
+ var LOGGED_CHARS = 300;
5055
5182
  var ATTEMPT_NUMBER_OFFSET = 1;
5056
5183
  var INITIAL_ATTEMPT = 0;
5057
5184
  var PROVIDER_ERROR_USER_MESSAGE = "The AI service had trouble responding. Run the wizard again to retry this step.";
5185
+ var SESSION_TIMEOUT_USER_MESSAGE = "The wizard session timed out after a period of inactivity. Run the wizard again to retry this step.";
5186
+ var PROXY_AUTH_REJECTED_HEADER2 = "x-wizard-proxy-auth";
5187
+ var PROXY_REJECTION_BODY = "You shall not pass!";
5188
+ function isProxyInactivityRejection(err) {
5189
+ const cause = RetryError.isInstance(err) ? err.lastError : err;
5190
+ return APICallError.isInstance(cause) && cause.statusCode === 401 && cause.responseBody === PROXY_REJECTION_BODY && cause.responseHeaders?.[PROXY_AUTH_REJECTED_HEADER2] === void 0;
5191
+ }
5058
5192
  var MissingReportError = class extends Error {
5059
5193
  atStepLimit;
5060
5194
  constructor(atStepLimit) {
@@ -5062,6 +5196,13 @@ var MissingReportError = class extends Error {
5062
5196
  this.atStepLimit = atStepLimit;
5063
5197
  }
5064
5198
  };
5199
+ var AgentReportedFailureError = class extends Error {
5200
+ reason;
5201
+ constructor(reason) {
5202
+ super(`Agent reported failure: ${reason ?? "no reason given"}`);
5203
+ this.reason = reason;
5204
+ }
5205
+ };
5065
5206
  function isMissingReportRetry(kind) {
5066
5207
  return kind === "missingReport" || kind === "missingReportAtStepLimit";
5067
5208
  }
@@ -5134,6 +5275,7 @@ async function runAgentAttempt(req, attempt, retryReason) {
5134
5275
  "When you need to read or search multiple files, issue those tool calls together in one step rather than one at a time."
5135
5276
  ] : [],
5136
5277
  "Call notifyUser whenever you start a new phase of work or your focus shifts (e.g. moving from reading code to writing files) \u2014 a short, plain-language, high-level update. Do not call it for every tool use.",
5278
+ STEP_LIMIT_INSTRUCTION,
5137
5279
  // Last so a retry does not bust the cached tools+system prefix from
5138
5280
  // the first attempt. Inspect-and-continue, not start over: implement
5139
5281
  // shares the worktree and toolContext across attempts.
@@ -5181,7 +5323,11 @@ async function runAgentAttempt(req, attempt, retryReason) {
5181
5323
  stopWhen: [
5182
5324
  hasToolCall("reportStatus"),
5183
5325
  stepCountIs(profile.maxSteps)
5184
- ]
5326
+ ],
5327
+ prepareStep: ({ stepNumber }) => stepNumber >= profile.maxSteps - 1 ? { activeTools: ["reportStatus"] } : void 0,
5328
+ // Not in the settings type, but forwarded to streamText, whose default prints over the UI.
5329
+ ...{ onError: () => {
5330
+ } }
5185
5331
  });
5186
5332
  const stream = await agent.stream({
5187
5333
  prompt: "Follow system instructions"
@@ -5208,6 +5354,9 @@ async function runAgentAttempt(req, attempt, retryReason) {
5208
5354
  }
5209
5355
  if (streamError !== void 0) {
5210
5356
  logger.error({ err: streamError }, "agent stream reported an error");
5357
+ if (isProxyInactivityRejection(streamError)) {
5358
+ throw new Error(SESSION_TIMEOUT_USER_MESSAGE, { cause: streamError });
5359
+ }
5211
5360
  throw streamError;
5212
5361
  }
5213
5362
  const end = Date.now();
@@ -5257,19 +5406,33 @@ async function runAgentAttempt(req, attempt, retryReason) {
5257
5406
  }
5258
5407
  const result = report.output;
5259
5408
  if (result.status !== "success") {
5260
- throw new Error(
5261
- `Agent reported failure: ${result.reason ?? "no reason given"}`
5409
+ const steps = await stream.steps;
5410
+ logger.warn(
5411
+ {
5412
+ reason: result.reason,
5413
+ stepCount: steps.length,
5414
+ recentSteps: steps.slice(-RECENT_STEPS_LOGGED).map((s) => ({
5415
+ text: s.text?.slice(0, LOGGED_CHARS),
5416
+ toolCalls: s.toolCalls?.map((c) => c.toolName),
5417
+ toolResults: s.toolResults?.map((r) => ({
5418
+ tool: r.toolName,
5419
+ output: JSON.stringify(r.output)?.slice(0, LOGGED_CHARS)
5420
+ }))
5421
+ }))
5422
+ },
5423
+ "agent reported failure"
5262
5424
  );
5425
+ throw new AgentReportedFailureError(result.reason);
5263
5426
  }
5264
5427
  return result.output;
5265
5428
  }
5266
5429
 
5267
5430
  // src/actions/detectLanguage.ts
5268
- import z21 from "zod";
5269
- var detectLanguageSchema = z21.object({
5270
- languages: z21.array(z21.object({ name: z21.string(), version: z21.string() })),
5271
- frameworks: z21.array(z21.object({ name: z21.string(), version: z21.string() })),
5272
- publicEnvVarPrefix: z21.string().regex(/^([A-Z0-9]+_)*$/).describe(
5431
+ import z22 from "zod";
5432
+ var detectLanguageSchema = z22.object({
5433
+ languages: z22.array(z22.object({ name: z22.string(), version: z22.string() })),
5434
+ frameworks: z22.array(z22.object({ name: z22.string(), version: z22.string() })),
5435
+ publicEnvVarPrefix: z22.string().regex(/^([A-Z0-9]+_)*$/).describe(
5273
5436
  `The prefix a bundler/framework requires for an env var to reach client-side code (e.g. "NEXT_PUBLIC_", "VITE_", "NUXT_PUBLIC_"), uppercase with a trailing underscore when non-empty. Prefer the project's own existing convention over a framework default. Empty string when the project has no client-side env var exposure (e.g. a backend-only project).`
5274
5437
  )
5275
5438
  });
@@ -5329,31 +5492,31 @@ var detectLanguage = async () => {
5329
5492
  };
5330
5493
 
5331
5494
  // src/actions/analyzeCodebase.ts
5332
- import z22 from "zod";
5495
+ import z23 from "zod";
5333
5496
  var READONLY_TOOLS = [
5334
5497
  "listFiles",
5335
5498
  "changeDirectory",
5336
5499
  "readFile",
5337
5500
  "searchFiles"
5338
5501
  ];
5339
- var ingestionAnalysisSchema = z22.object({
5340
- ingestionAnalysis: z22.array(
5341
- z22.object({
5342
- name: z22.string(),
5343
- paths: z22.array(z22.string()),
5502
+ var ingestionAnalysisSchema = z23.object({
5503
+ ingestionAnalysis: z23.array(
5504
+ z23.object({
5505
+ name: z23.string(),
5506
+ paths: z23.array(z23.string()),
5344
5507
  // indexable fields the agent found for this entity
5345
- attributes: z22.array(z22.string())
5508
+ attributes: z23.array(z23.string())
5346
5509
  })
5347
5510
  )
5348
5511
  });
5349
- var searchImplementationAnalysisSchema = z22.object({
5350
- searchImplementationAnalysis: z22.string()
5512
+ var searchImplementationAnalysisSchema = z23.object({
5513
+ searchImplementationAnalysis: z23.string()
5351
5514
  });
5352
- var verificationSchema = z22.object({
5353
- verification: z22.array(z22.string())
5515
+ var verificationSchema = z23.object({
5516
+ verification: z23.array(z23.string())
5354
5517
  });
5355
5518
  var confirmedEntitiesFieldSchema = ingestionAnalysisSchema.shape.ingestionAnalysis.optional();
5356
- var analyzeCodebaseSchema = z22.object({
5519
+ var analyzeCodebaseSchema = z23.object({
5357
5520
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
5358
5521
  searchImplementationAnalysis: searchImplementationAnalysisSchema.shape.searchImplementationAnalysis.optional(),
5359
5522
  verification: verificationSchema.shape.verification.optional(),
@@ -5475,8 +5638,8 @@ async function askList(ctx, prompt, { required = false } = {}) {
5475
5638
  }
5476
5639
 
5477
5640
  // src/actions/confirmLanguage.ts
5478
- import z24 from "zod";
5479
- var confirmLanguageSchema = z24.object({
5641
+ import z25 from "zod";
5642
+ var confirmLanguageSchema = z25.object({
5480
5643
  languages: detectLanguageSchema.shape.languages
5481
5644
  });
5482
5645
  async function confirmLanguage(ctx) {
@@ -5497,8 +5660,8 @@ async function confirmLanguage(ctx) {
5497
5660
  }
5498
5661
 
5499
5662
  // src/actions/confirmFramework.ts
5500
- import z25 from "zod";
5501
- var confirmFrameworkSchema = z25.object({
5663
+ import z26 from "zod";
5664
+ var confirmFrameworkSchema = z26.object({
5502
5665
  frameworks: detectLanguageSchema.shape.frameworks
5503
5666
  });
5504
5667
  var CURATED_FRAMEWORKS = [
@@ -5636,8 +5799,8 @@ async function promptUser(ctx, params) {
5636
5799
  }
5637
5800
 
5638
5801
  // src/actions/confirmEntities.ts
5639
- import z26 from "zod";
5640
- var confirmEntitiesSchema = z26.object({
5802
+ import z27 from "zod";
5803
+ var confirmEntitiesSchema = z27.object({
5641
5804
  // Final detection — the focused re-run may supersede project-scan's.
5642
5805
  ingestionAnalysis: ingestionAnalysisSchema.shape.ingestionAnalysis.optional(),
5643
5806
  confirmedEntities: confirmedEntitiesFieldSchema,
@@ -5788,15 +5951,15 @@ async function confirmEntities(ctx) {
5788
5951
  }
5789
5952
 
5790
5953
  // src/actions/review.ts
5791
- import { z as z27 } from "zod";
5792
- var reviewSchema = z27.object({
5954
+ import { z as z28 } from "zod";
5955
+ var reviewSchema = z28.object({
5793
5956
  // Broad, high-level takeaways grouped by theme (e.g. ingestion, search UI),
5794
5957
  // not one entry per workflow step — a step's raw output can be a long,
5795
5958
  // multi-paragraph blob (see implement.ts's summaries.join), and mirroring
5796
5959
  // that 1:1 is what made the old per-step summary an unreadable wall of text.
5797
- summaryPoints: z27.array(z27.string()),
5798
- reviewPrompt: z27.string(),
5799
- nextSteps: z27.array(z27.string())
5960
+ summaryPoints: z28.array(z28.string()),
5961
+ reviewPrompt: z28.string(),
5962
+ nextSteps: z28.array(z28.string())
5800
5963
  });
5801
5964
  function formatCompletedSteps(steps) {
5802
5965
  if (!steps.length) return "(no prior steps completed)";
@@ -5831,9 +5994,17 @@ ${formatCompletedSteps(ctx.completedSteps)}`,
5831
5994
  };
5832
5995
 
5833
5996
  // src/actions/implement.ts
5834
- import z28 from "zod";
5835
- import { access, mkdir as mkdir5, readFile as readFile9 } from "node:fs/promises";
5836
- import { dirname as dirname5, isAbsolute as isAbsolute4, join as join11, relative as relative7, resolve as resolve5, sep as sep3 } from "node:path";
5997
+ import z29 from "zod";
5998
+ import { access, mkdir as mkdir5, readdir as readdir3, readFile as readFile10 } from "node:fs/promises";
5999
+ import {
6000
+ dirname as dirname5,
6001
+ extname,
6002
+ isAbsolute as isAbsolute4,
6003
+ join as join11,
6004
+ relative as relative7,
6005
+ resolve as resolve5,
6006
+ sep as sep3
6007
+ } from "node:path";
5837
6008
 
5838
6009
  // src/lib/git.ts
5839
6010
  import { execFile as execFile3 } from "node:child_process";
@@ -5913,6 +6084,12 @@ function toRootRelative(p) {
5913
6084
  return p.replace(/^\/+/, "");
5914
6085
  }
5915
6086
 
6087
+ // src/lib/dashboard.ts
6088
+ function browseUrl(appId, indexName) {
6089
+ const base = appId ? `https://dashboard.algolia.com/apps/${appId}/explorer/browse` : "https://dashboard.algolia.com/explorer/browse";
6090
+ return `${base}/${encodeURIComponent(indexName)}`;
6091
+ }
6092
+
5916
6093
  // src/lib/algoliaDocs.ts
5917
6094
  import { readFileSync, readdirSync, existsSync } from "node:fs";
5918
6095
  import { dirname as dirname4, join as join10 } from "node:path";
@@ -5972,36 +6149,36 @@ function getFrameworkSpecificDoc(frameworks) {
5972
6149
  }
5973
6150
 
5974
6151
  // src/actions/implement.ts
5975
- var implementSchema = z28.object({
5976
- summary: z28.string(),
5977
- ingestCommand: z28.string().optional(),
5978
- ingestScriptRan: z28.boolean().optional(),
5979
- ingestRecordCount: z28.number().optional(),
5980
- ingestDurationMs: z28.number().optional(),
5981
- ingestionSource: z28.enum(["local", "fileUpload", "generated"]),
5982
- searchConfig: z28.object({
5983
- filePath: z28.string().optional(),
5984
- vars: z28.array(
5985
- z28.object({
5986
- name: z28.string(),
5987
- value: z28.string()
6152
+ var implementSchema = z29.object({
6153
+ summary: z29.string(),
6154
+ ingestCommand: z29.string().optional(),
6155
+ ingestScriptRan: z29.boolean().optional(),
6156
+ ingestRecordCount: z29.number().optional(),
6157
+ ingestDurationMs: z29.number().optional(),
6158
+ ingestionSource: z29.enum(["local", "fileUpload", "generated"]),
6159
+ searchConfig: z29.object({
6160
+ filePath: z29.string().optional(),
6161
+ vars: z29.array(
6162
+ z29.object({
6163
+ name: z29.string(),
6164
+ value: z29.string()
5988
6165
  })
5989
6166
  )
5990
6167
  }).optional()
5991
6168
  });
5992
- var implementationOutputSchema = z28.object({
5993
- summary: z28.string(),
5994
- ingestCommand: z28.string().optional(),
6169
+ var implementationOutputSchema = z29.object({
6170
+ summary: z29.string(),
6171
+ ingestCommand: z29.string().optional(),
5995
6172
  // Only for the search use case: the path of whatever module the agent
5996
6173
  // defined the Algolia config constants in, so the wizard can check it
5997
6174
  // won't end up gitignored (it's public, meant to be committed).
5998
- searchConfigFile: z28.string().optional()
6175
+ searchConfigFile: z29.string().optional()
5999
6176
  });
6000
- var validationOutputSchema = z28.object({
6001
- summary: z28.string(),
6002
- sufficient: z28.boolean(),
6003
- additionalInstructions: z28.string().optional(),
6004
- unrelatedFailure: z28.string().optional()
6177
+ var validationOutputSchema = z29.object({
6178
+ summary: z29.string(),
6179
+ sufficient: z29.boolean(),
6180
+ additionalInstructions: z29.string().optional(),
6181
+ unrelatedFailure: z29.string().optional()
6005
6182
  });
6006
6183
  var MAX_IMPLEMENT_VALIDATION_ATTEMPTS = 3;
6007
6184
  var FIRST_IMPLEMENTATION_ATTEMPT = 1;
@@ -6013,8 +6190,8 @@ var INGESTION_SOURCE = {
6013
6190
  fileUpload: "Import a data file (CSV, JSON, TSV)",
6014
6191
  generated: "Generate sample data"
6015
6192
  };
6016
- var BUILD_CHECK_PROMPT = "Run type-check and lint now? This can take several minutes on a large repository.";
6017
- var MISSING_DEPENDENCIES_PROMPT = "Dependencies are not installed. Install them and run type-check and lint now?";
6193
+ var BUILD_CHECK_PROMPT = "Run type-check now? This can take several minutes on a large repository.";
6194
+ var MISSING_DEPENDENCIES_PROMPT = "Dependencies are not installed. Install them and run type-check now?";
6018
6195
  var UNRELATED_FAILURE_PROMPT = "These checks are still failing and appear unrelated to the changes. Continue the investigation?";
6019
6196
  var FILE_UPLOAD_PATH_PROMPT = "Enter the path to your JSON, CSV, or TSV file (relative to the project root, or absolute):";
6020
6197
  var JS_LANGUAGES = ["javascript", "typescript", "jsx", "tsx", "node"];
@@ -6053,10 +6230,12 @@ function baseInstructions(input) {
6053
6230
  `Project languages and frameworks: ${JSON.stringify(input.language)}`,
6054
6231
  "Make minimal, idiomatic changes; do not touch unrelated code.",
6055
6232
  `Commands run through a shell on ${process.platform}. Write commands that work there.`,
6056
- "Use the project's own tooling for every command \u2014 its package manager, task runner, and test/lint commands.",
6233
+ "Use the project's own tooling for every command \u2014 its package manager and task runner.",
6234
+ "Never run a linter or formatter, and do not fix lint or formatting issues. The developer did not ask for them.",
6057
6235
  "runShell needs the developer to approve each command, so give every call a clear `explanation` naming what it does and any side effect. If a command is rejected, do not retry it \u2014 take a different approach or report the limitation."
6058
6236
  ];
6059
6237
  }
6238
+ var FILE_EDIT_INSTRUCTION = "To change an existing file, call editFile with only the changed part. Use writeFile only to create a file; never rewrite an existing file with writeFile.";
6060
6239
  function sourceSpecificInstructions(input) {
6061
6240
  const byLine = {
6062
6241
  local: [
@@ -6089,15 +6268,23 @@ function algoliaClientDoc(input) {
6089
6268
  doc
6090
6269
  ];
6091
6270
  }
6271
+ var LARGE_RECORD_PLAN = /^(grow|elevate|premium)/i;
6272
+ function recordSizeInstruction(plan) {
6273
+ const limit = plan && LARGE_RECORD_PLAN.test(plan.trim()) ? "Algolia rejects a record larger than 100 KB with a status 400 error, and the average record size in the index must stay under 10 KB. Keep every record well under 100 KB and the average under 10 KB" : "Algolia rejects a record larger than 10 KB with a status 400 error. Keep every record under 10 KB";
6274
+ return `${limit}: split long text fields (for example an article body) into several records that share a parent identifier, or truncate them. Never push the whole text of a long document as one record. Check the size of the largest source item before you write the script.`;
6275
+ }
6092
6276
  function ingestionInstructions(input) {
6093
6277
  return [
6094
6278
  ...input.confirmed && input.confirmed.length ? [
6095
6279
  `Create an ingestion script under "${input.ingestDir}/" at the repo root. That directory already exists \u2014 writeFile creates any nested path itself, so never run a shell command just to create a directory.`,
6280
+ FILE_EDIT_INSTRUCTION,
6096
6281
  `Ingest only the confirmed entity (name, source paths, attributes): ${JSON.stringify(input.confirmed)}.`,
6097
6282
  `Ingesting writes to Algolia, so the script needs a write API key and App ID \u2014 read them from the ${API_KEY_VAR} and ${APP_ID_VAR} environment variables rather than hardcoding them. The wizard sets these when it runs the script.`,
6098
6283
  `Read the index name from the ${INDEX_NAME_VAR} environment variable, which the wizard sets to "${input.targetIndex}". Never hardcode an index name or derive one from the project, file, or entity name \u2014 the write key only works for that exact index. Exit with an error if ${INDEX_NAME_VAR} is unset.`,
6099
6284
  "Write the script in the project's primary language, using Algolia's official client for that language. Do not use the raw HTTP API.",
6100
6285
  "After a successful ingest, the script must print exactly one line to stdout in the form `ALGOLIA_WIZARD_RECORD_COUNT=<n>`, where <n> is the total number of records pushed to Algolia. Print it last, on its own line, with no surrounding text.",
6286
+ recordSizeInstruction(input.appPlan),
6287
+ "When you split one source item into several records, keep the fields that the search UI shows (title, description, URL parts) on every record. After you push the records, the script must set the index settings with attributeForDistinct set to the parent identifier and distinct set to true, so that a search returns each source item once.",
6101
6288
  ...algoliaClientDoc(input),
6102
6289
  "Install the Algolia client with the project's own package manager via runShell, declaring it in whatever manifest the project uses (e.g. package.json, requirements.txt, Gemfile, go.mod, composer.json) so the dependency is not just installed ad hoc.",
6103
6290
  `If the script loads its env vars from a file (e.g. via dotenv or an equivalent for its language) rather than the process environment directly, decide that up front and call writeCredentials on that file before you finish the script \u2014 do not wait to discover the need for it by having a writeFile call refused.`,
@@ -6127,7 +6314,7 @@ function searchInstructions(input) {
6127
6314
  const placementMessage = "Place content-specific search beside the heading or controls for the corresponding browse/listing task. Use shared navigation only for genuinely app-wide search and only when it fits the available space and responsive layout without crowding primary actions.";
6128
6315
  const replacementMessage = "If a search control with the same content scope and purpose already exists, replace its usage with the new component while preserving its intentional placement, and delete the implementation it supersedes. Do not remove or replace unrelated filters, navigation, or differently scoped search.";
6129
6316
  const accessibilityInstructions = [
6130
- "Target WCAG 2.2 AA. Use semantic search structure and give the input a persistent accessible name: prefer a visible associated label and use the project's visually-hidden utility where the layout has no room for one. A placeholder or a labeled search landmark does not name the input. When the input uses aria-labelledby, every referenced element must exist and contain meaningful, content-specific text. Preserve a widget's generated label when it provides one and make it content-specific through supported options; if the installed widget exposes no such option, use its documented connector or headless API to render a labeled input instead of patching generated DOM.",
6317
+ "Target WCAG 2.2 AA. Use semantic search structure and give the input a persistent accessible name: prefer a visible associated label and use the project's visually-hidden utility where the layout has no room for one. A placeholder or a labeled search landmark does not name the input. When the input uses aria-labelledby, every referenced element must exist and contain meaningful, content-specific text. Preserve a widget's generated label when it provides one and make it content-specific through supported options. If the SDK reference says that no option changes the generated label, accept that label as a known widget limitation: do not rebuild the widget headless, do not patch generated DOM, and name the gap in the summary.",
6131
6318
  "Preserve widget-provided combobox, popup, and item roles, relationships, and keyboard behavior; for a headless widget, implement the documented semantics instead of inventing different ARIA. Tab must reach the input and controls, Arrow keys move through results, Enter selects, and Escape dismisses without trapping or losing focus.",
6132
6319
  "Meet 4.5:1 contrast for normal text, including input values, placeholders, and result text, and 3:1 for large text, meaningful icons, control boundaries, and focus indicators against adjacent colors. Keep focus visible and unobscured, and never communicate active, loading, empty, or error states by color alone.",
6133
6320
  "Keep loading, empty, and error status text perceivable to assistive technology. Do not assume a widget announces these states: use documented announcements when present, otherwise add a persistent polite live region driven by the current search status, results, and errors.",
@@ -6139,22 +6326,66 @@ function searchInstructions(input) {
6139
6326
  indexedContentMessage ?? "",
6140
6327
  `Build the search UI for ${input.searchUiTarget}.`,
6141
6328
  "Create search UI only. Do not create or modify ingestion scripts, rake/manage/CLI tasks, migrations, seeders, or other data-loading code, even if the data looks incomplete.",
6329
+ FILE_EDIT_INSTRUCTION,
6330
+ EXISTING_COMPONENT_INSTRUCTION,
6142
6331
  mountCandidateMessage,
6143
6332
  placementMessage,
6144
6333
  replacementMessage,
6145
6334
  "Inspect the chosen mount area at the app's existing breakpoints before writing CSS. Preserve its visual hierarchy, alignment, spacing, and primary actions rather than forcing the search control into an arbitrary gap.",
6146
6335
  ...packageManagedInstructions,
6336
+ CSP_INSTRUCTION,
6147
6337
  ...accessibilityInstructions,
6148
6338
  "Style the search surfaces through the widget's class and CSS-variable overrides (or, when hand-built, the app's existing theme tokens): give the results panel and the element that actually draws the input's border and background \u2014 for a widget, the form or wrapper it renders around the bare input \u2014 non-transparent surfaces, keep that frame distinguishable from the background behind it in every supported theme, and never assume a light surface or reuse the surrounding page's colors unchanged inside the panel.",
6149
6339
  "The panel takes its width from the input by default, so a small input makes it unreadably narrow: give it `min-width: min(320px, calc(100vw - 2rem))` independent of the input, cap its maximum width to the viewport, and anchor it to the input edge it opens from so widening does not push it off-screen.",
6150
6340
  "Match the application styles closely, including light, dark, and high-contrast themes the project already supports.",
6151
- "Before reporting, mount the component and fix any type-check or lint errors your changes cause. If a tool limit is reached or an error remains unresolved, keep the implementation, report status=success, and name the remaining issue in the summary so validation can drive the next pass. Report status=fail only when no search implementation can be written, with the concrete blocker as the reason.",
6341
+ "Before reporting, mount the component. Do not run a type check, build, or test command: the validation pass runs the type check, after the developer agrees to it. If a tool limit is reached or an error remains unresolved, keep the implementation, report status=success, and name the remaining issue in the summary so validation can drive the next pass. Report status=fail only when no search implementation can be written, with the concrete blocker as the reason.",
6152
6342
  "The summary should be extremely concise; do not mention manual testing steps."
6153
6343
  ];
6154
6344
  }
6345
+ var EXISTING_COMPONENT_INSTRUCTION = "A search component for this content can already exist, for example from an earlier run. Do not trust it: compare the record fields that it reads with the real records, and rewrite it when they differ. Do not report success while the UI reads a field that no record has.";
6346
+ var CSP_INSTRUCTION = 'Search for a Content-Security-Policy (next.config, middleware, proxy, server headers, or a meta tag). If the project sets one, it blocks Algolia until you allow it: add https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io to connect-src, and load search-insights from npm as the SDK reference describes instead of from jsDelivr. Without this, every search fails with "Unreachable hosts" and the widget stays empty.';
6347
+ var INGEST_SCRIPT_EXTENSIONS = /* @__PURE__ */ new Set([
6348
+ ".sh",
6349
+ ".py",
6350
+ ".js",
6351
+ ".mjs",
6352
+ ".cjs",
6353
+ ".ts",
6354
+ ".rb",
6355
+ ".php",
6356
+ ".go"
6357
+ ]);
6358
+ async function findIngestionScript(repoRoot, ingestDir, ingestCommand) {
6359
+ const fromCommand = ingestCommand?.split(/\s+/).map((token) => token.replace(/^["']|["']$/g, "")).find((token) => token.startsWith(`${ingestDir}/`));
6360
+ if (fromCommand) {
6361
+ try {
6362
+ await access(join11(repoRoot, fromCommand));
6363
+ return fromCommand;
6364
+ } catch {
6365
+ }
6366
+ }
6367
+ try {
6368
+ const entries = await readdir3(join11(repoRoot, ingestDir), {
6369
+ withFileTypes: true
6370
+ });
6371
+ const name = entries.filter(
6372
+ (entry) => entry.isFile() && !entry.name.startsWith(".") && INGEST_SCRIPT_EXTENSIONS.has(extname(entry.name))
6373
+ ).map((entry) => entry.name).sort()[0];
6374
+ return name && `${ingestDir}/${name}`;
6375
+ } catch {
6376
+ return void 0;
6377
+ }
6378
+ }
6379
+ function recordSourceInstruction(scriptPath) {
6380
+ return `The records in the index come from the ingestion script "${scriptPath}" (repo root). Read that one file to learn each record's objectID, its fields, and how they map to the app's data and URLs. Do not search the backend, api, or scripts folders for them, and do not use shell reads (sed, grep, cat) to explore. Read the fields with their exact attribute names from the records: a dotted name such as "copy.en.title" is one flat attribute, so read it as item["copy.en.title"]. Use only the fields of the current locale. When the script splits one item into several records that share a parent identifier, the index returns each item once, so show one result per item.`;
6381
+ }
6382
+ function recordFieldCheckInstruction(scriptPath) {
6383
+ return `Read the ingestion script "${scriptPath}" (repo root). Check that every record field that the search UI reads (title, description, link parts) exists in the records with that exact attribute name. A dotted name such as "copy.en.title" is one flat attribute. If the UI reads a field that no record has, set sufficient=false and name the wrong field and the right one in additionalInstructions. Do not use shell reads to explore.`;
6384
+ }
6155
6385
  function packageSearchInstructions(input) {
6156
6386
  const doc = getFrameworkSpecificDoc(frameworksForDoc(input.language));
6157
6387
  return [
6388
+ "To check an installed package version, call readFile on node_modules/<package>/package.json. Do not otherwise explore node_modules, and do not run node -e or other scripts to discover a package API. If the reference below does not cover a detail, choose the documented option and name the gap in the summary.",
6158
6389
  ...doc ? [
6159
6390
  "Follow the Algolia SDK reference below for client setup, search UI wiring, and Insights instrumentation \u2014 Insights is required, not optional; prefer the reference over prior knowledge:",
6160
6391
  doc
@@ -6188,25 +6419,30 @@ function validationInstructions(input) {
6188
6419
  "Validate the Algolia search implementation by inspecting the project source.",
6189
6420
  `Indexed content context: ${JSON.stringify(input.findings.confirmedEntities?.[0] ?? null)}.`,
6190
6421
  `The confirmed placement candidate was "${input.searchLocation ?? "unknown"}", but verify the actual mount rather than assuming the candidate was used.`,
6191
- "Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on lint, type checks, or tests.",
6422
+ "Inspect the generated search component, its actual mount file, that file's parent layout and sibling controls, and the relevant content UI. Do not rely only on type checks or tests.",
6192
6423
  "Confirm that the application renders exactly one search bar. Check for an old search bar and duplicate mounts.",
6424
+ "If the project sets a Content-Security-Policy, confirm that connect-src allows https://*.algolia.net, https://*.algolianet.com, and https://insights.algolia.io, and that search-insights is loaded from npm. Otherwise set sufficient=false.",
6193
6425
  "Confirm content-specific search is placed with the corresponding content or task controls. Shared navigation is valid only for genuinely app-wide search that fits the visual hierarchy and responsive layout without crowding primary actions.",
6194
6426
  "Confirm the input has a persistent accessible name; widget-provided combobox, popup, and item relationships and keyboard behavior are preserved; and loading, empty, and error status text is perceivable to assistive technology.",
6195
6427
  "Inspect the CSS that actually applies to the generated DOM. Confirm the results panel and the element drawing the input frame have non-transparent surfaces, the input frame is distinguishable from its surrounding background in every supported theme, text meets 4.5:1 contrast, meaningful boundaries and focus indicators meet 3:1, and no state relies on color alone.",
6196
6428
  "Confirm the search experience reflows without horizontal page scrolling at 320 CSS pixels, keeps its panel on-screen, preserves visible focus, and respects reduced-motion preferences for any added animation.",
6429
+ "If the project imports an InstantSearch theme and overrides its palette, confirm that the palette overrides (--ais-*-rgb) win over the theme: they must use a higher specificity than the theme, for example `:root:root`, and for a dark mode `:root:root.dark` or `:root:root[data-theme=dark]`, because the theme sets its own blue in `:root` and in `:root[data-theme=dark], .dark`. `:root:root` alone ties with `:root[data-theme=dark]`. If an override can lose to the theme, set sufficient=false, because the search icon and focus ring then show the theme blue instead of the app accent color.",
6430
+ "When the project has a package.json, confirm that the theme CSS of the search widget (for InstantSearch, a theme from instantsearch.css) is imported once, from the component or the global stylesheet. If it is installed but not imported, set sufficient=false.",
6197
6431
  "Confirm that the search component is reachable from and fits the corresponding content task at every supported breakpoint; require app-wide reachability only when the search scope is genuinely app-wide.",
6198
6432
  "Do not run the test suite or any test command.",
6199
6433
  "Do not modify files. Report each implementation issue as a concrete instruction for the next implementation pass.",
6200
- `Available type-check and lint tools: ${JSON.stringify(input.findings.verification ?? [])}.`,
6434
+ `Detected verification tools: ${JSON.stringify(input.findings.verification ?? [])}. Use only a type checker; ignore any linter or formatter in this list.`,
6201
6435
  ...!input.frontendHasPackageJson ? [
6202
6436
  "The frontend has no package.json: never create one or run npm, pnpm, yarn, or bun. Validate the CDN implementation by inspecting its HTML or template and classic scripts for the search container, Algolia \u2192 InstantSearch \u2192 config \u2192 search load order, browser globals, autocomplete widget configuration, search.start(), and exact pinned CDN URLs and integrity attributes for every Algolia script or stylesheet. Source inspection is required even when the project has no automated checks. window.instantsearch.widgets.autocomplete is a real widget in the pinned InstantSearch build; do not report its use as an error, and do not fetch a remote bundle or URL to verify it."
6203
6437
  ] : [],
6204
6438
  "Always call reportStatus with status=success after validation, even when sufficient=false.",
6205
6439
  "Set sufficient=false if the input's computed accessible name is empty or generic, including when aria-labelledby references a missing or empty element; a placeholder does not count as the label.",
6440
+ 'Exception: the InstantSearch Autocomplete widget (react-instantsearch or instantsearch.js) names its input "Submit" through its generated label (the input has aria-labelledby pointing at that label), and no widget option changes it. Accept that name when the widget is used unmodified, and do not ask for an aria-label, inputProps, or a label wrapper: do not set sufficient=false for it, and do not request a headless rebuild or DOM patch.',
6206
6441
  "Set sufficient=true when the search bar is unique and structurally complete and no placement or accessibility violation was found.",
6207
6442
  "Set sufficient=false when a required placement or accessibility property is observed to fail, the implementation is incomplete, or checks have implementation-caused failures; include concrete additionalInstructions for the next pass.",
6208
- "If any required placement or accessibility property cannot be established with the available tools\u2014including contrast, keyboard behavior, 320-pixel reflow, or live-region status\u2014set sufficient=false and request concrete verification or a fix in additionalInstructions; do not let an unverified requirement pass as a manual follow-up.",
6209
- "If a check fails only in untouched code, set unrelatedFailure to a concise failure description."
6443
+ "You can only read source files. If a required property can be checked in the source, check it there. If it can be established only in a browser (rendered contrast, keyboard behavior, 320-pixel reflow, live-region status, computed accessible name), set sufficient=true when the source shows no violation. Never request devtools, a screenshot, or a contrast tool, and never repeat a finding that an earlier pass already fixed.",
6444
+ "If a check fails only in untouched code, set unrelatedFailure to a concise failure description.",
6445
+ "Keep the summary to one short sentence. Put the details in additionalInstructions."
6210
6446
  ];
6211
6447
  }
6212
6448
  var IMPLEMENT_CONFIG = {
@@ -6227,12 +6463,19 @@ var useCaseToolMap = {
6227
6463
  ingestion: [
6228
6464
  ...FS_READ_TOOLS,
6229
6465
  "writeFile",
6466
+ "editFile",
6230
6467
  "writeCredentials",
6231
6468
  "runShell",
6232
6469
  "reviewScript",
6233
6470
  "notifyUser"
6234
6471
  ],
6235
- search: [...FS_READ_TOOLS, "writeFile", "runShell", "notifyUser"],
6472
+ search: [
6473
+ ...FS_READ_TOOLS,
6474
+ "writeFile",
6475
+ "editFile",
6476
+ "runShell",
6477
+ "notifyUser"
6478
+ ],
6236
6479
  validation: [...FS_READ_TOOLS, "runShell", "notifyUser"]
6237
6480
  };
6238
6481
  function toolsForUseCase(useCase, ingestionSource) {
@@ -6306,7 +6549,7 @@ function jsTypeName(value) {
6306
6549
  }
6307
6550
  async function firstRecordIn(filePath) {
6308
6551
  try {
6309
- const parsed = JSON.parse(await readFile9(filePath, "utf8"));
6552
+ const parsed = JSON.parse(await readFile10(filePath, "utf8"));
6310
6553
  const first = Array.isArray(parsed) ? parsed[0] : parsed;
6311
6554
  if (first && typeof first === "object" && !Array.isArray(first)) {
6312
6555
  return first;
@@ -6363,6 +6606,12 @@ function makeToolContext(root, appId, env = async () => ({}), secretEnv = async
6363
6606
  appId
6364
6607
  };
6365
6608
  }
6609
+ var ERROR_SUMMARY_MAX_CHARS = 200;
6610
+ function briefSummary(summary) {
6611
+ const firstLine = summary.trim().split("\n")[0] ?? "";
6612
+ if (!firstLine) return "no summary given";
6613
+ return firstLine.length > ERROR_SUMMARY_MAX_CHARS ? `${firstLine.slice(0, ERROR_SUMMARY_MAX_CHARS - 1)}\u2026` : firstLine;
6614
+ }
6366
6615
  function validationRetryInstructions(validation) {
6367
6616
  return [
6368
6617
  `Implementation insufficient. Address these findings before reporting completion: ${validation.additionalInstructions ?? validation.summary}`
@@ -6420,7 +6669,7 @@ function installCheckInstructions(plan, attempt) {
6420
6669
  }
6421
6670
  return ["Dependencies were already installed. Do not install them again."];
6422
6671
  }
6423
- function validationRunInstructions(plan, changedFiles, attempt) {
6672
+ function validationRunInstructions(plan, attempt) {
6424
6673
  if (!plan.run) {
6425
6674
  return [
6426
6675
  "The developer chose to skip build checks. Do not install dependencies or run shell checks."
@@ -6431,7 +6680,6 @@ function validationRunInstructions(plan, changedFiles, attempt) {
6431
6680
  ...installCheckInstructions(plan, attempt),
6432
6681
  "Do not use npx, dlx, or another package runner that can download an undeclared tool.",
6433
6682
  "Run the type check once with the project's existing command, when one is available.",
6434
- `Lint only these changed files: ${JSON.stringify(changedFiles)}. Do not run a full-project lint. If this list is empty, do not run lint.`,
6435
6683
  "Do not use an autofix option during validation."
6436
6684
  ];
6437
6685
  }
@@ -6526,11 +6774,16 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6526
6774
  const searchLocation = findings.searchImplementationAnalysis;
6527
6775
  let appId;
6528
6776
  let ingestAppId;
6777
+ let appPlan;
6529
6778
  if (useCases.includes("search")) {
6530
- appId = (await requireApplication(ctx.appId)).id;
6779
+ const app = await requireApplication(ctx.appId);
6780
+ appId = app.id;
6781
+ appPlan = app.plan;
6531
6782
  }
6532
6783
  if (useCases.includes("ingestion")) {
6533
- ingestAppId = appId ?? (await requireApplication(ctx.appId)).id;
6784
+ const app = appId ? void 0 : await requireApplication(ctx.appId);
6785
+ ingestAppId = appId ?? app?.id;
6786
+ appPlan ??= app?.plan;
6534
6787
  }
6535
6788
  let ingestWriteKey;
6536
6789
  if (useCases.includes("ingestion") && ingestAppId) {
@@ -6564,6 +6817,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6564
6817
  targetIndex,
6565
6818
  language,
6566
6819
  appId,
6820
+ appPlan,
6567
6821
  searchKey,
6568
6822
  searchKeyError,
6569
6823
  ingestDir: INGEST_DIR,
@@ -6577,6 +6831,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6577
6831
  };
6578
6832
  let agentRuns = 0;
6579
6833
  let ingestCommand;
6834
+ let ingestionScript;
6580
6835
  let ingestScriptRan = false;
6581
6836
  let ingestRecordCount;
6582
6837
  let ingestDurationMs;
@@ -6596,18 +6851,31 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6596
6851
  async function runImplementationUseCase(currentUseCase, extraInstructions = [], isRetry = false) {
6597
6852
  if (agentRuns > 0) ctx.recordStepExecution();
6598
6853
  agentRuns += 1;
6599
- return runAgent({
6600
- operation: `${currentUseCase}-implementation`,
6601
- instructions: buildAgentInstructions(
6602
- currentUseCase,
6603
- input,
6604
- extraInstructions
6605
- ),
6606
- tools: toolsForUseCase(currentUseCase, input.ingestionSource),
6607
- outputSchema: implementationOutputSchema,
6608
- modelProfile: isRetry ? "implementationRetry" /* implementationRetry */ : "implementation" /* implementation */,
6609
- toolContext: currentUseCase === "ingestion" ? ingestionTools : makeToolContext(repoRoot)
6610
- });
6854
+ const toolContext = currentUseCase === "ingestion" ? ingestionTools : makeToolContext(repoRoot);
6855
+ try {
6856
+ return await runAgent({
6857
+ operation: `${currentUseCase}-implementation`,
6858
+ instructions: buildAgentInstructions(currentUseCase, input, [
6859
+ ...currentUseCase === "search" && ingestionScript ? [recordSourceInstruction(ingestionScript)] : [],
6860
+ ...extraInstructions
6861
+ ]),
6862
+ tools: toolsForUseCase(currentUseCase, input.ingestionSource),
6863
+ outputSchema: implementationOutputSchema,
6864
+ modelProfile: isRetry ? "implementationRetry" /* implementationRetry */ : "implementation" /* implementation */,
6865
+ toolContext
6866
+ });
6867
+ } catch (err) {
6868
+ if (currentUseCase === "search" && err instanceof AgentReportedFailureError && readLimitReached(toolContext)) {
6869
+ logger.warn(
6870
+ { reason: err.reason, counts: toolContext.counts },
6871
+ "implement: search agent reported fail at a read limit; continuing to validation"
6872
+ );
6873
+ return {
6874
+ summary: "Stopped at a read limit before the implementation was complete."
6875
+ };
6876
+ }
6877
+ throw err;
6878
+ }
6611
6879
  }
6612
6880
  async function runValidationUseCase(extraInstructions) {
6613
6881
  if (agentRuns > 0) ctx.recordStepExecution();
@@ -6619,6 +6887,7 @@ async function implement(ctx, useCases = DEFAULT_IMPLEMENT_USE_CASES) {
6619
6887
  operation: "search-validation",
6620
6888
  instructions: buildAgentInstructions("validation", input, [
6621
6889
  writtenFiles.length ? `Files written during search implementation: ${JSON.stringify(writtenFiles)}. Inspect these first, then trace the actual import and render site.` : "The search agent reported no written files. Inspect the current diff and set sufficient=false if no complete mounted search implementation exists.",
6890
+ ...ingestionScript ? [recordFieldCheckInstruction(ingestionScript)] : [],
6622
6891
  ...extraInstructions
6623
6892
  ]),
6624
6893
  tools: toolsForUseCase("validation"),
@@ -6714,7 +6983,7 @@ ${detail}` : ""}`
6714
6983
  messages: [
6715
6984
  ingestOutcomeMessage,
6716
6985
  "View your ingested records in the Algolia dashboard:",
6717
- "https://dashboard.algolia.com/explorer/browse",
6986
+ browseUrl(ingestAppId, targetIndex),
6718
6987
  "When you're ready to continue, we'll move on to search implementation."
6719
6988
  ]
6720
6989
  });
@@ -6739,6 +7008,11 @@ ${detail}` : ""}`
6739
7008
  }
6740
7009
  let searchConfigFile;
6741
7010
  if (useCases.includes("search")) {
7011
+ ingestionScript = await findIngestionScript(
7012
+ repoRoot,
7013
+ INGEST_DIR,
7014
+ ingestCommand
7015
+ );
6742
7016
  let extraInstructions = [];
6743
7017
  let buildCheckPlan;
6744
7018
  const confirmedFrameworks = language.frameworks ?? [];
@@ -6786,11 +7060,8 @@ ${detail}` : ""}`
6786
7060
  searchConfigFile = searchResult.searchConfigFile;
6787
7061
  }
6788
7062
  buildCheckPlan ??= await resolveBuildCheckPlan(ctx, repoRoot, input);
6789
- const searchFilesChanged = [
6790
- ...new Set(useWizard.getState().writtenFiles)
6791
- ].map((file) => relative7(repoRoot, file));
6792
7063
  const validation = await runValidationUseCase(
6793
- validationRunInstructions(buildCheckPlan, searchFilesChanged, attempt)
7064
+ validationRunInstructions(buildCheckPlan, attempt)
6794
7065
  );
6795
7066
  summaries.push(formatSummary("validation", validation.summary));
6796
7067
  const finalAttempt = attempt === MAX_IMPLEMENT_VALIDATION_ATTEMPTS;
@@ -6824,8 +7095,16 @@ ${detail}` : ""}`
6824
7095
  if (finalAttempt) {
6825
7096
  ctx.setUserInput("implementation", "fail");
6826
7097
  trackComponentGenerated(false, attempt);
7098
+ logger.warn(
7099
+ {
7100
+ summary: validation.summary,
7101
+ additionalInstructions: validation.additionalInstructions,
7102
+ unrelatedFailure: validation.unrelatedFailure
7103
+ },
7104
+ "implement: search validation failed on the final attempt"
7105
+ );
6827
7106
  throw new Error(
6828
- `Unable to validate the implementation after ${MAX_IMPLEMENT_VALIDATION_ATTEMPTS} attempts: ${validation.additionalInstructions ?? validation.unrelatedFailure ?? validation.summary}`
7107
+ `Unable to validate the implementation after ${MAX_IMPLEMENT_VALIDATION_ATTEMPTS} attempts (details in wizard.log): ${briefSummary(validation.summary)}`
6829
7108
  );
6830
7109
  }
6831
7110
  extraInstructions = validation.sufficient ? [] : validationRetryInstructions(validation);
@@ -6911,8 +7190,8 @@ var defaultWorkflow = {
6911
7190
  defineStep({
6912
7191
  id: "select-index",
6913
7192
  title: "Set up index",
6914
- outputSchema: z29.object({
6915
- selection: z29.string()
7193
+ outputSchema: z30.object({
7194
+ selection: z30.string()
6916
7195
  }),
6917
7196
  run: (ctx) => selectIndexStep(ctx)
6918
7197
  }),
@@ -6973,7 +7252,7 @@ var defaultWorkflow = {
6973
7252
  import "zod";
6974
7253
 
6975
7254
  // src/actions/playbookSteps/selectPlaybook.ts
6976
- import { z as z32 } from "zod";
7255
+ import { z as z33 } from "zod";
6977
7256
 
6978
7257
  // src/lib/dashboardApi/client.ts
6979
7258
  import "zod";
@@ -7032,67 +7311,67 @@ async function dashboardError(res) {
7032
7311
  }
7033
7312
 
7034
7313
  // src/lib/dashboardApi/schemas.ts
7035
- import { z as z31 } from "zod";
7036
- var messageResponseSchema = z31.object({
7037
- message: z31.string()
7314
+ import { z as z32 } from "zod";
7315
+ var messageResponseSchema = z32.object({
7316
+ message: z32.string()
7038
7317
  });
7039
- var playbookSchema = z31.object({
7040
- slug: z31.string(),
7041
- category: z31.string().optional(),
7042
- title: z31.string(),
7043
- description: z31.string().optional()
7318
+ var playbookSchema = z32.object({
7319
+ slug: z32.string(),
7320
+ category: z32.string().optional(),
7321
+ title: z32.string(),
7322
+ description: z32.string().optional()
7044
7323
  });
7045
- var playbooksListSchema = z31.array(playbookSchema);
7046
- var playbookExecutionStepSchema = z31.enum([
7324
+ var playbooksListSchema = z32.array(playbookSchema);
7325
+ var playbookExecutionStepSchema = z32.enum([
7047
7326
  "assessment",
7048
7327
  "optimization",
7049
7328
  "implement"
7050
7329
  ]);
7051
- var playbookExecutionStepStatusSchema = z31.enum([
7330
+ var playbookExecutionStepStatusSchema = z32.enum([
7052
7331
  "not_started",
7053
7332
  "in_progress",
7054
7333
  "done",
7055
7334
  "error"
7056
7335
  ]);
7057
- var schemaComparisonEntrySchema = z31.object({
7058
- canonical_attr: z31.string(),
7059
- canonical_type: z31.array(z31.string()),
7060
- canonical_roles: z31.array(z31.string()),
7061
- canonical_required: z31.boolean(),
7062
- index_attr: z31.string().nullable(),
7063
- index_type: z31.array(z31.string()).nullable(),
7064
- index_required: z31.boolean().nullable(),
7065
- status: z31.string(),
7066
- mapping_confidence: z31.number()
7336
+ var schemaComparisonEntrySchema = z32.object({
7337
+ canonical_attr: z32.string(),
7338
+ canonical_type: z32.array(z32.string()),
7339
+ canonical_roles: z32.array(z32.string()),
7340
+ canonical_required: z32.boolean(),
7341
+ index_attr: z32.string().nullable(),
7342
+ index_type: z32.array(z32.string()).nullable(),
7343
+ index_required: z32.boolean().nullable(),
7344
+ status: z32.string(),
7345
+ mapping_confidence: z32.number()
7067
7346
  });
7068
- var settingsRecommendationSchema = z31.record(
7069
- z31.string(),
7070
- z31.object({
7071
- current: z31.any(),
7072
- recommended: z31.any()
7347
+ var settingsRecommendationSchema = z32.record(
7348
+ z32.string(),
7349
+ z32.object({
7350
+ current: z32.any(),
7351
+ recommended: z32.any()
7073
7352
  })
7074
7353
  );
7075
- var assessmentResultSchema = z31.object({
7076
- ready: z31.boolean(),
7077
- schema_comparison: z31.array(schemaComparisonEntrySchema),
7354
+ var assessmentResultSchema = z32.object({
7355
+ ready: z32.boolean(),
7356
+ schema_comparison: z32.array(schemaComparisonEntrySchema),
7078
7357
  settings_recommendation: settingsRecommendationSchema
7079
7358
  });
7080
- var playbookExecutionSchema = z31.object({
7081
- uuid: z31.string(),
7082
- playbook_slug: z31.string(),
7083
- application_id: z31.string(),
7084
- source_index_name: z31.string(),
7359
+ var playbookExecutionSchema = z32.object({
7360
+ uuid: z32.string(),
7361
+ playbook_slug: z32.string(),
7362
+ application_id: z32.string(),
7363
+ source_index_name: z32.string(),
7085
7364
  current_step: playbookExecutionStepSchema,
7086
7365
  step_status: playbookExecutionStepStatusSchema,
7087
- status_reason: z31.string().nullable(),
7088
- completed: z31.boolean(),
7366
+ status_reason: z32.string().nullable(),
7367
+ completed: z32.boolean(),
7089
7368
  assessment_result: assessmentResultSchema.nullable(),
7090
- settings_snapshot: z31.json().nullable(),
7091
- created_at: z31.iso.datetime()
7369
+ settings_snapshot: z32.json().nullable(),
7370
+ created_at: z32.iso.datetime()
7092
7371
  });
7093
7372
  var playbookExecutionCreateResponseSchema = playbookExecutionSchema.extend({
7094
- resumed: z31.boolean(),
7095
- sample_data: z31.boolean()
7373
+ resumed: z32.boolean(),
7374
+ sample_data: z32.boolean()
7096
7375
  });
7097
7376
 
7098
7377
  // src/lib/dashboardApi/api.ts
@@ -7142,8 +7421,8 @@ async function assessPlaybookExecution(uuid) {
7142
7421
  }
7143
7422
 
7144
7423
  // src/actions/playbookSteps/selectPlaybook.ts
7145
- var selectPlaybookSchema = z32.object({
7146
- slug: z32.string()
7424
+ var selectPlaybookSchema = z33.object({
7425
+ slug: z33.string()
7147
7426
  });
7148
7427
  function persistSlug(ctx, slug) {
7149
7428
  ctx.setUserInput("playbookSlug", slug);
@@ -7175,10 +7454,10 @@ async function selectPlaybookStep(ctx, requestedSlug) {
7175
7454
  }
7176
7455
 
7177
7456
  // src/actions/playbookSteps/selectIndex.ts
7178
- import { z as z33 } from "zod";
7179
- var selectIndexSchema = z33.object({
7180
- indexName: z33.string(),
7181
- sampleIndex: z33.boolean()
7457
+ import { z as z34 } from "zod";
7458
+ var selectIndexSchema = z34.object({
7459
+ indexName: z34.string(),
7460
+ sampleIndex: z34.boolean()
7182
7461
  });
7183
7462
  var CREATE_SAMPLE_INDEX = "Create a sample index";
7184
7463
  var GO_BACK_OPTION = "Go back to playbook selection";
@@ -8736,7 +9015,7 @@ function App() {
8736
9015
  onComplete: () => setTipsComplete(true)
8737
9016
  }
8738
9017
  ),
8739
- showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, { showAll: true, border: false }),
9018
+ showNoticesInMain && showNotices && /* @__PURE__ */ jsx21(Notices, { border: false }),
8740
9019
  /* @__PURE__ */ jsx21(Review, {}),
8741
9020
  !showTips && /* @__PURE__ */ jsx21(PromptInput, {}),
8742
9021
  phase === "error" && error && /* @__PURE__ */ jsx21(Box22, { marginTop: 1, children: /* @__PURE__ */ jsxs20(Text23, { color: COLORS.status.error, children: [
@@ -8967,7 +9246,7 @@ async function seedWorkflowState(workflow, value) {
8967
9246
  }
8968
9247
 
8969
9248
  // src/lib/resetState.ts
8970
- import { readdir as readdir3, rm as rm2 } from "node:fs/promises";
9249
+ import { readdir as readdir4, rm as rm2 } from "node:fs/promises";
8971
9250
  import { join as join12 } from "node:path";
8972
9251
  var KEEP = ["wizard.log"];
8973
9252
  async function resetProjectState() {
@@ -8975,7 +9254,7 @@ async function resetProjectState() {
8975
9254
  await forgetResolvedKeys();
8976
9255
  let entries;
8977
9256
  try {
8978
- entries = await readdir3(dir);
9257
+ entries = await readdir4(dir);
8979
9258
  } catch {
8980
9259
  return { dir, removed: [] };
8981
9260
  }
@@ -8991,7 +9270,7 @@ async function resetProjectState() {
8991
9270
  // package.json with { type: 'json' }
8992
9271
  var package_default2 = {
8993
9272
  name: "@algolia/wizard",
8994
- version: "0.83.0",
9273
+ version: "0.84.0-rc.202.644",
8995
9274
  description: "Magically implement Algolia functionality in your codebase",
8996
9275
  type: "module",
8997
9276
  engines: {