@leuduan/lagent 0.0.0-stage → 44.0.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 (328) hide show
  1. package/LICENSE +16 -0
  2. package/README.md +157 -2
  3. package/dist/chunks/2GCIWSOA.js +5 -0
  4. package/dist/chunks/2P47AHNK.js +7 -0
  5. package/dist/chunks/34KW6VNQ.js +1 -0
  6. package/dist/chunks/3UMZMPWW.js +1 -0
  7. package/dist/chunks/5IRF47YB.js +1 -0
  8. package/dist/chunks/6DRCKZ2C.js +1 -0
  9. package/dist/chunks/6FRNHMCV.js +1 -0
  10. package/dist/chunks/7LI64H5X.js +17 -0
  11. package/dist/chunks/7VAPNE3L.js +1 -0
  12. package/dist/chunks/ASSQZH3F.js +1 -0
  13. package/dist/chunks/B4FXK5ER.js +77 -0
  14. package/dist/chunks/BBCW2FZK.js +1 -0
  15. package/dist/chunks/BPRCULZH.js +1 -0
  16. package/dist/chunks/CANPZYTH.js +24 -0
  17. package/dist/chunks/CQEVREBT.js +1 -0
  18. package/dist/chunks/CUK7CGC7.js +1 -0
  19. package/dist/chunks/D4PQP5MA.js +3 -0
  20. package/dist/chunks/DDANMY45.js +3 -0
  21. package/dist/chunks/DDZ7YF26.js +1 -0
  22. package/dist/chunks/EMRB4KCN.js +6 -0
  23. package/dist/chunks/EPYQ5M3B.js +3 -0
  24. package/dist/chunks/FJLU4UH3.js +1 -0
  25. package/dist/chunks/G44LG6VL.js +1 -0
  26. package/dist/chunks/GGSVIIWJ.js +2 -0
  27. package/dist/chunks/HK6ZSONF.js +1 -0
  28. package/dist/chunks/IAAEP6VL.js +30 -0
  29. package/dist/chunks/JCTBX33H.js +36 -0
  30. package/dist/chunks/JFX2MXBA.js +4 -0
  31. package/dist/chunks/JFZZXVWD.js +15 -0
  32. package/dist/chunks/KUZCL2SH.js +1 -0
  33. package/dist/chunks/MHC6VPB4.js +7 -0
  34. package/dist/chunks/MMNNZCBO.js +5 -0
  35. package/dist/chunks/MNJ4SRF7.js +1 -0
  36. package/dist/chunks/N6IXG2VL.js +1 -0
  37. package/dist/chunks/OCBPT7KE.js +1 -0
  38. package/dist/chunks/OL5A6ITQ.js +1 -0
  39. package/dist/chunks/PUSUPTAT.js +1 -0
  40. package/dist/chunks/Q75FUN7D.js +2 -0
  41. package/dist/chunks/Q7S5LFGN.js +1 -0
  42. package/dist/chunks/RCIHV3KG.js +1 -0
  43. package/dist/chunks/S34ZA2R6.js +1 -0
  44. package/dist/chunks/SS3B5W34.js +12 -0
  45. package/dist/chunks/VYMT4TXD.js +1 -0
  46. package/dist/chunks/W57RW55W.js +1 -0
  47. package/dist/compositions/base.d.ts +11 -0
  48. package/dist/compositions/codex.d.ts +66 -0
  49. package/dist/compositions/codexPrompts.d.ts +56 -0
  50. package/dist/context.d.ts +109 -0
  51. package/dist/index.d.ts +55 -0
  52. package/dist/index.js +1 -0
  53. package/dist/journal/catalog.d.ts +112 -0
  54. package/dist/journal/forkChain.d.ts +16 -0
  55. package/dist/journal/index.d.ts +15 -0
  56. package/dist/journal/journal.d.ts +475 -0
  57. package/dist/journal/projection.d.ts +87 -0
  58. package/dist/journal/store.d.ts +181 -0
  59. package/dist/llm/client.d.ts +135 -0
  60. package/dist/llm/content.d.ts +52 -0
  61. package/dist/llm/failure.d.ts +82 -0
  62. package/dist/llm/index.d.ts +22 -0
  63. package/dist/llm/index.js +1 -0
  64. package/dist/llm/primitives.d.ts +286 -0
  65. package/dist/llm/prompt.d.ts +21 -0
  66. package/dist/llm/provider.d.ts +74 -0
  67. package/dist/llm/readLedger.d.ts +24 -0
  68. package/dist/llm/request.d.ts +58 -0
  69. package/dist/loop/agent.d.ts +18 -0
  70. package/dist/loop/authorization.d.ts +41 -0
  71. package/dist/loop/auxiliary.d.ts +58 -0
  72. package/dist/loop/chunks.d.ts +27 -0
  73. package/dist/loop/completion.d.ts +17 -0
  74. package/dist/loop/errors.d.ts +139 -0
  75. package/dist/loop/hooks.d.ts +274 -0
  76. package/dist/loop/index.d.ts +16 -0
  77. package/dist/loop/runner.d.ts +134 -0
  78. package/dist/loop/runtime.d.ts +76 -0
  79. package/dist/loop/strategy.d.ts +80 -0
  80. package/dist/loop/surfacePrepare.d.ts +30 -0
  81. package/dist/loop/tool.d.ts +106 -0
  82. package/dist/loop/toolCalls.d.ts +35 -0
  83. package/dist/plugin.d.ts +50 -0
  84. package/dist/plugin.js +1 -0
  85. package/dist/plugins/agentsMd.d.ts +80 -0
  86. package/dist/plugins/agentsMd.js +1 -0
  87. package/dist/plugins/ask/index.d.ts +26 -0
  88. package/dist/plugins/ask/index.js +1 -0
  89. package/dist/plugins/ask/service.d.ts +125 -0
  90. package/dist/plugins/ask/tools.d.ts +9 -0
  91. package/dist/plugins/commands/index.d.ts +43 -0
  92. package/dist/plugins/commands/index.js +1 -0
  93. package/dist/plugins/commands/output.d.ts +115 -0
  94. package/dist/plugins/commands/record.d.ts +558 -0
  95. package/dist/plugins/commands/service.d.ts +321 -0
  96. package/dist/plugins/commands/shell.d.ts +25 -0
  97. package/dist/plugins/commands/tools.d.ts +10 -0
  98. package/dist/plugins/compaction/checkpoint.d.ts +94 -0
  99. package/dist/plugins/compaction/config.d.ts +133 -0
  100. package/dist/plugins/compaction/estimate.d.ts +37 -0
  101. package/dist/plugins/compaction/index.d.ts +87 -0
  102. package/dist/plugins/compaction/index.js +7 -0
  103. package/dist/plugins/compaction/planner.d.ts +207 -0
  104. package/dist/plugins/compaction/prune.d.ts +9 -0
  105. package/dist/plugins/compaction/record.d.ts +227 -0
  106. package/dist/plugins/compaction/segments.d.ts +23 -0
  107. package/dist/plugins/compaction/state.d.ts +68 -0
  108. package/dist/plugins/compaction/summarize.d.ts +13 -0
  109. package/dist/plugins/dateAndBranch.d.ts +47 -0
  110. package/dist/plugins/dateAndBranch.js +1 -0
  111. package/dist/plugins/delegations/answers.d.ts +39 -0
  112. package/dist/plugins/delegations/assignmentKey.d.ts +22 -0
  113. package/dist/plugins/delegations/cleanup.d.ts +161 -0
  114. package/dist/plugins/delegations/constraint.d.ts +76 -0
  115. package/dist/plugins/delegations/index.d.ts +39 -0
  116. package/dist/plugins/delegations/index.js +1 -0
  117. package/dist/plugins/delegations/notices.d.ts +261 -0
  118. package/dist/plugins/delegations/obligations.d.ts +210 -0
  119. package/dist/plugins/delegations/record.d.ts +633 -0
  120. package/dist/plugins/delegations/request.d.ts +384 -0
  121. package/dist/plugins/delegations/role.d.ts +30 -0
  122. package/dist/plugins/delegations/service.d.ts +563 -0
  123. package/dist/plugins/delegations/standing.d.ts +19 -0
  124. package/dist/plugins/delegations/stopCheck.d.ts +21 -0
  125. package/dist/plugins/delegations/tools.d.ts +71 -0
  126. package/dist/plugins/delegations/waitContracts.d.ts +118 -0
  127. package/dist/plugins/delegations/waitResolver.d.ts +116 -0
  128. package/dist/plugins/diagnostics/digest.d.ts +14 -0
  129. package/dist/plugins/diagnostics/index.d.ts +68 -0
  130. package/dist/plugins/diagnostics/index.js +1 -0
  131. package/dist/plugins/fileTools/config.d.ts +10 -0
  132. package/dist/plugins/fileTools/editFiles.d.ts +117 -0
  133. package/dist/plugins/fileTools/fsRead.d.ts +17 -0
  134. package/dist/plugins/fileTools/gitDiscovery.d.ts +9 -0
  135. package/dist/plugins/fileTools/index.d.ts +18 -0
  136. package/dist/plugins/fileTools/index.js +1 -0
  137. package/dist/plugins/fileTools/ripgrep.d.ts +20 -0
  138. package/dist/plugins/fileTools/textEdit.d.ts +67 -0
  139. package/dist/plugins/fileTools/tools.d.ts +6 -0
  140. package/dist/plugins/historyRecall/history.d.ts +9 -0
  141. package/dist/plugins/historyRecall/index.d.ts +31 -0
  142. package/dist/plugins/historyRecall/index.js +1 -0
  143. package/dist/plugins/historyRecall/render.d.ts +34 -0
  144. package/dist/plugins/historyRecall/tools.d.ts +4 -0
  145. package/dist/plugins/mcp/catalog.d.ts +36 -0
  146. package/dist/plugins/mcp/config.d.ts +63 -0
  147. package/dist/plugins/mcp/connection.d.ts +53 -0
  148. package/dist/plugins/mcp/errors.d.ts +32 -0
  149. package/dist/plugins/mcp/index.d.ts +33 -0
  150. package/dist/plugins/mcp/index.js +5 -0
  151. package/dist/plugins/mcp/mount.d.ts +98 -0
  152. package/dist/plugins/mcp/names.d.ts +25 -0
  153. package/dist/plugins/mcp/render.d.ts +29 -0
  154. package/dist/plugins/mcp/slots.d.ts +15 -0
  155. package/dist/plugins/mcp/stdio.d.ts +49 -0
  156. package/dist/plugins/mcp/tools.d.ts +41 -0
  157. package/dist/plugins/mcp/version.d.ts +2 -0
  158. package/dist/plugins/routeNotice.d.ts +22 -0
  159. package/dist/plugins/routeNotice.js +1 -0
  160. package/dist/plugins/sandbox/absent.d.ts +33 -0
  161. package/dist/plugins/sandbox/client.d.ts +39 -0
  162. package/dist/plugins/sandbox/files.d.ts +28 -0
  163. package/dist/plugins/sandbox/index.d.ts +52 -0
  164. package/dist/plugins/sandbox/index.js +4 -0
  165. package/dist/plugins/sandbox/mode.d.ts +57 -0
  166. package/dist/plugins/sandbox/policy.d.ts +163 -0
  167. package/dist/plugins/sandbox/protocol.d.ts +107 -0
  168. package/dist/plugins/sandbox/service.d.ts +153 -0
  169. package/dist/plugins/sandbox/worker.d.ts +12 -0
  170. package/dist/plugins/sandbox/worker.js +1 -0
  171. package/dist/plugins/schedules/index.d.ts +21 -0
  172. package/dist/plugins/schedules/index.js +5 -0
  173. package/dist/plugins/schedules/record.d.ts +396 -0
  174. package/dist/plugins/schedules/service.d.ts +120 -0
  175. package/dist/plugins/schedules/time.d.ts +26 -0
  176. package/dist/plugins/schedules/tools.d.ts +6 -0
  177. package/dist/plugins/skills/discovery.d.ts +83 -0
  178. package/dist/plugins/skills/index.d.ts +31 -0
  179. package/dist/plugins/skills/index.js +1 -0
  180. package/dist/plugins/skills/tools.d.ts +11 -0
  181. package/dist/plugins/subAgents/availableRoutes.d.ts +53 -0
  182. package/dist/plugins/subAgents/index.d.ts +67 -0
  183. package/dist/plugins/subAgents/index.js +1 -0
  184. package/dist/plugins/subAgents/tools.d.ts +67 -0
  185. package/dist/plugins/threadDirs.d.ts +36 -0
  186. package/dist/plugins/threadDirs.js +1 -0
  187. package/dist/plugins/toolPolicy/index.d.ts +61 -0
  188. package/dist/plugins/toolPolicy/index.js +1 -0
  189. package/dist/plugins/truncateToolOutput.d.ts +30 -0
  190. package/dist/plugins/truncateToolOutput.js +6 -0
  191. package/dist/plugins/usage/index.d.ts +26 -0
  192. package/dist/plugins/usage/index.js +1 -0
  193. package/dist/plugins/usage/tracker.d.ts +34 -0
  194. package/dist/plugins/verify/index.d.ts +27 -0
  195. package/dist/plugins/verify/index.js +6 -0
  196. package/dist/plugins/verify/judgement.d.ts +16 -0
  197. package/dist/plugins/verify/record.d.ts +382 -0
  198. package/dist/plugins/verify/service.d.ts +93 -0
  199. package/dist/plugins/verify/state.d.ts +58 -0
  200. package/dist/plugins/webTools/html.d.ts +23 -0
  201. package/dist/plugins/webTools/index.d.ts +34 -0
  202. package/dist/plugins/webTools/index.js +1 -0
  203. package/dist/plugins/webTools/network.d.ts +23 -0
  204. package/dist/plugins/webTools/web.d.ts +32 -0
  205. package/dist/plugins/workflows/assignment.d.ts +20 -0
  206. package/dist/plugins/workflows/cleanup.d.ts +38 -0
  207. package/dist/plugins/workflows/definition.d.ts +184 -0
  208. package/dist/plugins/workflows/entryPlan.d.ts +95 -0
  209. package/dist/plugins/workflows/hostBindings.d.ts +146 -0
  210. package/dist/plugins/workflows/index.d.ts +28 -0
  211. package/dist/plugins/workflows/index.js +11 -0
  212. package/dist/plugins/workflows/observations.d.ts +81 -0
  213. package/dist/plugins/workflows/participantModels.d.ts +28 -0
  214. package/dist/plugins/workflows/participants.d.ts +143 -0
  215. package/dist/plugins/workflows/policy.d.ts +112 -0
  216. package/dist/plugins/workflows/record.d.ts +2849 -0
  217. package/dist/plugins/workflows/routing.d.ts +116 -0
  218. package/dist/plugins/workflows/schema.d.ts +26 -0
  219. package/dist/plugins/workflows/service.d.ts +477 -0
  220. package/dist/plugins/workflows/state.d.ts +373 -0
  221. package/dist/plugins/workflows/stopCheck.d.ts +13 -0
  222. package/dist/plugins/workflows/tools.d.ts +13 -0
  223. package/dist/plugins/workflows/view.d.ts +103 -0
  224. package/dist/plugins/workflows/workStatus.d.ts +21 -0
  225. package/dist/plugins/workspaceNotice.d.ts +36 -0
  226. package/dist/plugins/workspaceNotice.js +1 -0
  227. package/dist/providers/anthropic.d.ts +178 -0
  228. package/dist/providers/anthropicMessages.d.ts +66 -0
  229. package/dist/providers/codex.d.ts +71 -0
  230. package/dist/providers/codexAuth.d.ts +116 -0
  231. package/dist/providers/deepseek.d.ts +59 -0
  232. package/dist/providers/failure.d.ts +23 -0
  233. package/dist/providers/gateway.d.ts +24 -0
  234. package/dist/providers/index.d.ts +18 -0
  235. package/dist/providers/index.js +6 -0
  236. package/dist/providers/openaiChat.d.ts +73 -0
  237. package/dist/providers/openaiResponses.d.ts +109 -0
  238. package/dist/providers/opencodeCopilot.d.ts +23 -0
  239. package/dist/providers/opencodeGo.d.ts +109 -0
  240. package/dist/providers/shared.d.ts +112 -0
  241. package/dist/providers/stream.d.ts +156 -0
  242. package/dist/records/index.d.ts +13 -0
  243. package/dist/records/index.js +1 -0
  244. package/dist/records/journal.d.ts +2968 -0
  245. package/dist/records/toolResult.d.ts +65 -0
  246. package/dist/services/agents.d.ts +62 -0
  247. package/dist/services/catalogContributions.d.ts +73 -0
  248. package/dist/services/confinement/files.d.ts +118 -0
  249. package/dist/services/confinement/service.d.ts +219 -0
  250. package/dist/services/core.d.ts +28 -0
  251. package/dist/services/extensionEvents.d.ts +181 -0
  252. package/dist/services/journal.d.ts +26 -0
  253. package/dist/services/llm.d.ts +82 -0
  254. package/dist/services/modes.d.ts +65 -0
  255. package/dist/services/plugins.d.ts +34 -0
  256. package/dist/services/presets/definition.d.ts +154 -0
  257. package/dist/services/presets/installations.d.ts +139 -0
  258. package/dist/services/prompts.d.ts +85 -0
  259. package/dist/services/threads/activation.d.ts +100 -0
  260. package/dist/services/threads/admission.d.ts +174 -0
  261. package/dist/services/threads/birth.d.ts +219 -0
  262. package/dist/services/threads/index.d.ts +14 -0
  263. package/dist/services/threads/runEvents.d.ts +79 -0
  264. package/dist/services/threads/runOwner.d.ts +368 -0
  265. package/dist/services/threads/service.d.ts +450 -0
  266. package/dist/services/threads/thread.d.ts +268 -0
  267. package/dist/services/threads/view.d.ts +93 -0
  268. package/dist/services/tools.d.ts +118 -0
  269. package/dist/state/birth.d.ts +62 -0
  270. package/dist/state/chain.d.ts +46 -0
  271. package/dist/state/confirmations.d.ts +64 -0
  272. package/dist/state/continuation.d.ts +167 -0
  273. package/dist/state/decide.d.ts +72 -0
  274. package/dist/state/dispatches.d.ts +54 -0
  275. package/dist/state/errors.d.ts +26 -0
  276. package/dist/state/forks.d.ts +61 -0
  277. package/dist/state/holds.d.ts +104 -0
  278. package/dist/state/inbox.d.ts +188 -0
  279. package/dist/state/index.d.ts +31 -0
  280. package/dist/state/inputs.d.ts +58 -0
  281. package/dist/state/installations.d.ts +28 -0
  282. package/dist/state/limits.d.ts +14 -0
  283. package/dist/state/prompt.d.ts +13 -0
  284. package/dist/state/recovery.d.ts +7 -0
  285. package/dist/state/replace.d.ts +33 -0
  286. package/dist/state/requests.d.ts +129 -0
  287. package/dist/state/route.d.ts +61 -0
  288. package/dist/state/summary.d.ts +16 -0
  289. package/dist/state/surface.d.ts +153 -0
  290. package/dist/state/surfaceRead.d.ts +20 -0
  291. package/dist/state/thread.d.ts +73 -0
  292. package/dist/state/turns.d.ts +87 -0
  293. package/dist/state/waits.d.ts +38 -0
  294. package/dist/state/wake.d.ts +32 -0
  295. package/dist/stores/durableObject.d.ts +23 -0
  296. package/dist/stores/durableObject.js +1 -0
  297. package/dist/stores/durableObjectConnection.d.ts +27 -0
  298. package/dist/stores/memory.d.ts +29 -0
  299. package/dist/stores/sql.d.ts +59 -0
  300. package/dist/stores/sqlite.d.ts +31 -0
  301. package/dist/stores/sqlite.js +1 -0
  302. package/dist/sys/environment.d.ts +42 -0
  303. package/dist/sys/git.d.ts +47 -0
  304. package/dist/sys/paths.d.ts +9 -0
  305. package/dist/sys/spawn.d.ts +186 -0
  306. package/dist/testing/clock.d.ts +17 -0
  307. package/dist/testing/fakeLlm.d.ts +76 -0
  308. package/dist/testing/fedLlm.d.ts +34 -0
  309. package/dist/testing/helpers.d.ts +362 -0
  310. package/dist/testing/index.d.ts +13 -0
  311. package/dist/testing/index.js +1 -0
  312. package/dist/testing/processes.d.ts +13 -0
  313. package/dist/testing/settlement.d.ts +6 -0
  314. package/dist/testing/tmpDirs.d.ts +17 -0
  315. package/dist/util/async.d.ts +8 -0
  316. package/dist/util/diff.d.ts +25 -0
  317. package/dist/util/dispatch.d.ts +8 -0
  318. package/dist/util/errors.d.ts +28 -0
  319. package/dist/util/foldState.d.ts +17 -0
  320. package/dist/util/freeze.d.ts +2 -0
  321. package/dist/util/ids.d.ts +2 -0
  322. package/dist/util/json.d.ts +37 -0
  323. package/dist/util/jsonSchema.d.ts +7 -0
  324. package/dist/util/log.d.ts +6 -0
  325. package/dist/util/retries.d.ts +25 -0
  326. package/dist/util/text.d.ts +21 -0
  327. package/dist/util/timers.d.ts +43 -0
  328. package/package.json +183 -3
