@hachej/boring-agent 0.1.12 → 0.1.13

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.
@@ -215,9 +215,16 @@ interface VercelSandboxWorkspaceOptions {
215
215
  }
216
216
  declare function createVercelSandboxWorkspace(sandbox: Sandbox$1, workspaceOpts?: VercelSandboxWorkspaceOptions): VercelSandboxWorkspace;
217
217
 
218
- type RuntimeModeId = 'direct' | 'local' | 'vercel-sandbox';
218
+ type BuiltinRuntimeModeId = 'direct' | 'local' | 'vercel-sandbox';
219
+ type RuntimeModeId = BuiltinRuntimeModeId | (string & {});
219
220
  interface RuntimeModeAdapter {
220
221
  readonly id: RuntimeModeId;
222
+ /**
223
+ * Declares whether the workspace files are strongly available on the host
224
+ * path before create() runs. Composition layers use this to decide whether
225
+ * host-side fs checks/prompts are safe without hard-coding sandbox IDs.
226
+ */
227
+ readonly workspaceFsCapability?: Workspace['fsCapability'];
221
228
  create(ctx: ModeContext): Promise<RuntimeBundle>;
222
229
  dispose?(): Promise<void>;
223
230
  }
@@ -260,6 +267,8 @@ interface CreateAgentAppOptions {
260
267
  sessionId?: string;
261
268
  templatePath?: string;
262
269
  mode?: RuntimeModeId;
270
+ /** Supply a custom runtime adapter to plug in non-built-in sandbox/workspace modes. */
271
+ runtimeModeAdapter?: RuntimeModeAdapter;
263
272
  authToken?: string;
264
273
  version?: string;
265
274
  logger?: boolean;
@@ -295,12 +304,15 @@ interface RegisterAgentRoutesOptions {
295
304
  request?: FastifyRequest;
296
305
  }) => string | undefined | Promise<string | undefined>;
297
306
  mode?: RuntimeModeId;
307
+ /** Supply a custom runtime adapter to plug in non-built-in sandbox/workspace modes. */
308
+ runtimeModeAdapter?: RuntimeModeAdapter;
298
309
  version?: string;
299
310
  extraTools?: AgentTool[];
300
311
  getExtraTools?: (ctx: {
301
312
  workspaceId: string;
302
313
  workspaceRoot: string;
303
314
  runtimeMode: RuntimeModeId;
315
+ workspaceFsCapability?: Workspace['fsCapability'];
304
316
  }) => AgentTool[] | Promise<AgentTool[]>;
305
317
  systemPromptAppend?: string;
306
318
  resourceLoaderOptions?: PiResourceLoaderOptions;
@@ -329,4 +341,4 @@ interface Logger {
329
341
  }
330
342
  declare function createLogger(prefix: string): Logger;
331
343
 
