@odla-ai/cli 0.42.1 → 0.42.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js CHANGED
@@ -174,7 +174,7 @@ function absoluteEntryPath(entryPath) {
174
174
 
175
175
  // src/bin.ts
176
176
  var argv = process.argv.slice(2);
177
- requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-K5SBFFAY.js")).runCli()).catch((err) => {
177
+ requireCurrentCliForProvision(argv).then(() => requireCoherentProvisionRuntime(argv)).then(async () => (await import("./cli-4FCRBWED.js")).runCli()).catch((err) => {
178
178
  console.error(redactSecrets(`odla-ai: ${err instanceof Error ? err.message : String(err)}`));
179
179
  process.exitCode = exitCodeFor(err);
180
180
  });
@@ -6145,7 +6145,7 @@ async function stageWorkspacePair(baselineSource, workspaceSource, options = {})
6145
6145
  }
6146
6146
  }
6147
6147
 
6148
- // ../harness/dist/chunk-QZXCQSPZ.js
6148
+ // ../harness/dist/chunk-GYWQM76X.js
6149
6149
  import { createHash as createHash3 } from "crypto";
6150
6150
  import { readFile as readFile2, readdir as readdir2 } from "fs/promises";
6151
6151
  import { relative as relative4, resolve as resolve10 } from "path";
@@ -6482,7 +6482,7 @@ function validateSnapshot(snapshot, limits) {
6482
6482
  }
6483
6483
  }
6484
6484
 
6485
- // ../harness/dist/chunk-QZXCQSPZ.js
6485
+ // ../harness/dist/chunk-GYWQM76X.js
6486
6486
  import { spawn as spawn4 } from "child_process";
6487
6487
  import { lstat as lstat2 } from "fs/promises";
6488
6488
  import { resolve as resolve23, sep as sep3 } from "path";
@@ -6501,6 +6501,7 @@ import {
6501
6501
  runAgent
6502
6502
  } from "@odla-ai/ai";
6503
6503
  import { extractText } from "@odla-ai/ai";
6504
+ import { spawn as spawn33 } from "child_process";
6504
6505
  import { readFile as readFile22, readdir as readdir22 } from "fs/promises";
6505
6506
  import { relative as relative22, resolve as resolve42 } from "path";
6506
6507
 
@@ -6788,7 +6789,7 @@ function looksLikeDestination(value2) {
6788
6789
  return /^(?:[a-z][a-z0-9+.-]*:\/\/|\/|\\\\)/i.test(text3) || /^[\w.-]+\.[a-z]{2,}(?:[/:]|$)/i.test(text3);
6789
6790
  }
6790
6791
 
6791
- // ../harness/dist/chunk-QZXCQSPZ.js
6792
+ // ../harness/dist/chunk-GYWQM76X.js
6792
6793
  import { readFile as readFile4, stat as stat2 } from "fs/promises";
6793
6794
  import { readFile as readFile3 } from "fs/promises";
6794
6795
  import { join as join33 } from "path";
@@ -7056,7 +7057,7 @@ async function buildCodeGraph(input) {
7056
7057
  return builder.build();
7057
7058
  }
7058
7059
 
7059
- // ../harness/dist/chunk-QZXCQSPZ.js
7060
+ // ../harness/dist/chunk-GYWQM76X.js
7060
7061
  import { createHash as createHash32 } from "crypto";