@@ -0,0 +1,49 @@
1
+ import type { Transport } from '@modelcontextprotocol/sdk/shared/transport.js';
2
+ import type { JSONRPCMessage } from '@modelcontextprotocol/sdk/types.js';
3
+ import { type SpawnResult } from '../../sys/spawn.js';
4
+ export type StdioServerOptions = {
5
+ readonly server: string;
6
+ readonly command: readonly [string, ...string[]];
7
+ readonly cwd: string;
8
+ readonly env: Readonly<Record<string, string>>;
9
+ /** The owner: aborting it stops the server's process group within the bound. */
10
+ readonly signal: AbortSignal;
11
+ readonly killGraceMs?: number;
12
+ };
13
+ /**
14
+ * The SDK's `Transport` over a stdio server spawned through `sys/spawn`, so the server and
15
+ * everything it starts lead a process group that never outlives lagent. The SDK's own stdio
16
+ * transport spawns outside that containment.
17
+ *
18
+ * stdout carries newline-delimited JSON-RPC and nothing else. stderr is diagnostics: it is read
19
+ * from the start, since an unread pipe would block the server, and only a bounded tail is kept,
20
+ * for the host's errors and the debug log.
21
+ */
22
+ export declare class ContainedStdioTransport implements Transport {
23
+ onclose?: () => void;
24
+ onerror?: (error: Error) => void;
25
+ onmessage?: <T extends JSONRPCMessage>(message: T) => void;
26
+ private spawned;
27
+ private readonly buffer;
28
+ private stderr;
29
+ private exit;
30
+ private closed;
31
+ /** `close` came before the process existed: the process is stopped as soon as it does. */
32
+ private closedEarly;
33
+ private readonly options;
34
+ constructor(options: StdioServerOptions);
35
+ /** The server's pid, which is also its process group's id, once started. */
36
+ get pid(): number | undefined;
37
+ /** The last of what the server wrote to stderr. */
38
+ get stderrTail(): string;
39
+ /** How the server's process ended, once it has. */
40
+ get exited(): SpawnResult | null;
41
+ start(): Promise<void>;
42
+ private receive;
43
+ send(message: JSONRPCMessage): Promise<void>;
44
+ /**
45
+ * The spec's stdio shutdown: close the server's input, give it one grace period to exit, then
46
+ * stop its whole group, SIGTERM then SIGKILL, within `sys/spawn`'s bound.
47
+ */
48
+ close(): Promise<void>;
49
+ }
@@ -0,0 +1,41 @@
1
+ import type { Tool } from '../../loop/index.js';
2
+ import type { InterruptionPolicy } from '../../records/journal.js';
3
+ import type { CatalogEntry } from './catalog.js';
4
+ import type { McpConfig } from './config.js';
5
+ import { type Generation } from './connection.js';
6
+ import type { CallSlots } from './slots.js';
7
+ /**
8
+ * The `_meta` key carrying a call's durable identity: the thread id, an opaque identifier with no
9
+ * meaning outside lagent's journal, and the `tool/call` sequence. Stable across a re-dispatch after
10
+ * a crash, so a server that chooses to can deduplicate; servers that do not know it ignore it.
11
+ */
12
+ export declare const CALL_META_KEY = "work.leuduan.lagent/call";
13
+ /** Told with the first result after a new session. */
14
+ export declare const SESSION_RESET_NOTE = "The connection to this MCP server was re-established; any session state the server held may have been reset.";
15
+ /** What a tool needs from the mount that published it. */
16
+ export interface McpToolHost {
17
+ readonly server: string;
18
+ readonly config: Pick<McpConfig, 'maxCallSeconds' | 'callTimeoutSeconds' | 'maxContentBytes' | 'onInterrupted'>;
19
+ readonly slots: CallSlots;
20
+ /** The live generation, reconnecting once first if the last one went away. */
21
+ ready(signal: AbortSignal): Promise<Generation>;
22
+ /** The current catalog's entry under a lagent name. */
23
+ entry(name: string): CatalogEntry | undefined;
24
+ /** The note owed to the first result of `generation`, once. */
25
+ takeSessionNote(generation: Generation): string | null;
26
+ /** Ends a generation whose session the server no longer has. */
27
+ invalidate(generation: Generation): void;
28
+ }
29
+ /**
30
+ * What a re-dispatch may do with a call to `entry` after an interruption, by the mount's
31
+ * `onInterrupted`: `'report'` for every tool, the listed remote names for `{ rerun }`, and for
32
+ * `'annotations'` the server's own `readOnlyHint` or `idempotentHint`, trusted only because the
33
+ * mount asked for that.
34
+ */
35
+ export declare function interruptionPolicyOf(onInterrupted: McpConfig['onInterrupted'], entry: Pick<CatalogEntry, 'remoteName' | 'definition'>): InterruptionPolicy;
36
+ /**
37
+ * One MCP tool as a lagent tool, bound to the definition it was published with. Arguments go to
38
+ * the server unchanged: validating them is the server's job. The mount's `onInterrupted` decides
39
+ * what a re-dispatch may do; the server's annotations count only when the mount says so.
40
+ */
41
+ export declare const mcpTool: (host: McpToolHost, entry: CatalogEntry) => Tool;
@@ -0,0 +1,2 @@
1
+ /** The package version, for client/server identification strings. */
2
+ export declare function lagentVersion(): string;
@@ -0,0 +1,22 @@
1
+ import type { Plugin } from '@leuduan/cordis';
2
+ import { type ModelRoute } from '../llm/provider.js';
3
+ /**
4
+ * Tells an agent the route it runs on: provider, model and thinking effort.
5
+ *
6
+ * Told as a conversation message at the next model step already due whenever the turn's route
7
+ * differs from the latest notice the model can still see: on its first step, after a person
8
+ * switches its model, when a turn falls back to another provider or returns from one, and after a
9
+ * replacement took the notice that said it off the surface. The route is the one the turn pinned,
10
+ * which request middleware cannot change, so it names what lagent sends. A step whose route did not
11
+ * resolve is told nothing; its model call fails anyway. A fork inherits what its source was told and
12
+ * runs on its source's route, so it is told nothing unless the route moved.
13
+ *
14
+ * Not a prompt section: the recorded prompt must not change while the thread lives, and the route
15
+ * does. Mounted on the root it tells every thread; mounted through `thread.ctx` it tells that thread
16
+ * only.
17
+ */
18
+ export declare const routeNotice: Plugin.Object<undefined>;
19
+ /** What a thread is told: its route, and the one it replaces when the model can still see another. */
20
+ export declare function renderRouteNotice(route: ModelRoute, told: ModelRoute | null): string;
21
+ /** A route as a model reads it; an effort of `null` is the provider's default. */
22
+ export declare function renderRoute(route: ModelRoute): string;
@@ -0,0 +1 @@
1
+ import{a,b,c}from"../chunks/OCBPT7KE.js";import"../chunks/6FRNHMCV.js";import"../chunks/CUK7CGC7.js";import"../chunks/BPRCULZH.js";import"../chunks/DDANMY45.js";import"../chunks/BBCW2FZK.js";import"../chunks/MMNNZCBO.js";import"../chunks/G44LG6VL.js";import"../chunks/D4PQP5MA.js";export{c as renderRoute,b as renderRouteNotice,a as routeNotice};
@@ -0,0 +1,33 @@
1
+ import type { Plugin } from '@leuduan/cordis';
2
+ import { z } from 'zod';
3
+ declare const AbsentConfigSchema: z.ZodObject<{
4
+ drainTimeoutMs: z.ZodNumber;
5
+ }, z.core.$strict>;
6
+ export type SandboxAbsentConfig = z.input<typeof AbsentConfigSchema>;
7
+ /**
8
+ * For a host that records sandbox modes but started without the sandbox. A process with no launch
9
+ * handler would run every command plain, so a conversation recorded as confined would run unconfined
10
+ * silently after such a restart. This is the confinement handler of core's `ctx.confinement` for that
11
+ * host, which is what makes the refusal cover every caller of the seam, `commands`, the search tools
12
+ * and a host calling `ctx.confinement` directly alike: an operation for a conversation recorded as
13
+ * confined is refused with a message for the model, and a launch for one recorded as unconfined, or
14
+ * with no mode at all, runs plain (`/bin/sh -c` for shell text, the argv itself otherwise) with
15
+ * provenance that says so (`unconfined`, under this plugin's own launcher),
16
+ * which a host's display marks. The model is told through the sandbox's notice: the unavailable
17
+ * guidance when confined, and the lift a restart may have left owed otherwise.
18
+ *
19
+ * The refusal happens at the operation's first use, after any confirmation the tool call needed, so
20
+ * a call a person approved can still be refused: rare, since the notice says so at the first step.
21
+ * It does not cover what spawns outside the seam: MCP stdio servers and lagent's own spawns.
22
+ *
23
+ * The recorded mode is read once, at the operation's first use, from the journal's fold; core holds
24
+ * the histories it is decided from for the snapshot. A thread the journal does not hold, a caller
25
+ * chain that does not reach its conversation root and a mode row this build cannot read refuse the
26
+ * operation with that error, never allow it.
27
+ *
28
+ * Mount it unscoped, after `core` and before threads start, so a failed composition is refused before
29
+ * recovery. It excludes `sandbox` in either mount order. Disposing it releases its handler, which
30
+ * stops every command it let run, within `drainTimeoutMs`.
31
+ */
32
+ export declare const sandboxAbsent: Plugin.Object<SandboxAbsentConfig>;
33
+ export {};
@@ -0,0 +1,39 @@
1
+ import type { WorkerMessage, WorkerValues } from './protocol.js';
2
+ /**
3
+ * The main thread's end of the sandbox worker: it owns the thread and every request in flight. A
4
+ * request has an id and a deadline; a worker that errors or exits rejects every request at once and
5
+ * tells `onDeath` once, and a reply that arrives for a request already settled is dropped. Nothing
6
+ * here knows what the messages mean.
7
+ */
8
+ export declare class WorkerClient {
9
+ private readonly worker;
10
+ private readonly pending;
11
+ private nextId;
12
+ private ended;
13
+ private closing;
14
+ private readonly onDeath;
15
+ /**
16
+ * Starts the worker with exactly `env`, which it never inherits. `onDeath` runs once when the
17
+ * worker ends other than through {@link terminate}.
18
+ */
19
+ constructor(entry: URL | string, env: Record<string, string>, onDeath: (error: Error) => void);
20
+ get alive(): boolean;
21
+ /**
22
+ * Sends `message` and resolves with its reply. Rejects at `timeoutMs`, when `signal` aborts, or
23
+ * when the worker ends; the first two also ask the worker to abandon the work.
24
+ */
25
+ request<M extends WorkerMessage & {
26
+ type: keyof WorkerValues;
27
+ }>(message: M, options: {
28
+ timeoutMs: number;
29
+ signal?: AbortSignal;
30
+ }): Promise<WorkerValues[M['type']]>;
31
+ /** Ends the worker on purpose: pending requests reject, and `onDeath` is not told. */
32
+ terminate(): Promise<void>;
33
+ /** Lets the process exit while the worker still runs: for a worker kept after a failed drain. */
34
+ unref(): void;
35
+ private send;
36
+ private settle;
37
+ private end;
38
+ private rejectAll;
39
+ }
@@ -0,0 +1,28 @@
1
+ /**
2
+ * What the sandbox keeps on disk under its temporary root, which no command may write: the
3
+ * worker's private directory and the per-key scratch directories. Created by the host, never by a
4
+ * confined process, and never through a link.
5
+ */
6
+ /** The directory the worker runs in: sandbox-runtime's socket files and the published Java agent. */
7
+ export declare const privateDirectory: (tmpRoot: string) => string;
8
+ /** A key's temporary directory. The hash gives every key one safe, fixed-length segment of its own. */
9
+ export declare const scratchPath: (tmpRoot: string, key: string) => string;
10
+ /** `<tmpRoot>` and `<tmpRoot>/.srt`, created. */
11
+ export declare function ensurePrivateDirectory(tmpRoot: string): string;
12
+ /** The scratch directory of `key`, created. */
13
+ export declare function ensureScratchDirectory(tmpRoot: string, key: string): string;
14
+ /**
15
+ * Removes the socket files sandbox-runtime left in `directory` for host processes that are gone,
16
+ * which a SIGKILL leaves behind, and nothing else. Returns what it removed.
17
+ */
18
+ export declare function removeDeadSockets(directory: string): string[];
19
+ /**
20
+ * Publishes the Java agent into `directory` as `srt-proxy-agent-<sha256>.jar`, the one a JVM in the
21
+ * sandbox loads. Several hosts may share a temporary root, so a name is never rewritten: the bytes
22
+ * go to a temporary name and are linked into place, which fails rather than replace, and a file
23
+ * already there is reused only after its digest checks.
24
+ */
25
+ export declare function publishJar(directory: string, source: string): {
26
+ path: string;
27
+ sha256: string;
28
+ };
@@ -0,0 +1,52 @@
1
+ import type { Plugin } from '@leuduan/cordis';
2
+ import { type SandboxConfigInput } from './service.js';
3
+ export { sandboxAbsent, type SandboxAbsentConfig } from './absent.js';
4
+ export { sandboxRecorded, sandboxRecordedOn, type SandboxMode } from './mode.js';
5
+ export { SandboxConfigSchema, type SandboxConfigInput, type SandboxHealth, type SandboxService } from './service.js';
6
+ /**
7
+ * An optional OS sandbox for `run_command` and `start_command`, through Anthropic's sandbox-runtime
8
+ * (`@anthropic-ai/sandbox-runtime`, an optional peer dependency: only the worker module loads it).
9
+ * Mounting `sandbox` registers it as the confinement handler of core's `ctx.confinement`, so everything
10
+ * `commands` launches afterwards goes through it, and each confined launch is limited to writing its thread's working directory, the host's
11
+ * extra roots and a per-thread temporary directory, and, in `allowlist` network mode, to reaching the
12
+ * host's allowed domains (`unrestricted` leaves the network open: only the filesystem rules bind). A
13
+ * host mounts it before anything launches: once it is registered, no launch runs without it, though a
14
+ * conversation set to run unconfined launches plain through it.
15
+ *
16
+ * Each conversation is confined or unconfined, as its durable mode says: `ctx.sandbox.opening(mode)`
17
+ * records the first choice in the write that creates it, `ctx.sandbox.setMode` switches it, and
18
+ * `ctx.sandbox.mode` is the one effective value, which the host's `unconfined: 'refused'` forces to
19
+ * `confined`. `sandboxRecorded` reads what the journal says, mounted sandbox or not. A host that
20
+ * records modes but starts without the sandbox mounts `sandboxAbsent` instead: the two exclude each
21
+ * other.
22
+ *
23
+ * sandbox-runtime runs on a worker thread of the host, started once per process. The host says
24
+ * where the worker is (`sandboxWorker` from `dist`) and which Java agent to publish
25
+ * (`srtJavaAgentJar()` from `node_modules`); a compiled host embeds both itself.
26
+ *
27
+ * ```ts
28
+ * await root.plugin(sandbox, {
29
+ * worker: sandboxWorker,
30
+ * searchPath: '/usr/bin:/bin:/usr/sbin:/sbin',
31
+ * tmpRoot,
32
+ * javaAgentJar: srtJavaAgentJar(),
33
+ * unconfined: 'allowed',
34
+ * filesystem,
35
+ * network: { mode: 'allowlist', allowedDomains: ['registry.npmjs.org'], deniedDomains: [], parentProxy: 'none' },
36
+ * })
37
+ * await root.plugin(commands, { environment })
38
+ * ```
39
+ *
40
+ * One filesystem policy per conversation, enforced two ways: srt confines the processes `commands` and
41
+ * the search tools launch, and the same policy is checked in process for the file tools and the readers
42
+ * of instruction files and skills, through the confinement operation's file facade. Other spawns are
43
+ * unchanged. macOS only for now.
44
+ */
45
+ export declare const sandbox: Plugin.Constructor<SandboxConfigInput>;
46
+ /** The worker entry built beside this module, for a host that runs lagent from `dist`. */
47
+ export declare const sandboxWorker: URL;
48
+ /**
49
+ * The JVM proxy agent sandbox-runtime's package ships, for a host that runs from `node_modules`.
50
+ * Resolved when called, so a host without the package fails here and not on import.
51
+ */
52
+ export declare function srtJavaAgentJar(): string;
@@ -0,0 +1,4 @@
1
+ import{a as p}from"../../chunks/VYMT4TXD.js";import{b as D,c as R,d as B,e as te}from"../../chunks/6DRCKZ2C.js";import{d as ne}from"../../chunks/7LI64H5X.js";import"../../chunks/3UMZMPWW.js";import{a as ie,b as ae}from"../../chunks/DDZ7YF26.js";import{d as N}from"../../chunks/S34ZA2R6.js";import{b as re,c as oe,e as se}from"../../chunks/6FRNHMCV.js";import"../../chunks/CUK7CGC7.js";import"../../chunks/BPRCULZH.js";import{d as E}from"../../chunks/DDANMY45.js";import{c as v}from"../../chunks/BBCW2FZK.js";import"../../chunks/MMNNZCBO.js";import"../../chunks/G44LG6VL.js";import"../../chunks/D4PQP5MA.js";import{createRequire as bn}from"node:module";import{dirname as vn,join as En}from"node:path";import{randomUUID as dn}from"node:crypto";import{homedir as hn}from"node:os";import{isAbsolute as U}from"node:path";import{Service as Pe}from"@leuduan/cordis";import{z as d}from"zod";import{Worker as He}from"node:worker_threads";var O=class{worker;pending=new Map;nextId=1;ended=null;closing=!1;onDeath;constructor(e,n,o){this.onDeath=o,this.worker=new He(e,{env:n}),this.worker.on("message",r=>this.settle(r)),this.worker.on("error",r=>this.end(new Error(`The sandbox worker failed: ${r.message}`))),this.worker.on("exit",r=>this.end(new Error(`The sandbox worker exited with code ${r}`)))}get alive(){return this.ended===null&&!this.closing}request(e,n){if(this.ended!==null)return Promise.reject(this.ended);if(this.closing)return Promise.reject(new Error("The sandbox worker is shutting down"));let{signal:o}=n;if(o?.aborted)return Promise.reject(o.reason instanceof Error?o.reason:new Error("aborted"));let r=this.nextId++;return new Promise((s,i)=>{let a=m=>{this.pending.delete(r)&&(clearTimeout(c),l(),e.type==="wrap"&&this.send({type:"cancel",id:r}),i(m))},h=()=>a(o?.reason instanceof Error?o.reason:new Error("aborted")),c=setTimeout(()=>a(new Error(`The sandbox worker did not answer ${e.type} within ${n.timeoutMs} ms`)),n.timeoutMs),l=()=>o?.removeEventListener("abort",h);o?.addEventListener("abort",h,{once:!0}),this.pending.set(r,{resolve:s,reject:i,timer:c,cleanup:l}),this.send({...e,id:r})})}async terminate(){this.closing=!0,this.rejectAll(new Error("The sandbox worker is shutting down")),await this.worker.terminate()}unref(){this.worker.unref()}send(e){try{this.worker.postMessage(e)}catch(n){this.end(new Error(`The sandbox worker could not be reached: ${n instanceof Error?n.message:n}`))}}settle(e){let n=this.pending.get(e.id);n!==void 0&&(this.pending.delete(e.id),clearTimeout(n.timer),n.cleanup(),e.ok?n.resolve(e.value):n.reject(new Error(e.error)))}end(e){this.ended===null&&(this.ended=e,this.rejectAll(e),this.closing||this.onDeath(e))}rejectAll(e){let n=[...this.pending.values()];this.pending.clear();for(let o of n)clearTimeout(o.timer),o.cleanup(),o.reject(e)}};import{createHash as I,randomUUID as Ue}from"node:crypto";import{existsSync as We,linkSync as Be,lstatSync as de,mkdirSync as he,readdirSync as Ie,readFileSync as ce,unlinkSync as ue,writeFileSync as Fe}from"node:fs";import{join as T}from"node:path";var le=t=>T(t,".srt"),G=(t,e)=>T(t,"scratch",I("sha256").update(e).digest("hex"));function F(t){try{he(t,{mode:448})}catch(n){if(!E(n,"EEXIST"))throw n}let e=de(t);if(!e.isDirectory()||e.uid!==process.getuid?.())throw new Error(`${t} is not a directory owned by this user`)}function me(t){return he(t,{recursive:!0,mode:448}),F(le(t)),le(t)}function X(t,e){F(T(t,"scratch"));let n=G(t,e);return F(n),n}function fe(t){let e=[];for(let n of Ie(t)){let o=/^srt-mux-(\d+)-[0-9a-z]+\.sock$/.exec(n);if(!(o===null||Ge(Number(o[1]))))try{ue(T(t,n)),e.push(n)}catch{}}return e}function Ge(t){if(t===process.pid)return!0;try{return process.kill(t,0),!0}catch(e){return E(e,"EPERM")}}function pe(t,e){let n=ce(e),o=I("sha256").update(n).digest("hex"),r=T(t,`srt-proxy-agent-${o}.jar`);if(!We(r)){let i=T(t,`.publish-${Ue()}.tmp`);Fe(i,n,{flag:"wx",mode:292});try{Be(i,r)}catch(a){if(!E(a,"EEXIST"))throw a}finally{ue(i)}}if(!de(r).isFile()||I("sha256").update(ce(r)).digest("hex")!==o)throw new Error(`${r} exists and is not the Java agent it is named for`);return{path:r,sha256:o}}import{createHash as ge}from"node:crypto";import{existsSync as z,lstatSync as J,opendirSync as Xe,realpathSync as ye,statSync as Je}from"node:fs";import{basename as xe,dirname as A,isAbsolute as be,join as g,relative as ze,resolve as S,sep as Ve}from"node:path";var Ye=/^(LD_.*|DYLD_.*|BASH_ENV|ENV|SHELLOPTS|BASHOPTS|BASH_FUNC_.*)$/;function ve(t){let e=Object.keys(t).filter(n=>Ye.test(n));if(e.length>0)throw new Error(`The command sandbox refuses an environment that sets ${e.join(", ")}: they run code before the sandbox holds`)}var V=128*1024;function k(t){let e=S(t),n=[];for(;!z(e)&&A(e)!==e;)n.unshift(xe(e)),e=A(e);return g(ye.native(e),...n)}var Ee=t=>({path:t,literal:!0}),Ze=/[*?[\]]/,Ke=["__GLOBSTAR_SLASH__","__GLOBSTAR__"];function Te(t){if(!be(t))throw new Error(`The command sandbox needs an absolute root, not ${t}`);let e=k(t);if(Ze.test(e)||Ke.some(n=>e.includes(n)))throw new Error(`The command sandbox refuses ${e} as a root: its name holds glob characters, which sandbox-runtime would read as a pattern`);return e}var Qe=[".gitconfig",".gitmodules",".bashrc",".bash_profile",".zshrc",".zprofile",".profile",".ripgreprc",".mcp.json",".vscode",".idea",".claude/commands",".claude/agents",".git/hooks",".git/config"];function qe(t,e){let n=[];for(let o of e)n.push(Ee(g(t,o)),`${t}/**/${o}`,`${t}/**/${o}/**`);return n}var en=["env","which","log","/bin/sh","/usr/bin/sandbox-exec"];function Ae(t){let e=t.split(":"),n=en.flatMap(o=>(o.startsWith("/")?[o]:e.map(r=>g(r,o))).filter(r=>z(r)).map(r=>ye.native(r)));return[...e,...n]}function Y(t,e,n){let o=(r,s)=>p(r,s)||p(s,r);for(let r of t){let s=e.find(i=>o(i,r));if(s!==void 0)throw new Error(`The command sandbox refuses to let commands write ${r}: it overlaps ${s}, which the sandbox runs before confinement`);if(o(n,r))throw new Error(`The command sandbox refuses to let commands write ${r}: it overlaps the temporary root`)}}var Z=(t,e)=>({spelling:t,canonical:e,foldsCase:tn(e)}),f=t=>Z(t,k(t)),nn=t=>t.replace(/[a-zA-Z]/g,e=>e===e.toLowerCase()?e.toUpperCase():e.toLowerCase());function tn(t,e=n=>J(n).dev){let n=S(t);for(;!z(n)&&A(n)!==n;)n=A(n);let o;try{Je(n).isDirectory()||(n=A(n)),o=e(n)}catch{return null}let r=(s,i)=>{let a=nn(i);if(a===i)return null;try{if(e(g(s,i))!==o)return null;let h=J(g(s,i)),c;try{c=J(g(s,a))}catch(l){return E(l,"ENOENT")?!1:null}return e(g(s,a))===o?h.ino===c.ino:null}catch{return null}};for(let s=n;;){let i=[];try{let c=Xe(s);try{for(let l=c.readSync();l!==null&&i.length<64;l=c.readSync())i.push(l.name)}finally{c.closeSync()}}catch{}for(let c of i){let l=r(s,c);if(l!==null)return l}let a=A(s);if(a===s)return null;try{if(e(a)!==o)return null}catch{return null}let h=r(a,xe(s));if(h!==null)return h;s=a}}function Se(t,e,n){return{writable:[e,...t.allowWrite,n],denyWrite:[...t.denyWrite,...t.conveniences,f("/tmp/claude"),f("/private/tmp/claude"),t.privateDirectory],denyRead:t.denyRead,protected:{root:e,suffixes:Qe}}}function ke(t){let e=n=>[...new Set(n.map(o=>o.canonical))].map(Ee);return{allowWrite:e(t.writable),denyWrite:[...e(t.denyWrite),...qe(t.protected.root.canonical,t.protected.suffixes)],denyRead:e(t.denyRead)}}var we={allowed:!0};function $e(t){let e=(r,s,i)=>{let a=s?r.normalize("NFC"):r;return i?a.toLowerCase():a},n=(r,s,i)=>{let a=i?s.foldsCase!==!1:s.foldsCase===!0,h=e(r,i,a);return p(h,e(S(s.spelling),i,a))||p(h,e(s.canonical,i,a))},o=r=>{let{root:s,suffixes:i}=t.protected,a=s.foldsCase!==!1;for(let h of[S(s.spelling),s.canonical]){let c={path:e(r,!0,a),base:e(h,!0,a)};if(!p(c.path,c.base))continue;let l=ze(c.base,c.path).split(Ve),m=i.find(x=>{let b=e(x,!0,a).split("/");return l.some((ee,w)=>b.every((_,u)=>l[w+u]===_))});if(m!==void 0)return m}return null};return{identity:ge("sha256").update(v(t.denyRead)).digest("hex"),check(r,s){if(!be(r))throw new Error(`The file policy judges absolute paths, not ${r}`);let i=S(r);if(s==="read"){let c=t.denyRead.find(l=>n(i,l,!0));return c===void 0?we:{allowed:!1,reason:`${r} is in ${c.spelling}, which this conversation may not read`}}if(!t.writable.some(c=>n(i,c,!1)))return{allowed:!1,reason:`${r} is outside the directories this conversation may write`};let a=t.denyWrite.find(c=>n(i,c,!0));if(a!==void 0)return{allowed:!1,reason:`${r} is in ${a.spelling}, which this conversation may not write`};let h=o(i);return h===null?we:{allowed:!1,reason:`${r} is protected (${h}) and cannot be written here`}}}}var K=['if [ -n "${LAGENT_SANDBOX_PATH+x}" ]; then PATH=$LAGENT_SANDBOX_PATH; export PATH; else unset PATH; fi',"TMPDIR=$LAGENT_SANDBOX_TMPDIR; export TMPDIR",'set -- "$LAGENT_SANDBOX_COMMAND"',"unset LAGENT_SANDBOX_PATH LAGENT_SANDBOX_TMPDIR LAGENT_SANDBOX_COMMAND",'exec /bin/sh -c "$1"'].join("; ");function _e(t){return"shell"in t?t.shell:`exec ${t.argv.map(e=>`'${e.replaceAll("'","'\\''")}'`).join(" ")}`}function De(t,e,n,o){let{PATH:r,...s}=t;return{...s,PATH:e,LAGENT_SANDBOX_TMPDIR:n,LAGENT_SANDBOX_COMMAND:o,...r===void 0?{}:{LAGENT_SANDBOX_PATH:r}}}var P="lagent-srt",rn="5",Re=t=>`${P}/${rn} sandbox-runtime/${t}`;function Ne(t){return`sha256:${ge("sha256").update(v({...t,bootstrap:K})).digest("hex")}`}function Oe(t){let e=t.filesystem.allowWrite.length===0?"":`, in ${t.filesystem.allowWrite.join(", ")}`,{network:n}=t,o=n.mode==="unrestricted"?"They have unrestricted network access.":n.allowedDomains.length===0?"They have no network access.":`They reach the network only through a proxy, and only these hosts: ${n.allowedDomains.join(", ")}; any other host is refused with HTTP 403. Tools that ignore HTTP_PROXY cannot connect. Refused requests are usually, not always, listed after the command's output.`;return[`run_command and start_command run in an OS sandbox. The file tools (read_file, list_dir, edit_files, rg_search, rg_files) are checked against the same rules, and name the rule when they refuse a path. Commands and edits can write only inside the conversation's working directory${e}, and in $TMPDIR. A command's refused write fails with "Operation not permitted", which is all it reports. Inside the working directory, git hooks, .git/config, shell startup files and editor settings cannot be written, by a command or by an edit.`,o,"A refusal is the sandbox, not a fault to work around: say what you needed instead."].join(" ")}import{z as Q}from"zod";var y=ae({name:"lagent:sandbox",authority:"conversation",initial:"confined",modes:["confined","unconfined"]});function C(t,e){if(t.journal.summary(e)===void 0)throw new Error(`Unknown thread: ${e}`);return y.recorded(t.journal,y.authorityOf(t,e))}var on=(t,e)=>y.recorded(t,e),sn=Q.strictObject({mode:Q.enum(["confined","unconfined"]),guidance:Q.string().nullable()}),an=re({plugin:"lagent:sandbox",kind:"notice",version:1,schema:sn}),cn="The sandbox is off: commands you start from now on run with the user's full permissions and network, and the file tools are no longer limited; commands already preparing or running keep the confinement they started with.",ln="Commands you start from now on are sandboxed, and the file tools follow the same rules; commands already preparing or running keep how they were launched.",q="The sandbox is unavailable on this host: commands, searches and file access are refused for this conversation.";function M(t,e){se(t,{event:an,agents:"all",rank:1,current:n=>{try{return e(n.threadId)}catch(o){if(o instanceof ie)return;throw o}},render:(n,o,{notices:r})=>{let s=oe(r)?.value??null;return n.mode==="unconfined"?(o??(s?.mode==="confined"?s:null))?.mode==="confined"?cn:null:n.guidance===null?null:n.guidance===q?n.guidance:`${ln} ${n.guidance}`}})}var j=new WeakMap;function L(t,e){let n=t.reflect.target,o=j.get(n);if(o!==void 0)throw new Error(`${e} cannot mount: ${o} already owns this application's sandbox, and the two exclude each other`);j.set(n,e),t.effect(()=>()=>{j.get(n)===e&&j.delete(n)})}var $=d.string().refine(U,{message:"Expected an absolute path"}),je=d.url({protocol:/^https?$/}).refine(t=>new URL(t).username===""&&new URL(t).password==="",{message:"A parent proxy URL carrying credentials is not supported"}),Me=d.strictObject({worker:d.union([d.instanceof(URL),d.string().min(1)]),searchPath:d.string().refine(t=>t.split(":").every(U),{message:"Every searchPath entry must be absolute"}),tmpRoot:$,javaAgentJar:$,unconfined:d.enum(["allowed","refused"]),filesystem:d.strictObject({allowWrite:d.array($),denyRead:d.array($),denyWrite:d.array($)}),network:d.discriminatedUnion("mode",[d.strictObject({mode:d.literal("allowlist"),allowedDomains:d.array(d.string().min(1)),deniedDomains:d.array(d.string().min(1)),parentProxy:d.union([d.literal("none"),d.strictObject({http:je.optional(),https:je.optional(),noProxy:d.string().optional()})])}),d.strictObject({mode:d.literal("unrestricted")})]),drainTimeoutMs:d.number().int().positive().default(2e4)});function un(t){if(t.mode==="unrestricted")return{deniedDomains:[]};let{parentProxy:e}=t;return{allowedDomains:t.allowedDomains,deniedDomains:t.deniedDomains,...e==="none"?{}:{parentProxy:e}}}function mn(t,e,n){return t==="allowlist"?e.isCopy?[]:[`sandbox-runtime does not advertise the Java agent copy ${n}`]:e.flags===0?[]:["sandbox-runtime advertises a Java agent although the network is unrestricted"]}var H=3e4,fn=15e3,pn=5e3,Ce=!1,W=class extends Pe{static inject=["confinement","journal","threads"];guidance;config;state="new";failure=null;admitting=!0;client=null;host=null;handle;releasing=null;constructor(e,n){super(e,"sandbox"),N(e,"sandbox"),this.config=Me.parse(n),this.guidance=Oe(this.config),L(e,"sandbox"),M(e,o=>{let r=this.mode(o);return{mode:r,guidance:r==="confined"?this.guidance:null}}),this.handle=e.confinement.handle(this),e.fiber.beforeUnload(()=>this.closeAndRelease())}async[Pe.init](){try{await this.start()}catch(e){this.state="failed",this.admitting=!1;let n=this.releaseHandle();throw this.announceFailure(),await n,await this.client?.terminate(),this.client=null,e}return()=>this.release()}get unconfined(){return this.config.unconfined}get health(){return this.state==="ready"&&this.admitting?"ready":"failed"}mode(e){return this.config.unconfined==="refused"?"confined":y.of(this.ctx,e)}async setMode(e,n){this.refuseGated(n);let o=this.ctx.journal.summary(e);if(o===void 0)throw new Error(`Unknown thread: ${e}`);if(o.origin!==null)throw new Error(`Thread ${e} is in its conversation's sandbox mode`);await y.set(this.ctx,e,n),this.handle.changed()}opening(e){return this.refuseGated(e),y.opening(e)}refuseGated(e){if(e==="unconfined"&&this.config.unconfined==="refused")throw new Error("This host refuses unconfined conversations: commands, searches and file access stay confined")}async snapshot(e){this.requireOpen();let n=this.ctx.journal.summary(e);if(n===void 0)throw new Error(`The sandbox cannot serve unknown thread ${e}`);if(this.mode(e)==="unconfined")return{files:null,prepare:s=>this.prepare(null,s)};let r={cwd:n.environment.cwd,scratch:B(e),policy:this.policyFor(n.environment.cwd,B(e),!1)};return{files:$e(r.policy),prepare:s=>this.prepare(r,s)}}async prepare(e,n){if(this.requireOpen(),!U(n.workdir))throw new Error(`The command sandbox needs an absolute workdir, not ${n.workdir}`);if(e===null)return this.plain(n);if(n.root!==e.cwd||n.scratch!==e.scratch)throw new Error(`The command sandbox refuses a launch whose root and scratch key differ from its operation's (${e.cwd}, ${e.scratch}): one operation has one policy`);return this.build(n,!1,e.policy)}plain(e){let n=this.host;if(n===null)throw new Error("The sandbox is not running");return{argv:R(e.program),cwd:e.workdir,env:e.environment,provenance:{launcher:P,version:n.version,policy:D},violations:async()=>null}}async start(){if(process.platform!=="darwin")throw new Error(`The command sandbox supports macOS only; on ${process.platform}, sandbox-runtime's launches sharing a directory can remove each other's protective mount points`);let e=k(this.config.tmpRoot),n=this.config.filesystem.denyWrite.map(f),o=n.find(u=>p(e,u.canonical)||p(u.canonical,e));if(o!==void 0)throw new Error(`tmpRoot must be outside filesystem.denyWrite, which would keep commands from writing their scratch directories: ${o.spelling}`);if(Ce)throw new Error("The command sandbox starts once per process: sandbox-runtime keeps process-wide state and is never restarted, so restart the host");Ce=!0,this.state="starting";let{config:r}=this,s=r.searchPath.split(":").map(k).join(":"),i=Ae(s),a=r.filesystem.allowWrite.map(f);Y(a.map(u=>u.canonical),i,e);let h=r.filesystem.denyRead.map(f),c=hn(),l=me(e);fe(l);let m=pe(l,r.javaAgentJar),x=new O(r.worker,{PATH:s,HOME:c,TMPDIR:l,CLAUDE_CODE_TMPDIR:l},u=>this.onWorkerDeath(u));this.client=x;let b=u=>({path:u,literal:!0}),ee={network:un(r.network),filesystem:{denyRead:h.map(u=>b(u.canonical)),allowWrite:[],denyWrite:n.map(u=>b(u.canonical))},javaAgentJarPath:m.path},w=await x.request({type:"init",config:ee,expect:{tmpdir:l,home:c}},{timeoutMs:H}),_=[...w.supported?[]:["this platform is not supported"],...w.errors,...w.warnings,...mn(r.network.mode,w.javaAgent,m.path)];if(_.length>0)throw new Error(`The command sandbox cannot start here: ${_.join("; ")}`);if(this.host={tmpRoot:e,cwd:process.cwd(),home:c,searchPath:s,trusted:i,extraRoots:r.filesystem.allowWrite,facts:{allowWrite:a,denyWrite:n,denyRead:h,conveniences:w.conveniences.map(f),privateDirectory:f(l)},version:Re(w.version),javaAgentSha256:m.sha256},await this.selfCheck(e),!x.alive||this.failure!==null)throw new Error(`The command sandbox worker ended while it started${this.failure===null?"":`: ${this.failure.message}`}`);this.state="ready"}async selfCheck(e){let n=X(e,"self-check"),o=await this.build({program:{shell:"exit 0"},workdir:n,root:n,scratch:"self-check",environment:{},signal:AbortSignal.timeout(H)},!0),r=await ne({command:o.argv,cwd:o.cwd,env:o.env,signal:AbortSignal.timeout(H),timeoutMs:H});if(r.exitCode!==0){let s=r.exitSignal===null?`exit code ${r.exitCode}`:`signal ${r.exitSignal}`;throw new Error(`The command sandbox could not run a confined command: ${r.output.trim()||s}`)}}requireOpen(){if(this.state==="failed")throw new Error(`The sandbox failed${this.failure===null?"":`: ${this.failure.message}`}. Nothing runs unconfined; it stays failed until lagent restarts.`);if(this.state==="new"||this.state==="starting")throw new Error("The sandbox is still starting");if(this.state!=="ready"||!this.admitting)throw new Error("The sandbox is shutting down")}async build(e,n,o){let r=this.host,s=this.client;if(r===null||s===null)throw new Error("The sandbox is not running");if(ve(e.environment),!U(e.workdir))throw new Error(`The command sandbox needs an absolute workdir, not ${e.workdir}`);let i=_e(e.program);if(Buffer.byteLength(i)>V)throw new Error(`The command sandbox refuses a command longer than ${V} bytes`);if(process.cwd()!==r.cwd)throw new Error(`The host changed its working directory from ${r.cwd} to ${process.cwd()}: the command sandbox refuses to launch`);let a=this.policyFor(e.root,e.scratch,n);if(o!==void 0&&v(o)!==v(a))throw new Error("The command sandbox refuses to launch: a writable root or a link in it now leads somewhere other than where this operation selected its policy, so it could not enforce the same one. Start the operation again.");let h=o??a,c=X(r.tmpRoot,e.scratch),l=ke(h),m=dn();return{argv:["/bin/sh","-c",`exec ${await s.request({type:"wrap",command:K,commandId:m,commandText:i.slice(0,1e3),config:{filesystem:l}},{timeoutMs:fn,signal:e.signal})}`],cwd:e.workdir,env:De(e.environment,r.searchPath,c,i),provenance:{launcher:P,version:r.version,policy:Ne({filesystem:l,network:this.config.network,searchPath:r.searchPath,cwd:r.cwd,home:r.home,conveniences:r.facts.conveniences.map(b=>b.spelling),javaAgent:r.javaAgentSha256,version:r.version})},violations:()=>this.violationsOf(m)}}policyFor(e,n,o){let r=this.host;if(r===null)throw new Error("The sandbox is not running");let s=Z(e,Te(e)),i=r.extraRoots.map(f),a=i.map(h=>h.canonical);return Y(o?a:[s.canonical,...a],r.trusted,r.tmpRoot),Se({...r.facts,allowWrite:i},s,f(G(r.tmpRoot,n)))}async violationsOf(e){if(this.client===null)return null;let n=await this.client.request({type:"violations",commandId:e},{timeoutMs:te});return n.length===0?null:`<sandbox_violations>
2
+ ${n.join(`
3
+ `)}
4
+ </sandbox_violations>`}onWorkerDeath(e){this.failure??=e,this.state==="ready"&&(this.state="failed",this.admitting=!1,this.ctx.reportError(e),this.releaseHandle(),this.announceFailure())}announceFailure(){this.ctx.parallel("lagent/sandbox/health","failed").catch(e=>this.ctx.reportError(e))}async closeAndRelease(){this.admitting=!1,this.state==="ready"&&(this.state="stopping"),await this.releaseHandle()}releaseHandle(){return this.releasing??=this.handle.release(this.config.drainTimeoutMs).then(e=>(e||this.ctx.reportError(new Error(`A command could not be confirmed stopped within ${this.config.drainTimeoutMs} ms: a confined process may still run, so the sandbox worker is kept until the process exits`)),e)),this.releasing}async release(){let e=this.client;if(e!==null){if(!await this.releaseHandle()){e.unref();return}e.alive&&await e.request({type:"reset"},{timeoutMs:pn}).catch(()=>{}),await e.terminate(),this.client=null}}};import{z as Le}from"zod";var wn=Le.strictObject({drainTimeoutMs:Le.number().int().positive()}),gn="lagent-sandbox-absent",yn="1",xn={name:"lagent:sandbox-absent",inject:["journal","threads","confinement"],apply(t,e){N(t,"sandboxAbsent");let n=wn.parse(e);L(t,"sandboxAbsent");let o=t.confinement.handle({async snapshot(s){if(C(t,s)==="confined")throw new Error("This conversation is sandboxed, and the host started without a sandbox, so its commands, searches and file access are refused.");return{files:null,async prepare(i){return{argv:R(i.program),cwd:i.workdir,env:i.environment,provenance:{launcher:gn,version:yn,policy:D},violations:async()=>null}}}}}),r=null;t.fiber.beforeUnload(()=>(r??=o.release(n.drainTimeoutMs).then(s=>(s||t.reportError(new Error(`A command could not be confirmed stopped within ${n.drainTimeoutMs} ms`)),s)),r.then(()=>{}))),M(t,s=>C(t,s)==="confined"?{mode:"confined",guidance:q}:{mode:"unconfined",guidance:null})}};var dt=W,ht=new URL("./worker.js",import.meta.url);function ut(){let t=bn(import.meta.url).resolve("@anthropic-ai/sandbox-runtime/package.json");return En(vn(t),"vendor","java-proxy-agent","srt-proxy-agent.jar")}export{Me as SandboxConfigSchema,dt as sandbox,xn as sandboxAbsent,C as sandboxRecorded,on as sandboxRecordedOn,ht as sandboxWorker,ut as srtJavaAgentJar};
@@ -0,0 +1,57 @@
1
+ import type { Context } from '@leuduan/cordis';
2
+ import { z } from 'zod';
3
+ import type { PluginJournal } from '../../journal/index.js';
4
+ /**
5
+ * Whether a conversation's shell commands run in the OS sandbox. One vocabulary from lagent to a
6
+ * host's wire; "sandboxed" and "unsandboxed" are a display's words.
7
+ */
8
+ export type SandboxMode = 'confined' | 'unconfined';
9
+ /**
10
+ * The durable mode of each conversation, on its root: a delegated thread is in its conversation's
11
+ * mode, so a sub-agent is never in a mode its conversation is not. Private to this package: the
12
+ * service is its only writer, and `sandboxRecorded` is how a host reads it.
13
+ */
14
+ export declare const sandboxModes: import("../../services/modes.js").Modes<SandboxMode>;
15
+ /**
16
+ * The mode recorded for the conversation `threadId` belongs to, `null` only when none was ever
17
+ * recorded: never the `confined` a thread without a row reads as. For a host deciding what the
18
+ * journal says, whether the sandbox is mounted or not. Throws for a thread the journal does not hold,
19
+ * a caller chain that does not reach its conversation root, and a mode row this build cannot read.
20
+ * Needs the thread's history loaded, as a tool call and a launch it makes already have it.
21
+ */
22
+ export declare function sandboxRecorded(ctx: Context, threadId: string): SandboxMode | null;
23
+ /**
24
+ * The recorded mode in this thread's own fork-lineage fold, without resolving delegation authority:
25
+ * a fork reads what its history recorded up to the cut, and a delegated thread reads its own fold,
26
+ * `null` unless it inherited a row, never its conversation's mode. For a catalog contribution, which
27
+ * is computed from the thread's own folds. A host deciding what the journal says uses {@link sandboxRecorded}.
28
+ */
29
+ export declare const sandboxRecordedOn: (journal: PluginJournal, threadId: string) => SandboxMode | null;
30
+ /** What the model is told about a conversation's shell commands: the mode, and what confined ones may do. */
31
+ declare const SandboxFactSchema: z.ZodObject<{
32
+ mode: z.ZodEnum<{
33
+ unconfined: "unconfined";
34
+ confined: "confined";
35
+ }>;
36
+ guidance: z.ZodNullable<z.ZodString>;
37
+ }, z.core.$strict>;
38
+ export type SandboxFact = z.output<typeof SandboxFactSchema>;
39
+ /**
40
+ * What a confined conversation is told on a host started without a sandbox, where its commands, searches
41
+ * and file access are refused.
42
+ */
43
+ export declare const ABSENT_GUIDANCE = "The sandbox is unavailable on this host: commands, searches and file access are refused for this conversation.";
44
+ /**
45
+ * Tells every agent's threads the fact `current` says at their model steps. The guidance is part of
46
+ * the compared fact, so a changed sandbox file, or a host that lost its sandbox, is told again. A
47
+ * conversation that starts unconfined is told nothing; a lift is still owed when the confined notice
48
+ * that preceded it was compacted away, since its checkpoint may still say confined. Each telling of
49
+ * confinement carries the caveat that launches already preparing or running are not affected.
50
+ */
51
+ export declare function tellSandbox(ctx: Context, current: (threadId: string) => SandboxFact): void;
52
+ /**
53
+ * Claims the root's one sandbox slot for `owner`, released when `ctx` ends. Whichever of `sandbox`
54
+ * and `sandboxAbsent` mounts second fails, in either order, before it registers anything.
55
+ */
56
+ export declare function claimSandbox(ctx: Context, owner: 'sandbox' | 'sandboxAbsent'): void;
57
+ export {};
@@ -0,0 +1,163 @@
1
+ import type { FilePolicy, LaunchProgram } from '../../services/confinement/service.js';
2
+ import type { SpawnEnvironment } from '../../sys/spawn.js';
3
+ import type { CallConfig, PathEntry } from './protocol.js';
4
+ export declare function refuseStartupInjection(environment: SpawnEnvironment): void;
5
+ /** The most command text one launch carries, which travels in the environment of the process that starts it. */
6
+ export declare const MAX_COMMAND_BYTES: number;
7
+ /**
8
+ * `path` with every link in its existing part resolved, so containment is judged on real locations.
9
+ * The one rule for every path of a policy: a link that leads nowhere is not followed, it stays the
10
+ * name it is, so an entry that is a dangling link covers its own name and nothing it points at.
11
+ */
12
+ export declare function canonical(path: string): string;
13
+ /**
14
+ * The root a command's protected names are rebuilt for, canonical. sandbox-runtime substitutes the
15
+ * real prefix before it compiles a pattern, and cannot anchor one this module hands it, so a root
16
+ * whose real path holds glob characters or its compiler's placeholders would turn the patterns
17
+ * below into something else: it is refused, not escaped.
18
+ */
19
+ export declare function canonicalRoot(root: string): string;
20
+ /**
21
+ * The names sandbox-runtime protects below its working directory, which here is the host's, not the thread's:
22
+ * relative suffixes, matched at the root and at any depth below it, with everything beneath them.
23
+ */
24
+ export declare const PROTECTED_SUFFIXES: readonly string[];
25
+ /**
26
+ * sandbox-runtime's mandatory write denies, rebuilt for `root`: its own set anchors at the host's
27
+ * working directory and only ever adds denies there. Each of `suffixes` once as the path at the root and
28
+ * twice as patterns for the same name anywhere beneath it, itself and its descendants. `root` is
29
+ * canonical and holds no glob characters (`canonicalRoot`).
30
+ */
31
+ export declare function mandatoryDenies(root: string, suffixes: readonly string[]): PathEntry[];
32
+ /**
33
+ * The programs a launch runs before confinement, as the search path finds them: the directories
34
+ * searched, since a program dropped into an earlier one would win, and the real file of every
35
+ * candidate for each program, wherever its link leads.
36
+ */
37
+ export declare function helperLocations(searchPath: string): string[];
38
+ /**
39
+ * Refuses roots a command could use against the sandbox itself: one overlapping, either way, what
40
+ * a launch runs before confinement, or the temporary root, whose directories the host creates
41
+ * unconfined. `roots` are canonical.
42
+ */
43
+ export declare function refuseUnsafeRoots(roots: readonly string[], trusted: readonly string[], tmpRoot: string): void;
44
+ /**
45
+ * A path as the host configured it and as it really is, with whether its volume folds case
46
+ * (`null`: it could not be told). Both spellings are matched when a path is judged by name
47
+ * (`filePolicy`), so a workspace configured by its link path still matches a path spelled by its
48
+ * real one, and the other way round; the OS sees only the real one (`srtFilesystem`).
49
+ */
50
+ export type Anchor = {
51
+ readonly spelling: string;
52
+ readonly canonical: string;
53
+ readonly foldsCase: boolean | null;
54
+ };
55
+ /** `spelling` with the real path it leads to, which the caller already has, and its volume's case rule. */
56
+ export declare const anchorAt: (spelling: string, canonicalPath: string) => Anchor;
57
+ /** `path` as configured, with where it really leads. */
58
+ export declare const anchor: (path: string) => Anchor;
59
+ /**
60
+ * Whether the volume that holds `path` treats names that differ only in case as one, or `null` when
61
+ * it cannot be told. `path` may not exist yet: the answer is the volume of its nearest existing
62
+ * directory, probed by looking one of its entries up in another case and comparing what the two
63
+ * lookups find (device and inode), never by assuming a platform's default. Only what lives on that
64
+ * directory's own volume is probed: an entry on another device (a mount), or the directory's own
65
+ * name in a parent on another device, would answer for another volume. A directory with no entry to
66
+ * probe is probed through its own name in its parent, and so up, on the same device. `deviceOf` is
67
+ * the device lookup, replaceable to simulate volumes in tests.
68
+ */
69
+ export declare function foldsCase(path: string, deviceOf?: (path: string) => number): boolean | null;
70
+ /**
71
+ * What the sandbox allows a thread's processes and file tools, as one value in a restricted
72
+ * language: literal subtrees, each a path and everything below it, and protected relative suffixes
73
+ * under one root. `srtFilesystem` renders it for the OS, `filePolicy` for the file tools in this process.
74
+ */
75
+ export type FilesystemPolicy = {
76
+ /** Writable subtrees: the thread's root, the host's roots and the scratch directory. */
77
+ readonly writable: readonly Anchor[];
78
+ /** Unwritable even inside a writable subtree: what the host and the sandbox keep for themselves. */
79
+ readonly denyWrite: readonly Anchor[];
80
+ /** Unreadable subtrees: credentials. */
81
+ readonly denyRead: readonly Anchor[];
82
+ /** Names unwritable at `root` and at any depth below it, with everything beneath them. */
83
+ readonly protected: {
84
+ readonly root: Anchor;
85
+ readonly suffixes: readonly string[];
86
+ };
87
+ };
88
+ /** What a thread's filesystem policy is made of, besides its root and scratch directory. */
89
+ export type HostFacts = {
90
+ readonly allowWrite: readonly Anchor[];
91
+ readonly denyWrite: readonly Anchor[];
92
+ readonly denyRead: readonly Anchor[];
93
+ /** The paths under the home directory sandbox-runtime keeps writable for itself, as its worker reported them. */
94
+ readonly conveniences: readonly Anchor[];
95
+ readonly privateDirectory: Anchor;
96
+ };
97
+ /**
98
+ * A thread's filesystem policy: writes only in the root, the host's roots and the scratch directory,
99
+ * and never in what the host or sandbox-runtime keeps for itself or in the root's protected names.
100
+ * `root` is the thread's working directory, `canonicalRoot`'s answer for its spelling.
101
+ */
102
+ export declare function filesystemPolicy(facts: HostFacts, root: Anchor, scratchDirectory: Anchor): FilesystemPolicy;
103
+ /** The OS's lists for the policy, which replace the initialized ones for the call: every path real and literal. */
104
+ export declare function srtFilesystem(policy: FilesystemPolicy): CallConfig['filesystem'];
105
+ /**
106
+ * The policy for file tools in this process: the same value the OS enforces on processes, judged by
107
+ * name. It reads no file system, so a link in a path is the caller's to resolve: it asks for the
108
+ * path as requested and for where it resolves, and both must pass. A path is in a subtree, or under
109
+ * the protected root, by either spelling of that entry.
110
+ *
111
+ * Names are compared as the entry's volume would see them, and doubt never widens what is allowed.
112
+ * A rule that refuses compares in Unicode normalization form NFC, since a volume such as APFS treats
113
+ * the composed and decomposed spellings of a name as one, and folds case where the volume does:
114
+ * `.SSH` is `.ssh` there, even when `.ssh` does not exist yet, and `.GITCONFIG` is another file on a
115
+ * volume that keeps case. Where that could not be told (`foldsCase: null`) it folds. A rule that
116
+ * allows compares the spellings as they are, folding case only where the volume is known to. So on
117
+ * a volume that tells normalizations apart a refusal can over-match, and on APFS a writable root
118
+ * typed in another normalization than it was configured in is refused, never allowed too widely.
119
+ */
120
+ export declare function filePolicy(policy: FilesystemPolicy): FilePolicy;
121
+ /**
122
+ * The script the sandbox runs: restores the command's `PATH`, sets its temporary directory and runs
123
+ * it with `/bin/sh -c`. Everything it needs arrives in variables, never spliced into the script.
124
+ */
125
+ export declare const BOOTSTRAP: string;
126
+ /**
127
+ * A program as the shell text the sandbox runs: shell text as it is, an argv as `exec` and every word
128
+ * single-quoted, so the shell splits it back into exactly those words and looks the program up as a
129
+ * plain spawn does, never as one of its builtins or functions.
130
+ */
131
+ export declare function programText(program: LaunchProgram): string;
132
+ /**
133
+ * The environment of the process that starts the sandbox: the command's own, but with the trusted
134
+ * `PATH` until the sandbox holds, since the wrapped line runs `env` and `sandbox-exec` before
135
+ * confinement. The bootstrap restores the rest.
136
+ */
137
+ export declare function launchEnvironment(environment: SpawnEnvironment, searchPath: string, scratchDirectory: string, command: string): SpawnEnvironment;
138
+ /** What the name and version say in a command's recorded launch. */
139
+ export declare const LAUNCHER = "lagent-srt";
140
+ export declare const versionOf: (runtime: string) => string;
141
+ /** Everything enforcement depends on, digested: recorded with a launch as its policy. */
142
+ export declare function policyDigest(facts: {
143
+ readonly filesystem: CallConfig['filesystem'];
144
+ readonly network: unknown;
145
+ readonly searchPath: string;
146
+ readonly cwd: string;
147
+ readonly home: string;
148
+ readonly conveniences: readonly string[];
149
+ readonly javaAgent: string;
150
+ readonly version: string;
151
+ }): string;
152
+ /** What the model is told about sandboxed commands. */
153
+ export declare function instructionsFor(config: {
154
+ readonly filesystem: {
155
+ readonly allowWrite: readonly string[];
156
+ };
157
+ readonly network: {
158
+ readonly mode: 'allowlist';
159
+ readonly allowedDomains: readonly string[];
160
+ } | {
161
+ readonly mode: 'unrestricted';
162
+ };
163
+ }): string;