@hachej/boring-agent 0.1.63 → 0.1.64

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.
@@ -171,7 +171,8 @@ var BoringChatPartSchema = z2.discriminatedUnion("type", [
171
171
  filename: z2.string().optional(),
172
172
  mediaType: z2.string().optional(),
173
173
  url: z2.string().optional(),
174
- path: z2.string().optional()
174
+ path: z2.string().optional(),
175
+ filesystem: z2.string().optional()
175
176
  }),
176
177
  z2.object({
177
178
  type: z2.literal("notice"),
@@ -2,7 +2,7 @@ import * as react_jsx_runtime from 'react/jsx-runtime';
2
2
  import * as react from 'react';
3
3
  import { ComponentProps, HTMLAttributes, FormEvent, ReactNode, ComponentType } from 'react';
4
4
  import { FileUIPart, ChatStatus, UIMessage } from 'ai';
5
- import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-B6GDifo2.js';
5
+ import { T as ToolUiMetadata, B as BoringChatPart, d as BoringChatMessage, m as PiChatStatus, r as QueuedUserMessage, C as ChatError, P as PiChatEvent, o as PromptPayload, p as PromptReceipt, F as FollowUpPayload, i as FollowUpReceipt, Q as QueueClearPayload, q as QueueClearReceipt, I as InterruptPayload, e as CommandReceipt, S as StopPayload, s as StopReceipt } from '../piChatEvent-JbwYVBZj.js';
6
6
  import { e as SessionSummary } from '../chatSubmitPayload-DHqQL2wD.js';
7
7
  import { InputGroupAddon, InputGroupButton, InputGroupTextarea, Button, Collapsible, CollapsibleContent, CollapsibleTrigger } from '@hachej/boring-ui-kit';
8
8
  import { Streamdown } from 'streamdown';
@@ -650,7 +650,9 @@ interface PiChatPanelProps<TComposerBlocker extends ComposerBlocker = ComposerBl
650
650
  clientNonce: string;
651
651
  }) => void;
652
652
  onData?: (part: unknown) => void;
653
- onOpenArtifact?: (path: string) => void;
653
+ onOpenArtifact?: (path: string, options?: {
654
+ filesystem?: string;
655
+ }) => void;
654
656
  composerBlockers?: TComposerBlocker[];
655
657
  onComposerStop?: () => void;
656
658
  onComposerBlockerAction?: (blocker: TComposerBlocker, action: string) => void;
@@ -677,7 +679,10 @@ interface DebugDrawerProps {
677
679
  }
678
680
  declare function DebugDrawer({ apiBaseUrl, fetch, sessionId, messages, requestHeaders, storageScope, width, onWidthChange }: DebugDrawerProps): react_jsx_runtime.JSX.Element;
679
681
 
680
- type OpenArtifactHandler = (path: string) => void;
682
+ interface OpenArtifactOptions {
683
+ filesystem?: string;
684
+ }
685
+ type OpenArtifactHandler = (path: string, options?: OpenArtifactOptions) => void;
681
686
  interface ArtifactOpenProviderProps {
682
687
  onOpenArtifact?: OpenArtifactHandler;
683
688
  children: ReactNode;
@@ -13,7 +13,7 @@ import {
13
13
  StopReceiptSchema,
14
14
  extractToolUiMetadata,
15
15
  sanitizeToolUiMetadata
16
- } from "../chunk-G6YPXDHR.js";
16
+ } from "../chunk-UPRDF2PB.js";
17
17
  import {
18
18
  DebugDrawer,
19
19
  cn,
@@ -1534,7 +1534,7 @@ function toHeaderProps(part) {
1534
1534
  toolName: part.toolName
1535
1535
  };
1536
1536
  }
