@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.
package/dist/index.cjs CHANGED
@@ -3727,6 +3727,40 @@ Current item: ${JSON.stringify(summary)}`
3727
3727
  }
3728
3728
  });
3729
3729
 
3730
+ // ee/invoke-skills/artifact-filter.ts
3731
+ function isIgnoredArtifactPath(relativePath) {
3732
+ return relativePath.split(/[\\/]+/).some((segment) => IGNORED_SEGMENTS.has(segment));
3733
+ }
3734
+ function capArtifacts(artifacts, max = DEFAULT_ARTIFACT_CAP) {
3735
+ if (artifacts.length <= max) return { kept: artifacts, omitted: 0 };
3736
+ return { kept: artifacts.slice(0, max), omitted: artifacts.length - max };
3737
+ }
3738
+ function needsDownload(localSize, remoteSize) {
3739
+ return localSize === void 0 || localSize !== remoteSize;
3740
+ }
3741
+ var IGNORED_SEGMENTS, DEFAULT_ARTIFACT_CAP;
3742
+ var init_artifact_filter = __esm({
3743
+ "ee/invoke-skills/artifact-filter.ts"() {
3744
+ "use strict";
3745
+ init_cjs_shims();
3746
+ IGNORED_SEGMENTS = /* @__PURE__ */ new Set([
3747
+ "venv",
3748
+ ".venv",
3749
+ "env",
3750
+ "node_modules",
3751
+ "__pycache__",
3752
+ "site-packages",
3753
+ "dist-packages",
3754
+ ".cache",
3755
+ ".git",
3756
+ ".pytest_cache",
3757
+ ".mypy_cache",
3758
+ ".ipynb_checkpoints"
3759
+ ]);
3760
+ DEFAULT_ARTIFACT_CAP = 50;
3761
+ }
3762
+ });
3763
+
3730
3764
  // src/exulu/system-dependencies.ts
3731
3765
  async function getNpmGlobalRoot() {
3732
3766
  if (cachedNpmGlobalRoot !== void 0) return cachedNpmGlobalRoot;
@@ -3856,6 +3890,229 @@ var init_variable = __esm({
3856
3890
  }
3857
3891
  });
3858
3892
 
3893
+ // src/utils/python-setup.ts
3894
+ var python_setup_exports = {};
3895
+ __export(python_setup_exports, {
3896
+ getPackageRoot: () => getPackageRoot,
3897
+ getPythonSetupInstructions: () => getPythonSetupInstructions,
3898
+ getPythonVenvPath: () => getPythonVenvPath,
3899
+ isPythonEnvironmentSetup: () => isPythonEnvironmentSetup,
3900
+ setupPythonEnvironment: () => setupPythonEnvironment,
3901
+ validatePythonEnvironment: () => validatePythonEnvironment
3902
+ });
3903
+ function getPackageRoot() {
3904
+ const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
3905
+ let currentDir = (0, import_path.dirname)(currentFile);
3906
+ let attempts = 0;
3907
+ const maxAttempts = 10;
3908
+ while (attempts < maxAttempts) {
3909
+ const packageJsonPath = (0, import_path.join)(currentDir, "package.json");
3910
+ if ((0, import_fs.existsSync)(packageJsonPath)) {
3911
+ try {
3912
+ const packageJson = JSON.parse((0, import_fs.readFileSync)(packageJsonPath, "utf-8"));
3913
+ if (packageJson.name === "@exulu/backend") {
3914
+ return currentDir;
3915
+ }
3916
+ } catch {
3917
+ }
3918
+ }
3919
+ const parentDir = (0, import_path.resolve)(currentDir, "..");
3920
+ if (parentDir === currentDir) {
3921
+ break;
3922
+ }
3923
+ currentDir = parentDir;
3924
+ attempts++;
3925
+ }
3926
+ const fallback = (0, import_path.resolve)((0, import_path.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
3927
+ return fallback;
3928
+ }
3929
+ function getSetupScriptPath(packageRoot) {
3930
+ return (0, import_path.resolve)(packageRoot, "ee/python/setup.sh");
3931
+ }
3932
+ function getVenvPath(packageRoot) {
3933
+ return (0, import_path.resolve)(packageRoot, "ee/python/.venv");
3934
+ }
3935
+ function getPythonVenvPath(packageRoot) {
3936
+ const root = packageRoot ?? getPackageRoot();
3937
+ return isPythonEnvironmentSetup(root) ? getVenvPath(root) : void 0;
3938
+ }
3939
+ function isPythonEnvironmentSetup(packageRoot) {
3940
+ const root = packageRoot ?? getPackageRoot();
3941
+ const venvPath = getVenvPath(root);
3942
+ const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
3943
+ return (0, import_fs.existsSync)(venvPath) && (0, import_fs.existsSync)(pythonPath);
3944
+ }
3945
+ async function setupPythonEnvironment(options = {}) {
3946
+ const {
3947
+ packageRoot = getPackageRoot(),
3948
+ force = false,
3949
+ verbose = false,
3950
+ timeout = 6e5
3951
+ // 10 minutes
3952
+ } = options;
3953
+ if (!force && isPythonEnvironmentSetup(packageRoot)) {
3954
+ if (verbose) {
3955
+ console.log("\u2713 Python environment already set up");
3956
+ }
3957
+ return {
3958
+ success: true,
3959
+ message: "Python environment already exists",
3960
+ alreadyExists: true
3961
+ };
3962
+ }
3963
+ const setupScriptPath = getSetupScriptPath(packageRoot);
3964
+ if (!(0, import_fs.existsSync)(setupScriptPath)) {
3965
+ return {
3966
+ success: false,
3967
+ message: `Setup script not found at: ${setupScriptPath}`,
3968
+ alreadyExists: false
3969
+ };
3970
+ }
3971
+ try {
3972
+ if (verbose) {
3973
+ console.log("Setting up Python environment...");
3974
+ }
3975
+ const { stdout, stderr } = await execAsync2(`bash "${setupScriptPath}"`, {
3976
+ cwd: packageRoot,
3977
+ timeout,
3978
+ env: {
3979
+ ...process.env,
3980
+ // Ensure script can write to the directory
3981
+ PYTHONDONTWRITEBYTECODE: "1"
3982
+ },
3983
+ maxBuffer: 10 * 1024 * 1024
3984
+ // 10MB buffer
3985
+ });
3986
+ const output = stdout + stderr;
3987
+ const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
3988
+ const pythonVersion = versionMatch ? versionMatch[1] : void 0;
3989
+ if (verbose) {
3990
+ console.log(output);
3991
+ }
3992
+ return {
3993
+ success: true,
3994
+ message: "Python environment set up successfully",
3995
+ alreadyExists: false,
3996
+ pythonVersion,
3997
+ output
3998
+ };
3999
+ } catch (error) {
4000
+ const errorOutput = error.stdout + error.stderr;
4001
+ return {
4002
+ success: false,
4003
+ message: `Setup failed: ${error.message}`,
4004
+ alreadyExists: false,
4005
+ output: errorOutput
4006
+ };
4007
+ }
4008
+ }
4009
+ function getPythonSetupInstructions() {
4010
+ return `
4011
+ Python environment not set up. Please run one of the following commands:
4012
+
4013
+ Option 1 (Automatic):
4014
+ import { setupPythonEnvironment } from '@exulu/backend';
4015
+ await setupPythonEnvironment();
4016
+
4017
+ Option 2 (Manual - for package consumers):
4018
+ npx @exulu/backend setup-python
4019
+
4020
+ Option 3 (Manual - for contributors):
4021
+ npm run python:setup
4022
+
4023
+ These commands will automatically create a Python virtual environment (.venv)
4024
+ in the @exulu/backend package and install all required dependencies.
4025
+
4026
+ Requirements:
4027
+ - Python 3.10 or higher must be installed
4028
+ - pip must be available
4029
+ - venv module must be available (for creating virtual environments)
4030
+
4031
+ If Python dependencies are not installed, install them first, then run one of the commands above:
4032
+ - macOS: brew install python@3.12
4033
+ - Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
4034
+ - Alpine Linux: apk add python3 py3-pip python3-dev
4035
+ - Windows: Download from https://www.python.org/downloads/
4036
+
4037
+ Note: In Docker containers, ensure you install all three components:
4038
+ Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
4039
+ Alpine: apk add python3 py3-pip python3-dev
4040
+ `.trim();
4041
+ }
4042
+ async function validatePythonEnvironment(packageRoot, checkPackages = true) {
4043
+ const root = packageRoot ?? getPackageRoot();
4044
+ const venvPath = getVenvPath(root);
4045
+ const pythonPath = (0, import_path.join)(venvPath, "bin", "python");
4046
+ if (!(0, import_fs.existsSync)(venvPath)) {
4047
+ return {
4048
+ valid: false,
4049
+ message: getPythonSetupInstructions()
4050
+ };
4051
+ }
4052
+ if (!(0, import_fs.existsSync)(pythonPath)) {
4053
+ return {
4054
+ valid: false,
4055
+ message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
4056
+ };
4057
+ }
4058
+ try {
4059
+ await execAsync2(`"${pythonPath}" --version`, { cwd: root });
4060
+ } catch {
4061
+ return {
4062
+ valid: false,
4063
+ message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
4064
+ };
4065
+ }
4066
+ if (checkPackages) {
4067
+ const criticalPackages = ["docling", "transformers"];
4068
+ const missingPackages = [];
4069
+ for (const pkg of criticalPackages) {
4070
+ try {
4071
+ await execAsync2(`"${pythonPath}" -c "import ${pkg}"`, {
4072
+ cwd: root,
4073
+ timeout: 1e4
4074
+ // 10 second timeout per import check
4075
+ });
4076
+ } catch {
4077
+ missingPackages.push(pkg);
4078
+ }
4079
+ }
4080
+ if (missingPackages.length > 0) {
4081
+ return {
4082
+ valid: false,
4083
+ message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
4084
+
4085
+ This usually happens when:
4086
+ 1. The .venv folder was copied but dependencies were not installed
4087
+ 2. The package was installed via npm but setup script was not run
4088
+
4089
+ Please run:
4090
+ await setupPythonEnvironment({ force: true })
4091
+
4092
+ Or manually run the setup script:
4093
+ bash ` + getSetupScriptPath(root)
4094
+ };
4095
+ }
4096
+ }
4097
+ return {
4098
+ valid: true,
4099
+ message: "Python environment is valid"
4100
+ };
4101
+ }
4102
+ var import_child_process, import_util, import_path, import_fs, import_url, execAsync2;
4103
+ var init_python_setup = __esm({
4104
+ "src/utils/python-setup.ts"() {
4105
+ "use strict";
4106
+ init_cjs_shims();
4107
+ import_child_process = require("child_process");
4108
+ import_util = require("util");
4109
+ import_path = require("path");
4110
+ import_fs = require("fs");
4111
+ import_url = require("url");
4112
+ execAsync2 = (0, import_util.promisify)(import_child_process.exec);
4113
+ }
4114
+ });
4115
+
3859
4116
  // ee/invoke-skills/create-sandbox.ts
