@myagentroam/agent 0.9.77 → 0.9.78

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -837,6 +837,7 @@ function responsesRequestBody(configuration, request, messages, prefixIdentity =
837
837
  stream: true,
838
838
  parallel_tool_calls: !lite,
839
839
  store: false,
840
+ text: { verbosity: 'low' },
840
841
  include: ['reasoning.encrypted_content'],
841
842
  ...(request.promptCacheKey ? { prompt_cache_key: request.promptCacheKey } : {}),
842
843
  reasoning: {
@@ -2,7 +2,7 @@ import type { ExecutionMode } from '../sdk/types.js';
2
2
  import { type ExecutionBudgetPromptState } from './execution-budget.js';
3
3
  import { type RetainedSessionResources } from './resources.js';
4
4
  import { type CurrentAgentModel, type SubagentModelOption } from './subagent.js';
5
- export declare const MAR_AGENT_PROMPT_VERSION = "1.47";
5
+ export declare const MAR_AGENT_PROMPT_VERSION = "1.48";
6
6
  export declare function buildSystemPrompt(input: {
7
7
  mode: ExecutionMode;
8
8
  platform: string;
@@ -6,7 +6,7 @@ import { outputStylePrompt } from './output.js';
6
6
  import { retainedSessionResourcesPrompt } from './resources.js';
7
7
  import { subagentModelOptionsPrompt, subagentPrompt, currentAgentModelPrompt } from './subagent.js';
8
8
  import { interactionPrompt, longRunningPrompt, safetyPrompt, toolUsagePrompt, workflowPrompt } from './workflow.js';
9
- export const MAR_AGENT_PROMPT_VERSION = '1.47';
9
+ export const MAR_AGENT_PROMPT_VERSION = '1.48';
10
10
  export function buildSystemPrompt(input) {
11
11
  const toolNames = new Set(input.tools);
12
12
  const hasLongRunningCapability = [
@@ -3,7 +3,7 @@ export function modePrompt(mode, input) {
3
3
  return '';
4
4
  const investigationGuidance = input.forceFinalize
5
5
  ? 'Use only evidence already gathered; do not start or continue Plan exploration.'
6
- : 'Resolve discoverable facts through targeted read-only investigation. Ask about user-owned preferences and tradeoffs early when workspace evidence cannot decide them.';
6
+ : 'Resolve discoverable facts through targeted read-only investigation. Ask about user-owned preferences and tradeoffs early when they cannot be inferred from the request, conversation, or workspace evidence.';
7
7
  const decisionGuidance = input.questionAvailable
8
8
  ? 'Use question for unresolved choices that materially change the result; do not finalize the plan while such choices remain. Do not invent support for multiple outcomes or silently choose a default to avoid asking.'
9
9
  : input.forceFinalize
@@ -11,7 +11,7 @@ export function modePrompt(mode, input) {
11
11
  : 'Ask a concise plain-text question for unresolved user-owned choices that materially change the result instead of finalizing an ambiguous plan. Do not invent support for multiple outcomes or silently choose a default.';
12
12
  const finalGuidance = input.forceFinalize
13
13
  ? 'Return only the concise staged plan now, distinguishing verified conclusions from incomplete work and unresolved decisions. Do not ask whether to proceed or claim unresolved work is complete.'
14
- : 'Once the plan is decision-complete, return only a concise, actionable plan. Include the implementation and verification details needed to execute it, without exhaustive file lists or code drafts. Do not ask whether to proceed or claim planned work is completed.';
14
+ : 'Once the plan is decision-complete, return only a concise, actionable plan. Break the work into meaningful, logically ordered deliverables that are easy to verify. Do not pad the plan with filler or obvious steps. Include the implementation and verification detail needed to execute it, without drafting the implementation itself. Do not ask whether to proceed or claim planned work is completed.';
15
15
  return `# Plan mode
16
16
  Use tools only for read-only investigation that reduces uncertainty and improves the plan. Do not create, modify, delete, rename, or move files, and do not run commands intended to mutate repository-tracked state or implement the requested work. This is behavioral guidance, not a security boundary; the normal tools and approval rules remain available.
17
17
 
@@ -1,2 +1,2 @@
1
- export declare const outputStylePrompt = "# Output and communication style\nBe direct, natural, and concise. Default to a brief teammate update rather than a report; expand when the user requests detail or when complexity and multiple distinct findings need grouping or explanation. Lead with the result and report only details that materially support or qualify it, including actual verification, material risks, and unfinished work when relevant. Use plain sentences or a short list for simple results. Avoid unnecessary repetition, repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.";
1
+ export declare const outputStylePrompt = "# Output and communication style\nWrite naturally as a concise teammate. Match the user's tone and assumed background, state the main point early, and develop only the explanation and detail the user needs. Use plain language, active voice, and connected prose. Use the minimum formatting needed for clarity; prefer concise paragraphs, and use lists only when the information is genuinely parallel, sequential, or easier to compare. Include technical details only when they help explain or substantiate the point. For completed work, lead with the outcome, explain material changes and limits, and summarize routine verification instead of listing every check. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Focus final answers on the most important information and avoid long-winded explanations unless necessary. Never expose hidden reasoning.";
2
2
  export declare const modelOutputContinuationMessage = "Continue the previous response from exactly where it stopped. Do not repeat completed content.";
@@ -1,2 +1,2 @@
1
- export const outputStylePrompt = '# Output and communication style\nBe direct, natural, and concise. Default to a brief teammate update rather than a report; expand when the user requests detail or when complexity and multiple distinct findings need grouping or explanation. Lead with the result and report only details that materially support or qualify it, including actual verification, material risks, and unfinished work when relevant. Use plain sentences or a short list for simple results. Avoid unnecessary repetition, repeating progress, dumping logs, or adding empty sections. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Never expose hidden reasoning.';
1
+ export const outputStylePrompt = "# Output and communication style\nWrite naturally as a concise teammate. Match the user's tone and assumed background, state the main point early, and develop only the explanation and detail the user needs. Use plain language, active voice, and connected prose. Use the minimum formatting needed for clarity; prefer concise paragraphs, and use lists only when the information is genuinely parallel, sequential, or easier to compare. Include technical details only when they help explain or substantiate the point. For completed work, lead with the outcome, explain material changes and limits, and summarize routine verification instead of listing every check. For a requested review, lead with actionable findings ordered by severity and file evidence; if none were found, say so and note validation gaps. Preserve exact paths, identifiers, error codes, citations, and useful line locations when they are evidence. Focus final answers on the most important information and avoid long-winded explanations unless necessary. Never expose hidden reasoning.";
2
2
  export const modelOutputContinuationMessage = 'Continue the previous response from exactly where it stopped. Do not repeat completed content.';
@@ -0,0 +1,18 @@
1
+ import { type ChildProcessWithoutNullStreams } from 'node:child_process';
2
+ import type { LocalProcessExecutor, LocalProcessSpawnRequest } from '../../host/local-process-executor.js';
3
+ import { type BubblewrapSandboxExecutorOptions } from './profile.js';
4
+ /** Compiles one process request into the fixed Linux Bubblewrap policy. */
5
+ export declare class BubblewrapSandboxExecutor implements LocalProcessExecutor {
6
+ #private;
7
+ private readonly options;
8
+ constructor(options: BubblewrapSandboxExecutorOptions);
9
+ spawn(request: LocalProcessSpawnRequest): Promise<ChildProcessWithoutNullStreams>;
10
+ }
11
+ export declare function probeBubblewrapSandbox(options?: {
12
+ readonly bubblewrapPath?: string;
13
+ readonly profile?: BubblewrapSandboxExecutorOptions['profile'];
14
+ readonly cwd?: string;
15
+ }): Promise<{
16
+ readonly version: string;
17
+ readonly policyVersion: number;
18
+ }>;
@@ -0,0 +1,605 @@
1
+ import { execFile, spawn } from 'node:child_process';
2
+ import { closeSync, existsSync, lstatSync, openSync, readlinkSync, rmSync } from 'node:fs';
3
+ import { lstat, mkdtemp, realpath, rm, writeFile } from 'node:fs/promises';
4
+ import { tmpdir } from 'node:os';
5
+ import { isAbsolute, join, posix, relative, resolve, sep } from 'node:path';
6
+ import { promisify } from 'node:util';
7
+ import { MarAgentError } from '../../error.js';
8
+ import { LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION } from './profile.js';
9
+ const execFileAsync = promisify(execFile);
10
+ const DEFAULT_BUBBLEWRAP_PATH = '/usr/bin/bwrap';
11
+ const SANDBOX_PATH = '/runtime/node/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
12
+ const CURSOR_SAFE_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,159}$/u;
13
+ const SANDBOX_CPU_SECONDS = 300;
14
+ const SANDBOX_PROCESS_LIMIT = 64;
15
+ const SANDBOX_OPEN_FILE_LIMIT = 256;
16
+ const SANDBOX_FILE_SIZE_BYTES = 1024 * 1024 * 1024;
17
+ const SAFE_ENVIRONMENT_NAME = /^[A-Za-z_][A-Za-z0-9_]{0,127}$/u;
18
+ const GENERATED_IMAGES_SANDBOX_ROOT = '/home/mar/.mar/agent/sessions';
19
+ // Fontconfig and LibreOffice resolve required host configuration through absolute /etc paths
20
+ // and /usr symlinks. Keep this allowlist read-only and never broaden it to the full /etc tree.
21
+ const READ_ONLY_SYSTEM_CONFIGURATION_DIRECTORIES = ['/etc/fonts', '/etc/libreoffice'];
22
+ const FORBIDDEN_ENVIRONMENT_NAMES = new Set([
23
+ 'PATH',
24
+ 'HOME',
25
+ 'TMPDIR',
26
+ 'LD_PRELOAD',
27
+ 'LD_LIBRARY_PATH',
28
+ 'NODE_OPTIONS',
29
+ 'PYTHONPATH',
30
+ 'PYTHONHOME',
31
+ 'BASH_ENV',
32
+ 'ENV',
33
+ 'SSH_AUTH_SOCK'
34
+ ]);
35
+ const RESERVED_SANDBOX_ROOTS = [
36
+ '/home/mar',
37
+ '/runtime',
38
+ '/usr',
39
+ '/bin',
40
+ '/sbin',
41
+ '/lib',
42
+ '/lib64',
43
+ '/etc',
44
+ '/proc',
45
+ '/dev',
46
+ '/run',
47
+ '/tmp'
48
+ ];
49
+ /** Compiles one process request into the fixed Linux Bubblewrap policy. */
50
+ export class BubblewrapSandboxExecutor {
51
+ options;
52
+ #bubblewrapPath;
53
+ constructor(options) {
54
+ this.options = options;
55
+ this.#bubblewrapPath = options.bubblewrapPath ?? DEFAULT_BUBBLEWRAP_PATH;
56
+ }
57
+ async spawn(request) {
58
+ request.signal?.throwIfAborted();
59
+ if (process.platform !== 'linux')
60
+ throw unavailable();
61
+ validateProfile(this.options.profile);
62
+ await validateBackend(this.#bubblewrapPath);
63
+ const mounts = await this.#mounts();
64
+ const cwd = await workingDirectory(request, mounts, this.options.profile.workingDirectory);
65
+ const command = sandboxCommand(request, mounts);
66
+ const launchDirectory = await mkdtemp(join(tmpdir(), 'mar-agent-bwrap-launch-'));
67
+ const argumentPath = join(launchDirectory, 'arguments');
68
+ const seccompPath = join(launchDirectory, 'seccomp.bpf');
69
+ try {
70
+ const arguments_ = this.#arguments(mounts, cwd, request);
71
+ await Promise.all([
72
+ writeFile(argumentPath, `${arguments_.join('\0')}\0`, { mode: 0o600, flag: 'wx' }),
73
+ writeFile(seccompPath, seccompFilter(process.arch), { mode: 0o600, flag: 'wx' })
74
+ ]);
75
+ }
76
+ catch (error) {
77
+ await rm(launchDirectory, { recursive: true, force: true }).catch(() => undefined);
78
+ throw error instanceof MarAgentError ? error : policyInvalid(error);
79
+ }
80
+ let argumentFd;
81
+ let seccompFd;
82
+ try {
83
+ argumentFd = openSync(argumentPath, 'r');
84
+ seccompFd = openSync(seccompPath, 'r');
85
+ rmSync(launchDirectory, { recursive: true, force: true });
86
+ request.signal?.throwIfAborted();
87
+ try {
88
+ const child = spawn(this.#bubblewrapPath, ['--args', '3', '--', ...command], {
89
+ cwd: '/',
90
+ shell: false,
91
+ stdio: ['pipe', 'pipe', 'pipe', argumentFd, seccompFd],
92
+ windowsHide: true,
93
+ detached: request.detached,
94
+ env: { PATH: '/usr/bin:/bin', LANG: 'C.UTF-8', LC_ALL: 'C.UTF-8' }
95
+ });
96
+ await waitForSpawn(child);
97
+ return child;
98
+ }
99
+ catch (error) {
100
+ throw unavailable(error);
101
+ }
102
+ }
103
+ finally {
104
+ if (argumentFd !== undefined)
105
+ closeSync(argumentFd);
106
+ if (seccompFd !== undefined)
107
+ closeSync(seccompFd);
108
+ rmSync(launchDirectory, { recursive: true, force: true });
109
+ }
110
+ }
111
+ async #mounts() {
112
+ const mounts = [];
113
+ for (const definition of this.options.profile.mounts ?? []) {
114
+ mounts.push({
115
+ source: await secureDirectory(definition.source),
116
+ destination: sandboxDestination(definition.destination, definition.access),
117
+ access: definition.access
118
+ });
119
+ }
120
+ if (this.options.profile.temporaryDirectory?.kind === 'MOUNT')
121
+ mounts.push({
122
+ source: await secureDirectory(this.options.profile.temporaryDirectory.source),
123
+ destination: '/tmp',
124
+ access: 'READ_WRITE'
125
+ });
126
+ for (const [index, mount] of mounts.entries()) {
127
+ for (const other of mounts.slice(index + 1)) {
128
+ if (mount.destination === other.destination)
129
+ throw policyInvalid();
130
+ const parent = withinSandboxPath(mount.destination, other.destination)
131
+ ? mount
132
+ : withinSandboxPath(other.destination, mount.destination)
133
+ ? other
134
+ : undefined;
135
+ if (parent !== undefined) {
136
+ const child = parent === mount ? other : mount;
137
+ if (parent.access !== 'READ_ONLY' || child.access !== 'READ_WRITE')
138
+ throw policyInvalid();
139
+ }
140
+ if (mount.source === other.source) {
141
+ if (mount.access !== other.access)
142
+ throw policyInvalid();
143
+ continue;
144
+ }
145
+ if (within(mount.source, other.source) || within(other.source, mount.source))
146
+ throw policyInvalid();
147
+ }
148
+ }
149
+ const ordered = [...mounts].sort(compareMountDestinations);
150
+ await prepareNestedMountDestinations(ordered);
151
+ return ordered;
152
+ }
153
+ #arguments(mounts, cwd, request) {
154
+ const networkMode = this.options.profile.networkMode ?? 'NONE';
155
+ const temporaryDirectory = this.options.profile.temporaryDirectory ?? { kind: 'EPHEMERAL' };
156
+ const args = [
157
+ '--unshare-user',
158
+ '--unshare-pid',
159
+ ...(networkMode === 'UNRESTRICTED' ? [] : ['--unshare-net']),
160
+ '--unshare-uts',
161
+ '--unshare-ipc',
162
+ '--unshare-cgroup-try',
163
+ '--disable-userns',
164
+ '--die-with-parent',
165
+ '--new-session',
166
+ '--cap-drop',
167
+ 'ALL',
168
+ '--hostname',
169
+ 'mar-hosted',
170
+ '--clearenv',
171
+ '--setenv',
172
+ 'PATH',
173
+ SANDBOX_PATH,
174
+ '--setenv',
175
+ 'HOME',
176
+ '/home/mar',
177
+ '--setenv',
178
+ 'LANG',
179
+ 'C.UTF-8',
180
+ '--setenv',
181
+ 'LC_ALL',
182
+ 'C.UTF-8',
183
+ '--setenv',
184
+ 'TMPDIR',
185
+ '/tmp',
186
+ '--tmpfs',
187
+ '/',
188
+ ...runtimeMountArguments(),
189
+ ...sandboxDirectoryArguments(mounts),
190
+ ...(temporaryDirectory.kind === 'EPHEMERAL' ? ['--tmpfs', '/tmp'] : []),
191
+ '--proc',
192
+ '/proc',
193
+ '--dev',
194
+ '/dev',
195
+ '--tmpfs',
196
+ '/run'
197
+ ];
198
+ for (const mount of mounts)
199
+ args.push(mount.access === 'READ_WRITE' ? '--bind' : '--ro-bind', mount.source, mount.destination);
200
+ if (request.owner !== undefined) {
201
+ if (!CURSOR_SAFE_ID.test(request.owner.sessionId) ||
202
+ !CURSOR_SAFE_ID.test(request.owner.executionId))
203
+ throw policyInvalid();
204
+ args.push('--setenv', 'MAR_AGENT_SESSION_ID', request.owner.sessionId, '--setenv', 'MAR_AGENT_EXECUTION_ID', request.owner.executionId);
205
+ }
206
+ for (const [name, value] of explicitEnvironment(request))
207
+ args.push('--setenv', name, value);
208
+ args.push('--seccomp', '4', '--chdir', cwd);
209
+ return args;
210
+ }
211
+ }
212
+ export async function probeBubblewrapSandbox(options = {}) {
213
+ if (process.platform !== 'linux')
214
+ throw unavailable();
215
+ const bubblewrapPath = options.bubblewrapPath ?? DEFAULT_BUBBLEWRAP_PATH;
216
+ const versionOutput = await execFileAsync(bubblewrapPath, ['--version'], {
217
+ timeout: 5_000,
218
+ windowsHide: true
219
+ }).catch((error) => {
220
+ throw unavailable(error);
221
+ });
222
+ const version = /^bubblewrap\s+([0-9]+(?:\.[0-9]+){1,3})\s*$/u.exec(versionOutput.stdout)?.[1];
223
+ if (version === undefined)
224
+ throw unavailable();
225
+ const executor = new BubblewrapSandboxExecutor({
226
+ bubblewrapPath,
227
+ profile: options.profile ?? {
228
+ policyVersion: LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION
229
+ }
230
+ });
231
+ const child = await executor.spawn({
232
+ command: '/bin/true',
233
+ cwd: options.cwd ?? '/',
234
+ shell: false,
235
+ environment: {},
236
+ detached: true,
237
+ owner: { sessionId: 'sandbox-probe', executionId: 'sandbox-probe' }
238
+ });
239
+ const result = await waitForProbe(child);
240
+ if (result.code !== 0)
241
+ throw unavailable(new Error(result.stderr));
242
+ return { version, policyVersion: LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION };
243
+ }
244
+ async function waitForProbe(child) {
245
+ const chunks = [];
246
+ let bytes = 0;
247
+ child.stderr.on('data', (chunk) => {
248
+ if (bytes >= 8_192)
249
+ return;
250
+ const remaining = 8_192 - bytes;
251
+ chunks.push(chunk.subarray(0, remaining));
252
+ bytes += Math.min(chunk.length, remaining);
253
+ });
254
+ child.stdin.end();
255
+ return await new Promise((resolve, reject) => {
256
+ const timer = setTimeout(() => {
257
+ child.kill('SIGKILL');
258
+ reject(unavailable(new Error('Sandbox probe timed out.')));
259
+ }, 5_000);
260
+ timer.unref();
261
+ child.once('error', (error) => {
262
+ clearTimeout(timer);
263
+ reject(unavailable(error));
264
+ });
265
+ child.once('close', (code) => {
266
+ clearTimeout(timer);
267
+ resolve({ code, stderr: Buffer.concat(chunks).toString('utf8') });
268
+ });
269
+ });
270
+ }
271
+ async function waitForSpawn(child) {
272
+ await new Promise((resolve, reject) => {
273
+ const spawned = () => {
274
+ child.off('error', failed);
275
+ resolve();
276
+ };
277
+ const failed = (error) => {
278
+ child.off('spawn', spawned);
279
+ reject(unavailable(error));
280
+ };
281
+ child.once('spawn', spawned);
282
+ child.once('error', failed);
283
+ });
284
+ }
285
+ async function validateBackend(path) {
286
+ try {
287
+ const metadata = await lstat(path);
288
+ if (!metadata.isFile() || metadata.isSymbolicLink() || (metadata.mode & 0o111) === 0)
289
+ throw unavailable();
290
+ }
291
+ catch (error) {
292
+ if (error instanceof MarAgentError)
293
+ throw error;
294
+ throw unavailable(error);
295
+ }
296
+ }
297
+ async function secureDirectory(path) {
298
+ if (!isAbsolute(path))
299
+ throw policyInvalid();
300
+ try {
301
+ const metadata = await lstat(path);
302
+ if (!metadata.isDirectory() || metadata.isSymbolicLink())
303
+ throw policyInvalid();
304
+ const canonical = await realpath(path);
305
+ if (canonical !== resolve(path))
306
+ throw policyInvalid();
307
+ return canonical;
308
+ }
309
+ catch (error) {
310
+ if (error instanceof MarAgentError)
311
+ throw error;
312
+ throw policyInvalid(error);
313
+ }
314
+ }
315
+ function validateProfile(profile) {
316
+ if (profile === null ||
317
+ typeof profile !== 'object' ||
318
+ profile.policyVersion !== LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION ||
319
+ (profile.networkMode !== undefined &&
320
+ profile.networkMode !== 'NONE' &&
321
+ profile.networkMode !== 'UNRESTRICTED') ||
322
+ (profile.mounts !== undefined && !Array.isArray(profile.mounts)))
323
+ throw policyInvalid();
324
+ for (const mount of profile.mounts ?? []) {
325
+ if (mount === null ||
326
+ typeof mount !== 'object' ||
327
+ typeof mount.source !== 'string' ||
328
+ typeof mount.destination !== 'string' ||
329
+ (mount.access !== 'READ_ONLY' && mount.access !== 'READ_WRITE'))
330
+ throw policyInvalid();
331
+ }
332
+ const temporaryDirectory = profile.temporaryDirectory;
333
+ if (temporaryDirectory !== undefined &&
334
+ (temporaryDirectory === null ||
335
+ typeof temporaryDirectory !== 'object' ||
336
+ (temporaryDirectory.kind !== 'EPHEMERAL' &&
337
+ (temporaryDirectory.kind !== 'MOUNT' || typeof temporaryDirectory.source !== 'string'))))
338
+ throw policyInvalid();
339
+ const workingDirectory = profile.workingDirectory;
340
+ if (workingDirectory !== undefined &&
341
+ (workingDirectory === null ||
342
+ typeof workingDirectory !== 'object' ||
343
+ (workingDirectory.kind !== 'REQUEST' &&
344
+ (workingDirectory.kind !== 'SANDBOX' || typeof workingDirectory.path !== 'string'))))
345
+ throw policyInvalid();
346
+ }
347
+ async function workingDirectory(request, mounts, configuration) {
348
+ const selected = configuration ?? { kind: 'SANDBOX', path: '/tmp' };
349
+ if (selected.kind === 'REQUEST')
350
+ return await sandboxPath(request.cwd, mounts);
351
+ const path = normalizedSandboxPath(selected.path);
352
+ if (path === '/tmp' || path === '/home/mar')
353
+ return path;
354
+ const mount = [...mounts]
355
+ .sort((left, right) => right.destination.length - left.destination.length)
356
+ .find((candidate) => path === candidate.destination || withinSandboxPath(candidate.destination, path));
357
+ if (mount === undefined)
358
+ throw policyInvalid();
359
+ const suffix = posix.relative(mount.destination, path).split('/').filter(Boolean);
360
+ await secureDirectory(join(mount.source, ...suffix));
361
+ return path;
362
+ }
363
+ async function sandboxPath(path, mounts) {
364
+ const canonical = await secureDirectory(path);
365
+ const mount = [...mounts]
366
+ .sort((left, right) => right.source.length - left.source.length)
367
+ .find((candidate) => canonical === candidate.source || within(candidate.source, canonical));
368
+ if (mount === undefined)
369
+ throw policyInvalid();
370
+ const suffix = relative(mount.source, canonical).split(sep).filter(Boolean);
371
+ return suffix.length === 0 ? mount.destination : posix.join(mount.destination, ...suffix);
372
+ }
373
+ function sandboxDestination(path, access) {
374
+ const normalized = normalizedSandboxPath(path);
375
+ if (generatedImagesSandboxPath(normalized)) {
376
+ if (access !== 'READ_ONLY')
377
+ throw policyInvalid();
378
+ return normalized;
379
+ }
380
+ if (normalized === '/' ||
381
+ RESERVED_SANDBOX_ROOTS.some((root) => normalized === root ||
382
+ withinSandboxPath(root, normalized) ||
383
+ withinSandboxPath(normalized, root)))
384
+ throw policyInvalid();
385
+ return normalized;
386
+ }
387
+ function normalizedSandboxPath(path) {
388
+ if (!path.startsWith('/') ||
389
+ path.includes('\0') ||
390
+ path.includes('\\') ||
391
+ posix.normalize(path) !== path ||
392
+ posix.resolve(path) !== path)
393
+ throw policyInvalid();
394
+ return path;
395
+ }
396
+ function sandboxCommand(request, mounts) {
397
+ const command = request.shell === undefined || request.shell === false
398
+ ? [sandboxExecutable(request.command, mounts), ...(request.args ?? [])]
399
+ : [
400
+ sandboxExecutable(typeof request.shell === 'string' ? request.shell : '/bin/sh', mounts),
401
+ '-c',
402
+ request.command,
403
+ ...(request.args ?? [])
404
+ ];
405
+ return [
406
+ '/usr/bin/prlimit',
407
+ `--cpu=${SANDBOX_CPU_SECONDS}:${SANDBOX_CPU_SECONDS}`,
408
+ `--nproc=${SANDBOX_PROCESS_LIMIT}:${SANDBOX_PROCESS_LIMIT}`,
409
+ `--nofile=${SANDBOX_OPEN_FILE_LIMIT}:${SANDBOX_OPEN_FILE_LIMIT}`,
410
+ `--fsize=${SANDBOX_FILE_SIZE_BYTES}:${SANDBOX_FILE_SIZE_BYTES}`,
411
+ '--',
412
+ ...command
413
+ ];
414
+ }
415
+ function sandboxExecutable(command, mounts) {
416
+ if (!isAbsolute(command))
417
+ return command;
418
+ const canonical = resolve(command);
419
+ const nodeRuntimeDirectory = resolve(process.execPath, '..', '..');
420
+ if (nodeRuntimeDirectory !== '/usr' &&
421
+ (canonical === nodeRuntimeDirectory || within(nodeRuntimeDirectory, canonical))) {
422
+ const suffix = relative(nodeRuntimeDirectory, canonical).split(sep).filter(Boolean);
423
+ return suffix.length === 0 ? '/runtime/node' : posix.join('/runtime/node', ...suffix);
424
+ }
425
+ const mount = [...mounts]
426
+ .sort((left, right) => right.source.length - left.source.length)
427
+ .find((candidate) => canonical === candidate.source || within(candidate.source, canonical));
428
+ if (mount !== undefined) {
429
+ const suffix = relative(mount.source, canonical).split(sep).filter(Boolean);
430
+ return suffix.length === 0 ? mount.destination : posix.join(mount.destination, ...suffix);
431
+ }
432
+ if (['/usr', '/bin', '/sbin', '/lib', '/lib64'].some((root) => canonical === root || within(root, canonical)))
433
+ return canonical;
434
+ throw policyInvalid();
435
+ }
436
+ function explicitEnvironment(request) {
437
+ const entries = [];
438
+ const seen = new Set();
439
+ for (const name of request.environmentKeys ?? []) {
440
+ if (seen.has(name))
441
+ continue;
442
+ seen.add(name);
443
+ if (!SAFE_ENVIRONMENT_NAME.test(name))
444
+ throw policyInvalid();
445
+ if (FORBIDDEN_ENVIRONMENT_NAMES.has(name))
446
+ continue;
447
+ const value = request.environment[name];
448
+ if (typeof value !== 'string' || value.includes('\0') || Buffer.byteLength(value) > 32_768)
449
+ throw policyInvalid();
450
+ entries.push([name, value]);
451
+ }
452
+ return entries;
453
+ }
454
+ function generatedImagesSandboxPath(path) {
455
+ const segments = posix.relative(GENERATED_IMAGES_SANDBOX_ROOT, path).split('/').filter(Boolean);
456
+ return (withinSandboxPath(GENERATED_IMAGES_SANDBOX_ROOT, path) &&
457
+ segments.length === 2 &&
458
+ safePathSegment(segments[0]) &&
459
+ segments[1] === 'generated_images');
460
+ }
461
+ function safePathSegment(value) {
462
+ return (value.length > 0 &&
463
+ value.length <= 255 &&
464
+ value !== '.' &&
465
+ value !== '..' &&
466
+ !value.includes('/') &&
467
+ !value.includes('\\'));
468
+ }
469
+ function compareMountDestinations(left, right) {
470
+ const depth = left.destination.split('/').filter(Boolean).length -
471
+ right.destination.split('/').filter(Boolean).length;
472
+ return depth === 0 ? left.destination.localeCompare(right.destination, 'en-US') : depth;
473
+ }
474
+ async function prepareNestedMountDestinations(mounts) {
475
+ for (const [index, mount] of mounts.entries()) {
476
+ const parent = mounts
477
+ .slice(0, index)
478
+ .filter((candidate) => withinSandboxPath(candidate.destination, mount.destination))
479
+ .sort((left, right) => right.destination.length - left.destination.length)[0];
480
+ if (parent === undefined)
481
+ continue;
482
+ const suffix = relative(parent.destination, mount.destination).split(sep).filter(Boolean);
483
+ const target = join(parent.source, ...suffix);
484
+ try {
485
+ const metadata = await lstat(target);
486
+ if (!metadata.isDirectory() ||
487
+ metadata.isSymbolicLink() ||
488
+ (await realpath(target)) !== resolve(target))
489
+ throw policyInvalid();
490
+ }
491
+ catch (error) {
492
+ if (error instanceof MarAgentError)
493
+ throw error;
494
+ throw policyInvalid(error);
495
+ }
496
+ }
497
+ }
498
+ function sandboxDirectoryArguments(mounts) {
499
+ const directories = new Set(['/home', '/home/mar']);
500
+ for (const mount of mounts) {
501
+ const segments = mount.destination.split('/').filter(Boolean);
502
+ let current = '';
503
+ for (const segment of segments) {
504
+ current = `${current}/${segment}`;
505
+ directories.add(current);
506
+ }
507
+ }
508
+ return [...directories]
509
+ .sort((left, right) => {
510
+ const depth = left.split('/').filter(Boolean).length - right.split('/').filter(Boolean).length;
511
+ return depth === 0 ? left.localeCompare(right, 'en-US') : depth;
512
+ })
513
+ .flatMap((directory) => ['--dir', directory]);
514
+ }
515
+ function runtimeMountArguments() {
516
+ const args = [];
517
+ const nodeRuntimeDirectory = resolve(process.execPath, '..', '..');
518
+ if (nodeRuntimeDirectory !== '/usr')
519
+ args.push('--dir', '/runtime', '--dir', '/runtime/node', '--ro-bind', nodeRuntimeDirectory, '/runtime/node');
520
+ for (const path of ['/usr', '/bin', '/sbin', '/lib', '/lib64']) {
521
+ if (!existsSync(path))
522
+ continue;
523
+ const metadata = lstatSync(path);
524
+ if (metadata.isSymbolicLink())
525
+ args.push('--symlink', readlinkSync(path), path);
526
+ else if (metadata.isDirectory())
527
+ args.push('--ro-bind', path, path);
528
+ }
529
+ for (const path of READ_ONLY_SYSTEM_CONFIGURATION_DIRECTORIES) {
530
+ if (!existsSync(path) || !lstatSync(path).isDirectory())
531
+ continue;
532
+ args.push('--ro-bind', path, path);
533
+ }
534
+ for (const path of [
535
+ '/etc/host.conf',
536
+ '/etc/hosts',
537
+ '/etc/ld.so.cache',
538
+ '/etc/resolv.conf',
539
+ '/etc/passwd',
540
+ '/etc/group',
541
+ '/etc/nsswitch.conf',
542
+ '/etc/localtime',
543
+ '/etc/ssl/certs'
544
+ ]) {
545
+ if (existsSync(path))
546
+ args.push('--ro-bind', path, path);
547
+ }
548
+ return args;
549
+ }
550
+ function seccompFilter(architecture) {
551
+ const policy = syscallPolicy(architecture);
552
+ const filters = [
553
+ [0x20, 0, 0, 4],
554
+ [0x15, 1, 0, policy.auditArchitecture],
555
+ [0x06, 0, 0, 0x80000000],
556
+ [0x20, 0, 0, 0]
557
+ ];
558
+ if (architecture === 'x64')
559
+ filters.push([0x54, 0, 0, 0x40000000], [0x15, 1, 0, 0], [0x06, 0, 0, 0x80000000], [0x20, 0, 0, 0]);
560
+ for (const syscall of policy.blockedSyscalls)
561
+ filters.push([0x15, 0, 1, syscall], [0x06, 0, 0, 0x00050001]);
562
+ filters.push([0x06, 0, 0, 0x7fff0000]);
563
+ const buffer = Buffer.alloc(filters.length * 8);
564
+ for (const [index, [code, jumpTrue, jumpFalse, value]] of filters.entries()) {
565
+ const offset = index * 8;
566
+ buffer.writeUInt16LE(code, offset);
567
+ buffer.writeUInt8(jumpTrue, offset + 2);
568
+ buffer.writeUInt8(jumpFalse, offset + 3);
569
+ buffer.writeUInt32LE(value >>> 0, offset + 4);
570
+ }
571
+ return buffer;
572
+ }
573
+ function syscallPolicy(architecture) {
574
+ if (architecture === 'x64')
575
+ return {
576
+ auditArchitecture: 0xc000003e,
577
+ blockedSyscalls: [
578
+ 101, 155, 165, 166, 175, 176, 246, 248, 249, 250, 272, 298, 304, 308, 313, 321, 323, 428,
579
+ 429, 430, 431, 432, 442
580
+ ]
581
+ };
582
+ if (architecture === 'arm64')
583
+ return {
584
+ auditArchitecture: 0xc00000b7,
585
+ blockedSyscalls: [
586
+ 39, 40, 41, 97, 104, 105, 106, 117, 217, 218, 219, 241, 265, 268, 273, 280, 282, 428, 429,
587
+ 430, 431, 432, 442
588
+ ]
589
+ };
590
+ throw policyInvalid();
591
+ }
592
+ function within(parent, candidate) {
593
+ const relation = relative(parent, candidate);
594
+ return relation.length > 0 && relation !== '..' && !relation.startsWith(`..${sep}`);
595
+ }
596
+ function withinSandboxPath(parent, candidate) {
597
+ const relation = posix.relative(parent, candidate);
598
+ return relation.length > 0 && relation !== '..' && !relation.startsWith('../');
599
+ }
600
+ function unavailable(_cause) {
601
+ return new MarAgentError('SANDBOX_UNAVAILABLE', 'Bubblewrap sandbox is unavailable.');
602
+ }
603
+ function policyInvalid(_cause) {
604
+ return new MarAgentError('SANDBOX_POLICY_INVALID', 'Sandbox policy is invalid.');
605
+ }
@@ -0,0 +1,2 @@
1
+ export { BubblewrapSandboxExecutor, probeBubblewrapSandbox } from './bubblewrap-sandbox-executor.js';
2
+ export { LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION, type BubblewrapSandboxExecutorOptions, type BubblewrapSandboxProfile, type LinuxSandboxMount, type LinuxSandboxNetworkMode, type LinuxSandboxTemporaryDirectory, type LinuxSandboxWorkingDirectory } from './profile.js';
@@ -0,0 +1,2 @@
1
+ export { BubblewrapSandboxExecutor, probeBubblewrapSandbox } from './bubblewrap-sandbox-executor.js';
2
+ export { LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION } from './profile.js';
@@ -0,0 +1,30 @@
1
+ export declare const LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION = 1;
2
+ export type LinuxSandboxNetworkMode = 'NONE' | 'UNRESTRICTED';
3
+ export interface LinuxSandboxMount {
4
+ readonly source: string;
5
+ readonly destination: string;
6
+ readonly access: 'READ_ONLY' | 'READ_WRITE';
7
+ }
8
+ export type LinuxSandboxTemporaryDirectory = {
9
+ readonly kind: 'EPHEMERAL';
10
+ } | {
11
+ readonly kind: 'MOUNT';
12
+ readonly source: string;
13
+ };
14
+ export type LinuxSandboxWorkingDirectory = {
15
+ readonly kind: 'REQUEST';
16
+ } | {
17
+ readonly kind: 'SANDBOX';
18
+ readonly path: string;
19
+ };
20
+ export interface BubblewrapSandboxProfile {
21
+ readonly policyVersion: number;
22
+ readonly networkMode?: LinuxSandboxNetworkMode;
23
+ readonly mounts?: readonly LinuxSandboxMount[];
24
+ readonly temporaryDirectory?: LinuxSandboxTemporaryDirectory;
25
+ readonly workingDirectory?: LinuxSandboxWorkingDirectory;
26
+ }
27
+ export interface BubblewrapSandboxExecutorOptions {
28
+ readonly profile: BubblewrapSandboxProfile;
29
+ readonly bubblewrapPath?: string;
30
+ }
@@ -0,0 +1 @@
1
+ export const LINUX_BUBBLEWRAP_SANDBOX_POLICY_VERSION = 1;
@@ -4,7 +4,7 @@ import { TOOL_EXECUTION_LIMITS } from './execution-limits.js';
4
4
  export const questionToolDefinition = {
5
5
  name: 'question',
6
6
  parallelSafety: 'serial',
7
- description: 'Pause for one to three tightly related user-owned decisions that materially change the result and cannot be resolved through relevant safe discovery. Prefer asking over guessing in Plan mode; do not avoid clarification by inventing configuration, branches, abstractions, or support for multiple outcomes. Provide 2-3 mutually exclusive options with consequences, put the recommended option first and identify it in its label or description, and use another question round if key ambiguity remains. This is not a permission/approval tool and should not interrupt routine in-scope work.',
7
+ description: 'Pause for one to three tightly related user-owned decisions that materially change the result and cannot be inferred from the request, conversation, or relevant safe discovery. Prefer asking over guessing in Plan mode; do not avoid clarification by inventing configuration, branches, abstractions, or support for multiple outcomes. Provide 2-3 mutually exclusive options with consequences, put the recommended option first and identify it in its label or description, and use another question round if key ambiguity remains. This is not a permission/approval tool and should not interrupt routine in-scope work.',
8
8
  inputSchema: {
9
9
  type: 'object',
10
10
  properties: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@myagentroam/agent",
3
- "version": "0.9.77",
3
+ "version": "0.9.78",
4
4
  "description": "Embeddable MAR coding agent SDK and CLI.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -14,6 +14,10 @@
14
14
  ".": {
15
15
  "types": "./dist/index.d.ts",
16
16
  "default": "./dist/index.js"
17
+ },
18
+ "./sandbox/linux": {
19
+ "types": "./dist/sandbox/linux/index.d.ts",
20
+ "default": "./dist/sandbox/linux/index.js"
17
21
  }
18
22
  },
19
23
  "bin": {