"use strict";
// Cross-session artifact catalog.
//
// Surfaces files written by the agent into agent-outputs/ (the corralled
// scratch directory used by the file_write tool when it redirects bare
// filenames). The Artifacts UI tab uses this to give users a single
// view of every artifact the agent has produced, with light auto-tagging
// by extension so the table can group/filter without an LLM.
//
// Read-only and dependency-free — no fs writes, no daemons.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.listArtifacts = listArtifacts;
exports.readArtifact = readArtifact;
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const EXTENSION_CATEGORIES = {
    ts: 'code', tsx: 'code', js: 'code', jsx: 'code', mjs: 'code', cjs: 'code',
    py: 'code', rb: 'code', go: 'code', rs: 'code', java: 'code', kt: 'code',
    swift: 'code', cs: 'code', cpp: 'code', c: 'code', h: 'code', hpp: 'code',
    md: 'document', markdown: 'document', txt: 'document', rtf: 'document',
    pdf: 'document', docx: 'document', odt: 'document',
    json: 'data', jsonl: 'data', yaml: 'data', yml: 'data', toml: 'data',
    csv: 'data', tsv: 'data', xml: 'data', xlsx: 'data',
    html: 'web', htm: 'web', svg: 'web', css: 'web',
    png: 'image', jpg: 'image', jpeg: 'image', gif: 'image', webp: 'image', bmp: 'image',
    mp3: 'audio', wav: 'audio', m4a: 'audio', flac: 'audio', ogg: 'audio',
    mp4: 'video', mov: 'video', mkv: 'video', avi: 'video', webm: 'video',
    zip: 'archive', tar: 'archive', gz: 'archive', '7z': 'archive', rar: 'archive',
    sh: 'script', ps1: 'script', bat: 'script', cmd: 'script',
};
const DEFAULT_LIST_LIMIT = 200;
const DEFAULT_READ_MAX_BYTES = 256 * 1024;
/**
 * Walk the artifact root and return a flat list of artifacts ordered by
 * most-recently modified first. Returns an empty list if the root does
 * not exist; never throws.
 */
async function listArtifacts(rootDir, options = {}) {
    const limit = options.limit ?? DEFAULT_LIST_LIMIT;
    const records = [];
    await walk(rootDir, rootDir, records);
    let filtered = records;
    if (options.category) {
        filtered = filtered.filter((record) => record.category === options.category);
    }
    if (options.search) {
        const needle = options.search.toLowerCase();
        filtered = filtered.filter((record) => record.relativePath.toLowerCase().includes(needle));
    }
    filtered.sort((a, b) => (a.modifiedAt < b.modifiedAt ? 1 : -1));
    return filtered.slice(0, limit);
}
async function walk(rootDir, currentDir, records) {
    let entries;
    try {
        entries = await fs.readdir(currentDir, { withFileTypes: true });
    }
    catch {
        return;
    }
    for (const entry of entries) {
        const absolute = path.join(currentDir, entry.name);
        if (entry.isDirectory()) {
            await walk(rootDir, absolute, records);
            continue;
        }
        if (!entry.isFile())
            continue;
        let stat;
        try {
            stat = await fs.stat(absolute);
        }
        catch {
            continue;
        }
        const relativeRaw = path.relative(rootDir, absolute);
        const relativePath = relativeRaw.split(path.sep).join('/');
        const extension = path.extname(entry.name).replace(/^\./, '').toLowerCase();
        const category = EXTENSION_CATEGORIES[extension] ?? 'other';
        const tags = buildTags(entry.name, extension, category, stat.size);
        records.push({
            relativePath,
            name: entry.name,
            absolutePath: absolute,
            size: stat.size,
            modifiedAt: new Date(stat.mtimeMs).toISOString(),
            extension,
            category,
            tags,
        });
    }
}
function buildTags(name, extension, category, size) {
    const tags = new Set();
    tags.add(category);
    if (extension)
        tags.add(extension);
    if (size >= 1_000_000)
        tags.add('large');
    if (size === 0)
        tags.add('empty');
    if (/report|summary|brief/i.test(name))
        tags.add('report');
    if (/test|spec/i.test(name))
        tags.add('test');
    if (/draft|wip/i.test(name))
        tags.add('draft');
    return Array.from(tags);
}
/**
 * Read an artifact from inside `rootDir`. Refuses paths that escape the
 * root (path traversal protection). Returns up to maxBytes of UTF-8
 * content; binary files are reported by the caller via the extension
 * already exposed in `listArtifacts`.
 */
async function readArtifact(rootDir, relativePath, maxBytes = DEFAULT_READ_MAX_BYTES) {
    if (!relativePath || relativePath.includes('..')) {
        throw new Error('Invalid artifact path.');
    }
    const absolute = path.resolve(rootDir, relativePath);
    const root = path.resolve(rootDir);
    if (!absolute.startsWith(root + path.sep) && absolute !== root) {
        throw new Error('Artifact path escapes the artifact root.');
    }
    const stat = await fs.stat(absolute);
    const buffer = await fs.readFile(absolute);
    const truncated = buffer.length > maxBytes;
    const slice = truncated ? buffer.subarray(0, maxBytes) : buffer;
    return {
        absolutePath: absolute,
        content: slice.toString('utf-8'),
        truncated,
        size: stat.size,
        modifiedAt: new Date(stat.mtimeMs).toISOString(),
    };
}
//# sourceMappingURL=artifactCatalog.js.map