@hachej/boring-agent 0.1.5 → 0.1.6
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.
|
@@ -2,6 +2,11 @@ interface Workspace {
|
|
|
2
2
|
readonly root: string;
|
|
3
3
|
readFile(relPath: string): Promise<string>;
|
|
4
4
|
writeFile(relPath: string, data: string): Promise<void>;
|
|
5
|
+
/**
|
|
6
|
+
* Optional binary write operation for user-uploaded assets. Shared callers use
|
|
7
|
+
* Uint8Array so browser-safe workspace contracts do not depend on Node-only types.
|
|
8
|
+
*/
|
|
9
|
+
writeBinaryFile?(relPath: string, data: Uint8Array): Promise<void>;
|
|
5
10
|
/**
|
|
6
11
|
* Optional optimized read+metadata operation. Remote workspaces should
|
|
7
12
|
* implement this as one round trip when possible.
|
|
@@ -15,6 +20,8 @@ interface Workspace {
|
|
|
15
20
|
* implement this as one round trip when possible.
|
|
16
21
|
*/
|
|
17
22
|
writeFileWithStat?(relPath: string, data: string): Promise<Stat>;
|
|
23
|
+
/** Optional optimized binary write+metadata operation. */
|
|
24
|
+
writeBinaryFileWithStat?(relPath: string, data: Uint8Array): Promise<Stat>;
|
|
18
25
|
unlink(relPath: string): Promise<void>;
|
|
19
26
|
readdir(relPath: string): Promise<Entry[]>;
|
|
20
27
|
stat(relPath: string): Promise<Stat>;
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { S as Sandbox, f as SandboxHandleStore, e as SandboxHandleRecord, W as Workspace, F as FileSearch, A as AgentTool } from '../sandbox-handle-store-
|
|
1
|
+
import { S as Sandbox, f as SandboxHandleStore, e as SandboxHandleRecord, W as Workspace, F as FileSearch, A as AgentTool } from '../sandbox-handle-store-pNB_27Sd.js';
|
|
2
2
|
import { Sandbox as Sandbox$1 } from '@vercel/sandbox';
|
|
3
3
|
import { FastifyInstance, FastifyRequest, FastifyPluginAsync } from 'fastify';
|
|
4
4
|
import { PackageSource } from '@mariozechner/pi-coding-agent';
|
|
@@ -289,6 +289,11 @@ interface RegisterAgentRoutesOptions {
|
|
|
289
289
|
workspaceRoot?: string;
|
|
290
290
|
sessionId?: string;
|
|
291
291
|
templatePath?: string;
|
|
292
|
+
getTemplatePath?: (ctx: {
|
|
293
|
+
workspaceId: string;
|
|
294
|
+
workspaceRoot: string;
|
|
295
|
+
request?: FastifyRequest;
|
|
296
|
+
}) => string | undefined | Promise<string | undefined>;
|
|
292
297
|
mode?: RuntimeModeId;
|
|
293
298
|
version?: string;
|
|
294
299
|
extraTools?: AgentTool[];
|
package/dist/server/index.js
CHANGED
|
@@ -725,6 +725,11 @@ function logSandboxStop(logger, metadata) {
|
|
|
725
725
|
...metadata
|
|
726
726
|
});
|
|
727
727
|
}
|
|
728
|
+
function isSandboxAlreadyExistsError(error) {
|
|
729
|
+
const code = error?.json?.error?.code;
|
|
730
|
+
const message = error?.json?.error?.message;
|
|
731
|
+
return code === "bad_request" && typeof message === "string" && message.includes("already exists");
|
|
732
|
+
}
|
|
728
733
|
async function createFresh(workspaceId, snapshotId, tarballUrl, previous, store, vercel, logger) {
|
|
729
734
|
let sandbox;
|
|
730
735
|
let sourceType = "empty";
|
|
@@ -733,21 +738,25 @@ async function createFresh(workspaceId, snapshotId, tarballUrl, previous, store,
|
|
|
733
738
|
persistent: true,
|
|
734
739
|
snapshotExpiration: 0
|
|
735
740
|
};
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
})
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
741
|
+
const tryCreate = async () => {
|
|
742
|
+
try {
|
|
743
|
+
if (snapshotId) {
|
|
744
|
+
sourceType = "snapshot";
|
|
745
|
+
return await vercel.create({ ...base, source: { type: "snapshot", snapshotId } });
|
|
746
|
+
} else if (tarballUrl) {
|
|
747
|
+
sourceType = "tarball";
|
|
748
|
+
return await vercel.create({ ...base, source: { type: "tarball", url: tarballUrl } });
|
|
749
|
+
} else {
|
|
750
|
+
return await vercel.create(base);
|
|
751
|
+
}
|
|
752
|
+
} catch (error) {
|
|
753
|
+
if (isSandboxAlreadyExistsError(error)) {
|
|
754
|
+
return await vercel.get({ name: base.name, sandboxId: base.name, resume: true });
|
|
755
|
+
}
|
|
756
|
+
throw error;
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
sandbox = await tryCreate();
|
|
751
760
|
logSandboxCreate(logger, {
|
|
752
761
|
workspaceId,
|
|
753
762
|
sandboxId: getSandboxIdentifier(sandbox),
|
|
@@ -1384,6 +1393,10 @@ function createNodeWorkspace(root) {
|
|
|
1384
1393
|
const absPath = await ensureWritableWorkspacePath(root, relPath);
|
|
1385
1394
|
await writeFile3(absPath, data, "utf-8");
|
|
1386
1395
|
},
|
|
1396
|
+
async writeBinaryFile(relPath, data) {
|
|
1397
|
+
const absPath = await ensureWritableWorkspacePath(root, relPath);
|
|
1398
|
+
await writeFile3(absPath, data);
|
|
1399
|
+
},
|
|
1387
1400
|
async readFileWithStat(relPath) {
|
|
1388
1401
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
1389
1402
|
const [content, fileStat] = await Promise.all([
|
|
@@ -1409,6 +1422,16 @@ function createNodeWorkspace(root) {
|
|
|
1409
1422
|
kind: fileStat.isDirectory() ? "dir" : "file"
|
|
1410
1423
|
};
|
|
1411
1424
|
},
|
|
1425
|
+
async writeBinaryFileWithStat(relPath, data) {
|
|
1426
|
+
const absPath = await ensureWritableWorkspacePath(root, relPath);
|
|
1427
|
+
await writeFile3(absPath, data);
|
|
1428
|
+
const fileStat = await stat2(absPath);
|
|
1429
|
+
return {
|
|
1430
|
+
size: fileStat.size,
|
|
1431
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1432
|
+
kind: fileStat.isDirectory() ? "dir" : "file"
|
|
1433
|
+
};
|
|
1434
|
+
},
|
|
1412
1435
|
async unlink(relPath) {
|
|
1413
1436
|
const absPath = await ensureExistingWorkspacePath(root, relPath);
|
|
1414
1437
|
const pathStat = await lstat2(absPath);
|
|
@@ -1831,6 +1854,18 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
1831
1854
|
workspaceOpts.onMutation?.();
|
|
1832
1855
|
emitChange({ op: "write", path: relPath });
|
|
1833
1856
|
},
|
|
1857
|
+
async writeBinaryFile(relPath, data) {
|
|
1858
|
+
const sandboxPath = toSandboxPath(relPath);
|
|
1859
|
+
await sandbox.writeFiles([
|
|
1860
|
+
{
|
|
1861
|
+
path: sandboxPath,
|
|
1862
|
+
content: Buffer.from(data)
|
|
1863
|
+
}
|
|
1864
|
+
]);
|
|
1865
|
+
invalidateMetadataCache();
|
|
1866
|
+
workspaceOpts.onMutation?.();
|
|
1867
|
+
emitChange({ op: "write", path: relPath });
|
|
1868
|
+
},
|
|
1834
1869
|
async readFileWithStat(relPath) {
|
|
1835
1870
|
const sandboxPath = toSandboxPath(relPath);
|
|
1836
1871
|
const cachedStat = statCache.get(sandboxPath);
|
|
@@ -1905,6 +1940,44 @@ function createVercelSandboxWorkspace(sandbox, workspaceOpts = {}) {
|
|
|
1905
1940
|
emitChange({ op: "write", path: relPath, mtimeMs: writtenStat.mtimeMs });
|
|
1906
1941
|
return cloneStat(writtenStat);
|
|
1907
1942
|
},
|
|
1943
|
+
async writeBinaryFileWithStat(relPath, data) {
|
|
1944
|
+
const sandboxPath = toSandboxPath(relPath);
|
|
1945
|
+
const payload = Buffer.from(data);
|
|
1946
|
+
if (payload.byteLength > MAX_INLINE_WRITE_BYTES) {
|
|
1947
|
+
await sandbox.writeFiles([
|
|
1948
|
+
{
|
|
1949
|
+
path: sandboxPath,
|
|
1950
|
+
content: payload
|
|
1951
|
+
}
|
|
1952
|
+
]);
|
|
1953
|
+
invalidateMetadataCache();
|
|
1954
|
+
workspaceOpts.onMutation?.();
|
|
1955
|
+
const writtenStat2 = remote.fs?.stat ? await (async () => {
|
|
1956
|
+
const fileStat = await remote.fs.stat(sandboxPath);
|
|
1957
|
+
return {
|
|
1958
|
+
size: fileStat.size,
|
|
1959
|
+
mtimeMs: fileStat.mtimeMs,
|
|
1960
|
+
kind: fileStat.isDirectory() ? "dir" : "file"
|
|
1961
|
+
};
|
|
1962
|
+
})() : await runJson(
|
|
1963
|
+
remote,
|
|
1964
|
+
`node -e ${shellQuote2(`const fs=require('fs'); const p=process.argv[1]; const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote2(sandboxPath)}`
|
|
1965
|
+
);
|
|
1966
|
+
statCache.set(sandboxPath, writtenStat2);
|
|
1967
|
+
emitChange({ op: "write", path: relPath, mtimeMs: writtenStat2.mtimeMs });
|
|
1968
|
+
return cloneStat(writtenStat2);
|
|
1969
|
+
}
|
|
1970
|
+
const encoded = payload.toString("base64");
|
|
1971
|
+
const writtenStat = await runJson(
|
|
1972
|
+
remote,
|
|
1973
|
+
`node -e ${shellQuote2(`const fs=require('fs'); const p=process.argv[1]; const data=Buffer.from(process.argv[2],'base64'); fs.writeFileSync(p,data); const s=fs.statSync(p); process.stdout.write(JSON.stringify({size:s.size,mtimeMs:s.mtimeMs,kind:s.isDirectory()?'dir':'file'}))`)} ${shellQuote2(sandboxPath)} ${shellQuote2(encoded)}`
|
|
1974
|
+
);
|
|
1975
|
+
invalidateMetadataCache();
|
|
1976
|
+
statCache.set(sandboxPath, writtenStat);
|
|
1977
|
+
workspaceOpts.onMutation?.();
|
|
1978
|
+
emitChange({ op: "write", path: relPath, mtimeMs: writtenStat.mtimeMs });
|
|
1979
|
+
return cloneStat(writtenStat);
|
|
1980
|
+
},
|
|
1908
1981
|
async unlink(relPath) {
|
|
1909
1982
|
const sandboxPath = toSandboxPath(relPath);
|
|
1910
1983
|
if (remote.fs?.rm) await remote.fs.rm(sandboxPath, { recursive: false, force: false });
|
|
@@ -2410,6 +2483,32 @@ async function ensureVercelWorkspaceRoot(sandbox) {
|
|
|
2410
2483
|
throw new Error(`failed to initialize ${VERCEL_SANDBOX_REMOTE_ROOT} (exit ${result.exitCode ?? "unknown"})`);
|
|
2411
2484
|
}
|
|
2412
2485
|
}
|
|
2486
|
+
async function seedTemplateIntoVercelWorkspace(workspace, templatePath, logger) {
|
|
2487
|
+
const files = await collectFiles(templatePath);
|
|
2488
|
+
const hash = computeTemplateHash(files);
|
|
2489
|
+
const markerPath = `.boring-agent/templates/${hash}.json`;
|
|
2490
|
+
try {
|
|
2491
|
+
await workspace.stat(markerPath);
|
|
2492
|
+
logger.info("[vercel-sandbox:mode] template already seeded", {
|
|
2493
|
+
hash,
|
|
2494
|
+
fileCount: files.length
|
|
2495
|
+
});
|
|
2496
|
+
return;
|
|
2497
|
+
} catch {
|
|
2498
|
+
}
|
|
2499
|
+
for (const file of files) {
|
|
2500
|
+
if (workspace.writeBinaryFile) {
|
|
2501
|
+
await workspace.writeBinaryFile(file.rel, new Uint8Array(file.content));
|
|
2502
|
+
} else {
|
|
2503
|
+
await workspace.writeFile(file.rel, file.content.toString("utf-8"));
|
|
2504
|
+
}
|
|
2505
|
+
}
|
|
2506
|
+
await workspace.writeFile(markerPath, JSON.stringify({ hash, seededAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2));
|
|
2507
|
+
logger.info("[vercel-sandbox:mode] template seeded into workspace", {
|
|
2508
|
+
hash,
|
|
2509
|
+
fileCount: files.length
|
|
2510
|
+
});
|
|
2511
|
+
}
|
|
2413
2512
|
var DEFAULT_MODE_LOGGER = {
|
|
2414
2513
|
info(message, metadata) {
|
|
2415
2514
|
process.stderr.write(`${message} ${JSON.stringify(metadata)}
|
|
@@ -2521,17 +2620,13 @@ function createVercelSandboxModeAdapter(opts = {}) {
|
|
|
2521
2620
|
onMutation: markDirty
|
|
2522
2621
|
});
|
|
2523
2622
|
await ensureVercelWorkspaceRoot(sandboxHandle);
|
|
2524
|
-
if (ctx.templatePath
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
for (const file of files) {
|
|
2530
|
-
await workspace.writeFile(file.rel, file.content.toString("utf-8"));
|
|
2623
|
+
if (ctx.templatePath) {
|
|
2624
|
+
if (!tarballUrl) {
|
|
2625
|
+
logger.info("[vercel-sandbox:mode] falling back to writeFiles for template", {
|
|
2626
|
+
templatePath: ctx.templatePath
|
|
2627
|
+
});
|
|
2531
2628
|
}
|
|
2532
|
-
|
|
2533
|
-
fileCount: files.length
|
|
2534
|
-
});
|
|
2629
|
+
await seedTemplateIntoVercelWorkspace(workspace, ctx.templatePath, logger);
|
|
2535
2630
|
}
|
|
2536
2631
|
const sandbox = createVercelSandboxExec(sandboxHandle, {
|
|
2537
2632
|
onMutation: markDirty
|
|
@@ -4920,6 +5015,13 @@ function healthRoutes(app, opts, done) {
|
|
|
4920
5015
|
}
|
|
4921
5016
|
|
|
4922
5017
|
// src/server/http/routes/file.ts
|
|
5018
|
+
import { dirname as dirname7, extname as extname2, relative as relative7 } from "path/posix";
|
|
5019
|
+
var BORING_SETTINGS_PATH = ".boring/settings";
|
|
5020
|
+
var DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR = "assets/images";
|
|
5021
|
+
var MAX_UPLOAD_BYTES = 10 * 1024 * 1024;
|
|
5022
|
+
function defaultWorkspaceSettings() {
|
|
5023
|
+
return { markdown: { imageUploadDir: DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR } };
|
|
5024
|
+
}
|
|
4923
5025
|
function isPathValidationError(err) {
|
|
4924
5026
|
return err instanceof Error && typeof err.reason === "string";
|
|
4925
5027
|
}
|
|
@@ -4965,6 +5067,58 @@ function requireStringParam(value, field, reply) {
|
|
|
4965
5067
|
}
|
|
4966
5068
|
return value;
|
|
4967
5069
|
}
|
|
5070
|
+
function parseWorkspaceSettings(raw) {
|
|
5071
|
+
try {
|
|
5072
|
+
const parsed = JSON.parse(raw);
|
|
5073
|
+
const dir = parsed?.markdown?.imageUploadDir;
|
|
5074
|
+
return {
|
|
5075
|
+
...parsed,
|
|
5076
|
+
markdown: {
|
|
5077
|
+
...parsed.markdown ?? {},
|
|
5078
|
+
imageUploadDir: typeof dir === "string" && dir.trim() ? dir.trim() : DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR
|
|
5079
|
+
}
|
|
5080
|
+
};
|
|
5081
|
+
} catch {
|
|
5082
|
+
return defaultWorkspaceSettings();
|
|
5083
|
+
}
|
|
5084
|
+
}
|
|
5085
|
+
async function readWorkspaceSettings(workspace) {
|
|
5086
|
+
try {
|
|
5087
|
+
return parseWorkspaceSettings(await workspace.readFile(BORING_SETTINGS_PATH));
|
|
5088
|
+
} catch (error) {
|
|
5089
|
+
const code = error?.code;
|
|
5090
|
+
if (code === "ENOENT") return defaultWorkspaceSettings();
|
|
5091
|
+
throw error;
|
|
5092
|
+
}
|
|
5093
|
+
}
|
|
5094
|
+
function normalizeUploadDir(value) {
|
|
5095
|
+
if (typeof value !== "string") return null;
|
|
5096
|
+
const dir = value.trim().replace(/^\.\/+/, "").replace(/\/+/g, "/");
|
|
5097
|
+
if (!dir || dir.includes("\0") || dir.startsWith("/") || dir.split("/").includes("..")) return null;
|
|
5098
|
+
return dir.replace(/\/+$/, "");
|
|
5099
|
+
}
|
|
5100
|
+
function extForUpload(filename, contentType) {
|
|
5101
|
+
const fromName = extname2(filename).toLowerCase().replace(/^\./, "");
|
|
5102
|
+
if (/^[a-z0-9]{1,12}$/.test(fromName)) return fromName;
|
|
5103
|
+
if (contentType === "image/jpeg") return "jpg";
|
|
5104
|
+
if (contentType === "image/png") return "png";
|
|
5105
|
+
if (contentType === "image/gif") return "gif";
|
|
5106
|
+
if (contentType === "image/webp") return "webp";
|
|
5107
|
+
if (contentType === "image/svg+xml") return "svg";
|
|
5108
|
+
return "bin";
|
|
5109
|
+
}
|
|
5110
|
+
function basenameForUpload(filename) {
|
|
5111
|
+
const base = filename.split("/").pop()?.split("\\").pop() ?? "image";
|
|
5112
|
+
const withoutExt = base.replace(/\.[^.]*$/, "");
|
|
5113
|
+
const safe = withoutExt.normalize("NFKD").replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 64);
|
|
5114
|
+
return safe || "image";
|
|
5115
|
+
}
|
|
5116
|
+
function markdownUrlFor(sourcePath, assetPath) {
|
|
5117
|
+
if (!sourcePath) return assetPath;
|
|
5118
|
+
const fromDir = dirname7(sourcePath.replace(/\\/g, "/"));
|
|
5119
|
+
const rel = relative7(fromDir === "." ? "" : fromDir, assetPath);
|
|
5120
|
+
return rel && !rel.startsWith(".") ? rel : rel || assetPath;
|
|
5121
|
+
}
|
|
4968
5122
|
function fileRoutes(app, opts, done) {
|
|
4969
5123
|
async function resolveWorkspace(request) {
|
|
4970
5124
|
if (opts.getWorkspace) return await opts.getWorkspace(request);
|
|
@@ -5041,6 +5195,87 @@ function fileRoutes(app, opts, done) {
|
|
|
5041
5195
|
return classifyError(err, reply, "file");
|
|
5042
5196
|
}
|
|
5043
5197
|
});
|
|
5198
|
+
app.post("/api/v1/files/upload", async (request, reply) => {
|
|
5199
|
+
const body = request.body;
|
|
5200
|
+
const filename = requireStringParam(body?.filename, "filename", reply);
|
|
5201
|
+
if (filename === null) return;
|
|
5202
|
+
const contentBase64 = requireStringParam(body?.contentBase64, "contentBase64", reply);
|
|
5203
|
+
if (contentBase64 === null) return;
|
|
5204
|
+
const contentType = typeof body.contentType === "string" ? body.contentType.trim().toLowerCase() : "";
|
|
5205
|
+
if (!contentType.startsWith("image/")) {
|
|
5206
|
+
return reply.code(400).send({
|
|
5207
|
+
error: { code: ERROR_CODE_VALIDATION_ERROR, message: "contentType must be an image/* MIME type", field: "contentType" }
|
|
5208
|
+
});
|
|
5209
|
+
}
|
|
5210
|
+
try {
|
|
5211
|
+
const workspace = await resolveWorkspace(request);
|
|
5212
|
+
const settings = await readWorkspaceSettings(workspace);
|
|
5213
|
+
const dir = normalizeUploadDir(body.directory) ?? normalizeUploadDir(settings.markdown?.imageUploadDir) ?? DEFAULT_MARKDOWN_IMAGE_UPLOAD_DIR;
|
|
5214
|
+
const ext = extForUpload(filename, contentType);
|
|
5215
|
+
const base = basenameForUpload(filename);
|
|
5216
|
+
const unique = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
5217
|
+
const path4 = `${dir}/${base}-${unique}.${ext}`;
|
|
5218
|
+
const bytes = Buffer.from(contentBase64, "base64");
|
|
5219
|
+
if (bytes.byteLength === 0 || bytes.byteLength > MAX_UPLOAD_BYTES) {
|
|
5220
|
+
return reply.code(400).send({
|
|
5221
|
+
error: { code: ERROR_CODE_VALIDATION_ERROR, message: `upload must be 1 byte to ${MAX_UPLOAD_BYTES} bytes`, field: "contentBase64" }
|
|
5222
|
+
});
|
|
5223
|
+
}
|
|
5224
|
+
await workspace.mkdir(dir, { recursive: true });
|
|
5225
|
+
const stat7 = workspace.writeBinaryFileWithStat ? await workspace.writeBinaryFileWithStat(path4, bytes) : await (async () => {
|
|
5226
|
+
if (!workspace.writeBinaryFile) {
|
|
5227
|
+
throw new Error("workspace does not support binary uploads");
|
|
5228
|
+
}
|
|
5229
|
+
await workspace.writeBinaryFile(path4, bytes);
|
|
5230
|
+
return await workspace.stat(path4);
|
|
5231
|
+
})();
|
|
5232
|
+
const sourcePath = typeof body.sourcePath === "string" && !body.sourcePath.includes("\0") ? body.sourcePath : null;
|
|
5233
|
+
return {
|
|
5234
|
+
ok: true,
|
|
5235
|
+
path: path4,
|
|
5236
|
+
markdownUrl: markdownUrlFor(sourcePath, path4),
|
|
5237
|
+
mtimeMs: stat7.kind === "file" ? stat7.mtimeMs : void 0
|
|
5238
|
+
};
|
|
5239
|
+
} catch (err) {
|
|
5240
|
+
return classifyError(err, reply, "upload");
|
|
5241
|
+
}
|
|
5242
|
+
});
|
|
5243
|
+
app.get("/api/v1/workspace-settings", async (request, reply) => {
|
|
5244
|
+
try {
|
|
5245
|
+
const workspace = await resolveWorkspace(request);
|
|
5246
|
+
return { settings: await readWorkspaceSettings(workspace) };
|
|
5247
|
+
} catch (err) {
|
|
5248
|
+
return classifyError(err, reply, "settings");
|
|
5249
|
+
}
|
|
5250
|
+
});
|
|
5251
|
+
app.put("/api/v1/workspace-settings", async (request, reply) => {
|
|
5252
|
+
const body = request.body;
|
|
5253
|
+
const incoming = body?.settings ?? body;
|
|
5254
|
+
const markdown = incoming.markdown ?? {};
|
|
5255
|
+
const imageUploadDir = normalizeUploadDir(markdown.imageUploadDir);
|
|
5256
|
+
if (!imageUploadDir) {
|
|
5257
|
+
return reply.code(400).send({
|
|
5258
|
+
error: { code: ERROR_CODE_INVALID_PATH, message: "markdown.imageUploadDir must be a relative workspace path", field: "markdown.imageUploadDir" }
|
|
5259
|
+
});
|
|
5260
|
+
}
|
|
5261
|
+
try {
|
|
5262
|
+
const workspace = await resolveWorkspace(request);
|
|
5263
|
+
const current = await readWorkspaceSettings(workspace);
|
|
5264
|
+
const next = {
|
|
5265
|
+
...current,
|
|
5266
|
+
markdown: {
|
|
5267
|
+
...current.markdown ?? {},
|
|
5268
|
+
imageUploadDir
|
|
5269
|
+
}
|
|
5270
|
+
};
|
|
5271
|
+
await workspace.mkdir(".boring", { recursive: true });
|
|
5272
|
+
await workspace.writeFile(BORING_SETTINGS_PATH, `${JSON.stringify(next, null, 2)}
|
|
5273
|
+
`);
|
|
5274
|
+
return { settings: next };
|
|
5275
|
+
} catch (err) {
|
|
5276
|
+
return classifyError(err, reply, "settings");
|
|
5277
|
+
}
|
|
5278
|
+
});
|
|
5044
5279
|
app.delete("/api/v1/files", async (request, reply) => {
|
|
5045
5280
|
const query = request.query;
|
|
5046
5281
|
const path4 = requireStringParam(query.path, "path", reply);
|
|
@@ -5806,7 +6041,11 @@ function skillsRoutes(app, opts, done) {
|
|
|
5806
6041
|
return reply.code(200).send({ skills: cached.skills });
|
|
5807
6042
|
}
|
|
5808
6043
|
try {
|
|
5809
|
-
const result = loadSkills2({
|
|
6044
|
+
const result = loadSkills2({
|
|
6045
|
+
cwd: opts.workspaceRoot,
|
|
6046
|
+
skillPaths: opts.additionalSkillPaths ?? [],
|
|
6047
|
+
includeDefaults: true
|
|
6048
|
+
});
|
|
5810
6049
|
const skills = result.skills.map((s) => ({
|
|
5811
6050
|
name: s.name,
|
|
5812
6051
|
description: s.description
|
|
@@ -6289,7 +6528,10 @@ async function createAgentApp(opts = {}) {
|
|
|
6289
6528
|
});
|
|
6290
6529
|
await app.register(systemPromptRoutes, { harness });
|
|
6291
6530
|
await app.register(modelsRoutes);
|
|
6292
|
-
await app.register(skillsRoutes, {
|
|
6531
|
+
await app.register(skillsRoutes, {
|
|
6532
|
+
workspaceRoot,
|
|
6533
|
+
additionalSkillPaths: opts.resourceLoaderOptions?.additionalSkillPaths
|
|
6534
|
+
});
|
|
6293
6535
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
6294
6536
|
await app.register(catalogRoutes, { tools });
|
|
6295
6537
|
await app.register(readyStatusRoutes, { tracker: readyTracker });
|
|
@@ -6420,12 +6662,13 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
6420
6662
|
key: `${resolvedMode}:${workspaceId}:${root}`
|
|
6421
6663
|
};
|
|
6422
6664
|
}
|
|
6423
|
-
async function createRuntimeBinding(workspaceId, root) {
|
|
6665
|
+
async function createRuntimeBinding(workspaceId, root, request) {
|
|
6666
|
+
const scopedTemplatePath = opts.getTemplatePath ? await opts.getTemplatePath({ workspaceId, workspaceRoot: root, request }) : templatePath;
|
|
6424
6667
|
const runtimeBundle = await modeAdapter.create({
|
|
6425
6668
|
workspaceRoot: root,
|
|
6426
6669
|
sessionId: workspaceId,
|
|
6427
6670
|
workspaceId,
|
|
6428
|
-
templatePath
|
|
6671
|
+
templatePath: scopedTemplatePath
|
|
6429
6672
|
});
|
|
6430
6673
|
const standardTools = [
|
|
6431
6674
|
...buildHarnessAgentTools(runtimeBundle),
|
|
@@ -6494,7 +6737,7 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
6494
6737
|
await existing
|
|
6495
6738
|
);
|
|
6496
6739
|
}
|
|
6497
|
-
const created = createRuntimeBinding(workspaceId, scope.root);
|
|
6740
|
+
const created = createRuntimeBinding(workspaceId, scope.root, request);
|
|
6498
6741
|
runtimeBindings.set(scope.key, created);
|
|
6499
6742
|
try {
|
|
6500
6743
|
return await ensureRuntimeBindingReady(
|
|
@@ -6631,7 +6874,10 @@ var registerAgentRoutes = async (app, opts) => {
|
|
|
6631
6874
|
getHarness: async (request) => (await getBindingForRequest(request)).harness
|
|
6632
6875
|
});
|
|
6633
6876
|
await app.register(modelsRoutes);
|
|
6634
|
-
await app.register(skillsRoutes, {
|
|
6877
|
+
await app.register(skillsRoutes, {
|
|
6878
|
+
workspaceRoot,
|
|
6879
|
+
additionalSkillPaths: opts.resourceLoaderOptions?.additionalSkillPaths
|
|
6880
|
+
});
|
|
6635
6881
|
await app.register(sessionChangesRoutes, { tracker: sessionChangesTracker });
|
|
6636
6882
|
await app.register(
|
|
6637
6883
|
catalogRoutes,
|
package/dist/shared/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export { A as AgentHarness, R as RunContext, S as SendMessageInput, a as SessionCtx, b as SessionDetail, c as SessionStore, d as SessionSummary } from '../harness-CMiJ4kok.js';
|
|
2
|
-
import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../sandbox-handle-store-
|
|
3
|
-
export { E as Entry, a as ExecOptions, b as ExecResult, I as IsolatedCodeInput, c as IsolatedCodeOutput, J as JSONSchema, d as SandboxCapability, e as SandboxHandleRecord, f as SandboxHandleStore, g as Stat, T as ToolExecContext, h as ToolResult } from '../sandbox-handle-store-
|
|
2
|
+
import { W as Workspace, S as Sandbox, F as FileSearch, A as AgentTool } from '../sandbox-handle-store-pNB_27Sd.js';
|
|
3
|
+
export { E as Entry, a as ExecOptions, b as ExecResult, I as IsolatedCodeInput, c as IsolatedCodeOutput, J as JSONSchema, d as SandboxCapability, e as SandboxHandleRecord, f as SandboxHandleStore, g as Stat, T as ToolExecContext, h as ToolResult } from '../sandbox-handle-store-pNB_27Sd.js';
|
|
4
4
|
export { UIMessage, UIMessageChunk } from 'ai';
|
|
5
5
|
import { z } from 'zod';
|
|
6
6
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hachej/boring-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "Pane-embeddable coding agent. Ships direct/local/vercel-sandbox execution modes behind one interface.",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"use-stick-to-bottom": "^1.1.3",
|
|
75
75
|
"yaml": "^2.8.3",
|
|
76
76
|
"zod": "^3.25.76",
|
|
77
|
-
"@hachej/boring-ui-kit": "0.1.
|
|
77
|
+
"@hachej/boring-ui-kit": "0.1.6"
|
|
78
78
|
},
|
|
79
79
|
"devDependencies": {
|
|
80
80
|
"@opentelemetry/api": "^1.9.1",
|