7061
7062
  async function digestStagedWorkspace(root, limits) {
7062
7063
  const files = [];
@@ -8120,7 +8121,10 @@ Never claim a build or test passed unless odla_run_recipe returned that result.`
8120
8121
  var V2_SYSTEM_PROMPT = `You are the coding agent inside an odla Code harness.
8121
8122
  Start by orienting: odla_list shows the files in the workspace and odla_search
8122
8123
  finds a literal string across them. Prefer those over guessing a path.
8123
- Then odla_read a bounded range, and odla_apply_git_diff to mutate.
8124
+ Then odla_read a bounded range, and odla_apply_git_diff to mutate. When you
8125
+ need several independent searches or file ranges, issue those read-only calls
8126
+ together in one turn; their results stay ordered and the harness overlaps them.
8127
+ Never issue odla_apply_git_diff or odla_run_recipe alongside another tool call.
8124
8128
  For mutations, call odla_apply_git_diff with raw git diff text. It must start
8125
8129
  with "diff --git a/<path> b/<path>", include matching "---" and "+++" file
8126
8130
  headers and numbered "@@" hunks, and never use "*** Begin Patch" wrappers.
@@ -8151,18 +8155,26 @@ var SYSTEM_PROMPT_FOR = {
8151
8155
  };
8152
8156
  function codeSkill(opts) {
8153
8157
  let seq = 0;
8158
+ let nextCompletion = 1;
8159
+ const completed = /* @__PURE__ */ new Map();
8154
8160
  const call4 = async (tool, input, signal) => {
8161
+ const sequence = ++seq;
8155
8162
  const startedAt = Date.now();
8156
8163
  const response2 = await opts.broker.execute(
8157
8164
  { lease: opts.lease, workspaceDir: opts.workspaceDir, signal },
8158
- { requestId: `bench-${tool}-${++seq}`, tool, input }
8165
+ { requestId: `bench-${tool}-${sequence}`, tool, input }
8159
8166
  );
8160
- opts.onToolCall?.({
8167
+ completed.set(sequence, {
8161
8168
  tool,
8162
8169
  ok: response2.ok,
8163
8170
  durationMs: Date.now() - startedAt,
8164
8171
  ...response2.ok ? {} : { error: String(response2.content).slice(0, 300) }
8165
8172
  });
8173
+ while (completed.has(nextCompletion)) {
8174
+ const completion = completed.get(nextCompletion);
8175
+ completed.delete(nextCompletion++);
8176
+ opts.onToolCall?.(completion);
8177
+ }
8166
8178
  return { content: response2.content, isError: !response2.ok };
8167
8179
  };
8168
8180
  const read22 = {
@@ -8178,6 +8190,7 @@ function codeSkill(opts) {
8178
8190
  },
8179
8191
  additionalProperties: false
8180
8192
  },
8193
+ concurrency: "parallel",
8181
8194
  handler: (input, ctx) => call4("sandbox.read", input, ctx.signal)
8182
8195
  };
8183
8196
  const applyPatch = {
@@ -8219,6 +8232,7 @@ function codeSkill(opts) {
8219
8232
  },
8220
8233
  additionalProperties: false
8221
8234
  },
8235
+ concurrency: "parallel",
8222
8236
  handler: (input, ctx) => call4("sandbox.list", input, ctx.signal)
8223
8237
  };
8224
8238
  const searchFiles = {
@@ -8235,11 +8249,13 @@ function codeSkill(opts) {
8235
8249
  },
8236
8250
  additionalProperties: false
8237
8251
  },
8252
+ concurrency: "parallel",
8238
8253
  handler: (input, ctx) => call4("sandbox.search", input, ctx.signal)
8239
8254
  };
8240
8255
  const graphTool = (name, tool, description, required) => ({
8241
8256
  name,
8242
8257
  description,
8258
+ concurrency: "parallel",
8243
8259
  inputSchema: {
8244
8260
  type: "object",
8245
8261
  ...required ? { required: ["query"] } : {},
@@ -8417,6 +8433,24 @@ function createCodeRuntimeInference(options) {
8417
8433
  var DEFAULT_MAX_FILES = 2e4;
8418
8434
  var DEFAULT_MAX_RESULTS = 100;
8419
8435
  var DEFAULT_MAX_FILE_BYTES = 512 * 1024;
8436
+ function createWorkspaceFileRegistry(limit = DEFAULT_MAX_FILES, enumerate = registeredFiles) {
8437
+ const cache2 = /* @__PURE__ */ new Map();
8438
+ return {
8439
+ files(root) {
8440
+ const existing = cache2.get(root);
8441
+ if (existing) return existing;
8442
+ const pending = enumerate(root, limit).then((paths) => Object.freeze(paths));
8443
+ cache2.set(root, pending);
8444
+ void pending.catch(() => {
8445
+ if (cache2.get(root) === pending) cache2.delete(root);
8446
+ });
8447
+ return pending;
8448
+ },
8449
+ invalidate(root) {
8450
+ cache2.delete(root);
8451
+ }
8452
+ };
8453
+ }
8420
8454
  async function registeredFiles(root, limit = DEFAULT_MAX_FILES) {
8421
8455
  const paths = [];
8422
8456
  const walk = async (directory) => {
@@ -8447,28 +8481,122 @@ function listWorkspace(paths, options = {}) {
8447
8481
  return scoped.slice(0, max);
8448
8482
  }
8449
8483
  async function searchWorkspace(root, paths, options) {
8450
- const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
8451
- if (!query) throw new TypeError("search query must be a non-empty string");
8484
+ options.signal?.throwIfAborted();
8485
+ if (!options.query) throw new TypeError("search query must be a non-empty string");
8452
8486
  const maxResults = options.maxResults ?? DEFAULT_MAX_RESULTS;
8453
8487
  const maxFileBytes = options.maxFileBytes ?? DEFAULT_MAX_FILE_BYTES;
8454
8488
  const scoped = listWorkspace(paths, { ...options.prefix ? { prefix: options.prefix } : {}, maxEntries: paths.length });
8489
+ if (scoped.length === 0) return [];
8490
+ try {
8491
+ return await nativeSearch(root, scoped, { ...options, maxResults, maxFileBytes });
8492
+ } catch (error) {
8493
+ options.signal?.throwIfAborted();
8494
+ return fallbackSearch(root, scoped, { ...options, maxResults, maxFileBytes });
8495
+ }
8496
+ }
8497
+ var MAX_NATIVE_ARG_BYTES = 96 * 1024;
8498
+ async function nativeSearch(root, paths, options) {
8499
+ const batches = [];
8500
+ let batch = [];
8501
+ let bytes = 0;
8502
+ for (const path of paths) {
8503
+ const size = Buffer.byteLength(path) + 1;
8504
+ if (batch.length > 0 && bytes + size > MAX_NATIVE_ARG_BYTES) {
8505
+ batches.push(batch);
8506
+ batch = [];
8507
+ bytes = 0;
8508
+ }
8509
+ batch.push(path);
8510
+ bytes += size;
8511
+ }
8512
+ if (batch.length > 0) batches.push(batch);
8513
+ const matches = [];
8514
+ for (const files of batches) {
8515
+ const remaining = options.maxResults - matches.length;
8516
+ if (remaining <= 0) break;
8517
+ matches.push(...await nativeSearchBatch(root, files, options, remaining));
8518
+ }
8519
+ return matches;
8520
+ }
8521
+ function nativeSearchBatch(root, paths, options, remaining) {
8522
+ return new Promise((resolveMatches, reject) => {
8523
+ const args = [
8524
+ "--fixed-strings",
8525
+ "--json",
8526
+ "--no-messages",
8527
+ "--sort=path",
8528
+ `--max-filesize=${options.maxFileBytes}`,
8529
+ "--max-columns=4096",
8530
+ "--max-columns-preview",
8531
+ options.caseSensitive === false ? "--ignore-case" : "--case-sensitive",
8532
+ "--",
8533
+ options.query,
8534
+ ...paths
8535
+ ];
8536
+ const child = spawn33("rg", args, {
8537
+ cwd: root,
8538
+ stdio: ["ignore", "pipe", "ignore"],
8539
+ ...options.signal ? { signal: options.signal } : {}
8540
+ });
8541
+ const matches = [];
8542
+ let carry = "";
8543
+ let stopped = false;
8544
+ const consume = (line2) => {
8545
+ if (matches.length >= remaining) return;
8546
+ let event;
8547
+ try {
8548
+ event = JSON.parse(line2);
8549
+ } catch {
8550
+ return;
8551
+ }
8552
+ const path = event.data?.path?.text;
8553
+ const lineNumber = event.data?.line_number;
8554
+ const source = event.data?.lines?.text;
8555
+ if (event.type !== "match" || path === void 0 || lineNumber === void 0 || source === void 0) return;
8556
+ matches.push({ path, line: lineNumber, text: source.trim().slice(0, 240) });
8557
+ if (matches.length >= remaining) {
8558
+ stopped = true;
8559
+ child.kill();
8560
+ }
8561
+ };
8562
+ child.stdout.setEncoding("utf8");
8563
+ child.stdout.on("data", (chunk) => {
8564
+ carry += chunk;
8565
+ let newline = carry.indexOf("\n");
8566
+ while (newline >= 0) {
8567
+ consume(carry.slice(0, newline));
8568
+ carry = carry.slice(newline + 1);
8569
+ newline = carry.indexOf("\n");
8570
+ }
8571
+ });
8572
+ child.once("error", reject);
8573
+ child.once("close", (code) => {
8574
+ if (carry) consume(carry);
8575
+ if (stopped || code === 0 || code === 1) resolveMatches(matches);
8576
+ else reject(new Error(`native search exited with status ${code ?? "unknown"}`));
8577
+ });
8578
+ });
8579
+ }
8580
+ async function fallbackSearch(root, scoped, options) {
8581
+ const query = options.caseSensitive === false ? options.query.toLowerCase() : options.query;
8455
8582
  const matches = [];
8456
8583
  for (const path of scoped) {
8457
- if (matches.length >= maxResults) break;
8584
+ options.signal?.throwIfAborted();
8585
+ if (matches.length >= options.maxResults) break;
8458
8586
  let source;
8459
8587
  try {
8460
8588
  source = await readFile22(resolve42(root, path));
8461
8589
  } catch {
8462
8590
  continue;
8463
8591
  }
8464
- if (source.byteLength > maxFileBytes || source.includes(0)) continue;
8592
+ if (source.byteLength > options.maxFileBytes || source.includes(0)) continue;
8465
8593
  const lines = source.toString("utf8").split("\n");
8466
8594
  for (let index = 0; index < lines.length; index += 1) {
8467
8595
  const raw = lines[index];
8468
8596
  const haystack = options.caseSensitive === false ? raw.toLowerCase() : raw;
8469
8597
  if (!haystack.includes(query)) continue;
8470
8598
  matches.push({ path, line: index + 1, text: raw.trim().slice(0, 240) });
8471
- if (matches.length >= maxResults) break;
8599
+ if (matches.length >= options.maxResults) break;
8472
8600
  }
8473
8601
  }
8474
8602
  return matches;
@@ -8723,6 +8851,9 @@ function workspaceGraphs(workspaceDir, paths) {
8723
8851
  cache.set(workspaceDir, built);
8724
8852
  return built;
8725
8853
  }
8854
+ function forgetWorkspaceGraphs(workspaceDir) {
8855
+ cache.delete(workspaceDir);
8856
+ }
8726
8857
  var shortId = (id2) => id2.slice(id2.indexOf(":") + 1);
8727
8858
  function renderOverview(graphs, prefix) {
8728
8859
  const rows = rollup(graphs.graph, FILE, prefix === void 0 ? {} : { prefix });
@@ -8767,7 +8898,7 @@ var GRAPH_TOOLS = /* @__PURE__ */ new Set([
8767
8898
  "sandbox.who_imports",
8768
8899
  "sandbox.who_touches"
8769
8900
  ]);
8770
- async function read(context, request3, options, policy) {
8901
+ async function read(context, request3, options, policy, registry) {
8771
8902
  exactKeys(request3.input, ["path", "startLine", "endLine"]);
8772
8903
  const path = stringField(request3.input, "path");
8773
8904
  const startLine = optionalInteger(request3.input.startLine) ?? 1;
@@ -8775,7 +8906,7 @@ async function read(context, request3, options, policy) {
8775
8906
  if (endLine < startLine || endLine - startLine + 1 > (options.maxReadLines ?? 2e3)) {
8776
8907
  throw new TypeError("requested line range exceeds its bound");
8777
8908
  }
8778
- const paths = await registeredFiles(context.workspaceDir, 2e4);
8909
+ const paths = await registry.files(context.workspaceDir);
8779
8910
  if (!paths.includes(path)) {
8780
8911
  throw new TypeError(`no such file in the staged workspace: "${path}". Use sandbox.overview, sandbox.where_is or sandbox.search to find the correct path.`);
8781
8912
  }
@@ -8795,13 +8926,13 @@ async function read(context, request3, options, policy) {
8795
8926
  }
8796
8927
  return response(request3, true, content2, { path, startLine, endLine: Math.min(endLine, lines.length) });
8797
8928
  }
8798
- async function list(context, request3, options, policy) {
8929
+ async function list(context, request3, options, policy, registry) {
8799
8930
  exactKeys(request3.input, ["prefix", "maxEntries"]);
8800
8931
  const raw = request3.input.prefix;
8801
8932
  const prefix = typeof raw === "string" && raw.length > 0 ? raw : void 0;
8802
8933
  const maxEntries = optionalInteger(request3.input.maxEntries) ?? 1e3;
8803
8934
  if (maxEntries > 5e3) throw new TypeError("maxEntries exceeds its bound");
8804
- const paths = await registeredFiles(context.workspaceDir, 2e4);
8935
+ const paths = await registry.files(context.workspaceDir);
8805
8936
  const allowed = await policy.list(policyContext(context, request3, options, { paths, ...prefix ? { prefix } : {} }));
8806
8937
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8807
8938
  const entries = listWorkspace(paths, { ...prefix ? { prefix } : {}, maxEntries });
@@ -8819,7 +8950,7 @@ async function list(context, request3, options, policy) {
8819
8950
  { count: entries.length, truncated }
8820
8951
  );
8821
8952
  }
8822
- async function search(context, request3, options, policy) {
8953
+ async function search(context, request3, options, policy, registry) {
8823
8954
  exactKeys(request3.input, ["query", "prefix", "maxResults", "caseSensitive"]);
8824
8955
  const query = stringField(request3.input, "query");
8825
8956
  if (query.length > 512) throw new TypeError("search query exceeds its bound");
@@ -8828,21 +8959,22 @@ async function search(context, request3, options, policy) {
8828
8959
  const maxResults = optionalInteger(request3.input.maxResults) ?? 100;
8829
8960
  if (maxResults > 500) throw new TypeError("maxResults exceeds its bound");
8830
8961
  const caseSensitive = request3.input.caseSensitive === void 0 ? true : request3.input.caseSensitive === true;
8831
- const paths = await registeredFiles(context.workspaceDir, 2e4);
8962
+ const paths = await registry.files(context.workspaceDir);
8832
8963
  const allowed = await policy.search(policyContext(context, request3, options, { paths, query, ...prefix ? { prefix } : {} }));
8833
8964
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8834
8965
  const matches = await searchWorkspace(context.workspaceDir, paths, {
8835
8966
  query,
8836
8967
  maxResults,
8837
8968
  caseSensitive,
8838
- ...prefix ? { prefix } : {}
8969
+ ...prefix ? { prefix } : {},
8970
+ ...context.signal ? { signal: context.signal } : {}
8839
8971
  });
8840
8972
  if (!matches.length) return response(request3, true, `No match for "${query}".`, { count: 0 });
8841
8973
  return response(request3, true, matches.map((match) => `${match.path}:${match.line}: ${match.text}`).join("\n"), {
8842
8974
  count: matches.length
8843
8975
  });
8844
8976
  }
8845
- async function graphQuery(context, request3, options, policy) {
8977
+ async function graphQuery(context, request3, options, policy, registry) {
8846
8978
  exactKeys(request3.input, ["query"]);
8847
8979
  const raw = request3.input.query;
8848
8980
  const query = typeof raw === "string" ? raw : "";
@@ -8852,7 +8984,7 @@ async function graphQuery(context, request3, options, policy) {
8852
8984
  selector: query
8853
8985
  }));
8854
8986
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8855
- const paths = await registeredFiles(context.workspaceDir, 2e4);
8987
+ const paths = await registry.files(context.workspaceDir);
8856
8988
  const graphs = await workspaceGraphs(context.workspaceDir, paths);
8857
8989
  if (request3.tool === "sandbox.overview") {
8858
8990
  return response(request3, true, renderOverview(graphs, query || void 0));
@@ -8866,23 +8998,38 @@ function createCodeToolBroker(options) {
8866
8998
  validateOptions(options);
8867
8999
  const recipes = new Map(options.recipes.map((recipe2) => [recipe2.id, recipe2]));
8868
9000
  const policy = createCodePolicyGate(options);
8869
- let tail = Promise.resolve();
9001
+ const registry = createWorkspaceFileRegistry();
9002
+ let barrier = Promise.resolve();
9003
+ const activeReads = /* @__PURE__ */ new Set();
8870
9004
  return {
8871
9005
  execute(context, request3) {
8872
- const result = tail.then(() => route2(context, request3, options, recipes, policy));
8873
- tail = result.then(() => void 0, () => void 0);
9006
+ if (isReadTool(request3.tool)) {
9007
+ const result2 = barrier.then(() => route2(context, request3, options, recipes, policy, registry));
9008
+ const settled = result2.then(() => void 0, () => void 0);
9009
+ activeReads.add(settled);
9010
+ void settled.then(() => {
9011
+ activeReads.delete(settled);
9012
+ });
9013
+ return result2;
9014
+ }
9015
+ const earlierReads = [...activeReads];
9016
+ const result = barrier.then(() => Promise.all(earlierReads)).then(() => route2(context, request3, options, recipes, policy, registry));
9017
+ barrier = result.then(() => void 0, () => void 0);
8874
9018
  return result;
8875
9019
  }
8876
9020
  };
8877
9021
  }
8878
- async function route2(context, request3, options, recipes, policy) {
9022
+ function isReadTool(tool) {
9023
+ return tool === "sandbox.read" || tool === "sandbox.list" || tool === "sandbox.search" || GRAPH_TOOLS.has(tool);
9024
+ }
9025
+ async function route2(context, request3, options, recipes, policy, registry) {
8879
9026
  try {
8880
9027
  if (context.signal?.aborted) throw new TypeError("tool request was cancelled");
8881
- if (request3.tool === "sandbox.read") return await read(context, request3, options, policy);
8882
- if (request3.tool === "sandbox.list") return await list(context, request3, options, policy);
8883
- if (request3.tool === "sandbox.search") return await search(context, request3, options, policy);
8884
- if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy);
8885
- if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy);
9028
+ if (request3.tool === "sandbox.read") return await read(context, request3, options, policy, registry);
9029
+ if (request3.tool === "sandbox.list") return await list(context, request3, options, policy, registry);
9030
+ if (request3.tool === "sandbox.search") return await search(context, request3, options, policy, registry);
9031
+ if (GRAPH_TOOLS.has(request3.tool)) return await graphQuery(context, request3, options, policy, registry);
9032
+ if (request3.tool === "sandbox.apply_patch") return await patch(context, request3, options, policy, registry);
8886
9033
  return await recipe(context, request3, options, recipes, policy);
8887
9034
  } catch (reason) {
8888
9035
  return response(request3, false, toolFailureMessage(reason));
@@ -8897,7 +9044,7 @@ function toolFailureMessage(reason) {
8897
9044
  if (code === "EACCES" || code === "EPERM") return "that path is not readable through this tool";
8898
9045
  return "tool failed closed";
8899
9046
  }
8900
- async function patch(context, request3, options, policy) {
9047
+ async function patch(context, request3, options, policy, registry) {
8901
9048
  exactKeys(request3.input, ["patch"]);
8902
9049
  const value2 = stringField(request3.input, "patch");
8903
9050
  const paths = validateCodePatch(value2, options.maxPatchBytes ?? 256 * 1024);
@@ -8907,6 +9054,8 @@ async function patch(context, request3, options, policy) {
8907
9054
  const allowed = await policy.patch(policyContext(context, request3, options, { patch: value2 }));
8908
9055
  if (!allowed) return response(request3, false, "tool denied by CaMeL policy");
8909
9056
  await applyCodePatch(context.workspaceDir, value2, paths);
9057
+ registry.invalidate(context.workspaceDir);
9058
+ forgetWorkspaceGraphs(context.workspaceDir);
8910
9059
  return response(request3, true, `Applied patch to ${paths.length} file(s).`, { paths });
8911
9060
  }
8912
9061
  async function recipe(context, request3, options, recipes, policy) {
@@ -15809,4 +15958,4 @@ export {
15809
15958
  isTerminalHostedSecurityStatus,
15810
15959
  runCli
15811
15960
  };
15812
- //# sourceMappingURL=chunk-FWPQSTNF.js.map
15961
+ //# sourceMappingURL=chunk-K6ATAKGR.js.map