@hachej/boring-agent 0.1.54 → 0.1.55
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/{agentPluginEvents-Dgm57gEU.d.ts → agentPluginEvents-BBqEQ_rc.d.ts} +1 -1
- package/dist/front/index.js +40 -16
- package/dist/front/styles.css +6 -0
- package/dist/server/index.d.ts +139 -4
- package/dist/server/index.js +553 -278
- package/dist/shared/index.d.ts +2 -2
- package/package.json +2 -2
package/dist/server/index.js
CHANGED
|
@@ -2337,6 +2337,8 @@ function createRemoteWorkerModeAdapter(opts = {}) {
|
|
|
2337
2337
|
await sandbox.init?.({ workspace, sessionId: ctx.sessionId });
|
|
2338
2338
|
return {
|
|
2339
2339
|
runtimeContext: { runtimeCwd: REMOTE_WORKER_RUNTIME_CWD },
|
|
2340
|
+
bash: { kind: "remote" },
|
|
2341
|
+
filesystem: { kind: "remote-workspace" },
|
|
2340
2342
|
workspace,
|
|
2341
2343
|
sandbox,
|
|
2342
2344
|
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
@@ -2957,8 +2959,8 @@ function fileRoutes(app, opts, done) {
|
|
|
2957
2959
|
const dir = isImage ? normalizeUploadDir(body.directory) ?? normalizeUploadDir(settings.markdown?.imageUploadDir) ?? DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR : DEFAULT_FILE_UPLOAD_DIR;
|
|
2958
2960
|
const ext = extForUpload(filename, contentType);
|
|
2959
2961
|
const base = basenameForUpload(filename);
|
|
2960
|
-
const
|
|
2961
|
-
const path4 = `${dir}/${base}-${
|
|
2962
|
+
const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
2963
|
+
const path4 = `${dir}/${base}-${unique2}.${ext}`;
|
|
2962
2964
|
const estimatedBytes = Math.ceil(contentBase64.length * 0.75);
|
|
2963
2965
|
if (estimatedBytes === 0 || estimatedBytes > MAX_UPLOAD_BYTES) {
|
|
2964
2966
|
return reply.code(400).send({
|
|
@@ -4872,6 +4874,8 @@ var directModeAdapter = {
|
|
|
4872
4874
|
return {
|
|
4873
4875
|
runtimeContext,
|
|
4874
4876
|
storageRoot: ctx.workspaceRoot,
|
|
4877
|
+
bash: { kind: "host", preserveHostHome: true },
|
|
4878
|
+
filesystem: { kind: "host" },
|
|
4875
4879
|
workspace,
|
|
4876
4880
|
sandbox,
|
|
4877
4881
|
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
@@ -4901,6 +4905,8 @@ var localModeAdapter = {
|
|
|
4901
4905
|
return {
|
|
4902
4906
|
runtimeContext,
|
|
4903
4907
|
storageRoot: ctx.workspaceRoot,
|
|
4908
|
+
bash: { kind: "local-sandbox", sandboxRoot: "/workspace" },
|
|
4909
|
+
filesystem: { kind: "host" },
|
|
4904
4910
|
workspace,
|
|
4905
4911
|
sandbox,
|
|
4906
4912
|
fileSearch: createServerFileSearch(workspace, sandbox)
|
|
@@ -5168,6 +5174,38 @@ var ORPHAN_GUARD_MAX_IDLE_MS = 24 * 60 * 60 * 1e3;
|
|
|
5168
5174
|
var VERCEL_SANDBOX_TIMEOUT_MS_ENV = "BORING_AGENT_VERCEL_SANDBOX_TIMEOUT_MS";
|
|
5169
5175
|
var VERCEL_SANDBOX_RUNTIME_ENV = "BORING_AGENT_VERCEL_SANDBOX_RUNTIME";
|
|
5170
5176
|
var DEFAULT_VERCEL_SANDBOX_RUNTIME = "node24";
|
|
5177
|
+
var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
|
|
5178
|
+
var VERCEL_SAFE_DEFAULT_PATH = "/vercel/runtimes/node24/bin:/vercel/runtimes/node22/bin:/usr/local/bin:/usr/bin:/bin";
|
|
5179
|
+
function vercelRemoteWorkspacePathOptions() {
|
|
5180
|
+
return {
|
|
5181
|
+
rootAliases: [VERCEL_SANDBOX_REMOTE_ROOT, "/vercel/sandbox"],
|
|
5182
|
+
toRemotePath(value) {
|
|
5183
|
+
if (value === VERCEL_SANDBOX_WORKSPACE_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
|
|
5184
|
+
if (value.startsWith(`${VERCEL_SANDBOX_WORKSPACE_ROOT}/`)) {
|
|
5185
|
+
return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_WORKSPACE_ROOT.length)}`;
|
|
5186
|
+
}
|
|
5187
|
+
if (value === "/vercel/sandbox") return VERCEL_SANDBOX_REMOTE_ROOT;
|
|
5188
|
+
if (value.startsWith("/vercel/sandbox/")) {
|
|
5189
|
+
return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice("/vercel/sandbox".length)}`;
|
|
5190
|
+
}
|
|
5191
|
+
return value;
|
|
5192
|
+
},
|
|
5193
|
+
toRuntimePath(value) {
|
|
5194
|
+
if (value === VERCEL_SANDBOX_REMOTE_ROOT) return VERCEL_SANDBOX_WORKSPACE_ROOT;
|
|
5195
|
+
if (value.startsWith(`${VERCEL_SANDBOX_REMOTE_ROOT}/`)) {
|
|
5196
|
+
return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice(VERCEL_SANDBOX_REMOTE_ROOT.length)}`;
|
|
5197
|
+
}
|
|
5198
|
+
if (value === "/vercel/sandbox") return VERCEL_SANDBOX_WORKSPACE_ROOT;
|
|
5199
|
+
if (value.startsWith("/vercel/sandbox/")) {
|
|
5200
|
+
return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice("/vercel/sandbox".length)}`;
|
|
5201
|
+
}
|
|
5202
|
+
return value;
|
|
5203
|
+
},
|
|
5204
|
+
sanitizeErrorText(value) {
|
|
5205
|
+
return value.replaceAll("/vercel/sandbox", VERCEL_SANDBOX_WORKSPACE_ROOT);
|
|
5206
|
+
}
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
5171
5209
|
function sandboxTelemetryProperties(ctx, extra = {}) {
|
|
5172
5210
|
return {
|
|
5173
5211
|
runtimeMode: "vercel-sandbox",
|
|
@@ -5471,6 +5509,22 @@ var DEFAULT_MODE_LOGGER = {
|
|
|
5471
5509
|
`);
|
|
5472
5510
|
}
|
|
5473
5511
|
};
|
|
5512
|
+
function extractHttpStatus2(error) {
|
|
5513
|
+
const statusCode = error?.statusCode;
|
|
5514
|
+
if (typeof statusCode === "number") return statusCode;
|
|
5515
|
+
const status = error?.status;
|
|
5516
|
+
if (typeof status === "number") return status;
|
|
5517
|
+
const responseStatus = error?.response?.status;
|
|
5518
|
+
return typeof responseStatus === "number" ? responseStatus : null;
|
|
5519
|
+
}
|
|
5520
|
+
function isExpiredSandboxRuntimeError(error) {
|
|
5521
|
+
const code = error?.code;
|
|
5522
|
+
if (code === ErrorCode.enum.SANDBOX_EXPIRED) return true;
|
|
5523
|
+
const status = extractHttpStatus2(error);
|
|
5524
|
+
if (status === 404 || status === 410) return true;
|
|
5525
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5526
|
+
return /status code (404|410) is not ok/i.test(message);
|
|
5527
|
+
}
|
|
5474
5528
|
function requireEnvVar(name, getEnvVar) {
|
|
5475
5529
|
const value = getEnvVar(name)?.trim();
|
|
5476
5530
|
if (!value) {
|
|
@@ -5510,6 +5564,33 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
5510
5564
|
return {
|
|
5511
5565
|
id: "vercel-sandbox",
|
|
5512
5566
|
workspaceFsCapability: "best-effort",
|
|
5567
|
+
readiness: {
|
|
5568
|
+
initialSandboxReady: false,
|
|
5569
|
+
initialWorkspaceReadiness: { state: "preparing" },
|
|
5570
|
+
onTrackerCreated: (tracker) => {
|
|
5571
|
+
queueMicrotask(() => tracker.markSandboxReady());
|
|
5572
|
+
}
|
|
5573
|
+
},
|
|
5574
|
+
cachedBindingHealthCheck: {
|
|
5575
|
+
intervalMs: VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS,
|
|
5576
|
+
async check({ runtimeBundle }) {
|
|
5577
|
+
try {
|
|
5578
|
+
await runtimeBundle.workspace.stat(".");
|
|
5579
|
+
return { state: "ok" };
|
|
5580
|
+
} catch (error) {
|
|
5581
|
+
if (!isExpiredSandboxRuntimeError(error)) throw error;
|
|
5582
|
+
return {
|
|
5583
|
+
state: "recreate",
|
|
5584
|
+
message: "[sandbox] cached runtime expired; recreating from persisted handle",
|
|
5585
|
+
error
|
|
5586
|
+
};
|
|
5587
|
+
}
|
|
5588
|
+
}
|
|
5589
|
+
},
|
|
5590
|
+
getRuntimeLayoutRoot: () => VERCEL_SANDBOX_WORKSPACE_ROOT,
|
|
5591
|
+
evictCachedRuntime: ({ workspaceId }) => {
|
|
5592
|
+
evictSandboxHandleCacheForWorkspace(workspaceId);
|
|
5593
|
+
},
|
|
5513
5594
|
async dispose() {
|
|
5514
5595
|
await snapshotScheduler?.shutdown();
|
|
5515
5596
|
},
|
|
@@ -5692,7 +5773,9 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
5692
5773
|
workspace,
|
|
5693
5774
|
sandbox,
|
|
5694
5775
|
fileSearch: createServerFileSearch(workspace, sandbox),
|
|
5695
|
-
runtimeContext: workspace.runtimeContext
|
|
5776
|
+
runtimeContext: workspace.runtimeContext,
|
|
5777
|
+
bash: { kind: "remote", defaultPath: VERCEL_SAFE_DEFAULT_PATH },
|
|
5778
|
+
filesystem: { kind: "remote-workspace", pathOptions: vercelRemoteWorkspacePathOptions() }
|
|
5696
5779
|
};
|
|
5697
5780
|
} catch (error) {
|
|
5698
5781
|
const code = error?.code;
|
|
@@ -5736,7 +5819,13 @@ function autoDetectMode() {
|
|
|
5736
5819
|
}
|
|
5737
5820
|
return "direct";
|
|
5738
5821
|
}
|
|
5739
|
-
function resolveMode(mode = autoDetectMode()) {
|
|
5822
|
+
function resolveMode(mode = autoDetectMode(), opts = {}) {
|
|
5823
|
+
if (mode === "vercel-sandbox" && opts.sandboxHandleStore) {
|
|
5824
|
+
return createVercelSandboxModeAdapter({
|
|
5825
|
+
store: opts.sandboxHandleStore,
|
|
5826
|
+
orphanGuardMaxIdleMs: null
|
|
5827
|
+
});
|
|
5828
|
+
}
|
|
5740
5829
|
if (isBuiltinRuntimeModeId(mode)) return MODE_ADAPTERS[mode];
|
|
5741
5830
|
throw new Error(`Runtime mode "${mode}" has no built-in adapter. Pass runtimeModeAdapter to use a custom sandbox mode.`);
|
|
5742
5831
|
}
|
|
@@ -5745,15 +5834,48 @@ function resolveMode(mode = autoDetectMode()) {
|
|
|
5745
5834
|
import Fastify from "fastify";
|
|
5746
5835
|
|
|
5747
5836
|
// src/server/runtime/mode.ts
|
|
5837
|
+
function getOptionalRuntimeBundleStorageRoot(bundle) {
|
|
5838
|
+
return bundle.storageRoot ?? getNodeWorkspaceHostRoot(bundle.workspace) ?? void 0;
|
|
5839
|
+
}
|
|
5748
5840
|
function getRuntimeBundleStorageRoot(bundle) {
|
|
5749
|
-
const hostRoot =
|
|
5841
|
+
const hostRoot = getOptionalRuntimeBundleStorageRoot(bundle);
|
|
5750
5842
|
if (hostRoot) return hostRoot;
|
|
5751
|
-
if (bundle.sandbox.provider === "vercel-sandbox") return bundle.workspace.root;
|
|
5752
5843
|
throw new Error(
|
|
5753
5844
|
`RuntimeBundle.storageRoot is required for host-filesystem tools. Mode adapters must set storageRoot to the host workspace path. Got workspace.root=${bundle.workspace.root}, sandbox.provider=${bundle.sandbox.provider}`
|
|
5754
5845
|
);
|
|
5755
5846
|
}
|
|
5756
5847
|
|
|
5848
|
+
// src/server/runtimeEnvContributions.ts
|
|
5849
|
+
function withRuntimeEnvContributions(runtimeBundle, baseContext, contributions, telemetry) {
|
|
5850
|
+
const getRuntimeEnv = async () => {
|
|
5851
|
+
const contributedEnv = {};
|
|
5852
|
+
for (const contribution of contributions) {
|
|
5853
|
+
Object.assign(contributedEnv, await contribution.getEnv(baseContext));
|
|
5854
|
+
}
|
|
5855
|
+
if (telemetry) {
|
|
5856
|
+
safeCapture(telemetry, {
|
|
5857
|
+
name: "agent.runtime.env_contributed",
|
|
5858
|
+
properties: {
|
|
5859
|
+
runtimeMode: baseContext.runtimeMode,
|
|
5860
|
+
contributionIds: contributions.map((contribution) => contribution.id)
|
|
5861
|
+
}
|
|
5862
|
+
});
|
|
5863
|
+
}
|
|
5864
|
+
return contributedEnv;
|
|
5865
|
+
};
|
|
5866
|
+
const sandbox = {
|
|
5867
|
+
...runtimeBundle.sandbox,
|
|
5868
|
+
exec: async (cmd, execOpts = {}) => {
|
|
5869
|
+
const contributedEnv = await getRuntimeEnv();
|
|
5870
|
+
return runtimeBundle.sandbox.exec(cmd, {
|
|
5871
|
+
...execOpts,
|
|
5872
|
+
env: { ...contributedEnv, ...execOpts.env ?? {} }
|
|
5873
|
+
});
|
|
5874
|
+
}
|
|
5875
|
+
};
|
|
5876
|
+
return { ...runtimeBundle, sandbox, getRuntimeEnv };
|
|
5877
|
+
}
|
|
5878
|
+
|
|
5757
5879
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
5758
5880
|
import { existsSync, readFileSync as readFileSync2 } from "fs";
|
|
5759
5881
|
import { extname as extname3, join as join11 } from "path";
|
|
@@ -6273,10 +6395,34 @@ var PiSessionStore = class {
|
|
|
6273
6395
|
}
|
|
6274
6396
|
}
|
|
6275
6397
|
async loadPiSessionFile(_ctx, sessionId) {
|
|
6398
|
+
if (!SAFE_ID.test(sessionId)) return null;
|
|
6276
6399
|
try {
|
|
6277
|
-
const
|
|
6278
|
-
|
|
6279
|
-
|
|
6400
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
6401
|
+
let filepath = direct;
|
|
6402
|
+
let content;
|
|
6403
|
+
try {
|
|
6404
|
+
content = await readFile9(direct, "utf-8");
|
|
6405
|
+
} catch {
|
|
6406
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
6407
|
+
const match = files.find(
|
|
6408
|
+
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
6409
|
+
);
|
|
6410
|
+
if (!match) return null;
|
|
6411
|
+
filepath = join10(this.sessionDir, match);
|
|
6412
|
+
content = await readFile9(filepath, "utf-8");
|
|
6413
|
+
}
|
|
6414
|
+
const entries = safeParseEntries(content);
|
|
6415
|
+
const linkedPiFile = extractPiSessionFilePath(entries);
|
|
6416
|
+
if (linkedPiFile) return linkedPiFile;
|
|
6417
|
+
if (!isTimestampNamedPiSessionFile(filepath, sessionId)) return null;
|
|
6418
|
+
const existingWrapper = await this.findWrapperReferencingNativeSession(filepath);
|
|
6419
|
+
if (existingWrapper) {
|
|
6420
|
+
const wrapperSessionId = await this.readSessionFileId(existingWrapper);
|
|
6421
|
+
if (wrapperSessionId !== sessionId) return null;
|
|
6422
|
+
const wrapperEntries = parseJsonlPrefixEntries(await readJsonlPrefix(existingWrapper));
|
|
6423
|
+
return extractPiSessionFilePath(wrapperEntries);
|
|
6424
|
+
}
|
|
6425
|
+
return await this.ensureWrapperForNativeSession(sessionId, filepath);
|
|
6280
6426
|
} catch {
|
|
6281
6427
|
return null;
|
|
6282
6428
|
}
|
|
@@ -7182,8 +7328,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
7182
7328
|
const { session: piSession } = await createAgentSession({
|
|
7183
7329
|
cwd: runtimeCwd,
|
|
7184
7330
|
// Suppress Pi's built-in filesystem/shell tools while keeping Boring's
|
|
7185
|
-
// adapted tool catalog active.
|
|
7186
|
-
//
|
|
7331
|
+
// adapted tool catalog active. Do NOT pass an explicit empty tool-name
|
|
7332
|
+
// allowlist: in the current Pi SDK that disables custom tools too.
|
|
7187
7333
|
noTools: "builtin",
|
|
7188
7334
|
customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
|
|
7189
7335
|
model,
|
|
@@ -7450,12 +7596,12 @@ async function loadPlugins(options) {
|
|
|
7450
7596
|
|
|
7451
7597
|
// src/server/tools/filesystem/index.ts
|
|
7452
7598
|
import {
|
|
7453
|
-
createEditToolDefinition,
|
|
7454
|
-
createFindToolDefinition,
|
|
7599
|
+
createEditToolDefinition as createEditToolDefinition2,
|
|
7600
|
+
createFindToolDefinition as createFindToolDefinition2,
|
|
7455
7601
|
createGrepToolDefinition as createGrepToolDefinition2,
|
|
7456
|
-
createLsToolDefinition,
|
|
7457
|
-
createReadToolDefinition,
|
|
7458
|
-
createWriteToolDefinition
|
|
7602
|
+
createLsToolDefinition as createLsToolDefinition2,
|
|
7603
|
+
createReadToolDefinition as createReadToolDefinition2,
|
|
7604
|
+
createWriteToolDefinition as createWriteToolDefinition2
|
|
7459
7605
|
} from "@mariozechner/pi-coding-agent";
|
|
7460
7606
|
|
|
7461
7607
|
// src/server/tools/operations/bound.ts
|
|
@@ -7596,7 +7742,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7596
7742
|
}
|
|
7597
7743
|
return absolutePath;
|
|
7598
7744
|
};
|
|
7599
|
-
const
|
|
7745
|
+
const toRuntimePath = (absolutePath) => {
|
|
7600
7746
|
if (!shouldMapRuntimeRoot || !runtimeRoot) return absolutePath;
|
|
7601
7747
|
const rel = relative10(workspaceRoot, absolutePath);
|
|
7602
7748
|
if (rel === "") return runtimeRoot;
|
|
@@ -7662,7 +7808,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7662
7808
|
await assertWithinWorkspace(workspaceRoot, storageCwd);
|
|
7663
7809
|
const matches = [];
|
|
7664
7810
|
await walkMatches(storageCwd, storageCwd, pattern, options.ignore, options.limit, matches);
|
|
7665
|
-
return matches.map(
|
|
7811
|
+
return matches.map(toRuntimePath);
|
|
7666
7812
|
}
|
|
7667
7813
|
};
|
|
7668
7814
|
const grep = {
|
|
@@ -7702,20 +7848,28 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7702
7848
|
return { read, write, edit, find, grep, ls };
|
|
7703
7849
|
}
|
|
7704
7850
|
|
|
7705
|
-
// src/server/tools/
|
|
7851
|
+
// src/server/tools/filesystem/remoteWorkspaceTools.ts
|
|
7852
|
+
import {
|
|
7853
|
+
createEditToolDefinition,
|
|
7854
|
+
createFindToolDefinition,
|
|
7855
|
+
createLsToolDefinition,
|
|
7856
|
+
createReadToolDefinition,
|
|
7857
|
+
createWriteToolDefinition
|
|
7858
|
+
} from "@mariozechner/pi-coding-agent";
|
|
7859
|
+
|
|
7860
|
+
// src/server/tools/operations/remoteWorkspace.ts
|
|
7706
7861
|
import { isAbsolute as isAbsolute8, relative as relative11 } from "path";
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
return Array.from(new Set(aliases));
|
|
7862
|
+
function unique(values) {
|
|
7863
|
+
return Array.from(new Set(values));
|
|
7864
|
+
}
|
|
7865
|
+
function rootsFor(workspace, opts = {}) {
|
|
7866
|
+
return unique([workspace.root, ...opts.rootAliases ?? []]);
|
|
7713
7867
|
}
|
|
7714
7868
|
function isOutsideWorkspaceRel(rel) {
|
|
7715
7869
|
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute8(rel);
|
|
7716
7870
|
}
|
|
7717
|
-
function toRelPath(workspace, absolutePath) {
|
|
7718
|
-
for (const root of
|
|
7871
|
+
function toRelPath(workspace, absolutePath, opts = {}) {
|
|
7872
|
+
for (const root of rootsFor(workspace, opts)) {
|
|
7719
7873
|
const rel = relative11(root, absolutePath);
|
|
7720
7874
|
if (!isOutsideWorkspaceRel(rel)) return rel;
|
|
7721
7875
|
}
|
|
@@ -7732,113 +7886,92 @@ function toRelPath(workspace, absolutePath) {
|
|
|
7732
7886
|
`path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${workspace.root}`
|
|
7733
7887
|
);
|
|
7734
7888
|
}
|
|
7735
|
-
function
|
|
7736
|
-
return {
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
}).then((result) => ({ exitCode: result.exitCode }));
|
|
7748
|
-
}
|
|
7749
|
-
};
|
|
7889
|
+
function shellEscape(s) {
|
|
7890
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
7891
|
+
}
|
|
7892
|
+
function findPredicate(pattern) {
|
|
7893
|
+
const isPathShaped = pattern.includes("/") || pattern.includes("**");
|
|
7894
|
+
if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
|
|
7895
|
+
let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
|
|
7896
|
+
if (!translated.startsWith("*")) translated = `*${translated}`;
|
|
7897
|
+
return `-path ${shellEscape(translated)}`;
|
|
7898
|
+
}
|
|
7899
|
+
function findIgnoreArgs(cwd, ignore) {
|
|
7900
|
+
return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
|
|
7750
7901
|
}
|
|
7751
|
-
function
|
|
7902
|
+
function fallbackFindCommand(pattern, cwd, options) {
|
|
7903
|
+
return [
|
|
7904
|
+
"find",
|
|
7905
|
+
shellEscape(cwd),
|
|
7906
|
+
"-maxdepth 20",
|
|
7907
|
+
"-type f",
|
|
7908
|
+
findIgnoreArgs(cwd, options.ignore),
|
|
7909
|
+
findPredicate(pattern),
|
|
7910
|
+
`| head -n ${Math.max(1, Math.trunc(options.limit))}`
|
|
7911
|
+
].filter(Boolean).join(" ");
|
|
7912
|
+
}
|
|
7913
|
+
function isFdMissing(result) {
|
|
7914
|
+
if (result.exitCode !== 127) return false;
|
|
7915
|
+
const stderr = Buffer.from(result.stderr).toString("utf-8");
|
|
7916
|
+
return /\bfd: (?:not found|command not found)\b/i.test(stderr);
|
|
7917
|
+
}
|
|
7918
|
+
function remotePath(value, opts) {
|
|
7919
|
+
return opts.toRemotePath?.(value) ?? value;
|
|
7920
|
+
}
|
|
7921
|
+
function runtimePath(value, opts) {
|
|
7922
|
+
return opts.toRuntimePath?.(value) ?? value;
|
|
7923
|
+
}
|
|
7924
|
+
function sanitizeErrorText(value, opts) {
|
|
7925
|
+
return opts.sanitizeErrorText?.(value) ?? value;
|
|
7926
|
+
}
|
|
7927
|
+
function remoteWorkspaceReadOps(workspace, opts = {}) {
|
|
7752
7928
|
return {
|
|
7753
7929
|
async readFile(absolutePath) {
|
|
7754
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7930
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7755
7931
|
const content = await workspace.readFile(rel);
|
|
7756
7932
|
return Buffer.from(content, "utf-8");
|
|
7757
7933
|
},
|
|
7758
7934
|
async access(absolutePath) {
|
|
7759
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7935
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7760
7936
|
await workspace.stat(rel);
|
|
7761
7937
|
}
|
|
7762
7938
|
};
|
|
7763
7939
|
}
|
|
7764
|
-
function
|
|
7940
|
+
function remoteWorkspaceWriteOps(workspace, opts = {}) {
|
|
7765
7941
|
return {
|
|
7766
7942
|
async writeFile(absolutePath, content) {
|
|
7767
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7943
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7768
7944
|
await workspace.writeFile(rel, content);
|
|
7769
7945
|
},
|
|
7770
7946
|
async mkdir(dir) {
|
|
7771
|
-
const rel = toRelPath(workspace, dir);
|
|
7947
|
+
const rel = toRelPath(workspace, dir, opts);
|
|
7772
7948
|
await workspace.mkdir(rel, { recursive: true });
|
|
7773
7949
|
}
|
|
7774
7950
|
};
|
|
7775
7951
|
}
|
|
7776
|
-
function
|
|
7952
|
+
function remoteWorkspaceEditOps(workspace, opts = {}) {
|
|
7777
7953
|
return {
|
|
7778
7954
|
async readFile(absolutePath) {
|
|
7779
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7955
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7780
7956
|
const content = await workspace.readFile(rel);
|
|
7781
7957
|
return Buffer.from(content, "utf-8");
|
|
7782
7958
|
},
|
|
7783
7959
|
async writeFile(absolutePath, content) {
|
|
7784
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7960
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7785
7961
|
await workspace.writeFile(rel, content);
|
|
7786
7962
|
},
|
|
7787
7963
|
async access(absolutePath) {
|
|
7788
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7964
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7789
7965
|
await workspace.stat(rel);
|
|
7790
7966
|
}
|
|
7791
7967
|
};
|
|
7792
7968
|
}
|
|
7793
|
-
function
|
|
7794
|
-
if (value === VERCEL_SANDBOX_LEGACY_ROOT) return VERCEL_SANDBOX_REMOTE_ROOT;
|
|
7795
|
-
if (value.startsWith(`${VERCEL_SANDBOX_LEGACY_ROOT}/`)) {
|
|
7796
|
-
return `${VERCEL_SANDBOX_REMOTE_ROOT}${value.slice(VERCEL_SANDBOX_LEGACY_ROOT.length)}`;
|
|
7797
|
-
}
|
|
7798
|
-
return value;
|
|
7799
|
-
}
|
|
7800
|
-
function toRuntimePath(value) {
|
|
7801
|
-
if (value === VERCEL_SANDBOX_LEGACY_ROOT) return VERCEL_SANDBOX_WORKSPACE_ROOT;
|
|
7802
|
-
if (value.startsWith(`${VERCEL_SANDBOX_LEGACY_ROOT}/`)) {
|
|
7803
|
-
return `${VERCEL_SANDBOX_WORKSPACE_ROOT}${value.slice(VERCEL_SANDBOX_LEGACY_ROOT.length)}`;
|
|
7804
|
-
}
|
|
7805
|
-
return value;
|
|
7806
|
-
}
|
|
7807
|
-
function sanitizeRuntimeText(value) {
|
|
7808
|
-
return value.replaceAll(VERCEL_SANDBOX_LEGACY_ROOT, VERCEL_SANDBOX_WORKSPACE_ROOT);
|
|
7809
|
-
}
|
|
7810
|
-
function findPredicate(pattern) {
|
|
7811
|
-
const isPathShaped = pattern.includes("/") || pattern.includes("**");
|
|
7812
|
-
if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
|
|
7813
|
-
let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
|
|
7814
|
-
if (!translated.startsWith("*")) translated = `*${translated}`;
|
|
7815
|
-
return `-path ${shellEscape(translated)}`;
|
|
7816
|
-
}
|
|
7817
|
-
function findIgnoreArgs(cwd, ignore) {
|
|
7818
|
-
return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
|
|
7819
|
-
}
|
|
7820
|
-
function fallbackFindCommand(pattern, cwd, options) {
|
|
7821
|
-
return [
|
|
7822
|
-
"find",
|
|
7823
|
-
shellEscape(cwd),
|
|
7824
|
-
"-maxdepth 20",
|
|
7825
|
-
"-type f",
|
|
7826
|
-
findIgnoreArgs(cwd, options.ignore),
|
|
7827
|
-
findPredicate(pattern),
|
|
7828
|
-
`| head -n ${Math.max(1, Math.trunc(options.limit))}`
|
|
7829
|
-
].filter(Boolean).join(" ");
|
|
7830
|
-
}
|
|
7831
|
-
function isFdMissing(result) {
|
|
7832
|
-
if (result.exitCode !== 127) return false;
|
|
7833
|
-
const stderr = Buffer.from(result.stderr).toString("utf-8");
|
|
7834
|
-
return /\bfd: (?:not found|command not found)\b/i.test(stderr);
|
|
7835
|
-
}
|
|
7836
|
-
function vercelFindOps(sandbox, workspace) {
|
|
7969
|
+
function remoteWorkspaceFindOps(sandbox, workspace, opts = {}) {
|
|
7837
7970
|
return {
|
|
7838
7971
|
async exists(absolutePath) {
|
|
7839
7972
|
if (workspace) {
|
|
7840
7973
|
try {
|
|
7841
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7974
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7842
7975
|
await workspace.stat(rel);
|
|
7843
7976
|
return true;
|
|
7844
7977
|
} catch {
|
|
@@ -7851,11 +7984,9 @@ function vercelFindOps(sandbox, workspace) {
|
|
|
7851
7984
|
return result.exitCode === 0;
|
|
7852
7985
|
},
|
|
7853
7986
|
async glob(pattern, cwd, options) {
|
|
7854
|
-
const remoteCwd =
|
|
7987
|
+
const remoteCwd = remotePath(cwd, opts);
|
|
7855
7988
|
const args = ["fd", "--glob", "--no-require-git", "--max-results", String(options.limit)];
|
|
7856
|
-
for (const ig of options.ignore)
|
|
7857
|
-
args.push("--exclude", ig);
|
|
7858
|
-
}
|
|
7989
|
+
for (const ig of options.ignore) args.push("--exclude", ig);
|
|
7859
7990
|
args.push(pattern, remoteCwd);
|
|
7860
7991
|
let result = await sandbox.exec(args.map(shellEscape).join(" "), {
|
|
7861
7992
|
timeoutMs: 3e4,
|
|
@@ -7868,19 +7999,19 @@ function vercelFindOps(sandbox, workspace) {
|
|
|
7868
7999
|
});
|
|
7869
8000
|
}
|
|
7870
8001
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
7871
|
-
const stderr =
|
|
8002
|
+
const stderr = sanitizeErrorText(Buffer.from(result.stderr).toString("utf-8").trim(), opts);
|
|
7872
8003
|
throw new Error(`file search failed (exit ${result.exitCode}): ${stderr}`);
|
|
7873
8004
|
}
|
|
7874
8005
|
const stdout = Buffer.from(result.stdout).toString("utf-8");
|
|
7875
|
-
return stdout.split("\n").filter(Boolean).map(
|
|
8006
|
+
return stdout.split("\n").filter(Boolean).map((value) => runtimePath(value, opts));
|
|
7876
8007
|
}
|
|
7877
8008
|
};
|
|
7878
8009
|
}
|
|
7879
|
-
function
|
|
8010
|
+
function remoteWorkspaceLsOps(workspace, opts = {}) {
|
|
7880
8011
|
return {
|
|
7881
8012
|
async exists(absolutePath) {
|
|
7882
8013
|
try {
|
|
7883
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
8014
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7884
8015
|
await workspace.stat(rel);
|
|
7885
8016
|
return true;
|
|
7886
8017
|
} catch {
|
|
@@ -7888,22 +8019,18 @@ function vercelLsOps(workspace) {
|
|
|
7888
8019
|
}
|
|
7889
8020
|
},
|
|
7890
8021
|
async stat(absolutePath) {
|
|
7891
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7892
|
-
const
|
|
7893
|
-
return { isDirectory: () =>
|
|
8022
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
8023
|
+
const stat11 = await workspace.stat(rel);
|
|
8024
|
+
return { isDirectory: () => stat11.kind === "dir" };
|
|
7894
8025
|
},
|
|
7895
8026
|
async readdir(absolutePath) {
|
|
7896
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7897
|
-
|
|
7898
|
-
return entries.map((e) => e.name);
|
|
8027
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
8028
|
+
return (await workspace.readdir(rel)).map((entry) => entry.name);
|
|
7899
8029
|
}
|
|
7900
8030
|
};
|
|
7901
8031
|
}
|
|
7902
|
-
function shellEscape(arg) {
|
|
7903
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
7904
|
-
}
|
|
7905
8032
|
|
|
7906
|
-
// src/server/tools/
|
|
8033
|
+
// src/server/tools/remoteWorkspaceGrepTool.ts
|
|
7907
8034
|
import {
|
|
7908
8035
|
createGrepToolDefinition,
|
|
7909
8036
|
formatSize,
|
|
@@ -7927,7 +8054,7 @@ function decode(bytes) {
|
|
|
7927
8054
|
return decoder.decode(bytes);
|
|
7928
8055
|
}
|
|
7929
8056
|
|
|
7930
|
-
// src/server/tools/
|
|
8057
|
+
// src/server/tools/remoteWorkspaceGrepTool.ts
|
|
7931
8058
|
var PI_GREP_TOOL = createGrepToolDefinition("/");
|
|
7932
8059
|
var DEFAULT_LIMIT2 = 100;
|
|
7933
8060
|
var GREP_MAX_LINE_LENGTH = 500;
|
|
@@ -7943,7 +8070,7 @@ function numberParam(value, fallback, min = 1) {
|
|
|
7943
8070
|
function optionalStringParam(value) {
|
|
7944
8071
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7945
8072
|
}
|
|
7946
|
-
function buildRgCommand(params) {
|
|
8073
|
+
function buildRgCommand(params, searchPath) {
|
|
7947
8074
|
const args = ["--json", "--line-number", "--color=never", "--hidden"];
|
|
7948
8075
|
if (params.ignoreCase === true) args.push("--ignore-case");
|
|
7949
8076
|
if (params.literal === true) args.push("--fixed-strings");
|
|
@@ -7952,11 +8079,10 @@ function buildRgCommand(params) {
|
|
|
7952
8079
|
const context = numberParam(params.context, 0, 0);
|
|
7953
8080
|
if (context > 0) args.push("--context", String(context));
|
|
7954
8081
|
const pattern = params.pattern;
|
|
7955
|
-
const searchPath = optionalStringParam(params.path) ?? ".";
|
|
7956
8082
|
args.push("--", quoteShell(pattern), quoteShell(searchPath));
|
|
7957
8083
|
return `rg ${args.join(" ")}`;
|
|
7958
8084
|
}
|
|
7959
|
-
function parseRgJson(stdout, limit) {
|
|
8085
|
+
function parseRgJson(stdout, limit, pathOptions) {
|
|
7960
8086
|
const outputLines = [];
|
|
7961
8087
|
let matchCount = 0;
|
|
7962
8088
|
let matchLimitReached = false;
|
|
@@ -7989,7 +8115,7 @@ function parseRgJson(stdout, limit) {
|
|
|
7989
8115
|
const { text, wasTruncated } = truncateLine(sanitized);
|
|
7990
8116
|
if (wasTruncated) linesTruncated = true;
|
|
7991
8117
|
const separator = isMatch ? ":" : "-";
|
|
7992
|
-
outputLines.push(`${filePath}${separator}${lineNumber}${separator} ${text}`);
|
|
8118
|
+
outputLines.push(`${pathOptions?.toRuntimePath?.(filePath) ?? filePath}${separator}${lineNumber}${separator} ${text}`);
|
|
7993
8119
|
}
|
|
7994
8120
|
return { outputLines, matchCount, matchLimitReached, linesTruncated };
|
|
7995
8121
|
}
|
|
@@ -8010,6 +8136,29 @@ function syntheticExecTruncation(output) {
|
|
|
8010
8136
|
maxBytes: GREP_MAX_OUTPUT_BYTES
|
|
8011
8137
|
};
|
|
8012
8138
|
}
|
|
8139
|
+
function isOutsideWorkspace(rel) {
|
|
8140
|
+
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || rel.startsWith("/");
|
|
8141
|
+
}
|
|
8142
|
+
function isUnderRoot(root, path4) {
|
|
8143
|
+
const rel = relative12(root, path4);
|
|
8144
|
+
return !isOutsideWorkspace(rel);
|
|
8145
|
+
}
|
|
8146
|
+
function normalizeSearchPath(rawPath, workspaceRoot, pathOptions) {
|
|
8147
|
+
const path4 = optionalStringParam(rawPath) ?? ".";
|
|
8148
|
+
if (!workspaceRoot) return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
|
|
8149
|
+
if (!path4.startsWith("/")) {
|
|
8150
|
+
const resolved = resolve10(workspaceRoot, path4);
|
|
8151
|
+
const rel = relative12(workspaceRoot, resolved);
|
|
8152
|
+
if (isOutsideWorkspace(rel)) return { ok: false, message: `path "${path4}" is outside workspace` };
|
|
8153
|
+
return { ok: true, path: path4 };
|
|
8154
|
+
}
|
|
8155
|
+
const roots = [workspaceRoot, ...pathOptions?.rootAliases ?? []];
|
|
8156
|
+
if (!roots.some((root) => isUnderRoot(root, path4))) return { ok: false, message: `path "${path4}" is outside workspace` };
|
|
8157
|
+
return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
|
|
8158
|
+
}
|
|
8159
|
+
function sanitizeErrorMessage(message, pathOptions) {
|
|
8160
|
+
return pathOptions?.sanitizeErrorText?.(message) ?? message;
|
|
8161
|
+
}
|
|
8013
8162
|
function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
|
|
8014
8163
|
if (parsed.matchCount === 0) {
|
|
8015
8164
|
return {
|
|
@@ -8047,7 +8196,7 @@ function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
|
|
|
8047
8196
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
8048
8197
|
};
|
|
8049
8198
|
}
|
|
8050
|
-
function
|
|
8199
|
+
function remoteWorkspaceGrepTool(sandbox, workspaceRoot, pathOptions) {
|
|
8051
8200
|
return {
|
|
8052
8201
|
name: PI_GREP_TOOL.name,
|
|
8053
8202
|
description: PI_GREP_TOOL.description,
|
|
@@ -8061,36 +8210,31 @@ function vercelGrepTool(sandbox, workspaceRoot) {
|
|
|
8061
8210
|
if (typeof params.pattern !== "string" || params.pattern.length === 0) {
|
|
8062
8211
|
return makeError("pattern is required");
|
|
8063
8212
|
}
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
const rel = relative12(workspaceRoot, resolved);
|
|
8067
|
-
if (rel.startsWith("..") || rel.startsWith("/")) {
|
|
8068
|
-
return makeError(`path "${params.path}" is outside workspace`);
|
|
8069
|
-
}
|
|
8070
|
-
}
|
|
8213
|
+
const searchPath = normalizeSearchPath(params.path, workspaceRoot, pathOptions);
|
|
8214
|
+
if (!searchPath.ok) return makeError(searchPath.message);
|
|
8071
8215
|
try {
|
|
8072
|
-
const result = await sandbox.exec(buildRgCommand(params), {
|
|
8216
|
+
const result = await sandbox.exec(buildRgCommand(params, searchPath.path), {
|
|
8073
8217
|
signal: ctx.abortSignal,
|
|
8074
8218
|
timeoutMs: GREP_TIMEOUT_MS,
|
|
8075
8219
|
maxOutputBytes: GREP_MAX_OUTPUT_BYTES
|
|
8076
8220
|
});
|
|
8077
8221
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
8078
|
-
const stderr = decode(result.stderr).trim();
|
|
8222
|
+
const stderr = sanitizeErrorMessage(decode(result.stderr).trim(), pathOptions);
|
|
8079
8223
|
const message = stderr || `ripgrep exited with code ${result.exitCode}`;
|
|
8080
8224
|
return makeError(`grep failed: ${message}`);
|
|
8081
8225
|
}
|
|
8082
8226
|
const limit = numberParam(params.limit, DEFAULT_LIMIT2);
|
|
8083
|
-
const parsed = parseRgJson(decode(result.stdout), limit);
|
|
8227
|
+
const parsed = parseRgJson(decode(result.stdout), limit, pathOptions);
|
|
8084
8228
|
return buildSuccessResult(parsed, limit, result.truncated);
|
|
8085
8229
|
} catch (error) {
|
|
8086
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
8230
|
+
const message = error instanceof Error ? sanitizeErrorMessage(error.message, pathOptions) : "unknown error";
|
|
8087
8231
|
return makeError(`grep failed: ${message}`);
|
|
8088
8232
|
}
|
|
8089
8233
|
}
|
|
8090
8234
|
};
|
|
8091
8235
|
}
|
|
8092
8236
|
|
|
8093
|
-
// src/server/tools/filesystem/
|
|
8237
|
+
// src/server/tools/filesystem/remoteWorkspaceTools.ts
|
|
8094
8238
|
function isTextContent(content) {
|
|
8095
8239
|
return content.type === "text" && typeof content.text === "string";
|
|
8096
8240
|
}
|
|
@@ -8121,35 +8265,73 @@ function adaptPiTool(piTool) {
|
|
|
8121
8265
|
}
|
|
8122
8266
|
};
|
|
8123
8267
|
}
|
|
8268
|
+
function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
|
|
8269
|
+
const cwd = bundle.workspace.root;
|
|
8270
|
+
return [
|
|
8271
|
+
adaptPiTool(createReadToolDefinition(cwd, { operations: remoteWorkspaceReadOps(bundle.workspace, pathOptions) })),
|
|
8272
|
+
adaptPiTool(createWriteToolDefinition(cwd, { operations: remoteWorkspaceWriteOps(bundle.workspace, pathOptions) })),
|
|
8273
|
+
adaptPiTool(createEditToolDefinition(cwd, { operations: remoteWorkspaceEditOps(bundle.workspace, pathOptions) })),
|
|
8274
|
+
adaptPiTool(createFindToolDefinition(cwd, { operations: remoteWorkspaceFindOps(bundle.sandbox, bundle.workspace, pathOptions) })),
|
|
8275
|
+
{ ...remoteWorkspaceGrepTool(bundle.sandbox, cwd, pathOptions), readinessRequirements: ["workspace-fs"] },
|
|
8276
|
+
adaptPiTool(createLsToolDefinition(cwd, { operations: remoteWorkspaceLsOps(bundle.workspace, pathOptions) }))
|
|
8277
|
+
];
|
|
8278
|
+
}
|
|
8279
|
+
|
|
8280
|
+
// src/server/tools/filesystem/index.ts
|
|
8281
|
+
function isTextContent2(content) {
|
|
8282
|
+
return content.type === "text" && typeof content.text === "string";
|
|
8283
|
+
}
|
|
8284
|
+
function adaptPiTool2(piTool) {
|
|
8285
|
+
return {
|
|
8286
|
+
name: piTool.name,
|
|
8287
|
+
readinessRequirements: ["workspace-fs"],
|
|
8288
|
+
description: piTool.description,
|
|
8289
|
+
promptSnippet: piTool.promptSnippet,
|
|
8290
|
+
parameters: piTool.parameters,
|
|
8291
|
+
async execute(params, ctx) {
|
|
8292
|
+
const result = await piTool.execute(
|
|
8293
|
+
ctx.toolCallId,
|
|
8294
|
+
params,
|
|
8295
|
+
ctx.abortSignal,
|
|
8296
|
+
ctx.onUpdate ? (update) => {
|
|
8297
|
+
const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
|
|
8298
|
+
if (text) ctx.onUpdate?.(text);
|
|
8299
|
+
} : void 0,
|
|
8300
|
+
{}
|
|
8301
|
+
);
|
|
8302
|
+
const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
|
|
8303
|
+
return {
|
|
8304
|
+
content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
|
|
8305
|
+
isError: false,
|
|
8306
|
+
details: result.details
|
|
8307
|
+
};
|
|
8308
|
+
}
|
|
8309
|
+
};
|
|
8310
|
+
}
|
|
8311
|
+
function defaultFilesystemStrategyForBundle(bundle) {
|
|
8312
|
+
return bundle.sandbox.placement === "remote" ? { kind: "remote-workspace" } : { kind: "host" };
|
|
8313
|
+
}
|
|
8124
8314
|
function buildFilesystemAgentTools(bundle) {
|
|
8125
8315
|
const cwd = bundle.workspace.root;
|
|
8126
|
-
|
|
8127
|
-
|
|
8128
|
-
|
|
8129
|
-
adaptPiTool(createWriteToolDefinition(cwd, { operations: vercelWriteOps(bundle.workspace) })),
|
|
8130
|
-
adaptPiTool(createEditToolDefinition(cwd, { operations: vercelEditOps(bundle.workspace) })),
|
|
8131
|
-
adaptPiTool(createFindToolDefinition(cwd, { operations: vercelFindOps(bundle.sandbox, bundle.workspace) })),
|
|
8132
|
-
{ ...vercelGrepTool(bundle.sandbox, cwd), readinessRequirements: ["workspace-fs"] },
|
|
8133
|
-
adaptPiTool(createLsToolDefinition(cwd, { operations: vercelLsOps(bundle.workspace) }))
|
|
8134
|
-
];
|
|
8316
|
+
const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
|
|
8317
|
+
if (strategy.kind === "remote-workspace") {
|
|
8318
|
+
return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
|
|
8135
8319
|
}
|
|
8136
8320
|
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8137
8321
|
const ops = boundFs(storageRoot, { runtimeRoot: cwd });
|
|
8138
8322
|
return [
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8323
|
+
adaptPiTool2(createReadToolDefinition2(cwd, { operations: ops.read })),
|
|
8324
|
+
adaptPiTool2(createWriteToolDefinition2(cwd, { operations: ops.write })),
|
|
8325
|
+
adaptPiTool2(createEditToolDefinition2(cwd, { operations: ops.edit })),
|
|
8326
|
+
adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
|
|
8327
|
+
adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
|
|
8328
|
+
adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
|
|
8145
8329
|
];
|
|
8146
8330
|
}
|
|
8147
8331
|
|
|
8148
8332
|
// src/server/tools/harness/index.ts
|
|
8149
|
-
import {
|
|
8150
|
-
|
|
8151
|
-
createLocalBashOperations
|
|
8152
|
-
} from "@mariozechner/pi-coding-agent";
|
|
8333
|
+
import { unlink as unlink4 } from "fs/promises";
|
|
8334
|
+
import { createBashToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
8153
8335
|
|
|
8154
8336
|
// src/server/catalog/toolReadiness.ts
|
|
8155
8337
|
var WORKSPACE_PREPARING_MESSAGE = "Workspace is still preparing. Try again in a moment.";
|
|
@@ -8227,11 +8409,13 @@ function wrapToolForReadiness(tool, checkReadiness) {
|
|
|
8227
8409
|
};
|
|
8228
8410
|
}
|
|
8229
8411
|
|
|
8230
|
-
// src/server/tools/harness/
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
}
|
|
8234
|
-
|
|
8412
|
+
// src/server/tools/harness/bashToolOptions.ts
|
|
8413
|
+
import {
|
|
8414
|
+
createLocalBashOperations
|
|
8415
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8416
|
+
|
|
8417
|
+
// src/server/runtime/env.ts
|
|
8418
|
+
function mergeRuntimeProvisioningEnv(runtime, commandEnv) {
|
|
8235
8419
|
const current = runtime?.getCurrent?.() ?? runtime;
|
|
8236
8420
|
if (!current?.env && !current?.pathEntries?.length) return commandEnv;
|
|
8237
8421
|
const merged = {
|
|
@@ -8244,7 +8428,38 @@ function mergeRuntimeEnv(runtime, commandEnv) {
|
|
|
8244
8428
|
if (pathParts.length > 0) merged.PATH = pathParts.join(":");
|
|
8245
8429
|
return merged;
|
|
8246
8430
|
}
|
|
8247
|
-
|
|
8431
|
+
|
|
8432
|
+
// src/server/tools/operations/remoteSandbox.ts
|
|
8433
|
+
var REMOTE_SAFE_DEFAULT_PATH = "/usr/local/bin:/usr/bin:/bin";
|
|
8434
|
+
function mergeRemoteBashRuntimeEnv(runtime, executionRuntimeEnv, defaultPath) {
|
|
8435
|
+
const { PATH: executionPath, ...executionEnv } = executionRuntimeEnv ?? {};
|
|
8436
|
+
return mergeRuntimeProvisioningEnv(runtime, {
|
|
8437
|
+
...executionEnv,
|
|
8438
|
+
PATH: executionPath ? `${executionPath}:${defaultPath}` : defaultPath
|
|
8439
|
+
});
|
|
8440
|
+
}
|
|
8441
|
+
function remoteSandboxBashOps(sandbox, opts = {}) {
|
|
8442
|
+
return {
|
|
8443
|
+
exec(command, cwd, { onData, signal, timeout, env }) {
|
|
8444
|
+
const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : opts.runtime || opts.executionRuntimeEnv ? mergeRemoteBashRuntimeEnv(opts.runtime, opts.executionRuntimeEnv, opts.defaultPath ?? REMOTE_SAFE_DEFAULT_PATH) : env;
|
|
8445
|
+
const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
|
|
8446
|
+
return sandbox.exec(command, {
|
|
8447
|
+
cwd,
|
|
8448
|
+
env: filteredEnv2,
|
|
8449
|
+
signal,
|
|
8450
|
+
timeoutMs: timeout ? timeout * 1e3 : void 0,
|
|
8451
|
+
onStdout: (chunk) => onData(Buffer.from(chunk)),
|
|
8452
|
+
onStderr: (chunk) => onData(Buffer.from(chunk))
|
|
8453
|
+
}).then((result) => ({ exitCode: result.exitCode }));
|
|
8454
|
+
}
|
|
8455
|
+
};
|
|
8456
|
+
}
|
|
8457
|
+
|
|
8458
|
+
// src/server/tools/harness/bashToolOptions.ts
|
|
8459
|
+
function shellEscape2(s) {
|
|
8460
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
8461
|
+
}
|
|
8462
|
+
function bwrapSpawnHook(workspaceRoot, runtime, sandboxRoot = "/workspace") {
|
|
8248
8463
|
const args = buildBwrapArgs(workspaceRoot);
|
|
8249
8464
|
const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
|
|
8250
8465
|
return (context) => ({
|
|
@@ -8252,50 +8467,94 @@ function bwrapSpawnHook(workspaceRoot, runtime) {
|
|
|
8252
8467
|
command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
|
|
8253
8468
|
env: withWorkspacePythonEnv({
|
|
8254
8469
|
workspaceRoot,
|
|
8255
|
-
env:
|
|
8256
|
-
sandboxRoot
|
|
8470
|
+
env: mergeRuntimeProvisioningEnv(runtime, context.env),
|
|
8471
|
+
sandboxRoot
|
|
8257
8472
|
})
|
|
8258
8473
|
});
|
|
8259
8474
|
}
|
|
8260
|
-
function directSpawnHook(workspaceRoot, runtime) {
|
|
8475
|
+
function directSpawnHook(workspaceRoot, runtime, preserveHostHome = true) {
|
|
8261
8476
|
return (context) => ({
|
|
8262
8477
|
...context,
|
|
8263
8478
|
env: withWorkspacePythonEnv({
|
|
8264
8479
|
workspaceRoot,
|
|
8265
|
-
env:
|
|
8266
|
-
preserveHostHome
|
|
8480
|
+
env: mergeRuntimeProvisioningEnv(runtime, context.env),
|
|
8481
|
+
preserveHostHome
|
|
8267
8482
|
})
|
|
8268
8483
|
});
|
|
8269
8484
|
}
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8274
|
-
|
|
8275
|
-
return {
|
|
8276
|
-
|
|
8277
|
-
|
|
8278
|
-
|
|
8279
|
-
// provisioned runtime env plus a conservative remote PATH tail.
|
|
8280
|
-
mergeEnv: () => mergeRuntimeEnv(runtime, { PATH: VERCEL_SAFE_DEFAULT_PATH })
|
|
8281
|
-
})
|
|
8282
|
-
};
|
|
8283
|
-
case "bwrap": {
|
|
8284
|
-
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8285
|
-
return {
|
|
8286
|
-
operations: createLocalBashOperations(),
|
|
8287
|
-
spawnHook: bwrapSpawnHook(storageRoot, runtime)
|
|
8288
|
-
};
|
|
8289
|
-
}
|
|
8290
|
-
default: {
|
|
8291
|
-
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8292
|
-
return {
|
|
8293
|
-
operations: createLocalBashOperations(),
|
|
8294
|
-
spawnHook: directSpawnHook(storageRoot, runtime)
|
|
8295
|
-
};
|
|
8485
|
+
function localBashOperationsWithRuntimeEnv(bundle) {
|
|
8486
|
+
const local = createLocalBashOperations();
|
|
8487
|
+
return {
|
|
8488
|
+
async exec(command, cwd, options) {
|
|
8489
|
+
const runtimeEnv = await bundle.getRuntimeEnv?.();
|
|
8490
|
+
return local.exec(command, cwd, {
|
|
8491
|
+
...options,
|
|
8492
|
+
env: { ...options.env ?? {}, ...runtimeEnv ?? {} }
|
|
8493
|
+
});
|
|
8296
8494
|
}
|
|
8495
|
+
};
|
|
8496
|
+
}
|
|
8497
|
+
function hostBashToolOptions(bundle, runtime, strategy) {
|
|
8498
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8499
|
+
return {
|
|
8500
|
+
operations: localBashOperationsWithRuntimeEnv(bundle),
|
|
8501
|
+
spawnHook: directSpawnHook(storageRoot, runtime, strategy.preserveHostHome ?? true)
|
|
8502
|
+
};
|
|
8503
|
+
}
|
|
8504
|
+
function localSandboxBashToolOptions(bundle, runtime, strategy) {
|
|
8505
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8506
|
+
return {
|
|
8507
|
+
// localBashOperationsWithRuntimeEnv() injects bundle.getRuntimeEnv()
|
|
8508
|
+
// into the outer shell env before the spawned sandbox shell command runs,
|
|
8509
|
+
// so bridge runtime env reaches local sandboxed commands without relying
|
|
8510
|
+
// on provisioning PATH/env alone.
|
|
8511
|
+
operations: localBashOperationsWithRuntimeEnv(bundle),
|
|
8512
|
+
spawnHook: bwrapSpawnHook(storageRoot, runtime, strategy.sandboxRoot)
|
|
8513
|
+
};
|
|
8514
|
+
}
|
|
8515
|
+
function remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy) {
|
|
8516
|
+
return {
|
|
8517
|
+
operations: remoteSandboxBashOps(bundle.sandbox, {
|
|
8518
|
+
defaultPath: strategy.defaultPath,
|
|
8519
|
+
runtime,
|
|
8520
|
+
executionRuntimeEnv
|
|
8521
|
+
})
|
|
8522
|
+
};
|
|
8523
|
+
}
|
|
8524
|
+
function defaultBashStrategyForBundle(bundle) {
|
|
8525
|
+
return bundle.sandbox.placement === "remote" ? { kind: "remote" } : { kind: "host", preserveHostHome: true };
|
|
8526
|
+
}
|
|
8527
|
+
function createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv) {
|
|
8528
|
+
const strategy = bundle.bash ?? defaultBashStrategyForBundle(bundle);
|
|
8529
|
+
switch (strategy.kind) {
|
|
8530
|
+
case "host":
|
|
8531
|
+
return hostBashToolOptions(bundle, runtime, strategy);
|
|
8532
|
+
case "local-sandbox":
|
|
8533
|
+
return localSandboxBashToolOptions(bundle, runtime, strategy);
|
|
8534
|
+
case "remote":
|
|
8535
|
+
return remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy);
|
|
8297
8536
|
}
|
|
8298
8537
|
}
|
|
8538
|
+
|
|
8539
|
+
// src/server/tools/harness/index.ts
|
|
8540
|
+
function bashOptionsForBundle(bundle, runtime, executionRuntimeEnv) {
|
|
8541
|
+
return createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv);
|
|
8542
|
+
}
|
|
8543
|
+
function runtimeSecretValues(env) {
|
|
8544
|
+
return Object.entries(env ?? {}).filter(([key, value]) => key !== "PATH" && /TOKEN|SECRET|PASSWORD|API_KEY|PRIVATE_KEY/i.test(key) && value.length > 0).map(([, value]) => value);
|
|
8545
|
+
}
|
|
8546
|
+
function redactSecretsInString(text, secrets) {
|
|
8547
|
+
return secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), text);
|
|
8548
|
+
}
|
|
8549
|
+
function redactSecrets(value, secrets) {
|
|
8550
|
+
if (secrets.length === 0) return value;
|
|
8551
|
+
if (typeof value === "string") return redactSecretsInString(value, secrets);
|
|
8552
|
+
if (Array.isArray(value)) return value.map((item) => redactSecrets(item, secrets));
|
|
8553
|
+
if (!value || typeof value !== "object") return value;
|
|
8554
|
+
return Object.fromEntries(
|
|
8555
|
+
Object.entries(value).map(([key, child]) => [key, redactSecrets(child, secrets)])
|
|
8556
|
+
);
|
|
8557
|
+
}
|
|
8299
8558
|
function isRuntimeReady(readiness) {
|
|
8300
8559
|
return readiness === void 0 || readiness === true || typeof readiness === "object" && readiness !== null && readiness.ready === true;
|
|
8301
8560
|
}
|
|
@@ -8313,23 +8572,38 @@ function runtimeRequirementForFailure(text) {
|
|
|
8313
8572
|
if (/(?:^|\n)(?:[^\n:]+:\s*)?(?:line \d+:\s*)?[A-Za-z0-9_.-]+: command not found/i.test(text)) return "runtime-dependencies";
|
|
8314
8573
|
return void 0;
|
|
8315
8574
|
}
|
|
8316
|
-
function
|
|
8575
|
+
function runtimeNotReadyFromState(requirement, readiness) {
|
|
8576
|
+
return runtimeNotReadyToolResult(
|
|
8577
|
+
requirement,
|
|
8578
|
+
readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
|
|
8579
|
+
);
|
|
8580
|
+
}
|
|
8581
|
+
function adaptPiTool3(bundle, runtime) {
|
|
8582
|
+
const template = createBashToolDefinition(bundle.workspace.root, bashOptionsForBundle(bundle, runtime));
|
|
8317
8583
|
return {
|
|
8318
|
-
name:
|
|
8319
|
-
description:
|
|
8320
|
-
promptSnippet:
|
|
8321
|
-
parameters:
|
|
8584
|
+
name: template.name,
|
|
8585
|
+
description: template.description,
|
|
8586
|
+
promptSnippet: template.promptSnippet,
|
|
8587
|
+
parameters: template.parameters,
|
|
8322
8588
|
readinessRequirements: ["sandbox-exec"],
|
|
8323
8589
|
async execute(params, ctx) {
|
|
8324
8590
|
const command = typeof params.command === "string" ? params.command : "";
|
|
8325
8591
|
const readiness = runtime?.getReadiness?.();
|
|
8326
8592
|
const commandRuntimeRequirement = command ? runtimeRequirementForCommand(command) : void 0;
|
|
8327
8593
|
if (commandRuntimeRequirement && !isRuntimeReady(readiness)) {
|
|
8328
|
-
return
|
|
8329
|
-
commandRuntimeRequirement,
|
|
8330
|
-
readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
|
|
8331
|
-
);
|
|
8594
|
+
return runtimeNotReadyFromState(commandRuntimeRequirement, readiness);
|
|
8332
8595
|
}
|
|
8596
|
+
const runtimeEnv = await bundle.getRuntimeEnv?.();
|
|
8597
|
+
const secrets = runtimeSecretValues(runtimeEnv);
|
|
8598
|
+
const executionBundle = runtimeEnv === void 0 ? bundle : {
|
|
8599
|
+
...bundle,
|
|
8600
|
+
getRuntimeEnv: async () => runtimeEnv
|
|
8601
|
+
};
|
|
8602
|
+
const piTool = createBashToolDefinition(
|
|
8603
|
+
bundle.workspace.root,
|
|
8604
|
+
bashOptionsForBundle(executionBundle, runtime, runtimeEnv)
|
|
8605
|
+
);
|
|
8606
|
+
let emittedRedactionNotice = false;
|
|
8333
8607
|
let result;
|
|
8334
8608
|
try {
|
|
8335
8609
|
result = await piTool.execute(
|
|
@@ -8338,7 +8612,14 @@ function adaptPiTool2(piTool, runtime) {
|
|
|
8338
8612
|
ctx.abortSignal,
|
|
8339
8613
|
ctx.onUpdate ? (update) => {
|
|
8340
8614
|
const text2 = update.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
8341
|
-
|
|
8615
|
+
if (secrets.length === 0) {
|
|
8616
|
+
ctx.onUpdate(text2);
|
|
8617
|
+
return;
|
|
8618
|
+
}
|
|
8619
|
+
if (!emittedRedactionNotice) {
|
|
8620
|
+
emittedRedactionNotice = true;
|
|
8621
|
+
ctx.onUpdate("[streaming output redacted while runtime secrets are in scope]");
|
|
8622
|
+
}
|
|
8342
8623
|
} : void 0,
|
|
8343
8624
|
{}
|
|
8344
8625
|
);
|
|
@@ -8347,27 +8628,29 @@ function adaptPiTool2(piTool, runtime) {
|
|
|
8347
8628
|
const latestReadiness2 = runtime?.getReadiness?.();
|
|
8348
8629
|
const failureRuntimeRequirement2 = runtimeRequirementForFailure(message);
|
|
8349
8630
|
if (command && failureRuntimeRequirement2 && !isRuntimeReady(latestReadiness2)) {
|
|
8350
|
-
return
|
|
8351
|
-
failureRuntimeRequirement2,
|
|
8352
|
-
latestReadiness2 && typeof latestReadiness2 === "object" && latestReadiness2.ready === false ? latestReadiness2 : { ready: false, state: "preparing", retryable: true }
|
|
8353
|
-
);
|
|
8631
|
+
return runtimeNotReadyFromState(failureRuntimeRequirement2, latestReadiness2);
|
|
8354
8632
|
}
|
|
8355
|
-
|
|
8633
|
+
return {
|
|
8634
|
+
content: [{ type: "text", text: redactSecretsInString(message, secrets) }],
|
|
8635
|
+
isError: true,
|
|
8636
|
+
details: {}
|
|
8637
|
+
};
|
|
8356
8638
|
}
|
|
8357
|
-
|
|
8639
|
+
if (secrets.length > 0 && result.details && typeof result.details === "object" && "fullOutputPath" in result.details && typeof result.details.fullOutputPath === "string") {
|
|
8640
|
+
await unlink4(result.details.fullOutputPath).catch(() => void 0);
|
|
8641
|
+
delete result.details.fullOutputPath;
|
|
8642
|
+
}
|
|
8643
|
+
const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: redactSecretsInString(c.text, secrets) }));
|
|
8358
8644
|
const text = textContent.map((part) => part.text).join("\n");
|
|
8359
8645
|
const latestReadiness = runtime?.getReadiness?.();
|
|
8360
8646
|
const failureRuntimeRequirement = runtimeRequirementForFailure(text);
|
|
8361
8647
|
if (command && failureRuntimeRequirement && !isRuntimeReady(latestReadiness)) {
|
|
8362
|
-
return
|
|
8363
|
-
failureRuntimeRequirement,
|
|
8364
|
-
latestReadiness && typeof latestReadiness === "object" && latestReadiness.ready === false ? latestReadiness : { ready: false, state: "preparing", retryable: true }
|
|
8365
|
-
);
|
|
8648
|
+
return runtimeNotReadyFromState(failureRuntimeRequirement, latestReadiness);
|
|
8366
8649
|
}
|
|
8367
8650
|
return {
|
|
8368
8651
|
content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
|
|
8369
8652
|
isError: Boolean(result.isError),
|
|
8370
|
-
details: result.details
|
|
8653
|
+
details: redactSecrets(result.details, secrets)
|
|
8371
8654
|
};
|
|
8372
8655
|
}
|
|
8373
8656
|
};
|
|
@@ -8427,7 +8710,7 @@ function createExecuteIsolatedCodeTool(sandbox) {
|
|
|
8427
8710
|
}
|
|
8428
8711
|
function buildHarnessAgentTools(bundle, runtime) {
|
|
8429
8712
|
const tools = [
|
|
8430
|
-
|
|
8713
|
+
adaptPiTool3(bundle, runtime)
|
|
8431
8714
|
];
|
|
8432
8715
|
if (bundle.sandbox.capabilities.includes("isolated-code")) {
|
|
8433
8716
|
tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
|
|
@@ -9727,7 +10010,7 @@ function gitRoutes(app, opts, done) {
|
|
|
9727
10010
|
done();
|
|
9728
10011
|
}
|
|
9729
10012
|
|
|
9730
|
-
// src/server/
|
|
10013
|
+
// src/server/runtime/readyStatus.ts
|
|
9731
10014
|
function defaultCapabilities(sandboxReady, harnessReady) {
|
|
9732
10015
|
return {
|
|
9733
10016
|
chat: { state: harnessReady ? "ready" : "preparing" },
|
|
@@ -9829,6 +10112,21 @@ var ReadyStatusTracker = class {
|
|
|
9829
10112
|
}
|
|
9830
10113
|
};
|
|
9831
10114
|
|
|
10115
|
+
// src/server/runtime/modeReadiness.ts
|
|
10116
|
+
function createRuntimeReadyStatusTracker(modeAdapter, opts) {
|
|
10117
|
+
const readiness = modeAdapter.readiness;
|
|
10118
|
+
const tracker = new ReadyStatusTracker({
|
|
10119
|
+
sandboxReady: readiness?.initialSandboxReady ?? true,
|
|
10120
|
+
harnessReady: opts.harnessReady,
|
|
10121
|
+
capabilities: {
|
|
10122
|
+
...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
|
|
10123
|
+
...opts.capabilities ?? {}
|
|
10124
|
+
}
|
|
10125
|
+
});
|
|
10126
|
+
readiness?.onTrackerCreated?.(tracker);
|
|
10127
|
+
return tracker;
|
|
10128
|
+
}
|
|
10129
|
+
|
|
9832
10130
|
// src/server/pi-chat/piChatHistory.ts
|
|
9833
10131
|
function isRecord(value) {
|
|
9834
10132
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11879,11 +12177,24 @@ async function createAgentApp(opts = {}) {
|
|
|
11879
12177
|
const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
|
|
11880
12178
|
const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
|
|
11881
12179
|
const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
|
|
11882
|
-
|
|
12180
|
+
let runtimeBundle = await modeAdapter.create({
|
|
11883
12181
|
workspaceRoot,
|
|
11884
12182
|
sessionId,
|
|
11885
12183
|
templatePath
|
|
11886
12184
|
});
|
|
12185
|
+
if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
|
|
12186
|
+
runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
|
|
12187
|
+
workspaceId: sessionId,
|
|
12188
|
+
workspaceRoot,
|
|
12189
|
+
runtimeMode: resolvedMode,
|
|
12190
|
+
runtimeBundle
|
|
12191
|
+
}, opts.runtimeEnvContributions, opts.telemetry);
|
|
12192
|
+
}
|
|
12193
|
+
await opts.runtimeProvisioner?.({
|
|
12194
|
+
workspaceRoot,
|
|
12195
|
+
runtimeMode: resolvedMode,
|
|
12196
|
+
runtimeBundle
|
|
12197
|
+
});
|
|
11887
12198
|
const pluginTools = [];
|
|
11888
12199
|
const externalPluginsEnabled = opts.externalPlugins !== false;
|
|
11889
12200
|
if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
|
|
@@ -11940,13 +12251,9 @@ async function createAgentApp(opts = {}) {
|
|
|
11940
12251
|
});
|
|
11941
12252
|
harnessRef = harness;
|
|
11942
12253
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
11943
|
-
const readyTracker =
|
|
11944
|
-
sandboxReady: resolvedMode !== "vercel-sandbox",
|
|
12254
|
+
const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
|
|
11945
12255
|
harnessReady: true
|
|
11946
12256
|
});
|
|
11947
|
-
if (resolvedMode === "vercel-sandbox") {
|
|
11948
|
-
queueMicrotask(() => readyTracker.markSandboxReady());
|
|
11949
|
-
}
|
|
11950
12257
|
app.addHook(
|
|
11951
12258
|
"onRequest",
|
|
11952
12259
|
createAuthMiddleware({
|
|
@@ -11963,10 +12270,7 @@ async function createAgentApp(opts = {}) {
|
|
|
11963
12270
|
await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
|
|
11964
12271
|
await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
|
|
11965
12272
|
await app.register(gitRoutes, {
|
|
11966
|
-
getWorkspaceRoot: () =>
|
|
11967
|
-
if (runtimeBundle.sandbox.provider === "remote-worker") return void 0;
|
|
11968
|
-
return getRuntimeBundleStorageRoot(runtimeBundle);
|
|
11969
|
-
}
|
|
12273
|
+
getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
|
|
11970
12274
|
});
|
|
11971
12275
|
const piChatService = new HarnessPiChatService({
|
|
11972
12276
|
harness,
|
|
@@ -12148,8 +12452,8 @@ function buildUploadAgentTools(bundle) {
|
|
|
12148
12452
|
const contentType = contentTypeFromExt(filePath);
|
|
12149
12453
|
const ext = extForUpload2(filePath, contentType);
|
|
12150
12454
|
const base = basenameForUpload2(filePath);
|
|
12151
|
-
const
|
|
12152
|
-
const destPath = `${dir}/${base}-${
|
|
12455
|
+
const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
12456
|
+
const destPath = `${dir}/${base}-${unique2}.${ext}`;
|
|
12153
12457
|
await workspace.mkdir(dir, { recursive: true });
|
|
12154
12458
|
if (workspace.writeBinaryFile) {
|
|
12155
12459
|
await workspace.writeBinaryFile(destPath, bytes);
|
|
@@ -12179,7 +12483,6 @@ function buildUploadAgentTools(bundle) {
|
|
|
12179
12483
|
var DEFAULT_VERSION2 = "0.1.0-dev";
|
|
12180
12484
|
var DEFAULT_WORKSPACE_ID3 = "default";
|
|
12181
12485
|
var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
|
|
12182
|
-
var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
|
|
12183
12486
|
function pluginNameFromPath(path4) {
|
|
12184
12487
|
const fileName = basename3(path4);
|
|
12185
12488
|
if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
|
|
@@ -12198,15 +12501,6 @@ function getAvailableModelProviders() {
|
|
|
12198
12501
|
new Set(availableModels.map((model) => model.provider))
|
|
12199
12502
|
).sort((a, b) => a.localeCompare(b));
|
|
12200
12503
|
}
|
|
12201
|
-
function selectRuntimeModeAdapter(mode, sandboxHandleStore) {
|
|
12202
|
-
if (mode === "vercel-sandbox" && sandboxHandleStore) {
|
|
12203
|
-
return createVercelSandboxModeAdapter({
|
|
12204
|
-
store: sandboxHandleStore,
|
|
12205
|
-
orphanGuardMaxIdleMs: null
|
|
12206
|
-
});
|
|
12207
|
-
}
|
|
12208
|
-
return resolveMode(mode);
|
|
12209
|
-
}
|
|
12210
12504
|
function getRequestWorkspaceId(request) {
|
|
12211
12505
|
return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID3;
|
|
12212
12506
|
}
|
|
@@ -12226,27 +12520,11 @@ function isWorkspaceAgnosticAgentRequest(request, options) {
|
|
|
12226
12520
|
if (pathname === "/api/v1/ready-status") return !options?.readyStatusWorkspaceScoped;
|
|
12227
12521
|
return pathname === "/health" || pathname === "/ready" || pathname === "/api/v1/agent/models";
|
|
12228
12522
|
}
|
|
12229
|
-
function extractHttpStatus2(error) {
|
|
12230
|
-
const statusCode = error?.statusCode;
|
|
12231
|
-
if (typeof statusCode === "number") return statusCode;
|
|
12232
|
-
const status = error?.status;
|
|
12233
|
-
if (typeof status === "number") return status;
|
|
12234
|
-
const responseStatus = error?.response?.status;
|
|
12235
|
-
return typeof responseStatus === "number" ? responseStatus : null;
|
|
12236
|
-
}
|
|
12237
12523
|
function normalizeSessionNamespace(value) {
|
|
12238
12524
|
if (typeof value !== "string") return void 0;
|
|
12239
12525
|
const trimmed = value.trim();
|
|
12240
12526
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
12241
12527
|
}
|
|
12242
|
-
function isExpiredSandboxRuntimeError(error) {
|
|
12243
|
-
const code = error?.code;
|
|
12244
|
-
if (code === ErrorCode.enum.SANDBOX_EXPIRED) return true;
|
|
12245
|
-
const status = extractHttpStatus2(error);
|
|
12246
|
-
if (status === 404 || status === 410) return true;
|
|
12247
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
12248
|
-
return /status code (404|410) is not ok/i.test(message);
|
|
12249
|
-
}
|
|
12250
12528
|
function createHttpError(code, message, details = {}) {
|
|
12251
12529
|
const error = new Error(message);
|
|
12252
12530
|
error.code = code;
|
|
@@ -12301,7 +12579,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12301
12579
|
const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID3;
|
|
12302
12580
|
const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
|
|
12303
12581
|
const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
|
|
12304
|
-
const modeAdapter = opts.runtimeModeAdapter ??
|
|
12582
|
+
const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode, { sandboxHandleStore: opts.sandboxHandleStore });
|
|
12305
12583
|
app.addHook("onClose", async () => {
|
|
12306
12584
|
await modeAdapter.dispose?.();
|
|
12307
12585
|
});
|
|
@@ -12373,9 +12651,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12373
12651
|
requestId: request?.id,
|
|
12374
12652
|
telemetry: opts.telemetry
|
|
12375
12653
|
};
|
|
12376
|
-
const runtimeLayout = getBoringAgentRuntimePaths(
|
|
12377
|
-
resolvedMode === "vercel-sandbox" ? VERCEL_SANDBOX_WORKSPACE_ROOT : scope.root
|
|
12378
|
-
);
|
|
12654
|
+
const runtimeLayout = getBoringAgentRuntimePaths(modeAdapter.getRuntimeLayoutRoot?.(modeCtx) ?? scope.root);
|
|
12379
12655
|
return await opts.provisionRuntime({
|
|
12380
12656
|
workspaceId,
|
|
12381
12657
|
workspaceRoot: scope.root,
|
|
@@ -12403,19 +12679,22 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12403
12679
|
retryable: true
|
|
12404
12680
|
} : { state: "ready" };
|
|
12405
12681
|
let provisioningGeneration = 0;
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12682
|
+
let runtimeBundle = await modeAdapter.create(modeCtx);
|
|
12683
|
+
if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
|
|
12684
|
+
runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
|
|
12685
|
+
workspaceId,
|
|
12686
|
+
workspaceRoot: root,
|
|
12687
|
+
runtimeMode: resolvedMode,
|
|
12688
|
+
runtimeBundle
|
|
12689
|
+
}, opts.runtimeEnvContributions, opts.telemetry);
|
|
12690
|
+
}
|
|
12691
|
+
const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
|
|
12409
12692
|
harnessReady: false,
|
|
12410
12693
|
capabilities: {
|
|
12411
12694
|
chat: { state: "preparing" },
|
|
12412
|
-
workspace: { state: resolvedMode !== "vercel-sandbox" ? "ready" : "preparing" },
|
|
12413
12695
|
runtimeDependencies
|
|
12414
12696
|
}
|
|
12415
12697
|
});
|
|
12416
|
-
if (resolvedMode === "vercel-sandbox") {
|
|
12417
|
-
queueMicrotask(() => readyTracker.markSandboxReady());
|
|
12418
|
-
}
|
|
12419
12698
|
let binding;
|
|
12420
12699
|
const updateRuntimeDependencies = (next) => {
|
|
12421
12700
|
runtimeDependencies = next;
|
|
@@ -12633,7 +12912,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12633
12912
|
}
|
|
12634
12913
|
async function recreateRuntimeBinding(workspaceId, scope) {
|
|
12635
12914
|
runtimeBindings.delete(scope.key);
|
|
12636
|
-
|
|
12915
|
+
modeAdapter.evictCachedRuntime?.({ workspaceId });
|
|
12637
12916
|
const created = createRuntimeBindingEntry(workspaceId, scope);
|
|
12638
12917
|
runtimeBindings.set(scope.key, created);
|
|
12639
12918
|
evictRuntimeBindings();
|
|
@@ -12647,23 +12926,23 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12647
12926
|
}
|
|
12648
12927
|
}
|
|
12649
12928
|
async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
|
|
12650
|
-
|
|
12929
|
+
const healthCheck = modeAdapter.cachedBindingHealthCheck;
|
|
12930
|
+
if (!healthCheck) return binding;
|
|
12651
12931
|
const now = Date.now();
|
|
12652
|
-
|
|
12932
|
+
const intervalMs = healthCheck.intervalMs ?? 15e3;
|
|
12933
|
+
if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < intervalMs) {
|
|
12653
12934
|
return binding;
|
|
12654
12935
|
}
|
|
12655
|
-
|
|
12656
|
-
|
|
12936
|
+
const result = await healthCheck.check({ runtimeBundle: binding.runtimeBundle, workspaceId });
|
|
12937
|
+
if (result.state === "ok") {
|
|
12657
12938
|
binding.lastHealthCheckMs = now;
|
|
12658
12939
|
return binding;
|
|
12659
|
-
} catch (error) {
|
|
12660
|
-
if (!isExpiredSandboxRuntimeError(error)) throw error;
|
|
12661
|
-
app.log.warn({
|
|
12662
|
-
err: error,
|
|
12663
|
-
workspaceId
|
|
12664
|
-
}, "[sandbox] cached runtime expired; recreating from persisted handle");
|
|
12665
|
-
return await recreateRuntimeBinding(workspaceId, scope);
|
|
12666
12940
|
}
|
|
12941
|
+
app.log.warn({
|
|
12942
|
+
err: result.error,
|
|
12943
|
+
workspaceId
|
|
12944
|
+
}, result.message ?? "[runtime] cached runtime invalid; recreating");
|
|
12945
|
+
return await recreateRuntimeBinding(workspaceId, scope);
|
|
12667
12946
|
}
|
|
12668
12947
|
const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
|
|
12669
12948
|
const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
|
|
@@ -12761,11 +13040,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12761
13040
|
getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
|
|
12762
13041
|
});
|
|
12763
13042
|
await app.register(gitRoutes, {
|
|
12764
|
-
getWorkspaceRoot: async (request) =>
|
|
12765
|
-
const bundle = (await getBindingForRequest(request)).runtimeBundle;
|
|
12766
|
-
if (bundle.sandbox.provider === "remote-worker") return void 0;
|
|
12767
|
-
return getRuntimeBundleStorageRoot(bundle);
|
|
12768
|
-
}
|
|
13043
|
+
getWorkspaceRoot: async (request) => getOptionalRuntimeBundleStorageRoot((await getBindingForRequest(request)).runtimeBundle)
|
|
12769
13044
|
});
|
|
12770
13045
|
await app.register(piChatRoutes, {
|
|
12771
13046
|
getService: async (request) => {
|