332
- export { type CreateAgentAppOptions, type DeploymentSnapshotProvider, type DeploymentSnapshotRecipe, type DeploymentSnapshotResult, type DeploymentSnapshotStatus, FileHandleStore, type LogFields, type Logger, type ModeContext, PI_PACKAGE_RESOURCE_FILTERS, type PiPackageSource, type PiResourceLoaderOptions, type ProvisionRuntimeWorkspaceOptions, type RegisterAgentRoutesOptions, type RuntimeBundle, type RuntimeModeAdapter, type RuntimeModeId, type RuntimeProvisioningContribution, type RuntimePythonSpec, type RuntimeTemplateContribution, type RuntimeWorkspaceProvisioningResult, type SnapshotBakeOptions, type SnapshotBakeResult, UV_SETUP_COMMANDS, UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS, type VercelBakeClient, type VercelBakeSandbox, type VercelDeploymentSnapshotOptions, applyCspHeaders, autoDetectMode, bakeSnapshotIfNeeded, buildDeploymentSnapshotRecipe, buildPackageHash, buildSnapshotRecipeHash, compactPiPackages, createAgentApp, createBwrapSandbox, createDirectSandbox, createLogger, createNodeWorkspace, createVercelDeploymentSnapshotProvider, createVercelSandboxWorkspace, hasBwrap, mergePiPackageSources, piPackageSourceKey, prepareDeploymentSnapshot, prepareVercelDeploymentSnapshot, provisionRuntimeWorkspace, registerAgentRoutes, resolveMode, resolveSandboxHandle };
344
+ export { type BuiltinRuntimeModeId, type CreateAgentAppOptions, type DeploymentSnapshotProvider, type DeploymentSnapshotRecipe, type DeploymentSnapshotResult, type DeploymentSnapshotStatus, FileHandleStore, type LogFields, type Logger, type ModeContext, PI_PACKAGE_RESOURCE_FILTERS, type PiPackageSource, type PiResourceLoaderOptions, type ProvisionRuntimeWorkspaceOptions, type RegisterAgentRoutesOptions, type RuntimeBundle, type RuntimeModeAdapter, type RuntimeModeId, type RuntimeProvisioningContribution, type RuntimePythonSpec, type RuntimeTemplateContribution, type RuntimeWorkspaceProvisioningResult, type SnapshotBakeOptions, type SnapshotBakeResult, UV_SETUP_COMMANDS, UV_SETUP_COMMANDS as VERCEL_UV_SETUP_COMMANDS, type VercelBakeClient, type VercelBakeSandbox, type VercelDeploymentSnapshotOptions, applyCspHeaders, autoDetectMode, bakeSnapshotIfNeeded, buildDeploymentSnapshotRecipe, buildPackageHash, buildSnapshotRecipeHash, compactPiPackages, createAgentApp, createBwrapSandbox, createDirectSandbox, createLogger, createNodeWorkspace, createVercelDeploymentSnapshotProvider, createVercelSandboxWorkspace, hasBwrap, mergePiPackageSources, piPackageSourceKey, prepareDeploymentSnapshot, prepareVercelDeploymentSnapshot, provisionRuntimeWorkspace, registerAgentRoutes, resolveMode, resolveSandboxHandle };
@@ -2164,6 +2164,7 @@ async function copyTemplate(templatePath, workspaceRoot) {
2164
2164
  // src/server/runtime/modes/direct.ts
2165
2165
  var directModeAdapter = {
2166
2166
  id: "direct",
2167
+ workspaceFsCapability: "strong",
2167
2168
  async create(ctx) {
2168
2169
  await mkdir6(ctx.workspaceRoot, { recursive: true });
2169
2170
  await copyTemplate(ctx.templatePath, ctx.workspaceRoot);
@@ -2182,6 +2183,7 @@ var directModeAdapter = {
2182
2183
  import { mkdir as mkdir7 } from "fs/promises";
2183
2184
  var localModeAdapter = {
2184
2185
  id: "local",
2186
+ workspaceFsCapability: "strong",
2185
2187
  async create(ctx) {
2186
2188
  if (process.platform !== "linux") {
2187
2189
  throw new Error("local mode requires Linux with bubblewrap");
@@ -2249,17 +2251,32 @@ function toRemotePath(value) {
2249
2251
  }
2250
2252
  return value;
2251
2253
  }
2254
+ function escapeRegExp(value) {
2255
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2256
+ }
2257
+ var WORKSPACE_ALIAS_PREFIX_BOUNDARY = String.raw`(^|[\s"'=:;,\[({<>&|])`;
2258
+ var WORKSPACE_ALIAS_SUFFIX_BOUNDARY = String.raw`(?=/|$|[\s"'=:;,)\]}> &|])`;
2259
+ var WORKSPACE_ALIAS_PATTERN = new RegExp(
2260
+ `${WORKSPACE_ALIAS_PREFIX_BOUNDARY}${escapeRegExp(VERCEL_SANDBOX_WORKSPACE_ROOT)}${WORKSPACE_ALIAS_SUFFIX_BOUNDARY}`,
2261
+ "g"
2262
+ );
2263
+ function replaceWorkspaceAliases(value) {
2264
+ return value.replace(
2265
+ WORKSPACE_ALIAS_PATTERN,
2266
+ (_match, prefix) => `${prefix}${VERCEL_SANDBOX_REMOTE_ROOT}`
2267
+ );
2268
+ }
2252
2269
  function toRemoteCwd(cwd) {
2253
2270
  if (!cwd) return cwd;
2254
2271
  return toRemotePath(cwd);
2255
2272
  }
2256
2273
  function toRemoteCommand(command) {
2257
- return command.replaceAll(VERCEL_SANDBOX_WORKSPACE_ROOT, VERCEL_SANDBOX_REMOTE_ROOT);
2274
+ return replaceWorkspaceAliases(command);
2258
2275
  }
2259
2276
  function toRemoteEnv(env) {
2260
2277
  if (!env) return void 0;
2261
2278
  return Object.fromEntries(
2262
- Object.entries(env).map(([key, value]) => [key, toRemotePath(value)])
2279
+ Object.entries(env).map(([key, value]) => [key, replaceWorkspaceAliases(toRemotePath(value))])
2263
2280
  );
2264
2281
  }
2265
2282
  function createVercelSandboxExec(sandbox, execOpts = {}) {
@@ -2590,6 +2607,7 @@ function createVercelSandboxModeAdapter(opts = {}) {
2590
2607
  const snapshotScheduler = opts.snapshotScheduler ?? null;
2591
2608
  return {
2592
2609
  id: "vercel-sandbox",
2610
+ workspaceFsCapability: "best-effort",
2593
2611
  async dispose() {
2594
2612
  await snapshotScheduler?.shutdown();
2595
2613
  },
@@ -2686,7 +2704,7 @@ var MODE_ADAPTERS = {
2686
2704
  local: localModeAdapter,
2687
2705
  "vercel-sandbox": vercelSandboxModeAdapter
2688
2706
  };
2689
- function isRuntimeModeId(value) {
2707
+ function isBuiltinRuntimeModeId(value) {
2690
2708
  return value === "direct" || value === "local" || value === "vercel-sandbox";
2691
2709
  }
2692
2710
  function hasBwrap() {
@@ -2696,7 +2714,7 @@ function hasBwrap() {
2696
2714
  function autoDetectMode() {
2697
2715
  const explicitMode = getEnv("BORING_AGENT_MODE");
2698
2716
  if (explicitMode) {
2699
- if (!isRuntimeModeId(explicitMode)) {
2717
+ if (!isBuiltinRuntimeModeId(explicitMode)) {
2700
2718
  throw new Error(
2701
2719
  `Invalid BORING_AGENT_MODE "${explicitMode}". Expected direct, local, or vercel-sandbox.`
2702
2720
  );
@@ -2709,7 +2727,8 @@ function autoDetectMode() {
2709
2727
  return "direct";
2710
2728
  }
2711
2729
  function resolveMode(mode = autoDetectMode()) {
2712
- return MODE_ADAPTERS[mode];
2730
+ if (isBuiltinRuntimeModeId(mode)) return MODE_ADAPTERS[mode];
2731
+ throw new Error(`Runtime mode "${mode}" has no built-in adapter. Pass runtimeModeAdapter to use a custom sandbox mode.`);
2713
2732
  }
2714
2733
 
2715
2734
  // src/server/createAgentApp.ts
@@ -4554,22 +4573,29 @@ function boundFs(workspaceRoot) {
4554
4573
  }
4555
4574
 
4556
4575
  // src/server/tools/operations/vercel.ts
4557
- import { relative as relative5 } from "path";
4576
+ import { isAbsolute as isAbsolute5, relative as relative5 } from "path";
4558
4577
  function rootAliases(workspace) {
4559
4578
  const aliases = [workspace.root];
4560
4579
  if (workspace.root === VERCEL_SANDBOX_WORKSPACE_ROOT) aliases.push(VERCEL_SANDBOX_REMOTE_ROOT);
4561
4580
  if (workspace.root === VERCEL_SANDBOX_REMOTE_ROOT) aliases.push(VERCEL_SANDBOX_WORKSPACE_ROOT);
4562
4581
  return aliases;
4563
4582
  }
4583
+ function isOutsideWorkspaceRel(rel) {
4584
+ return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute5(rel);
4585
+ }
4564
4586
  function toRelPath(workspace, absolutePath) {
4565
4587
  for (const root of rootAliases(workspace)) {
4566
4588
  const rel = relative5(root, absolutePath);
4567
- if (!rel.startsWith("..") && !rel.startsWith("/")) return rel;
4589
+ if (!isOutsideWorkspaceRel(rel)) return rel;
4568
4590
  }
4569
4591
  const skillMarker = "/.agents/skills/";
4570
4592
  const skillIndex = absolutePath.indexOf(skillMarker);
4571
4593
  if (skillIndex >= 0) {
4572
- return `.agents/skills/${absolutePath.slice(skillIndex + skillMarker.length)}`;
4594
+ const skillPath = absolutePath.slice(skillIndex + skillMarker.length);
4595
+ if (skillPath.includes("\0") || skillPath.split(/[\\/]+/).includes("..")) {
4596
+ throw new Error(`path "${absolutePath}" escapes the workspace skills directory`);
4597
+ }
4598
+ return `.agents/skills/${skillPath}`;
4573
4599
  }
4574
4600
  throw new Error(
4575
4601
  `path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${rootAliases(workspace).join(" / ")}`
@@ -4663,7 +4689,7 @@ function fallbackFindCommand(pattern, cwd, options) {
4663
4689
  function isFdMissing(result) {
4664
4690
  if (result.exitCode !== 127) return false;
4665
4691
  const stderr = Buffer.from(result.stderr).toString("utf-8");
4666
- return /fd: not found|fd: command not found|not found/i.test(stderr);
4692
+ return /\bfd: (?:not found|command not found)\b/i.test(stderr);
4667
4693
  }
4668
4694
  function vercelFindOps(sandbox, workspace) {
4669
4695
  return {
@@ -6998,14 +7024,15 @@ async function createAgentApp(opts = {}) {
6998
7024
  const sessionId = opts.sessionId ?? DEFAULT_SESSION_ID;
6999
7025
  const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
7000
7026
  const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
7001
- const resolvedMode = opts.mode ?? autoDetectMode();
7002
- const runtimeBundle = await resolveMode(resolvedMode).create({
7027
+ const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
7028
+ const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
7029
+ const runtimeBundle = await modeAdapter.create({
7003
7030
  workspaceRoot,
7004
7031
  sessionId,
7005
7032
  templatePath
7006
7033
  });
7007
7034
  const pluginTools = [];
7008
- if (resolvedMode !== "vercel-sandbox") {
7035
+ if (modeAdapter.workspaceFsCapability === "strong") {
7009
7036
  const pluginResult = await loadPlugins({ cwd: workspaceRoot });
7010
7037
  if (pluginResult.errors.length > 0) {
7011
7038
  for (const e of pluginResult.errors) {
@@ -7293,8 +7320,8 @@ var registerAgentRoutes = async (app, opts) => {
7293
7320
  const workspaceRoot = opts.workspaceRoot ?? process.cwd();
7294
7321
  const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID3;
7295
7322
  const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
7296
- const resolvedMode = opts.mode ?? autoDetectMode();
7297
- const modeAdapter = selectRuntimeModeAdapter(resolvedMode, opts.sandboxHandleStore);
7323
+ const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
7324
+ const modeAdapter = opts.runtimeModeAdapter ?? selectRuntimeModeAdapter(resolvedMode, opts.sandboxHandleStore);
7298
7325
  app.addHook("onClose", async () => {
7299
7326
  await modeAdapter.dispose?.();
7300
7327
  });
@@ -7322,7 +7349,7 @@ var registerAgentRoutes = async (app, opts) => {
7322
7349
  ...buildUploadAgentTools(runtimeBundle)
7323
7350
  ];
7324
7351
  const pluginTools = [];
7325
- if (resolvedMode !== "vercel-sandbox") {
7352
+ if (modeAdapter.workspaceFsCapability === "strong") {
7326
7353
  const pluginResult = await loadPlugins({ cwd: root });
7327
7354
  if (pluginResult.errors.length > 0) {
7328
7355
  for (const e of pluginResult.errors) {
@@ -7339,7 +7366,8 @@ var registerAgentRoutes = async (app, opts) => {
7339
7366
  const scopedExtraTools = opts.getExtraTools ? await opts.getExtraTools({
7340
7367
  workspaceId,
7341
7368
  workspaceRoot: root,
7342
- runtimeMode: resolvedMode
7369
+ runtimeMode: resolvedMode,
7370
+ workspaceFsCapability: runtimeBundle.workspace.fsCapability
7343
7371
  }) : [];
7344
7372
  const tools = mergeTools({
7345
7373
  standardTools,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.12",
3
+ "version": "0.1.13",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
@@ -74,7 +74,7 @@
74
74
  "use-stick-to-bottom": "^1.1.3",
75
75
  "yaml": "^2.8.3",
76
76
  "zod": "^3.25.76",
77
- "@hachej/boring-ui-kit": "0.1.12"
77
+ "@hachej/boring-ui-kit": "0.1.13"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@opentelemetry/api": "^1.9.1",