@hachej/boring-agent 0.1.62 → 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.
- package/dist/{chunk-G6YPXDHR.js → chunk-UPRDF2PB.js} +2 -1
- package/dist/front/index.d.ts +8 -3
- package/dist/front/index.js +14 -9
- package/dist/front/styles.css +2 -0
- package/dist/{piChatEvent-B6GDifo2.d.ts → piChatEvent-JbwYVBZj.d.ts} +1 -0
- package/dist/server/index.d.ts +90 -0
- package/dist/server/index.js +321 -18
- package/dist/shared/index.d.ts +2 -2
- package/dist/shared/index.js +1 -1
- package/package.json +2 -2
|
@@ -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"),
|
package/dist/front/index.d.ts
CHANGED
|
@@ -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-
|
|
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
|
|
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
|
-
|
|
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;
|
package/dist/front/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
StopReceiptSchema,
|
|
14
14
|
extractToolUiMetadata,
|
|
15
15
|
sanitizeToolUiMetadata
|
|
16
|
-
} from "../chunk-
|
|
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;
|
package/dist/front/styles.css
CHANGED
package/dist/server/index.d.ts
CHANGED
|
@@ -444,6 +444,91 @@ 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
|
+
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
|
+
}>;
|
|
522
|
+
rejectMutation(operation: string, descriptor: {
|
|
523
|
+
filesystem: string;
|
|
524
|
+
path: string;
|
|
525
|
+
}): never;
|
|
526
|
+
}
|
|
527
|
+
interface RuntimeFilesystemBinding {
|
|
528
|
+
readonly filesystem: string;
|
|
529
|
+
readonly access: 'readonly' | 'readwrite';
|
|
530
|
+
readonly operations: RuntimeFilesystemBindingOperations;
|
|
531
|
+
}
|
|
447
532
|
interface RuntimeBundle {
|
|
448
533
|
runtimeContext?: WorkspaceRuntimeContext;
|
|
449
534
|
/**
|
|
@@ -461,6 +546,8 @@ interface RuntimeBundle {
|
|
|
461
546
|
bash?: RuntimeBashStrategy;
|
|
462
547
|
/** Runtime-owned filesystem strategy, consumed by the agent filesystem tool builder. */
|
|
463
548
|
filesystem?: RuntimeFilesystemStrategy;
|
|
549
|
+
/** Optional filesystem bindings prepared for this runtime/session. */
|
|
550
|
+
filesystemBindings?: RuntimeFilesystemBinding[];
|
|
464
551
|
}
|
|
465
552
|
|
|
466
553
|
declare const REMOTE_WORKER_RUNTIME_CWD = "/workspace";
|
|
@@ -608,6 +695,8 @@ declare function createRemoteWorkerSandbox(client: RemoteWorkerClient): Sandbox;
|
|
|
608
695
|
declare function fileRoutes(app: FastifyInstance, opts: {
|
|
609
696
|
workspace?: Workspace;
|
|
610
697
|
getWorkspace?: (request: FastifyRequest) => Workspace | Promise<Workspace>;
|
|
698
|
+
filesystemBindings?: RuntimeFilesystemBinding[];
|
|
699
|
+
getFilesystemBindings?: (request: FastifyRequest) => RuntimeFilesystemBinding[] | undefined | Promise<RuntimeFilesystemBinding[] | undefined>;
|
|
611
700
|
}, done: (err?: Error) => void): void;
|
|
612
701
|
|
|
613
702
|
interface RuntimeTemplateContribution {
|
|
@@ -964,6 +1053,7 @@ interface RegisterAgentRoutesOptions {
|
|
|
964
1053
|
workspaceRoot: string;
|
|
965
1054
|
runtimeMode: RuntimeModeId;
|
|
966
1055
|
workspaceFsCapability?: Workspace['fsCapability'];
|
|
1056
|
+
authSubject?: string;
|
|
967
1057
|
}) => AgentTool[] | Promise<AgentTool[]>;
|
|
968
1058
|
systemPromptAppend?: string;
|
|
969
1059
|
/** Optional dynamic system-prompt source forwarded to the harness. */
|
package/dist/server/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
StopPayloadSchema,
|
|
14
14
|
extractToolUiMetadata,
|
|
15
15
|
sanitizeToolUiMetadata2 as sanitizeToolUiMetadata
|
|
16
|
-
} from "../chunk-
|
|
16
|
+
} from "../chunk-UPRDF2PB.js";
|
|
17
17
|
|
|
18
18
|
// src/server/sandbox/direct/createDirectSandbox.ts
|
|
19
19
|
import { spawn } from "child_process";
|
|
@@ -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,41 @@ 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
|
+
}
|
|
2851
2875
|
app.get("/api/v1/files/raw", async (request, reply) => {
|
|
2852
2876
|
const query = request.query;
|
|
2853
2877
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
2854
2878
|
if (path4 === null) return;
|
|
2879
|
+
const filesystem = requestedFilesystem(query.filesystem);
|
|
2880
|
+
if (filesystem !== USER_FILESYSTEM_ID) {
|
|
2881
|
+
try {
|
|
2882
|
+
const binding = await resolveFilesystemBinding(request, filesystem);
|
|
2883
|
+
if (!binding) return sendNotFoundOrDenied(reply);
|
|
2884
|
+
const result = await binding.operations.read({ filesystem, path: path4 });
|
|
2885
|
+
const bytes = Buffer.from(result.content, "utf8");
|
|
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);
|
|
2887
|
+
} catch {
|
|
2888
|
+
return sendNotFoundOrDenied(reply);
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2855
2891
|
try {
|
|
2856
2892
|
const workspace = await resolveWorkspace(request);
|
|
2857
2893
|
if (!workspace.readBinaryFile) {
|
|
@@ -2905,6 +2941,17 @@ function fileRoutes(app, opts, done) {
|
|
|
2905
2941
|
const query = request.query;
|
|
2906
2942
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
2907
2943
|
if (path4 === null) return;
|
|
2944
|
+
const filesystem = requestedFilesystem(query.filesystem);
|
|
2945
|
+
if (filesystem !== USER_FILESYSTEM_ID) {
|
|
2946
|
+
try {
|
|
2947
|
+
const binding = await resolveFilesystemBinding(request, filesystem);
|
|
2948
|
+
if (!binding) return sendNotFoundOrDenied(reply);
|
|
2949
|
+
const result = await binding.operations.read({ filesystem, path: path4 });
|
|
2950
|
+
return { content: result.content, access: binding.access };
|
|
2951
|
+
} catch {
|
|
2952
|
+
return sendNotFoundOrDenied(reply);
|
|
2953
|
+
}
|
|
2954
|
+
}
|
|
2908
2955
|
try {
|
|
2909
2956
|
if (isReadonlySkillFilePath(path4)) {
|
|
2910
2957
|
const { content: content2, stat: stat13 } = await readReadonlySkillFile(path4);
|
|
@@ -2918,11 +2965,11 @@ function fileRoutes(app, opts, done) {
|
|
|
2918
2965
|
const workspace = await resolveWorkspace(request);
|
|
2919
2966
|
if (workspace.readFileWithStat) {
|
|
2920
2967
|
const { content: content2, stat: stat13 } = await workspace.readFileWithStat(path4);
|
|
2921
|
-
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" };
|
|
2922
2969
|
}
|
|
2923
2970
|
const content = await workspace.readFile(path4);
|
|
2924
2971
|
const stat12 = await workspace.stat(path4);
|
|
2925
|
-
return { content, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0 };
|
|
2972
|
+
return { content, mtimeMs: stat12.kind === "file" ? stat12.mtimeMs : void 0, access: "readwrite" };
|
|
2926
2973
|
} catch (err) {
|
|
2927
2974
|
return classifyError(err, reply, "file");
|
|
2928
2975
|
}
|
|
@@ -2936,8 +2983,28 @@ function fileRoutes(app, opts, done) {
|
|
|
2936
2983
|
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "content is required", field: "content" }
|
|
2937
2984
|
});
|
|
2938
2985
|
}
|
|
2986
|
+
const filesystem = requestedFilesystem(body.filesystem);
|
|
2939
2987
|
const expectedMtimeMs = typeof body.expectedMtimeMs === "number" ? body.expectedMtimeMs : null;
|
|
2940
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
|
+
}
|
|
2941
3008
|
try {
|
|
2942
3009
|
const workspace = await resolveWorkspace(request);
|
|
2943
3010
|
if (expectedMtimeMs !== null) {
|
|
@@ -3069,6 +3136,21 @@ function fileRoutes(app, opts, done) {
|
|
|
3069
3136
|
const query = request.query;
|
|
3070
3137
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
3071
3138
|
if (path4 === null) return;
|
|
3139
|
+
const filesystem = requestedFilesystem(query.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
|
+
}
|
|
3072
3154
|
try {
|
|
3073
3155
|
const workspace = await resolveWorkspace(request);
|
|
3074
3156
|
await workspace.unlink(path4);
|
|
@@ -3083,6 +3165,21 @@ function fileRoutes(app, opts, done) {
|
|
|
3083
3165
|
if (from === null) return;
|
|
3084
3166
|
const to = requireStringParam(body?.to, "to", reply);
|
|
3085
3167
|
if (to === null) return;
|
|
3168
|
+
const filesystem = requestedFilesystem(body.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
|
+
}
|
|
3086
3183
|
try {
|
|
3087
3184
|
const workspace = await resolveWorkspace(request);
|
|
3088
3185
|
await workspace.rename(from, to);
|
|
@@ -3096,6 +3193,21 @@ function fileRoutes(app, opts, done) {
|
|
|
3096
3193
|
const path4 = requireStringParam(body?.path, "path", reply);
|
|
3097
3194
|
if (path4 === null) return;
|
|
3098
3195
|
const recursive = body.recursive === true;
|
|
3196
|
+
const filesystem = requestedFilesystem(body.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
|
+
}
|
|
3099
3211
|
try {
|
|
3100
3212
|
const workspace = await resolveWorkspace(request);
|
|
3101
3213
|
await workspace.mkdir(path4, { recursive });
|
|
@@ -3108,6 +3220,17 @@ function fileRoutes(app, opts, done) {
|
|
|
3108
3220
|
const query = request.query;
|
|
3109
3221
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
3110
3222
|
if (path4 === null) return;
|
|
3223
|
+
const filesystem = requestedFilesystem(query.filesystem);
|
|
3224
|
+
if (filesystem !== USER_FILESYSTEM_ID) {
|
|
3225
|
+
try {
|
|
3226
|
+
const binding = await resolveFilesystemBinding(request, filesystem);
|
|
3227
|
+
if (!binding) return sendNotFoundOrDenied(reply);
|
|
3228
|
+
const result = await binding.operations.stat({ filesystem, path: path4 });
|
|
3229
|
+
return result.isDirectory ? { kind: "dir" } : { kind: "file", size: 0 };
|
|
3230
|
+
} catch {
|
|
3231
|
+
return sendNotFoundOrDenied(reply);
|
|
3232
|
+
}
|
|
3233
|
+
}
|
|
3111
3234
|
try {
|
|
3112
3235
|
if (isReadonlySkillFilePath(path4)) {
|
|
3113
3236
|
return await statReadonlySkillFile(path4);
|
|
@@ -8327,17 +8450,135 @@ function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
|
|
|
8327
8450
|
function isTextContent2(content) {
|
|
8328
8451
|
return content.type === "text" && typeof content.text === "string";
|
|
8329
8452
|
}
|
|
8453
|
+
function filesystemBindings(bundle) {
|
|
8454
|
+
return bundle.filesystemBindings ?? [];
|
|
8455
|
+
}
|
|
8456
|
+
function filesystemIds(bundle) {
|
|
8457
|
+
return filesystemBindings(bundle).map((binding) => binding.filesystem);
|
|
8458
|
+
}
|
|
8459
|
+
function withFilesystemParameter(parameters, filesystemIds2) {
|
|
8460
|
+
const schema = parameters && typeof parameters === "object" ? { ...parameters } : { type: "object" };
|
|
8461
|
+
const properties = schema.properties && typeof schema.properties === "object" ? { ...schema.properties } : {};
|
|
8462
|
+
return {
|
|
8463
|
+
...schema,
|
|
8464
|
+
properties: {
|
|
8465
|
+
...properties,
|
|
8466
|
+
filesystem: {
|
|
8467
|
+
type: "string",
|
|
8468
|
+
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.",
|
|
8469
|
+
enum: ["user", ...filesystemIds2]
|
|
8470
|
+
}
|
|
8471
|
+
}
|
|
8472
|
+
};
|
|
8473
|
+
}
|
|
8474
|
+
function requestedFilesystem2(params) {
|
|
8475
|
+
const value = params.filesystem;
|
|
8476
|
+
return typeof value === "string" && value.length > 0 ? value : "user";
|
|
8477
|
+
}
|
|
8478
|
+
function withoutFilesystem(params) {
|
|
8479
|
+
const { filesystem: _filesystem, ...rest } = params;
|
|
8480
|
+
return rest;
|
|
8481
|
+
}
|
|
8482
|
+
function boundFilesystemPath(params) {
|
|
8483
|
+
const value = params.path;
|
|
8484
|
+
return typeof value === "string" && value.length > 0 ? value : "/";
|
|
8485
|
+
}
|
|
8486
|
+
function assertNotFilesystemPathSpoof(path4, filesystemIds2) {
|
|
8487
|
+
const normalized = path4.replace(/\\/g, "/");
|
|
8488
|
+
if (normalized.includes(":/") || filesystemIds2.some((filesystem) => normalized === `/${filesystem}` || normalized.startsWith(`/${filesystem}/`))) {
|
|
8489
|
+
throw new Error("filesystem prefixes are not valid path strings; use the filesystem parameter");
|
|
8490
|
+
}
|
|
8491
|
+
}
|
|
8492
|
+
function filesystemBinding(bundle, filesystem) {
|
|
8493
|
+
return filesystemBindings(bundle).find((binding) => binding.filesystem === filesystem);
|
|
8494
|
+
}
|
|
8495
|
+
function boundOperations(bundle, filesystem) {
|
|
8496
|
+
const binding = filesystemBinding(bundle, filesystem);
|
|
8497
|
+
if (!binding) throw new Error(`No filesystem binding is available for ${filesystem}`);
|
|
8498
|
+
return binding.operations;
|
|
8499
|
+
}
|
|
8500
|
+
function formatBoundRead(content) {
|
|
8501
|
+
return { content: [{ type: "text", text: content }] };
|
|
8502
|
+
}
|
|
8503
|
+
function formatBoundList(entries) {
|
|
8504
|
+
return { content: [{ type: "text", text: entries.join("\n") }] };
|
|
8505
|
+
}
|
|
8506
|
+
function formatBoundFind(paths) {
|
|
8507
|
+
return { content: [{ type: "text", text: paths.join("\n") }] };
|
|
8508
|
+
}
|
|
8509
|
+
function formatBoundGrep(matches) {
|
|
8510
|
+
return { content: [{ type: "text", text: matches.map((match) => `${match.path}:${match.line}:${match.text}`).join("\n") }] };
|
|
8511
|
+
}
|
|
8512
|
+
async function executeBoundFilesystemTool(toolName, filesystem, params, bundle) {
|
|
8513
|
+
const path4 = boundFilesystemPath(params);
|
|
8514
|
+
assertNotFilesystemPathSpoof(path4, filesystemIds(bundle));
|
|
8515
|
+
const operations = boundOperations(bundle, filesystem);
|
|
8516
|
+
if (toolName === "read") {
|
|
8517
|
+
const result = await operations.read({ filesystem, path: path4 });
|
|
8518
|
+
return { ...formatBoundRead(result.content), details: { metadata: result.metadata } };
|
|
8519
|
+
}
|
|
8520
|
+
if (toolName === "ls") {
|
|
8521
|
+
const result = await operations.list({ filesystem, path: path4 });
|
|
8522
|
+
return { ...formatBoundList(result.entries), details: { metadata: result.metadata } };
|
|
8523
|
+
}
|
|
8524
|
+
if (toolName === "find") {
|
|
8525
|
+
const pattern = typeof params.pattern === "string" ? params.pattern : "*";
|
|
8526
|
+
const limit = typeof params.limit === "number" ? params.limit : void 0;
|
|
8527
|
+
const result = await operations.find({ filesystem, path: path4 }, pattern, { limit });
|
|
8528
|
+
return { ...formatBoundFind(result.paths), details: { metadata: result.metadata } };
|
|
8529
|
+
}
|
|
8530
|
+
if (toolName === "grep") {
|
|
8531
|
+
const pattern = typeof params.pattern === "string" ? params.pattern : "";
|
|
8532
|
+
const limit = typeof params.limit === "number" ? params.limit : void 0;
|
|
8533
|
+
const result = await operations.grep({ filesystem, path: path4 }, pattern, { limit });
|
|
8534
|
+
return { ...formatBoundGrep(result.matches), details: { metadata: result.metadata } };
|
|
8535
|
+
}
|
|
8536
|
+
if (toolName === "write" || toolName === "edit") {
|
|
8537
|
+
operations.rejectMutation(toolName, { filesystem, path: path4 });
|
|
8538
|
+
}
|
|
8539
|
+
throw new Error(`Tool ${toolName} does not support filesystem ${filesystem}`);
|
|
8540
|
+
}
|
|
8541
|
+
function withBoundFilesystemPromptGuidance(promptSnippet, filesystemIds2) {
|
|
8542
|
+
if (filesystemIds2.length === 0) return promptSnippet;
|
|
8543
|
+
const guidance = [
|
|
8544
|
+
"Named filesystem bindings: file tools default to the user workspace when filesystem is omitted.",
|
|
8545
|
+
`Use the filesystem parameter explicitly for bound readonly context (${filesystemIds2.join(", ")}), and start browsing at / unless told otherwise.`,
|
|
8546
|
+
"Readonly filesystem bindings reject writes; do not use path prefixes like filesystem:/x to switch filesystem."
|
|
8547
|
+
].join("\n");
|
|
8548
|
+
return [promptSnippet, guidance].filter(Boolean).join("\n");
|
|
8549
|
+
}
|
|
8550
|
+
function withFilesystemRouting(tool, bundle) {
|
|
8551
|
+
const ids = filesystemIds(bundle);
|
|
8552
|
+
return {
|
|
8553
|
+
...tool,
|
|
8554
|
+
promptSnippet: withBoundFilesystemPromptGuidance(tool.promptSnippet, ids),
|
|
8555
|
+
parameters: withFilesystemParameter(tool.parameters, ids),
|
|
8556
|
+
async execute(params, ctx) {
|
|
8557
|
+
const filesystem = requestedFilesystem2(params);
|
|
8558
|
+
if (filesystem !== "user") {
|
|
8559
|
+
const result = await executeBoundFilesystemTool(tool.name, filesystem, params, bundle);
|
|
8560
|
+
const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
|
|
8561
|
+
return {
|
|
8562
|
+
content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
|
|
8563
|
+
isError: false,
|
|
8564
|
+
details: result.details
|
|
8565
|
+
};
|
|
8566
|
+
}
|
|
8567
|
+
return await tool.execute(withoutFilesystem(params), ctx);
|
|
8568
|
+
}
|
|
8569
|
+
};
|
|
8570
|
+
}
|
|
8330
8571
|
function adaptPiTool2(piTool) {
|
|
8331
8572
|
return {
|
|
8332
8573
|
name: piTool.name,
|
|
8333
8574
|
readinessRequirements: ["workspace-fs"],
|
|
8334
8575
|
description: piTool.description,
|
|
8335
8576
|
promptSnippet: piTool.promptSnippet,
|
|
8336
|
-
parameters: piTool.parameters,
|
|
8577
|
+
parameters: piTool.parameters && typeof piTool.parameters === "object" ? { ...piTool.parameters } : { type: "object" },
|
|
8337
8578
|
async execute(params, ctx) {
|
|
8338
8579
|
const result = await piTool.execute(
|
|
8339
8580
|
ctx.toolCallId,
|
|
8340
|
-
params,
|
|
8581
|
+
withoutFilesystem(params),
|
|
8341
8582
|
ctx.abortSignal,
|
|
8342
8583
|
ctx.onUpdate ? (update) => {
|
|
8343
8584
|
const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
|
|
@@ -8361,7 +8602,7 @@ function buildFilesystemAgentTools(bundle) {
|
|
|
8361
8602
|
const cwd = bundle.workspace.root;
|
|
8362
8603
|
const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
|
|
8363
8604
|
if (strategy.kind === "remote-workspace") {
|
|
8364
|
-
return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
|
|
8605
|
+
return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions).map((tool) => withFilesystemRouting(tool, bundle));
|
|
8365
8606
|
}
|
|
8366
8607
|
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8367
8608
|
const ops = boundFs(storageRoot, { runtimeRoot: cwd });
|
|
@@ -8372,7 +8613,7 @@ function buildFilesystemAgentTools(bundle) {
|
|
|
8372
8613
|
adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
|
|
8373
8614
|
adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
|
|
8374
8615
|
adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
|
|
8375
|
-
];
|
|
8616
|
+
].map((tool) => withFilesystemRouting(tool, bundle));
|
|
8376
8617
|
}
|
|
8377
8618
|
|
|
8378
8619
|
// src/server/tools/harness/index.ts
|
|
@@ -9009,6 +9250,14 @@ function joinPath2(base, name) {
|
|
|
9009
9250
|
if (base === ".") return name;
|
|
9010
9251
|
return `${base}/${name}`;
|
|
9011
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
|
+
}
|
|
9012
9261
|
async function listTree(workspace, dir, recursive) {
|
|
9013
9262
|
const entries = [];
|
|
9014
9263
|
const initialEntries = await workspace.readdir(dir);
|
|
@@ -9033,12 +9282,44 @@ async function listTree(workspace, dir, recursive) {
|
|
|
9033
9282
|
}
|
|
9034
9283
|
return entries;
|
|
9035
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
|
+
}
|
|
9036
9313
|
function treeRoutes(app, opts, done) {
|
|
9037
9314
|
async function resolveWorkspace(request) {
|
|
9038
9315
|
if (opts.getWorkspace) return await opts.getWorkspace(request);
|
|
9039
9316
|
if (opts.workspace) return opts.workspace;
|
|
9040
9317
|
throw new Error("workspace route requires workspace or getWorkspace");
|
|
9041
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
|
+
}
|
|
9042
9323
|
app.get(
|
|
9043
9324
|
"/api/v1/tree",
|
|
9044
9325
|
async (request, reply) => {
|
|
@@ -9054,6 +9335,16 @@ function treeRoutes(app, opts, done) {
|
|
|
9054
9335
|
throw err;
|
|
9055
9336
|
}
|
|
9056
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
|
+
}
|
|
9057
9348
|
try {
|
|
9058
9349
|
const workspace = await resolveWorkspace(request);
|
|
9059
9350
|
const entries = await listTree(workspace, dir, recursive);
|
|
@@ -12321,7 +12612,7 @@ async function createAgentApp(opts = {}) {
|
|
|
12321
12612
|
});
|
|
12322
12613
|
await app.register(fileRoutes, { workspace: runtimeBundle.workspace });
|
|
12323
12614
|
await app.register(fsEventsRoutes, { workspace: runtimeBundle.workspace });
|
|
12324
|
-
await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
|
|
12615
|
+
await app.register(treeRoutes, { workspace: runtimeBundle.workspace, filesystemBindings: runtimeBundle.filesystemBindings });
|
|
12325
12616
|
await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
|
|
12326
12617
|
await app.register(gitRoutes, {
|
|
12327
12618
|
getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
|
|
@@ -12579,6 +12870,12 @@ function normalizeSessionNamespace(value) {
|
|
|
12579
12870
|
const trimmed = value.trim();
|
|
12580
12871
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
12581
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
|
+
}
|
|
12582
12879
|
function createHttpError(code, message, details = {}) {
|
|
12583
12880
|
const error = new Error(message);
|
|
12584
12881
|
error.code = code;
|
|
@@ -12637,7 +12934,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12637
12934
|
app.addHook("onClose", async () => {
|
|
12638
12935
|
await modeAdapter.dispose?.();
|
|
12639
12936
|
});
|
|
12640
|
-
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";
|
|
12641
12938
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
12642
12939
|
const externalPluginsEnabled = opts.externalPlugins !== false;
|
|
12643
12940
|
const runtimeBindings = /* @__PURE__ */ new Map();
|
|
@@ -12657,6 +12954,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12657
12954
|
const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
|
|
12658
12955
|
const pi = await resolveScopePi(workspaceId, root, request);
|
|
12659
12956
|
const sessionNamespace = normalizeSessionNamespace(opts.getSessionNamespace ? await opts.getSessionNamespace({ workspaceId, workspaceRoot: root, request }) : opts.sessionNamespace);
|
|
12957
|
+
const extraToolsAuthSubject = opts.getExtraTools ? getRequestAuthSubject(request) : void 0;
|
|
12660
12958
|
return {
|
|
12661
12959
|
root,
|
|
12662
12960
|
templatePath: scopedTemplatePath,
|
|
@@ -12668,7 +12966,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12668
12966
|
root,
|
|
12669
12967
|
scopedTemplatePath ?? null,
|
|
12670
12968
|
pi,
|
|
12671
|
-
sessionNamespace ?? null
|
|
12969
|
+
sessionNamespace ?? null,
|
|
12970
|
+
extraToolsAuthSubject ?? null
|
|
12672
12971
|
])
|
|
12673
12972
|
};
|
|
12674
12973
|
}
|
|
@@ -12852,7 +13151,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12852
13151
|
workspaceId,
|
|
12853
13152
|
workspaceRoot: root,
|
|
12854
13153
|
runtimeMode: resolvedMode,
|
|
12855
|
-
workspaceFsCapability: runtimeBundle.workspace.fsCapability
|
|
13154
|
+
workspaceFsCapability: runtimeBundle.workspace.fsCapability,
|
|
13155
|
+
authSubject: getRequestAuthSubject(request)
|
|
12856
13156
|
}) : [];
|
|
12857
13157
|
const tools = mergeTools({
|
|
12858
13158
|
standardTools,
|
|
@@ -12944,7 +13244,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12944
13244
|
return await ensureRuntimeBindingReady(
|
|
12945
13245
|
workspaceId,
|
|
12946
13246
|
scope,
|
|
12947
|
-
await existing.promise
|
|
13247
|
+
await existing.promise,
|
|
13248
|
+
request
|
|
12948
13249
|
);
|
|
12949
13250
|
}
|
|
12950
13251
|
}
|
|
@@ -12958,17 +13259,18 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12958
13259
|
return await ensureRuntimeBindingReady(
|
|
12959
13260
|
workspaceId,
|
|
12960
13261
|
scope,
|
|
12961
|
-
await created.promise
|
|
13262
|
+
await created.promise,
|
|
13263
|
+
request
|
|
12962
13264
|
);
|
|
12963
13265
|
} catch (error) {
|
|
12964
13266
|
if (runtimeBindings.get(scope.key) === created) runtimeBindings.delete(scope.key);
|
|
12965
13267
|
throw error;
|
|
12966
13268
|
}
|
|
12967
13269
|
}
|
|
12968
|
-
async function recreateRuntimeBinding(workspaceId, scope) {
|
|
13270
|
+
async function recreateRuntimeBinding(workspaceId, scope, request) {
|
|
12969
13271
|
runtimeBindings.delete(scope.key);
|
|
12970
13272
|
modeAdapter.evictCachedRuntime?.({ workspaceId });
|
|
12971
|
-
const created = createRuntimeBindingEntry(workspaceId, scope);
|
|
13273
|
+
const created = createRuntimeBindingEntry(workspaceId, scope, request);
|
|
12972
13274
|
runtimeBindings.set(scope.key, created);
|
|
12973
13275
|
evictRuntimeBindings();
|
|
12974
13276
|
try {
|
|
@@ -12980,7 +13282,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12980
13282
|
throw error;
|
|
12981
13283
|
}
|
|
12982
13284
|
}
|
|
12983
|
-
async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
|
|
13285
|
+
async function ensureRuntimeBindingReady(workspaceId, scope, binding, request) {
|
|
12984
13286
|
const healthCheck = modeAdapter.cachedBindingHealthCheck;
|
|
12985
13287
|
if (!healthCheck) return binding;
|
|
12986
13288
|
const now = Date.now();
|
|
@@ -12997,7 +13299,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12997
13299
|
err: result.error,
|
|
12998
13300
|
workspaceId
|
|
12999
13301
|
}, result.message ?? "[runtime] cached runtime invalid; recreating");
|
|
13000
|
-
return await recreateRuntimeBinding(workspaceId, scope);
|
|
13302
|
+
return await recreateRuntimeBinding(workspaceId, scope, request);
|
|
13001
13303
|
}
|
|
13002
13304
|
const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
|
|
13003
13305
|
const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
|
|
@@ -13090,7 +13392,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
13090
13392
|
getWorkspace: async (request) => (await getBindingForRequest(request)).runtimeBundle.workspace
|
|
13091
13393
|
});
|
|
13092
13394
|
await app.register(treeRoutes, {
|
|
13093
|
-
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
|
|
13094
13397
|
});
|
|
13095
13398
|
await app.register(searchRoutes, {
|
|
13096
13399
|
getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -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-
|
|
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-
|
|
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
|
|
package/dist/shared/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-agent",
|
|
3
|
-
"version": "0.1.
|
|
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.
|
|
77
|
+
"@hachej/boring-ui-kit": "0.1.64"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@antithesishq/bombadil": "0.5.0",
|