@librechat/agents 3.6.13 → 3.6.14

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.
Files changed (38) hide show
  1. package/dist/cjs/main.cjs +6 -0
  2. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs +33 -14
  3. package/dist/cjs/tools/cloudflare/CloudflareSandboxExecutionEngine.cjs.map +1 -1
  4. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs +3 -2
  5. package/dist/cjs/tools/cloudflare/CloudflareSandboxTools.cjs.map +1 -1
  6. package/dist/cjs/tools/local/LocalCodingTools.cjs +10 -14
  7. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -1
  8. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +67 -3
  9. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -1
  10. package/dist/cjs/tools/local/syntaxCheck.cjs +10 -14
  11. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -1
  12. package/dist/cjs/tools/local/workspaceFS.cjs +2 -2
  13. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -1
  14. package/dist/esm/main.mjs +3 -3
  15. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs +33 -15
  16. package/dist/esm/tools/cloudflare/CloudflareSandboxExecutionEngine.mjs.map +1 -1
  17. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs +4 -3
  18. package/dist/esm/tools/cloudflare/CloudflareSandboxTools.mjs.map +1 -1
  19. package/dist/esm/tools/local/LocalCodingTools.mjs +11 -15
  20. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -1
  21. package/dist/esm/tools/local/LocalExecutionEngine.mjs +63 -4
  22. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -1
  23. package/dist/esm/tools/local/syntaxCheck.mjs +11 -15
  24. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -1
  25. package/dist/esm/tools/local/workspaceFS.mjs +2 -2
  26. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -1
  27. package/dist/types/tools/cloudflare/CloudflareSandboxExecutionEngine.d.ts +6 -0
  28. package/dist/types/tools/local/LocalExecutionEngine.d.ts +22 -0
  29. package/dist/types/tools/local/workspaceFS.d.ts +1 -1
  30. package/dist/types/types/tools.d.ts +22 -37
  31. package/package.json +2 -1
  32. package/src/tools/cloudflare/CloudflareSandboxExecutionEngine.ts +68 -17
  33. package/src/tools/cloudflare/CloudflareSandboxTools.ts +4 -3
  34. package/src/tools/local/LocalCodingTools.ts +25 -35
  35. package/src/tools/local/LocalExecutionEngine.ts +120 -3
  36. package/src/tools/local/syntaxCheck.ts +23 -26
  37. package/src/tools/local/workspaceFS.ts +2 -3
  38. package/src/types/tools.ts +25 -37
@@ -18,12 +18,15 @@
18
18
 
19
19
  import { extname } from 'path';
20
20
  import type * as t from '@/types';
21
- import { isWorkspaceClientTimeoutError } from './workspaceFS';
22
21
  import {
22
+ commandAvailabilityEnvCacheKey,
23
23
  getSpawn,
24
24
  getWorkspaceFS,
25
+ probeLocalCommandAvailability,
26
+ setCommandAvailabilityCacheEntry,
25
27
  spawnLocalProcess,
26
28
  } from './LocalExecutionEngine';
29
+ import { isWorkspaceClientTimeoutError } from './workspaceFS';
27
30
 
28
31
  export type SyntaxCheckOutcome =
29
32
  | { ok: true }
