la-machina-engine 0.7.0 → 0.7.1

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/README.md CHANGED
@@ -1181,6 +1181,15 @@ await writeKnowledgeIndex({ adapter: k, base: 'hr-policies' })
1181
1181
  await writeKnowledgeIndex({ adapter: k, base: 'sales-playbook' })
1182
1182
  ```
1183
1183
 
1184
+ **Forgot to build the index?** Both tools fall back to an in-memory
1185
+ build on first call when `_index.json` is missing or corrupted. The
1186
+ fallback caches for the rest of the run, so subsequent searches are
1187
+ free. This makes the index a performance optimisation (skip the walk
1188
+ on every fresh run), not a setup requirement — drop files into the
1189
+ folder and the agent can discover them immediately. Pre-build with
1190
+ `writeKnowledgeIndex()` for production-scale corpora where the
1191
+ first-call cost matters.
1192
+
1184
1193
  **Configure the engine** to enable the tools (off by default):
1185
1194
 
1186
1195
  ```ts
package/dist/index.cjs CHANGED
@@ -8201,75 +8201,8 @@ init_cjs_shims();
8201
8201
  var import_zod26 = require("zod");
8202
8202
  init_contract();
8203
8203
 
8204
- // src/knowledge/scope.ts
8204
+ // src/knowledge/indexer.ts
8205
8205
  init_cjs_shims();
8206
- var SAFE_PATH_RE = /^[a-zA-Z0-9_\-./]+$/;
8207
- function parseFolderRef(raw) {
8208
- if (typeof raw !== "string" || raw.length === 0) {
8209
- throw new Error(`invalid knowledge folder ref: empty`);
8210
- }
8211
- if (raw.startsWith("/")) {
8212
- throw new Error(`invalid knowledge folder ref: absolute paths not allowed ("${raw}")`);
8213
- }
8214
- const trimmed = raw.replace(/\/+$/g, "");
8215
- if (trimmed.length === 0) {
8216
- throw new Error(`invalid knowledge folder ref: "${raw}"`);
8217
- }
8218
- if (!SAFE_PATH_RE.test(trimmed)) {
8219
- throw new Error(`invalid knowledge folder ref: unsafe characters in "${raw}"`);
8220
- }
8221
- if (trimmed.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) {
8222
- throw new Error(`invalid knowledge folder ref: traversal in "${raw}"`);
8223
- }
8224
- const segs = trimmed.split("/");
8225
- const base = segs[0];
8226
- const subPath = segs.slice(1).join("/");
8227
- return { path: trimmed, base, subPath };
8228
- }
8229
- function relPathInScope(folder, relPath) {
8230
- if (folder.subPath === "") return true;
8231
- return relPath === folder.subPath || relPath.startsWith(`${folder.subPath}/`);
8232
- }
8233
- function parseKnowledgeRef(raw) {
8234
- if (typeof raw !== "string" || raw.length === 0) {
8235
- throw new Error("invalid knowledge ref: empty");
8236
- }
8237
- if (raw.startsWith("ext:")) {
8238
- const name = raw.slice("ext:".length);
8239
- if (!/^[a-zA-Z0-9_-]+$/.test(name) || name.length === 0) {
8240
- throw new Error(`invalid knowledge ref: external name "${name}" has unsafe characters`);
8241
- }
8242
- return { kind: "ext", target: name };
8243
- }
8244
- if (raw.startsWith("/")) {
8245
- throw new Error(`invalid knowledge ref: absolute paths not allowed ("${raw}")`);
8246
- }
8247
- const hashAt = raw.indexOf("#");
8248
- const filePath = hashAt === -1 ? raw : raw.slice(0, hashAt);
8249
- const section = hashAt === -1 ? void 0 : raw.slice(hashAt + 1);
8250
- if (!SAFE_PATH_RE.test(filePath)) {
8251
- throw new Error(`invalid knowledge ref: unsafe characters in "${filePath}"`);
8252
- }
8253
- if (filePath.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) {
8254
- throw new Error(`invalid knowledge ref: traversal in "${filePath}"`);
8255
- }
8256
- if (section !== void 0) {
8257
- if (section.length > 0 && !/^[a-zA-Z0-9_-]+$/.test(section)) {
8258
- throw new Error(`invalid knowledge ref: unsafe characters in section "${section}"`);
8259
- }
8260
- return { kind: "section", target: filePath, section };
8261
- }
8262
- return { kind: "file", target: filePath };
8263
- }
8264
- function refInScope(folders, filePath) {
8265
- return folders.some((f) => {
8266
- if (filePath === f.base || filePath.startsWith(`${f.base}/`)) {
8267
- const relInBase = filePath === f.base ? "" : filePath.slice(f.base.length + 1);
8268
- return relPathInScope(f, relInBase);
8269
- }
8270
- return false;
8271
- });
8272
- }
8273
8206
 
