@librechat/agents 3.1.77 → 3.1.78

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 (185) 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 +155 -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 +31 -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 +156 -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 +31 -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/tools/subagent/SubagentExecutor.d.ts +29 -0
  123. package/dist/types/types/hitl.d.ts +56 -27
  124. package/dist/types/types/run.d.ts +8 -1
  125. package/dist/types/types/summarize.d.ts +30 -0
  126. package/dist/types/types/tools.d.ts +341 -6
  127. package/package.json +21 -2
  128. package/src/common/enum.ts +54 -0
  129. package/src/graphs/Graph.ts +173 -6
  130. package/src/hooks/__tests__/compactHooks.test.ts +38 -2
  131. package/src/hooks/__tests__/createWorkspacePolicyHook.test.ts +393 -0
  132. package/src/hooks/createWorkspacePolicyHook.ts +355 -0
  133. package/src/hooks/index.ts +6 -0
  134. package/src/index.ts +1 -0
  135. package/src/messages/__tests__/anthropicToolCache.test.ts +125 -0
  136. package/src/messages/__tests__/recency.test.ts +267 -0
  137. package/src/messages/anthropicToolCache.ts +116 -0
  138. package/src/messages/index.ts +2 -0
  139. package/src/messages/prune.ts +27 -1
  140. package/src/messages/recency.ts +155 -0
  141. package/src/run.ts +31 -0
  142. package/src/scripts/compare_pi_vs_ours.ts +840 -0
  143. package/src/scripts/local_engine.ts +166 -0
  144. package/src/scripts/local_engine_checkpointer.ts +205 -0
  145. package/src/scripts/local_engine_compile.ts +263 -0
  146. package/src/scripts/local_engine_hooks.ts +226 -0
  147. package/src/scripts/local_engine_image.ts +201 -0
  148. package/src/scripts/local_engine_ptc.ts +151 -0
  149. package/src/scripts/local_engine_workspace.ts +258 -0
  150. package/src/scripts/subagent-configurable-inheritance.ts +252 -0
  151. package/src/scripts/summarization-recency.ts +462 -0
  152. package/src/specs/prune.test.ts +39 -0
  153. package/src/summarization/__tests__/node.test.ts +499 -3
  154. package/src/summarization/node.ts +124 -7
  155. package/src/tools/ToolNode.ts +769 -20
  156. package/src/tools/__tests__/LocalExecutionTools.test.ts +2647 -0
  157. package/src/tools/__tests__/ProgrammaticToolCalling.test.ts +175 -0
  158. package/src/tools/__tests__/SubagentExecutor.test.ts +148 -0
  159. package/src/tools/__tests__/ToolNode.outputReferences.test.ts +114 -0
  160. package/src/tools/__tests__/ToolNode.session.test.ts +84 -0
  161. package/src/tools/__tests__/directToolHITLResumeScope.test.ts +467 -0
  162. package/src/tools/__tests__/directToolHooks.test.ts +411 -0
  163. package/src/tools/__tests__/localToolNames.test.ts +73 -0
  164. package/src/tools/__tests__/workspaceSeam.test.ts +134 -0
  165. package/src/tools/local/CompileCheckTool.ts +278 -0
  166. package/src/tools/local/FileCheckpointer.ts +93 -0
  167. package/src/tools/local/LocalCodingTools.ts +1342 -0
  168. package/src/tools/local/LocalExecutionEngine.ts +1329 -0
  169. package/src/tools/local/LocalExecutionTools.ts +167 -0
  170. package/src/tools/local/LocalProgrammaticToolCalling.ts +594 -0
  171. package/src/tools/local/__tests__/FileCheckpointer.test.ts +120 -0
  172. package/src/tools/local/__tests__/editStrategies.test.ts +134 -0
  173. package/src/tools/local/attachments.ts +251 -0
  174. package/src/tools/local/bashAst.ts +151 -0
  175. package/src/tools/local/editStrategies.ts +188 -0
  176. package/src/tools/local/index.ts +12 -0
  177. package/src/tools/local/resolveLocalExecutionTools.ts +208 -0
  178. package/src/tools/local/syntaxCheck.ts +243 -0
  179. package/src/tools/local/textEncoding.ts +37 -0
  180. package/src/tools/local/workspaceFS.ts +89 -0
  181. package/src/tools/subagent/SubagentExecutor.ts +60 -0
  182. package/src/types/hitl.ts +56 -27
  183. package/src/types/run.ts +12 -1
  184. package/src/types/summarize.ts +31 -0
  185. 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
