@letta-ai/letta-code 0.29.4 → 0.29.6

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 (30) hide show
  1. package/dist/memory-confinement.js +363 -0
  2. package/dist/memory-confinement.js.map +18 -0
  3. package/dist/types/channels/plugin-types.d.ts +2 -2
  4. package/dist/types/channels/plugin-types.d.ts.map +1 -1
  5. package/dist/types/channels/types.d.ts +15 -1
  6. package/dist/types/channels/types.d.ts.map +1 -1
  7. package/dist/types/memory-confinement.d.ts +13 -0
  8. package/dist/types/memory-confinement.d.ts.map +1 -0
  9. package/dist/types/permissions/memory-confinement-launcher.d.ts +20 -0
  10. package/dist/types/permissions/memory-confinement-launcher.d.ts.map +1 -0
  11. package/dist/types/permissions/sandbox-policy.d.ts +138 -0
  12. package/dist/types/permissions/sandbox-policy.d.ts.map +1 -0
  13. package/dist/types/sandbox/availability.d.ts +60 -0
  14. package/dist/types/sandbox/availability.d.ts.map +1 -0
  15. package/dist/types/sandbox/bwrap.d.ts +36 -0
  16. package/dist/types/sandbox/bwrap.d.ts.map +1 -0
  17. package/dist/types/sandbox/policy.d.ts +79 -0
  18. package/dist/types/sandbox/policy.d.ts.map +1 -0
  19. package/dist/types/sandbox/seatbelt.d.ts +39 -0
  20. package/dist/types/sandbox/seatbelt.d.ts.map +1 -0
  21. package/dist/types/sandbox/wrap.d.ts +18 -0
  22. package/dist/types/sandbox/wrap.d.ts.map +1 -0
  23. package/dist/types/types/protocol_v2.d.ts +2 -0
  24. package/dist/types/types/protocol_v2.d.ts.map +1 -1
  25. package/dist/types/utils/local-backend-paths.d.ts +23 -0
  26. package/dist/types/utils/local-backend-paths.d.ts.map +1 -0
  27. package/letta.js +523 -387
  28. package/package.json +11 -1
  29. package/scripts/isolated-unit-tests.json +5 -0
  30. package/scripts/source-file-size-baseline.json +5 -5
