Ctrl K
ComunidadGuías…

server/box-attachments.ts

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
import { constants, copyFileSync, existsSync, linkSync, mkdirSync, renameSync, rmSync } from "node:fs";
import { basename, dirname, join, resolve } from "node:path";
import { decodeAttachmentAttribute, escapeAttribute } from "../src/lib/composer-attachments.ts";
import { ATTACHMENTS_DIR } from "./attachments.ts";
import { botBox } from "./bot-box/box.ts";
import { boxUploadsDir, taskWorkspaceIds } from "./bot-box/plan.ts";
import { DATA_DIR } from "./config.ts";

const TAG = /(<attached-(?:file|image)[\t ]+path=")([^"\r\n]*)(")/g;
const NAME = /^[A-Za-z0-9][A-Za-z0-9._-]*$/;

/**
 * The bot box hides the data folder, uploads included, so an `<attached-file>` path would be unreadable there.
 * Each upload the text names is linked into the conversation's uploads folder (plan.ts boxUploadsDir), which the server
 * owns and every box of the conversation sees read-only, and the path is rewritten to it. The bot can read the upload but
 * never write it or swap the folder for a link. Only the text sent to the engine changes; with the box off, or a working
 * folder that is not a conversation's own, nothing does.
 */
export function attachmentsForBox(text: string, cwd: string | undefined, boxed = botBox.enabled(), dataDir = DATA_DIR, attachmentsDir = ATTACHMENTS_DIR): string {
  if (!boxed || !cwd || !text.includes("<attached-")) return text;
  const ids = taskWorkspaceIds(resolve(cwd), resolve(dataDir));
  if (!ids) return text;
  const uploads = resolve(attachmentsDir), shown = boxUploadsDir(resolve(dataDir), ids.botId, ids.threadId);
  return text.replace(TAG, (tag, open: string, raw: string, close: string) => {
    const path = decodeAttachmentAttribute(raw), name = basename(path);
    if (resolve(dirname(path)) !== uploads || !NAME.test(name) || !existsSync(path)) return tag;
    const target = join(shown, name);
    try {
      // Uploads never change under their name, so one an earlier turn put here is kept.
      if (!existsSync(target)) {
        mkdirSync(shown, { recursive: true, mode: 0o700 });
        try {
          linkSync(path, target);
        } catch {
          // Another file system: a copy, renamed into place so one cut short never passes for a whole one.
          const partial = `${target}.${process.pid}.partial`;
          try {
            copyFileSync(path, partial, constants.COPYFILE_FICLONE);
            renameSync(partial, target);
          } finally {
            rmSync(partial, { force: true });
          }
        }
      }
    } catch {
      return tag;
    }
    return `${open}${escapeAttribute(target)}${close}`;
  });
}