+ };
@@ -40,6 +40,35 @@ export type SubagentExecuteParams = {
40
40
  * without relying on event ordering heuristics.
41
41
  */
42
42
  parentToolCallId?: string;
43
+ /**
44
+ * Snapshot of the parent invocation's `config.configurable` at the
45
+ * spawn-tool call site. Inherited verbatim into the child workflow's
46
+ * `configurable` so host-set fields (`requestBody`, `user`,
47
+ * `userMCPAuthMap`, etc.) propagate — fixing MCP body-placeholder
48
+ * substitution and per-user lookups for subagent tool calls.
49
+ *
50
+ * Inheritance details (verified empirically against LangGraph):
51
+ * - host-set keys propagate as-is into the child's tool dispatches;
52
+ * - `thread_id` propagates (with `childRunId` as a fallback when
53
+ * parent did not supply one) — matches the "subagent is part of
54
+ * the same conversation" mental model and aligns with the
55
+ * `sessionId: this.parentRunId` convention this executor already
56
+ * uses for `SubagentStart` / `SubagentStop` hooks;
57
+ * - `parent_run_id` propagates when the host put it on parent's
58
+ * configurable;
59
+ * - `run_id` is *overwritten by the LangGraph runtime* at child
60
+ * invoke time regardless of what we forward — child's tool
61
+ * dispatches see the child graph's runtime runId in
62
+ * `configurable.run_id`, not the parent's. Hosts that need
63
+ * parent-scoped run identity for downstream consumers should
64
+ * plumb it via a host-defined key (e.g. `requestBody.messageId`),
65
+ * not `run_id`.
66
+ *
67
+ * A future revision will likely make this inheritance configurable
68
+ * per spawn type — background / async subagents may want isolation
69
+ * rather than sharing parent's host context.
70
+ */
71
+ parentConfigurable?: Record<string, unknown>;
43
72
  };
44
73
 
45
74
  export type SubagentExecuteResult = {
@@ -246,6 +275,36 @@ export class SubagentExecutor {
246
275
  * nested trace pollution).
247
276
  */
248
277
  const callbacks: Callbacks = forwarder ? [forwarder] : [];
278
+ /**
279
+ * Inherit the parent's `configurable` verbatim — host-set fields
280
+ * (`requestBody`, `user`, `userMCPAuthMap`, etc.) AND the run-
281
+ * identity fields (`run_id`, `parent_run_id`, `thread_id`) all
282
+ * propagate.
283
+ *
284
+ * Run-identity propagation is intentional and matches the
285
+ * convention this executor itself already uses for `SubagentStart`
286
+ * / `SubagentStop` hooks (`sessionId: this.parentRunId`): the
287
+ * subagent runs under the parent's session scope, not its own.
288
+ * Forwarding `run_id` / `parent_run_id` / `thread_id` makes
289
+ * `ToolNode`'s hook lookups (`hasHookFor(eventName, runId)`),
290
+ * `ToolOutputReferenceRegistry` keying, and trace lineage all
291
+ * resolve to the parent's session for tools dispatched from the
292
+ * subagent — so `PreToolUse` / `PostToolUse` hooks the host
293
+ * registered against the parent's run fire for subagent tool
294
+ * calls too. "Same run" matches the user-perceptual mental model.
295
+ *
296
+ * `thread_id` falls back to `childRunId` only when the parent
297
+ * didn't supply one (legacy behavior preserved for hosts that
298
+ * never set thread_id).
299
+ *
300
+ * NOTE: a future revision will likely make this configurable per
301
+ * spawn type — e.g. a background / async subagent that runs after
302
+ * the parent's run completes wants isolation, not inheritance.
303
+ * For now the inheritance path matches LibreChat's primary use
304
+ * case (synchronous subagents within a single user turn).
305
+ */
306
+ const inheritedConfigurable: Record<string, unknown> =
307
+ params.parentConfigurable ?? {};
249
308
  result = await workflow.invoke(
250
309
  { messages: [new HumanMessage(description)] },
251
310
  {
@@ -255,6 +314,7 @@ export class SubagentExecutor {
255
314
  runName: `subagent:${subagentType}`,
256
315
  configurable: {
257
316
  thread_id: childRunId,
317
+ ...inheritedConfigurable,
258
318
  },
259
319
  }
260
320
  );
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 {