@hachej/boring-agent 0.1.62 → 0.1.63

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.
@@ -2806,6 +2806,8 @@
2806
2806
  @layer theme, utilities;
2807
2807
  @layer theme {
2808
2808
  :root, :host {
2809
+ --font-sans: var(--boring-font-sans);
2810
+ --font-mono: var(--boring-font-mono);
2809
2811
  --color-black: #000;
2810
2812
  --spacing: 0.25rem;
2811
2813
  --container-xs: 20rem;
@@ -444,6 +444,62 @@ interface ModeContext {
444
444
  requestId?: string;
445
445
  telemetry?: TelemetrySink;
446
446
  }
447
+ interface RuntimeFilesystemBindingOperations {
448
+ read(descriptor: {
449
+ filesystem: string;
450
+ path: string;
451
+ }): Promise<{
452
+ content: string;
453
+ metadata?: unknown;
454
+ }>;
455
+ list(descriptor: {
456
+ filesystem: string;
457
+ path: string;
458
+ }): Promise<{
459
+ entries: string[];
460
+ metadata?: unknown;
461
+ }>;
462
+ find(descriptor: {
463
+ filesystem: string;
464
+ path: string;
465
+ }, pattern: string, options?: {
466
+ limit?: number;
467
+ offset?: number;
468
+ }): Promise<{
469
+ paths: string[];
470
+ metadata?: unknown;
471
+ }>;
472
+ grep(descriptor: {
473
+ filesystem: string;
474
+ path: string;
475
+ }, pattern: string, options?: {
476
+ limit?: number;
477
+ offset?: number;
478
+ }): Promise<{
479
+ matches: Array<{
480
+ path: string;
481
+ line: number;
482
+ text: string;
483
+ }>;
484
+ metadata?: unknown;
485
+ }>;
486
+ stat(descriptor: {
487
+ filesystem: string;
488
+ path: string;
489
+ }): Promise<{
490
+ isDirectory: boolean;
491
+ metadata?: unknown;
492
+ }>;
493
+ rejectMutation(operation: string, descriptor: {
494
+ filesystem: string;
495
+ path: string;
496
+ }): never;
497
+ }
498
+ interface RuntimeFilesystemBinding {
499
+ readonly filesystem: string;
500
+ readonly access: 'readonly';
501
+ readonly operations: RuntimeFilesystemBindingOperations;
502
+ }
447
503
  interface RuntimeBundle {
448
504
  runtimeContext?: WorkspaceRuntimeContext;
449
505
  /**
@@ -461,6 +517,8 @@ interface RuntimeBundle {
461
517
  bash?: RuntimeBashStrategy;
462
518
  /** Runtime-owned filesystem strategy, consumed by the agent filesystem tool builder. */
463
519
  filesystem?: RuntimeFilesystemStrategy;
520
+ /** Optional filesystem bindings prepared for this runtime/session. */
521
+ filesystemBindings?: RuntimeFilesystemBinding[];
464
522
  }
465
523
 
466
524
  declare const REMOTE_WORKER_RUNTIME_CWD = "/workspace";
@@ -608,6 +666,8 @@ declare function createRemoteWorkerSandbox(client: RemoteWorkerClient): Sandbox;
608
666
  declare function fileRoutes(app: FastifyInstance, opts: {
609
667
  workspace?: Workspace;
610
668
  getWorkspace?: (request: FastifyRequest) => Workspace | Promise<Workspace>;
669
+ filesystemBindings?: RuntimeFilesystemBinding[];
670
+ getFilesystemBindings?: (request: FastifyRequest) => RuntimeFilesystemBinding[] | undefined | Promise<RuntimeFilesystemBinding[] | undefined>;
611
671
  }, done: (err?: Error) => void): void;
612
672
 
613
673
  interface RuntimeTemplateContribution {
@@ -2656,6 +2656,9 @@ var BORING_SETTINGS_PATH = ".boring/settings";
2656
2656
  var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
2657
2657
  var DEFAULT_FILE_UPLOAD_DIR = "assets/uploads";
2658
2658
  var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
2659
+ var USER_FILESYSTEM_ID = "user";
2660
+ var ERROR_CODE_NOT_FOUND_OR_DENIED = "not_found_or_denied";
2661
+ var ERROR_CODE_READONLY = "readonly";
2659
2662
  var IMAGE_UPLOAD_EXTENSIONS = /* @__PURE__ */ new Set(["avif", "gif", "jpg", "jpeg", "png", "webp"]);
2660
2663
  var SAFE_UNKNOWN_FILE_EXTENSIONS = /* @__PURE__ */ new Set(["csv", "doc", "docx", "json", "md", "pdf", "ppt", "pptx", "rtf", "txt", "xls", "xlsx", "zip"]);
2661
2664
  var MIME_UPLOAD_EXTENSIONS = {
@@ -2723,6 +2726,14 @@ function classifyError(err, reply, subject) {
2723
2726
  error: { code: ERROR_CODE_INTERNAL, message }
2724
2727
  });
2725
2728
  }
2729
+ function requestedFilesystem(value) {
2730
+ return typeof value === "string" && value.length > 0 ? value : USER_FILESYSTEM_ID;
2731
+ }
2732
+ function sendNotFoundOrDenied(reply) {
2733
+ return reply.code(404).send({
2734
+ error: { code: ERROR_CODE_NOT_FOUND_OR_DENIED, message: "not found or denied" }
2735
+ });
2736
+ }
2726
2737
  function requireStringParam(value, field, reply) {
2727
2738
  if (typeof value !== "string" || value.length === 0) {
2728
2739
  reply.code(400).send({
@@ -2842,16 +2853,49 @@ function sendValidationError(reply, message, field) {
2842
2853
  error: { code: ERROR_CODE_VALIDATION_ERROR, message, ...field ? { field } : {} }
2843
2854
  });
2844
2855
  }
2856
+ function sendFilesystemBindingMutationDenied(reply, filesystem, access5) {
2857
+ return reply.code(403).send({
2858
+ error: { code: ERROR_CODE_READONLY, message: `${filesystem} binding is ${access5}` }
2859
+ });
2860
+ }
2845
2861
  function fileRoutes(app, opts, done) {
2846
2862
  async function resolveWorkspace(request) {
2847
2863
  if (opts.getWorkspace) return await opts.getWorkspace(request);
2848
2864
  if (opts.workspace) return opts.workspace;
2849
2865
  throw new Error("file route requires workspace or getWorkspace");
2850
2866
  }
2867
+ async function resolveFilesystemBindings(request) {
2868
+ if (opts.getFilesystemBindings) return await opts.getFilesystemBindings(request) ?? [];
2869
+ if (opts.filesystemBindings) return opts.filesystemBindings;
2870
+ return [];
2871
+ }
2872
+ async function resolveFilesystemBinding(request, filesystem) {
2873
+ return (await resolveFilesystemBindings(request)).find((binding) => binding.filesystem === filesystem);
2874
+ }
2875
+ async function rejectUnsupportedFilesystemMutation(request, reply, filesystem) {
2876
+ const binding = await resolveFilesystemBinding(request, filesystem);
2877
+ if (!binding) return sendNotFoundOrDenied(reply);
2878
+ if (binding.access === "readonly") return sendFilesystemBindingMutationDenied(reply, filesystem, binding.access);
2879
+ return reply.code(501).send({
2880
+ error: { code: ERROR_CODE_INTERNAL, message: `${filesystem} binding access is not supported by this route` }
2881
+ });
2882
+ }
2851
2883
  app.get("/api/v1/files/raw", async (request, reply) => {
2852
2884
  const query = request.query;
2853
2885
  const path4 = requireStringParam(query.path, "path", reply);
2854
2886
  if (path4 === null) return;
2887
+ const filesystem = requestedFilesystem(query.filesystem);
2888
+ if (filesystem !== USER_FILESYSTEM_ID) {
2889
+ try {
2890
+ const binding = await resolveFilesystemBinding(request, filesystem);
2891
+ if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
2892
+ const result = await binding.operations.read({ filesystem, path: path4 });
2893
+ const bytes = Buffer.from(result.content, "utf8");
2894
+ return reply.header("content-type", contentTypeForPath(path4)).header("content-length", String(bytes.byteLength)).header("cache-control", "no-store").header("x-content-type-options", "nosniff").send(bytes);
2895
+ } catch {
2896
+ return sendNotFoundOrDenied(reply);
2897
+ }
2898
+ }
2855
2899
  try {
2856
2900
  const workspace = await resolveWorkspace(request);
2857
2901
  if (!workspace.readBinaryFile) {
@@ -2905,6 +2949,17 @@ function fileRoutes(app, opts, done) {
2905
2949
  const query = request.query;
2906
2950
  const path4 = requireStringParam(query.path, "path", reply);
2907
2951
  if (path4 === null) return;
2952
+ const filesystem = requestedFilesystem(query.filesystem);
2953
+ if (filesystem !== USER_FILESYSTEM_ID) {
2954
+ try {
2955
+ const binding = await resolveFilesystemBinding(request, filesystem);
2956
+ if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
2957
+ const result = await binding.operations.read({ filesystem, path: path4 });
2958
+ return { content: result.content };
2959
+ } catch {
2960
+ return sendNotFoundOrDenied(reply);
2961
+ }
2962
+ }
2908
2963
  try {
2909
2964
  if (isReadonlySkillFilePath(path4)) {
2910
2965
  const { content: content2, stat: stat13 } = await readReadonlySkillFile(path4);
@@ -2936,6 +2991,8 @@ function fileRoutes(app, opts, done) {
2936
2991
  error: { code: ERROR_CODE_VALIDATION_ERROR, message: "content is required", field: "content" }
2937
2992
  });
2938
2993
  }
2994
+ const filesystem = requestedFilesystem(body.filesystem);
2995
+ if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
2939
2996
  const expectedMtimeMs = typeof body.expectedMtimeMs === "number" ? body.expectedMtimeMs : null;
2940
2997
  const shouldReturnMtimeMs = body.returnMtimeMs !== false || expectedMtimeMs !== null;
2941
2998
  try {
@@ -3069,6 +3126,8 @@ function fileRoutes(app, opts, done) {
3069
3126
  const query = request.query;
3070
3127
  const path4 = requireStringParam(query.path, "path", reply);
3071
3128
  if (path4 === null) return;
3129
+ const filesystem = requestedFilesystem(query.filesystem);
3130
+ if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3072
3131
  try {
3073
3132
  const workspace = await resolveWorkspace(request);
3074
3133
  await workspace.unlink(path4);
@@ -3083,6 +3142,8 @@ function fileRoutes(app, opts, done) {
3083
3142
  if (from === null) return;
3084
3143
  const to = requireStringParam(body?.to, "to", reply);
3085
3144
  if (to === null) return;
3145
+ const filesystem = requestedFilesystem(body.filesystem);
3146
+ if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3086
3147
  try {
3087
3148
  const workspace = await resolveWorkspace(request);
3088
3149
  await workspace.rename(from, to);
@@ -3096,6 +3157,8 @@ function fileRoutes(app, opts, done) {
3096
3157
  const path4 = requireStringParam(body?.path, "path", reply);
3097
3158
  if (path4 === null) return;
3098
3159
  const recursive = body.recursive === true;
3160
+ const filesystem = requestedFilesystem(body.filesystem);
3161
+ if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3099
3162
  try {
3100
3163
  const workspace = await resolveWorkspace(request);
3101
3164
  await workspace.mkdir(path4, { recursive });
@@ -3108,6 +3171,17 @@ function fileRoutes(app, opts, done) {
3108
3171
  const query = request.query;
3109
3172
  const path4 = requireStringParam(query.path, "path", reply);
3110
3173
  if (path4 === null) return;
3174
+ const filesystem = requestedFilesystem(query.filesystem);
3175
+ if (filesystem !== USER_FILESYSTEM_ID) {
3176
+ try {
3177
+ const binding = await resolveFilesystemBinding(request, filesystem);
3178
+ if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
3179
+ const result = await binding.operations.stat({ filesystem, path: path4 });
3180
+ return result.isDirectory ? { kind: "dir" } : { kind: "file", size: 0 };
3181
+ } catch {
3182
+ return sendNotFoundOrDenied(reply);
3183
+ }
3184
+ }
3111
3185
  try {
3112
3186
  if (isReadonlySkillFilePath(path4)) {
3113
3187
  return await statReadonlySkillFile(path4);
@@ -8327,17 +8401,135 @@ function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
8327
8401
  function isTextContent2(content) {
8328
8402
  return content.type === "text" && typeof content.text === "string";
8329
8403
  }
8404
+ function filesystemBindings(bundle) {
8405
+ return bundle.filesystemBindings ?? [];
8406
+ }
8407
+ function filesystemIds(bundle) {
8408
+ return filesystemBindings(bundle).map((binding) => binding.filesystem);
8409
+ }
8410
+ function withFilesystemParameter(parameters, filesystemIds2) {
8411
+ const schema = parameters && typeof parameters === "object" ? { ...parameters } : { type: "object" };
8412
+ const properties = schema.properties && typeof schema.properties === "object" ? { ...schema.properties } : {};
8413
+ return {
8414
+ ...schema,
8415
+ properties: {
8416
+ ...properties,
8417
+ filesystem: {
8418
+ type: "string",
8419
+ description: filesystemIds2.length > 0 ? "Logical filesystem to use. Omit or use user for workspace files; use an advertised named filesystem for bound readonly context." : "Logical filesystem to use. Omit or use user for workspace files.",
8420
+ enum: ["user", ...filesystemIds2]
8421
+ }
8422
+ }
8423
+ };
8424
+ }
8425
+ function requestedFilesystem2(params) {
8426
+ const value = params.filesystem;
8427
+ return typeof value === "string" && value.length > 0 ? value : "user";
8428
+ }
8429
+ function withoutFilesystem(params) {
8430
+ const { filesystem: _filesystem, ...rest } = params;
8431
+ return rest;
8432
+ }
8433
+ function boundFilesystemPath(params) {
8434
+ const value = params.path;
8435
+ return typeof value === "string" && value.length > 0 ? value : "/";
8436
+ }
8437
+ function assertNotFilesystemPathSpoof(path4, filesystemIds2) {
8438
+ const normalized = path4.replace(/\\/g, "/");
8439
+ if (normalized.includes(":/") || filesystemIds2.some((filesystem) => normalized === `/${filesystem}` || normalized.startsWith(`/${filesystem}/`))) {
8440
+ throw new Error("filesystem prefixes are not valid path strings; use the filesystem parameter");
8441
+ }
8442
+ }
8443
+ function filesystemBinding(bundle, filesystem) {
8444
+ return filesystemBindings(bundle).find((binding) => binding.filesystem === filesystem);
8445
+ }
8446
+ function boundOperations(bundle, filesystem) {
8447
+ const binding = filesystemBinding(bundle, filesystem);
8448
+ if (!binding) throw new Error(`No filesystem binding is available for ${filesystem}`);
8449
+ return binding.operations;
8450
+ }
8451
+ function formatBoundRead(content) {
8452
+ return { content: [{ type: "text", text: content }] };
8453
+ }
8454
+ function formatBoundList(entries) {
8455
+ return { content: [{ type: "text", text: entries.join("\n") }] };
8456
+ }
8457
+ function formatBoundFind(paths) {
8458
+ return { content: [{ type: "text", text: paths.join("\n") }] };
8459
+ }
8460
+ function formatBoundGrep(matches) {
8461
+ return { content: [{ type: "text", text: matches.map((match) => `${match.path}:${match.line}:${match.text}`).join("\n") }] };
8462
+ }
8463
+ async function executeBoundFilesystemTool(toolName, filesystem, params, bundle) {
8464
+ const path4 = boundFilesystemPath(params);
8465
+ assertNotFilesystemPathSpoof(path4, filesystemIds(bundle));
8466
+ const operations = boundOperations(bundle, filesystem);
8467
+ if (toolName === "read") {
8468
+ const result = await operations.read({ filesystem, path: path4 });
8469
+ return { ...formatBoundRead(result.content), details: { metadata: result.metadata } };
8470
+ }
8471
+ if (toolName === "ls") {
8472
+ const result = await operations.list({ filesystem, path: path4 });
8473
+ return { ...formatBoundList(result.entries), details: { metadata: result.metadata } };
8474
+ }
8475
+ if (toolName === "find") {
8476
+ const pattern = typeof params.pattern === "string" ? params.pattern : "*";
8477
+ const limit = typeof params.limit === "number" ? params.limit : void 0;
8478
+ const result = await operations.find({ filesystem, path: path4 }, pattern, { limit });
8479
+ return { ...formatBoundFind(result.paths), details: { metadata: result.metadata } };
8480
+ }
8481
+ if (toolName === "grep") {
8482
+ const pattern = typeof params.pattern === "string" ? params.pattern : "";
8483
+ const limit = typeof params.limit === "number" ? params.limit : void 0;
8484
+ const result = await operations.grep({ filesystem, path: path4 }, pattern, { limit });
8485
+ return { ...formatBoundGrep(result.matches), details: { metadata: result.metadata } };
8486
+ }
8487
+ if (toolName === "write" || toolName === "edit") {
8488
+ operations.rejectMutation(toolName, { filesystem, path: path4 });
8489
+ }
8490
+ throw new Error(`Tool ${toolName} does not support filesystem ${filesystem}`);
8491
+ }
8492
+ function withBoundFilesystemPromptGuidance(promptSnippet, filesystemIds2) {
8493
+ if (filesystemIds2.length === 0) return promptSnippet;
8494
+ const guidance = [
8495
+ "Named filesystem bindings: file tools default to the user workspace when filesystem is omitted.",
8496
+ `Use the filesystem parameter explicitly for bound readonly context (${filesystemIds2.join(", ")}), and start browsing at / unless told otherwise.`,
8497
+ "Readonly filesystem bindings reject writes; do not use path prefixes like filesystem:/x to switch filesystem."
8498
+ ].join("\n");
8499
+ return [promptSnippet, guidance].filter(Boolean).join("\n");
8500
+ }
8501
+ function withFilesystemRouting(tool, bundle) {
8502
+ const ids = filesystemIds(bundle);
8503
+ return {
8504
+ ...tool,
8505
+ promptSnippet: withBoundFilesystemPromptGuidance(tool.promptSnippet, ids),
8506
+ parameters: withFilesystemParameter(tool.parameters, ids),
8507
+ async execute(params, ctx) {
8508
+ const filesystem = requestedFilesystem2(params);
8509
+ if (filesystem !== "user") {
8510
+ const result = await executeBoundFilesystemTool(tool.name, filesystem, params, bundle);
8511
+ const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
8512
+ return {
8513
+ content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
8514
+ isError: false,
8515
+ details: result.details
8516
+ };
8517
+ }
8518
+ return await tool.execute(withoutFilesystem(params), ctx);
8519
+ }
8520
+ };
8521
+ }
8330
8522
  function adaptPiTool2(piTool) {
8331
8523
  return {
8332
8524
  name: piTool.name,
8333
8525
  readinessRequirements: ["workspace-fs"],
8334
8526
  description: piTool.description,
8335
8527
  promptSnippet: piTool.promptSnippet,
8336
- parameters: piTool.parameters,
8528
+ parameters: piTool.parameters && typeof piTool.parameters === "object" ? { ...piTool.parameters } : { type: "object" },
8337
8529
  async execute(params, ctx) {
8338
8530
  const result = await piTool.execute(
8339
8531
  ctx.toolCallId,
8340
- params,
8532
+ withoutFilesystem(params),
8341
8533
  ctx.abortSignal,
8342
8534
  ctx.onUpdate ? (update) => {
8343
8535
  const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
@@ -8361,7 +8553,7 @@ function buildFilesystemAgentTools(bundle) {
8361
8553
  const cwd = bundle.workspace.root;
8362
8554
  const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
8363
8555
  if (strategy.kind === "remote-workspace") {
8364
- return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
8556
+ return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions).map((tool) => withFilesystemRouting(tool, bundle));
8365
8557
  }
8366
8558
  const storageRoot = getRuntimeBundleStorageRoot(bundle);
8367
8559
  const ops = boundFs(storageRoot, { runtimeRoot: cwd });
@@ -8372,7 +8564,7 @@ function buildFilesystemAgentTools(bundle) {
8372
8564
  adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
8373
8565
  adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
8374
8566
  adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
8375
- ];
8567
+ ].map((tool) => withFilesystemRouting(tool, bundle));
8376
8568
  }
8377
8569
 
8378
8570
  // src/server/tools/harness/index.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.62",
3
+ "version": "0.1.63",
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.62"
77
+ "@hachej/boring-ui-kit": "0.1.63"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",