@hachej/boring-agent 0.1.6 → 0.1.7
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/front/index.d.ts +26 -3
- package/dist/front/index.js +290 -51
- package/dist/front/styles.css +24 -0
- package/dist/{harness-CMiJ4kok.d.ts → harness-DpMCKbzh.d.ts} +20 -0
- package/dist/{sandbox-handle-store-pNB_27Sd.d.ts → sandbox-handle-store-DCNEJJ6b.d.ts} +2 -0
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +436 -120
- package/dist/shared/index.d.ts +3 -3
- package/package.json +2 -2
package/dist/server/index.js
CHANGED
|
@@ -1389,6 +1389,10 @@ function createNodeWorkspace(root) {
|
|
|
1389
1389
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
1390
1390
|
return await readFile3(absPath, "utf-8");
|
|
1391
1391
|
},
|
|
1392
|
+
async readBinaryFile(relPath) {
|
|
1393
|
+
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
1394
|
+
return new Uint8Array(await readFile3(absPath));
|
|
1395
|
+
},
|
|
1392
1396
|
async writeFile(relPath, data) {
|
|
1393
1397
|
const absPath = await ensureWritableWorkspacePath(root, relPath);
|
|
1394
1398
|
await writeFile3(absPath, data, "utf-8");
|
|
@@ -1842,6 +1846,20 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
1842
1846
|
}
|
|
1843
1847
|
return Buffer.from(content).toString("utf-8");
|
|
1844
1848
|
},
|
|
1849
|
+
async readBinaryFile(relPath) {
|
|
1850
|
+
const sandboxPath = toSandboxPath(relPath);
|
|
1851
|
+
if (remote.fs?.readFile) {
|
|
1852
|
+
const content2 = await remote.fs.readFile(sandboxPath);
|
|
1853
|
+
return new Uint8Array(Buffer.from(content2));
|
|
1854
|
+
}
|
|
1855
|
+
const content = await remote.readFileToBuffer?.({ path: sandboxPath });
|
|
1856
|
+
if (!content) {
|
|
1857
|
+
const err = new Error(`ENOENT: file not found, open '${sandboxPath}'`);
|
|
1858
|
+
err.code = "ENOENT";
|
|
1859
|
+
throw err;
|
|
1860
|
+
}
|
|
1861
|
+
return new Uint8Array(Buffer.from(content));
|
|
1862
|
+
},
|
|
1845
1863
|
async writeFile(relPath, data) {
|
|
1846
1864
|
const sandboxPath = toSandboxPath(relPath);
|
|
1847
1865
|
await sandbox.writeFiles([
|
|
@@ -2509,6 +2527,13 @@ async function seedTemplateIntoVercelWorkspace(workspace, templatePath, logger)
|
|
|
2509
2527
|
fileCount: files.length
|
|
2510
2528
|
});
|
|
2511
2529
|
}
|
|
2530
|
+
async function ensureTemplateExecutables(sandbox) {
|
|
2531
|
+
if (!sandbox.runCommand) return;
|
|
2532
|
+
await sandbox.runCommand({
|
|
2533
|
+
cmd: "sh",
|
|
2534
|
+
args: ["-c", `chmod +x ${VERCEL_SANDBOX_REMOTE_ROOT}/.boring-agent/bin/* 2>/dev/null || true`]
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2512
2537
|
var DEFAULT_MODE_LOGGER = {
|
|
2513
2538
|
info(message, metadata) {
|
|
2514
2539
|
process.stderr.write(`${message} ${JSON.stringify(metadata)}
|
|
@@ -2627,6 +2652,7 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
2627
2652
|
});
|
|
2628
2653
|
}
|
|
2629
2654
|
await seedTemplateIntoVercelWorkspace(workspace, ctx.templatePath, logger);
|
|
2655
|
+
await ensureTemplateExecutables(sandboxHandle);
|
|
2630
2656
|
}
|
|
2631
2657
|
const sandbox = createVercelSandboxExec(sandboxHandle, {
|
|
2632
2658
|
onMutation: markDirty
|
|
@@ -2679,6 +2705,8 @@ import Fastify from "fastify";
|
|
|
2679
2705
|
import { basename as basename2 } from "path";
|
|
2680
2706
|
|
|
2681
2707
|
// src/server/harness/pi-coding-agent/createHarness.ts
|
|
2708
|
+
import { mkdir as mkdir9, writeFile as writeFile7 } from "fs/promises";
|
|
2709
|
+
import { extname, join as join6 } from "path";
|
|
2682
2710
|
import {
|
|
2683
2711
|
createAgentSession,
|
|
2684
2712
|
SessionManager,
|
|
@@ -2690,6 +2718,86 @@ import {
|
|
|
2690
2718
|
loadSkills
|
|
2691
2719
|
} from "@mariozechner/pi-coding-agent";
|
|
2692
2720
|
|
|
2721
|
+
// src/server/logging.ts
|
|
2722
|
+
var SENSITIVE_KEYS = new Set([
|
|
2723
|
+
"apiKey",
|
|
2724
|
+
"api_key",
|
|
2725
|
+
"token",
|
|
2726
|
+
"secret",
|
|
2727
|
+
"password",
|
|
2728
|
+
"authorization",
|
|
2729
|
+
"cookie",
|
|
2730
|
+
"oidcToken",
|
|
2731
|
+
"accessToken",
|
|
2732
|
+
"refreshToken",
|
|
2733
|
+
"ANTHROPIC_API_KEY",
|
|
2734
|
+
"OPENAI_API_KEY",
|
|
2735
|
+
"VERCEL_OIDC_TOKEN",
|
|
2736
|
+
"VERCEL_TEAM_ID"
|
|
2737
|
+
].map((key) => key.toLowerCase()));
|
|
2738
|
+
function isSensitiveKey(key) {
|
|
2739
|
+
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
2740
|
+
}
|
|
2741
|
+
function redactValue(key, value, seen) {
|
|
2742
|
+
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
2743
|
+
if (value == null || typeof value !== "object") return value;
|
|
2744
|
+
if (value instanceof Date) return value;
|
|
2745
|
+
if (seen.has(value)) return "[Circular]";
|
|
2746
|
+
seen.add(value);
|
|
2747
|
+
if (Array.isArray(value)) {
|
|
2748
|
+
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
2749
|
+
seen.delete(value);
|
|
2750
|
+
return out2;
|
|
2751
|
+
}
|
|
2752
|
+
const out = {};
|
|
2753
|
+
for (const [childKey, childValue] of Object.entries(value)) {
|
|
2754
|
+
out[childKey] = redactValue(childKey, childValue, seen);
|
|
2755
|
+
}
|
|
2756
|
+
seen.delete(value);
|
|
2757
|
+
return out;
|
|
2758
|
+
}
|
|
2759
|
+
function redact(fields) {
|
|
2760
|
+
const out = {};
|
|
2761
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
2762
|
+
for (const [k, v] of Object.entries(fields)) {
|
|
2763
|
+
out[k] = redactValue(k, v, seen);
|
|
2764
|
+
}
|
|
2765
|
+
return out;
|
|
2766
|
+
}
|
|
2767
|
+
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
2768
|
+
function createLogger(prefix) {
|
|
2769
|
+
function emit(level, msg, fields) {
|
|
2770
|
+
const entry = {
|
|
2771
|
+
level,
|
|
2772
|
+
prefix,
|
|
2773
|
+
msg,
|
|
2774
|
+
...fields ? redact(fields) : {},
|
|
2775
|
+
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
2776
|
+
};
|
|
2777
|
+
if (level === "error") {
|
|
2778
|
+
console.error(JSON.stringify(entry));
|
|
2779
|
+
} else if (level === "warn") {
|
|
2780
|
+
console.warn(JSON.stringify(entry));
|
|
2781
|
+
} else {
|
|
2782
|
+
console.log(JSON.stringify(entry));
|
|
2783
|
+
}
|
|
2784
|
+
}
|
|
2785
|
+
return {
|
|
2786
|
+
debug(msg, fields) {
|
|
2787
|
+
if (verbose) emit("debug", msg, fields);
|
|
2788
|
+
},
|
|
2789
|
+
info(msg, fields) {
|
|
2790
|
+
emit("info", msg, fields);
|
|
2791
|
+
},
|
|
2792
|
+
warn(msg, fields) {
|
|
2793
|
+
emit("warn", msg, fields);
|
|
2794
|
+
},
|
|
2795
|
+
error(msg, fields) {
|
|
2796
|
+
emit("error", msg, fields);
|
|
2797
|
+
}
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2693
2801
|
// src/server/harness/pi-coding-agent/tool-adapter.ts
|
|
2694
2802
|
function adaptToolForPi(tool) {
|
|
2695
2803
|
return {
|
|
@@ -3254,86 +3362,6 @@ function piMessagesToUIMessages(messages) {
|
|
|
3254
3362
|
return result;
|
|
3255
3363
|
}
|
|
3256
3364
|
|
|
3257
|
-
// src/server/logging.ts
|
|
3258
|
-
var SENSITIVE_KEYS = new Set([
|
|
3259
|
-
"apiKey",
|
|
3260
|
-
"api_key",
|
|
3261
|
-
"token",
|
|
3262
|
-
"secret",
|
|
3263
|
-
"password",
|
|
3264
|
-
"authorization",
|
|
3265
|
-
"cookie",
|
|
3266
|
-
"oidcToken",
|
|
3267
|
-
"accessToken",
|
|
3268
|
-
"refreshToken",
|
|
3269
|
-
"ANTHROPIC_API_KEY",
|
|
3270
|
-
"OPENAI_API_KEY",
|
|
3271
|
-
"VERCEL_OIDC_TOKEN",
|
|
3272
|
-
"VERCEL_TEAM_ID"
|
|
3273
|
-
].map((key) => key.toLowerCase()));
|
|
3274
|
-
function isSensitiveKey(key) {
|
|
3275
|
-
return SENSITIVE_KEYS.has(key.toLowerCase());
|
|
3276
|
-
}
|
|
3277
|
-
function redactValue(key, value, seen) {
|
|
3278
|
-
if (key && isSensitiveKey(key) && value != null) return "***";
|
|
3279
|
-
if (value == null || typeof value !== "object") return value;
|
|
3280
|
-
if (value instanceof Date) return value;
|
|
3281
|
-
if (seen.has(value)) return "[Circular]";
|
|
3282
|
-
seen.add(value);
|
|
3283
|
-
if (Array.isArray(value)) {
|
|
3284
|
-
const out2 = value.map((item) => redactValue(void 0, item, seen));
|
|
3285
|
-
seen.delete(value);
|
|
3286
|
-
return out2;
|
|
3287
|
-
}
|
|
3288
|
-
const out = {};
|
|
3289
|
-
for (const [childKey, childValue] of Object.entries(value)) {
|
|
3290
|
-
out[childKey] = redactValue(childKey, childValue, seen);
|
|
3291
|
-
}
|
|
3292
|
-
seen.delete(value);
|
|
3293
|
-
return out;
|
|
3294
|
-
}
|
|
3295
|
-
function redact(fields) {
|
|
3296
|
-
const out = {};
|
|
3297
|
-
const seen = /* @__PURE__ */ new WeakSet();
|
|
3298
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
3299
|
-
out[k] = redactValue(k, v, seen);
|
|
3300
|
-
}
|
|
3301
|
-
return out;
|
|
3302
|
-
}
|
|
3303
|
-
var verbose = typeof process !== "undefined" && process.env?.BORING_AGENT_VERBOSE === "1";
|
|
3304
|
-
function createLogger(prefix) {
|
|
3305
|
-
function emit(level, msg, fields) {
|
|
3306
|
-
const entry = {
|
|
3307
|
-
level,
|
|
3308
|
-
prefix,
|
|
3309
|
-
msg,
|
|
3310
|
-
...fields ? redact(fields) : {},
|
|
3311
|
-
t: (/* @__PURE__ */ new Date()).toISOString()
|
|
3312
|
-
};
|
|
3313
|
-
if (level === "error") {
|
|
3314
|
-
console.error(JSON.stringify(entry));
|
|
3315
|
-
} else if (level === "warn") {
|
|
3316
|
-
console.warn(JSON.stringify(entry));
|
|
3317
|
-
} else {
|
|
3318
|
-
console.log(JSON.stringify(entry));
|
|
3319
|
-
}
|
|
3320
|
-
}
|
|
3321
|
-
return {
|
|
3322
|
-
debug(msg, fields) {
|
|
3323
|
-
if (verbose) emit("debug", msg, fields);
|
|
3324
|
-
},
|
|
3325
|
-
info(msg, fields) {
|
|
3326
|
-
emit("info", msg, fields);
|
|
3327
|
-
},
|
|
3328
|
-
warn(msg, fields) {
|
|
3329
|
-
emit("warn", msg, fields);
|
|
3330
|
-
},
|
|
3331
|
-
error(msg, fields) {
|
|
3332
|
-
emit("error", msg, fields);
|
|
3333
|
-
}
|
|
3334
|
-
};
|
|
3335
|
-
}
|
|
3336
|
-
|
|
3337
3365
|
// src/server/harness/pi-coding-agent/sessionTitle.ts
|
|
3338
3366
|
var DEFAULT_SESSION_TITLE = "New session";
|
|
3339
3367
|
var FALLBACK_PREFIX = "New chat";
|
|
@@ -3732,6 +3760,24 @@ async function applyRequestedSessionOptions(handle, input) {
|
|
|
3732
3760
|
handle.piSession.setThinkingLevel(input.thinkingLevel);
|
|
3733
3761
|
}
|
|
3734
3762
|
}
|
|
3763
|
+
var log2 = createLogger("pi-harness");
|
|
3764
|
+
var DEFAULT_ATTACHMENT_DIR = "assets/images";
|
|
3765
|
+
function extForAttachment(filename, contentType) {
|
|
3766
|
+
const fromName = extname(filename).toLowerCase().replace(/^\./, "");
|
|
3767
|
+
if (/^[a-z0-9]{1,12}$/.test(fromName)) return fromName;
|
|
3768
|
+
if (contentType === "image/jpeg") return "jpg";
|
|
3769
|
+
if (contentType === "image/png") return "png";
|
|
3770
|
+
if (contentType === "image/gif") return "gif";
|
|
3771
|
+
if (contentType === "image/webp") return "webp";
|
|
3772
|
+
if (contentType === "image/svg+xml") return "svg";
|
|
3773
|
+
return "bin";
|
|
3774
|
+
}
|
|
3775
|
+
function basenameForAttachment(filename) {
|
|
3776
|
+
const base = filename.split("/").pop()?.split("\\").pop() ?? "image";
|
|
3777
|
+
const withoutExt = base.replace(/\.[^.]*$/, "");
|
|
3778
|
+
const safe = withoutExt.normalize("NFKD").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
3779
|
+
return safe || "image";
|
|
3780
|
+
}
|
|
3735
3781
|
function createPiCodingAgentHarness(opts) {
|
|
3736
3782
|
const sessionStore = new PiSessionStore(opts.cwd);
|
|
3737
3783
|
const piSessions = /* @__PURE__ */ new Map();
|
|
@@ -3828,10 +3874,17 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3828
3874
|
await originalDelete(ctx, sessionId);
|
|
3829
3875
|
disposePiSession(sessionId);
|
|
3830
3876
|
};
|
|
3877
|
+
const harnessFollowUpQueues = /* @__PURE__ */ new Map();
|
|
3831
3878
|
return {
|
|
3832
3879
|
id: "pi-coding-agent",
|
|
3833
3880
|
placement: "server",
|
|
3834
3881
|
sessions: sessionStore,
|
|
3882
|
+
followUp(sessionId, text, attachments) {
|
|
3883
|
+
harnessFollowUpQueues.set(sessionId, { message: text, attachments });
|
|
3884
|
+
},
|
|
3885
|
+
clearFollowUp(sessionId) {
|
|
3886
|
+
harnessFollowUpQueues.delete(sessionId);
|
|
3887
|
+
},
|
|
3835
3888
|
/**
|
|
3836
3889
|
* Pi exposes the resolved system prompt as a getter on AgentSession.
|
|
3837
3890
|
* Sessions are created lazily on first sendMessage, so callers may see
|
|
@@ -3847,6 +3900,7 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3847
3900
|
input,
|
|
3848
3901
|
ctx
|
|
3849
3902
|
);
|
|
3903
|
+
harnessFollowUpQueues.delete(input.sessionId);
|
|
3850
3904
|
const chunks = [];
|
|
3851
3905
|
let done = false;
|
|
3852
3906
|
let streamError = null;
|
|
@@ -3895,6 +3949,20 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3895
3949
|
return out;
|
|
3896
3950
|
}
|
|
3897
3951
|
let sawTextChunk = false;
|
|
3952
|
+
let inlineTurnIndex = 0;
|
|
3953
|
+
function namespaceInlinePartIds(input2) {
|
|
3954
|
+
if (inlineTurnIndex === 0) return input2;
|
|
3955
|
+
return input2.map((chunk2) => {
|
|
3956
|
+
const rec = chunk2;
|
|
3957
|
+
if ((rec.type === "text-start" || rec.type === "text-delta" || rec.type === "text-end" || rec.type === "reasoning-start" || rec.type === "reasoning-delta" || rec.type === "reasoning-end") && typeof rec.id === "string") {
|
|
3958
|
+
return { ...rec, id: `turn-${inlineTurnIndex}:${rec.id}` };
|
|
3959
|
+
}
|
|
3960
|
+
return chunk2;
|
|
3961
|
+
});
|
|
3962
|
+
}
|
|
3963
|
+
let pendingFollowUp;
|
|
3964
|
+
let promptSettled = false;
|
|
3965
|
+
let promptPromise = Promise.resolve();
|
|
3898
3966
|
const unsubscribe = piSession.subscribe((event) => {
|
|
3899
3967
|
if (event.type === "message_update") {
|
|
3900
3968
|
const ame = event.assistantMessageEvent;
|
|
@@ -3914,11 +3982,11 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3914
3982
|
activeTools.delete(event.toolCallId);
|
|
3915
3983
|
if (activeTools.size === 0) stopHeartbeat();
|
|
3916
3984
|
}
|
|
3917
|
-
let converted = dedupStartChunks(piEventToChunks(event));
|
|
3985
|
+
let converted = namespaceInlinePartIds(dedupStartChunks(piEventToChunks(event)));
|
|
3918
3986
|
if (event.type === "message_update") {
|
|
3919
3987
|
const ame = event.assistantMessageEvent;
|
|
3920
3988
|
if (ame.type === "text_end" && typeof ame.content === "string" && ame.content.length > 0 && !textDeltaSeen.has(ame.contentIndex)) {
|
|
3921
|
-
const id = String(ame.contentIndex)
|
|
3989
|
+
const id = inlineTurnIndex === 0 ? String(ame.contentIndex) : `turn-${inlineTurnIndex}:${ame.contentIndex}`;
|
|
3922
3990
|
converted = [
|
|
3923
3991
|
...textStartSeen.has(ame.contentIndex) ? [] : [{ type: "text-start", id }],
|
|
3924
3992
|
{ type: "text-delta", id, delta: ame.content },
|
|
@@ -3943,10 +4011,11 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3943
4011
|
chunks.push({ type: "error", errorText });
|
|
3944
4012
|
sawTextChunk = true;
|
|
3945
4013
|
} else if (role === "assistant" && text.length > 0) {
|
|
4014
|
+
const id = inlineTurnIndex === 0 ? "0" : `turn-${inlineTurnIndex}:0`;
|
|
3946
4015
|
chunks.push(
|
|
3947
|
-
{ type: "text-start", id
|
|
3948
|
-
{ type: "text-delta", id
|
|
3949
|
-
{ type: "text-end", id
|
|
4016
|
+
{ type: "text-start", id },
|
|
4017
|
+
{ type: "text-delta", id, delta: text },
|
|
4018
|
+
{ type: "text-end", id }
|
|
3950
4019
|
);
|
|
3951
4020
|
sawTextChunk = true;
|
|
3952
4021
|
assistantText += text;
|
|
@@ -3963,30 +4032,95 @@ function createPiCodingAgentHarness(opts) {
|
|
|
3963
4032
|
chunks.push({ type: "error", errorText });
|
|
3964
4033
|
sawTextChunk = true;
|
|
3965
4034
|
} else if (role === "assistant" && text.length > 0) {
|
|
4035
|
+
const id = inlineTurnIndex === 0 ? "0" : `turn-${inlineTurnIndex}:0`;
|
|
3966
4036
|
chunks.push(
|
|
3967
|
-
{ type: "text-start", id
|
|
3968
|
-
{ type: "text-delta", id
|
|
3969
|
-
{ type: "text-end", id
|
|
4037
|
+
{ type: "text-start", id },
|
|
4038
|
+
{ type: "text-delta", id, delta: text },
|
|
4039
|
+
{ type: "text-end", id }
|
|
3970
4040
|
);
|
|
3971
4041
|
sawTextChunk = true;
|
|
3972
4042
|
assistantText += text;
|
|
3973
4043
|
}
|
|
3974
4044
|
}
|
|
3975
|
-
|
|
3976
|
-
|
|
4045
|
+
const followUp = harnessFollowUpQueues.get(input.sessionId);
|
|
4046
|
+
if (followUp !== void 0 && !ctx.abortSignal.aborted) {
|
|
4047
|
+
harnessFollowUpQueues.delete(input.sessionId);
|
|
4048
|
+
chunks.push({ type: "data-followup-consumed" });
|
|
4049
|
+
inlineTurnIndex += 1;
|
|
4050
|
+
sawTextChunk = false;
|
|
4051
|
+
textStartSeen.clear();
|
|
4052
|
+
textDeltaSeen.clear();
|
|
4053
|
+
assistantText = "";
|
|
4054
|
+
pendingFollowUp = followUp;
|
|
4055
|
+
} else {
|
|
4056
|
+
done = true;
|
|
4057
|
+
stopHeartbeat();
|
|
4058
|
+
}
|
|
3977
4059
|
}
|
|
3978
4060
|
if (wake) wake();
|
|
3979
4061
|
});
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4062
|
+
async function prepareTurn(message, attachments) {
|
|
4063
|
+
const initialImages = [];
|
|
4064
|
+
const savedPaths = [];
|
|
4065
|
+
const writeErrors = [];
|
|
4066
|
+
for (const a of attachments ?? []) {
|
|
4067
|
+
const match = a.url.match(/^data:(image\/[^;]+);base64,(.+)$/);
|
|
4068
|
+
if (!match) continue;
|
|
4069
|
+
const [, contentType, b64] = match;
|
|
4070
|
+
initialImages.push({ type: "image", mimeType: contentType, data: b64 });
|
|
4071
|
+
if (!ctx.workdir) {
|
|
4072
|
+
writeErrors.push(`${a.filename ?? "image"}: no workdir`);
|
|
4073
|
+
continue;
|
|
4074
|
+
}
|
|
4075
|
+
try {
|
|
4076
|
+
const bytes = Buffer.from(b64, "base64");
|
|
4077
|
+
const ext = extForAttachment(a.filename ?? "image", contentType);
|
|
4078
|
+
const base = basenameForAttachment(a.filename ?? "image");
|
|
4079
|
+
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
4080
|
+
const relPath = `${DEFAULT_ATTACHMENT_DIR}/${base}-${unique}.${ext}`;
|
|
4081
|
+
await mkdir9(join6(ctx.workdir, DEFAULT_ATTACHMENT_DIR), { recursive: true });
|
|
4082
|
+
await writeFile7(join6(ctx.workdir, relPath), bytes);
|
|
4083
|
+
savedPaths.push(relPath);
|
|
4084
|
+
} catch (err) {
|
|
4085
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4086
|
+
log2.error("attachment write failed", { workdir: ctx.workdir, error: msg });
|
|
4087
|
+
writeErrors.push(`${a.filename ?? "image"}: ${msg}`);
|
|
4088
|
+
}
|
|
4089
|
+
}
|
|
4090
|
+
const attachmentNotes = [];
|
|
4091
|
+
if (savedPaths.length > 0) {
|
|
4092
|
+
attachmentNotes.push(`[Attached file(s) saved to workspace:
|
|
4093
|
+
${savedPaths.map((p) => `- ${p}`).join("\n")}]`);
|
|
4094
|
+
}
|
|
4095
|
+
if (writeErrors.length > 0) {
|
|
4096
|
+
attachmentNotes.push(`[Warning: failed to save attachment(s) to workspace \u2014 ${writeErrors.join("; ")}. The image is available via vision only.]`);
|
|
4097
|
+
}
|
|
4098
|
+
return {
|
|
4099
|
+
message: attachmentNotes.length > 0 ? `${message}
|
|
4100
|
+
|
|
4101
|
+
${attachmentNotes.join("\n")}` : message,
|
|
4102
|
+
promptOpts: initialImages.length > 0 ? { images: initialImages } : void 0
|
|
4103
|
+
};
|
|
4104
|
+
}
|
|
4105
|
+
async function startTurn(message, attachments) {
|
|
4106
|
+
promptSettled = false;
|
|
4107
|
+
const prepared = await prepareTurn(message, attachments);
|
|
4108
|
+
return piSession.prompt(prepared.message, prepared.promptOpts).then(() => {
|
|
4109
|
+
promptSettled = true;
|
|
4110
|
+
if (pendingFollowUp !== void 0 && !ctx.abortSignal.aborted) {
|
|
4111
|
+
const next = pendingFollowUp;
|
|
4112
|
+
pendingFollowUp = void 0;
|
|
4113
|
+
promptPromise = startTurn(next.message, next.attachments);
|
|
4114
|
+
}
|
|
4115
|
+
}).catch((err) => {
|
|
4116
|
+
promptSettled = true;
|
|
4117
|
+
streamError = err;
|
|
4118
|
+
done = true;
|
|
4119
|
+
stopHeartbeat();
|
|
4120
|
+
if (wake) wake();
|
|
4121
|
+
});
|
|
4122
|
+
}
|
|
4123
|
+
promptPromise = startTurn(input.message, input.attachments);
|
|
3990
4124
|
const onAbort = () => {
|
|
3991
4125
|
if (promptSettled) {
|
|
3992
4126
|
streamError = new Error("Aborted");
|
|
@@ -4034,11 +4168,11 @@ function createPiCodingAgentHarness(opts) {
|
|
|
4034
4168
|
|
|
4035
4169
|
// src/server/harness/pi-coding-agent/pluginLoader.ts
|
|
4036
4170
|
import { readdir as readdir5, stat as stat5, readFile as readFile7 } from "fs/promises";
|
|
4037
|
-
import { join as
|
|
4171
|
+
import { join as join7, extname as extname2, resolve as resolve4, sep as sep3 } from "path";
|
|
4038
4172
|
import { homedir as homedir4 } from "os";
|
|
4039
4173
|
import { pathToFileURL } from "url";
|
|
4040
4174
|
var VALID_EXTENSIONS = /* @__PURE__ */ new Set([".js", ".mjs"]);
|
|
4041
|
-
var GLOBAL_DIR =
|
|
4175
|
+
var GLOBAL_DIR = join7(homedir4(), ".pi", "agent", "extensions");
|
|
4042
4176
|
var LOCAL_DIR = ".pi/extensions";
|
|
4043
4177
|
var EXTENSIONS_JSON = ".pi/extensions.json";
|
|
4044
4178
|
async function dirExists(path4) {
|
|
@@ -4060,7 +4194,7 @@ async function fileExists2(path4) {
|
|
|
4060
4194
|
async function discoverFromDir(dir, source) {
|
|
4061
4195
|
if (!await dirExists(dir)) return [];
|
|
4062
4196
|
const entries = await readdir5(dir);
|
|
4063
|
-
return entries.filter((e) => VALID_EXTENSIONS.has(
|
|
4197
|
+
return entries.filter((e) => VALID_EXTENSIONS.has(extname2(e))).map((e) => ({ path: join7(dir, e), source }));
|
|
4064
4198
|
}
|
|
4065
4199
|
function extractTools(mod) {
|
|
4066
4200
|
const tools = [];
|
|
@@ -4090,17 +4224,17 @@ async function loadModule(filePath, importFn) {
|
|
|
4090
4224
|
return extractTools(mod);
|
|
4091
4225
|
}
|
|
4092
4226
|
async function discoverNpmPlugins(cwd) {
|
|
4093
|
-
const nodeModulesDir =
|
|
4227
|
+
const nodeModulesDir = join7(cwd, "node_modules");
|
|
4094
4228
|
if (!await dirExists(nodeModulesDir)) return [];
|
|
4095
4229
|
try {
|
|
4096
4230
|
const entries = await readdir5(nodeModulesDir);
|
|
4097
|
-
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) =>
|
|
4231
|
+
return entries.filter((e) => e.startsWith("pi-plugin-")).map((e) => join7(nodeModulesDir, e));
|
|
4098
4232
|
} catch {
|
|
4099
4233
|
return [];
|
|
4100
4234
|
}
|
|
4101
4235
|
}
|
|
4102
4236
|
async function loadNpmPlugin(pkgDir, importFn) {
|
|
4103
|
-
const pkgJsonPath =
|
|
4237
|
+
const pkgJsonPath = join7(pkgDir, "package.json");
|
|
4104
4238
|
if (!await fileExists2(pkgJsonPath)) return [];
|
|
4105
4239
|
const pkgJson = JSON.parse(await readFile7(pkgJsonPath, "utf-8"));
|
|
4106
4240
|
const main = pkgJson.main ?? "index.js";
|
|
@@ -4112,7 +4246,7 @@ async function loadNpmPlugin(pkgDir, importFn) {
|
|
|
4112
4246
|
return loadModule(resolvedMain, importFn);
|
|
4113
4247
|
}
|
|
4114
4248
|
async function loadExtensionsJson(cwd) {
|
|
4115
|
-
const configPath =
|
|
4249
|
+
const configPath = join7(cwd, EXTENSIONS_JSON);
|
|
4116
4250
|
if (!await fileExists2(configPath)) return null;
|
|
4117
4251
|
try {
|
|
4118
4252
|
const raw = await readFile7(configPath, "utf-8");
|
|
@@ -4130,7 +4264,7 @@ async function loadPlugins(options) {
|
|
|
4130
4264
|
const globals = await discoverFromDir(GLOBAL_DIR, "global");
|
|
4131
4265
|
candidates.push(...globals);
|
|
4132
4266
|
}
|
|
4133
|
-
const localDir =
|
|
4267
|
+
const localDir = join7(options.cwd, LOCAL_DIR);
|
|
4134
4268
|
const locals = await discoverFromDir(localDir, "local");
|
|
4135
4269
|
candidates.push(...locals);
|
|
4136
4270
|
const npmDirs = await discoverNpmPlugins(options.cwd);
|
|
@@ -4140,7 +4274,7 @@ async function loadPlugins(options) {
|
|
|
4140
4274
|
const config = await loadExtensionsJson(options.cwd);
|
|
4141
4275
|
if (config?.npm) {
|
|
4142
4276
|
for (const pkg of config.npm) {
|
|
4143
|
-
const pkgDir =
|
|
4277
|
+
const pkgDir = join7(options.cwd, "node_modules", pkg);
|
|
4144
4278
|
if (await dirExists(pkgDir)) {
|
|
4145
4279
|
const already = candidates.some((c) => c.path === pkgDir);
|
|
4146
4280
|
if (!already) {
|
|
@@ -4189,7 +4323,7 @@ import {
|
|
|
4189
4323
|
|
|
4190
4324
|
// src/server/tools/operations/bound.ts
|
|
4191
4325
|
import { constants as constants3 } from "fs";
|
|
4192
|
-
import { access as access3, lstat as lstat3, mkdir as
|
|
4326
|
+
import { access as access3, lstat as lstat3, mkdir as mkdir10, readFile as readFile8, readdir as readdir6, readlink, realpath as realpath2, stat as stat6, writeFile as writeFile8 } from "fs/promises";
|
|
4193
4327
|
import { dirname as dirname6, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve5 } from "path";
|
|
4194
4328
|
function toPosixPath(value) {
|
|
4195
4329
|
return value.split("\\").join("/");
|
|
@@ -4327,12 +4461,12 @@ function boundFs(workspaceRoot) {
|
|
|
4327
4461
|
const write = {
|
|
4328
4462
|
async writeFile(absolutePath, content) {
|
|
4329
4463
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
4330
|
-
await
|
|
4331
|
-
await
|
|
4464
|
+
await mkdir10(dirname6(absolutePath), { recursive: true });
|
|
4465
|
+
await writeFile8(absolutePath, content);
|
|
4332
4466
|
},
|
|
4333
4467
|
async mkdir(dir) {
|
|
4334
4468
|
await assertWithinWorkspace(workspaceRoot, dir);
|
|
4335
|
-
await
|
|
4469
|
+
await mkdir10(dir, { recursive: true });
|
|
4336
4470
|
}
|
|
4337
4471
|
};
|
|
4338
4472
|
const edit = {
|
|
@@ -4342,7 +4476,7 @@ function boundFs(workspaceRoot) {
|
|
|
4342
4476
|
},
|
|
4343
4477
|
async writeFile(absolutePath, content) {
|
|
4344
4478
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
4345
|
-
await
|
|
4479
|
+
await writeFile8(absolutePath, content);
|
|
4346
4480
|
},
|
|
4347
4481
|
async access(absolutePath) {
|
|
4348
4482
|
await assertWithinWorkspace(workspaceRoot, absolutePath);
|
|
@@ -5015,7 +5149,7 @@ function healthRoutes(app, opts, done) {
|
|
|
5015
5149
|
}
|
|
5016
5150
|
|
|
5017
5151
|
// src/server/http/routes/file.ts
|
|
5018
|
-
import { dirname as dirname7, extname as
|
|
5152
|
+
import { dirname as dirname7, extname as extname3, relative as relative7 } from "path/posix";
|
|
5019
5153
|
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
5020
5154
|
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
5021
5155
|
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
@@ -5098,7 +5232,7 @@ function normalizeUploadDir(value) {
|
|
|
5098
5232
|
return dir.replace(/\/+$/, "");
|
|
5099
5233
|
}
|
|
5100
5234
|
function extForUpload(filename, contentType) {
|
|
5101
|
-
const fromName =
|
|
5235
|
+
const fromName = extname3(filename).toLowerCase().replace(/^\./, "");
|
|
5102
5236
|
if (/^[a-z0-9]{1,12}$/.test(fromName)) return fromName;
|
|
5103
5237
|
if (contentType === "image/jpeg") return "jpg";
|
|
5104
5238
|
if (contentType === "image/png") return "png";
|
|
@@ -5119,12 +5253,56 @@ function markdownUrlFor(sourcePath, assetPath) {
|
|
|
5119
5253
|
const rel = relative7(fromDir === "." ? "" : fromDir, assetPath);
|
|
5120
5254
|
return rel && !rel.startsWith(".") ? rel : rel || assetPath;
|
|
5121
5255
|
}
|
|
5256
|
+
function contentTypeForPath(path4) {
|
|
5257
|
+
switch (extname3(path4).toLowerCase()) {
|
|
5258
|
+
case ".avif":
|
|
5259
|
+
return "image/avif";
|
|
5260
|
+
case ".gif":
|
|
5261
|
+
return "image/gif";
|
|
5262
|
+
case ".jpg":
|
|
5263
|
+
case ".jpeg":
|
|
5264
|
+
return "image/jpeg";
|
|
5265
|
+
case ".png":
|
|
5266
|
+
return "image/png";
|
|
5267
|
+
case ".svg":
|
|
5268
|
+
return "image/svg+xml";
|
|
5269
|
+
case ".webp":
|
|
5270
|
+
return "image/webp";
|
|
5271
|
+
case ".pdf":
|
|
5272
|
+
return "application/pdf";
|
|
5273
|
+
default:
|
|
5274
|
+
return "application/octet-stream";
|
|
5275
|
+
}
|
|
5276
|
+
}
|
|
5122
5277
|
function fileRoutes(app, opts, done) {
|
|
5123
5278
|
async function resolveWorkspace(request) {
|
|
5124
5279
|
if (opts.getWorkspace) return await opts.getWorkspace(request);
|
|
5125
5280
|
if (opts.workspace) return opts.workspace;
|
|
5126
5281
|
throw new Error("file route requires workspace or getWorkspace");
|
|
5127
5282
|
}
|
|
5283
|
+
app.get("/api/v1/files/raw", async (request, reply) => {
|
|
5284
|
+
const query = request.query;
|
|
5285
|
+
const path4 = requireStringParam(query.path, "path", reply);
|
|
5286
|
+
if (path4 === null) return;
|
|
5287
|
+
try {
|
|
5288
|
+
const workspace = await resolveWorkspace(request);
|
|
5289
|
+
if (!workspace.readBinaryFile) {
|
|
5290
|
+
return reply.code(501).send({
|
|
5291
|
+
error: { code: ERROR_CODE_INTERNAL, message: "workspace does not support binary reads" }
|
|
5292
|
+
});
|
|
5293
|
+
}
|
|
5294
|
+
const stat7 = await workspace.stat(path4);
|
|
5295
|
+
if (stat7.kind !== "file") {
|
|
5296
|
+
return reply.code(400).send({
|
|
5297
|
+
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "path is not a file", field: "path" }
|
|
5298
|
+
});
|
|
5299
|
+
}
|
|
5300
|
+
const bytes = await workspace.readBinaryFile(path4);
|
|
5301
|
+
return reply.header("content-type", contentTypeForPath(path4)).header("content-length", String(bytes.byteLength)).header("cache-control", "no-store").send(Buffer.from(bytes));
|
|
5302
|
+
} catch (err) {
|
|
5303
|
+
return classifyError(err, reply, "file");
|
|
5304
|
+
}
|
|
5305
|
+
});
|
|
5128
5306
|
app.get("/api/v1/files", async (request, reply) => {
|
|
5129
5307
|
const query = request.query;
|
|
5130
5308
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
@@ -5803,7 +5981,7 @@ function chatRoutes(app, opts, done) {
|
|
|
5803
5981
|
"/api/v1/agent/chat",
|
|
5804
5982
|
{ preHandler: validateBody },
|
|
5805
5983
|
async (request, reply) => {
|
|
5806
|
-
const { sessionId, message, model, thinkingLevel } = request.body;
|
|
5984
|
+
const { sessionId, message, model, thinkingLevel, attachments } = request.body;
|
|
5807
5985
|
const turnId = randomUUID3();
|
|
5808
5986
|
request.log.info({ sessionId, turnId, model, thinkingLevel }, "[chat] start");
|
|
5809
5987
|
const runtime = await resolveRuntime(request);
|
|
@@ -5822,7 +6000,7 @@ function chatRoutes(app, opts, done) {
|
|
|
5822
6000
|
const buf = buffers.create(sessionId, turnId);
|
|
5823
6001
|
try {
|
|
5824
6002
|
const chunks = runtime.harness.sendMessage(
|
|
5825
|
-
{ sessionId, message, model, thinkingLevel },
|
|
6003
|
+
{ sessionId, message, model, thinkingLevel, attachments },
|
|
5826
6004
|
ctx
|
|
5827
6005
|
);
|
|
5828
6006
|
const stream = createUIMessageStream({
|
|
@@ -5954,6 +6132,36 @@ function chatRoutes(app, opts, done) {
|
|
|
5954
6132
|
}
|
|
5955
6133
|
}
|
|
5956
6134
|
);
|
|
6135
|
+
app.post(
|
|
6136
|
+
"/api/v1/agent/chat/:sessionId/followup",
|
|
6137
|
+
async (request, reply) => {
|
|
6138
|
+
const { sessionId } = request.params;
|
|
6139
|
+
const body = request.body;
|
|
6140
|
+
if (typeof body?.message !== "string" || body.message.length === 0) {
|
|
6141
|
+
return reply.code(400).send({
|
|
6142
|
+
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "message is required" }
|
|
6143
|
+
});
|
|
6144
|
+
}
|
|
6145
|
+
const parsedAttachments = chatBodySchema.shape.attachments.safeParse(body.attachments);
|
|
6146
|
+
if (!parsedAttachments.success) {
|
|
6147
|
+
return reply.code(400).send({
|
|
6148
|
+
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "attachments is invalid" }
|
|
6149
|
+
});
|
|
6150
|
+
}
|
|
6151
|
+
const runtime = await resolveRuntime(request);
|
|
6152
|
+
runtime.harness.followUp?.(sessionId, body.message, parsedAttachments.data);
|
|
6153
|
+
return reply.code(202).send({ queued: true });
|
|
6154
|
+
}
|
|
6155
|
+
);
|
|
6156
|
+
app.delete(
|
|
6157
|
+
"/api/v1/agent/chat/:sessionId/followup",
|
|
6158
|
+
async (request, reply) => {
|
|
6159
|
+
const { sessionId } = request.params;
|
|
6160
|
+
const runtime = await resolveRuntime(request);
|
|
6161
|
+
runtime.harness.clearFollowUp?.(sessionId);
|
|
6162
|
+
return reply.code(204).send();
|
|
6163
|
+
}
|
|
6164
|
+
);
|
|
5957
6165
|
app.put(
|
|
5958
6166
|
"/api/v1/agent/chat/:sessionId/messages",
|
|
5959
6167
|
async (request, reply) => {
|
|
@@ -6460,7 +6668,7 @@ async function createAgentApp(opts = {}) {
|
|
|
6460
6668
|
const workspaceRoot = opts.workspaceRoot ?? process.cwd();
|
|
6461
6669
|
const sessionId = opts.sessionId ?? DEFAULT_SESSION_ID;
|
|
6462
6670
|
const templatePath = opts.templatePath ?? getEnv("BORING_AGENT_TEMPLATE_PATH");
|
|
6463
|
-
const app = Fastify({ logger: opts.logger ?? true });
|
|
6671
|
+
const app = Fastify({ logger: opts.logger ?? true, bodyLimit: 16 * 1024 * 1024 });
|
|
6464
6672
|
const resolvedMode = opts.mode ?? autoDetectMode();
|
|
6465
6673
|
const runtimeBundle = await resolveMode(resolvedMode).create({
|
|
6466
6674
|
workspaceRoot,
|
|
@@ -6592,6 +6800,113 @@ function mergeTools(options) {
|
|
|
6592
6800
|
return [...merged.values()];
|
|
6593
6801
|
}
|
|
6594
6802
|
|
|
6803
|
+
// src/server/tools/upload/index.ts
|
|
6804
|
+
import { readFile as readFile9 } from "fs/promises";
|
|
6805
|
+
import { extname as extname4, join as join8 } from "path";
|
|
6806
|
+
var DEFAULT_UPLOAD_DIR = "assets/images";
|
|
6807
|
+
var MAX_UPLOAD_BYTES2 = 10 * 1024 * 1024;
|
|
6808
|
+
function contentTypeFromExt(path4) {
|
|
6809
|
+
switch (extname4(path4).toLowerCase()) {
|
|
6810
|
+
case ".jpg":
|
|
6811
|
+
case ".jpeg":
|
|
6812
|
+
return "image/jpeg";
|
|
6813
|
+
case ".png":
|
|
6814
|
+
return "image/png";
|
|
6815
|
+
case ".gif":
|
|
6816
|
+
return "image/gif";
|
|
6817
|
+
case ".webp":
|
|
6818
|
+
return "image/webp";
|
|
6819
|
+
case ".svg":
|
|
6820
|
+
return "image/svg+xml";
|
|
6821
|
+
case ".avif":
|
|
6822
|
+
return "image/avif";
|
|
6823
|
+
default:
|
|
6824
|
+
return "application/octet-stream";
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
function extForUpload2(filename, contentType) {
|
|
6828
|
+
const fromName = extname4(filename).toLowerCase().replace(/^\./, "");
|
|
6829
|
+
if (/^[a-z0-9]{1,12}$/.test(fromName)) return fromName;
|
|
6830
|
+
if (contentType === "image/jpeg") return "jpg";
|
|
6831
|
+
if (contentType === "image/png") return "png";
|
|
6832
|
+
if (contentType === "image/gif") return "gif";
|
|
6833
|
+
if (contentType === "image/webp") return "webp";
|
|
6834
|
+
if (contentType === "image/svg+xml") return "svg";
|
|
6835
|
+
return "bin";
|
|
6836
|
+
}
|
|
6837
|
+
function basenameForUpload2(filename) {
|
|
6838
|
+
const base = filename.split("/").pop()?.split("\\").pop() ?? "image";
|
|
6839
|
+
const withoutExt = base.replace(/\.[^.]*$/, "");
|
|
6840
|
+
const safe = withoutExt.normalize("NFKD").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
6841
|
+
return safe || "image";
|
|
6842
|
+
}
|
|
6843
|
+
function buildUploadAgentTools(bundle) {
|
|
6844
|
+
const { workspace } = bundle;
|
|
6845
|
+
const cwd = workspace.root;
|
|
6846
|
+
return [
|
|
6847
|
+
{
|
|
6848
|
+
name: "upload_file",
|
|
6849
|
+
description: "Copy a workspace file into artifact storage (assets/images by default) and return its workspace-relative path. Use this when you want to embed an image in markdown or make a generated file accessible as a stable artifact. The returned path can be used directly in markdown image syntax: ",
|
|
6850
|
+
parameters: {
|
|
6851
|
+
type: "object",
|
|
6852
|
+
properties: {
|
|
6853
|
+
path: {
|
|
6854
|
+
type: "string",
|
|
6855
|
+
description: 'Workspace-relative path of the source file (e.g. "chart.png" or "output/plot.png")'
|
|
6856
|
+
},
|
|
6857
|
+
directory: {
|
|
6858
|
+
type: "string",
|
|
6859
|
+
description: `Destination directory inside the workspace. Defaults to "${DEFAULT_UPLOAD_DIR}".`
|
|
6860
|
+
}
|
|
6861
|
+
},
|
|
6862
|
+
required: ["path"],
|
|
6863
|
+
additionalProperties: false
|
|
6864
|
+
},
|
|
6865
|
+
async execute(input) {
|
|
6866
|
+
const filePath = typeof input.path === "string" ? input.path.trim() : "";
|
|
6867
|
+
if (!filePath || filePath.includes("\0") || filePath.startsWith("/") || filePath.split("/").includes("..")) {
|
|
6868
|
+
return { content: [{ type: "text", text: "invalid path" }], isError: true };
|
|
6869
|
+
}
|
|
6870
|
+
const rawDir = typeof input.directory === "string" ? input.directory.trim() : "";
|
|
6871
|
+
const dir = rawDir ? rawDir.replace(/^\.\/+/, "").replace(/\/+$/, "") : DEFAULT_UPLOAD_DIR;
|
|
6872
|
+
try {
|
|
6873
|
+
const bytes = await readFile9(join8(cwd, filePath));
|
|
6874
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES2) {
|
|
6875
|
+
return {
|
|
6876
|
+
content: [{ type: "text", text: `file must be between 1 byte and ${MAX_UPLOAD_BYTES2} bytes` }],
|
|
6877
|
+
isError: true
|
|
6878
|
+
};
|
|
6879
|
+
}
|
|
6880
|
+
const contentType = contentTypeFromExt(filePath);
|
|
6881
|
+
const ext = extForUpload2(filePath, contentType);
|
|
6882
|
+
const base = basenameForUpload2(filePath);
|
|
6883
|
+
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
6884
|
+
const destPath = `${dir}/${base}-${unique}.${ext}`;
|
|
6885
|
+
await workspace.mkdir(dir, { recursive: true });
|
|
6886
|
+
if (workspace.writeBinaryFile) {
|
|
6887
|
+
await workspace.writeBinaryFile(destPath, bytes);
|
|
6888
|
+
} else {
|
|
6889
|
+
return {
|
|
6890
|
+
content: [{ type: "text", text: "workspace does not support binary file writes" }],
|
|
6891
|
+
isError: true
|
|
6892
|
+
};
|
|
6893
|
+
}
|
|
6894
|
+
return {
|
|
6895
|
+
content: [{ type: "text", text: `Uploaded to ${destPath}` }],
|
|
6896
|
+
isError: false,
|
|
6897
|
+
details: { path: destPath }
|
|
6898
|
+
};
|
|
6899
|
+
} catch (err) {
|
|
6900
|
+
return {
|
|
6901
|
+
content: [{ type: "text", text: err instanceof Error ? err.message : "upload failed" }],
|
|
6902
|
+
isError: true
|
|
6903
|
+
};
|
|
6904
|
+
}
|
|
6905
|
+
}
|
|
6906
|
+
}
|
|
6907
|
+
];
|
|
6908
|
+
}
|
|
6909
|
+
|
|
6595
6910
|
// src/server/registerAgentRoutes.ts
|
|
6596
6911
|
var DEFAULT_VERSION2 = "0.1.0-dev";
|
|
6597
6912
|
var DEFAULT_WORKSPACE_ID3 = "default";
|
|
@@ -6672,7 +6987,8 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
6672
6987
|
});
|
|
6673
6988
|
const standardTools = [
|
|
6674
6989
|
...buildHarnessAgentTools(runtimeBundle),
|
|
6675
|
-
...buildFilesystemAgentTools(runtimeBundle)
|
|
6990
|
+
...buildFilesystemAgentTools(runtimeBundle),
|
|
6991
|
+
...buildUploadAgentTools(runtimeBundle)
|
|
6676
6992
|
];
|
|
6677
6993
|
const pluginTools = [];
|
|
6678
6994
|
if (resolvedMode !== "vercel-sandbox") {
|