3860
4117
  function probeSandboxSupport() {
3861
4118
  if (sandboxProbePromise) return sandboxProbePromise;
@@ -3941,7 +4198,7 @@ function resolveSessionPath(inputPath, sessionDir) {
3941
4198
  }
3942
4199
  return resolved;
3943
4200
  }
3944
- async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
4201
+ async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config, opts = {}) {
3945
4202
  const userPrefix = `user_${userId}/sessions/${sessionId}/`;
3946
4203
  let objects;
3947
4204
  try {
@@ -3961,7 +4218,17 @@ async function restoreArtifactsFromS3(sessionDir, sessionId, userId, config) {
3961
4218
  const idx = obj.key.indexOf(userPrefix);
3962
4219
  const relativePath = idx >= 0 ? obj.key.slice(idx + userPrefix.length) : "";
3963
4220
  if (!relativePath) continue;
4221
+ if (isIgnoredArtifactPath(relativePath)) continue;
3964
4222
  const localPath = (0, import_node_path4.join)(sessionDir, relativePath);
4223
+ if (opts.onlyMissing) {
4224
+ let localSize;
4225
+ try {
4226
+ localSize = (await (0, import_promises.stat)(localPath)).size;
4227
+ } catch {
4228
+ localSize = void 0;
4229
+ }
4230
+ if (!needsDownload(localSize, obj.size)) continue;
4231
+ }
3965
4232
  try {
3966
4233
  const bytes = await getS3ObjectBytes(obj.key, config);
3967
4234
  await (0, import_promises.mkdir)((0, import_node_path4.dirname)(localPath), { recursive: true });
@@ -3995,6 +4262,15 @@ async function downloadKeyIntoSandbox(opts) {
3995
4262
  await (0, import_promises.writeFile)(localPath, bytes);
3996
4263
  return { written: true, localPath };
3997
4264
  }
4265
+ async function resolvePythonVenvPath() {
4266
+ try {
4267
+ const { getPythonVenvPath: getPythonVenvPath2 } = await Promise.resolve().then(() => (init_python_setup(), python_setup_exports));
4268
+ return getPythonVenvPath2();
4269
+ } catch (err) {
4270
+ console.warn("[SKILLS] Could not resolve the Python venv for the session sandbox; skill scripts fall back to the system python.", err);
4271
+ return void 0;
4272
+ }
4273
+ }
3998
4274
  async function createSessionSandbox(sessionId, skills, config, userId) {
3999
4275
  const cached = sandboxCache.get(sessionId);
4000
4276
  if (cached) {
@@ -4008,6 +4284,13 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
4008
4284
  await downloadSkill(skill, skillsDirectory2, config);
4009
4285
  cached.installedSkills.set(skill.id, skill.current_version);
4010
4286
  }
4287
+ if (userId && config.fileUploads) {
4288
+ try {
4289
+ await restoreArtifactsFromS3(cached.handle.sessionDir, sessionId, userId, config, { onlyMissing: true });
4290
+ } catch (err) {
4291
+ console.error(`[SKILLS] Failed to re-sync S3 session files for session ${sessionId}; continuing.`, err);
4292
+ }
4293
+ }
4011
4294
  return cached.handle;
4012
4295
  }
4013
4296
  const sessionDir = (0, import_node_path4.join)("/tmp", "exulu-sessions", sessionId);
@@ -4025,8 +4308,8 @@ async function createSessionSandbox(sessionId, skills, config, userId) {
4025
4308
  `[SKILLS] S3 artifact persistence disabled for session ${sessionId} (userId=${userId ?? "missing"}, fileUploads=${config.fileUploads ? "configured" : "missing"})`
4026
4309
  );
4027
4310
  }
4028
- if (userId && config.fileUploads && !dirExisted) {
4029
- await restoreArtifactsFromS3(sessionDir, sessionId, userId, config);
4311
+ if (userId && config.fileUploads) {
4312
+ await restoreArtifactsFromS3(sessionDir, sessionId, userId, config, { onlyMissing: dirExisted });
4030
4313
  }
4031
4314
  const probe = await probeSandboxSupport();
4032
4315
  const useDirectExec = !probe.canSandbox;
@@ -4061,6 +4344,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4061
4344
  await import_sandbox_runtime.SandboxManager.initialize(baselineSandboxConfig);
4062
4345
  }
4063
4346
  const npmGlobalRoot = await getNpmGlobalRoot();
4347
+ const pythonVenvPath = await resolvePythonVenvPath();
4064
4348
  const sessionSandboxConfig = {
4065
4349
  network: {
4066
4350
  allowedDomains: [],
@@ -4075,7 +4359,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4075
4359
  // Allow Node to read globally-installed packages from inside
4076
4360
  // the sandbox. Without this, `require('docx')` fails with
4077
4361
  // EPERM even when NODE_PATH points the resolver here.
4078
- ...npmGlobalRoot ? [npmGlobalRoot] : []
4362
+ ...npmGlobalRoot ? [npmGlobalRoot] : [],
4363
+ ...pythonVenvPath ? [pythonVenvPath] : []
4079
4364
  ],
4080
4365
  allowWrite: [sessionDir],
4081
4366
  denyWrite: []
@@ -4093,7 +4378,8 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4093
4378
  const sandboxedExecEnv = {
4094
4379
  ...configuredVariables,
4095
4380
  ...process.env,
4096
- ...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {}
4381
+ ...npmGlobalRoot ? { NODE_PATH: npmGlobalRoot } : {},
4382
+ ...pythonVenvPath ? { PATH: `${(0, import_node_path4.join)(pythonVenvPath, "bin")}:${process.env.PATH ?? ""}`, VIRTUAL_ENV: pythonVenvPath } : {}
4097
4383
  };
4098
4384
  const wrapIfNeeded = async (command) => {
4099
4385
  if (useDirectExec) return command;
@@ -4102,7 +4388,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4102
4388
  const runWrapped = async (command) => {
4103
4389
  const wrapped = await wrapIfNeeded(command);
4104
4390
  try {
4105
- const { stdout, stderr } = await execAsync2(wrapped, {
4391
+ const { stdout, stderr } = await execAsync3(wrapped, {
4106
4392
  maxBuffer: EXEC_MAX_BUFFER,
4107
4393
  shell: "/bin/bash",
4108
4394
  env: sandboxedExecEnv
@@ -4218,6 +4504,7 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4218
4504
  for (const entry of entries) {
4219
4505
  const full = (0, import_node_path4.join)(dir, entry.name);
4220
4506
  if (full === skillsDir) continue;
4507
+ if (isIgnoredArtifactPath((0, import_node_path4.relative)(sessionDir, full))) continue;
4221
4508
  if (entry.isDirectory()) {
4222
4509
  await walk(full);
4223
4510
  } else if (entry.isFile()) {
@@ -4313,19 +4600,24 @@ Probe error: ${probe.reason ?? "(no detail)"}`
4313
4600
  }
4314
4601
  }
4315
4602
  let stdout = result?.stdout ?? "";
4316
- const withUrls = artifacts.filter((a) => a.url);
4603
+ const { kept, omitted } = capArtifacts(artifacts);
4604
+ const withUrls = kept.filter((a) => a.url);
4317
4605
  if (withUrls.length > 0) {
4318
4606
  const lines = ["", "[exulu-artifacts]"];
4319
4607
  for (const a of withUrls) {
4320
4608
  lines.push(` ${a.relativePath}: ${a.url}`);
4321
4609
  }
4610
+ if (omitted > 0) {
4611
+ lines.push(` \u2026 ${omitted} more file(s) were created and mirrored but are not listed here.`);
4612
+ }
4322
4613
  stdout = `${stdout}
4323
4614
  ${lines.join("\n")}`;
4324
4615
  }
4325
4616
  return {
4326
4617
  ...result,
4327
4618
  stdout,
4328
- artifacts
4619
+ artifacts: kept,
4620
+ ...omitted > 0 ? { artifactsOmitted: omitted } : {}
4329
4621
  };
4330
4622
  }
4331
4623
  });
@@ -4345,7 +4637,7 @@ ${lines.join("\n")}`;
4345
4637
  sandboxCache.set(sessionId, { handle, installedSkills });
4346
4638
  return handle;
4347
4639
  }
4348
- var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2, import_zod6, import_crypto_js3, getAllExuluVariables, execAsync2, EXEC_MAX_BUFFER, sandboxProbePromise, SANDBOX_FALLBACK_INSTRUCTIONS, degradedModeLogged, sandboxCache;
4640
+ var import_sandbox_runtime, import_promises, import_node_fs5, import_node_path4, import_node_child_process3, import_node_util2, import_bash_tool, import_ai2, import_zod6, import_crypto_js3, getAllExuluVariables, execAsync3, EXEC_MAX_BUFFER, sandboxProbePromise, SANDBOX_FALLBACK_INSTRUCTIONS, degradedModeLogged, sandboxCache;
4349
4641
  var init_create_sandbox = __esm({
4350
4642
  "ee/invoke-skills/create-sandbox.ts"() {
4351
4643
  "use strict";
@@ -4357,6 +4649,7 @@ var init_create_sandbox = __esm({
4357
4649
  import_node_child_process3 = require("child_process");
4358
4650
  import_node_util2 = require("util");
4359
4651
  init_uppy();
4652
+ init_artifact_filter();
4360
4653
  init_system_dependencies();
4361
4654
  import_bash_tool = require("bash-tool");
4362
4655
  import_ai2 = require("ai");
@@ -4390,7 +4683,7 @@ var init_create_sandbox = __esm({
4390
4683
  }
4391
4684
  return out;
4392
4685
  };
4393
- execAsync2 = (0, import_node_util2.promisify)(import_node_child_process3.exec);
4686
+ execAsync3 = (0, import_node_util2.promisify)(import_node_child_process3.exec);
4394
4687
  EXEC_MAX_BUFFER = 32 * 1024 * 1024;
4395
4688
  SANDBOX_FALLBACK_INSTRUCTIONS = 'Skill sandboxing is running in DEGRADED mode: bwrap cannot create user namespaces on this host, so commands\nexecute directly. The container remains the isolation boundary and resolveSessionPath still scopes\nreadFile/writeFile to the session directory at the JS layer, but bash commands are NOT kernel-sandboxed.\n\nTo restore full sandboxing on Ubuntu 23.10+ / 24.04+ hosts (kernel 6.5+):\n sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0\n echo "kernel.apparmor_restrict_unprivileged_userns=0" | sudo tee /etc/sysctl.d/60-userns.conf\n sudo sysctl --system\n\nOn Debian hosts where the same symptom appears:\n sudo sysctl -w kernel.unprivileged_userns_clone=1\n\nSet EXULU_REQUIRE_SANDBOX=1 to fail startup instead of degrading.';
4396
4689
  degradedModeLogged = false;
@@ -4629,6 +4922,18 @@ var init_auth_tool_model_output = __esm({
4629
4922
  }
4630
4923
  });
4631
4924
 
4925
+ // src/exulu/session-files.ts
4926
+ function sessionFilePrefix(ownerId, sessionId, s3prefix) {
4927
+ const general = s3prefix ? `${s3prefix.replace(/\/+$/, "")}/` : "";
4928
+ return `${general}user_${ownerId}/sessions/${sessionId}/`;
4929
+ }
4930
+ var init_session_files = __esm({
4931
+ "src/exulu/session-files.ts"() {
4932
+ "use strict";
4933
+ init_cjs_shims();
4934
+ }
4935
+ });
4936
+
4632
4937
  // src/templates/tools/session-file-read-tool.ts
4633
4938
  var import_zod7, DEFAULT_LIMIT, MAX_CONTENT_CHARS, createSessionFileReadTool;
4634
4939
  var init_session_file_read_tool = __esm({
@@ -4636,6 +4941,7 @@ var init_session_file_read_tool = __esm({
4636
4941
  "use strict";
4637
4942
  init_cjs_shims();
4638
4943
  import_zod7 = require("zod");
4944
+ init_session_files();
4639
4945
  init_tool();
4640
4946
  init_uppy();
4641
4947
  DEFAULT_LIMIT = 250;
@@ -4643,7 +4949,8 @@ var init_session_file_read_tool = __esm({
4643
4949
  createSessionFileReadTool = ({
4644
4950
  sessionID,
4645
4951
  user,
4646
- exuluConfig
4952
+ exuluConfig,
4953
+ ownerId
4647
4954
  }) => {
4648
4955
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
4649
4956
  const readSessionFileExecute = async ({ filename, offset, limit }) => {
@@ -4654,8 +4961,7 @@ var init_session_file_read_tool = __esm({
4654
4961
  };
4655
4962
  }
4656
4963
  const uploads = exuluConfig.fileUploads;
4657
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
4658
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
4964
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
4659
4965
  try {
4660
4966
  const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
4661
4967
  const res = await fetch(url);
@@ -4759,6 +5065,18 @@ var init_document_render_helpers = __esm({
4759
5065
  });
4760
5066
 
4761
5067
  // src/templates/tools/parse-document-tool.ts
5068
+ function looksLikeGarbledTextLayer(text) {
5069
+ let control = 0;
5070
+ let visible = 0;
5071
+ for (const ch of text) {
5072
+ const code = ch.charCodeAt(0);
5073
+ if (code === 9 || code === 10 || code === 12 || code === 13 || code === 32) continue;
5074
+ visible++;
5075
+ if (code < 32 || code === 127) control++;
5076
+ }
5077
+ if (visible < 40) return false;
5078
+ return control / visible > 0.01;
5079
+ }
4762
5080
  var import_zod8, import_node_path6, import_officeparser, DEFAULT_LIMIT2, MAX_CONTENT_CHARS2, MIN_CHARS_PER_PAGE, OFFICE_EXTENSIONS, pagesPattern, createParseDocumentTool;
4763
5081
  var init_parse_document_tool = __esm({
4764
5082
  "src/templates/tools/parse-document-tool.ts"() {
@@ -4770,6 +5088,7 @@ var init_parse_document_tool = __esm({
4770
5088
  init_tool();
4771
5089
  init_uppy();
4772
5090
  init_document_render_helpers();
5091
+ init_session_files();
4773
5092
  DEFAULT_LIMIT2 = 250;
4774
5093
  MAX_CONTENT_CHARS2 = 16e3;
4775
5094
  MIN_CHARS_PER_PAGE = 20;
@@ -4789,7 +5108,8 @@ var init_parse_document_tool = __esm({
4789
5108
  createParseDocumentTool = ({
4790
5109
  sessionID,
4791
5110
  user,
4792
- exuluConfig
5111
+ exuluConfig,
5112
+ ownerId
4793
5113
  }) => {
4794
5114
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
4795
5115
  const parseDocumentExecute = async ({
@@ -4814,8 +5134,7 @@ var init_parse_document_tool = __esm({
4814
5134
  return { error: `The pages option is only supported for PDF files \u2014 "${ext}" documents are extracted whole.` };
4815
5135
  }
4816
5136
  const uploads = exuluConfig.fileUploads;
4817
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
4818
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
5137
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
4819
5138
  try {
4820
5139
  const url = await getPresignedUrl(uploads.s3Bucket, key, exuluConfig);
4821
5140
  const res = await fetch(url);
@@ -4830,6 +5149,11 @@ var init_parse_document_tool = __esm({
4830
5149
  const pageTexts = raw.replace(/\f$/, "").split("\f");
4831
5150
  totalPages = pageTexts.length;
4832
5151
  const nonWhitespace = raw.replace(/\s/g, "").length;
5152
+ if (looksLikeGarbledTextLayer(raw)) {
5153
+ return {
5154
+ 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.`
5155
+ };
5156
+ }
4833
5157
  if (nonWhitespace < totalPages * MIN_CHARS_PER_PAGE) {
4834
5158
  return {
4835
5159
  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.`
@@ -4928,7 +5252,7 @@ async function getPdfPreviewBytes(opts) {
4928
5252
  const bytes = await getS3ObjectBytes(sourceKey, config);
4929
5253
  await (0, import_promises3.writeFile)(inputPath, bytes);
4930
5254
  try {
4931
- await execAsync3(
5255
+ await execAsync4(
4932
5256
  `soffice --headless --convert-to pdf "${inputPath}" --outdir "${CACHE_OUT}"`,
4933
5257
  { timeout: 6e4, maxBuffer: 16 * 1024 * 1024 }
4934
5258
  );
@@ -4954,7 +5278,7 @@ async function getPdfPreviewBytes(opts) {
4954
5278
  inFlight.set(safeEtag, promise);
4955
5279
  return promise;
4956
5280
  }
4957
- var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4, execAsync3, CACHE_ROOT, CACHE_IN, CACHE_OUT, inFlight, PreviewRenderError;
5281
+ var import_node_child_process5, import_node_fs6, import_promises3, import_node_path7, import_node_util4, execAsync4, CACHE_ROOT, CACHE_IN, CACHE_OUT, inFlight, PreviewRenderError;
4958
5282
  var init_pdf_preview_cache = __esm({
4959
5283
  "src/sessions/pdf-preview-cache.ts"() {
4960
5284
  "use strict";
@@ -4965,7 +5289,7 @@ var init_pdf_preview_cache = __esm({
4965
5289
  import_node_path7 = require("path");
4966
5290
  import_node_util4 = require("util");
4967
5291
  init_uppy();
4968
- execAsync3 = (0, import_node_util4.promisify)(import_node_child_process5.exec);
5292
+ execAsync4 = (0, import_node_util4.promisify)(import_node_child_process5.exec);
4969
5293
  CACHE_ROOT = "/tmp/exulu-pdf-cache";
4970
5294
  CACHE_IN = (0, import_node_path7.join)(CACHE_ROOT, "_in");
4971
5295
  CACHE_OUT = (0, import_node_path7.join)(CACHE_ROOT, "_out");
@@ -5165,6 +5489,7 @@ var init_view_document_page_tool = __esm({
5165
5489
  "use strict";
5166
5490
  init_cjs_shims();
5167
5491
  import_zod9 = require("zod");
5492
+ init_session_files();
5168
5493
  import_node_path8 = require("path");
5169
5494
  init_tool();
5170
5495
  init_uppy();
@@ -5197,7 +5522,8 @@ var init_view_document_page_tool = __esm({
5197
5522
  createViewDocumentPageTool = ({
5198
5523
  sessionID,
5199
5524
  user,
5200
- exuluConfig
5525
+ exuluConfig,
5526
+ ownerId
5201
5527
  }) => {
5202
5528
  if (!sessionID || !exuluConfig?.fileUploads?.s3Bucket) return void 0;
5203
5529
  const viewDocumentPageExecute = async ({ filename, page, model }, options) => {
@@ -5230,8 +5556,7 @@ var init_view_document_page_tool = __esm({
5230
5556
  }
5231
5557
  }
5232
5558
  const uploads = exuluConfig.fileUploads;
5233
- const generalPrefix = uploads.s3prefix ? `${uploads.s3prefix.replace(/\/$/, "")}/` : "";
5234
- const key = `${generalPrefix}user_${user?.id ?? "api"}/sessions/${sessionID}/${safeName}`;
5559
+ const key = `${sessionFilePrefix(ownerId ?? user?.id ?? "api", sessionID, uploads.s3prefix)}${safeName}`;
5235
5560
  const pageNumber = page ?? 1;
5236
5561
  try {
5237
5562
  let imageBytes;
@@ -5305,13 +5630,13 @@ var init_view_document_page_tool = __esm({
5305
5630
  });
5306
5631
 
5307
5632
  // src/exulu/audit/config.ts
5308
- var import_os, import_path, normalizePrefix, hasAllS3Fields, resolveAuditConfig;
5633
+ var import_os, import_path2, normalizePrefix, hasAllS3Fields, resolveAuditConfig;
5309
5634
  var init_config = __esm({
5310
5635
  "src/exulu/audit/config.ts"() {
5311
5636
  "use strict";
5312
5637
  init_cjs_shims();
5313
5638
  import_os = __toESM(require("os"), 1);
5314
- import_path = __toESM(require("path"), 1);
5639
+ import_path2 = __toESM(require("path"), 1);
5315
5640
  normalizePrefix = (p) => {
5316
5641
  const raw = (p ?? "audit").trim().replace(/^\/+|\/+$/g, "");
5317
5642
  return `${raw || "audit"}/`;
@@ -5343,7 +5668,7 @@ var init_config = __esm({
5343
5668
  retentionDays: a.retentionDays,
5344
5669
  manageLifecycle: a.manageLifecycle ?? !usingSharedFileUploadsBucket,
5345
5670
  usingSharedFileUploadsBucket,
5346
- spoolDir: a.spoolDir ?? import_path.default.join(import_os.default.tmpdir(), "exulu-audit-spool"),
5671
+ spoolDir: a.spoolDir ?? import_path2.default.join(import_os.default.tmpdir(), "exulu-audit-spool"),
5347
5672
  flush: {
5348
5673
  maxRecords: a.flush?.maxRecords ?? 100,
5349
5674
  maxIntervalMs: a.flush?.maxIntervalMs ?? 5e3
@@ -5464,29 +5789,29 @@ ${JSON.stringify(config, null, 2)}`
5464
5789
  });
5465
5790
 
5466
5791
  // src/exulu/audit/sink.ts
5467
- var import_crypto2, import_fs, import_path2, createFsSpoolStore, pad, AuditSink;
5792
+ var import_crypto2, import_fs2, import_path3, createFsSpoolStore, pad, AuditSink;
5468
5793
  var init_sink = __esm({
5469
5794
  "src/exulu/audit/sink.ts"() {
5470
5795
  "use strict";
5471
5796
  init_cjs_shims();
5472
5797
  import_crypto2 = require("crypto");
5473
- import_fs = require("fs");
5474
- import_path2 = __toESM(require("path"), 1);
5798
+ import_fs2 = require("fs");
5799
+ import_path3 = __toESM(require("path"), 1);
5475
5800
  createFsSpoolStore = (dir) => ({
5476
5801
  write: async (name, body) => {
5477
- await import_fs.promises.mkdir(dir, { recursive: true });
5478
- await import_fs.promises.writeFile(import_path2.default.join(dir, name), body, "utf8");
5802
+ await import_fs2.promises.mkdir(dir, { recursive: true });
5803
+ await import_fs2.promises.writeFile(import_path3.default.join(dir, name), body, "utf8");
5479
5804
  },
5480
5805
  list: async () => {
5481
5806
  try {
5482
- return (await import_fs.promises.readdir(dir)).filter((f) => f.endsWith(".ndjson"));
5807
+ return (await import_fs2.promises.readdir(dir)).filter((f) => f.endsWith(".ndjson"));
5483
5808
  } catch {
5484
5809
  return [];
5485
5810
  }
5486
5811
  },
5487
- read: async (name) => import_fs.promises.readFile(import_path2.default.join(dir, name), "utf8"),
5812
+ read: async (name) => import_fs2.promises.readFile(import_path3.default.join(dir, name), "utf8"),
5488
5813
  remove: async (name) => {
5489
- await import_fs.promises.rm(import_path2.default.join(dir, name), { force: true });
5814
+ await import_fs2.promises.rm(import_path3.default.join(dir, name), { force: true });
5490
5815
  }
5491
5816
  });
5492
5817
  pad = (n) => String(n).padStart(2, "0");
@@ -6032,7 +6357,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
6032
6357
  await Promise.all(promises2);
6033
6358
  return tool4;
6034
6359
  };
6035
- convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools) => {
6360
+ convertExuluToolsToAiSdkTools = async (currentTools, currentSkills, approvedTools, allExuluTools, configs, contexts, user, exuluConfig, sessionID, req, project, sessionItems, model, agent, memoryItems, contextWindow, disabledTools, sessionOwnerId) => {
6036
6361
  if (!currentTools) return {};
6037
6362
  if (!allExuluTools) {
6038
6363
  allExuluTools = [];
@@ -6049,7 +6374,7 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
6049
6374
  sessionID,
6050
6375
  currentSkills || [],
6051
6376
  exuluConfig,
6052
- user?.id
6377
+ sessionOwnerId ?? user?.id
6053
6378
  );
6054
6379
  } catch (err) {
6055
6380
  console.error(
@@ -6114,15 +6439,15 @@ var init_convert_exulu_tools_to_ai_sdk_tools = __esm({
6114
6439
  currentTools.push(sessionItemsRetrievalTool);
6115
6440
  }
6116
6441
  }
6117
- const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig });
6442
+ const sessionFileReadTool = createSessionFileReadTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
6118
6443
  if (sessionFileReadTool && !disabled.has(sessionFileReadTool.id)) {
6119
6444
  currentTools.push(sessionFileReadTool);
6120
6445
  }
6121
- const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig });
6446
+ const parseDocumentTool = createParseDocumentTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
6122
6447
  if (parseDocumentTool && !disabled.has(parseDocumentTool.id)) {
6123
6448
  currentTools.push(parseDocumentTool);
6124
6449
  }
6125
- const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig });
6450
+ const viewDocumentPageTool = createViewDocumentPageTool({ sessionID, user, exuluConfig, ownerId: sessionOwnerId });
6126
6451
  if (viewDocumentPageTool && !disabled.has(viewDocumentPageTool.id)) {
6127
6452
  currentTools.push(viewDocumentPageTool);
6128
6453
  }
@@ -17292,10 +17617,12 @@ var generateSync = async ({
17292
17617
  }
17293
17618
  let project;
17294
17619
  let sessionItems;
17620
+ let sessionOwnerId;
17295
17621
  if (session) {
17296
17622
  const sessionData = await getSession({ sessionID: session });
17297
17623
  sessionItems = sessionData.session_items;
17298
17624
  project = sessionData.project;
17625
+ sessionOwnerId = sessionData.user ?? void 0;
17299
17626
  }
17300
17627
  const model = languageModel;
17301
17628
  console.log("[EXULU] Model created for generating sync.");
@@ -17399,7 +17726,8 @@ var generateSync = async ({
17399
17726
  agent,
17400
17727
  memoryItems,
17401
17728
  contextWindow,
17402
- disabledTools
17729
+ disabledTools,
17730
+ sessionOwnerId
17403
17731
  );
17404
17732
  const agenticEntry = currentTools?.find((t) => t.id === "agentic_context_search");
17405
17733
  const agenticToolKey = agenticEntry ? sanitizeToolName(agenticEntry.name) : void 0;
@@ -17634,10 +17962,12 @@ var generateStream = async ({
17634
17962
  let previousMessagesContent = previousMessages || [];
17635
17963
  let project;
17636
17964
  let sessionItems;
17965
+ let sessionOwnerId;
17637
17966
  if (session) {
17638
17967
  const sessionData = await getSession({ sessionID: session });
17639
17968
  project = sessionData.project;
17640
17969
  sessionItems = sessionData.session_items;
17970
+ sessionOwnerId = sessionData.user ?? void 0;
17641
17971
  console.log("[EXULU] loading previous messages from session: " + session);
17642
17972
  const previousMessages2 = await getAgentMessages({
17643
17973
  session,
@@ -17817,7 +18147,8 @@ When a tool execution is not approved by the user, do not retry it unless explic
17817
18147
  agent,
17818
18148
  memoryItems,
17819
18149
  contextWindow,
17820
- disabledTools
18150
+ disabledTools,
18151
+ sessionOwnerId
17821
18152
  );
17822
18153
  console.log("[EXULU] Converted tools", Object.keys(tools));
17823
18154
  const includesContextSearchTool = currentTools?.some(
@@ -19977,7 +20308,7 @@ var mapRoutineRunRow = (row, routineById) => {
19977
20308
 
19978
20309
  // src/graphql/schemas/index.ts
19979
20310
  init_entitlements();
19980
- var import_fs2 = require("fs");
20311
+ var import_fs3 = require("fs");
19981
20312
 
19982
20313
  // src/exulu/transcription/service.ts
19983
20314
  init_cjs_shims();
@@ -23983,7 +24314,7 @@ var import_utils5 = require("@apollo/utils.keyvaluecache");
23983
24314
  var import_body_parser = __toESM(require("body-parser"), 1);
23984
24315
  var import_crypto_js7 = require("crypto-js");
23985
24316
  var import_openai = require("openai");
23986
- var import_fs3 = __toESM(require("fs"), 1);
24317
+ var import_fs4 = __toESM(require("fs"), 1);
23987
24318
  var import_node_crypto15 = require("crypto");
23988
24319
  var import_api2 = require("@opentelemetry/api");
23989
24320
  var import_jszip3 = __toESM(require("jszip"), 1);
@@ -25320,7 +25651,7 @@ var REQUEST_SIZE_LIMIT = "50mb";
25320
25651
  var getExuluVersionNumber = async () => {
25321
25652
  try {
25322
25653
  const path4 = process.cwd();
25323
- const packageJson = import_fs3.default.readFileSync(path4 + "/package.json", "utf8");
25654
+ const packageJson = import_fs4.default.readFileSync(path4 + "/package.json", "utf8");
25324
25655
  const packageData = JSON.parse(packageJson);
25325
25656
  const exuluVersion = packageData.dependencies["@exulu/backend"];
25326
25657
  console.log(`[EXULU] Installed exulu-backend version: ${exuluVersion}`);
@@ -30301,210 +30632,7 @@ init_singleton();
30301
30632
  init_entitlements();
30302
30633
  init_system_dependencies();
30303
30634
  init_supervisor();
30304
-
30305
- // src/utils/python-setup.ts
30306
- init_cjs_shims();
30307
- var import_child_process = require("child_process");
30308
- var import_util = require("util");
30309
- var import_path3 = require("path");
30310
- var import_fs4 = require("fs");
30311
- var import_url = require("url");
30312
- var execAsync4 = (0, import_util.promisify)(import_child_process.exec);
30313
- function getPackageRoot() {
30314
- const currentFile = (0, import_url.fileURLToPath)(importMetaUrl);
30315
- let currentDir = (0, import_path3.dirname)(currentFile);
30316
- let attempts = 0;
30317
- const maxAttempts = 10;
30318
- while (attempts < maxAttempts) {
30319
- const packageJsonPath = (0, import_path3.join)(currentDir, "package.json");
30320
- if ((0, import_fs4.existsSync)(packageJsonPath)) {
30321
- try {
30322
- const packageJson = JSON.parse((0, import_fs4.readFileSync)(packageJsonPath, "utf-8"));
30323
- if (packageJson.name === "@exulu/backend") {
30324
- return currentDir;
30325
- }
30326
- } catch {
30327
- }
30328
- }
30329
- const parentDir = (0, import_path3.resolve)(currentDir, "..");
30330
- if (parentDir === currentDir) {
30331
- break;
30332
- }
30333
- currentDir = parentDir;
30334
- attempts++;
30335
- }
30336
- const fallback = (0, import_path3.resolve)((0, import_path3.dirname)((0, import_url.fileURLToPath)(importMetaUrl)), "../..");
30337
- return fallback;
30338
- }
30339
- function getSetupScriptPath(packageRoot) {
30340
- return (0, import_path3.resolve)(packageRoot, "ee/python/setup.sh");
30341
- }
30342
- function getVenvPath(packageRoot) {
30343
- return (0, import_path3.resolve)(packageRoot, "ee/python/.venv");
30344
- }
30345
- function isPythonEnvironmentSetup(packageRoot) {
30346
- const root = packageRoot ?? getPackageRoot();
30347
- const venvPath = getVenvPath(root);
30348
- const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
30349
- return (0, import_fs4.existsSync)(venvPath) && (0, import_fs4.existsSync)(pythonPath);
30350
- }
30351
- async function setupPythonEnvironment(options = {}) {
30352
- const {
30353
- packageRoot = getPackageRoot(),
30354
- force = false,
30355
- verbose = false,
30356
- timeout = 6e5
30357
- // 10 minutes
30358
- } = options;
30359
- if (!force && isPythonEnvironmentSetup(packageRoot)) {
30360
- if (verbose) {
30361
- console.log("\u2713 Python environment already set up");
30362
- }
30363
- return {
30364
- success: true,
30365
- message: "Python environment already exists",
30366
- alreadyExists: true
30367
- };
30368
- }
30369
- const setupScriptPath = getSetupScriptPath(packageRoot);
30370
- if (!(0, import_fs4.existsSync)(setupScriptPath)) {
30371
- return {
30372
- success: false,
30373
- message: `Setup script not found at: ${setupScriptPath}`,
30374
- alreadyExists: false
30375
- };
30376
- }
30377
- try {
30378
- if (verbose) {
30379
- console.log("Setting up Python environment...");
30380
- }
30381
- const { stdout, stderr } = await execAsync4(`bash "${setupScriptPath}"`, {
30382
- cwd: packageRoot,
30383
- timeout,
30384
- env: {
30385
- ...process.env,
30386
- // Ensure script can write to the directory
30387
- PYTHONDONTWRITEBYTECODE: "1"
30388
- },
30389
- maxBuffer: 10 * 1024 * 1024
30390
- // 10MB buffer
30391
- });
30392
- const output = stdout + stderr;
30393
- const versionMatch = output.match(/Python (\d+\.\d+\.\d+)/);
30394
- const pythonVersion = versionMatch ? versionMatch[1] : void 0;
30395
- if (verbose) {
30396
- console.log(output);
30397
- }
30398
- return {
30399
- success: true,
30400
- message: "Python environment set up successfully",
30401
- alreadyExists: false,
30402
- pythonVersion,
30403
- output
30404
- };
30405
- } catch (error) {
30406
- const errorOutput = error.stdout + error.stderr;
30407
- return {
30408
- success: false,
30409
- message: `Setup failed: ${error.message}`,
30410
- alreadyExists: false,
30411
- output: errorOutput
30412
- };
30413
- }
30414
- }
30415
- function getPythonSetupInstructions() {
30416
- return `
30417
- Python environment not set up. Please run one of the following commands:
30418
-
30419
- Option 1 (Automatic):
30420
- import { setupPythonEnvironment } from '@exulu/backend';
30421
- await setupPythonEnvironment();
30422
-
30423
- Option 2 (Manual - for package consumers):
30424
- npx @exulu/backend setup-python
30425
-
30426
- Option 3 (Manual - for contributors):
30427
- npm run python:setup
30428
-
30429
- These commands will automatically create a Python virtual environment (.venv)
30430
- in the @exulu/backend package and install all required dependencies.
30431
-
30432
- Requirements:
30433
- - Python 3.10 or higher must be installed
30434
- - pip must be available
30435
- - venv module must be available (for creating virtual environments)
30436
-
30437
- If Python dependencies are not installed, install them first, then run one of the commands above:
30438
- - macOS: brew install python@3.12
30439
- - Ubuntu/Debian: sudo apt-get install python3.12 python3-pip python3-venv
30440
- - Alpine Linux: apk add python3 py3-pip python3-dev
30441
- - Windows: Download from https://www.python.org/downloads/
30442
-
30443
- Note: In Docker containers, ensure you install all three components:
30444
- Ubuntu/Debian: apt-get install -y python3 python3-pip python3-venv
30445
- Alpine: apk add python3 py3-pip python3-dev
30446
- `.trim();
30447
- }
30448
- async function validatePythonEnvironment(packageRoot, checkPackages = true) {
30449
- const root = packageRoot ?? getPackageRoot();
30450
- const venvPath = getVenvPath(root);
30451
- const pythonPath = (0, import_path3.join)(venvPath, "bin", "python");
30452
- if (!(0, import_fs4.existsSync)(venvPath)) {
30453
- return {
30454
- valid: false,
30455
- message: getPythonSetupInstructions()
30456
- };
30457
- }
30458
- if (!(0, import_fs4.existsSync)(pythonPath)) {
30459
- return {
30460
- valid: false,
30461
- message: "Python virtual environment is corrupted. Please run:\n await setupPythonEnvironment({ force: true })"
30462
- };
30463
- }
30464
- try {
30465
- await execAsync4(`"${pythonPath}" --version`, { cwd: root });
30466
- } catch {
30467
- return {
30468
- valid: false,
30469
- message: "Python executable is not working. Please run:\n await setupPythonEnvironment({ force: true })"
30470
- };
30471
- }
30472
- if (checkPackages) {
30473
- const criticalPackages = ["docling", "transformers"];
30474
- const missingPackages = [];
30475
- for (const pkg of criticalPackages) {
30476
- try {
30477
- await execAsync4(`"${pythonPath}" -c "import ${pkg}"`, {
30478
- cwd: root,
30479
- timeout: 1e4
30480
- // 10 second timeout per import check
30481
- });
30482
- } catch {
30483
- missingPackages.push(pkg);
30484
- }
30485
- }
30486
- if (missingPackages.length > 0) {
30487
- return {
30488
- valid: false,
30489
- message: `Python environment exists but required packages are not installed: ${missingPackages.join(", ")}
30490
-
30491
- This usually happens when:
30492
- 1. The .venv folder was copied but dependencies were not installed
30493
- 2. The package was installed via npm but setup script was not run
30494
-
30495
- Please run:
30496
- await setupPythonEnvironment({ force: true })
30497
-
30498
- Or manually run the setup script:
30499
- bash ` + getSetupScriptPath(root)
30500
- };
30501
- }
30502
- }
30503
- return {
30504
- valid: true,
30505
- message: "Python environment is valid"
30506
- };
30507
- }
30635
+ init_python_setup();
30508
30636
 
30509
30637
  // src/templates/contexts/index.ts
30510
30638
  init_cjs_shims();
@@ -32493,6 +32621,7 @@ var initLiteLLMDatabase = async (packageRoot) => {
32493
32621
  };
32494
32622
 
32495
32623
  // src/postgres/init-litellm-db.ts
32624
+ init_python_setup();
32496
32625
  var initLitellmDb = async () => {
32497
32626
  await initLiteLLMDatabase(getPackageRoot());
32498
32627
  console.log("[EXULU] LiteLLM database initialized.");
@@ -33027,6 +33156,9 @@ var MarkdownChunker = class {
33027
33156
  }
33028
33157
  };
33029
33158
 
33159
+ // src/index.ts
33160
+ init_python_setup();
33161
+
33030
33162
  // ee/python/documents/processing/doc_processor.ts
33031
33163
  init_cjs_shims();
33032
33164
  var fs4 = __toESM(require("fs"), 1);
@@ -33049,6 +33181,7 @@ var import_util3 = require("util");
33049
33181
  var import_path4 = require("path");
33050
33182
  var import_fs5 = require("fs");
33051
33183
  var import_url2 = require("url");
33184
+ init_python_setup();
33052
33185
  var execAsync5 = (0, import_util3.promisify)(import_child_process2.exec);
33053
33186
  function getPackageRoot2() {
33054
33187
  const currentFile = (0, import_url2.fileURLToPath)(importMetaUrl);
@@ -33184,6 +33317,7 @@ ${command}`;
33184
33317
  }
33185
33318
 
33186
33319
  // ee/python/documents/processing/doc_processor.ts
33320
+ init_python_setup();
33187
33321
  var import_liteparse = require("@llamaindex/liteparse");
33188
33322
 
33189
33323
  // src/exulu/resolve-ocr.ts