1537
- function PathLabel({ path }) {
1537
+ function PathLabel({ path, filesystem }) {
1538
1538
  const onOpen = useOpenArtifact();
1539
1539
  if (!onOpen) {
1540
1540
  return /* @__PURE__ */ jsx11("span", { className: "font-mono text-[12.5px] text-foreground/85", children: path });
@@ -1542,7 +1542,8 @@ function PathLabel({ path }) {
1542
1542
  const open = (e) => {
1543
1543
  e.preventDefault();
1544
1544
  e.stopPropagation();
1545
- onOpen(path);
1545
+ if (filesystem) onOpen(path, { filesystem });
1546
+ else onOpen(path);
1546
1547
  };
1547
1548
  const handleKeyDown = (e) => {
1548
1549
  if (e.key === "Enter" || e.key === " ") open(e);
@@ -1603,11 +1604,11 @@ function renderRuntimeNotReady(part) {
1603
1604
  function renderReadinessBlock(part) {
1604
1605
  return renderWorkspaceNotReady(part) ?? renderRuntimeNotReady(part);
1605
1606
  }
1606
- function pathTitle(prefix, path) {
1607
+ function pathTitle(prefix, path, filesystem) {
1607
1608
  return /* @__PURE__ */ jsxs10("span", { className: "flex min-w-0 items-center gap-1.5", children: [
1608
1609
  /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground", children: prefix }),
1609
1610
  /* @__PURE__ */ jsx11("span", { className: "text-muted-foreground/40", "aria-hidden": "true", children: "\xB7" }),
1610
- /* @__PURE__ */ jsx11(PathLabel, { path })
1611
+ /* @__PURE__ */ jsx11(PathLabel, { path, filesystem })
1611
1612
  ] });
1612
1613
  }
1613
1614
  function renderBash2(part) {
@@ -1649,10 +1650,11 @@ function renderRead2(part) {
1649
1650
  const input = asRecord2(part.input);
1650
1651
  const output = asRecord2(part.output);
1651
1652
  const path = typeof input.path === "string" ? input.path : "";
1653
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1652
1654
  const content = extractTextContent(output);
1653
1655
  const lang = langFromPath(path);
1654
1656
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1655
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileTextIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("read", path), ...toHeaderProps(part) }),
1657
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileTextIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("read", path, filesystem), ...toHeaderProps(part) }),
1656
1658
  /* @__PURE__ */ jsxs10(ToolContent, { children: [
1657
1659
  /* @__PURE__ */ jsx11(ToolInput, { input }),
1658
1660
  content && /* @__PURE__ */ jsxs10("section", { className: "space-y-2", children: [
@@ -1667,11 +1669,12 @@ function renderWrite2(part) {
1667
1669
  if (readiness) return readiness;
1668
1670
  const input = asRecord2(part.input);
1669
1671
  const path = typeof input.path === "string" ? input.path : "";
1672
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1670
1673
  const content = typeof input.content === "string" ? input.content : "";
1671
1674
  const bytes = content.length;
1672
1675
  const lang = langFromPath(path);
1673
1676
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1674
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 text-muted-foreground" }), title: pathTitle("write", path), ...toHeaderProps(part) }),
1677
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FilePlus2Icon, { className: "size-4 text-muted-foreground" }), title: pathTitle("write", path, filesystem), ...toHeaderProps(part) }),
1675
1678
  /* @__PURE__ */ jsx11(ToolContent, { children: /* @__PURE__ */ jsxs10(Artifact, { className: "rounded-none border-0 bg-transparent shadow-none", children: [
1676
1679
  /* @__PURE__ */ jsxs10(ArtifactHeader, { className: "border-0 px-0 pt-0 pb-2", children: [
1677
1680
  /* @__PURE__ */ jsxs10("div", { children: [
@@ -1728,10 +1731,11 @@ function renderEdit2(part) {
1728
1731
  if (readiness) return readiness;
1729
1732
  const input = asRecord2(part.input);
1730
1733
  const path = typeof input.path === "string" ? input.path : "";
1734
+ const filesystem = typeof input.filesystem === "string" ? input.filesystem : void 0;
1731
1735
  const oldString = typeof input.oldString === "string" ? input.oldString : "";
1732
1736
  const newString = typeof input.newString === "string" ? input.newString : "";
1733
1737
  return /* @__PURE__ */ jsxs10(Tool2, { children: [
1734
- /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("edit", path), ...toHeaderProps(part) }),
1738
+ /* @__PURE__ */ jsx11(ToolHeader, { icon: /* @__PURE__ */ jsx11(FileDiffIcon, { className: "size-4 text-muted-foreground" }), title: pathTitle("edit", path, filesystem), ...toHeaderProps(part) }),
1735
1739
  /* @__PURE__ */ jsxs10(ToolContent, { children: [
1736
1740
  /* @__PURE__ */ jsxs10("section", { className: "space-y-2", children: [
1737
1741
  /* @__PURE__ */ jsx11("h4", { className: "font-medium text-muted-foreground text-xs uppercase tracking-wide", children: "Diff" }),
@@ -7046,7 +7050,8 @@ function PiTimelineMessage({ message, isLast, isStreaming, showThoughts, toolRen
7046
7050
  const canOpen = Boolean(openArtifact && openPath);
7047
7051
  const openAttachment = () => {
7048
7052
  if (!openPath) return;
7049
- openArtifact?.(openPath);
7053
+ if (file.filesystem) openArtifact?.(openPath, { filesystem: file.filesystem });
7054
+ else openArtifact?.(openPath);
7050
7055
  };
7051
7056
  const openAttachmentFromKeyboard = (event) => {
7052
7057
  if (event.key !== "Enter" && event.key !== " ") return;
@@ -112,6 +112,7 @@ type BoringChatPart = {
112
112
  mediaType?: string;
113
113
  url?: string;
114
114
  path?: string;
115
+ filesystem?: string;
115
116
  } | {
116
117
  type: 'notice';
117
118
  id?: string;
@@ -490,6 +490,35 @@ interface RuntimeFilesystemBindingOperations {
490
490
  isDirectory: boolean;
491
491
  metadata?: unknown;
492
492
  }>;
493
+ write?(descriptor: {
494
+ filesystem: string;
495
+ path: string;
496
+ content: string;
497
+ expectedMtimeMs?: number;
498
+ }): Promise<{
499
+ mtimeMs?: number;
500
+ metadata?: unknown;
501
+ }>;
502
+ delete?(descriptor: {
503
+ filesystem: string;
504
+ path: string;
505
+ }): Promise<{
506
+ metadata?: unknown;
507
+ }>;
508
+ move?(descriptor: {
509
+ filesystem: string;
510
+ from: string;
511
+ to: string;
512
+ }): Promise<{
513
+ metadata?: unknown;
514
+ }>;
515
+ mkdir?(descriptor: {
516
+ filesystem: string;
517
+ path: string;
518
+ recursive?: boolean;
519
+ }): Promise<{
520
+ metadata?: unknown;
521
+ }>;
493
522
  rejectMutation(operation: string, descriptor: {
494
523
  filesystem: string;
495
524
  path: string;
@@ -497,7 +526,7 @@ interface RuntimeFilesystemBindingOperations {
497
526
  }
498
527
  interface RuntimeFilesystemBinding {
499
528
  readonly filesystem: string;
500
- readonly access: 'readonly';
529
+ readonly access: 'readonly' | 'readwrite';
501
530
  readonly operations: RuntimeFilesystemBindingOperations;
502
531
  }
503
532
  interface RuntimeBundle {
@@ -1024,6 +1053,7 @@ interface RegisterAgentRoutesOptions {
1024
1053
  workspaceRoot: string;
1025
1054
  runtimeMode: RuntimeModeId;
1026
1055
  workspaceFsCapability?: Workspace['fsCapability'];
1056
+ authSubject?: string;
1027
1057
  }) => AgentTool[] | Promise<AgentTool[]>;
1028
1058
  systemPromptAppend?: string;
1029
1059
  /** Optional dynamic system-prompt source forwarded to the harness. */
@@ -13,7 +13,7 @@ import {
13
13
  StopPayloadSchema,
14
14
  extractToolUiMetadata,
15
15
  sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
16
- } from "../chunk-G6YPXDHR.js";
16
+ } from "../chunk-UPRDF2PB.js";
17
17
 
18
18
  // src/server/sandbox/direct/createDirectSandbox.ts
19
19
  import { spawn } from "child_process";
@@ -2872,14 +2872,6 @@ function fileRoutes(app, opts, done) {
2872
2872
  async function resolveFilesystemBinding(request, filesystem) {
2873
2873
  return (await resolveFilesystemBindings(request)).find((binding) => binding.filesystem === filesystem);
2874
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
- }
2883
2875
  app.get("/api/v1/files/raw", async (request, reply) => {
2884
2876
  const query = request.query;
2885
2877
  const path4 = requireStringParam(query.path, "path", reply);
@@ -2888,7 +2880,7 @@ function fileRoutes(app, opts, done) {
2888
2880
  if (filesystem !== USER_FILESYSTEM_ID) {
2889
2881
  try {
2890
2882
  const binding = await resolveFilesystemBinding(request, filesystem);
2891
- if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
2883
+ if (!binding) return sendNotFoundOrDenied(reply);
2892
2884
  const result = await binding.operations.read({ filesystem, path: path4 });
2893
2885
  const bytes = Buffer.from(result.content, "utf8");
2894
2886
  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);
@@ -2953,9 +2945,9 @@ function fileRoutes(app, opts, done) {
2953
2945
  if (filesystem !== USER_FILESYSTEM_ID) {
2954
2946
  try {
2955
2947
  const binding = await resolveFilesystemBinding(request, filesystem);
2956
- if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
2948
+ if (!binding) return sendNotFoundOrDenied(reply);
2957
2949
  const result = await binding.operations.read({ filesystem, path: path4 });
2958
- return { content: result.content };
2950
+ return { content: result.content, access: binding.access };
2959
2951
  } catch {
2960
2952
  return sendNotFoundOrDenied(reply);
2961
2953
  }
@@ -2973,11 +2965,11 @@ function fileRoutes(app, opts, done) {
2973
2965
  const workspace = await resolveWorkspace(request);
2974
2966
  if (workspace.readFileWithStat) {
2975
2967
  const { content: content2, stat: stat13 } = await workspace.readFileWithStat(path4);
2976
- return { content: content2, mtimeMs: stat13.kind === "file" ? stat13.mtimeMs : void 0 };
2968
+ return { content: content2, mtimeMs: stat13.kind === "file" ? stat13.mtimeMs : void 0, access: "readwrite" };
2977
2969
  }
2978
2970
  const content = await workspace.readFile(path4);
2979
2971
  const stat12 = await workspace.stat(path4);
2980
- return { content, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0 };
2972
+ return { content, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0, access: "readwrite" };
2981
2973
  } catch (err) {
2982
2974
  return classifyError(err, reply, "file");
2983
2975
  }
@@ -2992,9 +2984,27 @@ function fileRoutes(app, opts, done) {
2992
2984
  });
2993
2985
  }
2994
2986
  const filesystem = requestedFilesystem(body.filesystem);
2995
- if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
2996
2987
  const expectedMtimeMs = typeof body.expectedMtimeMs === "number" ? body.expectedMtimeMs : null;
2997
2988
  const shouldReturnMtimeMs = body.returnMtimeMs !== false || expectedMtimeMs !== null;
2989
+ if (filesystem !== USER_FILESYSTEM_ID) {
2990
+ try {
2991
+ const binding = await resolveFilesystemBinding(request, filesystem);
2992
+ if (!binding) return sendNotFoundOrDenied(reply);
2993
+ if (binding.access !== "readwrite") return sendFilesystemBindingMutationDenied(reply, filesystem, binding.access);
2994
+ if (!binding.operations.write) {
2995
+ return reply.code(501).send({ error: { code: ERROR_CODE_INTERNAL, message: `${filesystem} write operation is unavailable` } });
2996
+ }
2997
+ const result = await binding.operations.write({
2998
+ filesystem,
2999
+ path: path4,
3000
+ content: body.content,
3001
+ ...expectedMtimeMs !== null ? { expectedMtimeMs } : {}
3002
+ });
3003
+ return shouldReturnMtimeMs ? { ok: true, mtimeMs: result.mtimeMs } : { ok: true };
3004
+ } catch (err) {
3005
+ return classifyError(err, reply, "file");
3006
+ }
3007
+ }
2998
3008
  try {
2999
3009
  const workspace = await resolveWorkspace(request);
3000
3010
  if (expectedMtimeMs !== null) {
@@ -3127,7 +3137,20 @@ function fileRoutes(app, opts, done) {
3127
3137
  const path4 = requireStringParam(query.path, "path", reply);
3128
3138
  if (path4 === null) return;
3129
3139
  const filesystem = requestedFilesystem(query.filesystem);
3130
- if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3140
+ if (filesystem !== USER_FILESYSTEM_ID) {
3141
+ try {
3142
+ const binding = await resolveFilesystemBinding(request, filesystem);
3143
+ if (!binding) return sendNotFoundOrDenied(reply);
3144
+ if (binding.access !== "readwrite") return sendFilesystemBindingMutationDenied(reply, filesystem, binding.access);
3145
+ if (!binding.operations.delete) {
3146
+ return reply.code(501).send({ error: { code: ERROR_CODE_INTERNAL, message: `${filesystem} delete operation is unavailable` } });
3147
+ }
3148
+ await binding.operations.delete({ filesystem, path: path4 });
3149
+ return { ok: true };
3150
+ } catch (err) {
3151
+ return classifyError(err, reply, "file");
3152
+ }
3153
+ }
3131
3154
  try {
3132
3155
  const workspace = await resolveWorkspace(request);
3133
3156
  await workspace.unlink(path4);
@@ -3143,7 +3166,20 @@ function fileRoutes(app, opts, done) {
3143
3166
  const to = requireStringParam(body?.to, "to", reply);
3144
3167
  if (to === null) return;
3145
3168
  const filesystem = requestedFilesystem(body.filesystem);
3146
- if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3169
+ if (filesystem !== USER_FILESYSTEM_ID) {
3170
+ try {
3171
+ const binding = await resolveFilesystemBinding(request, filesystem);
3172
+ if (!binding) return sendNotFoundOrDenied(reply);
3173
+ if (binding.access !== "readwrite") return sendFilesystemBindingMutationDenied(reply, filesystem, binding.access);
3174
+ if (!binding.operations.move) {
3175
+ return reply.code(501).send({ error: { code: ERROR_CODE_INTERNAL, message: `${filesystem} move operation is unavailable` } });
3176
+ }
3177
+ await binding.operations.move({ filesystem, from, to });
3178
+ return { ok: true };
3179
+ } catch (err) {
3180
+ return classifyError(err, reply, "file");
3181
+ }
3182
+ }
3147
3183
  try {
3148
3184
  const workspace = await resolveWorkspace(request);
3149
3185
  await workspace.rename(from, to);
@@ -3158,7 +3194,20 @@ function fileRoutes(app, opts, done) {
3158
3194
  if (path4 === null) return;
3159
3195
  const recursive = body.recursive === true;
3160
3196
  const filesystem = requestedFilesystem(body.filesystem);
3161
- if (filesystem !== USER_FILESYSTEM_ID) return await rejectUnsupportedFilesystemMutation(request, reply, filesystem);
3197
+ if (filesystem !== USER_FILESYSTEM_ID) {
3198
+ try {
3199
+ const binding = await resolveFilesystemBinding(request, filesystem);
3200
+ if (!binding) return sendNotFoundOrDenied(reply);
3201
+ if (binding.access !== "readwrite") return sendFilesystemBindingMutationDenied(reply, filesystem, binding.access);
3202
+ if (!binding.operations.mkdir) {
3203
+ return reply.code(501).send({ error: { code: ERROR_CODE_INTERNAL, message: `${filesystem} mkdir operation is unavailable` } });
3204
+ }
3205
+ await binding.operations.mkdir({ filesystem, path: path4, recursive });
3206
+ return { ok: true };
3207
+ } catch (err) {
3208
+ return classifyError(err, reply, "directory");
3209
+ }
3210
+ }
3162
3211
  try {
3163
3212
  const workspace = await resolveWorkspace(request);
3164
3213
  await workspace.mkdir(path4, { recursive });
@@ -3175,7 +3224,7 @@ function fileRoutes(app, opts, done) {
3175
3224
  if (filesystem !== USER_FILESYSTEM_ID) {
3176
3225
  try {
3177
3226
  const binding = await resolveFilesystemBinding(request, filesystem);
3178
- if (!binding || binding.access !== "readonly") return sendNotFoundOrDenied(reply);
3227
+ if (!binding) return sendNotFoundOrDenied(reply);
3179
3228
  const result = await binding.operations.stat({ filesystem, path: path4 });
3180
3229
  return result.isDirectory ? { kind: "dir" } : { kind: "file", size: 0 };
3181
3230
  } catch {
@@ -9201,6 +9250,14 @@ function joinPath2(base, name) {
9201
9250
  if (base === ".") return name;
9202
9251
  return `${base}/${name}`;
9203
9252
  }
9253
+ function requestedFilesystem3(value) {
9254
+ return typeof value === "string" && value.length > 0 ? value : "user";
9255
+ }
9256
+ function sendNotFoundOrDenied2(reply) {
9257
+ return reply.code(404).send({
9258
+ error: { code: ERROR_CODE_NOT_FOUND_OR_DENIED, message: "not found or denied" }
9259
+ });
9260
+ }
9204
9261
  async function listTree(workspace, dir, recursive) {
9205
9262
  const entries = [];
9206
9263
  const initialEntries = await workspace.readdir(dir);
@@ -9225,12 +9282,44 @@ async function listTree(workspace, dir, recursive) {
9225
9282
  }
9226
9283
  return entries;
9227
9284
  }
9285
+ async function listBoundTree(binding, dir, recursive) {
9286
+ const root = dir === "." ? "/" : dir;
9287
+ const displayParentDir = dir === "." || dir === "/" ? "." : dir.replace(/\/+$/, "");
9288
+ const entries = [];
9289
+ const rootEntries = await binding.operations.list({ filesystem: binding.filesystem, path: root });
9290
+ const queue = [
9291
+ { parentDir: displayParentDir || ".", items: rootEntries.entries, depth: 0 }
9292
+ ];
9293
+ while (queue.length > 0) {
9294
+ if (entries.length >= MAX_ENTRIES) break;
9295
+ const batch = queue.shift();
9296
+ for (const name of batch.items) {
9297
+ if (entries.length >= MAX_ENTRIES) break;
9298
+ const entryPath = joinPath2(batch.parentDir, name);
9299
+ const stat12 = await binding.operations.stat({ filesystem: binding.filesystem, path: entryPath });
9300
+ const kind = stat12.isDirectory ? "dir" : "file";
9301
+ entries.push({ name, kind, path: entryPath });
9302
+ if (recursive && kind === "dir" && batch.depth < MAX_DEPTH && !isIgnoredDirName(name)) {
9303
+ try {
9304
+ const childEntries = await binding.operations.list({ filesystem: binding.filesystem, path: entryPath });
9305
+ queue.push({ parentDir: entryPath, items: childEntries.entries, depth: batch.depth + 1 });
9306
+ } catch {
9307
+ }
9308
+ }
9309
+ }
9310
+ }
9311
+ return entries;
9312
+ }
9228
9313
  function treeRoutes(app, opts, done) {
9229
9314
  async function resolveWorkspace(request) {
9230
9315
  if (opts.getWorkspace) return await opts.getWorkspace(request);
9231
9316
  if (opts.workspace) return opts.workspace;
9232
9317
  throw new Error("workspace route requires workspace or getWorkspace");
9233
9318
  }
9319
+ async function resolveFilesystemBinding(request, filesystem) {
9320
+ const bindings = opts.getFilesystemBindings ? await opts.getFilesystemBindings(request) ?? [] : opts.filesystemBindings ?? [];
9321
+ return bindings.find((binding) => binding.filesystem === filesystem);
9322
+ }
9234
9323
  app.get(
9235
9324
  "/api/v1/tree",
9236
9325
  async (request, reply) => {
@@ -9246,6 +9335,16 @@ function treeRoutes(app, opts, done) {
9246
9335
  throw err;
9247
9336
  }
9248
9337
  const recursive = request.query.recursive === "true";
9338
+ const filesystem = requestedFilesystem3(request.query.filesystem);
9339
+ if (filesystem !== "user") {
9340
+ try {
9341
+ const binding = await resolveFilesystemBinding(request, filesystem);
9342
+ if (!binding) return sendNotFoundOrDenied2(reply);
9343
+ return { entries: await listBoundTree(binding, dir, recursive) };
9344
+ } catch {
9345
+ return sendNotFoundOrDenied2(reply);
9346
+ }
9347
+ }
9249
9348
  try {
9250
9349
  const workspace = await resolveWorkspace(request);
9251
9350
  const entries = await listTree(workspace, dir, recursive);
@@ -12513,7 +12612,7 @@ async function createAgentApp(opts = {}) {
12513
12612
  });
12514
12613
  await app.register(fileRoutes, { workspace: runtimeBundle.workspace });
12515
12614
  await app.register(fsEventsRoutes, { workspace: runtimeBundle.workspace });
12516
- await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
12615
+ await app.register(treeRoutes, { workspace: runtimeBundle.workspace, filesystemBindings: runtimeBundle.filesystemBindings });
12517
12616
  await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
12518
12617
  await app.register(gitRoutes, {
12519
12618
  getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
@@ -12771,6 +12870,12 @@ function normalizeSessionNamespace(value) {
12771
12870
  const trimmed = value.trim();
12772
12871
  return trimmed.length > 0 ? trimmed : void 0;
12773
12872
  }
12873
+ function getRequestAuthSubject(request) {
12874
+ const userId = request?.user?.id;
12875
+ if (typeof userId === "string" && userId.trim()) return userId.trim();
12876
+ const authSubject = request?.workspaceContext?.authSubject;
12877
+ return typeof authSubject === "string" && authSubject.trim() ? authSubject.trim() : void 0;
12878
+ }
12774
12879
  function createHttpError(code, message, details = {}) {
12775
12880
  const error = new Error(message);
12776
12881
  error.code = code;
@@ -12829,7 +12934,7 @@ var registerAgentRoutes = async (app, opts) => {
12829
12934
  app.addHook("onClose", async () => {
12830
12935
  await modeAdapter.dispose?.();
12831
12936
  });
12832
- const requestScopedRuntime = typeof opts.getWorkspaceId === "function" || typeof opts.getWorkspaceRoot === "function" || typeof opts.getTemplatePath === "function" || typeof opts.getPi === "function" || typeof opts.getSessionNamespace === "function" || typeof opts.getSystemPromptDynamic === "function";
12937
+ const requestScopedRuntime = typeof opts.getWorkspaceId === "function" || typeof opts.getWorkspaceRoot === "function" || typeof opts.getTemplatePath === "function" || typeof opts.getPi === "function" || typeof opts.getExtraTools === "function" || typeof opts.getSessionNamespace === "function" || typeof opts.getSystemPromptDynamic === "function";
12833
12938
  const sessionChangesTracker = new InMemorySessionChangesTracker();
12834
12939
  const externalPluginsEnabled = opts.externalPlugins !== false;
12835
12940
  const runtimeBindings = /* @__PURE__ */ new Map();
@@ -12849,6 +12954,7 @@ var registerAgentRoutes = async (app, opts) => {
12849
12954
  const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
12850
12955
  const pi = await resolveScopePi(workspaceId, root, request);
12851
12956
  const sessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot: root, request }) : opts.sessionNamespace);
12957
+ const extraToolsAuthSubject = opts.getExtraTools ? getRequestAuthSubject(request) : void 0;
12852
12958
  return {
12853
12959
  root,
12854
12960
  templatePath: scopedTemplatePath,
@@ -12860,7 +12966,8 @@ var registerAgentRoutes = async (app, opts) => {
12860
12966
  root,
12861
12967
  scopedTemplatePath ?? null,
12862
12968
  pi,
12863
- sessionNamespace ?? null
12969
+ sessionNamespace ?? null,
12970
+ extraToolsAuthSubject ?? null
12864
12971
  ])
12865
12972
  };
12866
12973
  }
@@ -13044,7 +13151,8 @@ var registerAgentRoutes = async (app, opts) => {
13044
13151
  workspaceId,
13045
13152
  workspaceRoot: root,
13046
13153
  runtimeMode: resolvedMode,
13047
- workspaceFsCapability: runtimeBundle.workspace.fsCapability
13154
+ workspaceFsCapability: runtimeBundle.workspace.fsCapability,
13155
+ authSubject: getRequestAuthSubject(request)
13048
13156
  }) : [];
13049
13157
  const tools = mergeTools({
13050
13158
  standardTools,
@@ -13136,7 +13244,8 @@ var registerAgentRoutes = async (app, opts) => {
13136
13244
  return await ensureRuntimeBindingReady(
13137
13245
  workspaceId,
13138
13246
  scope,
13139
- await existing.promise
13247
+ await existing.promise,
13248
+ request
13140
13249
  );
13141
13250
  }
13142
13251
  }
@@ -13150,17 +13259,18 @@ var registerAgentRoutes = async (app, opts) => {
13150
13259
  return await ensureRuntimeBindingReady(
13151
13260
  workspaceId,
13152
13261
  scope,
13153
- await created.promise
13262
+ await created.promise,
13263
+ request
13154
13264
  );
13155
13265
  } catch (error) {
13156
13266
  if (runtimeBindings.get(scope.key) === created) runtimeBindings.delete(scope.key);
13157
13267
  throw error;
13158
13268
  }
13159
13269
  }
13160
- async function recreateRuntimeBinding(workspaceId, scope) {
13270
+ async function recreateRuntimeBinding(workspaceId, scope, request) {
13161
13271
  runtimeBindings.delete(scope.key);
13162
13272
  modeAdapter.evictCachedRuntime?.({ workspaceId });
13163
- const created = createRuntimeBindingEntry(workspaceId, scope);
13273
+ const created = createRuntimeBindingEntry(workspaceId, scope, request);
13164
13274
  runtimeBindings.set(scope.key, created);
13165
13275
  evictRuntimeBindings();
13166
13276
  try {
@@ -13172,7 +13282,7 @@ var registerAgentRoutes = async (app, opts) => {
13172
13282
  throw error;
13173
13283
  }
13174
13284
  }
13175
- async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
13285
+ async function ensureRuntimeBindingReady(workspaceId, scope, binding, request) {
13176
13286
  const healthCheck = modeAdapter.cachedBindingHealthCheck;
13177
13287
  if (!healthCheck) return binding;
13178
13288
  const now = Date.now();
@@ -13189,7 +13299,7 @@ var registerAgentRoutes = async (app, opts) => {
13189
13299
  err: result.error,
13190
13300
  workspaceId
13191
13301
  }, result.message ?? "[runtime] cached runtime invalid; recreating");
13192
- return await recreateRuntimeBinding(workspaceId, scope);
13302
+ return await recreateRuntimeBinding(workspaceId, scope, request);
13193
13303
  }
13194
13304
  const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
13195
13305
  const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
@@ -13282,7 +13392,8 @@ var registerAgentRoutes = async (app, opts) => {
13282
13392
  getWorkspace: async (request) => (await getBindingForRequest(request)).runtimeBundle.workspace
13283
13393
  });
13284
13394
  await app.register(treeRoutes, {
13285
- getWorkspace: async (request) => (await getBindingForRequest(request)).runtimeBundle.workspace
13395
+ getWorkspace: async (request) => (await getBindingForRequest(request)).runtimeBundle.workspace,
13396
+ getFilesystemBindings: async (request) => (await getBindingForRequest(request)).runtimeBundle.filesystemBindings
13286
13397
  });
13287
13398
  await app.register(searchRoutes, {
13288
13399
  getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
@@ -1,7 +1,7 @@
1
1
  import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../agentPluginEvents-Ddn5DQ5E.js';
2
2
  export { a as AgentHarness, C as CommandNotifyPayload, E as Entry, b as ExecOptions, c as ExecResult, I as IsolatedCodeInput, d as IsolatedCodeOutput, J as JSONSchema, R as RunContext, e as SandboxCapability, f as SandboxHandleRecord, g as SandboxHandleStore, h as SendMessageInput, i as Stat, T as TelemetryEvent, j as TelemetrySink, k as ToolExecContext, l as ToolResult, m as WORKSPACE_AGENT_PLUGINS_RELOADED_EVENT, n as WORKSPACE_COMMAND_NOTIFY_EVENT, o as WorkspaceRuntimeContext, p as noopTelemetry, s as safeCapture } from '../agentPluginEvents-Ddn5DQ5E.js';
3
- import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-B6GDifo2.js';
4
- export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-B6GDifo2.js';
3
+ import { B as BoringChatPart, T as ToolUiMetadata } from '../piChatEvent-JbwYVBZj.js';
4
+ export { A as ApiErrorPayload, a as ApiErrorPayloadSchema, b as ApiErrorResponse, c as ApiErrorResponseSchema, d as BoringChatMessage, C as ChatError, e as CommandReceipt, E as ERROR_CODES, f as ErrorCode, g as ErrorLogFields, h as ErrorLogFieldsSchema, F as FollowUpPayload, i as FollowUpReceipt, I as InterruptPayload, j as InterruptReceipt, P as PiChatEvent, k as PiChatHeartbeatFrame, l as PiChatSnapshot, m as PiChatStatus, n as PiChatStreamFrame, o as PromptPayload, p as PromptReceipt, Q as QueueClearPayload, q as QueueClearReceipt, r as QueuedUserMessage, S as StopPayload, s as StopReceipt, t as extractToolUiMetadata, u as isToolUiMetadata } from '../piChatEvent-JbwYVBZj.js';
5
5
  export { C as ChatAttachmentPayload, a as ChatModelSelection, b as ChatSubmitPayload, S as SessionCtx, c as SessionDetail, d as SessionStore, e as SessionSummary, T as ThinkingLevel } from '../chatSubmitPayload-DHqQL2wD.js';
6
6
  import { z } from 'zod';
7
7
 
@@ -39,7 +39,7 @@ import {
39
39
  extractToolUiMetadata,
40
40
  isToolUiMetadata,
41
41
  sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
42
- } from "../chunk-G6YPXDHR.js";
42
+ } from "../chunk-UPRDF2PB.js";
43
43
 
44
44
  // src/shared/config-schema.ts
45
45
  import { z } from "zod";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hachej/boring-agent",
3
- "version": "0.1.63",
3
+ "version": "0.1.64",
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.63"
77
+ "@hachej/boring-ui-kit": "0.1.64"
78
78
  },
79
79
  "devDependencies": {
80
80
  "@antithesishq/bombadil": "0.5.0",