@exulu/backend 3.7.3 → 3.7.4

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.
@@ -39,6 +39,10 @@ function getSetupScriptPath(packageRoot) {
39
39
  function getVenvPath(packageRoot) {
40
40
  return resolve(packageRoot, "ee/python/.venv");
41
41
  }
42
+ function getPythonVenvPath(packageRoot) {
43
+ const root = packageRoot ?? getPackageRoot();
44
+ return isPythonEnvironmentSetup(root) ? getVenvPath(root) : void 0;
45
+ }
42
46
  function isPythonEnvironmentSetup(packageRoot) {
43
47
  const root = packageRoot ?? getPackageRoot();
44
48
  const venvPath = getVenvPath(root);
@@ -205,6 +209,7 @@ Or manually run the setup script:
205
209
 
206
210
  export {
207
211
  getPackageRoot,
212
+ getPythonVenvPath,
208
213
  isPythonEnvironmentSetup,
209
214
  setupPythonEnvironment,
210
215
  getPythonSetupInstructions,
@@ -1716,7 +1716,7 @@ var ExuluTool = class _ExuluTool {
1716
1716
  if (!agent) {
1717
1717
  throw new Error("Agent not found.");
1718
1718
  }
1719
- const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-UQSLJDXE.js");
1719
+ const { convertExuluToolsToAiSdkTools: convertExuluToolsToAiSdkTools2 } = await import("./convert-exulu-tools-to-ai-sdk-tools-XNQ6Q3X6.js");
1720
1720
  const tools = await convertExuluToolsToAiSdkTools2(
1721
1721
  [this],
1722
1722
  [],
@@ -5723,6 +5723,33 @@ var createUppyRoutes = async (app, config) => {
5723
5723
  return app;
5724
5724
  };
5725
5725
 
5726
+ // ee/invoke-skills/artifact-filter.ts
5727
+ var IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
5728
+ "venv",
5729
+ ".venv",
5730
+ "env",
5731
+ "node_modules",
5732
+ "__pycache__",
5733
+ "site-packages",
5734
+ "dist-packages",
5735
+ ".cache",
5736
+ ".git",
5737
+ ".pytest_cache",
5738
+ ".mypy_cache",
5739
+ ".ipynb_checkpoints"
5740
+ ]);
5741
+ function isIgnoredArtifactPath(relativePath) {
5742
+ return relativePath.split(/[\\/]+/).some((segment) => IGNORED_SEGMENTS.has(segment));
5743
+ }
5744
+ var DEFAULT_ARTIFACT_CAP = 50;
5745
+ function capArtifacts(artifacts, max = DEFAULT_ARTIFACT_CAP) {
5746
+ if (artifacts.length <= max) return { kept: artifacts, omitted: 0 };
5747
+ return { kept: artifacts.slice(0, max), omitted: artifacts.length - max };
5748
+ }
5749
+ function needsDownload(localSize, remoteSize) {
5750
+ return localSize === void 0 || localSize !== remoteSize;
5751
+ }
5752
+
5726
5753
  // src/exulu/system-dependencies.ts
5727
5754
  import { exec } from "child_process";
5728
5755
  import { existsSync as existsSync2 } from "fs";
@@ -5959,7 +5986,7 @@ function resolveSessionPath(inputPath, sessionDir) {
5959
5986
  }
5960
5987
  return resolved;
5961
5988
  }
5962
- async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
5989
+ async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config, opts = {}) {
5963
5990
  const userPrefix = `user_${userId}/sessions/${sessionId}/`;
5964
5991
  let objects;
5965
5992
  try {
@@ -5979,7 +6006,17 @@ async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
5979
6006
  const idx = obj.key.indexOf(userPrefix);
5980
6007
  const relativePath = idx >= 0 ? obj.key.slice(idx + userPrefix.length) : "";
5981
6008
  if (!relativePath) continue;
6009
+ if (isIgnoredArtifactPath(relativePath)) continue;
5982
6010
  const localPath = join2(sessionDir, relativePath);
6011
+ if (opts.onlyMissing) {
6012
+ let localSize;
6013
+ try {
6014
+ localSize = (await stat(localPath)).size;
6015
+ } catch {
6016
+ localSize = void 0;
6017
+ }
6018
+ if (!needsDownload(localSize, obj.size)) continue;
6019
+ }
5983
6020
  try {
5984
6021
  const bytes = await getS3ObjectBytes(obj.key, config);
5985
6022
  await mkdir(dirname(localPath), { recursive: true });
@@ -6013,6 +6050,15 @@ async function downloadKeyIntoSandbox(opts) {
6013
6050
  await writeFile(localPath, bytes);
6014
6051
  return { written: true, localPath };
6015
6052
  }
6053
+ async function resolvePythonVenvPath() {
6054
+ try {
6055
+ const { getPythonVenvPath } = await import("./python-setup-JZGHWQCG.js");
6056
+ return getPythonVenvPath();
6057
+ } catch (err) {
6058
+ console.warn("[SKILLS] Could not resolve the Python venv for the session sandbox; skill scripts fall back to the system python.", err);
6059
+ return void 0;
6060
+ }
6061
+ }
6016
6062
  async function createSessionSandbox(sessionId, skills, config, userId) {
6017
6063
  const cached = sandboxCache.get(sessionId);
6018
6064
  if (cached) {
@@ -6026,6 +6072,13 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
6026
6072
  await downloadSkill(skill, skillsDirectory2, config);
6027
6073
  cached.installedSkills.set(skill.id, skill.current_version);
6028
6074
  }
6075
+ if (userId && config.fileUploads) {
6076
+ try {
6077
+ await restoreArtifactsFromS3(cached.handle.sessionDir, sessionId, userId, config, { onlyMissing: true });
6078
+ } catch (err) {
6079
+ console.error(`[SKILLS] Failed to re-sync S3 session files for session ${sessionId}; continuing.`, err);
6080
+ }
6081
+ }
6029
6082
  return cached.handle;
6030
6083
  }
6031
6084
  const sessionDir = join2("/tmp", "exulu-sessions", sessionId);
@@ -6043,8 +6096,8 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
6043
6096
  `[SKILLS] S3 artifact persistence disabled for session ${sessionId} (userId=${userId ?? "missing"}, fileUploads=${config.fileUploads ? "configured" : "missing"})`
6044
6097
  );
6045
6098
  }
6046
- if (userId && config.fileUploads && !dirExisted) {
6047
- await restoreArtifactsFromS3(sessionDir, sessionId, userId, config);
6099
+ if (userId && config.fileUploads) {
6100
+ await restoreArtifactsFromS3(sessionDir, sessionId, userId, config, { onlyMissing: dirExisted });
6048
6101
  }
6049
6102
  const probe = await probeSandboxSupport();
6050
6103
  const useDirectExec = !probe.canSandbox;
@@ -6079,6 +6132,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
6079
6132
  await SandboxManager.initialize(baselineSandboxConfig);
6080
6133
  }
6081
6134
  const npmGlobalRoot = await getNpmGlobalRoot();
6135
+ const pythonVenvPath = await resolvePythonVenvPath();
6082
6136
  const sessionSandboxConfig = {
6083
6137
  network: {
6084
6138
  allowedDomains: [],
@@ -6093,7 +6147,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
6093
6147
  // Allow Node to read globally-installed packages from inside
6094
6148
  // the sandbox. Without this, `require('docx')` fails with
6095
6149
  // EPERM even when NODE_PATH points the resolver here.
6096
- ...npmGlobalRoot ? [npmGlobalRoot] : []
6150
+ ...npmGlobalRoot ? [npmGlobalRoot] : [],
6151
+ ...pythonVenvPath ? [pythonVenvPath] : []
6097
6152
  ],
6098
6153
  allowWrite: [sessionDir],
6099
6154
  denyWrite: []
@@ -6111,7 +6166,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
6111
6166
  const sandboxedExecEnv = {
6112
6167
  ...configuredVariables,
6113
6168
  ...process.env,
6114
- ...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {}
6169
+ ...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {},
6170
+ ...pythonVenvPath ? { PATH: `${join2(pythonVenvPath, "bin")}:${process.env.PATH ?? ""}`, VIRTUAL_ENV: pythonVenvPath } : {}
6115
6171
  };
6116
6172
  const wrapIfNeeded = async (command) => {
6117
6173
  if (useDirectExec) return command;
@@ -6236,6 +6292,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
6236
6292
  for (const entry of entries) {
6237
6293
  const full = join2(dir, entry.name);
6238
6294
  if (full === skillsDir) continue;
6295
+ if (isIgnoredArtifactPath(relative(sessionDir, full))) continue;
6239
6296
  if (entry.isDirectory()) {
6240
6297
  await walk(full);
6241
6298
  } else if (entry.isFile()) {
@@ -6331,19 +6388,24 @@ Probe error: ${probe.reason ?? "(no detail)"}`
6331
6388
  }
6332
6389
  }
6333
6390
  let stdout = result?.stdout ?? "";
6334
- const withUrls = artifacts.filter((a) => a.url);
6391
+ const { kept, omitted } = capArtifacts(artifacts);
6392
+ const withUrls = kept.filter((a) => a.url);
6335
6393
  if (withUrls.length > 0) {
6336
6394
  const lines = ["", "[exulu-artifacts]"];
6337
6395
  for (const a of withUrls) {
6338
6396
  lines.push(` ${a.relativePath}: ${a.url}`);
6339
6397
  }
6398
+ if (omitted > 0) {
6399
+ lines.push(` \u2026 ${omitted} more file(s) were created and mirrored but are not listed here.`);
6400
+ }
6340
6401
  stdout = `${stdout}
6341
6402
  ${lines.join("\n")}`;
6342
6403
  }
6343
6404
  return {
6344
6405
  ...result,
6345
6406
  stdout,
6346
- artifacts
6407
+ artifacts: kept,
6408
+ ...omitted > 0 ? { artifactsOmitted: omitted } : {}
6347
6409
  };
6348
6410
  }
6349
6411
  });
@@ -6561,12 +6623,21 @@ var buildAuthToolModelOutput = (tool3) => ({ output }) => {
6561
6623
 
6562
6624
  // src/templates/tools/session-file-read-tool.ts
6563
6625
  import { z as z12 } from "zod";
6626
+
6627
+ // src/exulu/session-files.ts
6628
+ function sessionFilePrefix(ownerId, sessionId, s3prefix) {
6629
+ const general = s3prefix ? `${s3prefix.replace(/\/+$/, "")}/` : "";
6630
+ return `${general}user_${ownerId}/sessions/${sessionId}/`;
6631
+ }
6632
+
6633
+ // src/templates/tools/session-file-read-tool.ts
6564
6634
  var DEFAULT_LIMIT = 250;
6565
6635
  var MAX_CONTENT_CHARS = 16e3;
6566
6636
  var createSessionFileReadTool = ({
6567
6637
  sessionID,
6568
6638
  user,
6569
- exuluConfig
6639
+ exuluConfig,
6640
+ ownerId
6570
6641
  }) => {
6571
6642
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
6572
6643
  const readSessionFileExecute = async ({ filename, offset, limit }) => {
@@ -6577,8 +6648,7 @@ var createSessionFileReadTool = ({
6577
6648
  };
6578
6649
  }
6579
6650
  const uploads = exuluConfig.fileUploads;
6580
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
6581
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
6651
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
6582
6652
  try {
6583
6653
  const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
6584
6654
  const res = await fetch(url);
@@ -6681,6 +6751,18 @@ async function renderPdfPageToPng(pdf, page, scaleTo) {
6681
6751
  var DEFAULT_LIMIT2 = 250;
6682
6752
  var MAX_CONTENT_CHARS2 = 16e3;
6683
6753
  var MIN_CHARS_PER_PAGE = 20;
6754
+ function looksLikeGarbledTextLayer(text) {
6755
+ let control = 0;
6756
+ let visible = 0;
6757
+ for (const ch of text) {
6758
+ const code = ch.charCodeAt(0);
6759
+ if (code === 9 || code === 10 || code === 12 || code === 13 || code === 32) continue;
6760
+ visible++;
6761
+ if (code < 32 || code === 127) control++;
6762
+ }
6763
+ if (visible < 40) return false;
6764
+ return control / visible > 0.01;
6765
+ }
6684
6766
  var OFFICE_EXTENSIONS = /* @__PURE__ */ new Set([
6685
6767
  ".docx",
6686
6768
  ".doc",
@@ -6697,7 +6779,8 @@ var pagesPattern = /^(\d+)(?:-(\d+))?$/;
6697
6779
  var createParseDocumentTool = ({
6698
6780
  sessionID,
6699
6781
  user,
6700
- exuluConfig
6782
+ exuluConfig,
6783
+ ownerId
6701
6784
  }) => {
6702
6785
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
6703
6786
  const parseDocumentExecute = async ({
@@ -6722,8 +6805,7 @@ var createParseDocumentTool = ({
6722
6805
  return { error: `The pages option is only supported for PDF files \u2014 "${ext}" documents are extracted whole.` };
6723
6806
  }
6724
6807
  const uploads = exuluConfig.fileUploads;
6725
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
6726
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
6808
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
6727
6809
  try {
6728
6810
  const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
6729
6811
  const res = await fetch(url);
@@ -6738,6 +6820,11 @@ var createParseDocumentTool = ({
6738
6820
  const pageTexts = raw.replace(/\f$/, "").split("\f");
6739
6821
  totalPages = pageTexts.length;
6740
6822
  const nonWhitespace = raw.replace(/\s/g, "").length;
6823
+ if (looksLikeGarbledTextLayer(raw)) {
6824
+ return {
6825
+ error: `"${safeName}" has a text layer that is unreadable (its font encoding maps glyphs to the wrong characters, so words and especially numbers come out wrong or vanish). Do not use extracted text from this file. Use view_document_page to read the pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
6826
+ };
6827
+ }
6741
6828
  if (nonWhitespace < totalPages * MIN_CHARS_PER_PAGE) {
6742
6829
  return {
6743
6830
  error: `"${safeName}" has no extractable text layer (likely a scan or image-based PDF). Use view_document_page to look at pages visually, or suggest the user add the document to a knowledge base with a document processor for full OCR.`
@@ -6985,7 +7072,8 @@ var OFFICE_EXTENSIONS2 = /* @__PURE__ */ new Set([
6985
7072
  var createViewDocumentPageTool = ({
6986
7073
  sessionID,
6987
7074
  user,
6988
- exuluConfig
7075
+ exuluConfig,
7076
+ ownerId
6989
7077
  }) => {
6990
7078
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
6991
7079
  const viewDocumentPageExecute = async ({ filename, page, model }, options) => {
@@ -7018,8 +7106,7 @@ var createViewDocumentPageTool = ({
7018
7106
  }
7019
7107
  }
7020
7108
  const uploads = exuluConfig.fileUploads;
7021
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
7022
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
7109
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
7023
7110
  const pageNumber = page ?? 1;
7024
7111
  try {
7025
7112
  let imageBytes;
@@ -7704,7 +7791,7 @@ var hydrateVariables = async (tool3) => {
7704
7791
  await Promise.all(promises);
7705
7792
  return tool3;
7706
7793
  };
7707
- var convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools) => {
7794
+ var convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools, sessionOwnerId) => {
7708
7795
  if (!currentTools) return {};
7709
7796
  if (!allExuluTools) {
7710
7797
  allExuluTools = [];
@@ -7721,7 +7808,7 @@ var convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approved
7721
7808
  sessionID,
7722
7809
  currentSkills || [],
7723
7810
  exuluConfig,
7724
- user?.id
7811
+ sessionOwnerId ?? user?.id
7725
7812
  );
7726
7813
  } catch (err) {
7727
7814
  console.error(
@@ -7786,15 +7873,15 @@ var convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approved
7786
7873
  currentTools.push(sessionItemsRetrievalTool);
7787
7874
  }
7788
7875
  }
7789
- const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig });
7876
+ const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
7790
7877
  if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
7791
7878
  currentTools.push(sessionFileReadTool);
7792
7879
  }
7793
- const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig });
7880
+ const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
7794
7881
  if (parseDocumentTool && !disabled.has(parseDocumentTool.id)) {
7795
7882
  currentTools.push(parseDocumentTool);
7796
7883
  }
7797
- const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig });
7884
+ const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
7798
7885
  if (viewDocumentPageTool && !disabled.has(viewDocumentPageTool.id)) {
7799
7886
  currentTools.push(viewDocumentPageTool);
7800
7887
  }
@@ -2,7 +2,7 @@
2
2
  import "dotenv/config";
3
3
  import {
4
4
  getPackageRoot
5
- } from "../chunk-T6JVFT7L.js";
5
+ } from "../chunk-27K2CO47.js";
6
6
 
7
7
  // src/cli/start-whisper.ts
8
8
  import "dotenv/config";
@@ -2,7 +2,7 @@ import "dotenv/config";
2
2
  import {
3
3
  convertExuluToolsToAiSdkTools,
4
4
  hydrateVariables
5
- } from "./chunk-BNTL6LYY.js";
5
+ } from "./chunk-AWMU6QXB.js";
6
6
  import "./chunk-4PDWNVNT.js";
7
7
  export {
8
8
  convertExuluToolsToAiSdkTools,