Stream computer files instead of reading them through the shell

import_file sent a computer's bytes back with base64 over /v1/shell/exec,
whose stdout is capped at 30,047 characters. Every import was therefore
cut at 22,528 bytes and saved anyway, with the tool reporting success: a
1.12 MiB archive became a 22 KB file that would not open, and a crawler's
crawl.json lost half its pages.

The bytes now come from the sandbox's streaming GET /v1/file/download.
The size is read first so nothing over BotFileImportMaxBytes is
transferred, the download asks for change_policy=abort so a file that
changes mid-send is refused rather than half-saved, and the received
length is checked against the size read, so a cut-short transfer fails
loudly. Folders go the same way, as a gzipped tar unpacked into one row
per file.

Bots also name a path as [path:volume:owner:path] now, so what a bot
leaves on a computer is a link into the files browser instead of text.
This commit is contained in:
2026-10-22 07:37:19 +01:00
parent 956942bba9
commit f1b7ba535f
7 changed files with 658 additions and 6 deletions
+18 -4
View File
@@ -30,12 +30,20 @@ export const WorkspaceRefHrefPrefix = "#workspace-";
*/
export const FileRefHrefPrefix = "#file-";
/**
* The link target a path ref (`pathRef`) becomes, e.g.
* `#path-computer%3A3%3Aseo%2Fraw`. The chat renders it as a chip that
* opens that folder in the files browser.
*/
export const PathRefHrefPrefix = "#path-";
/**
* Turns every computer ref (`[computer:3]`) and take-over ref
* (`[take-over:3:browser]`), bot ref (`[bot:3]`), workspace ref
* (`[workspace:2]`) and file ref (`[file:12]`) in a message into a
* markdown link, so it survives markdown rendering as a link the chat can
* swap for a button or chip.
* (`[workspace:2]`), file ref (`[file:12]`) and path ref
* (`[path:computer:3:seo/raw]`) in a message into a markdown link, so it
* survives markdown rendering as a link the chat can swap for a button or
* chip.
*/
export default function linkComputerRefs({ text }: { text: string }) {
return text
@@ -52,5 +60,11 @@ export default function linkComputerRefs({ text }: { text: string }) {
/\[workspace:(\d+)\](?!\()/g,
(_, id: string) => `[workspace ${id}](${WorkspaceRefHrefPrefix}${id})`,
)
.replace(/\[file:(\d+)\](?!\()/g, (_, id: string) => `[file ${id}](${FileRefHrefPrefix}${id})`);
.replace(/\[file:(\d+)\](?!\()/g, (_, id: string) => `[file ${id}](${FileRefHrefPrefix}${id})`)
.replace(/\[path:([a-z]+):(\d+):([^\]\n]+)\](?!\()/g, (_, volume: string, owner: string, path: string) =>
`[${path}](${PathRefHrefPrefix}${encodeURIComponent(`${volume}:${owner}:${path}`)})`,
)
.replace(/\[path:([a-z]+):(\d+)\](?!\()/g, (_, volume: string, owner: string) =>
`[${volume} ${owner}](${PathRefHrefPrefix}${encodeURIComponent(`${volume}:${owner}`)})`,
);
}