8274
8207
  // src/knowledge/tokenize.ts
8275
8208
  init_cjs_shims();
@@ -8346,6 +8279,220 @@ function scoreOverlap(sectionWords, queryTokens) {
8346
8279
  return n;
8347
8280
  }
8348
8281
 
8282
+ // src/knowledge/indexer.ts
8283
+ var HEADING_RE = /^(#{1,6})[ \t]+(.+?)\s*$/;
8284
+ var WIKI_LINK_RE = /\[\[([^\]|#]+)(?:[#|][^\]]*)?\]\]/g;
8285
+ var FORMAT_BY_EXT = {
8286
+ md: "md",
8287
+ markdown: "md",
8288
+ txt: "txt",
8289
+ json: "json",
8290
+ csv: "csv",
8291
+ html: "html",
8292
+ htm: "html",
8293
+ pdf: "pdf",
8294
+ docx: "docx"
8295
+ };
8296
+ var PREVIEW_CHARS = 200;
8297
+ async function buildKnowledgeIndex(options) {
8298
+ const { adapter, base } = options;
8299
+ const safeBase = base.replace(/^\/+|\/+$/g, "");
8300
+ if (safeBase.length === 0 || safeBase.includes("..")) {
8301
+ throw new Error(`buildKnowledgeIndex: invalid base "${base}"`);
8302
+ }
8303
+ const files = await listFilesRecursive(adapter, safeBase);
8304
+ const sections = [];
8305
+ const filesMeta = {};
8306
+ for (const fileRel of files) {
8307
+ if (fileRel === "_index.json") continue;
8308
+ const fullPath = `${safeBase}/${fileRel}`;
8309
+ const ext = (fileRel.split(".").pop() ?? "").toLowerCase();
8310
+ const format = FORMAT_BY_EXT[ext];
8311
+ if (format === void 0) continue;
8312
+ const raw = await adapter.readFile(fullPath);
8313
+ if (raw === null) continue;
8314
+ const sizeBytes = byteLength3(raw);
8315
+ const meta = { format, size: sizeBytes };
8316
+ if (format === "md" || format === "txt") {
8317
+ const fileSections = splitSections(raw, fileRel);
8318
+ sections.push(...fileSections);
8319
+ const wikiLinks = extractWikiLinks(raw);
8320
+ if (wikiLinks.length > 0) meta.wikiLinks = wikiLinks;
8321
+ }
8322
+ filesMeta[fileRel] = meta;
8323
+ }
8324
+ return {
8325
+ schema: "v1",
8326
+ base: safeBase,
8327
+ builtAt: options.nowIso ?? (/* @__PURE__ */ new Date()).toISOString(),
8328
+ fileCount: Object.keys(filesMeta).length,
8329
+ sections,
8330
+ files: filesMeta
8331
+ };
8332
+ }
8333
+ async function writeKnowledgeIndex(options) {
8334
+ const index = await buildKnowledgeIndex(options);
8335
+ await options.adapter.writeFile(`${index.base}/_index.json`, JSON.stringify(index, null, 2));
8336
+ return index;
8337
+ }
8338
+ async function listFilesRecursive(adapter, dir) {
8339
+ const out = [];
8340
+ const stack = [""];
8341
+ while (stack.length > 0) {
8342
+ const sub = stack.pop();
8343
+ const fullDir = sub === "" ? dir : `${dir}/${sub}`;
8344
+ let entries = [];
8345
+ try {
8346
+ entries = await adapter.listDir(fullDir);
8347
+ } catch {
8348
+ continue;
8349
+ }
8350
+ for (const name of entries) {
8351
+ const childRel = sub === "" ? name : `${sub}/${name}`;
8352
+ const childFull = `${dir}/${childRel}`;
8353
+ const isDir = await adapter.isDirectory(childFull).catch(() => false);
8354
+ if (isDir) {
8355
+ stack.push(childRel);
8356
+ } else {
8357
+ out.push(childRel);
8358
+ }
8359
+ }
8360
+ }
8361
+ return out.sort();
8362
+ }
8363
+ function splitSections(content, relPath) {
8364
+ const lines = content.split(/\r?\n/);
8365
+ const out = [];
8366
+ const heads = [];
8367
+ for (let i = 0; i < lines.length; i++) {
8368
+ const line = lines[i];
8369
+ const m = HEADING_RE.exec(line);
8370
+ if (m) heads.push({ line: i + 1, depth: m[1].length, heading: m[2].trim() });
8371
+ }
8372
+ const leadInEndLine = heads.length > 0 ? heads[0].line - 1 : lines.length;
8373
+ const leadInBody = lines.slice(0, leadInEndLine).join("\n").trim();
8374
+ if (leadInBody.length > 0) {
8375
+ out.push({
8376
+ relPath,
8377
+ heading: "",
8378
+ slug: `${relPath}#`,
8379
+ depth: 0,
8380
+ words: tokenize(leadInBody),
8381
+ preview: makePreview(leadInBody),
8382
+ startLine: 1,
8383
+ endLine: leadInEndLine
8384
+ });
8385
+ }
8386
+ for (let i = 0; i < heads.length; i++) {
8387
+ const h = heads[i];
8388
+ const startLine = h.line;
8389
+ const endLine = i + 1 < heads.length ? heads[i + 1].line - 1 : lines.length;
8390
+ const body = lines.slice(startLine - 1, endLine).join("\n");
8391
+ out.push({
8392
+ relPath,
8393
+ heading: h.heading,
8394
+ slug: `${relPath}#${slugify(h.heading)}`,
8395
+ depth: h.depth,
8396
+ words: tokenize(body),
8397
+ preview: makePreview(body),
8398
+ startLine,
8399
+ endLine
8400
+ });
8401
+ }
8402
+ return out;
8403
+ }
8404
+ function makePreview(body) {
8405
+ const trimmed = body.replace(/^#{1,6}\s+.+$\r?\n?/m, "").trim();
8406
+ if (trimmed.length <= PREVIEW_CHARS) return trimmed;
8407
+ return trimmed.slice(0, PREVIEW_CHARS) + "\u2026";
8408
+ }
8409
+ function slugify(text2) {
8410
+ return text2.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
8411
+ }
8412
+ function extractWikiLinks(text2) {
8413
+ const seen = /* @__PURE__ */ new Set();
8414
+ let m;
8415
+ WIKI_LINK_RE.lastIndex = 0;
8416
+ while ((m = WIKI_LINK_RE.exec(text2)) !== null) {
8417
+ const target = m[1].trim();
8418
+ if (target.length > 0) seen.add(target);
8419
+ }
8420
+ return [...seen].sort();
8421
+ }
8422
+ function byteLength3(s) {
8423
+ return new TextEncoder().encode(s).byteLength;
8424
+ }
8425
+
8426
+ // src/knowledge/scope.ts
8427
+ init_cjs_shims();
8428
+ var SAFE_PATH_RE = /^[a-zA-Z0-9_\-./]+$/;
8429
+ function parseFolderRef(raw) {
8430
+ if (typeof raw !== "string" || raw.length === 0) {
8431
+ throw new Error(`invalid knowledge folder ref: empty`);
8432
+ }
8433
+ if (raw.startsWith("/")) {
8434
+ throw new Error(`invalid knowledge folder ref: absolute paths not allowed ("${raw}")`);
8435
+ }
8436
+ const trimmed = raw.replace(/\/+$/g, "");
8437
+ if (trimmed.length === 0) {
8438
+ throw new Error(`invalid knowledge folder ref: "${raw}"`);
8439
+ }
8440
+ if (!SAFE_PATH_RE.test(trimmed)) {
8441
+ throw new Error(`invalid knowledge folder ref: unsafe characters in "${raw}"`);
8442
+ }
8443
+ if (trimmed.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) {
8444
+ throw new Error(`invalid knowledge folder ref: traversal in "${raw}"`);
8445
+ }
8446
+ const segs = trimmed.split("/");
8447
+ const base = segs[0];
8448
+ const subPath = segs.slice(1).join("/");
8449
+ return { path: trimmed, base, subPath };
8450
+ }
8451
+ function relPathInScope(folder, relPath) {
8452
+ if (folder.subPath === "") return true;
8453
+ return relPath === folder.subPath || relPath.startsWith(`${folder.subPath}/`);
8454
+ }
8455
+ function parseKnowledgeRef(raw) {
8456
+ if (typeof raw !== "string" || raw.length === 0) {
8457
+ throw new Error("invalid knowledge ref: empty");
8458
+ }
8459
+ if (raw.startsWith("ext:")) {
8460
+ const name = raw.slice("ext:".length);
8461
+ if (!/^[a-zA-Z0-9_-]+$/.test(name) || name.length === 0) {
8462
+ throw new Error(`invalid knowledge ref: external name "${name}" has unsafe characters`);
8463
+ }
8464
+ return { kind: "ext", target: name };
8465
+ }
8466
+ if (raw.startsWith("/")) {
8467
+ throw new Error(`invalid knowledge ref: absolute paths not allowed ("${raw}")`);
8468
+ }
8469
+ const hashAt = raw.indexOf("#");
8470
+ const filePath = hashAt === -1 ? raw : raw.slice(0, hashAt);
8471
+ const section = hashAt === -1 ? void 0 : raw.slice(hashAt + 1);
8472
+ if (!SAFE_PATH_RE.test(filePath)) {
8473
+ throw new Error(`invalid knowledge ref: unsafe characters in "${filePath}"`);
8474
+ }
8475
+ if (filePath.split("/").some((seg) => seg === ".." || seg === "." || seg === "")) {
8476
+ throw new Error(`invalid knowledge ref: traversal in "${filePath}"`);
8477
+ }
8478
+ if (section !== void 0) {
8479
+ if (section.length > 0 && !/^[a-zA-Z0-9_-]+$/.test(section)) {
8480
+ throw new Error(`invalid knowledge ref: unsafe characters in section "${section}"`);
8481
+ }
8482
+ return { kind: "section", target: filePath, section };
8483
+ }
8484
+ return { kind: "file", target: filePath };
8485
+ }
8486
+ function refInScope(folders, filePath) {
8487
+ return folders.some((f) => {
8488
+ if (filePath === f.base || filePath.startsWith(`${f.base}/`)) {
8489
+ const relInBase = filePath === f.base ? "" : filePath.slice(f.base.length + 1);
8490
+ return relPathInScope(f, relInBase);
8491
+ }
8492
+ return false;
8493
+ });
8494
+ }
8495
+
8349
8496
  // src/tools/searchKnowledge.ts
8350
8497
  var DEFAULT_MAX_RESULTS = 5;
8351
8498
  var inputSchema18 = import_zod26.z.object({
@@ -8430,17 +8577,24 @@ function truncatePreview(s) {
8430
8577
  async function loadIndex(adapter, base, cache) {
8431
8578
  const cached2 = cache.get(base);
8432
8579
  if (cached2 !== void 0) return cached2;
8433
- let raw;
8580
+ let raw = null;
8434
8581
  try {
8435
8582
  raw = await adapter.readFile(`${base}/_index.json`);
8436
8583
  } catch {
8437
- return null;
8584
+ raw = null;
8585
+ }
8586
+ if (raw !== null) {
8587
+ try {
8588
+ const parsed = JSON.parse(raw);
8589
+ cache.set(base, parsed);
8590
+ return parsed;
8591
+ } catch {
8592
+ }
8438
8593
  }
8439
- if (raw === null) return null;
8440
8594
  try {
8441
- const parsed = JSON.parse(raw);
8442
- cache.set(base, parsed);
8443
- return parsed;
8595
+ const built = await buildKnowledgeIndex({ adapter, base });
8596
+ cache.set(base, built);
8597
+ return built;
8444
8598
  } catch {
8445
8599
  return null;
8446
8600
  }
@@ -8599,7 +8753,7 @@ function createReadKnowledgeTool(opts) {
8599
8753
  const idx = await loadIndex2(opts.adapter, base, indexCache);
8600
8754
  if (idx === null) {
8601
8755
  return {
8602
- content: `ERR_KNOWLEDGE_INDEX_MISSING: no _index.json for base "${base}"`,
8756
+ content: `ERR_KNOWLEDGE_REF_NOT_FOUND: base "${base}" has no readable files`,
8603
8757
  isError: true
8604
8758
  };
8605
8759
  }
@@ -8705,17 +8859,24 @@ ${payload}`,
8705
8859
  async function loadIndex2(adapter, base, cache) {
8706
8860
  const cached2 = cache.get(base);
8707
8861
  if (cached2 !== void 0) return cached2;
8708
- let raw;
8862
+ let raw = null;
8709
8863
  try {
8710
8864
  raw = await adapter.readFile(`${base}/_index.json`);
8711
8865
  } catch {
8712
- return null;
8866
+ raw = null;
8867
+ }
8868
+ if (raw !== null) {
8869
+ try {
8870
+ const idx = JSON.parse(raw);
8871
+ cache.set(base, idx);
8872
+ return idx;
8873
+ } catch {
8874
+ }
8713
8875
  }
8714
- if (raw === null) return null;
8715
8876
  try {
8716
- const idx = JSON.parse(raw);
8717
- cache.set(base, idx);
8718
- return idx;
8877
+ const built = await buildKnowledgeIndex({ adapter, base });
8878
+ cache.set(base, built);
8879
+ return built;
8719
8880
  } catch {
8720
8881
  return null;
8721
8882
  }
@@ -11239,153 +11400,6 @@ function buildToolRegistry(options) {
11239
11400
  // src/index.ts
11240
11401
  init_contract();
11241
11402
  init_fetchData();
11242
-
11243
- // src/knowledge/indexer.ts
11244
- init_cjs_shims();
11245
- var HEADING_RE = /^(#{1,6})[ \t]+(.+?)\s*$/;
11246
- var WIKI_LINK_RE = /\[\[([^\]|#]+)(?:[#|][^\]]*)?\]\]/g;
11247
- var FORMAT_BY_EXT = {
11248
- md: "md",
11249
- markdown: "md",
11250
- txt: "txt",
11251
- json: "json",
11252
- csv: "csv",
11253
- html: "html",
11254
- htm: "html",
11255
- pdf: "pdf",
11256
- docx: "docx"
11257
- };
11258
- var PREVIEW_CHARS = 200;
11259
- async function buildKnowledgeIndex(options) {
11260
- const { adapter, base } = options;
11261
- const safeBase = base.replace(/^\/+|\/+$/g, "");
11262
- if (safeBase.length === 0 || safeBase.includes("..")) {
11263
- throw new Error(`buildKnowledgeIndex: invalid base "${base}"`);
11264
- }
11265
- const files = await listFilesRecursive(adapter, safeBase);
11266
- const sections = [];
11267
- const filesMeta = {};
11268
- for (const fileRel of files) {
11269
- if (fileRel === "_index.json") continue;
11270
- const fullPath = `${safeBase}/${fileRel}`;
11271
- const ext = (fileRel.split(".").pop() ?? "").toLowerCase();
11272
- const format = FORMAT_BY_EXT[ext];
11273
- if (format === void 0) continue;
11274
- const raw = await adapter.readFile(fullPath);
11275
- if (raw === null) continue;
11276
- const sizeBytes = byteLength3(raw);
11277
- const meta = { format, size: sizeBytes };
11278
- if (format === "md" || format === "txt") {
11279
- const fileSections = splitSections(raw, fileRel);
11280
- sections.push(...fileSections);
11281
- const wikiLinks = extractWikiLinks(raw);
11282
- if (wikiLinks.length > 0) meta.wikiLinks = wikiLinks;
11283
- }
11284
- filesMeta[fileRel] = meta;
11285
- }
11286
- return {
11287
- schema: "v1",
11288
- base: safeBase,
11289
- builtAt: options.nowIso ?? (/* @__PURE__ */ new Date()).toISOString(),
11290
- fileCount: Object.keys(filesMeta).length,
11291
- sections,
11292
- files: filesMeta
11293
- };
11294
- }
11295
- async function writeKnowledgeIndex(options) {
11296
- const index = await buildKnowledgeIndex(options);
11297
- await options.adapter.writeFile(`${index.base}/_index.json`, JSON.stringify(index, null, 2));
11298
- return index;
11299
- }
11300
- async function listFilesRecursive(adapter, dir) {
11301
- const out = [];
11302
- const stack = [""];
11303
- while (stack.length > 0) {
11304
- const sub = stack.pop();
11305
- const fullDir = sub === "" ? dir : `${dir}/${sub}`;
11306
- let entries = [];
11307
- try {
11308
- entries = await adapter.listDir(fullDir);
11309
- } catch {
11310
- continue;
11311
- }
11312
- for (const name of entries) {
11313
- const childRel = sub === "" ? name : `${sub}/${name}`;
11314
- const childFull = `${dir}/${childRel}`;
11315
- const isDir = await adapter.isDirectory(childFull).catch(() => false);
11316
- if (isDir) {
11317
- stack.push(childRel);
11318
- } else {
11319
- out.push(childRel);
11320
- }
11321
- }
11322
- }
11323
- return out.sort();
11324
- }
11325
- function splitSections(content, relPath) {
11326
- const lines = content.split(/\r?\n/);
11327
- const out = [];
11328
- const heads = [];
11329
- for (let i = 0; i < lines.length; i++) {
11330
- const line = lines[i];
11331
- const m = HEADING_RE.exec(line);
11332
- if (m) heads.push({ line: i + 1, depth: m[1].length, heading: m[2].trim() });
11333
- }
11334
- const leadInEndLine = heads.length > 0 ? heads[0].line - 1 : lines.length;
11335
- const leadInBody = lines.slice(0, leadInEndLine).join("\n").trim();
11336
- if (leadInBody.length > 0) {
11337
- out.push({
11338
- relPath,
11339
- heading: "",
11340
- slug: `${relPath}#`,
11341
- depth: 0,
11342
- words: tokenize(leadInBody),
11343
- preview: makePreview(leadInBody),
11344
- startLine: 1,
11345
- endLine: leadInEndLine
11346
- });
11347
- }
11348
- for (let i = 0; i < heads.length; i++) {
11349
- const h = heads[i];
11350
- const startLine = h.line;
11351
- const endLine = i + 1 < heads.length ? heads[i + 1].line - 1 : lines.length;
11352
- const body = lines.slice(startLine - 1, endLine).join("\n");
11353
- out.push({
11354
- relPath,
11355
- heading: h.heading,
11356
- slug: `${relPath}#${slugify(h.heading)}`,
11357
- depth: h.depth,
11358
- words: tokenize(body),
11359
- preview: makePreview(body),
11360
- startLine,
11361
- endLine
11362
- });
11363
- }
11364
- return out;
11365
- }
11366
- function makePreview(body) {
11367
- const trimmed = body.replace(/^#{1,6}\s+.+$\r?\n?/m, "").trim();
11368
- if (trimmed.length <= PREVIEW_CHARS) return trimmed;
11369
- return trimmed.slice(0, PREVIEW_CHARS) + "\u2026";
11370
- }
11371
- function slugify(text2) {
11372
- return text2.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
11373
- }
11374
- function extractWikiLinks(text2) {
11375
- const seen = /* @__PURE__ */ new Set();
11376
- let m;
11377
- WIKI_LINK_RE.lastIndex = 0;
11378
- while ((m = WIKI_LINK_RE.exec(text2)) !== null) {
11379
- const target = m[1].trim();
11380
- if (target.length > 0) seen.add(target);
11381
- }
11382
- return [...seen].sort();
11383
- }
11384
- function byteLength3(s) {
11385
- return new TextEncoder().encode(s).byteLength;
11386
- }
11387
-
11388
- // src/index.ts
11389
11403
  init_orchestrate();
11390
11404
  init_planParser();
11391
11405
  init_retry();