@@ -0,0 +1,363 @@
1
+ // src/permissions/memory-confinement-launcher.ts
2
+ import { homedir as homedir3 } from "node:os";
3
+ import { basename as basename2, dirname as dirname2, join as join3, resolve as resolve2 } from "node:path";
4
+
5
+ // src/permissions/sandbox-policy.ts
6
+ import { existsSync, realpathSync } from "node:fs";
7
+ import { homedir as homedir2 } from "node:os";
8
+ import { basename, dirname, isAbsolute, join as join2, resolve } from "node:path";
9
+
10
+ // src/sandbox/policy.ts
11
+ import { posix, win32 } from "node:path";
12
+ var SANDBOX_ENV_VAR = "LETTA_SANDBOX";
13
+ function buildFsSandboxPolicy(options) {
14
+ return {
15
+ baseWritableRoots: normalizeRoots(options.baseWritableRoots ?? []),
16
+ deniedRoots: normalizeRoots(options.deniedRoots ?? []),
17
+ readonlyRoots: normalizeRoots(options.readonlyRoots ?? []),
18
+ writableRoots: normalizeRoots(options.writableRoots ?? []),
19
+ restrictWrites: options.restrictWrites ?? false
20
+ };
21
+ }
22
+ function normalizeSandboxPath(path) {
23
+ const trimmed = path.trim();
24
+ const absolute = posix.isAbsolute(trimmed) || win32.isAbsolute(trimmed) ? trimmed : posix.resolve("/", trimmed);
25
+ const forward = absolute.replace(/\\/g, "/");
26
+ return forward.replace(/\/+$/, "") || "/";
27
+ }
28
+ function normalizeRoots(roots) {
29
+ const seen = new Set;
30
+ for (const root of roots) {
31
+ if (!root || !root.trim())
32
+ continue;
33
+ seen.add(normalizeSandboxPath(root));
34
+ }
35
+ return [...seen];
36
+ }
37
+
38
+ // src/utils/local-backend-paths.ts
39
+ import { homedir } from "node:os";
40
+ import { join } from "node:path";
41
+ var LOCAL_BACKEND_DIR_ENV = "LETTA_LOCAL_BACKEND_DIR";
42
+ function getLocalBackendStorageDir(homeDir = homedir(), env = process.env) {
43
+ return env[LOCAL_BACKEND_DIR_ENV] ?? join(homeDir, ".letta", "lc-local-backend");
44
+ }
45
+ function getLocalBackendCrossAgentTreeRoot(storageDir = getLocalBackendStorageDir()) {
46
+ return join(storageDir, "memfs");
47
+ }
48
+
49
+ // src/permissions/sandbox-policy.ts
50
+ function getDefaultAgentsTreeRoot(homeDir = homedir2()) {
51
+ return canonicalizeRoot(join2(homeDir, ".letta", "agents"));
52
+ }
53
+ function getCrossBackendAgentsTreeRoots(options = {}) {
54
+ const homeDir = options.homeDir ?? homedir2();
55
+ const localBackendStorageDir = options.localBackendStorageDir ?? getLocalBackendStorageDir(homeDir, options.env ?? process.env);
56
+ return [
57
+ getDefaultAgentsTreeRoot(homeDir),
58
+ canonicalizeRoot(getLocalBackendCrossAgentTreeRoot(localBackendStorageDir))
59
+ ];
60
+ }
61
+ function getLettaHomeRoot(homeDir = homedir2()) {
62
+ return canonicalizeRoot(join2(homeDir, ".letta"));
63
+ }
64
+ function canonicalizeRoot(input) {
65
+ const abs = isAbsolute(input) ? input : resolve(input);
66
+ let dir = abs;
67
+ const tail = [];
68
+ while (!existsSync(dir)) {
69
+ tail.unshift(basename(dir));
70
+ const parent = dirname(dir);
71
+ if (parent === dir) {
72
+ return normalizeSandboxPath(abs);
73
+ }
74
+ dir = parent;
75
+ }
76
+ try {
77
+ const real = realpathSync(dir);
78
+ return normalizeSandboxPath(tail.length ? join2(real, ...tail) : real);
79
+ } catch {
80
+ return normalizeSandboxPath(abs);
81
+ }
82
+ }
83
+ function isWithinRoot(path, root) {
84
+ return path === root || path.startsWith(`${root}/`);
85
+ }
86
+ function isAncestorOfRoot(path, root) {
87
+ const prefix = path === "/" ? "/" : `${path}/`;
88
+ return root.startsWith(prefix);
89
+ }
90
+ function isTreeOrAncestorOfTree(path, canonicalTrees) {
91
+ return canonicalTrees.some((tree) => path === tree || isAncestorOfRoot(path, tree));
92
+ }
93
+ function resolveAgentsTreeRootsInput(roots) {
94
+ return roots?.length ? roots.map(canonicalizeRoot) : getCrossBackendAgentsTreeRoots();
95
+ }
96
+ function deriveSelfAgentRootsForTrees(memoryRoots, agentsTreeRoots = getCrossBackendAgentsTreeRoots()) {
97
+ const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
98
+ const out = new Set;
99
+ for (const root of memoryRoots) {
100
+ const canon = canonicalizeRoot(root);
101
+ const containingTree = canonicalTrees.find((tree) => canon !== tree && isWithinRoot(canon, tree));
102
+ if (containingTree) {
103
+ const leaf = basename(canon);
104
+ const parentLeaf = basename(dirname(canon));
105
+ out.add(leaf === "memory" || leaf === "memory-worktrees" ? dirname(canon) : parentLeaf === "memory-worktrees" || parentLeaf === "memory" && leaf === ".git" ? dirname(dirname(canon)) : canon);
106
+ continue;
107
+ }
108
+ if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
109
+ out.add(canon);
110
+ }
111
+ }
112
+ return [...out];
113
+ }
114
+ function deriveWritableMemoryRootsForTrees(memoryRoots, agentsTreeRoots) {
115
+ const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);
116
+ const out = new Set;
117
+ for (const root of memoryRoots) {
118
+ const canon = canonicalizeRoot(root);
119
+ if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {
120
+ out.add(canon);
121
+ }
122
+ }
123
+ return [...out];
124
+ }
125
+ function buildMemorySubagentSandboxPolicy(input) {
126
+ const agentsTreeRoots = resolveAgentsTreeRootsInput(input.agentsTreeRoots);
127
+ const baseWritableRoots = [
128
+ getLettaHomeRoot(),
129
+ ...input.harnessWritableRoots ?? []
130
+ ].map(canonicalizeRoot);
131
+ return buildFsSandboxPolicy({
132
+ baseWritableRoots,
133
+ deniedRoots: agentsTreeRoots,
134
+ readonlyRoots: [
135
+ ...deriveSelfAgentRootsForTrees(input.memoryRoots, agentsTreeRoots),
136
+ ...(input.readonlyRoots ?? []).map(canonicalizeRoot)
137
+ ],
138
+ writableRoots: deriveWritableMemoryRootsForTrees(input.memoryRoots, agentsTreeRoots),
139
+ restrictWrites: true
140
+ });
141
+ }
142
+
143
+ // src/sandbox/bwrap.ts
144
+ var BWRAP_BIN = "bwrap";
145
+ function buildBwrapArgs(policy) {
146
+ const args = [];
147
+ args.push(policy.restrictWrites ? "--ro-bind" : "--bind", "/", "/");
148
+ args.push("--dev", "/dev");
149
+ args.push("--proc", "/proc");
150
+ for (const root of policy.baseWritableRoots) {
151
+ args.push("--bind-try", root, root);
152
+ }
153
+ for (const root of policy.deniedRoots) {
154
+ args.push("--tmpfs", root);
155
+ }
156
+ for (const root of policy.readonlyRoots) {
157
+ args.push("--ro-bind-try", root, root);
158
+ }
159
+ for (const root of policy.writableRoots) {
160
+ args.push("--bind-try", root, root);
161
+ }
162
+ args.push("--die-with-parent");
163
+ return args;
164
+ }
165
+
166
+ // src/sandbox/seatbelt.ts
167
+ var SANDBOX_EXEC_PATH = "/usr/bin/sandbox-exec";
168
+ function buildSeatbeltProfile(policy) {
169
+ const defines = [];
170
+ const lines = ["(version 1)", "(allow default)"];
171
+ if (policy.restrictWrites) {
172
+ lines.push('(deny file-write* (subpath "/"))');
173
+ lines.push('(allow file-write* (subpath "/dev"))');
174
+ }
175
+ policy.baseWritableRoots.forEach((root, i) => {
176
+ const name = `BASEWRITABLE_${i}`;
177
+ defines.push({ name, value: root });
178
+ lines.push(`(allow file-write* (subpath (param "${name}")))`);
179
+ });
180
+ policy.deniedRoots.forEach((root, i) => {
181
+ const name = `DENIED_${i}`;
182
+ defines.push({ name, value: root });
183
+ lines.push(`(deny file-read* file-write* (subpath (param "${name}")))`);
184
+ lines.push(`(allow file-read-metadata (literal (param "${name}")))`);
185
+ });
186
+ policy.writableRoots.forEach((root, i) => {
187
+ const name = `WRITABLE_${i}`;
188
+ defines.push({ name, value: root });
189
+ lines.push(`(allow file-read* file-write* (subpath (param "${name}")))`);
190
+ });
191
+ policy.readonlyRoots.forEach((root, i) => {
192
+ const name = `READONLY_${i}`;
193
+ defines.push({ name, value: root });
194
+ lines.push(`(allow file-read* (subpath (param "${name}")))`);
195
+ });
196
+ return { profile: `${lines.join(`
197
+ `)}
198
+ `, defines };
199
+ }
200
+ function buildSeatbeltArgs(policy) {
201
+ const { profile, defines } = buildSeatbeltProfile(policy);
202
+ const args = ["-p", profile];
203
+ for (const { name, value } of defines) {
204
+ args.push(`-D${name}=${value}`);
205
+ }
206
+ return args;
207
+ }
208
+
209
+ // src/sandbox/wrap.ts
210
+ function wrapLauncher(launcher, policy, options) {
211
+ if (!options.backend)
212
+ return null;
213
+ if (launcher.length === 0)
214
+ return null;
215
+ switch (options.backend) {
216
+ case "seatbelt":
217
+ return [
218
+ SANDBOX_EXEC_PATH,
219
+ ...buildSeatbeltArgs(policy),
220
+ "--",
221
+ ...launcher
222
+ ];
223
+ case "bwrap":
224
+ return [
225
+ options.bwrapPath ?? BWRAP_BIN,
226
+ ...buildBwrapArgs(policy),
227
+ "--",
228
+ ...launcher
229
+ ];
230
+ }
231
+ }
232
+
233
+ // src/permissions/memory-confinement-launcher.ts
234
+ function normalizeRoot(path) {
235
+ const trimmed = path.trim();
236
+ const expanded = trimmed.startsWith("~/") ? join3(homedir3(), trimmed.slice(2)) : trimmed.startsWith("$HOME/") ? join3(homedir3(), trimmed.slice(6)) : trimmed;
237
+ return resolve2(expanded);
238
+ }
239
+ function resolveWritableMemoryRoots(env) {
240
+ const roots = new Set;
241
+ for (const value of [env.MEMORY_DIR, env.LETTA_MEMORY_DIR]) {
242
+ if (!value?.trim())
243
+ continue;
244
+ const root = normalizeRoot(value);
245
+ roots.add(root);
246
+ if (basename2(root) === "memory") {
247
+ roots.add(join3(dirname2(root), "memory-worktrees"));
248
+ }
249
+ }
250
+ return [...roots];
251
+ }
252
+ function customHarnessWritableRoots(env) {
253
+ return [env.LETTA_LOCAL_BACKEND_DIR, env.LETTA_TRANSCRIPT_ROOT].filter((value) => Boolean(value?.trim())).map(normalizeRoot);
254
+ }
255
+ function createMemoryConfinementLauncherWithAvailability(input, availability) {
256
+ if (input.launcher.length === 0) {
257
+ throw new Error("Memory confinement requires a non-empty launcher.");
258
+ }
259
+ const memoryRoots = resolveWritableMemoryRoots(input.env);
260
+ if (memoryRoots.length === 0) {
261
+ throw new Error("Memory confinement requires MEMORY_DIR or LETTA_MEMORY_DIR.");
262
+ }
263
+ if (!availability.backend) {
264
+ throw new Error(`Memory confinement is unavailable: ${availability.reason}.`);
265
+ }
266
+ const localBackendStorageDir = input.env.LETTA_LOCAL_BACKEND_DIR?.trim() || undefined;
267
+ const policy = buildMemorySubagentSandboxPolicy({
268
+ memoryRoots,
269
+ agentsTreeRoots: getCrossBackendAgentsTreeRoots({
270
+ env: input.env,
271
+ localBackendStorageDir
272
+ }),
273
+ harnessWritableRoots: customHarnessWritableRoots(input.env)
274
+ });
275
+ const launcher = wrapLauncher(input.launcher, policy, {
276
+ backend: availability.backend,
277
+ bwrapPath: availability.bwrapPath
278
+ });
279
+ if (!launcher) {
280
+ throw new Error("Memory confinement could not wrap the launcher.");
281
+ }
282
+ return {
283
+ launcher,
284
+ env: { ...input.env, [SANDBOX_ENV_VAR]: availability.backend },
285
+ backend: availability.backend
286
+ };
287
+ }
288
+
289
+ // src/sandbox/availability.ts
290
+ import { spawnSync } from "node:child_process";
291
+ import { existsSync as existsSync2 } from "node:fs";
292
+ import { delimiter, isAbsolute as isAbsolute2, join as join4 } from "node:path";
293
+ var cached = null;
294
+ var warnedUnavailableContexts = new Set;
295
+ function detectSandboxBackend(options = {}) {
296
+ const platform = options.platform ?? process.platform;
297
+ if (!options.force && cached && !options.platform) {
298
+ return cached;
299
+ }
300
+ const result = probe(platform);
301
+ if (!options.platform) {
302
+ cached = result;
303
+ }
304
+ return result;
305
+ }
306
+ function probe(platform) {
307
+ if (platform === "darwin") {
308
+ if (existsSync2(SANDBOX_EXEC_PATH)) {
309
+ return { backend: "seatbelt", reason: "sandbox-exec available" };
310
+ }
311
+ return {
312
+ backend: null,
313
+ reason: `${SANDBOX_EXEC_PATH} not found`
314
+ };
315
+ }
316
+ if (platform === "linux") {
317
+ return probeBwrap();
318
+ }
319
+ return {
320
+ backend: null,
321
+ reason: `no filesystem sandbox backend for platform "${platform}"`
322
+ };
323
+ }
324
+ function probeBwrap() {
325
+ const bwrapPath = resolveExecutableOnPath("bwrap");
326
+ if (!bwrapPath) {
327
+ return { backend: null, reason: "bwrap not found on PATH" };
328
+ }
329
+ const version = spawnSync(bwrapPath, ["--version"], { timeout: 5000 });
330
+ if (version.error || version.status !== 0) {
331
+ return { backend: null, reason: "bwrap not found on PATH" };
332
+ }
333
+ const userns = spawnSync(bwrapPath, ["--ro-bind", "/", "/", "--unshare-user", "/bin/true"], { timeout: 5000 });
334
+ if (userns.error || userns.status !== 0) {
335
+ return {
336
+ backend: null,
337
+ reason: "bwrap present but user namespaces are unavailable"
338
+ };
339
+ }
340
+ return { backend: "bwrap", bwrapPath, reason: "bwrap available" };
341
+ }
342
+ function resolveExecutableOnPath(executable, envPath = process.env.PATH) {
343
+ if (isAbsolute2(executable) && existsSync2(executable))
344
+ return executable;
345
+ for (const dir of (envPath ?? "").split(delimiter)) {
346
+ if (!dir)
347
+ continue;
348
+ const candidate = join4(dir, executable);
349
+ if (existsSync2(candidate))
350
+ return candidate;
351
+ }
352
+ return null;
353
+ }
354
+
355
+ // src/memory-confinement.ts
356
+ function createMemoryConfinementLauncher(input) {
357
+ return createMemoryConfinementLauncherWithAvailability(input, detectSandboxBackend());
358
+ }
359
+ export {
360
+ createMemoryConfinementLauncher
361
+ };
362
+
363
+ //# debugId=AE23F92737D03FFB64756E2164756E21
@@ -0,0 +1,18 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/permissions/memory-confinement-launcher.ts", "../src/permissions/sandbox-policy.ts", "../src/sandbox/policy.ts", "../src/utils/local-backend-paths.ts", "../src/sandbox/bwrap.ts", "../src/sandbox/seatbelt.ts", "../src/sandbox/wrap.ts", "../src/sandbox/availability.ts", "../src/memory-confinement.ts"],
4
+ "sourcesContent": [
5
+ "import { homedir } from \"node:os\";\nimport { basename, dirname, join, resolve } from \"node:path\";\n\nimport {\n buildMemorySubagentSandboxPolicy,\n getCrossBackendAgentsTreeRoots,\n} from \"@/permissions/sandbox-policy\";\nimport type { SandboxAvailability } from \"@/sandbox/availability\";\nimport { SANDBOX_ENV_VAR, type SandboxBackend } from \"@/sandbox/policy\";\nimport { wrapLauncher } from \"@/sandbox/wrap\";\n\nexport interface MemoryConfinementLauncherInput {\n /** Command and arguments for the process that should be confined. */\n launcher: string[];\n /**\n * Environment for the confined process. `MEMORY_DIR` (or\n * `LETTA_MEMORY_DIR`) identifies the memory root that stays writable.\n */\n env: NodeJS.ProcessEnv;\n}\n\nexport interface MemoryConfinementLauncherResult {\n /** Sandbox wrapper followed by the original launcher. */\n launcher: string[];\n /** Original environment plus the nested-sandbox sentinel. */\n env: NodeJS.ProcessEnv;\n backend: SandboxBackend;\n}\n\nfunction normalizeRoot(path: string): string {\n const trimmed = path.trim();\n const expanded = trimmed.startsWith(\"~/\")\n ? join(homedir(), trimmed.slice(2))\n : trimmed.startsWith(\"$HOME/\")\n ? join(homedir(), trimmed.slice(6))\n : trimmed;\n return resolve(expanded);\n}\n\nfunction resolveWritableMemoryRoots(env: NodeJS.ProcessEnv): string[] {\n const roots = new Set<string>();\n for (const value of [env.MEMORY_DIR, env.LETTA_MEMORY_DIR]) {\n if (!value?.trim()) continue;\n const root = normalizeRoot(value);\n roots.add(root);\n if (basename(root) === \"memory\") {\n roots.add(join(dirname(root), \"memory-worktrees\"));\n }\n }\n return [...roots];\n}\n\nfunction customHarnessWritableRoots(env: NodeJS.ProcessEnv): string[] {\n return [env.LETTA_LOCAL_BACKEND_DIR, env.LETTA_TRANSCRIPT_ROOT]\n .filter((value): value is string => Boolean(value?.trim()))\n .map(normalizeRoot);\n}\n\nexport function createMemoryConfinementLauncherWithAvailability(\n input: MemoryConfinementLauncherInput,\n availability: SandboxAvailability,\n): MemoryConfinementLauncherResult {\n if (input.launcher.length === 0) {\n throw new Error(\"Memory confinement requires a non-empty launcher.\");\n }\n\n const memoryRoots = resolveWritableMemoryRoots(input.env);\n if (memoryRoots.length === 0) {\n throw new Error(\n \"Memory confinement requires MEMORY_DIR or LETTA_MEMORY_DIR.\",\n );\n }\n if (!availability.backend) {\n throw new Error(\n `Memory confinement is unavailable: ${availability.reason}.`,\n );\n }\n\n const localBackendStorageDir =\n input.env.LETTA_LOCAL_BACKEND_DIR?.trim() || undefined;\n const policy = buildMemorySubagentSandboxPolicy({\n memoryRoots,\n agentsTreeRoots: getCrossBackendAgentsTreeRoots({\n env: input.env,\n localBackendStorageDir,\n }),\n harnessWritableRoots: customHarnessWritableRoots(input.env),\n });\n const launcher = wrapLauncher(input.launcher, policy, {\n backend: availability.backend,\n bwrapPath: availability.bwrapPath,\n });\n if (!launcher) {\n throw new Error(\"Memory confinement could not wrap the launcher.\");\n }\n\n return {\n launcher,\n env: { ...input.env, [SANDBOX_ENV_VAR]: availability.backend },\n backend: availability.backend,\n };\n}\n",
6
+ "import { existsSync, realpathSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { basename, dirname, isAbsolute, join, resolve } from \"node:path\";\n\nimport {\n buildFsSandboxPolicy,\n type FsSandboxPolicy,\n normalizeSandboxPath,\n} from \"@/sandbox/policy\";\nimport {\n getLocalBackendCrossAgentTreeRoot,\n getLocalBackendStorageDir,\n} from \"@/utils/local-backend-paths\";\n\n/**\n * Builders that translate agent/memory context into a concrete\n * {@link FsSandboxPolicy}. This is the bridge between the domain (agent ids,\n * memory roots) and the pure `@/sandbox` generators — it lives in\n * `permissions/` alongside the static guards it is meant to replace.\n *\n * Every root is canonicalized with realpath before it reaches a backend: both\n * Seatbelt and bwrap match rules against the kernel-resolved path, so a policy\n * built from a lexical path that passes through a symlink would silently match\n * nothing — i.e. a sandbox that allows everything. See `canonicalizeRoot`.\n */\n\n/** The per-agent tree to wall off, e.g. `/Users/me/.letta/agents`. */\nexport function getDefaultAgentsTreeRoot(homeDir: string = homedir()): string {\n return canonicalizeRoot(join(homeDir, \".letta\", \"agents\"));\n}\n\nexport interface CrossBackendAgentsTreeRootsOptions {\n homeDir?: string;\n env?: NodeJS.ProcessEnv;\n /** Explicit local backend storage dir, when already resolved by a caller. */\n localBackendStorageDir?: string | null;\n}\n\n/**\n * Every cross-agent memory tree the kernel sandbox must wall off. API/cloud\n * agents live under `~/.letta/agents`; local-backend agents live under\n * `<storage>/memfs`. A process running in either backend must deny both trees,\n * then carve back only the current/parent agent roots it is allowed to touch.\n */\nexport function getCrossBackendAgentsTreeRoots(\n options: CrossBackendAgentsTreeRootsOptions = {},\n): string[] {\n const homeDir = options.homeDir ?? homedir();\n const localBackendStorageDir =\n options.localBackendStorageDir ??\n getLocalBackendStorageDir(homeDir, options.env ?? process.env);\n\n return [\n getDefaultAgentsTreeRoot(homeDir),\n canonicalizeRoot(getLocalBackendCrossAgentTreeRoot(localBackendStorageDir)),\n ];\n}\n\n/**\n * The harness state directory, e.g. `/Users/me/.letta`. Used as the broad\n * writable base for memory subagents: they may write harness metadata anywhere\n * under it (settings, logs, conversations, transcripts, memory) but not the\n * repo/home/temp — while the cross-agent tree nested inside it stays denied.\n */\nexport function getLettaHomeRoot(homeDir: string = homedir()): string {\n return canonicalizeRoot(join(homeDir, \".letta\"));\n}\n\n/**\n * Resolve a path to the real (symlink-free) path the kernel will see. The leaf\n * may not exist yet (a file about to be created), so we realpath the nearest\n * existing ancestor and re-append the missing tail.\n */\nexport function canonicalizeRoot(input: string): string {\n const abs = isAbsolute(input) ? input : resolve(input);\n\n let dir = abs;\n const tail: string[] = [];\n while (!existsSync(dir)) {\n tail.unshift(basename(dir));\n const parent = dirname(dir);\n if (parent === dir) {\n // Reached the filesystem root without finding an existing ancestor.\n return normalizeSandboxPath(abs);\n }\n dir = parent;\n }\n\n try {\n const real = realpathSync(dir);\n return normalizeSandboxPath(tail.length ? join(real, ...tail) : real);\n } catch {\n return normalizeSandboxPath(abs);\n }\n}\n\n/** Whether a canonical path is the given root or nested inside it. */\nfunction isWithinRoot(path: string, root: string): boolean {\n return path === root || path.startsWith(`${root}/`);\n}\n\nfunction isAncestorOfRoot(path: string, root: string): boolean {\n const prefix = path === \"/\" ? \"/\" : `${path}/`;\n return root.startsWith(prefix);\n}\n\n/**\n * True when a canonical path IS one of the trees or an ancestor of one. Such a\n * path must never be carved back out: re-exposing a whole tree (or an ancestor\n * that contains it) would re-expose the denied roots under bwrap's\n * last-mount-wins semantics, and is too broad under Seatbelt as well.\n */\nfunction isTreeOrAncestorOfTree(\n path: string,\n canonicalTrees: string[],\n): boolean {\n return canonicalTrees.some(\n (tree) => path === tree || isAncestorOfRoot(path, tree),\n );\n}\n\n/**\n * Resolve the caller-supplied agents trees (canonicalizing each), or fall back\n * to both backend trees when none were given. Shared by the policy builders.\n */\nfunction resolveAgentsTreeRootsInput(roots?: string[]): string[] {\n return roots?.length\n ? roots.map(canonicalizeRoot)\n : getCrossBackendAgentsTreeRoots();\n}\n\n/**\n * Map memory roots to the agent directories to carve out of the walled-off\n * agents tree. A memory root under the tree\n * (`~/.letta/agents/<id>/memory[-worktrees]`) yields the whole agent dir\n * (`~/.letta/agents/<id>`); carving the *agent dir* rather than just `/memory`\n * keeps the cwd's immediate parent traversable, so a read-deny on the tree does\n * not empty the child env under Seatbelt. Roots outside the tree (a custom\n * `MEMORY_DIR`) are returned as-is.\n */\nexport function deriveSelfAgentRootsForTrees(\n memoryRoots: string[],\n agentsTreeRoots: string[] = getCrossBackendAgentsTreeRoots(),\n): string[] {\n const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);\n const out = new Set<string>();\n for (const root of memoryRoots) {\n const canon = canonicalizeRoot(root);\n const containingTree = canonicalTrees.find(\n (tree) => canon !== tree && isWithinRoot(canon, tree),\n );\n if (containingTree) {\n // A memory root nested inside a tree: carve back the whole agent dir so\n // the cwd's immediate parent stays traversable (Seatbelt empty-env bug).\n const leaf = basename(canon);\n const parentLeaf = basename(dirname(canon));\n out.add(\n leaf === \"memory\" || leaf === \"memory-worktrees\"\n ? dirname(canon)\n : parentLeaf === \"memory-worktrees\" ||\n (parentLeaf === \"memory\" && leaf === \".git\")\n ? dirname(dirname(canon))\n : canon,\n );\n continue;\n }\n // Outside every tree: keep as-is, unless it's a tree itself or an ancestor\n // of one (carving those back out would re-expose the denied tree).\n if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {\n out.add(canon);\n }\n }\n return [...out];\n}\n\nfunction deriveWritableMemoryRootsForTrees(\n memoryRoots: string[],\n agentsTreeRoots: string[],\n): string[] {\n const canonicalTrees = agentsTreeRoots.map(canonicalizeRoot);\n const out = new Set<string>();\n for (const root of memoryRoots) {\n const canon = canonicalizeRoot(root);\n // Never re-carve a whole denied tree, or an ancestor that would re-expose\n // that tree under bwrap's last-mount-wins semantics.\n if (!isTreeOrAncestorOfTree(canon, canonicalTrees)) {\n out.add(canon);\n }\n }\n return [...out];\n}\n\nexport interface MemorySubagentSandboxInput {\n /**\n * Memory roots the child may write to — typically the resolved\n * `MEMORY_DIR` plus its `memory-worktrees` sibling.\n */\n memoryRoots: string[];\n /** Additional roots to carve back read-only after denying agents trees. */\n readonlyRoots?: string[];\n /**\n * Harness state roots configured OUTSIDE `~/.letta` to also make writable —\n * `~/.letta` itself is always the base. The caller passes a custom\n * `LETTA_LOCAL_BACKEND_DIR` / `LETTA_TRANSCRIPT_ROOT` here so the in-process\n * child can still persist conversation/agent-state/transcripts when those are\n * relocated off the default tree. Usually empty (the defaults live under\n * `~/.letta`).\n */\n harnessWritableRoots?: string[];\n /**\n * The agents trees to wall off + carve self out of. Defaults to both\n * `~/.letta/agents` (API/cloud) and `lc-local-backend/memfs` (local). Each\n * agent's memory lives at `<tree>/<id>/memory` on both, so\n * {@link deriveSelfAgentRootsForTrees} carves the same way regardless of\n * backend.\n *\n * Resolved by the caller's layer (`tools/` / `agent/`, which may import\n * `backend/`): `permissions/` sits below `backend/`, so this builder takes the\n * already-resolved path rather than branching on a backend it cannot import.\n */\n agentsTreeRoots?: string[];\n}\n\n/**\n * Policy for the memory-subagent launch profile: it may read the filesystem broadly to do\n * its work, write only under the harness state dir (`~/.letta`), and not read or\n * write *other* agents' memory.\n *\n * The whole subagent process runs under this policy, so it is the sole\n * enforcement for these agents — the static guard is skipped for them. It covers\n * both axes:\n * - writes: `restrictWrites` denies writes everywhere except the base\n * `~/.letta` carve (and self memory). This scopes the agent's\n * non-deterministic work — it can persist memory + harness metadata\n * (settings, logs, conversations, transcripts) but cannot write the repo,\n * home, or temp. Carving the WHOLE `~/.letta` rather than enumerating each\n * harness file is deliberate: the harness writes many paths under it and the\n * set is unbounded, so a per-file carve would silently break as new writers\n * appear. The cross-agent tree nested inside `~/.letta` stays denied.\n * - cross-agent reads: the agents tree is read+write denied, with the agent's\n * own (and inherited parent's) directory carved back out READ-only.\n *\n * Carving the whole agent *directory* readable — not just `/memory` — is what\n * lets us deny the tree without re-triggering the empty-env bug: the subagent's\n * cwd is its memory dir inside the agents tree, and under Seatbelt a child\n * launches with an EMPTY environment if a cwd *ancestor* is read-denied. With\n * the agent dir (the cwd's immediate parent) readable, process init can traverse\n * to the cwd and the env survives.\n *\n * Both backend trees are denied by default so cloud/API agents cannot read local\n * agent memories and local agents cannot read cloud/API memories. Self memory is\n * re-carved writable in `writableRoots` because it is nested inside a denied\n * tree (the base `~/.letta` carve is overridden there by the deny).\n */\nexport function buildMemorySubagentSandboxPolicy(\n input: MemorySubagentSandboxInput,\n): FsSandboxPolicy {\n const agentsTreeRoots = resolveAgentsTreeRootsInput(input.agentsTreeRoots);\n\n // Writes are scoped to the harness state dir. `~/.letta` is the always-on base\n // (covers settings/logs/conversations/transcripts/memory under the defaults);\n // `harnessWritableRoots` adds any harness root relocated OUTSIDE `~/.letta`\n // (custom LETTA_LOCAL_BACKEND_DIR / LETTA_TRANSCRIPT_ROOT). These are emitted\n // BEFORE the cross-agent deny, so the nested tree is still walled off.\n const baseWritableRoots = [\n getLettaHomeRoot(),\n ...(input.harnessWritableRoots ?? []),\n ].map(canonicalizeRoot);\n\n return buildFsSandboxPolicy({\n baseWritableRoots,\n deniedRoots: agentsTreeRoots,\n readonlyRoots: [\n ...deriveSelfAgentRootsForTrees(input.memoryRoots, agentsTreeRoots),\n ...(input.readonlyRoots ?? []).map(canonicalizeRoot),\n ],\n // Self memory is nested inside the denied tree; re-carve it writable so the\n // deny (which overrides the base ~/.letta carve there) is itself overridden.\n writableRoots: deriveWritableMemoryRootsForTrees(\n input.memoryRoots,\n agentsTreeRoots,\n ),\n restrictWrites: true,\n });\n}\n\nexport interface CrossAgentSandboxInput {\n /**\n * Directories the agent may freely read+write inside the walled-off agents\n * tree — typically its own agent directory (`~/.letta/agents/<self-id>`).\n */\n selfRoots: string[];\n /** The agents trees to wall off (read+write). Defaults to both backends. */\n agentsTreeRoots?: string[];\n}\n\n/**\n * Policy for a normal agent that may use the whole filesystem but must not read\n * or write *other* agents' memory. This is the kernel-enforced replacement for\n * the static cross-agent guard.\n *\n * Walls off both backend agents trees (read + write) and carves the agent's own\n * directory back out. Writes elsewhere — the repo, the home dir, temp — stay\n * allowed (`restrictWrites: false`): the only thing this policy removes is\n * access to other agents' memory, exactly like the guard it replaces.\n *\n * Unlike the memory-subagent policy, this one DOES deny reads of the agents tree.\n * That is only safe when the process cwd is outside the tree (the parent\n * agent's cwd is the repo); a cwd inside a read-denied subtree launches with an\n * empty environment under Seatbelt. Callers must enforce that precondition.\n */\nexport function buildCrossAgentSandboxPolicy(\n input: CrossAgentSandboxInput,\n): FsSandboxPolicy {\n const agentsTreeRoots = resolveAgentsTreeRootsInput(input.agentsTreeRoots);\n\n return buildFsSandboxPolicy({\n deniedRoots: agentsTreeRoots,\n writableRoots: input.selfRoots.map(canonicalizeRoot),\n restrictWrites: false,\n });\n}\n",
7
+ "import { posix, win32 } from \"node:path\";\n\n/**\n * A filesystem sandbox policy, expressed entirely in concrete absolute paths.\n *\n * This module is a pure leaf: it knows nothing about agents, launch profiles, or\n * how paths are derived. Callers resolve agent ids / memory roots into paths\n * (via `@/permissions/memory-paths`) and hand the finished policy here. That\n * keeps the OS-specific generators (`seatbelt.ts`, `bwrap.ts`) trivially\n * testable and out of the domain layer graph.\n *\n * Enforcement semantics (both backends implement the same model):\n *\n * - `baseWritableRoots` write re-allowed under these, emitted BEFORE\n * `deniedRoots` so a denied root nested inside still wins.\n * Used to grant a broad harness dir (e.g. all of\n * `~/.letta`) while keeping the cross-agent tree denied —\n * so a memory subagent may write harness state anywhere\n * under `~/.letta` but not the repo/home/temp.\n * - `deniedRoots` read + write denied (e.g. `~/.letta/agents`).\n * - `readonlyRoots` read re-allowed, write stays denied. Overrides denied.\n * - `writableRoots` read + write re-allowed. Overrides denied, the global\n * write-deny, AND `baseWritableRoots` — for a self carve\n * nested inside a denied root (self memory).\n * - `restrictWrites` when true, writes are denied *everywhere* except\n * `baseWritableRoots`/`writableRoots` (write-scoped profile). When\n * false, writes are allowed by default except under\n * `deniedRoots` (cross-agent profile — the normal agent that\n * simply may not touch other agents' memory).\n *\n * Reads are never globally restricted: an agent can read the whole filesystem\n * to do its work, minus the other-agent directories in `deniedRoots`.\n *\n * Specificity is expressed through ordering, not nesting depth. The emitted\n * order is: global write-deny → `baseWritableRoots` → `deniedRoots` →\n * `writableRoots` → `readonlyRoots`. So a broad base carve is overridden by a\n * nested deny, which is in turn overridden by a still-more-specific self carve.\n */\nexport interface FsSandboxPolicy {\n baseWritableRoots: string[];\n deniedRoots: string[];\n readonlyRoots: string[];\n writableRoots: string[];\n restrictWrites: boolean;\n}\n\n/** Backend tag, also the value of the `LETTA_SANDBOX` env sentinel. */\nexport type SandboxBackend = \"seatbelt\" | \"bwrap\";\n\n/**\n * Env var set inside a sandboxed process. Mirrors Codex's `CODEX_SANDBOX`.\n * Tools and nested logic can read it to detect that they are already confined\n * (and, for the re-exec pattern, to avoid wrapping themselves twice).\n */\nexport const SANDBOX_ENV_VAR = \"LETTA_SANDBOX\";\n\nexport interface BuildPolicyOptions {\n /**\n * Broad write carves emitted BEFORE `deniedRoots` (a nested deny still wins).\n * e.g. all of `~/.letta` so a memory subagent can write harness state but not\n * the repo/home/temp.\n */\n baseWritableRoots?: string[];\n /** Roots to wall off (read+write), e.g. `~/.letta/agents`. */\n deniedRoots?: string[];\n /** Paths to re-expose read-only (e.g. a subagent's parent memory dir). */\n readonlyRoots?: string[];\n /** Paths to re-expose read-write (self agent/memory dir, $TMPDIR, /tmp). */\n writableRoots?: string[];\n /** Memory mode: deny writes everywhere except the writable roots. */\n restrictWrites?: boolean;\n}\n\n/**\n * Assemble an {@link FsSandboxPolicy} from plain paths, normalizing and\n * de-duplicating each set. Pure — no filesystem or agent-context access.\n */\nexport function buildFsSandboxPolicy(\n options: BuildPolicyOptions,\n): FsSandboxPolicy {\n return {\n baseWritableRoots: normalizeRoots(options.baseWritableRoots ?? []),\n deniedRoots: normalizeRoots(options.deniedRoots ?? []),\n readonlyRoots: normalizeRoots(options.readonlyRoots ?? []),\n writableRoots: normalizeRoots(options.writableRoots ?? []),\n restrictWrites: options.restrictWrites ?? false,\n };\n}\n\n/**\n * Normalize a path for use in a sandbox rule: absolute, forward slashes, no\n * trailing slash. Relative inputs are resolved against `/` so a malformed\n * policy can never silently scope a rule to the current working directory.\n */\nexport function normalizeSandboxPath(path: string): string {\n const trimmed = path.trim();\n const absolute =\n posix.isAbsolute(trimmed) || win32.isAbsolute(trimmed)\n ? trimmed\n : posix.resolve(\"/\", trimmed);\n const forward = absolute.replace(/\\\\/g, \"/\");\n return forward.replace(/\\/+$/, \"\") || \"/\";\n}\n\nfunction normalizeRoots(roots: string[]): string[] {\n const seen = new Set<string>();\n for (const root of roots) {\n if (!root || !root.trim()) continue;\n seen.add(normalizeSandboxPath(root));\n }\n return [...seen];\n}\n",
8
+ "import { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\n/**\n * Env override for the local-backend storage dir (defaults to\n * `~/.letta/lc-local-backend`).\n */\nexport const LOCAL_BACKEND_DIR_ENV = \"LETTA_LOCAL_BACKEND_DIR\";\n\n/**\n * Root dir holding all local-backend on-disk state. Pure path resolution (home\n * dir + one env override).\n *\n * Lives in `utils/` (the bottom layer) so it can be shared by both `backend/`\n * — which owns the local store — and the `permissions/` cross-agent guard, which\n * sits below `backend/` and cannot import it but still needs to know where\n * local memory lives to wall off cross-agent access for in-process file tools.\n */\nexport function getLocalBackendStorageDir(\n homeDir: string = homedir(),\n env: NodeJS.ProcessEnv = process.env,\n): string {\n return (\n env[LOCAL_BACKEND_DIR_ENV] ?? join(homeDir, \".letta\", \"lc-local-backend\")\n );\n}\n\n/**\n * The tree holding every local-backend agent's memory (`<storage>/memfs`) — the\n * cross-agent boundary the filesystem sandbox walls off, analogous to\n * `~/.letta/agents` on the API backend. Each agent's memory lives at\n * `<this>/<agentId>/memory`, so self is carved the same way on both backends.\n */\nexport function getLocalBackendCrossAgentTreeRoot(\n storageDir: string = getLocalBackendStorageDir(),\n): string {\n return join(storageDir, \"memfs\");\n}\n",
9
+ "import type { FsSandboxPolicy } from \"./policy.js\";\n\n/**\n * Linux bubblewrap backend.\n *\n * We shell out to `bwrap`, building a mount namespace that mirrors the same\n * policy model as Seatbelt:\n *\n * - The root filesystem is bound `--ro-bind` (write-scoped profile,\n * default-deny writes) or `--bind` (cross-agent profile, default-allow writes). This single\n * choice implements `restrictWrites` for free: under a read-only root, the\n * only writable paths are the explicit `--bind` carveouts.\n * - Each denied root is masked with `--tmpfs`, so other agents' directories\n * are not merely unwritable but *absent* — unreadable and unenumerable,\n * strictly stronger than the static guard.\n * - readonly / writable carveouts are re-bound on top. bwrap creates the\n * mountpoints inside the tmpfs as needed, so a self-memory dir nested in a\n * masked agents tree reappears.\n *\n * No `--unshare-net`: network stays open (out of scope for memory isolation).\n * `--die-with-parent` ensures the sandbox tears down with the agent process,\n * backing up the process-group kill in `shell-runner.ts`.\n *\n * Mount order matters — later operations layer over earlier ones: root → dev →\n * proc → base writable → mask denied → restore readonly → restore writable.\n * The base-writable binds come BEFORE the masks so a denied root nested inside a\n * broad base carve (the cross-agent tree under `~/.letta`) is still masked.\n */\n\n/** Default discovery name; availability probing may substitute a bundled path. */\nexport const BWRAP_BIN = \"bwrap\";\n\n/**\n * Build the bwrap flag list (everything between the binary and the `--`\n * separator). The caller prepends the bwrap path and appends\n * `\"--\"` + the inner launcher.\n */\nexport function buildBwrapArgs(policy: FsSandboxPolicy): string[] {\n const args: string[] = [];\n\n // Root view: read-only for write-scoped profiles, writable for cross-agent.\n args.push(policy.restrictWrites ? \"--ro-bind\" : \"--bind\", \"/\", \"/\");\n\n // Minimal writable /dev (gives us /dev/null, /dev/urandom, ptys) and a fresh\n // /proc so the masked root doesn't leak host process state.\n args.push(\"--dev\", \"/dev\");\n args.push(\"--proc\", \"/proc\");\n\n // Base writable roots: re-bind a broad harness dir (~/.letta) read-write on top\n // of the read-only root. Emitted BEFORE the masks below so a denied root nested\n // inside (the cross-agent tree) is still masked — the ancestor-carve hazard is\n // intentional and safe HERE precisely because the mask runs last. `-try` for\n // the same not-yet-created tolerance as the other carves.\n for (const root of policy.baseWritableRoots) {\n args.push(\"--bind-try\", root, root);\n }\n\n // Mask each denied root with an empty tmpfs.\n //\n // HAZARD (ancestor carve-out): the restore binds below run *after* these\n // masks, and bwrap is last-mount-wins for overlapping paths. A carve-out must\n // therefore be a *descendant of* (or disjoint from) every denied root — never\n // an ancestor. An ancestor carve-out (e.g. binding `/tmp` writable when the\n // agents tree lives under `/tmp`) re-binds the whole subtree on top of the\n // mask and silently re-exposes the denied roots. Callers must not produce\n // such roots; this is why the memory-subagent profile scopes writes to memory\n // roots and does not carve a temp dir. (Seatbelt has no equivalent: it matches\n // most-specific deny rules, independent of order.)\n for (const root of policy.deniedRoots) {\n args.push(\"--tmpfs\", root);\n }\n\n // Restore readonly/writable carveouts on top of the masks. Use the `-try`\n // variants: a carve-out root may not exist on disk yet — e.g. the\n // `memory-worktrees` sibling that `resolveAllowedMemoryRoots` always lists but\n // which is created lazily. Plain `--ro-bind`/`--bind` abort the *entire*\n // sandbox when the source is missing, so a not-yet-created root would fail the\n // spawn outright; `-try` skips a missing source (nothing to expose anyway, and\n // the denied-root masks above still hold). Seatbelt tolerates non-existent\n // paths natively, so this keeps the two backends behaviorally aligned.\n for (const root of policy.readonlyRoots) {\n args.push(\"--ro-bind-try\", root, root);\n }\n\n for (const root of policy.writableRoots) {\n args.push(\"--bind-try\", root, root);\n }\n\n args.push(\"--die-with-parent\");\n return args;\n}\n",
10
+ "import type { FsSandboxPolicy } from \"./policy.js\";\n\n/**\n * macOS Seatbelt backend.\n *\n * We shell out to `/usr/bin/sandbox-exec` with an inline SBPL profile (`-p`).\n * Concrete paths are passed as `-D` parameters and referenced in the profile as\n * `(param \"NAME\")`, so the profile text never needs SBPL string escaping and\n * paths with spaces / quotes are safe (we spawn argv directly, no shell).\n *\n * The profile is `(allow default)` plus targeted denies — deliberately narrower\n * than Codex's Chrome-derived `(deny default)` base. Our threat model is\n * filesystem scoping for memory isolation, not general untrusted-code\n * confinement, so allow-default keeps network, signals, ttys, and arbitrary dev\n * tools working untouched while the FS rules do the isolation. SBPL is\n * last-match-wins, which is why the deny/allow ordering below matters.\n */\n\n/** Hardcoded path — never resolved via PATH, to defend against a planted\n * `sandbox-exec` earlier on PATH (same rationale as Codex). */\nexport const SANDBOX_EXEC_PATH = \"/usr/bin/sandbox-exec\";\n\ninterface SeatbeltDefine {\n name: string;\n value: string;\n}\n\n/**\n * Build the SBPL profile text plus the `-D NAME=value` defines it references.\n * Exposed for snapshot testing; `buildSeatbeltArgs` is what callers use.\n */\nexport function buildSeatbeltProfile(policy: FsSandboxPolicy): {\n profile: string;\n defines: SeatbeltDefine[];\n} {\n const defines: SeatbeltDefine[] = [];\n const lines: string[] = [\"(version 1)\", \"(allow default)\"];\n\n // 1. Memory mode: deny all writes globally. Keep device writes (/dev/null,\n // ttys, pipes) working — countless tools depend on them and they are\n // irrelevant to filesystem isolation.\n if (policy.restrictWrites) {\n lines.push('(deny file-write* (subpath \"/\"))');\n lines.push('(allow file-write* (subpath \"/dev\"))');\n }\n\n // 2. Base writable roots: re-allow writes under a broad harness dir (~/.letta).\n // Emitted AFTER the global write-deny but BEFORE the denied roots, so a\n // cross-agent tree nested inside still gets walled off in step 3.\n policy.baseWritableRoots.forEach((root, i) => {\n const name = `BASEWRITABLE_${i}`;\n defines.push({ name, value: root });\n lines.push(`(allow file-write* (subpath (param \"${name}\")))`);\n });\n\n // 3. Wall off the denied roots entirely (read + write). Overrides the base\n // writable above for the cross-agent subtree.\n policy.deniedRoots.forEach((root, i) => {\n const name = `DENIED_${i}`;\n defines.push({ name, value: root });\n lines.push(`(deny file-read* file-write* (subpath (param \"${name}\")))`);\n // Git linked worktrees may stat denied ancestor directories while resolving\n // an allowed worktree path. Re-allow metadata on the denied root itself so\n // path canonicalization can succeed without exposing directory contents or\n // file data from other agents.\n lines.push(`(allow file-read-metadata (literal (param \"${name}\")))`);\n });\n\n // 4. Restore writable roots (read + write). Emitted after every deny above so\n // it wins — including for a self-memory dir nested inside a denied root.\n policy.writableRoots.forEach((root, i) => {\n const name = `WRITABLE_${i}`;\n defines.push({ name, value: root });\n lines.push(`(allow file-read* file-write* (subpath (param \"${name}\")))`);\n });\n\n // 5. Restore readonly roots (read only). Wins over the read-deny; the\n // corresponding write-deny still stands, so these stay read-only.\n policy.readonlyRoots.forEach((root, i) => {\n const name = `READONLY_${i}`;\n defines.push({ name, value: root });\n lines.push(`(allow file-read* (subpath (param \"${name}\")))`);\n });\n\n return { profile: `${lines.join(\"\\n\")}\\n`, defines };\n}\n\n/**\n * Build the argv tail for `sandbox-exec`: `[\"-p\", <profile>, \"-DNAME=value\",\n * ...]`. The caller prepends {@link SANDBOX_EXEC_PATH} and appends\n * `\"--\"` + the inner launcher.\n */\nexport function buildSeatbeltArgs(policy: FsSandboxPolicy): string[] {\n const { profile, defines } = buildSeatbeltProfile(policy);\n const args = [\"-p\", profile];\n for (const { name, value } of defines) {\n args.push(`-D${name}=${value}`);\n }\n return args;\n}\n",
11
+ "import { BWRAP_BIN, buildBwrapArgs } from \"./bwrap.js\";\nimport type { FsSandboxPolicy, SandboxBackend } from \"./policy.js\";\nimport { buildSeatbeltArgs, SANDBOX_EXEC_PATH } from \"./seatbelt.js\";\n\nexport interface WrapOptions {\n /** Which backend to render for. `null` disables wrapping (returns null). */\n backend: SandboxBackend | null;\n /** Resolved bwrap binary path (system or bundled). Defaults to `bwrap`. */\n bwrapPath?: string;\n}\n\n/**\n * Wrap an inner launcher (e.g. `[\"/bin/zsh\", \"-c\", cmd]`) so it runs under the\n * given sandbox backend with the given policy. Returns the wrapped argv, or\n * `null` when no backend is available — in which case the caller spawns the\n * launcher unchanged (falling back to the static guard tier).\n *\n * Backend selection is the caller's job (see `availability.ts`); this function\n * only renders argv, which keeps it a pure, snapshot-testable transform.\n */\nexport function wrapLauncher(\n launcher: string[],\n policy: FsSandboxPolicy,\n options: WrapOptions,\n): string[] | null {\n if (!options.backend) return null;\n if (launcher.length === 0) return null;\n\n switch (options.backend) {\n case \"seatbelt\":\n return [\n SANDBOX_EXEC_PATH,\n ...buildSeatbeltArgs(policy),\n \"--\",\n ...launcher,\n ];\n case \"bwrap\":\n return [\n options.bwrapPath ?? BWRAP_BIN,\n ...buildBwrapArgs(policy),\n \"--\",\n ...launcher,\n ];\n }\n}\n",
12
+ "import { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { delimiter, isAbsolute, join } from \"node:path\";\nimport type { SandboxBackend } from \"./policy.js\";\nimport { SANDBOX_EXEC_PATH } from \"./seatbelt.js\";\n\nexport interface SandboxAvailability {\n /** The usable backend, or null when none is available on this host. */\n backend: SandboxBackend | null;\n /** Resolved bwrap binary path, when `backend === \"bwrap\"`. */\n bwrapPath?: string;\n /** Human-readable explanation, primarily for the null case. */\n reason: string;\n}\n\nexport interface DetectOptions {\n /** Override the platform (for tests). Defaults to `process.platform`. */\n platform?: NodeJS.Platform;\n /** Bypass the module-level cache (for tests / re-probing). */\n force?: boolean;\n}\n\nlet cached: SandboxAvailability | null = null;\nconst warnedUnavailableContexts = new Set<string>();\n\n/**\n * Detect which filesystem-sandbox backend works on this host, probing for real\n * (Seatbelt: binary presence; bwrap: an actual user-namespace mount probe).\n * Result is cached for the process since it cannot change mid-run.\n */\nexport function detectSandboxBackend(\n options: DetectOptions = {},\n): SandboxAvailability {\n const platform = options.platform ?? process.platform;\n\n if (!options.force && cached && !options.platform) {\n return cached;\n }\n\n const result = probe(platform);\n\n if (!options.platform) {\n cached = result;\n }\n return result;\n}\n\n/** Clear the cached probe result (tests only). */\nexport function resetSandboxAvailabilityCache(): void {\n cached = null;\n}\n\n/**\n * Whether the memory-subagent filesystem sandbox is enabled. It is **on by\n * default**: memory subagents (reflection, memory, init, history-analyzer) run\n * as whole confined processes with a scoped write surface, and there is no\n * interactive approve/deny flow that could stand in for it. Set\n * `LETTA_FS_SANDBOX=0` (or `false`) to opt out entirely. When no backend is\n * available on the host, {@link detectSandboxBackend} returns `{backend:null}`\n * and every sandbox entry point no-ops regardless of this flag.\n *\n * Lives in this leaf so both subagent spawning (agent layer) and parent Bash\n * wrapping (tools layer) gate on the same env var without importing each other.\n */\nexport function isFsSandboxEnabled(\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n const value = env.LETTA_FS_SANDBOX?.trim().toLowerCase();\n // Default on: only an explicit off-switch disables it.\n return value !== \"0\" && value !== \"false\";\n}\n\n/**\n * Whether the cross-agent shell sandbox (per-shell-command confinement of the\n * agent process's spawned shells) is enabled. It is **off by default**: an\n * interactive agent's own shells walling off other agents' memory broke\n * legitimate workflows (agents inspecting `~/.letta/agents`) with kernel\n * `Operation not permitted` errors that no permission mode could approve\n * through. Set `LETTA_FS_SANDBOX=1` (or `true`) to opt in — recommended for\n * multi-tenant deployments (app server, experiment runners) where one host\n * runs many agents that must not read each other's memory.\n *\n * `LETTA_FS_SANDBOX` semantics across both checks:\n * - unset → memory subagents sandboxed; agent shells unconfined\n * - `1`/`true` → both sandboxed\n * - `0`/`false` → nothing sandboxed\n */\nexport function isShellSandboxEnabled(\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n const value = env.LETTA_FS_SANDBOX?.trim().toLowerCase();\n // Opt-in only: an explicit on-switch enables it.\n return value === \"1\" || value === \"true\";\n}\n\n/**\n * Emit a loud, once-per-process warning when sandboxing was requested but this\n * host cannot provide a kernel backend. We intentionally continue rather than\n * fail closed: users can still work, but should know filesystem isolation is\n * degraded on this host.\n */\nexport function warnSandboxBackendUnavailable(\n availability: SandboxAvailability,\n context: string,\n): void {\n if (availability.backend) return;\n const key = `${context}:${availability.reason}`;\n if (warnedUnavailableContexts.has(key)) return;\n warnedUnavailableContexts.add(key);\n console.warn(\n `[sandbox] WARNING: ${context} requested filesystem isolation, but no ` +\n `kernel sandbox backend is available (${availability.reason}). ` +\n `Continuing without filesystem sandbox isolation.`,\n );\n}\n\nfunction probe(platform: NodeJS.Platform): SandboxAvailability {\n if (platform === \"darwin\") {\n if (existsSync(SANDBOX_EXEC_PATH)) {\n return { backend: \"seatbelt\", reason: \"sandbox-exec available\" };\n }\n return {\n backend: null,\n reason: `${SANDBOX_EXEC_PATH} not found`,\n };\n }\n\n if (platform === \"linux\") {\n return probeBwrap();\n }\n\n return {\n backend: null,\n reason: `no filesystem sandbox backend for platform \"${platform}\"`,\n };\n}\n\nfunction probeBwrap(): SandboxAvailability {\n // `bwrap` must exist on PATH (a bundled fallback can be wired in later).\n const bwrapPath = resolveExecutableOnPath(\"bwrap\");\n if (!bwrapPath) {\n return { backend: null, reason: \"bwrap not found on PATH\" };\n }\n\n const version = spawnSync(bwrapPath, [\"--version\"], { timeout: 5000 });\n if (version.error || version.status !== 0) {\n return { backend: null, reason: \"bwrap not found on PATH\" };\n }\n\n // Confirm unprivileged user namespaces actually work (they don't in WSL1 or\n // some hardened/container hosts). A read-only root + /bin/true is the\n // cheapest mount that exercises the namespace machinery.\n const userns = spawnSync(\n bwrapPath,\n [\"--ro-bind\", \"/\", \"/\", \"--unshare-user\", \"/bin/true\"],\n { timeout: 5000 },\n );\n if (userns.error || userns.status !== 0) {\n return {\n backend: null,\n reason: \"bwrap present but user namespaces are unavailable\",\n };\n }\n\n return { backend: \"bwrap\", bwrapPath, reason: \"bwrap available\" };\n}\n\nfunction resolveExecutableOnPath(\n executable: string,\n envPath: string | undefined = process.env.PATH,\n): string | null {\n if (isAbsolute(executable) && existsSync(executable)) return executable;\n for (const dir of (envPath ?? \"\").split(delimiter)) {\n if (!dir) continue;\n const candidate = join(dir, executable);\n if (existsSync(candidate)) return candidate;\n }\n return null;\n}\n",
13
+ "import {\n createMemoryConfinementLauncherWithAvailability,\n type MemoryConfinementLauncherInput,\n type MemoryConfinementLauncherResult,\n} from \"@/permissions/memory-confinement-launcher\";\nimport { detectSandboxBackend } from \"@/sandbox/availability\";\n\nexport type {\n MemoryConfinementLauncherInput,\n MemoryConfinementLauncherResult,\n} from \"@/permissions/memory-confinement-launcher\";\n\n/**\n * Wrap a process in the same fail-closed filesystem policy used by Letta\n * Code's unattended memory subagents.\n *\n * The process can read the host broadly, write harness state and its own\n * memory, and cannot read or write other agents' memory. Throws when no\n * supported kernel sandbox is available rather than silently running with a\n * weaker policy.\n */\nexport function createMemoryConfinementLauncher(\n input: MemoryConfinementLauncherInput,\n): MemoryConfinementLauncherResult {\n return createMemoryConfinementLauncherWithAvailability(\n input,\n detectSandboxBackend(),\n );\n}\n"
14
+ ],
15
+ "mappings": "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",
16
+ "debugId": "AE23F92737D03FFB64756E2164756E21",
17
+ "names": []
18
+ }
@@ -1,4 +1,4 @@
1
- import type { ChannelAccount, ChannelAdapter, ChannelChatType, ChannelDefaultPermissionMode, ChannelRoute, DiscordChannelMode, DmPolicy, OutboundChannelMessage, SignalGroupMode, SlackAllowBotsMode, SlackChannelMode, TelegramGroupMode, WhatsAppGroupMode } from "./types";
1
+ import type { ChannelAccount, ChannelAdapter, ChannelAllowBotsMode, ChannelChatType, ChannelDefaultPermissionMode, ChannelRoute, DiscordChannelMode, DmPolicy, OutboundChannelMessage, SignalGroupMode, SlackChannelMode, TelegramGroupMode, WhatsAppGroupMode } from "./types";
2
2
  export interface ChannelPluginMetadata {
3
3
  id: string;
4
4
  displayName: string;
@@ -119,7 +119,7 @@ export interface ChannelPluginAccountPatch {
119
119
  threadPolicyByChannel?: Record<string, boolean>;
120
120
  acknowledgeMessageReaction?: boolean;
121
121
  listenMode?: boolean;
122
- allowBots?: SlackAllowBotsMode;
122
+ allowBots?: ChannelAllowBotsMode;
123
123
  removeStaleRoutes?: boolean;
124
124
  inboundDebounceMs?: number;
125
125
  selfChatMode?: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin-types.d.ts","sourceRoot":"","sources":["../../../src/channels/plugin-types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"plugin-types.d.ts","sourceRoot":"","sources":["../../../src/channels/plugin-types.ts"],"names":[],"mappings":"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"}
@@ -399,7 +399,9 @@ export type DmPolicy = "pairing" | "allowlist" | "open";
399
399
  */
400
400
  export type ChannelGroupSenderPolicy = "open" | "allowlist";
401
401
  export type SlackChannelMode = "socket";
402
- export type SlackAllowBotsMode = false | "mentions";
402
+ export type ChannelAllowBotsMode = false | "mentions";
403
+ export type SlackAllowBotsMode = ChannelAllowBotsMode;
404
+ export type DiscordAllowBotsMode = ChannelAllowBotsMode;
403
405
  export type TelegramGroupMode = "open" | "mention-only";
404
406
  export type WhatsAppGroupMode = "disabled" | "mention" | "open";
405
407
  export type SignalGroupMode = "disabled" | "mention" | "open";
@@ -534,6 +536,12 @@ export interface DiscordChannelConfig {
534
536
  * Clamped to `0..10000`.
535
537
  */
536
538
  inboundDebounceMs?: number;
539
+ /**
540
+ * Bot-authored inbound policy. Default false drops bot messages. "mentions"
541
+ * accepts only explicit foreign bot mentions. There is intentionally no
542
+ * accept-all mode until Letta has a shared pair-loop guard.
543
+ */
544
+ allowBots?: DiscordAllowBotsMode;
537
545
  }
538
546
  export interface WhatsAppChannelConfig {
539
547
  channel: "whatsapp";
@@ -704,6 +712,12 @@ export interface DiscordChannelAccount extends ChannelAccountBase {
704
712
  * Clamped to `0..10000`.
705
713
  */
706
714
  inboundDebounceMs?: number;
715
+ /**
716
+ * Bot-authored inbound policy. Default false drops bot messages. "mentions"
717
+ * accepts only explicit foreign bot mentions. There is intentionally no
718
+ * accept-all mode until Letta has a shared pair-loop guard.
719
+ */
720
+ allowBots?: DiscordAllowBotsMode;
707
721
  }
708
722
  export interface WhatsAppChannelAccount extends ChannelAccountBase {
709
723
  channel: "whatsapp";