@lotics/cli 0.142.0 → 0.143.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/src/cli.js CHANGED
@@ -16769,7 +16769,7 @@ var require_operators = __commonJS({
16769
16769
  }
16770
16770
  };
16771
16771
  var type2Number = { "boolean": 3, "string": 2, "number": 1 };
16772
- var Infix = {
16772
+ var Infix2 = {
16773
16773
  compareOp: (value1, infix, value2, isArray1, isArray22) => {
16774
16774
  if (value1 == null) value1 = 0;
16775
16775
  if (value2 == null) value2 = 0;
@@ -16860,7 +16860,7 @@ var require_operators = __commonJS({
16860
16860
  module2.exports = {
16861
16861
  Prefix,
16862
16862
  Postfix,
16863
- Infix,
16863
+ Infix: Infix2,
16864
16864
  Operators: {
16865
16865
  compareOp: ["<", ">", "=", "<>", "<=", ">="],
16866
16866
  concatOp: ["&"],
@@ -16875,7 +16875,7 @@ var require_math = __commonJS({
16875
16875
  "../../node_modules/fast-formula-parser/formulas/functions/math.js"(exports2, module2) {
16876
16876
  var FormulaError = require_error();
16877
16877
  var { FormulaHelpers, Types, Factorials, Criteria } = require_helpers();
16878
- var { Infix } = require_operators();
16878
+ var { Infix: Infix2 } = require_operators();
16879
16879
  var H = FormulaHelpers;
16880
16880
  var f = [];
16881
16881
  var fd2 = [];
@@ -17398,7 +17398,7 @@ var require_math = __commonJS({
17398
17398
  if (criteria.match === criteria.value.test(value2)) {
17399
17399
  sum2 += valueToAdd;
17400
17400
  }
17401
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
17401
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
17402
17402
  sum2 += valueToAdd;
17403
17403
  }
17404
17404
  });
@@ -23678,7 +23678,7 @@ var require_statistical = __commonJS({
23678
23678
  "../../node_modules/fast-formula-parser/formulas/functions/statistical.js"(exports2, module2) {
23679
23679
  var FormulaError = require_error();
23680
23680
  var { FormulaHelpers, Types, Criteria, Address } = require_helpers();
23681
- var { Infix } = require_operators();
23681
+ var { Infix: Infix2 } = require_operators();
23682
23682
  var H = FormulaHelpers;
23683
23683
  var { DistributionFunctions } = require_distribution();
23684
23684
  var StatisticalFunctions = {
@@ -23740,7 +23740,7 @@ var require_statistical = __commonJS({
23740
23740
  sum2 += valueToAdd;
23741
23741
  cnt++;
23742
23742
  }
23743
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23743
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23744
23744
  sum2 += valueToAdd;
23745
23745
  cnt++;
23746
23746
  }
@@ -23779,7 +23779,7 @@ var require_statistical = __commonJS({
23779
23779
  if (criteria.op === "wc") {
23780
23780
  if (criteria.match === criteria.value.test(value2))
23781
23781
  cnt++;
23782
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23782
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
23783
23783
  cnt++;
23784
23784
  }
23785
23785
  });
@@ -33332,7 +33332,7 @@ var require_utils2 = __commonJS({
33332
33332
  "../../node_modules/fast-formula-parser/grammar/utils.js"(exports2, module2) {
33333
33333
  var FormulaError = require_error();
33334
33334
  var { Address } = require_helpers();
33335
- var { Prefix, Postfix, Infix, Operators } = require_operators();
33335
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
33336
33336
  var Collection = require_collection();
33337
33337
  var MAX_ROW2 = 1048576;
33338
33338
  var MAX_COLUMN2 = 16384;
@@ -33456,11 +33456,11 @@ var require_utils2 = __commonJS({
33456
33456
  if (this.isFormulaError(val2))
33457
33457
  return val2;
33458
33458
  if (Operators.compareOp.includes(infix))
33459
- return Infix.compareOp(val1, infix, val2, isArray1, isArray22);
33459
+ return Infix2.compareOp(val1, infix, val2, isArray1, isArray22);
33460
33460
  else if (Operators.concatOp.includes(infix))
33461
- return Infix.concatOp(val1, infix, val2, isArray1, isArray22);
33461
+ return Infix2.concatOp(val1, infix, val2, isArray1, isArray22);
33462
33462
  else if (Operators.mathOp.includes(infix))
33463
- return Infix.mathOp(val1, infix, val2, isArray1, isArray22);
33463
+ return Infix2.mathOp(val1, infix, val2, isArray1, isArray22);
33464
33464
  else
33465
33465
  throw new Error(`Unrecognized infix: ${infix}`);
33466
33466
  }
@@ -34012,7 +34012,7 @@ var require_utils3 = __commonJS({
34012
34012
  "../../node_modules/fast-formula-parser/grammar/dependency/utils.js"(exports2, module2) {
34013
34013
  var FormulaError = require_error();
34014
34014
  var { FormulaHelpers, Types, Address } = require_helpers();
34015
- var { Prefix, Postfix, Infix, Operators } = require_operators();
34015
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
34016
34016
  var Collection = require_collection();
34017
34017
  var MAX_ROW2 = 1048576;
34018
34018
  var MAX_COLUMN2 = 16384;
@@ -72146,7 +72146,7 @@ function writeAgentFiles(projectDir, agents, opts) {
72146
72146
  console.error(`\u26A0 Skipped src/agents/${alias}.md \u2014 the live declaration carries no instructions.`);
72147
72147
  continue;
72148
72148
  }
72149
- if (!opts.overwrite && localPromptUnpushed(projectDir, alias)) {
72149
+ if (!opts.overwrite && localPromptUnpushed(projectDir, alias, instructions)) {
72150
72150
  opts.kept.push(`src/agents/${alias}.md`);
72151
72151
  continue;
72152
72152
  }
@@ -72155,22 +72155,26 @@ function writeAgentFiles(projectDir, agents, opts) {
72155
72155
  }
72156
72156
  return written;
72157
72157
  }
72158
- function localPromptUnpushed(projectDir, alias) {
72158
+ function localPromptUnpushed(projectDir, alias, live) {
72159
72159
  const file2 = agentFilePath2(projectDir, alias);
72160
72160
  if (!fs8.existsSync(file2)) return false;
72161
+ const onDisk = fs8.readFileSync(file2, "utf-8");
72161
72162
  const baseline = readSynced(projectDir).agents[alias]?.content;
72162
- if (baseline === void 0) return true;
72163
- return contentSha(stripAgentHeader(fs8.readFileSync(file2, "utf-8"))) !== baseline;
72163
+ if (baseline === void 0) return stripAgentHeader(onDisk).trim() !== live.trim();
72164
+ return contentSha(stripAgentHeader(onDisk)) !== baseline;
72164
72165
  }
72165
72166
  function generatedWorkflowDescription(appName, alias) {
72166
72167
  return `Workflow invoked by app "${appName}" via useWorkflow("${alias}").`;
72167
72168
  }
72168
- function localBodyUnpushed(projectDir, alias) {
72169
+ function localBodyUnpushed(projectDir, alias, live) {
72169
72170
  const file2 = workflowFilePath(projectDir, alias);
72170
72171
  if (!fs8.existsSync(file2)) return false;
72172
+ const onDisk = fs8.readFileSync(file2, "utf-8");
72171
72173
  const baseline = readSynced(projectDir).workflows[alias]?.content;
72172
- if (baseline === void 0) return true;
72173
- return contentSha(stripWorkflowHeader(fs8.readFileSync(file2, "utf-8"))) !== baseline;
72174
+ if (baseline === void 0) {
72175
+ return stripWorkflowHeader(onDisk).trim() !== normalizeWorkflowBody(live).trim();
72176
+ }
72177
+ return contentSha(stripWorkflowHeader(onDisk)) !== baseline;
72174
72178
  }
72175
72179
  async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows, opts) {
72176
72180
  const written = [];
@@ -72197,7 +72201,7 @@ async function writeWorkflowFiles(client, projectDir, app_id, appName, workflows
72197
72201
  alias,
72198
72202
  toWorkflowDtsDeclaration(declaration)
72199
72203
  );
72200
- if (!opts.overwrite && localBodyUnpushed(projectDir, alias)) {
72204
+ if (!opts.overwrite && localBodyUnpushed(projectDir, alias, source)) {
72201
72205
  opts.kept.push(`src/workflows/${alias}.ts`);
72202
72206
  continue;
72203
72207
  }
@@ -72816,7 +72820,12 @@ async function appPull(client, args) {
72816
72820
  try {
72817
72821
  await runTar(["-xzf", tmpFile, "-C", stagingDir], stagingDir);
72818
72822
  console.error(`Extracting to ${targetPath}...`);
72819
- keptLocal.push(...copyTree(stagingDir, targetPath, { overwrite: args.force === true }));
72823
+ keptLocal.push(
72824
+ ...copyTree(stagingDir, targetPath, {
72825
+ overwrite: args.force === true,
72826
+ skip: [WORKFLOWS_DIR, AGENTS_DIR].map((d) => d.split(path9.sep).join("/"))
72827
+ })
72828
+ );
72820
72829
  } finally {
72821
72830
  if (fs8.existsSync(tmpFile)) fs8.unlinkSync(tmpFile);
72822
72831
  fs8.rmSync(stagingDir, { recursive: true, force: true });
@@ -73009,6 +73018,7 @@ async function appDeploy(client, args) {
73009
73018
  }
73010
73019
  if (liveAfter) {
73011
73020
  warnIfUnbranded(liveAfter);
73021
+ warnIfUndescribed(liveAfter);
73012
73022
  warnIfUnboundAliases(liveAfter, called);
73013
73023
  warnIfInertAgentTools(liveAfter);
73014
73024
  }
@@ -73047,6 +73057,15 @@ function warnIfUnbranded(app) {
73047
73057
  Find an icon: lotics run search_app_icons '{"query":"<word>"}'`
73048
73058
  );
73049
73059
  }
73060
+ function warnIfUndescribed(app) {
73061
+ if (app.description && app.description.trim() !== "") return;
73062
+ console.error(
73063
+ `
73064
+ \u26A0 This app has no description \u2014 the capability catalog its chat agent reads every turn
73065
+ lists the aliases and says nothing about what to do with them.
73066
+ Set it: lotics run update_app '{"app_id":"${app.id}","description":"<what the app is for, and the standing job it does>"}'`
73067
+ );
73068
+ }
73050
73069
  async function readLiveQueries(client, appId) {
73051
73070
  try {
73052
73071
  const app = await client.getApp(appId);
@@ -73154,6 +73173,7 @@ async function appCheck(client, args = {}) {
73154
73173
  warnIfInertAgentTools(app);
73155
73174
  warnIfProjectFootguns(projectDir, sourceText);
73156
73175
  warnIfUnbranded(app);
73176
+ warnIfUndescribed(app);
73157
73177
  if (!nothingPending(pending)) {
73158
73178
  process.exit(1);
73159
73179
  }
@@ -73348,6 +73368,7 @@ function copyTree(from, to, opts, relative = "") {
73348
73368
  const src = path9.join(from, entry.name);
73349
73369
  const dest = path9.join(to, entry.name);
73350
73370
  const rel = path9.join(relative, entry.name);
73371
+ if (opts.skip?.includes(rel.split(path9.sep).join("/"))) continue;
73351
73372
  if (entry.isDirectory()) {
73352
73373
  fs8.mkdirSync(dest, { recursive: true });
73353
73374
  kept.push(...copyTree(src, dest, opts, rel));
@@ -84764,14 +84785,14 @@ function readSourceData(workbook, cache) {
84764
84785
  if (!range2) return void 0;
84765
84786
  const header = [];
84766
84787
  for (let col = range2.startCol; col <= range2.endCol; col++) {
84767
- const cell = sheet.getCell(rowColToRef(range2.startRow, col));
84788
+ const cell = sheet.getCellAt(range2.startRow, col);
84768
84789
  header.push(formatHeader(cell?.value));
84769
84790
  }
84770
84791
  const records = [];
84771
84792
  for (let row = range2.startRow + 1; row <= range2.endRow; row++) {
84772
84793
  const rec = [];
84773
84794
  for (let col = range2.startCol; col <= range2.endCol; col++) {
84774
- const cell = sheet.getCell(rowColToRef(row, col));
84795
+ const cell = sheet.getCellAt(row, col);
84775
84796
  rec.push(coerceValue(cell?.value));
84776
84797
  }
84777
84798
  records.push(rec);
@@ -86315,23 +86336,70 @@ var CellMap = class {
86315
86336
  get size() {
86316
86337
  return this.store.size;
86317
86338
  }
86339
+ /**
86340
+ * The populated extent of this map, memoized until a mutation invalidates it.
86341
+ *
86342
+ * It lives HERE rather than on `SheetModel` because it is a pure function of
86343
+ * the cell set: a cache held next to the data cannot outlive it, so replacing
86344
+ * a sheet's map can never serve the previous map's extent. Every mutator
86345
+ * below drops it, including the packed pair — those bypass `set`/`delete`
86346
+ * entirely and are what the row/column commands use.
86347
+ *
86348
+ * Walking PACKED keys is the other half. `keys()` unpacks each key into an A1
86349
+ * string, which the caller then reparsed with a regex, so the old form packed,
86350
+ * unpacked and reparsed the whole sheet on every call — and it is called once
86351
+ * per oversized range during a recompute.
86352
+ */
86353
+ usedRange() {
86354
+ if (!this.usedRangeCache) {
86355
+ let minRow2 = Infinity;
86356
+ let maxRow2 = 0;
86357
+ let minCol2 = Infinity;
86358
+ let maxCol2 = 0;
86359
+ for (const packed of this.store.keys()) {
86360
+ const { row, col } = unpackRowCol(packed);
86361
+ if (row < minRow2) minRow2 = row;
86362
+ if (row > maxRow2) maxRow2 = row;
86363
+ if (col < minCol2) minCol2 = col;
86364
+ if (col > maxCol2) maxCol2 = col;
86365
+ }
86366
+ this.usedRangeCache = maxRow2 === 0 ? { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 } : { minRow: minRow2, maxRow: maxRow2, minCol: minCol2, maxCol: maxCol2 };
86367
+ }
86368
+ const { minRow, maxRow, minCol, maxCol } = this.usedRangeCache;
86369
+ return { minRow, maxRow, minCol, maxCol };
86370
+ }
86318
86371
  get(ref2) {
86319
86372
  return this.store.get(packRef(ref2));
86320
86373
  }
86374
+ /**
86375
+ * Read by coordinate, skipping the A1 round trip.
86376
+ *
86377
+ * `get(ref)` costs a number→string→regex→number conversion per call, which
86378
+ * is invisible until a caller reads a range cell by cell: on one real
86379
+ * workbook `refToRowCol` alone was 39% of a full recompute. A caller already
86380
+ * holding (row, col) — the range resolver, any grid walk — should use this.
86381
+ */
86382
+ getAt(row, col) {
86383
+ return this.store.get(packRowCol(row, col));
86384
+ }
86321
86385
  set(ref2, cell) {
86322
86386
  const key = packRef(ref2);
86323
86387
  if (key < 0) return this;
86324
86388
  this.store.set(key, cell);
86389
+ this.usedRangeCache = void 0;
86325
86390
  return this;
86326
86391
  }
86327
86392
  has(ref2) {
86328
86393
  return this.store.has(packRef(ref2));
86329
86394
  }
86330
86395
  delete(ref2) {
86331
- return this.store.delete(packRef(ref2));
86396
+ const deleted = this.store.delete(packRef(ref2));
86397
+ if (deleted) this.usedRangeCache = void 0;
86398
+ return deleted;
86332
86399
  }
86333
86400
  clear() {
86334
86401
  this.store.clear();
86402
+ this.usedRangeCache = void 0;
86335
86403
  }
86336
86404
  *keys() {
86337
86405
  for (const k of this.store.keys()) yield unpackRef(k);
@@ -86359,10 +86427,13 @@ var CellMap = class {
86359
86427
  /** Direct packed-key set, for the same hot loops. */
86360
86428
  setPacked(packed, cell) {
86361
86429
  this.store.set(packed, cell);
86430
+ this.usedRangeCache = void 0;
86362
86431
  }
86363
86432
  /** Direct packed-key delete. */
86364
86433
  deletePacked(packed) {
86365
- return this.store.delete(packed);
86434
+ const deleted = this.store.delete(packed);
86435
+ if (deleted) this.usedRangeCache = void 0;
86436
+ return deleted;
86366
86437
  }
86367
86438
  };
86368
86439
  var StyleRegistry = class {
@@ -86545,6 +86616,10 @@ var SheetModel = class {
86545
86616
  getCell(ref2) {
86546
86617
  return this.cells.get(ref2);
86547
86618
  }
86619
+ /** Read by coordinate, skipping the A1 round trip — see `CellMap.getAt`. */
86620
+ getCellAt(row, col) {
86621
+ return this.cells.getAt(row, col);
86622
+ }
86548
86623
  /**
86549
86624
  * Set cell value with optional inline style. Interns style via the
86550
86625
  * StyleRegistry when available. When `style` is supplied and matches the
@@ -86639,22 +86714,12 @@ var SheetModel = class {
86639
86714
  this.set(ref2, label, style);
86640
86715
  });
86641
86716
  }
86642
- /** Compute the bounding box of all non-empty cells. */
86717
+ /**
86718
+ * The populated extent of the sheet. Derived from — and cached by — the cell
86719
+ * map, so it cannot outlive the data it describes.
86720
+ */
86643
86721
  getUsedRange() {
86644
- let minRow = Infinity;
86645
- let maxRow = 0;
86646
- let minCol = Infinity;
86647
- let maxCol = 0;
86648
- for (const ref2 of this.cells.keys()) {
86649
- const rc = refToRowCol(ref2);
86650
- if (!rc) continue;
86651
- if (rc.row < minRow) minRow = rc.row;
86652
- if (rc.row > maxRow) maxRow = rc.row;
86653
- if (rc.col < minCol) minCol = rc.col;
86654
- if (rc.col > maxCol) maxCol = rc.col;
86655
- }
86656
- if (maxRow === 0) return { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 };
86657
- return { minRow, maxRow, minCol, maxCol };
86722
+ return this.cells.usedRange();
86658
86723
  }
86659
86724
  };
86660
86725
  var WorkbookModel = class {
@@ -86901,7 +86966,7 @@ function applyAutoFilterHiddenRows(sheet) {
86901
86966
  let visible = true;
86902
86967
  for (const filterCol of activeFilters) {
86903
86968
  const col = startCol + filterCol.colIndex;
86904
- const cell = sheet.getCell(rowColToRef(r, col));
86969
+ const cell = sheet.getCellAt(r, col);
86905
86970
  const cellText = cell?.displayValue ?? "";
86906
86971
  if (filterCol.filterValues && filterCol.filterValues.length > 0) {
86907
86972
  if (!filterCol.filterValues.includes(cellText)) {
@@ -94131,6 +94196,28 @@ function parseCellKey(key) {
94131
94196
  const bang = key.indexOf("!");
94132
94197
  return { sheet: key.slice(0, bang), ref: key.slice(bang + 1) };
94133
94198
  }
94199
+ var COARSE_COL_SUFFIX = ":*";
94200
+ var COARSE_ROW_PREFIX = "*:";
94201
+ function columnKey(sheet, column) {
94202
+ return `${sheet}!${column}${COARSE_COL_SUFFIX}`;
94203
+ }
94204
+ function rowKey(sheet, row) {
94205
+ return `${sheet}!${COARSE_ROW_PREFIX}${row}`;
94206
+ }
94207
+ function isCoarseKey(key) {
94208
+ return key.endsWith(COARSE_COL_SUFFIX) || key.includes(`!${COARSE_ROW_PREFIX}`);
94209
+ }
94210
+ var A1 = /^([A-Z]+)(\d+)$/;
94211
+ function coarseKeysFor(cellKeyStr) {
94212
+ const bang = cellKeyStr.indexOf("!");
94213
+ if (bang < 0) return [];
94214
+ const sheet = cellKeyStr.slice(0, bang);
94215
+ const ref2 = cellKeyStr.slice(bang + 1);
94216
+ const m = A1.exec(ref2);
94217
+ if (!m) return [];
94218
+ return [columnKey(sheet, m[1]), rowKey(sheet, Number(m[2]))];
94219
+ }
94220
+ var EMPTY = /* @__PURE__ */ new Set();
94134
94221
  var DependencyGraph = class {
94135
94222
  constructor() {
94136
94223
  // Forward edges: cell → cells it references (precedents)
@@ -94165,19 +94252,35 @@ var DependencyGraph = class {
94165
94252
  }
94166
94253
  /** Get all cells that directly depend on the given cell. */
94167
94254
  getDirectDependents(cell) {
94168
- return Array.from(this.dependents.get(cell) ?? []);
94255
+ return Array.from(this.dependentsOf(cell));
94256
+ }
94257
+ /**
94258
+ * Everything that references this cell — directly, or through the column or
94259
+ * row node that contains it. The single place `dependents` is read, so a
94260
+ * whole-column reference cannot be forgotten by one traversal and honoured by
94261
+ * another.
94262
+ */
94263
+ dependentsOf(cell) {
94264
+ const direct = this.dependents.get(cell);
94265
+ if (isCoarseKey(cell)) return direct ?? EMPTY;
94266
+ const coarse = coarseKeysFor(cell);
94267
+ let widened;
94268
+ for (const key of coarse) {
94269
+ const via = this.dependents.get(key);
94270
+ if (!via || via.size === 0) continue;
94271
+ widened ??= new Set(direct);
94272
+ for (const d of via) widened.add(d);
94273
+ }
94274
+ return widened ?? direct ?? EMPTY;
94169
94275
  }
94170
94276
  /** Get all cells that need recalculation when the given cells change, in topological order. */
94171
94277
  getRecalcOrder(changed) {
94172
- const changedSet = new Set(changed);
94173
94278
  const affected = /* @__PURE__ */ new Set();
94174
94279
  const queue = [...changed];
94175
94280
  let head = 0;
94176
94281
  while (head < queue.length) {
94177
94282
  const cell = queue[head++];
94178
- const deps = this.dependents.get(cell);
94179
- if (!deps) continue;
94180
- for (const dep of deps) {
94283
+ for (const dep of this.dependentsOf(cell)) {
94181
94284
  if (!affected.has(dep)) {
94182
94285
  affected.add(dep);
94183
94286
  queue.push(dep);
@@ -94186,15 +94289,11 @@ var DependencyGraph = class {
94186
94289
  }
94187
94290
  if (affected.size === 0) return [];
94188
94291
  const inDegree = /* @__PURE__ */ new Map();
94292
+ for (const cell of affected) inDegree.set(cell, 0);
94189
94293
  for (const cell of affected) {
94190
- let count = 0;
94191
- const prec = this.precedents.get(cell);
94192
- if (prec) {
94193
- for (const p of prec) {
94194
- if (affected.has(p)) count++;
94195
- }
94294
+ for (const dep of this.dependentsOf(cell)) {
94295
+ if (affected.has(dep)) inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
94196
94296
  }
94197
- inDegree.set(cell, count);
94198
94297
  }
94199
94298
  const result = [];
94200
94299
  const ready = [];
@@ -94205,9 +94304,7 @@ var DependencyGraph = class {
94205
94304
  while (readyHead < ready.length) {
94206
94305
  const cell = ready[readyHead++];
94207
94306
  result.push(cell);
94208
- const deps = this.dependents.get(cell);
94209
- if (!deps) continue;
94210
- for (const dep of deps) {
94307
+ for (const dep of this.dependentsOf(cell)) {
94211
94308
  if (!affected.has(dep)) continue;
94212
94309
  const newDeg = (inDegree.get(dep) ?? 1) - 1;
94213
94310
  inDegree.set(dep, newDeg);
@@ -94228,12 +94325,13 @@ var DependencyGraph = class {
94228
94325
  const stack = new Array();
94229
94326
  const prec = this.precedents.get(cell);
94230
94327
  if (!prec) return null;
94328
+ const originCoarse = new Set(coarseKeysFor(cell));
94231
94329
  for (const p of prec) {
94232
94330
  stack.push({ cell: p, path: [cell, p] });
94233
94331
  }
94234
94332
  while (stack.length > 0) {
94235
94333
  const { cell: current, path: path12 } = stack.pop();
94236
- if (current === cell) return path12;
94334
+ if (current === cell || originCoarse.has(current)) return path12;
94237
94335
  if (visited.has(current)) continue;
94238
94336
  visited.add(current);
94239
94337
  const nextPrec = this.precedents.get(current);
@@ -94346,6 +94444,20 @@ function parseTableRange(ref2) {
94346
94444
  };
94347
94445
  }
94348
94446
 
94447
+ // ../xlsx/src/blank_comparison.ts
94448
+ var import_operators = __toESM(require_operators(), 1);
94449
+ var patched = false;
94450
+ function applyExcelBlankSemantics() {
94451
+ if (patched) return;
94452
+ patched = true;
94453
+ const original = import_operators.Infix.compareOp;
94454
+ import_operators.Infix.compareOp = (value1, infix, value2, isArray1, isArray22) => {
94455
+ if (value1 == null && typeof value2 === "string") value1 = "";
94456
+ if (value2 == null && typeof value1 === "string") value2 = "";
94457
+ return original(value1, infix, value2, isArray1, isArray22);
94458
+ };
94459
+ }
94460
+
94349
94461
  // ../xlsx/src/formula_engine.ts
94350
94462
  var unwrapArg = (a) => a !== null && typeof a === "object" && "value" in a ? a.value : a;
94351
94463
  function adaptFormulajs(fn) {
@@ -94388,9 +94500,24 @@ function formulajsBridgeWithContext() {
94388
94500
  bridgeCtxCache = withCtx;
94389
94501
  return withCtx;
94390
94502
  }
94503
+ var MAX_EXPANDED_CELLS = 1e4;
94391
94504
  var FormulaEngine = class _FormulaEngine {
94392
94505
  constructor(workbook) {
94393
94506
  this.graph = new DependencyGraph();
94507
+ /**
94508
+ * Formula cells (by key) whose text the parser could not read AT ALL, found
94509
+ * while building the graph from a loaded file.
94510
+ *
94511
+ * They are held apart from every other failure because the honest response
94512
+ * differs: a formula that evaluates TO an error has been understood, so
94513
+ * writing `#DIV/0!` reports a real answer, while one we cannot parse has not
94514
+ * been — and overwriting the value its author computed would replace a
94515
+ * correct number with a guess. These keep their cached value and are named in
94516
+ * the report instead. The engine's own rule, one level down: degrade what you
94517
+ * cannot draw, refuse what you cannot read.
94518
+ */
94519
+ this.unreadableFormulas = /* @__PURE__ */ new Set();
94520
+ applyExcelBlankSemantics();
94394
94521
  this.workbook = workbook;
94395
94522
  this.parser = new import_fast_formula_parser.default({
94396
94523
  onCell: (ref2) => this.resolveCell(ref2),
@@ -94411,11 +94538,13 @@ var FormulaEngine = class _FormulaEngine {
94411
94538
  /** Build dependency graph from all formula cells in the workbook. */
94412
94539
  buildGraph() {
94413
94540
  this.graph.clear();
94541
+ this.unreadableFormulas.clear();
94414
94542
  for (const sheet of this.workbook.sheets) {
94415
94543
  for (const [ref2, cell] of sheet.cells) {
94416
94544
  if (!cell.formula) continue;
94417
94545
  const key = cellKey({ sheet: sheet.name, ref: ref2 });
94418
- const deps = this.extractDependencies(cell.formula, sheet.name);
94546
+ const { deps, readable } = this.analyzeFormula(cell.formula, sheet.name);
94547
+ if (!readable) this.unreadableFormulas.add(key);
94419
94548
  this.graph.setDependencies(key, deps);
94420
94549
  }
94421
94550
  }
@@ -94471,8 +94600,13 @@ var FormulaEngine = class _FormulaEngine {
94471
94600
  onCellChanged(sheetName, ref2, formula) {
94472
94601
  const key = cellKey({ sheet: sheetName, ref: ref2 });
94473
94602
  if (formula) {
94474
- const deps = this.extractDependencies(formula, sheetName);
94603
+ const { deps, readable } = this.analyzeFormula(formula, sheetName);
94475
94604
  this.graph.setDependencies(key, deps);
94605
+ this.unreadableFormulas.delete(key);
94606
+ if (!readable) {
94607
+ this.setCellError(key, "#ERROR!");
94608
+ return;
94609
+ }
94476
94610
  const cycle = this.graph.detectCycle(key);
94477
94611
  if (cycle) {
94478
94612
  this.setCellError(key, "#CIRC!");
@@ -94481,6 +94615,7 @@ var FormulaEngine = class _FormulaEngine {
94481
94615
  this.evaluateCell(key);
94482
94616
  } else {
94483
94617
  this.graph.removeCell(key);
94618
+ this.unreadableFormulas.delete(key);
94484
94619
  }
94485
94620
  const order = this.graph.getRecalcOrder([key]);
94486
94621
  for (const depKey of order) {
@@ -94497,6 +94632,7 @@ var FormulaEngine = class _FormulaEngine {
94497
94632
  // ===========================================================================
94498
94633
  /** Evaluate a single formula cell and update its value. */
94499
94634
  evaluateCell(key) {
94635
+ if (this.unreadableFormulas.has(key)) return;
94500
94636
  const { sheet: sheetName, ref: ref2 } = parseCellKey(key);
94501
94637
  const sheetIndex = this.workbook.sheets.findIndex((s) => s.name === sheetName);
94502
94638
  if (sheetIndex < 0) return;
@@ -94545,26 +94681,60 @@ var FormulaEngine = class _FormulaEngine {
94545
94681
  // ===========================================================================
94546
94682
  /** Extract cell/range references from a formula string. Returns empty array for unparseable formulas. */
94547
94683
  extractDependencies(formula, currentSheet) {
94548
- const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
94549
- const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
94550
- const deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
94684
+ return this.analyzeFormula(formula, currentSheet).deps;
94685
+ }
94686
+ /**
94687
+ * Dependencies plus whether the parser could read the formula at all.
94688
+ *
94689
+ * `fast-formula-parser` reports a formula it cannot LEX by throwing, the same
94690
+ * channel it uses for `#VALUE!`, so the lexer failure of a single cell used to
94691
+ * escape `buildGraph` and abort the recompute for the entire workbook —
94692
+ * observed on a real workbook whose `VLOOKUP` pointed at another file
94693
+ * (`'[1]DATA'!B:C`, an external-workbook reference the lexer has no rule for).
94694
+ * Anything the grammar does not cover lands here the same way, so this catches
94695
+ * the class rather than that one syntax.
94696
+ */
94697
+ analyzeFormula(formula, currentSheet) {
94698
+ let deps;
94699
+ try {
94700
+ const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
94701
+ const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
94702
+ deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
94703
+ } catch {
94704
+ return { deps: [], readable: false };
94705
+ }
94551
94706
  const refs = [];
94552
94707
  for (const dep of deps) {
94553
94708
  const depSheet = dep.sheet ?? currentSheet;
94554
94709
  if (dep.from && dep.to) {
94555
- const maxCells = 1e4;
94556
- let count = 0;
94557
- for (let r = dep.from.row; r <= dep.to.row && count < maxCells; r++) {
94558
- for (let c = dep.from.col; c <= dep.to.col && count < maxCells; c++) {
94710
+ const rows = dep.to.row - dep.from.row + 1;
94711
+ const cols = dep.to.col - dep.from.col + 1;
94712
+ if (rows * cols > MAX_EXPANDED_CELLS) {
94713
+ if (cols <= rows) {
94714
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
94715
+ refs.push(columnKey(depSheet, colNumToLetters(c)));
94716
+ }
94717
+ } else {
94718
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
94719
+ refs.push(rowKey(depSheet, r));
94720
+ }
94721
+ }
94722
+ continue;
94723
+ }
94724
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
94725
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
94559
94726
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(r, c) }));
94560
- count++;
94561
94727
  }
94562
94728
  }
94563
94729
  } else if (dep.row != null && dep.col != null) {
94564
94730
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(dep.row, dep.col) }));
94565
94731
  }
94566
94732
  }
94567
- return refs;
94733
+ return { deps: refs, readable: true };
94734
+ }
94735
+ /** Formula cells the parser could not read, by key. Read by the recompute report. */
94736
+ unreadable() {
94737
+ return this.unreadableFormulas;
94568
94738
  }
94569
94739
  // ===========================================================================
94570
94740
  // Cell value resolution callbacks
@@ -94601,8 +94771,7 @@ var FormulaEngine = class _FormulaEngine {
94601
94771
  for (let r = ref2.from.row; r <= lastRow; r++) {
94602
94772
  const row = [];
94603
94773
  for (let c = ref2.from.col; c <= lastCol; c++) {
94604
- const cell = sheet.getCell(rowColToRef(r, c));
94605
- row.push(cell?.value ?? null);
94774
+ row.push(sheet.getCellAt(r, c)?.value ?? null);
94606
94775
  }
94607
94776
  result.push(row);
94608
94777
  }
@@ -94686,15 +94855,19 @@ function recomputeAll(workbook) {
94686
94855
  const engine = new FormulaEngine(workbook);
94687
94856
  engine.buildGraph();
94688
94857
  engine.evaluateAll();
94689
- return collectRecomputeReport(workbook);
94858
+ return collectRecomputeReport(workbook, engine.unreadable());
94690
94859
  }
94691
- function collectRecomputeReport(workbook) {
94860
+ function collectRecomputeReport(workbook, unreadable) {
94692
94861
  let total = 0;
94693
94862
  const unevaluated = [];
94694
94863
  for (const sheet of workbook.sheets) {
94695
94864
  for (const [ref2, cell] of sheet.cells) {
94696
94865
  if (!cell.formula) continue;
94697
94866
  total++;
94867
+ if (unreadable.has(cellKey({ sheet: sheet.name, ref: ref2 }))) {
94868
+ unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: "#UNREADABLE!" });
94869
+ continue;
94870
+ }
94698
94871
  if (cell.value === null) {
94699
94872
  unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: cell.error ?? "#ERROR!" });
94700
94873
  }
@@ -95632,9 +95805,9 @@ function applySheetCells(workbook, sheetIndex, def) {
95632
95805
  }
95633
95806
  }
95634
95807
  if (def.rowHeights) {
95635
- for (const [rowKey, height] of Object.entries(def.rowHeights)) {
95636
- const row = Number(rowKey);
95637
- if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${rowKey}`);
95808
+ for (const [rowKey2, height] of Object.entries(def.rowHeights)) {
95809
+ const row = Number(rowKey2);
95810
+ if (!Number.isInteger(row) || row < 1) fail2(`Invalid row in sheet "${def.name}".rowHeights: ${rowKey2}`);
95638
95811
  sheet.rowHeights.set(row, height);
95639
95812
  }
95640
95813
  }
@@ -224,6 +224,10 @@ export declare class LoticsClient {
224
224
  getApp(app_id: string): Promise<{
225
225
  id: string;
226
226
  name: string;
227
+ /** What the app is for. Heads the capability catalog the member's chat agent
228
+ * reads every turn the app is open, so it is where a standing process is
229
+ * stated. Null when unset. */
230
+ description?: string | null;
227
231
  workspace_id: string;
228
232
  current_version_id: string | null;
229
233
  /** Launcher icon — a Lucide name, or an image ref. Null when unset. */