Open a bot's own files from a workspace conversation

A workspace run saves to the bot's own folder unless it picks the
workspace's, but a file chip in a workspace conversation only looked in
the workspace's folder, so those refs rendered as dead text. The chip
now sends both ids and the single-file lookup also searches the bot's
own folder for a user who reaches that bot.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-27 17:41:46 +01:00
co-authored by Claude Opus 5.5
parent 211599e0fc
commit 2d3c15e48b
3 changed files with 17 additions and 4 deletions
+1 -1
View File
@@ -169,7 +169,7 @@ Set in Settings > Environment (super admin), not `.env`. `envs` (private; encryp
### Bot files and folders
- Files live on the data volume, not on computers: `DATA_DIR/bots/<id>` and `DATA_DIR/workspaces/<id>`. Catalog `bot_files`, bytes in private `media` (`bot-file`) written by `recordBotFile`. Reach via `grabStorageScopes`; paths via `resolveBotPath` (no absolute, `..`, NUL, symlink escape); quota checked by `assertStorageRoom`.
- Tools in `src/harness/tools/storage/` (every run). `attach` puts a file on the reply. `grabConversationFiles` must list the **whole** folder tree, or nested `[file:ID]` refs render dead.
- Tools in `src/harness/tools/storage/` (every run). `attach` puts a file on the reply. `grabConversationFiles` must list the **whole** folder tree, or nested `[file:ID]` refs render dead. A workspace run saves to the bot's own folder unless it picks the workspace's, so a chip in a workspace conversation looks up both (`api/admin/bots/files?file_id=` with `bot_id` and `workspace_id`).
- `import_file` copies from a computer through the sandbox's streaming `GET /v1/file/download`, **never through its shell** (`/v1/shell/exec` caps output at ~30K chars and silently truncated imports). Size read first, `change_policy=abort`, length checked.
- Ingestion: chunks + embeddings into `bot_file_chunks` / `bot_file_vectors` (vec0), worked by `src/file-ingest/`; without an embedding model search falls back to words and says so. sqlite-vec limits: metadata columns are NOT NULL; side-car columns can't be filtered with `MATCH` (filter in code). **Never pass `--vector` to `bunx bun-sqlite schema` in `entrypoint.sh`** (wipes vectors on boot). Changing `AppData.EmbeddingDimensions` needs a manual `--vector` run and a re-ingest.
- **Foreign keys are off per connection** unless `enableForeignKeys` ran (only `src/server.ts` does). Scripts never fire `ON DELETE CASCADE`, so **any helper that deletes a parent row must delete its children explicitly** (as `deleteBotFile`, `purgeStorage`, `deleteSkill` do). A leftover child fails the next `bun run db:schema` with a foreign key violation.
@@ -27,10 +27,13 @@ function fetchFile({
if (cached) return cached;
const query = new URLSearchParams({ file_id: String(file_id) });
/**
* Both ids in a workspace: a bot working there saves to its own folder
* unless it picks the workspace's, and the chip has to find either.
*/
const query = new URLSearchParams({ file_id: String(file_id), bot_id: String(bot_id) });
if (workspace_id) query.set("workspace_id", String(workspace_id));
else query.set("bot_id", String(bot_id));
const pending = fetchApi<{}, APIResponseObject<BotFileScopesPayload>>(
`/api/admin/bots/files?${query.toString()}`,
+11 -1
View File
@@ -56,7 +56,17 @@ export const handler: BunextAPIRouteHandler<APIResponseObject<BotFileScopesPaylo
const scopes = [scope];
if (file_id) {
const file = await grabBotFile({ scopes, file_id });
/**
* A workspace run saves to the bot's own folder by default
* (`grabStorageScopes`, own first), so a chip in a workspace
* conversation looks there too, for a user who reaches that bot
* (the same reach `grabBotFileMedia` checks before the bytes).
*/
const file_scopes =
scope.scope == "workspace" && bot_id && (await grabUserBot({ bot_id, user_id: user.id }))
? [...scopes, { scope: "bot" as const, owner_id: bot_id }]
: scopes;
const file = await grabBotFile({ scopes: file_scopes, file_id });
if (!file?.id) return { success: false, msg: `Not found.` };