import { ClaudeAgentSession } from "./engines/claude/session.js";
import { PiCodingAgentSession } from "./engines/pi-coding/session.js";
import { getAgentEngine } from "./engines/engine-factory.js";
import { getRuntimeLabel } from "./model-provider.js";
import { loadOwnerConfig, rotateSessionId } from "../config/index.js";
import { loadMailbox, setActiveSession, addMemo, consumeMemosFor, ackMemos, updateCodingSessionInfo, } from "./mailbox.js";
import { logger } from "../logger.js";
import { getAgentState } from "./state.js";
const GENERAL_SESSION_ADDENDUM = `

## Session-Specific Instructions (General Session)

You are currently in the **general session**. This session handles:
- Conversations and quick questions from the owner
- Status checks, scheduling, and calendar tasks
- Simple one-off tasks (send an email, look something up, etc.)
- Monitoring and proactive behavior during heartbeats

If the owner asks for a complex, multi-step coding task (refactoring, debugging, multi-file feature work), use \`switch_session(target="coding")\` to hand it off. Do NOT attempt sustained coding work in this session — it will pollute your context.

When switching to the coding session for a task that involves a specific project or repository, pass the project's absolute path as the \`cwd\` parameter (e.g. \`switch_session(target="coding", cwd="/Users/jessica/myproject", memo="...")\`). This sets the coding session's working directory so Bash commands default to the project root.

When the coding session is active, you can tell the owner its status. If they send a message related to the active coding task (e.g. "actually use a different approach"), forward it via \`switch_session\` with the details in the memo.`;
function buildCodingSessionAddendum(config) {
    const ownerConfig = loadOwnerConfig();
    const runtimeLabel = getRuntimeLabel(config);
    return `

## VisionClaw Context

You are **${config.agentName}**, a personal desktop assistant built on VisionClaw (powered by ${runtimeLabel}). You are the **coding session** of a dual-session agent running on ${process.platform === "darwin" ? "macOS" : "Windows"}.

- **Your email**: ${config.gmail}
- **Owner**: ${ownerConfig.ownerName}${ownerConfig.ownerEmail ? ` <${ownerConfig.ownerEmail}>` : ""}

You have full control of the computer: terminal, filesystem, browser (Playwright MCP), desktop (computer_use_* tools), and custom VisionClaw tools (notify_user, memory, manage_skills, etc.).

When you need to communicate with the owner, use the \`notify_user\` tool — do NOT try to print to stdout or use other mechanisms.

## Session-Specific Instructions (Coding Session)

You are currently in the **coding session** of a dual-session agent architecture. This session is reserved for focused, multi-step coding work. Your context is protected from unrelated messages.

Guidelines:
- Stay focused on the current coding task. Do not get sidetracked by unrelated messages.
- If an unrelated message arrives (chat, question, non-coding request), immediately call \`switch_session(target="general", reason="handoff")\` to hand it off. Include enough context in the memo so the general session can reply.
- Break your work into clear steps. Save progress to memory or plan files so you can recover if the context is compacted.
- When the coding task is complete, call \`switch_session(target="general", reason="complete", memo="Task completed: <summary>")\` to return.
- Use any available provider-specific coding or delegation tools when they are useful, but keep orchestration in this session.

## Memory Protocol

IMPORTANT: ALWAYS VIEW YOUR MEMORY DIRECTORY BEFORE DOING ANYTHING ELSE.
1. Use the \`memory\` tool with command \`view\` and path \`/memories\` to check for earlier progress or context.
2. Work on the task as needed.
3. As you make progress, record status, progress, and important information in your memory.

ASSUME INTERRUPTION: Your context window might be compacted at any moment, so you risk losing any progress that is not recorded in your memory directory. Keep memory content up-to-date, coherent, and organized. Delete files that are no longer relevant.`;
}
/**
 * Manages two AgentSessions (general + coding) with cooperative switching.
 *
 * Only one session is active at a time. The agent decides when to switch
 * by calling the switch_session tool. The manager handles suspending the
 * current session and resuming the target.
 */
