@librechat/agents 3.1.77 → 3.1.78-dev.0

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 (181) hide show
  1. package/dist/cjs/common/enum.cjs +54 -0
  2. package/dist/cjs/common/enum.cjs.map +1 -1
  3. package/dist/cjs/graphs/Graph.cjs +148 -4
  4. package/dist/cjs/graphs/Graph.cjs.map +1 -1
  5. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs +291 -0
  6. package/dist/cjs/hooks/createWorkspacePolicyHook.cjs.map +1 -0
  7. package/dist/cjs/main.cjs +90 -0
  8. package/dist/cjs/main.cjs.map +1 -1
  9. package/dist/cjs/messages/anthropicToolCache.cjs +102 -0
  10. package/dist/cjs/messages/anthropicToolCache.cjs.map +1 -0
  11. package/dist/cjs/messages/prune.cjs +27 -0
  12. package/dist/cjs/messages/prune.cjs.map +1 -1
  13. package/dist/cjs/messages/recency.cjs +99 -0
  14. package/dist/cjs/messages/recency.cjs.map +1 -0
  15. package/dist/cjs/run.cjs +30 -0
  16. package/dist/cjs/run.cjs.map +1 -1
  17. package/dist/cjs/summarization/node.cjs +100 -6
  18. package/dist/cjs/summarization/node.cjs.map +1 -1
  19. package/dist/cjs/tools/ToolNode.cjs +635 -23
  20. package/dist/cjs/tools/ToolNode.cjs.map +1 -1
  21. package/dist/cjs/tools/local/CompileCheckTool.cjs +227 -0
  22. package/dist/cjs/tools/local/CompileCheckTool.cjs.map +1 -0
  23. package/dist/cjs/tools/local/FileCheckpointer.cjs +90 -0
  24. package/dist/cjs/tools/local/FileCheckpointer.cjs.map +1 -0
  25. package/dist/cjs/tools/local/LocalCodingTools.cjs +1098 -0
  26. package/dist/cjs/tools/local/LocalCodingTools.cjs.map +1 -0
  27. package/dist/cjs/tools/local/LocalExecutionEngine.cjs +1042 -0
  28. package/dist/cjs/tools/local/LocalExecutionEngine.cjs.map +1 -0
  29. package/dist/cjs/tools/local/LocalExecutionTools.cjs +122 -0
  30. package/dist/cjs/tools/local/LocalExecutionTools.cjs.map +1 -0
  31. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs +453 -0
  32. package/dist/cjs/tools/local/LocalProgrammaticToolCalling.cjs.map +1 -0
  33. package/dist/cjs/tools/local/attachments.cjs +183 -0
  34. package/dist/cjs/tools/local/attachments.cjs.map +1 -0
  35. package/dist/cjs/tools/local/bashAst.cjs +129 -0
  36. package/dist/cjs/tools/local/bashAst.cjs.map +1 -0
  37. package/dist/cjs/tools/local/editStrategies.cjs +188 -0
  38. package/dist/cjs/tools/local/editStrategies.cjs.map +1 -0
  39. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs +141 -0
  40. package/dist/cjs/tools/local/resolveLocalExecutionTools.cjs.map +1 -0
  41. package/dist/cjs/tools/local/syntaxCheck.cjs +182 -0
  42. package/dist/cjs/tools/local/syntaxCheck.cjs.map +1 -0
  43. package/dist/cjs/tools/local/textEncoding.cjs +30 -0
  44. package/dist/cjs/tools/local/textEncoding.cjs.map +1 -0
  45. package/dist/cjs/tools/local/workspaceFS.cjs +51 -0
  46. package/dist/cjs/tools/local/workspaceFS.cjs.map +1 -0
  47. package/dist/cjs/tools/subagent/SubagentExecutor.cjs +1 -0
  48. package/dist/cjs/tools/subagent/SubagentExecutor.cjs.map +1 -1
  49. package/dist/esm/common/enum.mjs +53 -1
  50. package/dist/esm/common/enum.mjs.map +1 -1
  51. package/dist/esm/graphs/Graph.mjs +149 -5
  52. package/dist/esm/graphs/Graph.mjs.map +1 -1
  53. package/dist/esm/hooks/createWorkspacePolicyHook.mjs +289 -0
  54. package/dist/esm/hooks/createWorkspacePolicyHook.mjs.map +1 -0
  55. package/dist/esm/main.mjs +17 -2
  56. package/dist/esm/main.mjs.map +1 -1
  57. package/dist/esm/messages/anthropicToolCache.mjs +99 -0
  58. package/dist/esm/messages/anthropicToolCache.mjs.map +1 -0
  59. package/dist/esm/messages/prune.mjs +26 -1
  60. package/dist/esm/messages/prune.mjs.map +1 -1
  61. package/dist/esm/messages/recency.mjs +97 -0
  62. package/dist/esm/messages/recency.mjs.map +1 -0
  63. package/dist/esm/run.mjs +30 -0
  64. package/dist/esm/run.mjs.map +1 -1
  65. package/dist/esm/summarization/node.mjs +100 -6
  66. package/dist/esm/summarization/node.mjs.map +1 -1
  67. package/dist/esm/tools/ToolNode.mjs +635 -23
  68. package/dist/esm/tools/ToolNode.mjs.map +1 -1
  69. package/dist/esm/tools/local/CompileCheckTool.mjs +223 -0
  70. package/dist/esm/tools/local/CompileCheckTool.mjs.map +1 -0
  71. package/dist/esm/tools/local/FileCheckpointer.mjs +87 -0
  72. package/dist/esm/tools/local/FileCheckpointer.mjs.map +1 -0
  73. package/dist/esm/tools/local/LocalCodingTools.mjs +1075 -0
  74. package/dist/esm/tools/local/LocalCodingTools.mjs.map +1 -0
  75. package/dist/esm/tools/local/LocalExecutionEngine.mjs +1022 -0
  76. package/dist/esm/tools/local/LocalExecutionEngine.mjs.map +1 -0
  77. package/dist/esm/tools/local/LocalExecutionTools.mjs +117 -0
  78. package/dist/esm/tools/local/LocalExecutionTools.mjs.map +1 -0
  79. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs +448 -0
  80. package/dist/esm/tools/local/LocalProgrammaticToolCalling.mjs.map +1 -0
  81. package/dist/esm/tools/local/attachments.mjs +180 -0
  82. package/dist/esm/tools/local/attachments.mjs.map +1 -0
  83. package/dist/esm/tools/local/bashAst.mjs +126 -0
  84. package/dist/esm/tools/local/bashAst.mjs.map +1 -0
  85. package/dist/esm/tools/local/editStrategies.mjs +185 -0
  86. package/dist/esm/tools/local/editStrategies.mjs.map +1 -0
  87. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs +137 -0
  88. package/dist/esm/tools/local/resolveLocalExecutionTools.mjs.map +1 -0
  89. package/dist/esm/tools/local/syntaxCheck.mjs +179 -0
  90. package/dist/esm/tools/local/syntaxCheck.mjs.map +1 -0
  91. package/dist/esm/tools/local/textEncoding.mjs +27 -0
  92. package/dist/esm/tools/local/textEncoding.mjs.map +1 -0
  93. package/dist/esm/tools/local/workspaceFS.mjs +49 -0
  94. package/dist/esm/tools/local/workspaceFS.mjs.map +1 -0
  95. package/dist/esm/tools/subagent/SubagentExecutor.mjs +1 -0
  96. package/dist/esm/tools/subagent/SubagentExecutor.mjs.map +1 -1
  97. package/dist/types/common/enum.d.ts +39 -1
  98. package/dist/types/graphs/Graph.d.ts +34 -0
  99. package/dist/types/hooks/createWorkspacePolicyHook.d.ts +95 -0
  100. package/dist/types/hooks/index.d.ts +2 -0
  101. package/dist/types/index.d.ts +1 -0
  102. package/dist/types/messages/anthropicToolCache.d.ts +51 -0
  103. package/dist/types/messages/index.d.ts +2 -0
  104. package/dist/types/messages/prune.d.ts +11 -0
  105. package/dist/types/messages/recency.d.ts +64 -0
  106. package/dist/types/run.d.ts +21 -0
  107. package/dist/types/tools/ToolNode.d.ts +145 -2
  108. package/dist/types/tools/local/CompileCheckTool.d.ts +31 -0
  109. package/dist/types/tools/local/FileCheckpointer.d.ts +39 -0
  110. package/dist/types/tools/local/LocalCodingTools.d.ts +57 -0
  111. package/dist/types/tools/local/LocalExecutionEngine.d.ts +149 -0
  112. package/dist/types/tools/local/LocalExecutionTools.d.ts +9 -0
  113. package/dist/types/tools/local/LocalProgrammaticToolCalling.d.ts +21 -0
  114. package/dist/types/tools/local/attachments.d.ts +84 -0
  115. package/dist/types/tools/local/bashAst.d.ts +11 -0
  116. package/dist/types/tools/local/editStrategies.d.ts +28 -0
  117. package/dist/types/tools/local/index.d.ts +12 -0
  118. package/dist/types/tools/local/resolveLocalExecutionTools.d.ts +38 -0
  119. package/dist/types/tools/local/syntaxCheck.d.ts +42 -0
  120. package/dist/types/tools/local/textEncoding.d.ts +21 -0
  121. package/dist/types/tools/local/workspaceFS.d.ts +49 -0
  122. package/dist/types/types/hitl.d.ts +56 -27
  123. package/dist/types/types/run.d.ts +8 -1
  124. package/dist/types/types/summarize.d.ts +30 -0
  125. package/dist/types/types/tools.d.ts +341 -6
  126. package/package.json +21 -2
  127. package/src/common/enum.ts +54 -0
  128. package/src/graphs/Graph.ts +164 -6
  129. package/src/hooks/__tests__/compactHooks.test.ts +38 -2
  130. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +393 -0
  131. package/src/hooks/createWorkspacePolicyHook.ts +355 -0
  132. package/src/hooks/index.ts +6 -0
  133. package/src/index.ts +1 -0
  134. package/src/messages/__tests__/anthropicToolCache.test.ts +125 -0
  135. package/src/messages/__tests__/recency.test.ts +267 -0
  136. package/src/messages/anthropicToolCache.ts +116 -0
  137. package/src/messages/index.ts +2 -0
  138. package/src/messages/prune.ts +27 -1
  139. package/src/messages/recency.ts +155 -0
  140. package/src/run.ts +31 -0
  141. package/src/scripts/compare_pi_vs_ours.ts +840 -0
  142. package/src/scripts/local_engine.ts +166 -0
  143. package/src/scripts/local_engine_checkpointer.ts +205 -0
  144. package/src/scripts/local_engine_compile.ts +263 -0
  145. package/src/scripts/local_engine_hooks.ts +226 -0
  146. package/src/scripts/local_engine_image.ts +201 -0
  147. package/src/scripts/local_engine_ptc.ts +151 -0
  148. package/src/scripts/local_engine_workspace.ts +258 -0
  149. package/src/scripts/summarization-recency.ts +462 -0
  150. package/src/specs/prune.test.ts +39 -0
  151. package/src/summarization/__tests__/node.test.ts +499 -3
  152. package/src/summarization/node.ts +124 -7
  153. package/src/tools/ToolNode.ts +769 -20
  154. package/src/tools/__tests__/LocalExecutionTools.test.ts +2647 -0
  155. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +175 -0
  156. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +114 -0
  157. package/src/tools/__tests__/ToolNode.session.test.ts +84 -0
  158. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +467 -0
  159. package/src/tools/__tests__/directToolHooks.test.ts +411 -0
  160. package/src/tools/__tests__/localToolNames.test.ts +73 -0
  161. package/src/tools/__tests__/workspaceSeam.test.ts +134 -0
  162. package/src/tools/local/CompileCheckTool.ts +278 -0
  163. package/src/tools/local/FileCheckpointer.ts +93 -0
  164. package/src/tools/local/LocalCodingTools.ts +1342 -0
  165. package/src/tools/local/LocalExecutionEngine.ts +1329 -0
  166. package/src/tools/local/LocalExecutionTools.ts +167 -0
  167. package/src/tools/local/LocalProgrammaticToolCalling.ts +594 -0
  168. package/src/tools/local/__tests__/FileCheckpointer.test.ts +120 -0
  169. package/src/tools/local/__tests__/editStrategies.test.ts +134 -0
  170. package/src/tools/local/attachments.ts +251 -0
  171. package/src/tools/local/bashAst.ts +151 -0
  172. package/src/tools/local/editStrategies.ts +188 -0
  173. package/src/tools/local/index.ts +12 -0
  174. package/src/tools/local/resolveLocalExecutionTools.ts +208 -0
  175. package/src/tools/local/syntaxCheck.ts +243 -0
  176. package/src/tools/local/textEncoding.ts +37 -0
  177. package/src/tools/local/workspaceFS.ts +89 -0
  178. package/src/types/hitl.ts +56 -27
  179. package/src/types/run.ts +12 -1
  180. package/src/types/summarize.ts +31 -0
  181. package/src/types/tools.ts +359 -7
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Engine-agnostic filesystem seam for the local-coding tool suite.
3
+ *
4
+ * The current "local" engine maps every operation to Node's
5
+ * `fs/promises` against the host machine. A future engine — e.g. a
6
+ * stateful remote sandbox (e2b / Modal / Daytona / ssh-jail) —
7
+ * supplies its own `WorkspaceFS` implementation and reuses every
8
+ * tool factory unchanged. Same fuzzy-match `edit_file`, same
9
+ * checkpointer, same syntax-check, same image attachments — only
10
+ * the underlying I/O changes.
11
+ *
12
+ * Path semantics belong to the implementation. The local engine
13
+ * interprets paths as host filesystem paths; a remote engine would
14
+ * interpret them as remote-namespace paths. Tool factories don't
15
+ * inspect the strings beyond passing them through.
16
+ *
17
+ * Keep this surface minimal. Add a method only when an existing
18
+ * tool genuinely needs it; resist the temptation to mirror all of
19
+ * `fs/promises`.
20
+ */
21
+
22
+ import {
23
+ mkdir as fsMkdir,
24
+ open as fsOpen,
25
+ readdir as fsReaddir,
26
+ readFile as fsReadFile,
27
+ realpath as fsRealpath,
28
+ stat as fsStat,
29
+ unlink as fsUnlink,
30
+ writeFile as fsWriteFile,
31
+ } from 'fs/promises';
32
+ import type { MakeDirectoryOptions, Stats, WriteFileOptions } from 'fs';
33
+ import type { FileHandle } from 'fs/promises';
34
+
35
+ export type ReaddirEntry = {
36
+ name: string;
37
+ isFile(): boolean;
38
+ isDirectory(): boolean;
39
+ isSymbolicLink(): boolean;
40
+ };
41
+
42
+ export interface WorkspaceFS {
43
+ readFile(path: string, encoding: 'utf8'): Promise<string>;
44
+ readFile(path: string): Promise<Buffer>;
45
+ writeFile(
46
+ path: string,
47
+ content: string | Buffer,
48
+ options?: WriteFileOptions
49
+ ): Promise<void>;
50
+ stat(path: string): Promise<Stats>;
51
+ readdir(
52
+ path: string,
53
+ options: { withFileTypes: true }
54
+ ): Promise<ReaddirEntry[]>;
55
+ readdir(path: string): Promise<string[]>;
56
+ mkdir(path: string, options?: MakeDirectoryOptions): Promise<void>;
57
+ realpath(path: string): Promise<string>;
58
+ unlink(path: string): Promise<void>;
59
+ /** Open a file for low-level read access (used by binary detection). */
60
+ open(path: string, flags: 'r'): Promise<FileHandle>;
61
+ }
62
+
63
+ /**
64
+ * Default `WorkspaceFS` backed by Node's `fs/promises` module.
65
+ * Returned by `getWorkspaceFS(config)` when the host hasn't supplied
66
+ * an override on `local.exec.fs`.
67
+ */
68
+ export const nodeWorkspaceFS: WorkspaceFS = {
69
+ // The runtime impl ignores the encoding-vs-buffer distinction; the
70
+ // overload signatures above are what callers see.
71
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
72
+ readFile: ((path: string, encoding?: 'utf8') =>
73
+ encoding != null
74
+ ? fsReadFile(path, encoding)
75
+ : fsReadFile(path)) as WorkspaceFS['readFile'],
76
+ writeFile: (path, content, options) =>
77
+ fsWriteFile(path, content, options ?? 'utf8'),
78
+ stat: (path) => fsStat(path),
79
+ readdir: ((path: string, options?: { withFileTypes: true }) =>
80
+ options?.withFileTypes === true
81
+ ? fsReaddir(path, { withFileTypes: true })
82
+ : fsReaddir(path)) as WorkspaceFS['readdir'],
83
+ mkdir: async (path, options) => {
84
+ await fsMkdir(path, options);
85
+ },
86
+ realpath: (path) => fsRealpath(path),
87
+ unlink: (path) => fsUnlink(path),
88
+ open: (path, flags) => fsOpen(path, flags),
89
+ };
package/src/types/hitl.ts CHANGED
@@ -233,39 +233,68 @@ export function isAskUserQuestionInterrupt(
233
233
  * matches the pre-HITL behavior so existing hosts upgrading the SDK
234
234
  * see no change until they're ready to wire the resume UI.
235
235
  *
236
- * ## Scope: event-driven tools only
236
+ * ## Scope: every tool the ToolNode runs
237
237
  *
238
- * The interrupt path is wired into `ToolNode.dispatchToolEvents`, which
239
- * runs when the agent uses event-driven tool dispatch (the path
240
- * LibreChat and most production hosts take). Tools that execute via
241
- * the direct path i.e. tools listed in `directToolNames` (the
242
- * graph-managed handoff and subagent tools) or tools on agents
243
- * configured WITHOUT `eventDrivenMode` bypass the hook system
244
- * entirely. `PreToolUse` hooks do not fire for those tools and HITL
245
- * approval does not gate them.
238
+ * The interrupt path is wired into both `dispatchToolEvents` (the
239
+ * event-driven path) and `runDirectToolWithLifecycleHooks` (the
240
+ * direct path used by `directToolNames` entries graph-managed
241
+ * handoff/subagent tools and every in-process `graphTool` instance).
242
+ * `PreToolUse` hooks fire for every tool the ToolNode invokes, and
243
+ * HITL approval gates every tool whose hook returns `'ask'` —
244
+ * regardless of whether the tool is dispatched as an event or
245
+ * invoked in-process. This convergence happened in two follow-up
246
+ * commits to the original HITL surface (see `Graph.ts` —
247
+ * `hookRegistry`/`humanInTheLoop` are passed in both
248
+ * event-driven and legacy branches; and `ToolNode.runDirectToolWithLifecycleHooks`
249
+ * — direct-path tools build their own single-tool `tool_approval`
250
+ * payload and raise `interrupt()` the same way the event path does).
246
251
  *
247
252
  * Practical implications:
248
- * - LibreChat-style hosts using event-driven dispatch get the full
249
- * HITL surface across every tool the model calls.
250
- * - Hosts using `AgentInputs.tools` directly without event-driven
251
- * mode get policy enforcement for nothing the hooks register
252
- * but never fire. Either switch to event-driven mode or accept
253
- * that direct tools are not approval-gated. This is documented
254
- * also on `ToolNodeOptions.hookRegistry`.
255
- * - Mixed direct + event batches (e.g. a handoff tool sharing an
256
- * LLM turn with a regular tool) currently re-execute the direct
257
- * half on resume, since LangGraph rolls back the entire ToolNode
258
- * on `interrupt()` throw. Hosts whose direct tools have side
259
- * effects (subagents that invoke models, handoffs that trigger
260
- * downstream work) should avoid mixing those tools into the same
261
- * batch as approval-gated event tools.
253
+ * - Every host gets the full HITL surface across every tool the
254
+ * model calls event-dispatched, direct, mixed.
255
+ * - `createToolPolicyHook` and `createWorkspacePolicyHook` apply
256
+ * uniformly. A hook can be registered without knowing or caring
257
+ * which path the tool will take.
258
+ * - Direct tools that the host opted into via `directToolNames` no
259
+ * longer bypass policy. If you need a tool to skip the hook
260
+ * surface entirely, omit it from any registered matcher.
261
+ *
262
+ * ## Resume re-execution: every tool in the interrupted batch
263
+ *
264
+ * LangGraph rolls back to the start of the interrupted node on
265
+ * resume. That means **every tool in the same batch as the one that
266
+ * interrupted re-runs from the top on the resume pass**, not just
267
+ * the interrupting tool, and not just the direct half (this used to
268
+ * be framed as a direct-tool-specific concern; it is not — it
269
+ * applies to event-dispatched siblings too). Practical contract:
270
+ *
271
+ * - The body of the interrupting tool itself runs **once** total
272
+ * (the first pass interrupted *before* the body, the resume pass
273
+ * ran the body after the host's decision was applied).
274
+ * - The body of any sibling tool that already executed in the
275
+ * same batch before the interrupting tool runs **twice** — once
276
+ * on the first pass, once on the resume pass.
277
+ * - `PreToolUse` hooks fire **once per pass per tool**. A hook
278
+ * that always returns `'ask'` will loop forever on resume; real
279
+ * hooks should be deterministic w.r.t. inputs and use the
280
+ * `'ask' → host approves → resume → hook returns 'allow'`
281
+ * pattern, where the second-pass `allow` reflects the host
282
+ * having recorded the approval (e.g., a session-scoped approved-
283
+ * paths set keyed by `runId`).
284
+ *
285
+ * Consequence: any tool with side effects MUST be idempotent if
286
+ * there's any chance another tool in the same batch could trigger
287
+ * an interrupt. This applies equally to direct tools (handoffs,
288
+ * subagents) and to event tools.
262
289
  *
263
290
  * ## Note on idempotency
264
291
  *
265
- * When an interrupt fires, LangGraph re-runs the interrupted node
266
- * from the start on resume, which fires `PreToolUse` hooks again.
267
- * Hooks that produce side effects (logging, external calls) will see
268
- * two invocations per paused turn.
292
+ * Same root cause as the resume re-execution above: LangGraph
293
+ * re-runs the interrupted node from the start on resume, which
294
+ * fires `PreToolUse` hooks again. Hooks that produce side effects
295
+ * (logging, external calls) will see at least two invocations per
296
+ * paused turn — exactly two for the interrupting tool, possibly
297
+ * more across siblings.
269
298
  */
270
299
  export interface HumanInTheLoopConfig {
271
300
  /**
package/src/types/run.ts CHANGED
@@ -11,7 +11,11 @@ import type * as s from '@/types/stream';
11
11
  import type * as e from '@/common/enum';
12
12
  import type * as g from '@/types/graph';
13
13
  import type * as l from '@/types/llm';
14
- import type { ToolSessionMap, ToolOutputReferencesConfig } from '@/types/tools';
14
+ import type {
15
+ ToolSessionMap,
16
+ ToolExecutionConfig,
17
+ ToolOutputReferencesConfig,
18
+ } from '@/types/tools';
15
19
  import type { HumanInTheLoopConfig } from '@/types/hitl';
16
20
  import type { HookRegistry } from '@/hooks';
17
21
 
@@ -157,6 +161,13 @@ export type RunConfig = {
157
161
  * placeholders. Disabled by default so existing runs are unaffected.
158
162
  */
159
163
  toolOutputReferences?: ToolOutputReferencesConfig;
164
+ /**
165
+ * Selects the execution backend for built-in code tools. Omit this to keep
166
+ * the remote LibreChat Code API sandbox. Set `{ engine: 'local' }` to run
167
+ * code execution locally and auto-bind the local coding tool suite unless
168
+ * `local.includeCodingTools` is set to `false`.
169
+ */
170
+ toolExecution?: ToolExecutionConfig;
160
171
  /**
161
172
  * First-class human-in-the-loop (HITL) flow for this run.
162
173
  *
@@ -10,6 +10,30 @@ export type SummarizationTrigger = {
10
10
  value: number;
11
11
  };
12
12
 
13
+ /**
14
+ * Controls how many recent messages are preserved verbatim during
15
+ * compaction. The most recent user-led turn is always preserved
16
+ * regardless of these caps, so a single oversized first message is
17
+ * never destroyed by summarization.
18
+ */
19
+ export type RetainRecentConfig = {
20
+ /**
21
+ * Maximum number of recent user-led turns to keep in the tail. A turn
22
+ * begins at a HumanMessage and includes every following AIMessage and
23
+ * ToolMessage up to (but not including) the next HumanMessage. Cutting
24
+ * at turn boundaries guarantees tool_use / tool_result pairs are never
25
+ * split. Set to `0` to disable the recency window (legacy behavior:
26
+ * summarize everything). Defaults to `2`.
27
+ */
28
+ turns?: number;
29
+ /**
30
+ * Optional cap on retained-recent tokens beyond the most recent turn.
31
+ * Older turns are added whole only while cumulative tokens stay below
32
+ * the cap. Defaults to undefined (no cap; bounded only by `turns`).
33
+ */
34
+ tokens?: number;
35
+ };
36
+
13
37
  export type SummarizationConfig = {
14
38
  provider?: Providers;
15
39
  model?: string;
@@ -20,6 +44,13 @@ export type SummarizationConfig = {
20
44
  maxSummaryTokens?: number;
21
45
  /** Fraction of the token budget reserved as headroom (0–1). Defaults to 0.05. */
22
46
  reserveRatio?: number;
47
+ /**
48
+ * Recent-message preservation policy. When unset, defaults to
49
+ * `{ turns: 2 }` so the last two user-led turns are kept verbatim
50
+ * while older content is summarized. Setting `{ turns: 0 }` reverts
51
+ * to the legacy behavior of summarizing every message.
52
+ */
53
+ retainRecent?: RetainRecentConfig;
23
54
  };
24
55
 
25
56
  export interface SummarizeResult {
@@ -52,9 +52,14 @@ export type ToolNodeOptions = {
52
52
  /** Tool names that must be executed directly (via runTool) even in event-driven mode (e.g., graph-managed handoff tools) */
53
53
  directToolNames?: Set<string>;
54
54
  /**
55
- * Hook registry for PreToolUse/PostToolUse lifecycle hooks.
56
- * Only fires for event-driven tool calls (`dispatchToolEvents`). Tools
57
- * routed through `directToolNames` bypass hook dispatch entirely.
55
+ * Hook registry for PreToolUse/PostToolUse/PostToolUseFailure/
56
+ * PermissionDenied lifecycle hooks. Fires for **every** tool the
57
+ * ToolNode invokes both event-dispatched tools (via
58
+ * `dispatchToolEvents`) and direct-path tools (via
59
+ * `runDirectToolWithLifecycleHooks`). The pre-existing limitation
60
+ * that direct-path tools "bypass hook dispatch entirely" was
61
+ * lifted in a follow-up to the HITL surface; both paths now route
62
+ * through the same hook lifecycle.
58
63
  */
59
64
  hookRegistry?: HookRegistry;
60
65
  /**
@@ -70,9 +75,11 @@ export type ToolNodeOptions = {
70
75
  *
71
76
  * Mirrors `RunConfig.humanInTheLoop` (which is the canonical place
72
77
  * to set this); the Graph threads it down to every ToolNode it
73
- * compiles. Same caveat: the interrupt path is only wired into the
74
- * event-driven dispatch (`dispatchToolEvents`), not into
75
- * `directToolNames` execution — direct tools bypass HITL entirely.
78
+ * compiles. The interrupt path is wired into both the event
79
+ * dispatch (`dispatchToolEvents`) and the direct path
80
+ * (`runDirectToolWithLifecycleHooks`) — direct tools no longer
81
+ * bypass HITL. See `HumanInTheLoopConfig` for the resume re-execution
82
+ * contract that applies equally to both paths.
76
83
  */
77
84
  humanInTheLoop?: HumanInTheLoopConfig;
78
85
  /** Max context tokens for the agent — used to compute tool result truncation limits. */
@@ -98,6 +105,25 @@ export type ToolNodeOptions = {
98
105
  * precedence over `toolOutputReferences` when both are set.
99
106
  */
100
107
  toolOutputRegistry?: ToolOutputReferenceRegistry;
108
+ /**
109
+ * Selects where built-in code execution tools run. Defaults to the
110
+ * remote LibreChat Code API sandbox; `local` swaps those same tool names
111
+ * to process-based local executors at ToolNode construction time.
112
+ */
113
+ toolExecution?: ToolExecutionConfig;
114
+ /**
115
+ * Pre-constructed file checkpointer shared across every ToolNode in
116
+ * a Run so `Run.rewindFiles()` (and direct
117
+ * `Run.getFileCheckpointer()` callers) sees a single unified
118
+ * snapshot store. The Graph layer creates one per Run when
119
+ * `toolExecution.local.fileCheckpointing === true` and threads it
120
+ * to every ToolNode it compiles; without this shared instance,
121
+ * multi-agent graphs would each get their own private checkpointer
122
+ * and the host couldn't reach any of them. Wins over the
123
+ * auto-created bundle checkpointer in
124
+ * `resolveLocalExecutionTools`.
125
+ */
126
+ fileCheckpointer?: LocalFileCheckpointer;
101
127
  };
102
128
 
103
129
  export type ToolNodeConstructorParams = ToolRefs & ToolNodeOptions;
@@ -116,6 +142,15 @@ export type CodeEnvFile = {
116
142
  id: string;
117
143
  name: string;
118
144
  session_id: string;
145
+ /**
146
+ * Identifier of the entity that owns this file's session (skill id,
147
+ * agent id, etc). Forwarded to codeapi so it can resolve the
148
+ * per-file sessionKey instead of falling back to a single
149
+ * request-level entity. Required when a single execute request
150
+ * references files uploaded under different entities (e.g. a skill
151
+ * file plus a user attachment in the same call).
152
+ */
153
+ entity_id?: string;
119
154
  };
120
155
 
121
156
  export type CodeExecutionToolParams =
@@ -132,6 +167,13 @@ export type FileRef = {
132
167
  path?: string;
133
168
  /** Session ID this file belongs to (for multi-session file tracking) */
134
169
  session_id?: string;
170
+ /**
171
+ * Entity that owns this file's session (skill id, agent id, etc).
172
+ * Carried on tracked session files so it can flow through to
173
+ * `_injected_files` when a subsequent execute references a mix of
174
+ * files uploaded under different entities.
175
+ */
176
+ entity_id?: string;
135
177
  /**
136
178
  * `true` when the codeapi sandbox echoed this entry as an unchanged
137
179
  * passthrough of an input the caller already owns (skill files,
@@ -333,7 +375,317 @@ export type ToolOutputReferencesConfig = {
333
375
  maxTotalSize?: number;
334
376
  };
335
377
 
336
- export type ProgrammaticCache = { toolMap: ToolMap; toolDefs: LCTool[] };
378
+ export type ToolExecutionEngine = 'sandbox' | 'local';
379
+
380
+ /**
381
+ * Records pre-write file contents so callers can rewind edits/writes
382
+ * made by the local engine. Implementations live in `src/tools/local`.
383
+ */
384
+ export interface LocalFileCheckpointer {
385
+ /**
386
+ * Captures the current contents of `absolutePath` before a write or
387
+ * edit. Idempotent: capturing the same path twice keeps the first
388
+ * snapshot. Records "did not exist" so creates can be undone with
389
+ * deletion.
390
+ */
391
+ captureBeforeWrite(absolutePath: string): Promise<void>;
392
+ /** Restores all captured snapshots. Returns the number of files restored. */
393
+ rewind(): Promise<number>;
394
+ /** Returns paths that have been captured during this run. */
395
+ capturedPaths(): string[];
396
+ }
397
+
398
+ /**
399
+ * Pluggable process launcher used by the local execution engine. When
400
+ * provided, the engine calls this in place of `child_process.spawn`,
401
+ * letting callers route shell commands through SSH, containers, or
402
+ * remote runners without forking the engine. The implementation must
403
+ * return a `ChildProcess`-shaped value whose `stdout`/`stderr` streams
404
+ * emit `data` events and that resolves a `close` event when finished.
405
+ */
406
+ export type LocalSpawn = (
407
+ command: string,
408
+ args: string[],
409
+ options: import('child_process').SpawnOptions
410
+ ) => import('child_process').ChildProcessWithoutNullStreams;
411
+
412
+ /** Bash command-validation strictness for the local engine. */
413
+ export type LocalBashAstMode = 'auto' | 'off' | 'strict';
414
+
415
+ export type LocalSandboxConfig = {
416
+ /**
417
+ * Enable Anthropic Sandbox Runtime wrapping for local process tools.
418
+ * Defaults to false; requires @anthropic-ai/sandbox-runtime to be installed.
419
+ */
420
+ enabled?: boolean;
421
+ /** Throw when native sandbox dependencies are unavailable. Defaults to false. */
422
+ failIfUnavailable?: boolean;
423
+ filesystem?: {
424
+ denyRead?: string[];
425
+ allowRead?: string[];
426
+ allowWrite?: string[];
427
+ denyWrite?: string[];
428
+ allowGitConfig?: boolean;
429
+ };
430
+ network?: {
431
+ allowedDomains?: string[];
432
+ deniedDomains?: string[];
433
+ allowUnixSockets?: string[];
434
+ allowAllUnixSockets?: boolean;
435
+ allowLocalBinding?: boolean;
436
+ allowMachLookup?: string[];
437
+ };
438
+ };
439
+
440
+ /**
441
+ * Workspace boundary the local-coding tools clamp file operations to.
442
+ *
443
+ * `root` is the canonical "project directory" — every file tool that
444
+ * accepts a path resolves it relative to `root` and refuses to touch
445
+ * paths outside it (after symlink resolution). `additionalRoots` lets
446
+ * monorepos extend the boundary to sibling directories without
447
+ * disabling clamping entirely.
448
+ *
449
+ * `allowReadOutside` and `allowWriteOutside` are escape hatches that
450
+ * disable clamping for read- and write-shaped tools respectively.
451
+ * Most hosts shouldn't need them — prefer wiring
452
+ * `createWorkspacePolicyHook` if you want "ask the user" semantics
453
+ * via the existing PreToolUse / HITL machinery instead of an
454
+ * unconditional bypass.
455
+ */
456
+ export type LocalWorkspaceConfig = {
457
+ /** Required. The canonical workspace root. */
458
+ root: string;
459
+ /**
460
+ * Sibling roots that also count as inside the workspace. Useful in
461
+ * monorepos where a project legitimately needs to reach a paired
462
+ * directory without disabling clamping.
463
+ */
464
+ additionalRoots?: readonly string[];
465
+ /** When true, disable the read-outside-workspace clamp. */
466
+ allowReadOutside?: boolean;
467
+ /** When true, disable the write-outside-workspace clamp. */
468
+ allowWriteOutside?: boolean;
469
+ };
470
+
471
+ /**
472
+ * Engine-agnostic execution seam. Default uses Node's
473
+ * `child_process.spawn` and `fs/promises`. A future engine (e.g.
474
+ * stateful remote sandbox) supplies its own `spawn` and `fs` and
475
+ * inherits every tool factory unchanged.
476
+ *
477
+ * **Important — pair `spawn` and `fs` together.** Most file-touching
478
+ * surfaces in the local engine route through `getWorkspaceFS(config)`
479
+ * so a host can transparently swap in a remote/in-memory FS. A small
480
+ * set of helpers — currently the `execute_code` non-bash temp-file
481
+ * write (Codex P2 [48]) — still uses host `fs/promises` directly to
482
+ * stage source on disk before invoking the spawn. If you override
483
+ * `spawn` to point at a remote runtime (SSH, container, etc.) you
484
+ * MUST also override `fs` with the corresponding remote
485
+ * implementation; otherwise temp source files written to the host
486
+ * `/tmp` won't be visible to the remote interpreter and `py`/`js`/
487
+ * `ts`/etc. executions will fail. (Bash-style executions go through
488
+ * `executeLocalBash` which doesn't stage temp files, so they're
489
+ * unaffected.)
490
+ *
491
+ * Threat-model note: the regex-based command validators
492
+ * (`dangerousCommandPatterns`, `quotedDestructivePatterns`, etc.) and
493
+ * the workspace policy hook are documented as best-effort tripwires.
494
+ * The hard security boundary is `local.sandbox.enabled: true` (which
495
+ * wraps execution in `@anthropic-ai/sandbox-runtime`); for adversarial-
496
+ * model threat models, do NOT rely on the regex layer alone.
497
+ */
498
+ export type LocalExecConfig = {
499
+ /** Pluggable spawn (for SSH, container, remote workers, etc.). */
500
+ spawn?: LocalSpawn;
501
+ /**
502
+ * Pluggable filesystem (for remote-workspace engines). Pair with
503
+ * `spawn` — see the type-level note above on why both should be
504
+ * overridden together for non-host engines.
505
+ */
506
+ fs?: import('@/tools/local/workspaceFS').WorkspaceFS;
507
+ };
508
+
509
+ export type LocalExecutionConfig = {
510
+ /**
511
+ * Working directory for local commands. Defaults to process.cwd().
512
+ * Back-compat shorthand for `workspace.root`.
513
+ *
514
+ * @deprecated Prefer `workspace.root`. Kept for backward
515
+ * compatibility with pre-workspace configs.
516
+ */
517
+ cwd?: string;
518
+ /**
519
+ * Workspace boundary configuration. When omitted, the implementation
520
+ * derives `{ root: cwd ?? process.cwd() }` so existing call sites
521
+ * keep working.
522
+ */
523
+ workspace?: LocalWorkspaceConfig;
524
+ /**
525
+ * Execution seam (spawn + fs). When omitted, defaults to Node-host
526
+ * child_process and fs/promises. Same back-compat: a top-level
527
+ * `spawn` field is honoured if `exec.spawn` isn't set.
528
+ */
529
+ exec?: LocalExecConfig;
530
+ /** Shell executable for bash-style tools. Defaults to `bash`. */
531
+ shell?: string;
532
+ /** Default timeout for local processes, in milliseconds. */
533
+ timeoutMs?: number;
534
+ /** Maximum stdout/stderr characters surfaced to the model. */
535
+ maxOutputChars?: number;
536
+ /**
537
+ * Hard cap on total bytes a single child process can stream across
538
+ * stdout+stderr before its process tree is killed. Independent from
539
+ * `maxOutputChars` (which controls what the model sees); this is the
540
+ * OOM backstop for noisy / runaway commands (`yes`, `cat /dev/urandom`,
541
+ * a verbose build that loops). Defaults to 50 MiB. Set higher for
542
+ * legitimately large logs; setting to 0 disables the cap and risks
543
+ * an OOM crash on the host.
544
+ */
545
+ maxSpawnedBytes?: number;
546
+ /** Extra environment variables merged over process.env. */
547
+ env?: NodeJS.ProcessEnv;
548
+ /** Optional process sandboxing via @anthropic-ai/sandbox-runtime. */
549
+ sandbox?: LocalSandboxConfig;
550
+ /**
551
+ * When true, block obviously mutating shell commands before execution.
552
+ * Useful for read-only agent modes and dry-run workflows.
553
+ */
554
+ readOnly?: boolean;
555
+ /** Permit dangerous commands that the validator otherwise blocks. */
556
+ allowDangerousCommands?: boolean;
557
+ /**
558
+ * Permit file tools to resolve paths outside `cwd`. Defaults to false.
559
+ *
560
+ * @deprecated Prefer the granular
561
+ * `workspace.allowReadOutside` / `workspace.allowWriteOutside`.
562
+ * Kept for backward compatibility; setting either of the
563
+ * workspace fields takes precedence over this flag.
564
+ */
565
+ allowOutsideWorkspace?: boolean;
566
+ /**
567
+ * Add the built-in local coding suite (`read_file`, `write_file`,
568
+ * `edit_file`, `grep_search`, `glob_search`, `list_directory`, plus local
569
+ * code/bash execution tools) when `engine` is `local`. Defaults to true.
570
+ */
571
+ includeCodingTools?: boolean;
572
+ /**
573
+ * Override the process launcher. When set, replaces
574
+ * `child_process.spawn` for every local tool invocation, allowing
575
+ * SSH/container delegation. Default: native spawn.
576
+ */
577
+ spawn?: LocalSpawn;
578
+ /**
579
+ * Tree-sitter-bash AST validation pass on bash commands.
580
+ * - `'off'` skips AST validation (regex + `bash -n` only — current behavior).
581
+ * - `'auto'` runs the AST validator when `tree-sitter` modules are
582
+ * available; falls back silently otherwise.
583
+ * - `'strict'` requires the AST validator and fails closed when
584
+ * parsing is unavailable or the command is too complex to verify.
585
+ * Default: `'off'` to preserve historical behavior.
586
+ */
587
+ bashAst?: LocalBashAstMode;
588
+ /**
589
+ * Enable per-Run file checkpointing for `edit_file` / `write_file`
590
+ * so callers can rewind file changes after a failed batch. When
591
+ * true and the run is constructed via `Run.create(...)`, the host
592
+ * can call `Run.rewindFiles()` (or `Run.getFileCheckpointer()` for
593
+ * the raw checkpointer). Defaults to false.
594
+ */
595
+ fileCheckpointing?: boolean;
596
+ /**
597
+ * Maximum bytes to read in `read_file` before returning a stub.
598
+ * Defaults to 10 MiB.
599
+ */
600
+ maxReadBytes?: number;
601
+ /**
602
+ * Controls whether `read_file` returns binary files as inline
603
+ * `MessageContentComplex[]` attachments (so vision-capable models
604
+ * see them) or as a textual stub.
605
+ *
606
+ * - `'off'` : never embed; current binary-stub behavior.
607
+ * - `'images-only'`: embed images (png/jpeg/gif/webp) as
608
+ * `image_url` blocks; other binaries get the stub.
609
+ * - `'images-and-pdf'` : also embed PDFs as `image_url` data URLs
610
+ * (Anthropic accepts these in tool_result; other providers may
611
+ * degrade to JSON).
612
+ *
613
+ * Defaults to `'off'` to preserve current behavior.
614
+ */
615
+ attachReadAttachments?: 'off' | 'images-only' | 'images-and-pdf';
616
+ /**
617
+ * Maximum pre-encoding byte size to embed inline. Anything larger
618
+ * degrades to an `<oversize>` stub. Defaults to 5 MiB to bound the
619
+ * post-base64 token cost.
620
+ */
621
+ maxAttachmentBytes?: number;
622
+ /**
623
+ * Run a fast per-file syntax check after every successful
624
+ * `edit_file` / `write_file`. When the checker finds an error,
625
+ * the diagnostics are appended to the tool result so the model
626
+ * can self-correct without a separate read round-trip.
627
+ *
628
+ * - `'off'` (default) : skip; current behavior.
629
+ * - `'auto'` : run the checker for known file types
630
+ * when the corresponding tool is on PATH. Silently skip
631
+ * otherwise.
632
+ * - `'strict'` : same as `'auto'`, plus fail the tool
633
+ * call with the error so the model is forced to react. Use
634
+ * when you don't trust the model to read a non-blocking
635
+ * advisory.
636
+ *
637
+ * Built-in checkers: Node `node --check` for `.js/.mjs/.cjs`,
638
+ * Python `py_compile` for `.py`, `JSON.parse` for `.json`,
639
+ * `bash -n` for `.sh/.bash`. TypeScript falls back to `compile_check`
640
+ * (project-level) since per-file `.ts` syntax check requires the
641
+ * `typescript` package; the host can wire a per-file checker via
642
+ * `local.spawn` if desired.
643
+ */
644
+ postEditSyntaxCheck?: 'off' | 'auto' | 'strict';
645
+ /**
646
+ * Configuration for the `compile_check` tool. When `engine` is
647
+ * `local` and `includeCodingTools` is on, the SDK exposes a
648
+ * `compile_check` tool that runs the project's standard
649
+ * type/lint command (`tsc --noEmit`, `cargo check`, etc.).
650
+ */
651
+ compileCheck?: {
652
+ /**
653
+ * Override the auto-detected command. Runs verbatim from `cwd`
654
+ * via the local engine's standard spawn pipeline (sandbox / AST
655
+ * validation / output overflow all apply).
656
+ */
657
+ command?: string;
658
+ /** Default timeout for `compile_check`, in milliseconds. Defaults to 120s. */
659
+ timeoutMs?: number;
660
+ };
661
+ };
662
+
663
+ export type ToolExecutionConfig = {
664
+ /** `sandbox` preserves the remote Code API behavior and is the default. */
665
+ engine?: ToolExecutionEngine;
666
+ /** Local process execution settings used when `engine` is `local`. */
667
+ local?: LocalExecutionConfig;
668
+ };
669
+
670
+ export type ProgrammaticCache = {
671
+ toolMap: ToolMap;
672
+ toolDefs: LCTool[];
673
+ /**
674
+ * Hook context plumbed through by ToolNode for the local
675
+ * programmatic-tool path so the in-process bridge can run
676
+ * `PreToolUse` hooks (deny / updatedInput) for inner tool calls.
677
+ * Not present for non-local (remote-engine) programmatic calling
678
+ * which dispatches inner tools through the host's own pipeline.
679
+ */
680
+ hookContext?: ProgrammaticHookContext;
681
+ };
682
+
683
+ export type ProgrammaticHookContext = {
684
+ registry: import('@/hooks').HookRegistry | undefined;
685
+ runId: string;
686
+ threadId?: string;
687
+ agentId?: string;
688
+ };
337
689
 
338
690
  /** Search mode: code_interpreter uses external sandbox, local uses safe substring matching */
339
691
  export type ToolSearchMode = 'code_interpreter' | 'local';