/**
 * Sidechain artifacts: per-subagent JSONL transcript files written under
 * `<sessionDir>/subagents/<parent>/<sessionId>.jsonl`, plus the markdown
 * output file under `<sessionDir>/outputs/<parent>/<agentId>.md`.
 *
 * The transcript is exporter-compatible with the parent session schema so
 * `/trace` and replay tooling can walk parent and child uniformly. The writer
 * persists only stable milestones (turn boundaries, finalized messages,
 * tool_execution_end) — never streaming token deltas.
 */
import { randomUUID } from 'node:crypto';
import { appendFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
import { basename, join } from 'node:path';
import { CURRENT_SESSION_VERSION, parseSessionEntries } from '../../../core/session-schema.js';
/** Allocate a short, unique entry id for the sidechain transcript. */
function nextId(usedIds) {
    for (let i = 0; i < 100; i++) {
        const id = randomUUID().slice(0, 8);
        if (!usedIds.has(id)) {
            usedIds.add(id);
            return id;
        }
    }
    const id = randomUUID();
    usedIds.add(id);
    return id;
}
/** Sanitize a string for use as a path segment, falling back to "session" if empty. */
function safePathPart(value) {
    return value.replace(/[^a-zA-Z0-9._-]+/g, '-').replace(/^-+|-+$/g, '') || 'session';
}
/** Append a single JSONL-encoded entry to a file. */
function appendJsonl(filePath, entry) {
    appendFileSync(filePath, `${JSON.stringify(entry)}\n`, 'utf-8');
}
/** Create the sidechain transcript artifact for a subagent under the parent session. */
export function createTaskSidechainArtifact(session, agentName, agentId) {
    const parentSessionFile = session.getSessionFile();
    if (!parentSessionFile)
        return undefined;
    const parentBase = safePathPart(basename(parentSessionFile, '.jsonl'));
    const sessionId = agentId ?? `subagent-${safePathPart(agentName)}-${randomUUID()}`;
    const traceDir = join(session.getSessionDir(), 'subagents', parentBase);
    mkdirSync(traceDir, { recursive: true });
    return {
        sessionId,
        sessionFile: join(traceDir, `${safePathPart(sessionId)}.jsonl`),
        parentSessionFile
    };
}
/** Resolve (and create) the markdown output file path for a registered task. */
export function createTaskOutputFile(session, agentId) {
    const parentSessionFile = session.getSessionFile();
    const parentBase = parentSessionFile
        ? safePathPart(basename(parentSessionFile, '.jsonl'))
        : 'no-session';
    const outputDir = join(session.getSessionDir(), 'outputs', parentBase);
    mkdirSync(outputDir, { recursive: true });
    return join(outputDir, `${safePathPart(agentId)}.md`);
}
export class TaskSidechainWriter {
    options;
    leafId = null;
    turnIndex = 0;
    turnStartedAt = Date.now();
    usedIds = new Set();
    /** Open or resume the sidechain JSONL file for a subagent run. */
    constructor(options) {
        this.options = options;
        // Resume path: re-link the leafId chain to the last entry in the file so
        // appended turns continue the existing parentId thread. Skip rewriting
        // the header/system prompt/tool definitions.
        if (options.append && existsSync(options.sessionFile)) {
            this.restoreLeafFromExistingFile(options.sessionFile);
            return;
        }
        const header = {
            type: 'session',
            version: CURRENT_SESSION_VERSION,
            id: options.sessionId,
            timestamp: new Date().toISOString(),
            cwd: options.cwd,
            parentSession: options.parentSessionFile,
            isSubagent: true
        };
        writeFileSync(options.sessionFile, `${JSON.stringify(header)}\n`, 'utf-8');
        this.append({
            type: 'system_prompt',
            content: options.systemPrompt,
            ...this.nextBase()
        });
        this.append({
            type: 'tool_definitions',
            tools: options.tools.map((tool) => ({
                name: tool.name,
                description: tool.description,
                parameters: tool.parameters
            })),
            ...this.nextBase()
        });
    }
    /** Persist a turn-start boundary entry. */
    writeTurnStart() {
        this.turnIndex++;
        this.turnStartedAt = Date.now();
        this.append({ type: 'turn_start', turnIndex: this.turnIndex, ...this.nextBase() });
    }
    /** Persist a finalized agent message. */
    writeMessage(message) {
        this.append({ type: 'message', message, ...this.nextBase() });
    }
    /** Persist a tool execution end record (result + isError). */
    writeToolExecutionEnd(input) {
        this.append({
            type: 'tool_execution_end',
            toolCallId: input.toolCallId,
            toolName: input.toolName,
            result: input.result,
            isError: input.isError,
            ...this.nextBase()
        });
    }
    /** Persist a turn-end boundary entry with the wall-clock duration. */
    writeTurnEnd() {
        const durationMs = Math.max(0, Date.now() - this.turnStartedAt);
        this.append({
            type: 'turn_end',
            turnIndex: this.turnIndex,
            durationMs,
            ...this.nextBase()
        });
    }
    /** Allocate a new entry id chained to the current leaf and stamp a timestamp. */
    nextBase() {
        const id = nextId(this.usedIds);
        const parentId = this.leafId;
        this.leafId = id;
        return { id, parentId, timestamp: new Date().toISOString() };
    }
    /** Append an entry to the JSONL file. */
    append(entry) {
        appendJsonl(this.options.sessionFile, entry);
    }
    /** Re-link the leaf id and turn index from an existing transcript when resuming. */
    restoreLeafFromExistingFile(sessionFile) {
        const entries = parseSessionEntries(readFileSync(sessionFile, 'utf-8'));
        for (const entry of entries) {
            if ('id' in entry && typeof entry.id === 'string') {
                this.usedIds.add(entry.id);
                this.leafId = entry.id;
            }
            if ((entry.type === 'turn_start' || entry.type === 'turn_end') &&
                typeof entry.turnIndex === 'number') {
                this.turnIndex = Math.max(this.turnIndex, entry.turnIndex);
            }
        }
    }
}
/** Read prior agent messages from a sidechain transcript for resume runs. */
export function readTaskSidechainMessages(sessionFile) {
    if (!existsSync(sessionFile))
        return [];
    return parseSessionEntries(readFileSync(sessionFile, 'utf-8'))
        .filter((entry) => entry.type === 'message')
        .map((entry) => entry.message);
}
//# sourceMappingURL=sidechain.js.map