@@ -48,26 +51,17 @@ export type SyntaxChecker = (
48
51
  * reset hook re-creates the map.
49
52
  */
50
53
  type ProbeKind = 'hasNode' | 'hasPython' | 'hasBash';
51
- type ProbeCache = Partial<Record<ProbeKind, Promise<boolean>>>;
54
+ type ProbeCache = Partial<
55
+ Record<ProbeKind, ReturnType<typeof probeLocalCommandAvailability>>
56
+ >;
52
57
 
53
- // Per-backend × per-env cache. Codex P2 #40 keying by spawn
54
- // backend alone misses env-driven availability changes (e.g. PATH
55
- // loses node between Runs that share the same backend). Same fix
56
- // shape as the ripgrep cache (Codex P1 #34).
58
+ // Per-backend × hashed-env cache. The inner map is bounded so a long-lived
59
+ // world cannot retain unbounded environment variants.
57
60
  let probeCacheByBackend = new WeakMap<
58
61
  t.LocalSpawn,
59
62
  Map<string, ProbeCache>
60
63
  >();
61
64
 
62
- function envCacheKey(env: NodeJS.ProcessEnv | undefined): string {
63
- if (env == null) return '';
64
- const sorted: Record<string, string | undefined> = {};
65
- for (const k of Object.keys(env).sort()) {
66
- sorted[k] = env[k];
67
- }
68
- return JSON.stringify(sorted);
69
- }
70
-
71
65
  function cacheFor(
72
66
  config: t.LocalExecutionConfig
73
67
  ): ProbeCache {
@@ -77,11 +71,11 @@ function cacheFor(
77
71
  envMap = new Map();
78
72
  probeCacheByBackend.set(backend, envMap);
79
73
  }
80
- const envKey = envCacheKey(config.env);
74
+ const envKey = commandAvailabilityEnvCacheKey(config.env);
81
75
  let entry = envMap.get(envKey);
82
76
  if (entry == null) {
83
77
  entry = {};
84
- envMap.set(envKey, entry);
78
+ setCommandAvailabilityCacheEntry(envMap, envKey, entry);
85
79
  }
86
80
  return entry;
87
81
  }
@@ -95,17 +89,20 @@ async function probe(
95
89
  const entry = cacheFor(config);
96
90
  let probePromise = entry[cached];
97
91
  if (probePromise == null) {
98
- probePromise = spawnLocalProcess(
99
- command,
100
- args,
101
- { ...config, timeoutMs: 5000, sandbox: { enabled: false } },
102
- { internal: true }
103
- )
104
- .then((result) => result != null && result.exitCode === 0)
105
- .catch(() => false);
92
+ probePromise = probeLocalCommandAvailability(command, args, config);
106
93
  entry[cached] = probePromise;
107
94
  }
108
- return probePromise;
95
+ const result = await probePromise;
96
+ if (!result.cacheable && entry[cached] === probePromise) {
97
+ delete entry[cached];
98
+ }
99
+ if (result.cacheUntil != null && result.cacheUntil <= Date.now()) {
100
+ if (entry[cached] === probePromise) {
101
+ delete entry[cached];
102
+ }
103
+ return probe(command, args, cached, config);
104
+ }
105
+ return result.available;
109
106
  }
110
107
 
111
108
  /**
@@ -92,10 +92,9 @@ export interface WorkspaceFS {
92
92
  * Returned by `getWorkspaceFS(config)` when the host hasn't supplied
93
93
  * an override on `local.exec.fs`.
94
94
  */
95
- export const nodeWorkspaceFS: WorkspaceFS = {
95
+ export const nodeWorkspaceFS = Object.freeze<WorkspaceFS>({
96
96
  // The runtime impl ignores the encoding-vs-buffer distinction; the
97
97
  // overload signatures above are what callers see.
98
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
99
98
  readFile: ((path: string, encoding?: 'utf8') =>
100
99
  encoding != null
101
100
  ? fsReadFile(path, encoding)
@@ -113,4 +112,4 @@ export const nodeWorkspaceFS: WorkspaceFS = {
113
112
  realpath: (path) => fsRealpath(path),
114
113
  unlink: (path) => fsUnlink(path),
115
114
  open: (path, flags) => fsOpen(path, flags),
116
- };
115
+ });
@@ -820,47 +820,35 @@ export type LocalWorkspaceConfig = {
820
820
  };
821
821
 
822
822
  /**
823
- * Engine-agnostic execution seam. Default uses Node's
824
- * `child_process.spawn` and `fs/promises`. A future engine (e.g.
825
- * stateful remote sandbox) supplies its own `spawn` and `fs` and
826
- * inherits every tool factory unchanged.
823
+ * One execution world shared by filesystem and subprocess operations.
824
+ * Backend identity is stable so capability probes remain warm when tool
825
+ * bundles are rebuilt for later agent bindings.
826
+ */
827
+ export interface ExecutionWorld {
828
+ /** Launches a process inside this world's filesystem namespace. */
829
+ readonly spawn: LocalSpawn;
830
+ /** Reads and writes the same namespace observed by `spawn`. */
831
+ readonly fs: Readonly<import('@/tools/local/workspaceFS').WorkspaceFS>;
832
+ /** Whether the world already enforces a sandbox boundary. */
833
+ readonly sandboxed: boolean;
834
+ }
835
+
836
+ /**
837
+ * Backward-compatible partial execution-world override. Omitted fields use
838
+ * the Node host world; remote backends should provide the complete trio.
827
839
  *
828
- * **Important — pair `spawn` and `fs` together.** Most file-touching
829
- * surfaces in the local engine route through `getWorkspaceFS(config)`
830
- * so a host can transparently swap in a remote/in-memory FS. A small
831
- * set of helpers — currently the `execute_code` non-bash temp-file
832
- * write (Codex P2 [48]) — still uses host `fs/promises` directly to
833
- * stage source on disk before invoking the spawn. If you override
834
- * `spawn` to point at a remote runtime (SSH, container, etc.) you
835
- * MUST also override `fs` with the corresponding remote
836
- * implementation; otherwise temp source files written to the host
837
- * `/tmp` won't be visible to the remote interpreter and `py`/`js`/
838
- * `ts`/etc. executions will fail. (Bash-style executions go through
839
- * `executeLocalBash` which doesn't stage temp files, so they're
840
- * unaffected.)
840
+ * Non-bash `execute_code` still stages source through the host temporary
841
+ * directory. Remote runtimes should use their dedicated execution engine for
842
+ * that tool until temporary lifecycle operations join this seam.
841
843
  *
842
- * Threat-model note: the regex-based command validators
843
- * (`dangerousCommandPatterns`, `quotedDestructivePatterns`, etc.) and
844
- * the workspace policy hook are documented as best-effort tripwires.
845
- * The hard security boundary is `local.sandbox.enabled: true` (which
846
- * wraps execution in `@anthropic-ai/sandbox-runtime`); for adversarial-
847
- * model threat models, do NOT rely on the regex layer alone.
844
+ * Command validators and workspace policies are best-effort tripwires. For an
845
+ * adversarial-model threat model, use a backend sandbox boundary rather than
846
+ * relying on validation alone.
848
847
  */
849
848
  export type LocalExecConfig = {
850
- /** Pluggable spawn (for SSH, container, remote workers, etc.). */
851
- spawn?: LocalSpawn;
852
- /**
853
- * Pluggable filesystem (for remote-workspace engines). Pair with
854
- * `spawn` — see the type-level note above on why both should be
855
- * overridden together for non-host engines.
856
- */
857
- fs?: import('@/tools/local/workspaceFS').WorkspaceFS;
858
- /**
859
- * Set by custom execution backends that already provide their own
860
- * sandbox boundary. Suppresses the local host-sandbox warning while
861
- * preserving the warning for plain host `child_process` execution.
862
- */
863
- sandboxed?: boolean;
849
+ -readonly [Key in keyof ExecutionWorld]?: Key extends 'fs'
850
+ ? import('@/tools/local/workspaceFS').WorkspaceFS
851
+ : ExecutionWorld[Key];
864
852
  };
865
853
 
866
854
  export type LocalExecutionConfig = {