export class SessionManager {
    config;
    buildBaseSystemPrompt;
    /** Resolved runtime tool and external MCP surface for this model/provider pair. */
    runtimeSurface;
    _requestedDualSessionEnabled;
    _androidUseEnabled;
    generalSession;
    codingSession = null;
    _activeMode = "general";
    _managerStopRequested = false;
    /** Set after a switch_session tool call is detected in the stream. */
    _pendingSwitch = null;
    /** Propagated to all sessions for auto-screenshot after Playwright tools. */
    _screenshotInjector = null;
    buildCodingSystemPrompt() {
        if (getAgentEngine(this.config) !== "claude") {
            return this.buildBaseSystemPrompt() + buildCodingSessionAddendum(this.config);
        }
        return {
            type: "preset",
            preset: "claude_code",
            append: buildCodingSessionAddendum(this.config),
        };
    }
    buildGeneralSystemPrompt() {
        return this.dualSessionEnabled
            ? this.buildBaseSystemPrompt() + GENERAL_SESSION_ADDENDUM
            : this.buildBaseSystemPrompt();
    }
    buildSessionContext(mode) {
        return {
            mode,
            getDualSessionEnabled: () => this.dualSessionEnabled,
            getAndroidUseEnabled: () => this._androidUseEnabled,
            getCardAvailable: () => {
                try {
                    return getAgentState().cardAvailable;
                }
                catch {
                    return false;
                }
            },
        };
    }
    createSession(buildPrompt, mode) {
        const engine = getAgentEngine(this.config);
        if (engine === "pi-coding") {
            return new PiCodingAgentSession({
                config: this.config,
                buildSystemPrompt: buildPrompt,
                runtimeSurface: this.runtimeSurface,
                sessionContext: this.buildSessionContext(mode),
            });
        }
        return new ClaudeAgentSession({
            config: this.config,
            buildSystemPrompt: buildPrompt,
            runtimeSurface: this.runtimeSurface,
            sessionContext: this.buildSessionContext(mode),
        });
    }
    constructor(config, buildSystemPrompt, runtimeSurface, options) {
        this.config = config;
        this.buildBaseSystemPrompt = buildSystemPrompt;
        this._requestedDualSessionEnabled = options.dualSessionEnabled;
        this._androidUseEnabled = options.androidUseEnabled;
        this.runtimeSurface = runtimeSurface;
        this.generalSession = this.createSession(() => this.buildGeneralSystemPrompt(), "general");
        if (this.dualSessionEnabled) {
            const mailbox = loadMailbox();
            this._activeMode = mailbox.activeSession;
            if (mailbox.codingSession.status === "in_progress" || mailbox.codingSession.status === "paused") {
                this.codingSession = this.createSession(() => this.buildCodingSystemPrompt(), "coding");
            }
        }
    }
    get dualSessionEnabled() {
        return this._requestedDualSessionEnabled
            && this.runtimeSurface.visionClawToolTransport.supportsSwitchSessionTool;
    }
    /**
     * Toggle dual-session mode at runtime. Takes effect on the next wake cycle
     * because each query() call rebuilds its tool surface.
     */
    setDualSessionEnabled(enabled) {
        this._requestedDualSessionEnabled = enabled;
        if (!this.dualSessionEnabled && this._activeMode === "coding") {
            this._activeMode = "general";
            setActiveSession("general");
            logger.system("Dual session disabled — forced switch to general");
        }
    }
    /**
     * Set the screenshot injector that auto-injects screenshots after
     * Playwright tool calls. Propagated to all existing sessions.
     */
    setScreenshotInjector(injector) {
        this._screenshotInjector = injector;
        this.generalSession.screenshotInjector = injector;
        if (this.codingSession) {
            this.codingSession.screenshotInjector = injector;
        }
    }
    get activeMode() {
        return this._activeMode;
    }
    get activeSession() {
        if (this._activeMode === "coding" && this.codingSession) {
            return this.codingSession;
        }
        return this.generalSession;
    }
    get pendingSwitch() {
        return this._pendingSwitch;
    }
    /**
     * Record a switch request detected from the stream.
     * The main loop should check this after each stream ends and act on it.
     */
    setSwitchRequest(req) {
        this._pendingSwitch = req;
    }
    /**
     * Execute a pending session switch.
     * - Writes the memo to the mailbox
     * - Creates the coding session if it doesn't exist
     * - Updates the active mode
     * Returns the forwarded message content to send to the new session.
     */
    executePendingSwitch() {
        if (!this.dualSessionEnabled)
            return null;
        const req = this._pendingSwitch;
        if (!req)
            return null;
        this._pendingSwitch = null;
        const currentMode = this._activeMode;
        if (req.memo) {
            addMemo(currentMode, req.target, req.memo);
        }
        if (req.target === "coding") {
            this.codingSession ??= this.createSession(() => this.buildCodingSystemPrompt(), "coding");
            if (this._screenshotInjector) {
                this.codingSession.screenshotInjector = this._screenshotInjector;
            }
            updateCodingSessionInfo({
                status: "in_progress",
                taskSummary: req.memo || "Coding task in progress",
                ...(req.cwd ? { cwd: req.cwd } : {}),
            });
        }
        else if (currentMode === "coding") {
            if (req.reason === "complete") {
                updateCodingSessionInfo({ status: "completed" });
            }
            else {
                updateCodingSessionInfo({ status: "paused" });
            }
        }
        this._activeMode = req.target;
        setActiveSession(req.target);
        logger.system(`Session switch: ${currentMode} → ${req.target}`);
        return { target: req.target, forwardMessage: req.forwardMessage };
    }
    /**
     * Get any unread memos for the currently active session.
     * Call this before sending a message to the active session so
     * the memos can be prepended to the context.
     */
    consumeMemosForActive() {
        if (!this.dualSessionEnabled)
            return null;
        const memos = consumeMemosFor(this._activeMode);
        if (memos.length === 0)
            return null;
        const lines = ["## Messages from the other session"];
        for (const m of memos) {
            lines.push(`[${m.from} session at ${m.timestamp}]: ${m.content}`);
        }
        return lines.join("\n");
    }
    /**
     * Acknowledge that consumed memos for the active session have been
     * successfully injected. Removes them from the mailbox on disk.
     */
    ackMemosForActive() {
        if (!this.dualSessionEnabled)
            return;
        ackMemos(this._activeMode);
    }
    /**
     * End the coding session (task complete or abandoned).
     * Switches back to general mode.
     */
    endCodingSession(summary) {
        updateCodingSessionInfo({ status: "completed", taskSummary: summary });
        if (this._activeMode === "coding") {
            addMemo("coding", "general", `Coding task completed: ${summary}`);
            this._activeMode = "general";
            setActiveSession("general");
            logger.system("Coding session ended, switched to general");
        }
    }
    /**
     * Discard pre-warmed subprocesses on every session so the next turn
     * rebuilds them from the current `config`. Use after mutating provider-
     * bound state (e.g. client-side provider fallback) — without this, the
     * Claude warm subprocess can silently keep using the previous provider
     * for one more turn.
     */
    discardAllWarmQueries() {
        this.generalSession.discardWarmQuery();
        this.codingSession?.discardWarmQuery();
    }
    requestStop() {
        this._managerStopRequested = true;
        this.activeSession.requestStop();
    }
    clearStop() {
        this._managerStopRequested = false;
        this.activeSession.clearStop();
    }
    isStopRequested() {
        return this._managerStopRequested || this.activeSession.isStopRequested();
    }
    getCodingSessionStatus() {
        if (!this.dualSessionEnabled)
            return null;
        const mailbox = loadMailbox();
        const cs = mailbox.codingSession;
        if (!cs.taskSummary)
            return null;
        if (cs.status === "in_progress")
            return `Coding session is active: ${cs.taskSummary}`;
        if (cs.status === "paused")
            return `Coding session is paused: ${cs.taskSummary}`;
        return null;
    }
    /**
     * Reset the active session: rotates the current session ID to
     * `previousSessionIds`, clears the active session ID, and passes the
     * summary to the session object for injection into the next turn.
     *
     * The next `sendAndStream` will create a brand-new SDK session.
     */
    resetActiveSession(summary) {
        this.resetSession(this._activeMode, summary);
    }
    /**
     * Mode-aware variant of {@link resetActiveSession}. Rotates the
     * mode's persisted session id into `previousSessionIds`, then hands
     * `summary` to the corresponding session object so it is prepended to
     * the next turn as priority context.
     *
     * Used by `/refresh` (via {@link resetActiveSession}, which targets the
     * currently-active mode).
     *
     * No-op when the target session has not been constructed (the coding
     * session is lazy-created when the mailbox marks it active).
     */
    resetSession(mode, summary) {
        const target = mode === "coding" ? this.codingSession : this.generalSession;
        if (!target)
            return;
        rotateSessionId(mode);
        target.resetForNewSession(summary);
        logger.system(`Session reset (mode=${mode}), summary ${summary.length} chars`);
    }
    /**
     * Inject a summary into a session WITHOUT rotating its session id.
     *
     * Used by the startup engine-switch boundary: rotation and mailbox
     * clearing happen *before* `SessionManager` is constructed (so the
     * manager sees the post-collapse world), and then this method just
     * threads the combined summary into the freshly created session's
     * first turn.
     *
     * No-op when the target session has not been constructed.
     */
    injectSummary(mode, summary) {
        const target = mode === "coding" ? this.codingSession : this.generalSession;
        if (!target)
            return;
        target.injectSummary(summary);
        logger.system(`[session-manager] Injected summary into ${mode} session (${summary.length} chars)`);
    }
}
//# sourceMappingURL=session-manager.js.map