@hachej/boring-agent 0.1.54 → 0.1.56
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-Ddn5DQ5E.d.ts} +2 -1
- package/dist/front/index.js +153 -49
- package/dist/front/styles.css +6 -0
- package/dist/server/index.d.ts +141 -4
- package/dist/server/index.js +564 -284
- package/dist/shared/index.d.ts +2 -2
- package/docs/runtime.md +3 -1
- 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";
|
|
@@ -6053,24 +6175,26 @@ import {
|
|
|
6053
6175
|
parseSessionEntries,
|
|
6054
6176
|
CURRENT_SESSION_VERSION
|
|
6055
6177
|
} from "@mariozechner/pi-coding-agent";
|
|
6056
|
-
function sessionBaseDir() {
|
|
6178
|
+
function sessionBaseDir(explicitRoot) {
|
|
6179
|
+
const explicit = explicitRoot?.trim();
|
|
6180
|
+
if (explicit) return resolve7(explicit);
|
|
6057
6181
|
const configured = getEnv(SESSION_ROOT_ENV)?.trim();
|
|
6058
6182
|
return configured ? resolve7(configured) : join10(homedir3(), ".pi", "agent", "sessions");
|
|
6059
6183
|
}
|
|
6060
|
-
function defaultSessionDir(cwd) {
|
|
6184
|
+
function defaultSessionDir(cwd, explicitRoot) {
|
|
6061
6185
|
const safePath = `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`;
|
|
6062
|
-
return join10(sessionBaseDir(), safePath);
|
|
6186
|
+
return join10(sessionBaseDir(explicitRoot), safePath);
|
|
6063
6187
|
}
|
|
6064
6188
|
var SAFE_ID = /^[a-zA-Z0-9_-]+$/;
|
|
6065
6189
|
var SAFE_SESSION_NAMESPACE = /^[a-zA-Z0-9_-]+$/;
|
|
6066
6190
|
var SESSION_ROOT_ENV = "BORING_AGENT_SESSION_ROOT";
|
|
6067
6191
|
var SUMMARY_PREFIX_BYTES = 64 * 1024;
|
|
6068
|
-
function sessionDirForNamespace(namespace) {
|
|
6192
|
+
function sessionDirForNamespace(namespace, explicitRoot) {
|
|
6069
6193
|
const safeNamespace = namespace.trim();
|
|
6070
6194
|
if (!SAFE_SESSION_NAMESPACE.test(safeNamespace)) {
|
|
6071
6195
|
throw new Error("session namespace must contain only letters, numbers, underscores, and dashes");
|
|
6072
6196
|
}
|
|
6073
|
-
return join10(sessionBaseDir(), safeNamespace);
|
|
6197
|
+
return join10(sessionBaseDir(explicitRoot), safeNamespace);
|
|
6074
6198
|
}
|
|
6075
6199
|
function normalizeListOptions(options) {
|
|
6076
6200
|
return {
|
|
@@ -6090,7 +6214,7 @@ var PiSessionStore = class {
|
|
|
6090
6214
|
this.sessionDir = options;
|
|
6091
6215
|
return;
|
|
6092
6216
|
}
|
|
6093
|
-
this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace) : defaultSessionDir(options?.storageCwd ?? cwd));
|
|
6217
|
+
this.sessionDir = options?.sessionDir ?? (options?.sessionNamespace ? sessionDirForNamespace(options.sessionNamespace, options.sessionRoot) : defaultSessionDir(options?.storageCwd ?? cwd, options?.sessionRoot));
|
|
6094
6218
|
}
|
|
6095
6219
|
getSessionDir() {
|
|
6096
6220
|
return this.sessionDir;
|
|
@@ -6273,10 +6397,34 @@ var PiSessionStore = class {
|
|
|
6273
6397
|
}
|
|
6274
6398
|
}
|
|
6275
6399
|
async loadPiSessionFile(_ctx, sessionId) {
|
|
6400
|
+
if (!SAFE_ID.test(sessionId)) return null;
|
|
6276
6401
|
try {
|
|
6277
|
-
const
|
|
6278
|
-
|
|
6279
|
-
|
|
6402
|
+
const direct = join10(this.sessionDir, `${sessionId}.jsonl`);
|
|
6403
|
+
let filepath = direct;
|
|
6404
|
+
let content;
|
|
6405
|
+
try {
|
|
6406
|
+
content = await readFile9(direct, "utf-8");
|
|
6407
|
+
} catch {
|
|
6408
|
+
const files = await readdir7(this.sessionDir).catch(() => []);
|
|
6409
|
+
const match = files.find(
|
|
6410
|
+
(f) => f.endsWith(`_${sessionId}.jsonl`) || f === `${sessionId}.jsonl`
|
|
6411
|
+
);
|
|
6412
|
+
if (!match) return null;
|
|
6413
|
+
filepath = join10(this.sessionDir, match);
|
|
6414
|
+
content = await readFile9(filepath, "utf-8");
|
|
6415
|
+
}
|
|
6416
|
+
const entries = safeParseEntries(content);
|
|
6417
|
+
const linkedPiFile = extractPiSessionFilePath(entries);
|
|
6418
|
+
if (linkedPiFile) return linkedPiFile;
|
|
6419
|
+
if (!isTimestampNamedPiSessionFile(filepath, sessionId)) return null;
|
|
6420
|
+
const existingWrapper = await this.findWrapperReferencingNativeSession(filepath);
|
|
6421
|
+
if (existingWrapper) {
|
|
6422
|
+
const wrapperSessionId = await this.readSessionFileId(existingWrapper);
|
|
6423
|
+
if (wrapperSessionId !== sessionId) return null;
|
|
6424
|
+
const wrapperEntries = parseJsonlPrefixEntries(await readJsonlPrefix(existingWrapper));
|
|
6425
|
+
return extractPiSessionFilePath(wrapperEntries);
|
|
6426
|
+
}
|
|
6427
|
+
return await this.ensureWrapperForNativeSession(sessionId, filepath);
|
|
6280
6428
|
} catch {
|
|
6281
6429
|
return null;
|
|
6282
6430
|
}
|
|
@@ -7065,6 +7213,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
7065
7213
|
const pi = withPiHarnessDefaults(opts.pi);
|
|
7066
7214
|
const sessionStore = new PiSessionStore(opts.runtimeCwd ?? opts.cwd, {
|
|
7067
7215
|
sessionNamespace: opts.sessionNamespace,
|
|
7216
|
+
sessionRoot: opts.sessionRoot,
|
|
7068
7217
|
sessionDir: opts.sessionDir,
|
|
7069
7218
|
storageCwd: opts.cwd
|
|
7070
7219
|
});
|
|
@@ -7182,8 +7331,8 @@ function createPiCodingAgentHarness(opts) {
|
|
|
7182
7331
|
const { session: piSession } = await createAgentSession({
|
|
7183
7332
|
cwd: runtimeCwd,
|
|
7184
7333
|
// Suppress Pi's built-in filesystem/shell tools while keeping Boring's
|
|
7185
|
-
// adapted tool catalog active.
|
|
7186
|
-
//
|
|
7334
|
+
// adapted tool catalog active. Do NOT pass an explicit empty tool-name
|
|
7335
|
+
// allowlist: in the current Pi SDK that disables custom tools too.
|
|
7187
7336
|
noTools: "builtin",
|
|
7188
7337
|
customTools: adaptToolsForPi(opts.tools, input.sessionId, opts.telemetry),
|
|
7189
7338
|
model,
|
|
@@ -7450,12 +7599,12 @@ async function loadPlugins(options) {
|
|
|
7450
7599
|
|
|
7451
7600
|
// src/server/tools/filesystem/index.ts
|
|
7452
7601
|
import {
|
|
7453
|
-
createEditToolDefinition,
|
|
7454
|
-
createFindToolDefinition,
|
|
7602
|
+
createEditToolDefinition as createEditToolDefinition2,
|
|
7603
|
+
createFindToolDefinition as createFindToolDefinition2,
|
|
7455
7604
|
createGrepToolDefinition as createGrepToolDefinition2,
|
|
7456
|
-
createLsToolDefinition,
|
|
7457
|
-
createReadToolDefinition,
|
|
7458
|
-
createWriteToolDefinition
|
|
7605
|
+
createLsToolDefinition as createLsToolDefinition2,
|
|
7606
|
+
createReadToolDefinition as createReadToolDefinition2,
|
|
7607
|
+
createWriteToolDefinition as createWriteToolDefinition2
|
|
7459
7608
|
} from "@mariozechner/pi-coding-agent";
|
|
7460
7609
|
|
|
7461
7610
|
// src/server/tools/operations/bound.ts
|
|
@@ -7596,7 +7745,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7596
7745
|
}
|
|
7597
7746
|
return absolutePath;
|
|
7598
7747
|
};
|
|
7599
|
-
const
|
|
7748
|
+
const toRuntimePath = (absolutePath) => {
|
|
7600
7749
|
if (!shouldMapRuntimeRoot || !runtimeRoot) return absolutePath;
|
|
7601
7750
|
const rel = relative10(workspaceRoot, absolutePath);
|
|
7602
7751
|
if (rel === "") return runtimeRoot;
|
|
@@ -7662,7 +7811,7 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7662
7811
|
await assertWithinWorkspace(workspaceRoot, storageCwd);
|
|
7663
7812
|
const matches = [];
|
|
7664
7813
|
await walkMatches(storageCwd, storageCwd, pattern, options.ignore, options.limit, matches);
|
|
7665
|
-
return matches.map(
|
|
7814
|
+
return matches.map(toRuntimePath);
|
|
7666
7815
|
}
|
|
7667
7816
|
};
|
|
7668
7817
|
const grep = {
|
|
@@ -7702,20 +7851,28 @@ function boundFs(workspaceRoot, opts = {}) {
|
|
|
7702
7851
|
return { read, write, edit, find, grep, ls };
|
|
7703
7852
|
}
|
|
7704
7853
|
|
|
7705
|
-
// src/server/tools/
|
|
7854
|
+
// src/server/tools/filesystem/remoteWorkspaceTools.ts
|
|
7855
|
+
import {
|
|
7856
|
+
createEditToolDefinition,
|
|
7857
|
+
createFindToolDefinition,
|
|
7858
|
+
createLsToolDefinition,
|
|
7859
|
+
createReadToolDefinition,
|
|
7860
|
+
createWriteToolDefinition
|
|
7861
|
+
} from "@mariozechner/pi-coding-agent";
|
|
7862
|
+
|
|
7863
|
+
// src/server/tools/operations/remoteWorkspace.ts
|
|
7706
7864
|
import { isAbsolute as isAbsolute8, relative as relative11 } from "path";
|
|
7707
|
-
|
|
7708
|
-
|
|
7709
|
-
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
return Array.from(new Set(aliases));
|
|
7865
|
+
function unique(values) {
|
|
7866
|
+
return Array.from(new Set(values));
|
|
7867
|
+
}
|
|
7868
|
+
function rootsFor(workspace, opts = {}) {
|
|
7869
|
+
return unique([workspace.root, ...opts.rootAliases ?? []]);
|
|
7713
7870
|
}
|
|
7714
7871
|
function isOutsideWorkspaceRel(rel) {
|
|
7715
7872
|
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || isAbsolute8(rel);
|
|
7716
7873
|
}
|
|
7717
|
-
function toRelPath(workspace, absolutePath) {
|
|
7718
|
-
for (const root of
|
|
7874
|
+
function toRelPath(workspace, absolutePath, opts = {}) {
|
|
7875
|
+
for (const root of rootsFor(workspace, opts)) {
|
|
7719
7876
|
const rel = relative11(root, absolutePath);
|
|
7720
7877
|
if (!isOutsideWorkspaceRel(rel)) return rel;
|
|
7721
7878
|
}
|
|
@@ -7732,113 +7889,92 @@ function toRelPath(workspace, absolutePath) {
|
|
|
7732
7889
|
`path "${absolutePath}" is outside workspace; use a path relative to the workspace root or under ${workspace.root}`
|
|
7733
7890
|
);
|
|
7734
7891
|
}
|
|
7735
|
-
function
|
|
7736
|
-
return {
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
7741
|
-
|
|
7742
|
-
|
|
7743
|
-
|
|
7744
|
-
|
|
7745
|
-
|
|
7746
|
-
|
|
7747
|
-
}).then((result) => ({ exitCode: result.exitCode }));
|
|
7748
|
-
}
|
|
7749
|
-
};
|
|
7892
|
+
function shellEscape(s) {
|
|
7893
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
7894
|
+
}
|
|
7895
|
+
function findPredicate(pattern) {
|
|
7896
|
+
const isPathShaped = pattern.includes("/") || pattern.includes("**");
|
|
7897
|
+
if (!isPathShaped) return `-name ${shellEscape(pattern)}`;
|
|
7898
|
+
let translated = pattern.replaceAll("**", "*").replace(/^\/+/, "");
|
|
7899
|
+
if (!translated.startsWith("*")) translated = `*${translated}`;
|
|
7900
|
+
return `-path ${shellEscape(translated)}`;
|
|
7901
|
+
}
|
|
7902
|
+
function findIgnoreArgs(cwd, ignore) {
|
|
7903
|
+
return ignore.map((pattern) => `! -path ${shellEscape(`${cwd}/${pattern.replace(/^\/+/, "")}/*`)}`).join(" ");
|
|
7750
7904
|
}
|
|
7751
|
-
function
|
|
7905
|
+
function fallbackFindCommand(pattern, cwd, options) {
|
|
7906
|
+
return [
|
|
7907
|
+
"find",
|
|
7908
|
+
shellEscape(cwd),
|
|
7909
|
+
"-maxdepth 20",
|
|
7910
|
+
"-type f",
|
|
7911
|
+
findIgnoreArgs(cwd, options.ignore),
|
|
7912
|
+
findPredicate(pattern),
|
|
7913
|
+
`| head -n ${Math.max(1, Math.trunc(options.limit))}`
|
|
7914
|
+
].filter(Boolean).join(" ");
|
|
7915
|
+
}
|
|
7916
|
+
function isFdMissing(result) {
|
|
7917
|
+
if (result.exitCode !== 127) return false;
|
|
7918
|
+
const stderr = Buffer.from(result.stderr).toString("utf-8");
|
|
7919
|
+
return /\bfd: (?:not found|command not found)\b/i.test(stderr);
|
|
7920
|
+
}
|
|
7921
|
+
function remotePath(value, opts) {
|
|
7922
|
+
return opts.toRemotePath?.(value) ?? value;
|
|
7923
|
+
}
|
|
7924
|
+
function runtimePath(value, opts) {
|
|
7925
|
+
return opts.toRuntimePath?.(value) ?? value;
|
|
7926
|
+
}
|
|
7927
|
+
function sanitizeErrorText(value, opts) {
|
|
7928
|
+
return opts.sanitizeErrorText?.(value) ?? value;
|
|
7929
|
+
}
|
|
7930
|
+
function remoteWorkspaceReadOps(workspace, opts = {}) {
|
|
7752
7931
|
return {
|
|
7753
7932
|
async readFile(absolutePath) {
|
|
7754
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7933
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7755
7934
|
const content = await workspace.readFile(rel);
|
|
7756
7935
|
return Buffer.from(content, "utf-8");
|
|
7757
7936
|
},
|
|
7758
7937
|
async access(absolutePath) {
|
|
7759
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7938
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7760
7939
|
await workspace.stat(rel);
|
|
7761
7940
|
}
|
|
7762
7941
|
};
|
|
7763
7942
|
}
|
|
7764
|
-
function
|
|
7943
|
+
function remoteWorkspaceWriteOps(workspace, opts = {}) {
|
|
7765
7944
|
return {
|
|
7766
7945
|
async writeFile(absolutePath, content) {
|
|
7767
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7946
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7768
7947
|
await workspace.writeFile(rel, content);
|
|
7769
7948
|
},
|
|
7770
7949
|
async mkdir(dir) {
|
|
7771
|
-
const rel = toRelPath(workspace, dir);
|
|
7950
|
+
const rel = toRelPath(workspace, dir, opts);
|
|
7772
7951
|
await workspace.mkdir(rel, { recursive: true });
|
|
7773
7952
|
}
|
|
7774
7953
|
};
|
|
7775
7954
|
}
|
|
7776
|
-
function
|
|
7955
|
+
function remoteWorkspaceEditOps(workspace, opts = {}) {
|
|
7777
7956
|
return {
|
|
7778
7957
|
async readFile(absolutePath) {
|
|
7779
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7958
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7780
7959
|
const content = await workspace.readFile(rel);
|
|
7781
7960
|
return Buffer.from(content, "utf-8");
|
|
7782
7961
|
},
|
|
7783
7962
|
async writeFile(absolutePath, content) {
|
|
7784
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7963
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7785
7964
|
await workspace.writeFile(rel, content);
|
|
7786
7965
|
},
|
|
7787
7966
|
async access(absolutePath) {
|
|
7788
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7967
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7789
7968
|
await workspace.stat(rel);
|
|
7790
7969
|
}
|
|
7791
7970
|
};
|
|
7792
7971
|
}
|
|
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) {
|
|
7972
|
+
function remoteWorkspaceFindOps(sandbox, workspace, opts = {}) {
|
|
7837
7973
|
return {
|
|
7838
7974
|
async exists(absolutePath) {
|
|
7839
7975
|
if (workspace) {
|
|
7840
7976
|
try {
|
|
7841
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7977
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7842
7978
|
await workspace.stat(rel);
|
|
7843
7979
|
return true;
|
|
7844
7980
|
} catch {
|
|
@@ -7851,11 +7987,9 @@ function vercelFindOps(sandbox, workspace) {
|
|
|
7851
7987
|
return result.exitCode === 0;
|
|
7852
7988
|
},
|
|
7853
7989
|
async glob(pattern, cwd, options) {
|
|
7854
|
-
const remoteCwd =
|
|
7990
|
+
const remoteCwd = remotePath(cwd, opts);
|
|
7855
7991
|
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
|
-
}
|
|
7992
|
+
for (const ig of options.ignore) args.push("--exclude", ig);
|
|
7859
7993
|
args.push(pattern, remoteCwd);
|
|
7860
7994
|
let result = await sandbox.exec(args.map(shellEscape).join(" "), {
|
|
7861
7995
|
timeoutMs: 3e4,
|
|
@@ -7868,19 +8002,19 @@ function vercelFindOps(sandbox, workspace) {
|
|
|
7868
8002
|
});
|
|
7869
8003
|
}
|
|
7870
8004
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
7871
|
-
const stderr =
|
|
8005
|
+
const stderr = sanitizeErrorText(Buffer.from(result.stderr).toString("utf-8").trim(), opts);
|
|
7872
8006
|
throw new Error(`file search failed (exit ${result.exitCode}): ${stderr}`);
|
|
7873
8007
|
}
|
|
7874
8008
|
const stdout = Buffer.from(result.stdout).toString("utf-8");
|
|
7875
|
-
return stdout.split("\n").filter(Boolean).map(
|
|
8009
|
+
return stdout.split("\n").filter(Boolean).map((value) => runtimePath(value, opts));
|
|
7876
8010
|
}
|
|
7877
8011
|
};
|
|
7878
8012
|
}
|
|
7879
|
-
function
|
|
8013
|
+
function remoteWorkspaceLsOps(workspace, opts = {}) {
|
|
7880
8014
|
return {
|
|
7881
8015
|
async exists(absolutePath) {
|
|
7882
8016
|
try {
|
|
7883
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
8017
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
7884
8018
|
await workspace.stat(rel);
|
|
7885
8019
|
return true;
|
|
7886
8020
|
} catch {
|
|
@@ -7888,22 +8022,18 @@ function vercelLsOps(workspace) {
|
|
|
7888
8022
|
}
|
|
7889
8023
|
},
|
|
7890
8024
|
async stat(absolutePath) {
|
|
7891
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7892
|
-
const
|
|
7893
|
-
return { isDirectory: () =>
|
|
8025
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
8026
|
+
const stat11 = await workspace.stat(rel);
|
|
8027
|
+
return { isDirectory: () => stat11.kind === "dir" };
|
|
7894
8028
|
},
|
|
7895
8029
|
async readdir(absolutePath) {
|
|
7896
|
-
const rel = toRelPath(workspace, absolutePath);
|
|
7897
|
-
|
|
7898
|
-
return entries.map((e) => e.name);
|
|
8030
|
+
const rel = toRelPath(workspace, absolutePath, opts);
|
|
8031
|
+
return (await workspace.readdir(rel)).map((entry) => entry.name);
|
|
7899
8032
|
}
|
|
7900
8033
|
};
|
|
7901
8034
|
}
|
|
7902
|
-
function shellEscape(arg) {
|
|
7903
|
-
return `'${arg.replace(/'/g, "'\\''")}'`;
|
|
7904
|
-
}
|
|
7905
8035
|
|
|
7906
|
-
// src/server/tools/
|
|
8036
|
+
// src/server/tools/remoteWorkspaceGrepTool.ts
|
|
7907
8037
|
import {
|
|
7908
8038
|
createGrepToolDefinition,
|
|
7909
8039
|
formatSize,
|
|
@@ -7927,7 +8057,7 @@ function decode(bytes) {
|
|
|
7927
8057
|
return decoder.decode(bytes);
|
|
7928
8058
|
}
|
|
7929
8059
|
|
|
7930
|
-
// src/server/tools/
|
|
8060
|
+
// src/server/tools/remoteWorkspaceGrepTool.ts
|
|
7931
8061
|
var PI_GREP_TOOL = createGrepToolDefinition("/");
|
|
7932
8062
|
var DEFAULT_LIMIT2 = 100;
|
|
7933
8063
|
var GREP_MAX_LINE_LENGTH = 500;
|
|
@@ -7943,7 +8073,7 @@ function numberParam(value, fallback, min = 1) {
|
|
|
7943
8073
|
function optionalStringParam(value) {
|
|
7944
8074
|
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
7945
8075
|
}
|
|
7946
|
-
function buildRgCommand(params) {
|
|
8076
|
+
function buildRgCommand(params, searchPath) {
|
|
7947
8077
|
const args = ["--json", "--line-number", "--color=never", "--hidden"];
|
|
7948
8078
|
if (params.ignoreCase === true) args.push("--ignore-case");
|
|
7949
8079
|
if (params.literal === true) args.push("--fixed-strings");
|
|
@@ -7952,11 +8082,10 @@ function buildRgCommand(params) {
|
|
|
7952
8082
|
const context = numberParam(params.context, 0, 0);
|
|
7953
8083
|
if (context > 0) args.push("--context", String(context));
|
|
7954
8084
|
const pattern = params.pattern;
|
|
7955
|
-
const searchPath = optionalStringParam(params.path) ?? ".";
|
|
7956
8085
|
args.push("--", quoteShell(pattern), quoteShell(searchPath));
|
|
7957
8086
|
return `rg ${args.join(" ")}`;
|
|
7958
8087
|
}
|
|
7959
|
-
function parseRgJson(stdout, limit) {
|
|
8088
|
+
function parseRgJson(stdout, limit, pathOptions) {
|
|
7960
8089
|
const outputLines = [];
|
|
7961
8090
|
let matchCount = 0;
|
|
7962
8091
|
let matchLimitReached = false;
|
|
@@ -7989,7 +8118,7 @@ function parseRgJson(stdout, limit) {
|
|
|
7989
8118
|
const { text, wasTruncated } = truncateLine(sanitized);
|
|
7990
8119
|
if (wasTruncated) linesTruncated = true;
|
|
7991
8120
|
const separator = isMatch ? ":" : "-";
|
|
7992
|
-
outputLines.push(`${filePath}${separator}${lineNumber}${separator} ${text}`);
|
|
8121
|
+
outputLines.push(`${pathOptions?.toRuntimePath?.(filePath) ?? filePath}${separator}${lineNumber}${separator} ${text}`);
|
|
7993
8122
|
}
|
|
7994
8123
|
return { outputLines, matchCount, matchLimitReached, linesTruncated };
|
|
7995
8124
|
}
|
|
@@ -8010,6 +8139,29 @@ function syntheticExecTruncation(output) {
|
|
|
8010
8139
|
maxBytes: GREP_MAX_OUTPUT_BYTES
|
|
8011
8140
|
};
|
|
8012
8141
|
}
|
|
8142
|
+
function isOutsideWorkspace(rel) {
|
|
8143
|
+
return rel === ".." || rel.startsWith("../") || rel.startsWith("..\\") || rel.startsWith("/");
|
|
8144
|
+
}
|
|
8145
|
+
function isUnderRoot(root, path4) {
|
|
8146
|
+
const rel = relative12(root, path4);
|
|
8147
|
+
return !isOutsideWorkspace(rel);
|
|
8148
|
+
}
|
|
8149
|
+
function normalizeSearchPath(rawPath, workspaceRoot, pathOptions) {
|
|
8150
|
+
const path4 = optionalStringParam(rawPath) ?? ".";
|
|
8151
|
+
if (!workspaceRoot) return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
|
|
8152
|
+
if (!path4.startsWith("/")) {
|
|
8153
|
+
const resolved = resolve10(workspaceRoot, path4);
|
|
8154
|
+
const rel = relative12(workspaceRoot, resolved);
|
|
8155
|
+
if (isOutsideWorkspace(rel)) return { ok: false, message: `path "${path4}" is outside workspace` };
|
|
8156
|
+
return { ok: true, path: path4 };
|
|
8157
|
+
}
|
|
8158
|
+
const roots = [workspaceRoot, ...pathOptions?.rootAliases ?? []];
|
|
8159
|
+
if (!roots.some((root) => isUnderRoot(root, path4))) return { ok: false, message: `path "${path4}" is outside workspace` };
|
|
8160
|
+
return { ok: true, path: pathOptions?.toRemotePath?.(path4) ?? path4 };
|
|
8161
|
+
}
|
|
8162
|
+
function sanitizeErrorMessage(message, pathOptions) {
|
|
8163
|
+
return pathOptions?.sanitizeErrorText?.(message) ?? message;
|
|
8164
|
+
}
|
|
8013
8165
|
function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
|
|
8014
8166
|
if (parsed.matchCount === 0) {
|
|
8015
8167
|
return {
|
|
@@ -8047,7 +8199,7 @@ function buildSuccessResult(parsed, effectiveLimit, sandboxTruncated) {
|
|
|
8047
8199
|
details: Object.keys(details).length > 0 ? details : void 0
|
|
8048
8200
|
};
|
|
8049
8201
|
}
|
|
8050
|
-
function
|
|
8202
|
+
function remoteWorkspaceGrepTool(sandbox, workspaceRoot, pathOptions) {
|
|
8051
8203
|
return {
|
|
8052
8204
|
name: PI_GREP_TOOL.name,
|
|
8053
8205
|
description: PI_GREP_TOOL.description,
|
|
@@ -8061,36 +8213,31 @@ function vercelGrepTool(sandbox, workspaceRoot) {
|
|
|
8061
8213
|
if (typeof params.pattern !== "string" || params.pattern.length === 0) {
|
|
8062
8214
|
return makeError("pattern is required");
|
|
8063
8215
|
}
|
|
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
|
-
}
|
|
8216
|
+
const searchPath = normalizeSearchPath(params.path, workspaceRoot, pathOptions);
|
|
8217
|
+
if (!searchPath.ok) return makeError(searchPath.message);
|
|
8071
8218
|
try {
|
|
8072
|
-
const result = await sandbox.exec(buildRgCommand(params), {
|
|
8219
|
+
const result = await sandbox.exec(buildRgCommand(params, searchPath.path), {
|
|
8073
8220
|
signal: ctx.abortSignal,
|
|
8074
8221
|
timeoutMs: GREP_TIMEOUT_MS,
|
|
8075
8222
|
maxOutputBytes: GREP_MAX_OUTPUT_BYTES
|
|
8076
8223
|
});
|
|
8077
8224
|
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
8078
|
-
const stderr = decode(result.stderr).trim();
|
|
8225
|
+
const stderr = sanitizeErrorMessage(decode(result.stderr).trim(), pathOptions);
|
|
8079
8226
|
const message = stderr || `ripgrep exited with code ${result.exitCode}`;
|
|
8080
8227
|
return makeError(`grep failed: ${message}`);
|
|
8081
8228
|
}
|
|
8082
8229
|
const limit = numberParam(params.limit, DEFAULT_LIMIT2);
|
|
8083
|
-
const parsed = parseRgJson(decode(result.stdout), limit);
|
|
8230
|
+
const parsed = parseRgJson(decode(result.stdout), limit, pathOptions);
|
|
8084
8231
|
return buildSuccessResult(parsed, limit, result.truncated);
|
|
8085
8232
|
} catch (error) {
|
|
8086
|
-
const message = error instanceof Error ? error.message : "unknown error";
|
|
8233
|
+
const message = error instanceof Error ? sanitizeErrorMessage(error.message, pathOptions) : "unknown error";
|
|
8087
8234
|
return makeError(`grep failed: ${message}`);
|
|
8088
8235
|
}
|
|
8089
8236
|
}
|
|
8090
8237
|
};
|
|
8091
8238
|
}
|
|
8092
8239
|
|
|
8093
|
-
// src/server/tools/filesystem/
|
|
8240
|
+
// src/server/tools/filesystem/remoteWorkspaceTools.ts
|
|
8094
8241
|
function isTextContent(content) {
|
|
8095
8242
|
return content.type === "text" && typeof content.text === "string";
|
|
8096
8243
|
}
|
|
@@ -8121,35 +8268,73 @@ function adaptPiTool(piTool) {
|
|
|
8121
8268
|
}
|
|
8122
8269
|
};
|
|
8123
8270
|
}
|
|
8271
|
+
function buildRemoteWorkspaceFilesystemAgentTools(bundle, pathOptions) {
|
|
8272
|
+
const cwd = bundle.workspace.root;
|
|
8273
|
+
return [
|
|
8274
|
+
adaptPiTool(createReadToolDefinition(cwd, { operations: remoteWorkspaceReadOps(bundle.workspace, pathOptions) })),
|
|
8275
|
+
adaptPiTool(createWriteToolDefinition(cwd, { operations: remoteWorkspaceWriteOps(bundle.workspace, pathOptions) })),
|
|
8276
|
+
adaptPiTool(createEditToolDefinition(cwd, { operations: remoteWorkspaceEditOps(bundle.workspace, pathOptions) })),
|
|
8277
|
+
adaptPiTool(createFindToolDefinition(cwd, { operations: remoteWorkspaceFindOps(bundle.sandbox, bundle.workspace, pathOptions) })),
|
|
8278
|
+
{ ...remoteWorkspaceGrepTool(bundle.sandbox, cwd, pathOptions), readinessRequirements: ["workspace-fs"] },
|
|
8279
|
+
adaptPiTool(createLsToolDefinition(cwd, { operations: remoteWorkspaceLsOps(bundle.workspace, pathOptions) }))
|
|
8280
|
+
];
|
|
8281
|
+
}
|
|
8282
|
+
|
|
8283
|
+
// src/server/tools/filesystem/index.ts
|
|
8284
|
+
function isTextContent2(content) {
|
|
8285
|
+
return content.type === "text" && typeof content.text === "string";
|
|
8286
|
+
}
|
|
8287
|
+
function adaptPiTool2(piTool) {
|
|
8288
|
+
return {
|
|
8289
|
+
name: piTool.name,
|
|
8290
|
+
readinessRequirements: ["workspace-fs"],
|
|
8291
|
+
description: piTool.description,
|
|
8292
|
+
promptSnippet: piTool.promptSnippet,
|
|
8293
|
+
parameters: piTool.parameters,
|
|
8294
|
+
async execute(params, ctx) {
|
|
8295
|
+
const result = await piTool.execute(
|
|
8296
|
+
ctx.toolCallId,
|
|
8297
|
+
params,
|
|
8298
|
+
ctx.abortSignal,
|
|
8299
|
+
ctx.onUpdate ? (update) => {
|
|
8300
|
+
const text = update.content?.filter(isTextContent2).map((c) => c.text).join("");
|
|
8301
|
+
if (text) ctx.onUpdate?.(text);
|
|
8302
|
+
} : void 0,
|
|
8303
|
+
{}
|
|
8304
|
+
);
|
|
8305
|
+
const textContent = (result.content ?? []).filter(isTextContent2).map((c) => ({ type: "text", text: c.text }));
|
|
8306
|
+
return {
|
|
8307
|
+
content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
|
|
8308
|
+
isError: false,
|
|
8309
|
+
details: result.details
|
|
8310
|
+
};
|
|
8311
|
+
}
|
|
8312
|
+
};
|
|
8313
|
+
}
|
|
8314
|
+
function defaultFilesystemStrategyForBundle(bundle) {
|
|
8315
|
+
return bundle.sandbox.placement === "remote" ? { kind: "remote-workspace" } : { kind: "host" };
|
|
8316
|
+
}
|
|
8124
8317
|
function buildFilesystemAgentTools(bundle) {
|
|
8125
8318
|
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
|
-
];
|
|
8319
|
+
const strategy = bundle.filesystem ?? defaultFilesystemStrategyForBundle(bundle);
|
|
8320
|
+
if (strategy.kind === "remote-workspace") {
|
|
8321
|
+
return buildRemoteWorkspaceFilesystemAgentTools(bundle, strategy.pathOptions);
|
|
8135
8322
|
}
|
|
8136
8323
|
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8137
8324
|
const ops = boundFs(storageRoot, { runtimeRoot: cwd });
|
|
8138
8325
|
return [
|
|
8139
|
-
|
|
8140
|
-
|
|
8141
|
-
|
|
8142
|
-
|
|
8143
|
-
|
|
8144
|
-
|
|
8326
|
+
adaptPiTool2(createReadToolDefinition2(cwd, { operations: ops.read })),
|
|
8327
|
+
adaptPiTool2(createWriteToolDefinition2(cwd, { operations: ops.write })),
|
|
8328
|
+
adaptPiTool2(createEditToolDefinition2(cwd, { operations: ops.edit })),
|
|
8329
|
+
adaptPiTool2(createFindToolDefinition2(cwd, { operations: ops.find })),
|
|
8330
|
+
adaptPiTool2(createGrepToolDefinition2(cwd, { operations: ops.grep })),
|
|
8331
|
+
adaptPiTool2(createLsToolDefinition2(cwd, { operations: ops.ls }))
|
|
8145
8332
|
];
|
|
8146
8333
|
}
|
|
8147
8334
|
|
|
8148
8335
|
// src/server/tools/harness/index.ts
|
|
8149
|
-
import {
|
|
8150
|
-
|
|
8151
|
-
createLocalBashOperations
|
|
8152
|
-
} from "@mariozechner/pi-coding-agent";
|
|
8336
|
+
import { unlink as unlink4 } from "fs/promises";
|
|
8337
|
+
import { createBashToolDefinition } from "@mariozechner/pi-coding-agent";
|
|
8153
8338
|
|
|
8154
8339
|
// src/server/catalog/toolReadiness.ts
|
|
8155
8340
|
var WORKSPACE_PREPARING_MESSAGE = "Workspace is still preparing. Try again in a moment.";
|
|
@@ -8227,11 +8412,13 @@ function wrapToolForReadiness(tool, checkReadiness) {
|
|
|
8227
8412
|
};
|
|
8228
8413
|
}
|
|
8229
8414
|
|
|
8230
|
-
// src/server/tools/harness/
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
}
|
|
8234
|
-
|
|
8415
|
+
// src/server/tools/harness/bashToolOptions.ts
|
|
8416
|
+
import {
|
|
8417
|
+
createLocalBashOperations
|
|
8418
|
+
} from "@mariozechner/pi-coding-agent";
|
|
8419
|
+
|
|
8420
|
+
// src/server/runtime/env.ts
|
|
8421
|
+
function mergeRuntimeProvisioningEnv(runtime, commandEnv) {
|
|
8235
8422
|
const current = runtime?.getCurrent?.() ?? runtime;
|
|
8236
8423
|
if (!current?.env && !current?.pathEntries?.length) return commandEnv;
|
|
8237
8424
|
const merged = {
|
|
@@ -8244,7 +8431,38 @@ function mergeRuntimeEnv(runtime, commandEnv) {
|
|
|
8244
8431
|
if (pathParts.length > 0) merged.PATH = pathParts.join(":");
|
|
8245
8432
|
return merged;
|
|
8246
8433
|
}
|
|
8247
|
-
|
|
8434
|
+
|
|
8435
|
+
// src/server/tools/operations/remoteSandbox.ts
|
|
8436
|
+
var REMOTE_SAFE_DEFAULT_PATH = "/usr/local/bin:/usr/bin:/bin";
|
|
8437
|
+
function mergeRemoteBashRuntimeEnv(runtime, executionRuntimeEnv, defaultPath) {
|
|
8438
|
+
const { PATH: executionPath, ...executionEnv } = executionRuntimeEnv ?? {};
|
|
8439
|
+
return mergeRuntimeProvisioningEnv(runtime, {
|
|
8440
|
+
...executionEnv,
|
|
8441
|
+
PATH: executionPath ? `${executionPath}:${defaultPath}` : defaultPath
|
|
8442
|
+
});
|
|
8443
|
+
}
|
|
8444
|
+
function remoteSandboxBashOps(sandbox, opts = {}) {
|
|
8445
|
+
return {
|
|
8446
|
+
exec(command, cwd, { onData, signal, timeout, env }) {
|
|
8447
|
+
const effectiveEnv = opts.mergeEnv ? opts.mergeEnv(env) : opts.runtime || opts.executionRuntimeEnv ? mergeRemoteBashRuntimeEnv(opts.runtime, opts.executionRuntimeEnv, opts.defaultPath ?? REMOTE_SAFE_DEFAULT_PATH) : env;
|
|
8448
|
+
const filteredEnv2 = effectiveEnv ? Object.fromEntries(Object.entries(effectiveEnv).filter((e) => e[1] != null)) : void 0;
|
|
8449
|
+
return sandbox.exec(command, {
|
|
8450
|
+
cwd,
|
|
8451
|
+
env: filteredEnv2,
|
|
8452
|
+
signal,
|
|
8453
|
+
timeoutMs: timeout ? timeout * 1e3 : void 0,
|
|
8454
|
+
onStdout: (chunk) => onData(Buffer.from(chunk)),
|
|
8455
|
+
onStderr: (chunk) => onData(Buffer.from(chunk))
|
|
8456
|
+
}).then((result) => ({ exitCode: result.exitCode }));
|
|
8457
|
+
}
|
|
8458
|
+
};
|
|
8459
|
+
}
|
|
8460
|
+
|
|
8461
|
+
// src/server/tools/harness/bashToolOptions.ts
|
|
8462
|
+
function shellEscape2(s) {
|
|
8463
|
+
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
8464
|
+
}
|
|
8465
|
+
function bwrapSpawnHook(workspaceRoot, runtime, sandboxRoot = "/workspace") {
|
|
8248
8466
|
const args = buildBwrapArgs(workspaceRoot);
|
|
8249
8467
|
const bwrapPrefix = ["bwrap", ...args].map(shellEscape2).join(" ");
|
|
8250
8468
|
return (context) => ({
|
|
@@ -8252,50 +8470,94 @@ function bwrapSpawnHook(workspaceRoot, runtime) {
|
|
|
8252
8470
|
command: `${bwrapPrefix} bash -lc ${shellEscape2(context.command)}`,
|
|
8253
8471
|
env: withWorkspacePythonEnv({
|
|
8254
8472
|
workspaceRoot,
|
|
8255
|
-
env:
|
|
8256
|
-
sandboxRoot
|
|
8473
|
+
env: mergeRuntimeProvisioningEnv(runtime, context.env),
|
|
8474
|
+
sandboxRoot
|
|
8257
8475
|
})
|
|
8258
8476
|
});
|
|
8259
8477
|
}
|
|
8260
|
-
function directSpawnHook(workspaceRoot, runtime) {
|
|
8478
|
+
function directSpawnHook(workspaceRoot, runtime, preserveHostHome = true) {
|
|
8261
8479
|
return (context) => ({
|
|
8262
8480
|
...context,
|
|
8263
8481
|
env: withWorkspacePythonEnv({
|
|
8264
8482
|
workspaceRoot,
|
|
8265
|
-
env:
|
|
8266
|
-
preserveHostHome
|
|
8483
|
+
env: mergeRuntimeProvisioningEnv(runtime, context.env),
|
|
8484
|
+
preserveHostHome
|
|
8267
8485
|
})
|
|
8268
8486
|
});
|
|
8269
8487
|
}
|
|
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
|
-
};
|
|
8488
|
+
function localBashOperationsWithRuntimeEnv(bundle) {
|
|
8489
|
+
const local = createLocalBashOperations();
|
|
8490
|
+
return {
|
|
8491
|
+
async exec(command, cwd, options) {
|
|
8492
|
+
const runtimeEnv = await bundle.getRuntimeEnv?.();
|
|
8493
|
+
return local.exec(command, cwd, {
|
|
8494
|
+
...options,
|
|
8495
|
+
env: { ...options.env ?? {}, ...runtimeEnv ?? {} }
|
|
8496
|
+
});
|
|
8296
8497
|
}
|
|
8498
|
+
};
|
|
8499
|
+
}
|
|
8500
|
+
function hostBashToolOptions(bundle, runtime, strategy) {
|
|
8501
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8502
|
+
return {
|
|
8503
|
+
operations: localBashOperationsWithRuntimeEnv(bundle),
|
|
8504
|
+
spawnHook: directSpawnHook(storageRoot, runtime, strategy.preserveHostHome ?? true)
|
|
8505
|
+
};
|
|
8506
|
+
}
|
|
8507
|
+
function localSandboxBashToolOptions(bundle, runtime, strategy) {
|
|
8508
|
+
const storageRoot = getRuntimeBundleStorageRoot(bundle);
|
|
8509
|
+
return {
|
|
8510
|
+
// localBashOperationsWithRuntimeEnv() injects bundle.getRuntimeEnv()
|
|
8511
|
+
// into the outer shell env before the spawned sandbox shell command runs,
|
|
8512
|
+
// so bridge runtime env reaches local sandboxed commands without relying
|
|
8513
|
+
// on provisioning PATH/env alone.
|
|
8514
|
+
operations: localBashOperationsWithRuntimeEnv(bundle),
|
|
8515
|
+
spawnHook: bwrapSpawnHook(storageRoot, runtime, strategy.sandboxRoot)
|
|
8516
|
+
};
|
|
8517
|
+
}
|
|
8518
|
+
function remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy) {
|
|
8519
|
+
return {
|
|
8520
|
+
operations: remoteSandboxBashOps(bundle.sandbox, {
|
|
8521
|
+
defaultPath: strategy.defaultPath,
|
|
8522
|
+
runtime,
|
|
8523
|
+
executionRuntimeEnv
|
|
8524
|
+
})
|
|
8525
|
+
};
|
|
8526
|
+
}
|
|
8527
|
+
function defaultBashStrategyForBundle(bundle) {
|
|
8528
|
+
return bundle.sandbox.placement === "remote" ? { kind: "remote" } : { kind: "host", preserveHostHome: true };
|
|
8529
|
+
}
|
|
8530
|
+
function createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv) {
|
|
8531
|
+
const strategy = bundle.bash ?? defaultBashStrategyForBundle(bundle);
|
|
8532
|
+
switch (strategy.kind) {
|
|
8533
|
+
case "host":
|
|
8534
|
+
return hostBashToolOptions(bundle, runtime, strategy);
|
|
8535
|
+
case "local-sandbox":
|
|
8536
|
+
return localSandboxBashToolOptions(bundle, runtime, strategy);
|
|
8537
|
+
case "remote":
|
|
8538
|
+
return remoteBashToolOptions(bundle, runtime, executionRuntimeEnv, strategy);
|
|
8297
8539
|
}
|
|
8298
8540
|
}
|
|
8541
|
+
|
|
8542
|
+
// src/server/tools/harness/index.ts
|
|
8543
|
+
function bashOptionsForBundle(bundle, runtime, executionRuntimeEnv) {
|
|
8544
|
+
return createBashToolOptionsForRuntime(bundle, runtime, executionRuntimeEnv);
|
|
8545
|
+
}
|
|
8546
|
+
function runtimeSecretValues(env) {
|
|
8547
|
+
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);
|
|
8548
|
+
}
|
|
8549
|
+
function redactSecretsInString(text, secrets) {
|
|
8550
|
+
return secrets.reduce((current, secret) => current.split(secret).join("[REDACTED]"), text);
|
|
8551
|
+
}
|
|
8552
|
+
function redactSecrets(value, secrets) {
|
|
8553
|
+
if (secrets.length === 0) return value;
|
|
8554
|
+
if (typeof value === "string") return redactSecretsInString(value, secrets);
|
|
8555
|
+
if (Array.isArray(value)) return value.map((item) => redactSecrets(item, secrets));
|
|
8556
|
+
if (!value || typeof value !== "object") return value;
|
|
8557
|
+
return Object.fromEntries(
|
|
8558
|
+
Object.entries(value).map(([key, child]) => [key, redactSecrets(child, secrets)])
|
|
8559
|
+
);
|
|
8560
|
+
}
|
|
8299
8561
|
function isRuntimeReady(readiness) {
|
|
8300
8562
|
return readiness === void 0 || readiness === true || typeof readiness === "object" && readiness !== null && readiness.ready === true;
|
|
8301
8563
|
}
|
|
@@ -8313,23 +8575,38 @@ function runtimeRequirementForFailure(text) {
|
|
|
8313
8575
|
if (/(?:^|\n)(?:[^\n:]+:\s*)?(?:line \d+:\s*)?[A-Za-z0-9_.-]+: command not found/i.test(text)) return "runtime-dependencies";
|
|
8314
8576
|
return void 0;
|
|
8315
8577
|
}
|
|
8316
|
-
function
|
|
8578
|
+
function runtimeNotReadyFromState(requirement, readiness) {
|
|
8579
|
+
return runtimeNotReadyToolResult(
|
|
8580
|
+
requirement,
|
|
8581
|
+
readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
|
|
8582
|
+
);
|
|
8583
|
+
}
|
|
8584
|
+
function adaptPiTool3(bundle, runtime) {
|
|
8585
|
+
const template = createBashToolDefinition(bundle.workspace.root, bashOptionsForBundle(bundle, runtime));
|
|
8317
8586
|
return {
|
|
8318
|
-
name:
|
|
8319
|
-
description:
|
|
8320
|
-
promptSnippet:
|
|
8321
|
-
parameters:
|
|
8587
|
+
name: template.name,
|
|
8588
|
+
description: template.description,
|
|
8589
|
+
promptSnippet: template.promptSnippet,
|
|
8590
|
+
parameters: template.parameters,
|
|
8322
8591
|
readinessRequirements: ["sandbox-exec"],
|
|
8323
8592
|
async execute(params, ctx) {
|
|
8324
8593
|
const command = typeof params.command === "string" ? params.command : "";
|
|
8325
8594
|
const readiness = runtime?.getReadiness?.();
|
|
8326
8595
|
const commandRuntimeRequirement = command ? runtimeRequirementForCommand(command) : void 0;
|
|
8327
8596
|
if (commandRuntimeRequirement && !isRuntimeReady(readiness)) {
|
|
8328
|
-
return
|
|
8329
|
-
commandRuntimeRequirement,
|
|
8330
|
-
readiness && typeof readiness === "object" && readiness.ready === false ? readiness : { ready: false, state: "preparing", retryable: true }
|
|
8331
|
-
);
|
|
8597
|
+
return runtimeNotReadyFromState(commandRuntimeRequirement, readiness);
|
|
8332
8598
|
}
|
|
8599
|
+
const runtimeEnv = await bundle.getRuntimeEnv?.();
|
|
8600
|
+
const secrets = runtimeSecretValues(runtimeEnv);
|
|
8601
|
+
const executionBundle = runtimeEnv === void 0 ? bundle : {
|
|
8602
|
+
...bundle,
|
|
8603
|
+
getRuntimeEnv: async () => runtimeEnv
|
|
8604
|
+
};
|
|
8605
|
+
const piTool = createBashToolDefinition(
|
|
8606
|
+
bundle.workspace.root,
|
|
8607
|
+
bashOptionsForBundle(executionBundle, runtime, runtimeEnv)
|
|
8608
|
+
);
|
|
8609
|
+
let emittedRedactionNotice = false;
|
|
8333
8610
|
let result;
|
|
8334
8611
|
try {
|
|
8335
8612
|
result = await piTool.execute(
|
|
@@ -8338,7 +8615,14 @@ function adaptPiTool2(piTool, runtime) {
|
|
|
8338
8615
|
ctx.abortSignal,
|
|
8339
8616
|
ctx.onUpdate ? (update) => {
|
|
8340
8617
|
const text2 = update.content.filter((c) => c.type === "text").map((c) => c.text).join("");
|
|
8341
|
-
|
|
8618
|
+
if (secrets.length === 0) {
|
|
8619
|
+
ctx.onUpdate(text2);
|
|
8620
|
+
return;
|
|
8621
|
+
}
|
|
8622
|
+
if (!emittedRedactionNotice) {
|
|
8623
|
+
emittedRedactionNotice = true;
|
|
8624
|
+
ctx.onUpdate("[streaming output redacted while runtime secrets are in scope]");
|
|
8625
|
+
}
|
|
8342
8626
|
} : void 0,
|
|
8343
8627
|
{}
|
|
8344
8628
|
);
|
|
@@ -8347,27 +8631,29 @@ function adaptPiTool2(piTool, runtime) {
|
|
|
8347
8631
|
const latestReadiness2 = runtime?.getReadiness?.();
|
|
8348
8632
|
const failureRuntimeRequirement2 = runtimeRequirementForFailure(message);
|
|
8349
8633
|
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
|
-
);
|
|
8634
|
+
return runtimeNotReadyFromState(failureRuntimeRequirement2, latestReadiness2);
|
|
8354
8635
|
}
|
|
8355
|
-
|
|
8636
|
+
return {
|
|
8637
|
+
content: [{ type: "text", text: redactSecretsInString(message, secrets) }],
|
|
8638
|
+
isError: true,
|
|
8639
|
+
details: {}
|
|
8640
|
+
};
|
|
8356
8641
|
}
|
|
8357
|
-
|
|
8642
|
+
if (secrets.length > 0 && result.details && typeof result.details === "object" && "fullOutputPath" in result.details && typeof result.details.fullOutputPath === "string") {
|
|
8643
|
+
await unlink4(result.details.fullOutputPath).catch(() => void 0);
|
|
8644
|
+
delete result.details.fullOutputPath;
|
|
8645
|
+
}
|
|
8646
|
+
const textContent = (result.content ?? []).filter((c) => c.type === "text").map((c) => ({ type: "text", text: redactSecretsInString(c.text, secrets) }));
|
|
8358
8647
|
const text = textContent.map((part) => part.text).join("\n");
|
|
8359
8648
|
const latestReadiness = runtime?.getReadiness?.();
|
|
8360
8649
|
const failureRuntimeRequirement = runtimeRequirementForFailure(text);
|
|
8361
8650
|
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
|
-
);
|
|
8651
|
+
return runtimeNotReadyFromState(failureRuntimeRequirement, latestReadiness);
|
|
8366
8652
|
}
|
|
8367
8653
|
return {
|
|
8368
8654
|
content: textContent.length > 0 ? textContent : [{ type: "text", text: "" }],
|
|
8369
8655
|
isError: Boolean(result.isError),
|
|
8370
|
-
details: result.details
|
|
8656
|
+
details: redactSecrets(result.details, secrets)
|
|
8371
8657
|
};
|
|
8372
8658
|
}
|
|
8373
8659
|
};
|
|
@@ -8427,7 +8713,7 @@ function createExecuteIsolatedCodeTool(sandbox) {
|
|
|
8427
8713
|
}
|
|
8428
8714
|
function buildHarnessAgentTools(bundle, runtime) {
|
|
8429
8715
|
const tools = [
|
|
8430
|
-
|
|
8716
|
+
adaptPiTool3(bundle, runtime)
|
|
8431
8717
|
];
|
|
8432
8718
|
if (bundle.sandbox.capabilities.includes("isolated-code")) {
|
|
8433
8719
|
tools.push(createExecuteIsolatedCodeTool(bundle.sandbox));
|
|
@@ -9727,7 +10013,7 @@ function gitRoutes(app, opts, done) {
|
|
|
9727
10013
|
done();
|
|
9728
10014
|
}
|
|
9729
10015
|
|
|
9730
|
-
// src/server/
|
|
10016
|
+
// src/server/runtime/readyStatus.ts
|
|
9731
10017
|
function defaultCapabilities(sandboxReady, harnessReady) {
|
|
9732
10018
|
return {
|
|
9733
10019
|
chat: { state: harnessReady ? "ready" : "preparing" },
|
|
@@ -9829,6 +10115,21 @@ var ReadyStatusTracker = class {
|
|
|
9829
10115
|
}
|
|
9830
10116
|
};
|
|
9831
10117
|
|
|
10118
|
+
// src/server/runtime/modeReadiness.ts
|
|
10119
|
+
function createRuntimeReadyStatusTracker(modeAdapter, opts) {
|
|
10120
|
+
const readiness = modeAdapter.readiness;
|
|
10121
|
+
const tracker = new ReadyStatusTracker({
|
|
10122
|
+
sandboxReady: readiness?.initialSandboxReady ?? true,
|
|
10123
|
+
harnessReady: opts.harnessReady,
|
|
10124
|
+
capabilities: {
|
|
10125
|
+
...readiness?.initialWorkspaceReadiness ? { workspace: readiness.initialWorkspaceReadiness } : {},
|
|
10126
|
+
...opts.capabilities ?? {}
|
|
10127
|
+
}
|
|
10128
|
+
});
|
|
10129
|
+
readiness?.onTrackerCreated?.(tracker);
|
|
10130
|
+
return tracker;
|
|
10131
|
+
}
|
|
10132
|
+
|
|
9832
10133
|
// src/server/pi-chat/piChatHistory.ts
|
|
9833
10134
|
function isRecord(value) {
|
|
9834
10135
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
@@ -11879,11 +12180,24 @@ async function createAgentApp(opts = {}) {
|
|
|
11879
12180
|
const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
|
|
11880
12181
|
const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
|
|
11881
12182
|
const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode);
|
|
11882
|
-
|
|
12183
|
+
let runtimeBundle = await modeAdapter.create({
|
|
11883
12184
|
workspaceRoot,
|
|
11884
12185
|
sessionId,
|
|
11885
12186
|
templatePath
|
|
11886
12187
|
});
|
|
12188
|
+
if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
|
|
12189
|
+
runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
|
|
12190
|
+
workspaceId: sessionId,
|
|
12191
|
+
workspaceRoot,
|
|
12192
|
+
runtimeMode: resolvedMode,
|
|
12193
|
+
runtimeBundle
|
|
12194
|
+
}, opts.runtimeEnvContributions, opts.telemetry);
|
|
12195
|
+
}
|
|
12196
|
+
await opts.runtimeProvisioner?.({
|
|
12197
|
+
workspaceRoot,
|
|
12198
|
+
runtimeMode: resolvedMode,
|
|
12199
|
+
runtimeBundle
|
|
12200
|
+
});
|
|
11887
12201
|
const pluginTools = [];
|
|
11888
12202
|
const externalPluginsEnabled = opts.externalPlugins !== false;
|
|
11889
12203
|
if (externalPluginsEnabled && modeAdapter.workspaceFsCapability === "strong") {
|
|
@@ -11940,13 +12254,9 @@ async function createAgentApp(opts = {}) {
|
|
|
11940
12254
|
});
|
|
11941
12255
|
harnessRef = harness;
|
|
11942
12256
|
const sessionChangesTracker = new InMemorySessionChangesTracker();
|
|
11943
|
-
const readyTracker =
|
|
11944
|
-
sandboxReady: resolvedMode !== "vercel-sandbox",
|
|
12257
|
+
const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
|
|
11945
12258
|
harnessReady: true
|
|
11946
12259
|
});
|
|
11947
|
-
if (resolvedMode === "vercel-sandbox") {
|
|
11948
|
-
queueMicrotask(() => readyTracker.markSandboxReady());
|
|
11949
|
-
}
|
|
11950
12260
|
app.addHook(
|
|
11951
12261
|
"onRequest",
|
|
11952
12262
|
createAuthMiddleware({
|
|
@@ -11963,10 +12273,7 @@ async function createAgentApp(opts = {}) {
|
|
|
11963
12273
|
await app.register(treeRoutes, { workspace: runtimeBundle.workspace });
|
|
11964
12274
|
await app.register(searchRoutes, { fileSearch: runtimeBundle.fileSearch });
|
|
11965
12275
|
await app.register(gitRoutes, {
|
|
11966
|
-
getWorkspaceRoot: () =>
|
|
11967
|
-
if (runtimeBundle.sandbox.provider === "remote-worker") return void 0;
|
|
11968
|
-
return getRuntimeBundleStorageRoot(runtimeBundle);
|
|
11969
|
-
}
|
|
12276
|
+
getWorkspaceRoot: () => getOptionalRuntimeBundleStorageRoot(runtimeBundle)
|
|
11970
12277
|
});
|
|
11971
12278
|
const piChatService = new HarnessPiChatService({
|
|
11972
12279
|
harness,
|
|
@@ -12148,8 +12455,8 @@ function buildUploadAgentTools(bundle) {
|
|
|
12148
12455
|
const contentType = contentTypeFromExt(filePath);
|
|
12149
12456
|
const ext = extForUpload2(filePath, contentType);
|
|
12150
12457
|
const base = basenameForUpload2(filePath);
|
|
12151
|
-
const
|
|
12152
|
-
const destPath = `${dir}/${base}-${
|
|
12458
|
+
const unique2 = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
12459
|
+
const destPath = `${dir}/${base}-${unique2}.${ext}`;
|
|
12153
12460
|
await workspace.mkdir(dir, { recursive: true });
|
|
12154
12461
|
if (workspace.writeBinaryFile) {
|
|
12155
12462
|
await workspace.writeBinaryFile(destPath, bytes);
|
|
@@ -12179,7 +12486,6 @@ function buildUploadAgentTools(bundle) {
|
|
|
12179
12486
|
var DEFAULT_VERSION2 = "0.1.0-dev";
|
|
12180
12487
|
var DEFAULT_WORKSPACE_ID3 = "default";
|
|
12181
12488
|
var STANDARD_AGENT_TOOL_NAMES = ["bash", "read", "write", "edit", "find", "grep", "ls"];
|
|
12182
|
-
var VERCEL_BINDING_HEALTHCHECK_INTERVAL_MS = 15e3;
|
|
12183
12489
|
function pluginNameFromPath(path4) {
|
|
12184
12490
|
const fileName = basename3(path4);
|
|
12185
12491
|
if (fileName.endsWith(".mjs")) return fileName.slice(0, -4);
|
|
@@ -12198,15 +12504,6 @@ function getAvailableModelProviders() {
|
|
|
12198
12504
|
new Set(availableModels.map((model) => model.provider))
|
|
12199
12505
|
).sort((a, b) => a.localeCompare(b));
|
|
12200
12506
|
}
|
|
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
12507
|
function getRequestWorkspaceId(request) {
|
|
12211
12508
|
return request.workspaceContext?.workspaceId ?? DEFAULT_WORKSPACE_ID3;
|
|
12212
12509
|
}
|
|
@@ -12226,27 +12523,11 @@ function isWorkspaceAgnosticAgentRequest(request, options) {
|
|
|
12226
12523
|
if (pathname === "/api/v1/ready-status") return !options?.readyStatusWorkspaceScoped;
|
|
12227
12524
|
return pathname === "/health" || pathname === "/ready" || pathname === "/api/v1/agent/models";
|
|
12228
12525
|
}
|
|
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
12526
|
function normalizeSessionNamespace(value) {
|
|
12238
12527
|
if (typeof value !== "string") return void 0;
|
|
12239
12528
|
const trimmed = value.trim();
|
|
12240
12529
|
return trimmed.length > 0 ? trimmed : void 0;
|
|
12241
12530
|
}
|
|
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
12531
|
function createHttpError(code, message, details = {}) {
|
|
12251
12532
|
const error = new Error(message);
|
|
12252
12533
|
error.code = code;
|
|
@@ -12301,7 +12582,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12301
12582
|
const sessionId = opts.sessionId ?? DEFAULT_WORKSPACE_ID3;
|
|
12302
12583
|
const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
|
|
12303
12584
|
const resolvedMode = opts.runtimeModeAdapter?.id ?? opts.mode ?? autoDetectMode();
|
|
12304
|
-
const modeAdapter = opts.runtimeModeAdapter ??
|
|
12585
|
+
const modeAdapter = opts.runtimeModeAdapter ?? resolveMode(resolvedMode, { sandboxHandleStore: opts.sandboxHandleStore });
|
|
12305
12586
|
app.addHook("onClose", async () => {
|
|
12306
12587
|
await modeAdapter.dispose?.();
|
|
12307
12588
|
});
|
|
@@ -12373,9 +12654,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12373
12654
|
requestId: request?.id,
|
|
12374
12655
|
telemetry: opts.telemetry
|
|
12375
12656
|
};
|
|
12376
|
-
const runtimeLayout = getBoringAgentRuntimePaths(
|
|
12377
|
-
resolvedMode === "vercel-sandbox" ? VERCEL_SANDBOX_WORKSPACE_ROOT : scope.root
|
|
12378
|
-
);
|
|
12657
|
+
const runtimeLayout = getBoringAgentRuntimePaths(modeAdapter.getRuntimeLayoutRoot?.(modeCtx) ?? scope.root);
|
|
12379
12658
|
return await opts.provisionRuntime({
|
|
12380
12659
|
workspaceId,
|
|
12381
12660
|
workspaceRoot: scope.root,
|
|
@@ -12403,19 +12682,22 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12403
12682
|
retryable: true
|
|
12404
12683
|
} : { state: "ready" };
|
|
12405
12684
|
let provisioningGeneration = 0;
|
|
12406
|
-
|
|
12407
|
-
|
|
12408
|
-
|
|
12685
|
+
let runtimeBundle = await modeAdapter.create(modeCtx);
|
|
12686
|
+
if (opts.runtimeEnvContributions && opts.runtimeEnvContributions.length > 0) {
|
|
12687
|
+
runtimeBundle = withRuntimeEnvContributions(runtimeBundle, {
|
|
12688
|
+
workspaceId,
|
|
12689
|
+
workspaceRoot: root,
|
|
12690
|
+
runtimeMode: resolvedMode,
|
|
12691
|
+
runtimeBundle
|
|
12692
|
+
}, opts.runtimeEnvContributions, opts.telemetry);
|
|
12693
|
+
}
|
|
12694
|
+
const readyTracker = createRuntimeReadyStatusTracker(modeAdapter, {
|
|
12409
12695
|
harnessReady: false,
|
|
12410
12696
|
capabilities: {
|
|
12411
12697
|
chat: { state: "preparing" },
|
|
12412
|
-
workspace: { state: resolvedMode !== "vercel-sandbox" ? "ready" : "preparing" },
|
|
12413
12698
|
runtimeDependencies
|
|
12414
12699
|
}
|
|
12415
12700
|
});
|
|
12416
|
-
if (resolvedMode === "vercel-sandbox") {
|
|
12417
|
-
queueMicrotask(() => readyTracker.markSandboxReady());
|
|
12418
|
-
}
|
|
12419
12701
|
let binding;
|
|
12420
12702
|
const updateRuntimeDependencies = (next) => {
|
|
12421
12703
|
runtimeDependencies = next;
|
|
@@ -12555,6 +12837,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12555
12837
|
tools,
|
|
12556
12838
|
cwd: root,
|
|
12557
12839
|
sessionNamespace: scope.sessionNamespace,
|
|
12840
|
+
sessionRoot: opts.sessionRoot,
|
|
12558
12841
|
systemPromptAppend: opts.systemPromptAppend,
|
|
12559
12842
|
systemPromptDynamic: opts.getSystemPromptDynamic ? () => opts.getSystemPromptDynamic?.({ workspaceId, workspaceRoot: root }) : opts.systemPromptDynamic,
|
|
12560
12843
|
telemetry: opts.telemetry
|
|
@@ -12633,7 +12916,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12633
12916
|
}
|
|
12634
12917
|
async function recreateRuntimeBinding(workspaceId, scope) {
|
|
12635
12918
|
runtimeBindings.delete(scope.key);
|
|
12636
|
-
|
|
12919
|
+
modeAdapter.evictCachedRuntime?.({ workspaceId });
|
|
12637
12920
|
const created = createRuntimeBindingEntry(workspaceId, scope);
|
|
12638
12921
|
runtimeBindings.set(scope.key, created);
|
|
12639
12922
|
evictRuntimeBindings();
|
|
@@ -12647,23 +12930,23 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12647
12930
|
}
|
|
12648
12931
|
}
|
|
12649
12932
|
async function ensureRuntimeBindingReady(workspaceId, scope, binding) {
|
|
12650
|
-
|
|
12933
|
+
const healthCheck = modeAdapter.cachedBindingHealthCheck;
|
|
12934
|
+
if (!healthCheck) return binding;
|
|
12651
12935
|
const now = Date.now();
|
|
12652
|
-
|
|
12936
|
+
const intervalMs = healthCheck.intervalMs ?? 15e3;
|
|
12937
|
+
if (binding.lastHealthCheckMs !== void 0 && now - binding.lastHealthCheckMs < intervalMs) {
|
|
12653
12938
|
return binding;
|
|
12654
12939
|
}
|
|
12655
|
-
|
|
12656
|
-
|
|
12940
|
+
const result = await healthCheck.check({ runtimeBundle: binding.runtimeBundle, workspaceId });
|
|
12941
|
+
if (result.state === "ok") {
|
|
12657
12942
|
binding.lastHealthCheckMs = now;
|
|
12658
12943
|
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
12944
|
}
|
|
12945
|
+
app.log.warn({
|
|
12946
|
+
err: result.error,
|
|
12947
|
+
workspaceId
|
|
12948
|
+
}, result.message ?? "[runtime] cached runtime invalid; recreating");
|
|
12949
|
+
return await recreateRuntimeBinding(workspaceId, scope);
|
|
12667
12950
|
}
|
|
12668
12951
|
const hasRuntimeProvisioningInput = opts.provisionWorkspace !== false && Boolean(opts.provisionRuntime);
|
|
12669
12952
|
const staticBinding = requestScopedRuntime ? null : await getOrCreateRuntimeBinding(sessionId);
|
|
@@ -12688,6 +12971,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12688
12971
|
if (cached) return cached;
|
|
12689
12972
|
const store = new PiSessionStore(scope.root, {
|
|
12690
12973
|
sessionNamespace: scope.sessionNamespace,
|
|
12974
|
+
sessionRoot: opts.sessionRoot,
|
|
12691
12975
|
storageCwd: scope.root
|
|
12692
12976
|
});
|
|
12693
12977
|
earlySessionStores.set(scope.key, store);
|
|
@@ -12761,11 +13045,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
12761
13045
|
getFileSearch: async (request) => (await getBindingForRequest(request)).runtimeBundle.fileSearch
|
|
12762
13046
|
});
|
|
12763
13047
|
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
|
-
}
|
|
13048
|
+
getWorkspaceRoot: async (request) => getOptionalRuntimeBundleStorageRoot((await getBindingForRequest(request)).runtimeBundle)
|
|
12769
13049
|
});
|
|
12770
13050
|
await app.register(piChatRoutes, {
|
|
12771
13051
|
getService: async (request) => {
|