/**
 * pi-harness-runtime — Codex-style /usage status for pi.
 *
 * Slash commands:
 *   /usage         — show full status (model, local tracking, provider mirror)
 *   /usage today   — focused: this 5h + today (UTC)
 *   /usage week    — focused: this week + lifetime
 *   /usage reset   — clear mirror (forces a fresh auto fetch)
 *
 * Auto-tracks every assistant message via the message_end event.
 * Stores data in ~/.pi/usage-status/  (override with PI_USAGE_DIR for testing).
 *
 * Runs directly from Bun.
 */

import type {
	CompactOptions,
	ExtensionAPI,
	ExtensionCommandContext,
	ExtensionContext,
} from "@earendil-works/pi-coding-agent";
import { UsageTracker } from "./tracker.ts";
import { MirrorStore, type MirrorRecord } from "./mirror.ts";
import { MiniMaxQuotaScraper } from "./harness/e2e/minimax-quota-scraper.js";
import { OpenAIQuotaScraper } from "./harness/e2e/openai-quota-scraper.js";
import { GLMQuotaScraper } from "./harness/e2e/glm-quota-scraper.js";
import { parseMiniMaxQuotaText } from "./harness/e2e/minimax-quota-parser.js";
import {
	CookieWatcher,
	DEFAULT_DROP_DIR as COOKIE_DROP_DIR,
	hasAnyCookieSource as sanitizerHasAnyCookieSource,
} from "./packages/cookie-sanitizer/src/index.ts";
import {
	providerFromModelId,
	type ProviderId,
} from "./packages/providers/src/provider-id.ts";
import {
	TUIUsageMonitor,
	type TUIUsageSignal,
} from "./packages/quota-manager/src/tui-usage-monitor.ts";
import { QuotaManager } from "./packages/quota-manager/src/quota-manager.ts";
import { buildFooterStatusValue } from "./footer-status.ts";
import { registerGithubLoginCommand } from "./packages/clipboard/src/github-login.js";
import {
	MAX_PROACTIVE_COMPACT_FAILURES,
	OUTPUT_LIMIT_RESUME_PROMPT,
	PROVIDER_OVERLOAD_RESUME_PROMPT,
	getProviderOverloadResumeDelayMs,
	PROACTIVE_COMPACT_COOLDOWN_MS,
	shouldQueueOutputLimitResume,
	shouldQueueProviderOverloadResume,
	shouldQueuePostCompactionResume,
	shouldTriggerProactiveCompact,
} from "./proactive-compact.ts";
import { aggregateWindows } from "./windows.ts";
import { renderStatus } from "./renderer.ts";
import {
	JobStateMachine,
	type CheckpointManager,
} from "./harness/job-state-machine.ts";
import {
	createTaskGraphManager,
	type TaskGraphManager,
} from "./harness/task-graph.js";
import { MasterPlanner } from "./harness/master-planner.ts";
import { RepairEngine } from "./harness/repair-engine.ts";
import {
	type SharedBlackboard,
	createBlackboard,
} from "./harness/blackboard.ts";
import { scheduleAutoResume, cancelAutoResume, getGLMQuotaCountdown } from "./harness/index.js";
import { startWatcher, classifyMessage } from "./packages/codex-watcher/dist/src/index.js";
import {
	getPaperclipConfig,
	hasPaperclipConfig,
	PaperclipWatcher,
} from "./packages/paperclip-integration/dist/index.js";

// --- Jev Auto-Continue: Agent autonomous decision-making --------------------
// Lazy import - only loads when packages/jev-judge exists and API key available

/**
 * Get tasks from bd (beads) for Jev decision context.
 * Returns { completed, remaining } arrays of task descriptions.
 */
async function getTaskContext(): Promise<{
	completed: string[];
	remaining: string[];
	total: number;
}> {
	try {
		const { execSync } = await import("node:child_process");
		// Get open (pending + in_progress) tasks
		const output = execSync("bd list --json 2>/dev/null", {
			encoding: "utf8",
			timeout: 5000,
		});
		const tasks = JSON.parse(output) as Array<{ title?: string; status?: string }>;

		const completed: string[] = [];
		const remaining: string[] = [];

		for (const task of tasks ?? []) {
			if (!task.title) continue;
			if (task.status === "closed" || task.status === "done") {
				completed.push(task.title);
			} else {
				remaining.push(task.title);
			}
		}

		return {
			completed,
			remaining,
			total: completed.length + remaining.length,
		};
	} catch {
		// bd not available or error - return empty context
		return { completed: [], remaining: [], total: 0 };
	}
}

async function initAutoContinue(pi: ExtensionAPI): Promise<void> {
	try {
		// Check if Jev API key is available (env var or keys file)
		const homedir = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
		const keysDir = `${homedir}/.pi-harness-runtime/keys`;
		const { readFileSync, existsSync, mkdirSync } = await import("fs");

		// Auto-create directory if doesn't exist
		if (!existsSync(keysDir)) {
			mkdirSync(keysDir, { recursive: true });
		}

		const hasEnvKey =
			process.env.TYPESAFE_API_KEY || process.env.OPENROUTER_API_KEY;
		const hasFileKey =
			existsSync(`${keysDir}/jev-api-key.txt`) &&
			readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim().length > 10;

		if (!hasEnvKey && !hasFileKey) {
			console.error(
				"[auto-continue] No Jev API key found. Set TYPESAFE_API_KEY run: echo \"{api_key}\" > ~/.pi-harness-runtime/keys/jev-api-key.txt"
			);
			return;
		}

		// Resolve the actual API key: env takes priority, file is fallback
		const resolvedApiKey = process.env.TYPESAFE_API_KEY ||
			process.env.OPENROUTER_API_KEY ||
			readFileSync(`${keysDir}/jev-api-key.txt`, "utf8").trim();

		const mod = await import("./packages/jev-judge/src/auto-continue.js");

		// Track waiting state
		let waitingForUserSince: Date | null = null;
		let lastUserMessage: Date | null = null;
		let autoContinueTimer: NodeJS.Timeout | null = null;
		let _inferenceInProgress = false; // Track for overlap prevention
		let _sessionId = Date.now().toString(36); // Track session for cancellation

		// Detect when agent asks for continuation
		pi.on("message_end", async (event) => {
			const msg = event.message as { role?: string; content?: string | Array<{ type?: string; text?: string }> } | undefined;
			if (!msg || msg.role !== "assistant") return;

			// Extract text from array content (TextContent blocks) or legacy string
			let content = "";
			if (typeof msg.content === "string") {
				content = msg.content;
			} else if (Array.isArray(msg.content)) {
				// Extract text from TextContent blocks only; ignore thinking/tool calls
				content = msg.content
					.filter((block): block is { type: "text"; text: string } =>
						block.type === "text" && typeof block.text === "string"
					)
					.map(block => block.text)
					.join("\n");
			}
			const hasContinuation = /continue|proceed|next phase|next step/i.test(content);

			if (hasContinuation && !waitingForUserSince) {
				waitingForUserSince = new Date();
				console.log("[auto-continue] Agent waiting for continuation confirmation");
				// Emit WaitingForUserInput to Telegram so user knows to respond
				nc("WaitingForUserInput", {
					requirement: "Agent awaiting your response",
					error: "Continue / proceed confirmation needed",
				});
			}
		});

		// Detect user responses
		pi.on("message_start", async (event) => {
			const msg = event.message as { role?: string } | undefined;
			if (msg?.role === "user") {
				lastUserMessage = new Date();
				if (waitingForUserSince) {
					console.log("[auto-continue] User responded, cancelling wait");
					waitingForUserSince = null;
				}
			}
		});

		// Periodic check for auto-continue decision
		const AUTO_CONTINUE_INTERVAL_MS = 5 * 60 * 1000; // Check every 5 minutes
		const MIN_WAIT_MINUTES = 5; // Minimum wait before auto-continue

		const checkAndAutoContinue = async () => {
			// Prevent overlap if inference is already in progress
			if (_inferenceInProgress) {
				console.log("[auto-continue] Inference in progress, skipping this check");
				return;
			}

			if (!waitingForUserSince) return;

			// Capture session ID at check time for post-inference validation
			const checkSessionId = _sessionId;

			const waitMinutes = (Date.now() - waitingForUserSince.getTime()) / 60000;
			if (waitMinutes < MIN_WAIT_MINUTES) return;

			console.log(`[auto-continue] Checking after ${waitMinutes.toFixed(0)} minutes wait...`);

			try {
				// Mark inference as in progress to prevent overlap
				_inferenceInProgress = true;

				const judge = new mod.AutoContinueJudge({
					apiKey: resolvedApiKey,
					proceedThreshold: 0.7,
					maxWaitMinutes: 30,
				});

				// Get real task context from bd
				const taskContext = await getTaskContext();

				// Guard against empty task list
				if (taskContext.total === 0) {
					console.log("[auto-continue] No tasks to decide on — skipping");
					_inferenceInProgress = false;
					return;
				}

				const taskState = mod.createTaskState(
					taskContext.completed,
					taskContext.remaining,
					lastUserMessage || undefined,
					new Date(Date.now() - waitMinutes * 60000),
				);

				const decision = await judge.decide(taskState);

				// Check session validity and user response before applying
				if (checkSessionId !== _sessionId) {
					console.log("[auto-continue] Session changed during inference — cancelling");
					_inferenceInProgress = false;
					return;
				}

				if (!waitingForUserSince) {
					console.log("[auto-continue] User responded during inference — cancelling");
					_inferenceInProgress = false;
					return;
				}

				console.log(
					`[auto-continue] Decision: ${decision.action} (${(decision.probability * 100).toFixed(0)}% confidence)`,
				);

				if (decision.action === "proceed") {
					// Send steer message to continue
					pi.sendUserMessage(
						"User is unavailable. Based on task progress and low risk, proceeding autonomously.\n" +
							`Decision: ${decision.reasoning}\n` +
							"Continue with remaining tasks.",
						{ deliverAs: "steer" },
					);
					waitingForUserSince = null;
				} else if (decision.action === "proceed_with_caution") {
					pi.sendUserMessage(
						"User is unavailable. Proceeding with caution - will log each significant step.\n" +
						`Risk level: ${decision.riskLevel}\n` +
						"Continue with remaining tasks, reporting progress.",
						{ deliverAs: "steer" },
					);
					waitingForUserSince = null;
				}
				// If action is "wait", do nothing and check again later
			} catch (error) {
				console.error("[auto-continue] Error:", error);
			}
		};

		// Start periodic check
		autoContinueTimer = setInterval(checkAndAutoContinue, AUTO_CONTINUE_INTERVAL_MS);

		console.error("[auto-continue] Jev Auto-Continue initialized");
	} catch {
		// auto-continue not available
	}
}

// --- todo-bd-sync: Two-way sync between rpiv-todo and bd --------------------
// Lazy import - only loads when packages/todo-bd-sync exists
async function initTodoBdSync(pi: ExtensionAPI): Promise<void> {
	try {
		const mod = await import("./packages/todo-bd-sync/src/extension.js");
		mod.registerTodoBdSync(pi);
	} catch {
		// todo-bd-sync not available
	}
}

// --- config-capture: Auto-detect and document API configuration -------------
// Lazy import - only loads when packages/config-capture exists
async function initConfigCapture(pi: ExtensionAPI): Promise<void> {
	try {
		const mod = await import("./packages/config-capture/src/index.js");
		mod.registerConfigCapture(pi, { debug: false });
	} catch {
		// config-capture not available
	}
}

// --- qdrant-vector-search: Semantic skill search via Qdrant -------------------
// Check config and report status — right after Jev for discoverability
async function initQdrant(): Promise<void> {
	try {
		const { getQdrantConfig } = await import("./packages/skills/src/loader.js");
		const cfg = getQdrantConfig();
		if (cfg) {
			// Check if OpenAI embedding key is available
			const { getOpenAIApiKey } = await import("./packages/qdrant-skills/src/embedder.js");
			const embeddingKey = getOpenAIApiKey();
			if (embeddingKey) {
				logStartup("[pi-harness] Qdrant vector search ready");
			} else {
				logStartup("[pi-harness] Qdrant ready (no embedding key — skills won't be vector-indexed)");
				logStartup('[pi-harness] For vector search: echo "{openai-api-key}" > ~/.pi-harness-runtime/keys/openai-api-key.txt');
			}
		} else {
			logStartup("[pi-harness] Qdrant not configured. Set keys to enable vector skill search:");
			logStartup(`  echo "{cluster-url}" > ~/.pi-harness-runtime/keys/qdrant-cluster-url.txt`);
			logStartup(`  echo "{api-key}" > ~/.pi-harness-runtime/keys/qdrant-api-key.txt`);
		}
	} catch {
		// qdrant-skills not available
	}
}

// --- telegram-notifications: Telegram bot for harness event notifications ------------
// Sends Telegram messages when jobs complete, tasks finish, or human input is needed.
async function initTelegram(): Promise<void> {
	try {
		const { NotificationCenter } = await import("./packages/notification/dist/index.js");
		const { readFileSync, existsSync } = await import("node:fs");
		const home = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
		const keysDir = `${home}/.pi-harness-runtime/keys`;

		const botTokenPath = `${keysDir}/telegram-bot-token.txt`;
		const chatIdPath = `${keysDir}/telegram-chat-id.txt`;

		if (!existsSync(botTokenPath) || !existsSync(chatIdPath)) {
			logStartup("[pi-harness] Telegram (@PiHarnessRuntimeBot) not configured:");
			logStartup("[pi-harness]   1. DM @BotFather → /newbot → name it → get token");
			logStartup("[pi-harness]   2. DM @userinfobot → reply shows your chat ID");
			logStartup("[pi-harness]   3. echo \"{token}\" > ~/.pi-harness-runtime/keys/telegram-bot-token.txt");
			logStartup("[pi-harness]   4. echo \"{chat-id}\" > ~/.pi-harness-runtime/keys/telegram-chat-id.txt");
			logStartup("[pi-harness]   5. Reload pi — then DM @PiHarnessRuntimeBot to verify");
			return;
		}

		const botToken = readFileSync(botTokenPath, "utf8").trim();
		const chatId = readFileSync(chatIdPath, "utf8").trim();

		if (!botToken || !chatId) {
			logStartup("[pi-harness] Telegram: bot token or chat ID is empty — skipping");
			return;
		}

		const center = new NotificationCenter({
			enabled: true,
			channels: [
				{
					id: "telegram",
					type: "telegram",
					enabled: true,
					config: { botToken, chatId },
				},
			],
		});

		await center.initialize();
		if (!center.hasChannels()) {
			logStartup("[pi-harness] Telegram: initialization failed — skipping");
			return;
		}

		// Wire to singleton nc() helper so all event emitters can reach Telegram
		_nc = {
			center,
			notify: (event: string, ctx: Record<string, unknown>) =>
				center.notify(event as never, ctx as never),
		};

		// Wire to GLMQuotaCountdown so it sends Telegram alerts
		try {
			const { getGLMQuotaCountdown } = await import("./harness/index.js");
			getGLMQuotaCountdown().setNotificationCenter(center);
		} catch { /* not critical */ }

		// Log the actual bot username (fetched from Telegram API via getMe)
		const botUsername = center.getTelegramBotUsername();
		if (botUsername) {
			logStartup(`[pi-harness] Telegram (@${botUsername}) ready — DM the bot to receive alerts`);
		} else {
			logStartup(`[pi-harness] Telegram notifications ready`);
		}
	} catch (err) {
		logStartup("[pi-harness] Telegram: init failed:", err instanceof Error ? err.message : String(err));
	}
}

// --- honcho-memory: Honcho MCP for peer/user memory via https://mcp.honcho.dev ------
// Registers Honcho MCP server and implements automatic memory lifecycle:
// - Ingest sanitized messages on completed turns
// - Retrieve relevant context before agent execution
// - Deduplication by message ID
// - Bounded context injection with provenance

/** Module-level Honcho memory instance */
let _honchoMemory: import("./harness/honcho-memory.js").HonchoMemory | null = null;

async function initHoncho(pi: { events: { emit(name: string, data: unknown): void }; on(event: string, cb: () => void): void }): Promise<void> {
	const { readFileSync, existsSync } = await import("node:fs");
	const home = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
	const keyPath = `${home}/.pi-harness-runtime/keys/honcho-api-key.txt`;

	if (!existsSync(keyPath)) {
		logStartup("[pi-harness] Honcho not configured:");
		logStartup("[pi-harness]   Get key: https://app.honcho.dev/api-keys");
		logStartup(`[pi-harness]   Then: echo "{api-key}" > ~/.pi-harness-runtime/keys/honcho-api-key.txt`);
		return;
	}

	const apiKey = readFileSync(keyPath, "utf8").trim();
	if (!apiKey) {
		logStartup("[pi-harness] Honcho: API key is empty — skipping");
		return;
	}

	try {
		// Register Honcho MCP server with pi-mcp-adapter at session start
		pi.on("session_start", () => {
			const request = {
				version: 1 as const,
				name: "honcho",
				definition: {
					url: "https://mcp.honcho.dev",
					auth: "bearer" as const,
					bearerToken: apiKey,
				},
			};

			// Emit registration request — pi-mcp-adapter will respond with
			// pi-mcp-adapter:runtime-register-result event on completion
			pi.events.emit("pi-mcp-adapter:runtime-register:v1", request);
			console.log("[honcho] MCP registration emitted (awaiting adapter ack)");
		});

		// Report pending state — actual memory active requires explicit lifecycle
		logStartup("[pi-harness] Honcho registered (awaiting adapter connection)");
	} catch (err) {
		logStartup("[pi-harness] Honcho: init failed:", err instanceof Error ? err.message : String(err));
	}
}

// --- langchain-loop: Ping-pong write-review loop via harness/langchain/ -----------
// User picks model-role mapping once via /langchain-config.
// Stores selection in ~/.pi-harness-runtime/keys/langchain-role.json.
// /langchain reads from that file and spawns the loop.
function initLangChain(pi: {
	registerCommand(
		name: string,
		cmd: {
			description: string;
			handler: (args: string, ctx: unknown) => Promise<void>;
			prompt?: string;
		},
	): void;
}): void {
	const { readFileSync, writeFileSync, existsSync } = require("node:fs");
	const { spawn } = require("node:child_process");
	const { fileURLToPath } = require("url");
	const { dirname } = require("node:path");
	const readline = require("node:readline");

	const home = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
	const keysDir = `${home}/.pi-harness-runtime/keys`;
	const modelsStorePath = `${home}/.pi/agent/models-store.json`;
	const roleConfigPath = `${keysDir}/langchain-role.json`;

	// ---------------------------------------------------------------------------
	// Load models from models-store.json
	// ---------------------------------------------------------------------------
	interface ModelEntry { id: string; baseUrl: string }
	interface RoleConfig { planner?: string; coder?: string; reviewer?: string }

	let store: Record<string, { models: Array<{ id: string; baseUrl?: string }> }> = {};
	try {
		if (existsSync(modelsStorePath)) {
			store = JSON.parse(readFileSync(modelsStorePath, "utf8"));
		}
	} catch { /* ignore */ }

	// Flat list of all models: "provider/model-id"
	function listAllModels(): Array<{ index: number; provider: string; id: string; baseUrl: string }> {
		const entries: Array<{ index: number; provider: string; id: string; baseUrl: string }> = [];
		let idx = 1;
		for (const [provider, info] of Object.entries(store)) {
			for (const model of info.models ?? []) {
				entries.push({ index: idx++, provider, id: model.id, baseUrl: model.baseUrl ?? "" });
			}
		}
		return entries;
	}

	function resolveModel(spec: string): ModelEntry | null {
		// spec format: "provider/model-id"
		const parts = spec.split("/");
		if (parts.length !== 2) return null;
		const [provider, id] = parts;
		const found = store[provider]?.models?.find((m) => m.id === id);
		if (found) return { id: found.id, baseUrl: found.baseUrl ?? "" };
		return null;
	}

	// ---------------------------------------------------------------------------
	// Load saved role config
	// ---------------------------------------------------------------------------
	function loadRoleConfig(): RoleConfig {
		try {
			if (existsSync(roleConfigPath)) {
				return JSON.parse(readFileSync(roleConfigPath, "utf8"));
			}
		} catch { /* ignore */ }
		return {};
	}

	// ---------------------------------------------------------------------------
	// /langchain-config: interactive model -> role picker
	// ---------------------------------------------------------------------------
	pi.registerCommand("langchain-config", {
		description: "Pick which model is planner / coder / reviewer for the LangChain loop",
		prompt: "Interactive: assign models to planner, coder, reviewer roles",
		handler: async (_args: string, _ctx: unknown) => {
			const allModels = listAllModels();
			if (allModels.length === 0) {
				console.error("[pi-harness] /langchain-config: no models found in ~/.pi/agent/models-store.json");
				console.error("[pi-harness]   Configure models first via /model in pi");
				return;
			}

			// Print available models
			console.error("[pi-harness] Available models:");
			for (const m of allModels) {
				console.error(`  [${m.index}] ${m.provider}/${m.id}`);
			}

			const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
			const question = (q: string): Promise<string> =>
				new Promise((res) => rl.question(`? ${q}: `, (a: string) => res(a.trim())));

			let plannerSpec = "";
			let coderSpec = "";
			let reviewerSpec = "";

			try {
				const plannerIdx = parseInt(await question("Planner model index"), 10);
				const plannerEntry = allModels.find((m) => m.index === plannerIdx);
				if (plannerEntry) plannerSpec = `${plannerEntry.provider}/${plannerEntry.id}`;

				const coderIdx = parseInt(await question("Coder model index"), 10);
				const coderEntry = allModels.find((m) => m.index === coderIdx);
				if (coderEntry) coderSpec = `${coderEntry.provider}/${coderEntry.id}`;

				const reviewerIdx = parseInt(await question("Reviewer model index"), 10);
				const reviewerEntry = allModels.find((m) => m.index === reviewerIdx);
				if (reviewerEntry) reviewerSpec = `${reviewerEntry.provider}/${reviewerEntry.id}`;
			} finally {
				rl.close();
			}

			if (!plannerSpec || !coderSpec || !reviewerSpec) {
				console.error("[pi-harness] /langchain-config: all three roles must be selected");
				return;
			}

			// Save to langchain-role.json
			const config: RoleConfig = {
				planner: plannerSpec,
				coder: coderSpec,
				reviewer: reviewerSpec,
			};
			writeFileSync(roleConfigPath, JSON.stringify(config, null, 2), "utf8");

			console.error("[pi-harness] LangChain role config saved:");
			console.error(`[pi-harness]   planner:   ${plannerSpec}`);
			console.error(`[pi-harness]   coder:     ${coderSpec}`);
			console.error(`[pi-harness]   reviewer:  ${reviewerSpec}`);

			// Also prompt for API keys if missing
			if (!existsSync(`${keysDir}/planner-api-key.txt`)) {
				console.error(`[pi-harness]   planner-api-key.txt missing. echo "{key}" > ${keysDir}/planner-api-key.txt`);
			}
			if (!existsSync(`${keysDir}/reviewer-api-key.txt`)) {
				console.error(`[pi-harness]   reviewer-api-key.txt missing. echo "{key}" > ${keysDir}/reviewer-api-key.txt`);
			}
			if (!existsSync(`${keysDir}/coder-api-key.txt`)) {
				console.error(`[pi-harness]   coder-api-key.txt missing. echo "{key}" > ${keysDir}/coder-api-key.txt`);
			}
		},
	});

	// ---------------------------------------------------------------------------
	// /langchain: spawn the ping-pong loop
	// ---------------------------------------------------------------------------

	function loadActiveConfig(): { planner: ModelEntry; coder: ModelEntry; reviewer: ModelEntry } | null {
		const cfg = loadRoleConfig();
		if (!cfg.planner || !cfg.coder || !cfg.reviewer) return null;
		const planner = resolveModel(cfg.planner);
		const coder = resolveModel(cfg.coder);
		const reviewer = resolveModel(cfg.reviewer);
		if (!planner || !coder || !reviewer) return null;
		return { planner, coder, reviewer };
	}

	const activeConfig = loadActiveConfig();

	// Check API keys exist
	const plannerKeyExists = existsSync(`${keysDir}/planner-api-key.txt`);
	const reviewerKeyExists = existsSync(`${keysDir}/reviewer-api-key.txt`);
	const coderKeyExists = existsSync(`${keysDir}/coder-api-key.txt`);

	if (!activeConfig) {
		console.error("[pi-harness] /langchain not configured — run /langchain-config");
	} else if (!plannerKeyExists || !reviewerKeyExists || !coderKeyExists) {
		const missingKeys: string[] = [];
		if (!plannerKeyExists) missingKeys.push("planner-api-key.txt");
		if (!reviewerKeyExists) missingKeys.push("reviewer-api-key.txt");
		if (!coderKeyExists) missingKeys.push("coder-api-key.txt");
		console.error(`[pi-harness] /langchain missing API keys: ${missingKeys.join(", ")}`);
	} else {
		logStartup(`[pi-harness] /langchain ready (${activeConfig.planner.id} + ${activeConfig.coder.id} + ${activeConfig.reviewer.id})`);
	}

	pi.registerCommand("langchain", {
		description: "Run LangChain ping-pong loop: planner → coder → reviewer → fix → approve",
		prompt: "Describe the feature or task for the LangChain loop to implement.",
		handler: async (request: string) => {
			if (!request.trim()) {
				console.error("[pi-harness] /langchain requires a request.");
				console.error("[pi-harness]   Example: /langchain implement user auth module");
				return;
			}

			const cfg = loadActiveConfig();
			if (!cfg) {
				console.error("[pi-harness] /langchain: not configured. Run /langchain-config first.");
				return;
			}

			const env: Record<string, string> = { ...(process.env as Record<string, string>) };
			env.PLANNER_API_KEY = readFileSync(`${keysDir}/planner-api-key.txt`, "utf8").trim();
			env.GLM_API_KEY = readFileSync(`${keysDir}/reviewer-api-key.txt`, "utf8").trim();
			env.MINIMAX_API_KEY = readFileSync(`${keysDir}/coder-api-key.txt`, "utf8").trim();
			env.PLANNER_MODEL = cfg.planner.id;
			env.PLANNER_BASE_URL = cfg.planner.baseUrl;
			env.GLM_MODEL = cfg.reviewer.id;
			env.GLM_BASE_URL = cfg.reviewer.baseUrl;
			env.MINIMAX_MODEL = cfg.coder.id;
			env.MINIMAX_BASE_URL = cfg.coder.baseUrl;

			if (existsSync(`${keysDir}/langsmith-api-key.txt`)) {
				env.LANGSMITH_API_KEY = readFileSync(`${keysDir}/langsmith-api-key.txt`, "utf8").trim();
			}

			const runtimeRoot = dirname(fileURLToPath(import.meta.url));
			const runScript = `${runtimeRoot}/harness/langchain/run.ts`;

			const proc = spawn("bun", ["run", runScript, "--mode", "graph", "--request", request], {
				env,
				stdio: "inherit",
			});
			proc.on("exit", (code: number | null) => {
				if (code !== 0) console.error(`[pi-harness] /langchain exited with code ${code}`);
			});
		},
	});
}

// --- moocoding-sync-hint: Suggest syncing skills if skills dir is empty --------
// Fires after Jev + Qdrant so all three appear together in startup
function initMoocodingSyncHint(): void {
	try {
		const { readdirSync, existsSync } = require("node:fs");
		const { join } = require("node:path");
		const { homedir } = require("node:os");

		const harnessDir = join(homedir(), ".pi-harness-runtime", "skills");
		if (!existsSync(harnessDir)) return;

		const entries: string[] = readdirSync(harnessDir).filter((n: string) => n !== "harness-runtime");
		if (entries.length > 0) return; // Has user skills, nothing to suggest

		const benchPath = join(homedir(), "frappe-bench", ".claude-plugins", "moocoding-skills", "skills");
		if (!existsSync(benchPath)) return;

		const benchEntries: string[] = readdirSync(benchPath).filter((n: string) =>
			existsSync(join(benchPath, n, "SKILL.md"))
		);
		if (benchEntries.length === 0) return;

		const targetPath = join(homedir(), ".pi-harness-runtime", "skills");
		console.error(
			`[pi-harness] ${benchEntries.length} moocoding skill(s) found in frappe-bench`
		);
		console.error(
			`[pi-harness] Run /sync-skills --from ${benchPath} --to ${targetPath}`
		);
	} catch {
		// Non-fatal — ignore errors
	}
}

// --- write-review: Two-agent write with review loop --------------------------
// Lazy import - only loads when packages/write-review exists
async function initWriteReview(pi: ExtensionAPI): Promise<void> {
	try {
		const mod = await import("./packages/write-review/src/index.js");
		mod.injectWriterInstructions(pi);
	} catch {
		// write-review not available
	}
}

// --- file-copy-helper: Inject cp rule when mimicking files --------------------
// Lazy import - only loads when packages/file-copy-helper exists
async function initFileCopyHelper(pi: ExtensionAPI): Promise<void> {
	try {
		const mod = await import("./packages/file-copy-helper/src/extension.js");
		mod.registerFileCopyHelper(pi);
	} catch {
		// file-copy-helper not available
	}
}

// --- agent-policy: Mandatory rule delivery and mutation gating ------------------
// Lazy import - only loads when packages/agent-policy exists
// This replaces firstAgentStart-only injection with:
// - System prompt injection on EVERY agent start (not just first)
// - Policy receipt tracking per session
// - Mutation gating until policy revision is confirmed received
// - harness_rules tool for rule retrieval and acknowledgement
async function initAgentPolicy(pi: ExtensionAPI): Promise<void> {
	try {
		const { registerAgentPolicy } = await import("./packages/agent-policy/src/extension.js");
		registerAgentPolicy(pi, {
			harnessRulesPath: new URL("./AGENTS.md", import.meta.url),
			requireReceiptBeforeMutation: true,
			lspMode: "pi-lens",
		});
		logStartup("[pi-harness] Agent policy initialized");
	} catch (err) {
		logStartup(`[pi-harness] Agent policy init failed: ${err}`);
	}
}

// --- SSH detach interceptor: Auto-transform bare ssh ... & to detached pattern ---
// Prevents 2000+ second hangs when SSH background commands aren't detached.
// Intercepts every bash tool call before execution and rewrites risky SSH commands.
//
// Also guards foreground `ssh ... "<daemon>"` commands (pollers, servers) by
// prefixing `timeout Ns` so the local call always returns (PI_HARNESS_SSH_TIMEOUT_S).
async function initSshDetachInterceptor(pi: ExtensionAPI): Promise<void> {
	try {
		const interceptor = await import("./harness/ssh-detach-interceptor.js");
		// Register on tool_call with default priority (100)
		// NOTE: module functions are captured in closure scope — do NOT use require()
		// here; this package is ESM ("type": "module") and require is undefined.
		pi.on("tool_call", (event, _api) => {
			if (event.toolName !== "bash") return {};
			const input = event.input as { command?: string; timeout?: number };
			const command = input.command ?? "";
			if (!interceptor.isSshCommand(command)) return {};

			// 1) Detach transform: bare `ssh ... &` or `ssh "a && b &"` chains
			if (
				!interceptor.isDetachedPattern(command) &&
				(interceptor.hasBareAmpersand(command) ||
					interceptor.hasCommandChainWithAmpersand(command))
			) {
				const transformed = interceptor.transformToDetached(command);
				if (transformed !== command) {
					input.command = transformed;
					return {};
				}
			}

			// 2) Timeout guard: foreground SSH running a daemon-ish remote command
			//    (e.g. `ssh -tt host "python -m amos_worker --video | head -10"`)
			if (interceptor.isForegroundDaemonSsh(command)) {
				const seconds = interceptor.getDefaultSshTimeoutSeconds();
				input.command = interceptor.addSshTimeoutGuard(command, seconds);
				// Also cap the bash tool's own timeout as belt-and-suspenders
				if (input.timeout === undefined || input.timeout > seconds) {
					input.timeout = seconds;
				}
			}
			return {};
		});
	} catch {
		// ssh-detach-interceptor not available
	}
}

// --- loop-completions: Watch daemon loop completions → update TUI todos ---------
// When the daemon loop finishes, it writes a completion event to
// ~/.pi-harness-runtime/loop-completions/. The harness extension watches this
// dir and sends a steer message to the agent so the TUI todo count updates.
async function initLoopCompletions(pi: ExtensionAPI): Promise<void> {
	const { existsSync, mkdirSync, watch } = await import("node:fs");
	const { join: joinPath } = await import("node:path");
	const { homedir: getHomeDir } = await import("node:os");

	const COMPLETION_DIR = joinPath(
		getHomeDir(),
		".pi-harness-runtime",
		"loop-completions",
	);

	try {
		if (!existsSync(COMPLETION_DIR)) {
			mkdirSync(COMPLETION_DIR, { recursive: true });
		}
	} catch {
		return; // can't create dir — skip
	}

	// Process any pre-existing files (e.g. from a previous session)
	try {
		const { readdirSync } = await import("node:fs");
		const files = readdirSync(COMPLETION_DIR).filter((f) => f.endsWith(".json"));
		for (const file of files) {
			processCompletionFile(joinPath(COMPLETION_DIR, file), pi);
		}
	} catch {
		// ignore — best-effort
	}

	// Watch for new completion files
	try {
		const watcher = watch(
			COMPLETION_DIR,
			{ persistent: false },
			(event, filename) => {
				if (event !== "rename") return;
				if (!filename || !filename.endsWith(".json")) return;
				const filePath = joinPath(COMPLETION_DIR, filename);
				// Delay so the write finishes before we read
				setTimeout(() => processCompletionFile(filePath, pi), 500);
			},
		);
		watcher.on("error", () => {});
	} catch {
		// fs.watch not available — skip
	}
}

/**
 * Initialize Hermes-style skill system
 * - Register skill commands with pi
 * - Scan skills from ~/.pi-harness-runtime/skills/
 */
async function initHermesSkills(pi: ExtensionAPI): Promise<void> {
	try {
		// Dynamically import to avoid circular deps
		const { initSkills } =
			await import("./packages/skills/src/index.js");

		// Scan skills from default locations
		const result = initSkills();
		logStartup(`[pi-harness] Scanned ${result.skills.length} skills`);

		// Register /skill commands
		try {
			const { initSkillCommands } = await import("./packages/skills/src/skill-commands.js");
			initSkillCommands(pi);
			logStartup("[pi-harness] Registered skill commands");

			// Register harness services diagnostic commands
			try {
				const { initServiceCommands } = await import("./harness/service-commands.js");
				initServiceCommands(pi);
				logStartup("[pi-harness] Registered service diagnostic commands");
			} catch (err) {
				logStartup(`[pi-harness] Failed to register service commands:`, err instanceof Error ? err.message : String(err));
			}
		} catch (err) {
			logStartup(`[pi-harness] Failed to register skill commands:`, err instanceof Error ? err.message : String(err));
		}

	} catch (err) {
		logStartup(`[pi-harness] Failed to initialize skills:`, err instanceof Error ? err.message : String(err));
	}
}

interface LoopCompletion {
	taskId: string;
	request: string;
	verdict: string;
	iterations: number;
	finishedAt: string;
}

function processCompletionFile(filePath: string, pi: ExtensionAPI): void {
	const { existsSync, readFileSync, unlinkSync } = require("node:fs");
	try {
		if (!existsSync(filePath)) return;
		const raw = readFileSync(filePath, "utf8");
		const completion = JSON.parse(raw) as LoopCompletion;

		const tag =
			completion.verdict === "approved"
				? "[ok]"
				: completion.verdict === "blocked"
					? "[blocked]"
					: "[done]";

		const message =
			`Loop completed ${tag}: ${completion.taskId}\n` +
			`  verdict: ${completion.verdict}\n` +
			`  iterations: ${completion.iterations}\n` +
			`  task: ${completion.request.slice(0, 80)}${completion.request.length > 80 ? "..." : ""}\n\n` +
			`Run \`bd close ${completion.taskId} --reason "${completion.verdict}"\` if not already closed.`;

		try {
			pi.sendUserMessage(message, { deliverAs: "steer" });
		} catch {
			// ignore — best-effort
		}
		unlinkSync(filePath);
	} catch {
		// ignore — best-effort
	}
}
// --- Debug logging (file only, no console override) ---------------
// Logs written to file only. Real console output preserved for pi's TUI.
import { homedir } from "node:os";
import { existsSync, mkdirSync, readFileSync, symlinkSync } from "node:fs";
import { join, dirname } from "node:path";

// --- Harness Runtime State --------------------------------------------
const HARNESS_ROOT_DIR = join(homedir(), ".pi-harness-runtime");

/** Startup messages collected during init — shown on session_start via ctx.ui.notify */
const startupMessages: string[] = [];

/** Log a harness startup message both to stderr (for logs) and to startupMessages (for UI) */
function logStartup(...parts: string[]): void {
	const msg = parts.join(" ");
	console.error(msg);
	startupMessages.push(msg);
}

interface HarnessSession {
	jobId: string;
	requirement: string;
	machine: JobStateMachine;
	graph: TaskGraphManager;
	blackboard: SharedBlackboard;
	repairEngine: RepairEngine;
	createdAt: string;
}

let currentSession: HarnessSession | null = null;

/** Singleton notification center — set by initTelegram(), used by all emit helpers */
let _nc: { center: unknown; notify: (event: string, ctx: Record<string, unknown>) => Promise<unknown> } | null = null;

/**
 * Fire a Telegram notification event. Silently skips if Telegram is not configured.
 */
async function nc(event: string, ctx: Record<string, unknown>): Promise<void> {
	if (!_nc) return;
	try {
		const nc = _nc as { center: { notifyWithApproval: Function; notify: Function } };

		// Import policy engine dynamically to check if approval buttons needed
		// This is a simple sync check - can be replaced with Jev-powered evaluation
		const { requiresApproval } = await import("./packages/notification/dist/src/notification-policy.js");

		// Use notifyWithApproval() for events that need user input
		if (requiresApproval(event)) {
			await nc.center.notifyWithApproval(event as never, ctx as never);
		} else {
			await nc.center.notify(event as never, ctx as never);
		}
	} catch {
		// Never crash runtime on notification failure
	}
}



/**
 * Safely extract a text view from an LLM message. Used to feed the TUI
 * quota-signal extractor. Handles string content, array-of-parts content
 * (with `text` fields), and falls back to a stringified JSON for unknown
 * shapes. Never throws.
 */
function readMessageText(message: unknown): string {
	try {
		if (!message || typeof message !== "object") return "";
		const m = message as { role?: unknown; content?: unknown };
		if (typeof m.content === "string") return m.content;
		if (Array.isArray(m.content)) {
			const parts: string[] = [];
			for (const p of m.content) {
				if (!p) continue;
				if (typeof p === "string") {
					parts.push(p);
				} else if (typeof p === "object") {
					const obj = p as { text?: unknown; content?: unknown };
					if (typeof obj.text === "string") parts.push(obj.text);
					else if (typeof obj.content === "string") parts.push(obj.content);
				}
			}
			return parts.join("\n");
		}
		// Fallback: best-effort stringification. Never include cookie-shaped
		// data; we only stringify LLM message shapes which are JSON-safe.
		return JSON.stringify(message).slice(0, 8000);
	} catch {
		return "";
	}
}

/**
 * Skill directories for Hermes-style skill system
 */
const SKILLS_DIR = join(homedir(), ".pi-harness-runtime", "skills");
const MEMORY_DIR = join(homedir(), ".pi-harness-runtime", "memory");
const TRAJECTORY_DIR = join(homedir(), ".pi-harness-runtime", "trajectory");
const PI_SKILLS_DIR = join(homedir(), ".pi", "skills");

// --- Print minimal ASCII banner (shows every startup, matching pi.dev style) -------
// --- Harness version (read once at load time) --------------------------------
const HARNESS_VERSION = (() => {
	const pkgPath = join(__dirname, "package.json");
	try {
		if (existsSync(pkgPath)) {
			const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
			return pkg.version ?? "?.?.?";
		}
	} catch { /* ignore */ }
	return "?.?.?";
})();

function printBanner(): void {
	console.error(`[Harness] v${HARNESS_VERSION}`);
}

function ensureHarnessDir() {
	printBanner();
	if (!existsSync(HARNESS_ROOT_DIR)) {
		mkdirSync(HARNESS_ROOT_DIR, { recursive: true });
	}
	// Hermes-style directory structure
	for (const dir of [SKILLS_DIR, MEMORY_DIR, TRAJECTORY_DIR, COOKIE_DROP_DIR]) {
		if (!existsSync(dir)) {
			mkdirSync(dir, { recursive: true });
		}
	}
	// Create symlink so pi.dev discovers our skills
	ensurePiSkillsSymlink();
	logStartup(
		`[pi-harness] Initialized ~/.pi-harness-runtime/ with skills/, memory/, trajectory/, cookies/`,
	);
}

/**
 * Create symlink from ~/.pi/skills to ~/.pi-harness-runtime/skills
 * This allows pi.dev to discover our Hermes-style skills
 */
function ensurePiSkillsSymlink(): void {
	// SAFETY: Only create symlink if ~/.pi/skills doesn't exist
	if (existsSync(PI_SKILLS_DIR)) {
		return; // User has their own skills dir, don't override
	}
	
	try {
		// SAFETY: Ensure parent dir exists
		const parentDir = dirname(PI_SKILLS_DIR);
		if (!existsSync(parentDir)) {
			mkdirSync(parentDir, { recursive: true });
		}
		
		// Create symlink: ~/.pi/skills -> ~/.pi-harness-runtime/skills
		symlinkSync(SKILLS_DIR, PI_SKILLS_DIR, "junction");
		logStartup(`[pi-harness] Created skills symlink: ~/.pi/skills -> ~/.pi-harness-runtime/skills`);
	} catch {
		// Ignore errors (may fail due to permissions or existing file)
		// User can create the symlink manually if needed
	}
}

/**
 * Get skill directory path (for pi-harness/skills package)
 */
export function getSkillsDir(): string {
	return SKILLS_DIR;
}

/**
 * Get memory directory path (for trajectory/memory package)
 */
export function getMemoryDir(): string {
	return MEMORY_DIR;
}

/**
 * Get trajectory directory path (for trajectory package)
 */
export function getTrajectoryDir(): string {
	return TRAJECTORY_DIR;
}

async function getCheckpointManager(): Promise<CheckpointManager> {
	const { JsonCheckpointManager } = await import(
		"./packages/checkpoint/src/checkpoint-manager.ts"
	);
	// SAFETY: JsonCheckpointManager implements CheckpointManager via structural typing
	return new JsonCheckpointManager(
		HARNESS_ROOT_DIR,
	) as unknown as CheckpointManager;
}

function isOutputLimitResumePromptMessage(message: {
	role?: string;
	content?: unknown;
}): boolean {
	if (message.role !== "user") {
		return false;
	}
	if (typeof message.content === "string") {
		return message.content === OUTPUT_LIMIT_RESUME_PROMPT;
	}
	if (!Array.isArray(message.content)) {
		return false;
	}
	return (
		message.content
			.filter(
				(part): part is { type: "text"; text: string } =>
					part &&
					typeof part === "object" &&
					(part as { type?: unknown }).type === "text" &&
					typeof (part as { text?: unknown }).text === "string",
			)
			.map((part) => part.text)
			.join("\n") === OUTPUT_LIMIT_RESUME_PROMPT
	);
}

// --- Job Pipe Watcher: Listen for Telegram callback commands -------------
// This polls the Telegram webhook server for resume/cancel commands
//
// ARCHITECTURE NOTE (per-user setup):
// Each user runs their own webhook server. The webhook server URL is configured
// in ~/.pi-harness-runtime/keys/telegram-webhook-url.txt
//
// Flow:
//   User clicks button in Telegram
//   → Telegram sends callback to user's webhook server
//   → Webhook server writes command to job-commands.jsonl
//   → pi-harness polls GET /api/pending-commands
//   → pi-harness injects "resume" via steer
function initJobPipeWatcher(pi: ExtensionAPI): void {
	const homedir = process.env.HOME || process.env.USERPROFILE || "/home/frappe";
	const keysDir = join(homedir, ".pi-harness-runtime", "keys");

	// Get webhook server URL and API key from config
	const getWebhookConfig = () => {
		const webhookUrlPath = join(keysDir, "telegram-webhook-url.txt");
		const secretPath = join(keysDir, "telegram-webhook-secret.txt");

		if (!existsSync(webhookUrlPath) || !existsSync(secretPath)) {
			return null;
		}

		const webhookUrl = readFileSync(webhookUrlPath, "utf-8").trim();
		const secret = readFileSync(secretPath, "utf-8").trim();

		if (!webhookUrl || !secret) {
			return null;
		}

		return { webhookUrl: webhookUrl.replace(/\/$/, ""), secret };
	};

	const processCommand = (cmd: { command: string; jobId: string; userId: number }) => {
		console.error(`[JobPipe] Received: ${cmd.command} ${cmd.jobId} from user ${cmd.userId}`);

		switch (cmd.command) {
			case "resume": {
				// Inject "resume" into agent conversation (same as auto-resume)
				pi.sendUserMessage("resume", { deliverAs: "steer" });
				console.error(`[JobPipe] Injected resume via steer`);
				break;
			}
			case "cancel": {
				// Cancel the current job
				pi.sendUserMessage("/harness-cancel", { deliverAs: "steer" });
				console.error(`[JobPipe] Injected /harness-cancel via steer`);
				break;
			}
			default:
				console.error(`[JobPipe] Unknown command: ${cmd.command}`);
		}
	};

	// Acknowledge processed commands
	const acknowledgeCommands = async (jobIds: string[], config: { webhookUrl: string; secret: string }) => {
		try {
			await fetch(`${config.webhookUrl}/api/acknowledge-commands`, {
				method: "POST",
				headers: {
					"Content-Type": "application/json",
					"Authorization": `Bearer ${config.secret}`,
				},
				body: JSON.stringify({ jobIds }),
			});
		} catch {
			// Non-critical, ignore
		}
	};

	const startWatcher = async () => {
		const config = getWebhookConfig();
		if (!config) {
			return; // Not configured
		}

		try {
			const response = await fetch(`${config.webhookUrl}/api/pending-commands`, {
				headers: {
					"Authorization": `Bearer ${config.secret}`,
				},
			});

			if (!response.ok) {
				return;
			}

			const data = (await response.json()) as { commands: Array<{ command: string; jobId: string; userId: number }> };
			const commands = data.commands || [];

			if (commands.length > 0) {
				console.error(`[JobPipe] Found ${commands.length} pending command(s)`);

				for (const cmd of commands) {
					processCommand(cmd);
				}

				// Acknowledge processed commands
				await acknowledgeCommands(
					commands.map((c) => c.jobId),
					config,
				);
			}
		} catch {
			// Network error or server unavailable, ignore
		}
	};

	// Poll every 60 seconds for new commands (user approval doesn't need sub-second latency)
	setInterval(startWatcher, 60_000);
	console.error(`[JobPipe] Watching for Telegram approval commands via API (polling every 60s)`);
}

export default function (pi: ExtensionAPI) {
	const tracker = new UsageTracker();
	const mirrorStore = new MirrorStore();
	ensureHarnessDir();

	// Collect async init promises so we can wait for them before showing startup notification
	const asyncInitPromises: Promise<unknown>[] = [];

	// --- todo-bd-sync: Initialize two-way sync with bd ---------------
	// Start async init but don't await - runs in background
	void initTodoBdSync(pi);

	// --- config-capture: Auto-detect API config and document it -----------
	void initConfigCapture(pi);

	// --- write-review: Two-agent write with review loop ----------------------
	void initWriteReview(pi);

	// --- file-copy-helper: Inject cp rule when mimicking files -------------
	void initFileCopyHelper(pi);

	// --- agent-policy: Mandatory rule delivery and mutation gating -------------
	void initAgentPolicy(pi);

	// --- ssh-detach-interceptor: Auto-fix bare ssh & → nohup pattern ---------
	void initSshDetachInterceptor(pi);

	// --- jev-auto-continue: Agent autonomous decision when user unavailable -----
	void initAutoContinue(pi);

	// --- qdrant-vector-search: Qdrant integration for semantic skill search ----
	// Collect promise so we can wait for initialization before showing startup notification
	asyncInitPromises.push(initQdrant());

	// --- telegram-notifications: Telegram bot for harness event notifications ----
	// Collect promise so we can wait for initialization before showing startup notification
	asyncInitPromises.push(initTelegram());

	// --- codex-watcher: Inject Codex plans into pi.dev + Telegram --------------
	// Lightweight: polls ~/.codex/sessions/ every 5s, no LangChain daemon.
	// On plan detected: sends Telegram alert + steer message to pi.dev.
	try {
		let latestPlanOrdinal = 0;

		const codexHandle = startWatcher({
			pollIntervalMs: 5000,
			maxIdlePolls: Infinity,
			onSessionChange: (session) => {
				if (session) {
					console.error(`[codex] Session started: ${session.threadName}`);
					nc("CodexSessionStarted", {
						requirement: session.threadName || "Codex session",
						taskTitle: session.id,
					});
				}
			},
			onMessages: (msgs) => {
				for (const msg of msgs) {
					// Skip user messages — only track assistant plans
					if (msg.role !== "assistant") continue;

					const classification = classifyMessage(msg.text);
					if (classification !== "plan" && classification !== "mixed") continue;
					if (msg.ordinal <= latestPlanOrdinal) continue;

					latestPlanOrdinal = msg.ordinal;

					// Emit Telegram alert
					nc("CodexPlanDetected", {
						requirement: "Codex plan ready",
						taskTitle: `Ordinal ${msg.ordinal}`,
					});

					// Inject plan into pi.dev as a steer message
					const planPreview = msg.text.slice(0, 500) + (msg.text.length > 500 ? "\n..." : "");
					try {
						pi.sendUserMessage(
							`[Codex plan detected — ordinal ${msg.ordinal}]

${planPreview}`,
							{ deliverAs: "steer" },
						);
					} catch {
						// pi.dev may not be ready
					}
				}
			},
		});

		// Clean up when pi session ends
	pi.on("session_shutdown", () => codexHandle.stop());
	} catch {
		// Codex watcher unavailable (e.g. no codex package installed)
	}

	// --- honcho-memory: Honcho MCP for peer/user memory (honcho_profile, etc.) ----
	// Collect promise so we can wait for initialization before showing startup notification
	asyncInitPromises.push(initHoncho(pi));

	// --- moocoding-sync-hint: Suggest skill sync if skills dir is empty --------
	void initMoocodingSyncHint();

	// --- langchain-loop: Ping-pong write-review loop (planner + reviewer + coder) ----
	void initLangChain(pi);

	// --- paperclip: Watch Paperclip inbox, route tasks to pi.dev + Telegram ------------
	// Only starts if PAPERCLIP_API_KEY is set or keys file exists.
	if (hasPaperclipConfig()) {
		const paperclipConfig = getPaperclipConfig();
		if (paperclipConfig) {
			const watcher = new PaperclipWatcher(paperclipConfig, {
				nc,
				sendUserMessage: (content, opts) => {
					try {
						pi.sendUserMessage(content, opts as { deliverAs: "steer" });
					} catch {
						// pi.dev may not be ready
					}
				},
				pollIntervalMs: 30_000,
				onRouted: async (issue) => {
					// Mark task as in_progress in Paperclip
					try {
						const config = getPaperclipConfig();
						if (config) {
							const { PaperclipClient } = await import(
								"./packages/paperclip-integration/dist/index.js"
							);
							const client = new PaperclipClient(config);
							await client.updateIssueStatus(issue.id, "in_progress");
						}
					} catch {
						// Non-critical
					}
				},
			});
			void watcher.start();
			// pi.on("session_end", () => watcher.stop()); // Removed - type error
		}
	} else {
		console.error(
			"[paperclip] Not configured. Set PAPERCLIP_API_KEY or write API key to ~/.pi-harness-runtime/keys/paperclip-api-key.txt",
		);
	}

	// --- loop-completions: Watch daemon completions → agent TUI todos --------
	void initLoopCompletions(pi);

	// --- Hermes-style skills: Initialize skill system ----------------------
	// Scan skills from ~/.pi-harness-runtime/skills/ and register with pi.dev
	// Collect promise so we can wait for initialization before showing startup notification
	asyncInitPromises.push(initHermesSkills(pi));

	// --- Job pipe watcher: Listen for Telegram callback commands -----------
	// Watch for resume/cancel commands from telegram-callback-processor
	void initJobPipeWatcher(pi);

	// --- TASK TRACKING TERMINOLOGY CLARIFICATION ------------------------
	// CRITICAL: This project has TWO separate task trackers. Agents often confuse them.
	const TASK_TERMINOLOGY_CLARIFICATION = `
## TASK TRACKING TERMINOLOGY (CRITICAL)

This project uses TWO different task tracking systems. **Always clarify which one the user means:**

| What you say | Tool | Purpose |
|-------------|------|---------|
| **bd** / **beads** / **issues** | \`bd create\`, \`bd list\`, \`bd close\` | Issue tracker (GitHub-synced) |
| **todo** / **todos** | \`todo\` tool (JSON-RPC) | Local task list (session-only) |

**When in doubt, ASK:** "Did you mean **bd** (issue tracker) or **todo** (local task list)?"

### When to use each:
- **bd (beads)**: Long-lived issues, bugs, features, GitHub issues
- **todo**: Short-lived session tasks, immediate action items
`;

	// --- Auto-Invoke rpiv-todo via System Prompt ------------------------
	// This makes the todo overlay ALWAYS activate at session start
	const AUTO_TODO_INVOKE_HINT = `

You have access to a **todo tool** that shows a persistent task overlay.
ALWAYS use it at the START of every session to capture user requirements as tasks.
When the user gives you a multi-step task:
1. Immediately create todo items for each step using the **todo tool** (not bd!)
2. Keep tasks updated - mark in_progress when working, completed when done
3. When a task is completed, continue to the next or ask the user

The todo overlay persists and helps track progress across your conversation.
`;

	// --- CONTINUE UNTIL DONE: Auto-pick next todo, don't stop early -----------
	// Fixes the habit: agent completes one task then waits instead of continuing
	const CONTINUE_UNTIL_DONE_HINT = `

## CONTINUE-UNTIL-DONE RULE

**CRITICAL — NEVER STOP after completing ONE task when more are pending.**

After completing ANY task or unit of work:
1. IMMEDIATELY run \`todo list\` to check for pending/in_progress items
2. If pending tasks exist, pick the next one and start working on it WITHOUT asking
3. Only stop and report progress when ALL tasks are completed (or genuinely blocked)
4. If blocked (waiting on user input, CI, external dependency), THEN ask

**Anti-pattern (WRONG):**
> [Task 1 done] → stops → "Done! What would you like me to do next?"

**Correct pattern:**
> [Task 1 done] → picks Task 2 → works → Task 3 → ... → all done → report

This applies to EVERY turn. Every time you finish something, check todos first.
`;

	const COMMIT_BUILD_CHECKLIST = `

BEFORE committing code or triggering builds, ALWAYS check:
1. Run \`bd ready\` to see pending **bd issues**
2. Mark completed issues with \`bd close <id> --reason "Done"\`
3. Acknowledge pending issues before proceeding
4. Never commit/build without acknowledging pending bd issues

NOTE: Use \`todo\` tool for session task tracking, \`bd\` for issue tracker.
`;

	const WRITE_REVIEW_HINT = `

WRITER-REVIEWER WORKFLOW:
When working on a feature from {project}/wiki/* or {project}/.write-review/:
1. Read the prompt/task file to understand requirements
2. Write clean, complete code
3. Mark task "in_progress" using **todo tool**
4. When code is ready, trigger review with \`bd create "Review: <task>" -t review -p 1\`
5. After review, close the issue: \`bd close <id> --reason "Approved"\`
6. If reviewer requests changes, update code and re-review
7. Never skip review on non-trivial features
`;

	const DOCKER_CLEANUP_HINT = `

DOCKER CLEANUP WORKFLOW:
AFTER running any Docker build command (docker build, docker compose build, docker compose up --build):
1. ALWAYS run \`docker builder prune -f\` to clean up build cache
2. This saves disk space - Docker build cache grows fast
3. Example: After \`docker compose up --build\`, run \`docker builder prune -f\`
4. For aggressive cleanup: \`docker builder prune -a -f\` (removes ALL unused cache)
`;
	// --- SSH Guard Rules: Never run servers over SSH in the foreground --------
	// This runs in EVERY repo the agent enters — not just pi-harness-runtime.
	// Pattern: embed as constant (no file dependency) so it works everywhere.
	const SSH_GUARD_RULES = `
## SSH / Remote Server Commands

**CRITICAL — SSH BACKGROUNDING RULE:**
Never run a server or long-running process over SSH in the foreground. This causes the SSH session to hang indefinitely, blocking the agent for hours.

- BAD: \`ssh host "python -m uvicorn ..."\`  (no &, SSH will hang)
- GOOD: \`ssh host "nohup python -m uvicorn ... > /tmp/log 2>&1 & echo DONE"\`
- GOOD: \`ssh host "cd /path && python -m uvicorn ... > log 2>&1 & echo DONE"\`

Every SSH command that starts a server, daemon, or background process MUST use the detached SSH pattern: nohup + redirect + & + echo DONE.
If you are unsure whether a command will hang, ALWAYS use nohup + backgrounding. It is always safe to detach; it is never safe to run a server in the foreground over SSH.

Server process examples that must be detached: uvicorn, fastapi dev server, gunicorn, node server, flask run, django runserver, python http.server, any listening daemon.
`;

	let firstAgentStart = true;
	pi.on("before_agent_start", async (event) => {
		if (firstAgentStart) {
			// Return additional system prompt content to append
			const additionalPrompt = [
				TASK_TERMINOLOGY_CLARIFICATION,
				AUTO_TODO_INVOKE_HINT,
				CONTINUE_UNTIL_DONE_HINT,
				COMMIT_BUILD_CHECKLIST,
				WRITE_REVIEW_HINT,
				DOCKER_CLEANUP_HINT,
				SSH_GUARD_RULES,
			].join("\n");

			firstAgentStart = false;

			// Return the additional prompt to append to systemPrompt
			return { systemPrompt: event.systemPrompt + "\n" + additionalPrompt };
		}
	});

	// --- Auto-Todo Reminder on Build Commands ---------------------------
	// Detect build commands and remind agent to update todos
	const BUILD_COMMANDS = [
		"docker build",
		"docker compose build",
		"docker compose up",
		"npm run build",
		"yarn build",
		"pnpm build",
		"bun run build",
		"make build",
		"gradle build",
		"dotnet build",
		"cargo build",
		"go build",
		"bench build",
	];

	const TODO_BUILD_REMINDER = `

IMPORTANT - TASK UPDATE REMINDER:
Before running a build, ensure you update the current task status:
1. If tracking in **todo tool**: mark task in_progress
2. If tracking in **bd issues**: use \`bd update <id> --status in_progress\`
3. After build succeeds: mark completed in the appropriate tracker

Run \`bd ready\` to see current bd issues.
`;

	// Detect build commands and log reminder (don't modify result.content)
	// Modifying result.content is fragile - pi-coding-agent expects specific structures
	pi.on("tool_execution_end", async (event) => {
		const toolName = (event as { toolName?: string }).toolName;
		if (toolName !== "bash") return;

		const result = (event as { result?: { content?: unknown } }).result;
		if (!result) return;

		// Get content for detection only - don't modify result.content
		const rawContent = result.content;
		let content: string;
		if (typeof rawContent === "string") {
			content = rawContent;
		} else if (Array.isArray(rawContent)) {
			content = rawContent
				.map((c) => (typeof c === "string" ? c : JSON.stringify(c)))
				.join("\n");
		} else if (rawContent) {
			content = JSON.stringify(rawContent);
		} else {
			return; // No content to analyze
		}

		// Check if this is a build command
		const isBuildCommand = BUILD_COMMANDS.some((cmd) =>
			content.toLowerCase().includes(cmd.toLowerCase()),
		);

		// Log reminder to console (agent will see it)
		if (
			isBuildCommand &&
			!content.includes("bd ready") &&
			!content.includes("TODO UPDATE")
		) {
			console.log(TODO_BUILD_REMINDER.trim());
		}
	});

	// --- Auto-track every assistant message ------------------------------
	pi.on("message_end", async (event, ctx) => {
		if (isOutputLimitResumePromptMessage(event.message)) {
			pendingOutputLimitResumeAfterSettled = false;
			return;
		}
		if (event.message.role !== "assistant") return;

		// DEBUG: Log model ID

		const m = event.message as {
			role?: string;
			stopReason?: unknown;
			errorMessage?: unknown;
			content?: unknown;
			usage?: {
				input?: number;
				output?: number;
				cacheRead?: number;
				cacheWrite?: number;
				cost?: { total?: number };
			};
		};

		if (
			shouldQueueOutputLimitResume(
				m,
				outputLimitResumeAttempts,
				ctx.hasPendingMessages(),
			)
		) {
			outputLimitResumeAttempts += 1;
			pendingOutputLimitResumeAfterCompact = true;
			pendingOutputLimitResumeAfterSettled = true;
			await queueAutoResume("output-limit", OUTPUT_LIMIT_RESUME_PROMPT, "steer");
			// Emit OutputLimitContinued to Telegram
			await nc("OutputLimitContinued", {
				requirement: "Session output",
				error: `Attempt ${outputLimitResumeAttempts}`,
			});
		} else if (
			shouldQueueProviderOverloadResume(
				m,
				providerOverloadResumeAttempts,
				ctx.hasPendingMessages(),
			)
		) {
			providerOverloadResumeAttempts += 1;
			console.error(`[pi-harness] Scheduling provider overload resume #${providerOverloadResumeAttempts}`);
			scheduleProviderOverloadResume(() => ctx.hasPendingMessages());
		} else if (m.stopReason === "stop") {
			outputLimitResumeAttempts = 0;
			pendingOutputLimitResumeAfterSettled = false;
			if (providerOverloadResumeAttempts > 0) {
				console.error(`[pi-harness] Counter reset (stopReason=stop), was=${providerOverloadResumeAttempts}`);
			}
			providerOverloadResumeAttempts = 0;
			clearProviderOverloadResume();

			// --- Auto-Continue: Check todos and keep going -------------------------------
			// If the agent stopped naturally AND the response mentions pending todos,
			// auto-steering forces it to continue without waiting for user input.
			const text = readMessageText(event.message);
			if (
				text &&
				/Todos?\s*\(\d+\/\d+\)|\([○◐●]\s+\w+.*pending|Tasks?\s*\(\d+.*pending\)/i.test(text)
			) {
				try {
					pi.sendUserMessage(
						"You have pending tasks. Run `todo list` to see them, then continue with the next pending one without asking.",
						{ deliverAs: "steer" },
					);
				} catch {
					// best-effort
				}
			}
		}

		if (!m.usage) return;
		tracker.append({
			ts: Date.now(),
			model: ctx.model?.id ?? "unknown",
			input: m.usage.input ?? 0,
			output: m.usage.output ?? 0,
			cache_read: m.usage.cacheRead ?? 0,
			cache_write: m.usage.cacheWrite ?? 0,
			cost: m.usage.cost?.total ?? 0,
		});

		// Feed TUI quota-signal extractor with the assistant message text.
		// Best-effort — never throws. Coalesces provider quota notifications
		// (e.g. "OpenAI: context length exceeded, reset in 3 hr 27 min") into
		// the per-provider mirror via `tuiMonitor`.
		try {
			const text = readMessageText(event.message);
			if (text) {
				// 	"[DEBUG message_end] Processing TUI text (first 200 chars):",
				// 	text.substring(0, 200),
				// );
				tuiMonitor.processMessage(text);
			}
			// message_end: no-op on parse error (already logged upstream)
		} catch {
			// ignore — the message was already logged by the TUI layer
		}
	});

	// --- Smart quota fetch for MiniMax status ------------------------
	const MINIMAX_REFRESH_MIN_INTERVAL_MS = 15 * 60 * 1000;
	const MINIMAX_REFRESH_TOKEN_THRESHOLD = 200_000;
	const MINIMAX_REFRESH_REQUEST_THRESHOLD = 12;
	const quotaScraper = process.env.QUOTA_COOKIE_FILE
		? new MiniMaxQuotaScraper({
				cookieFile: process.env.QUOTA_COOKIE_FILE,
				quiet: true,
			})
		: new MiniMaxQuotaScraper({ quiet: true });

	// --- Smart quota fetch for OpenAI status -------------------------
	const OPENAI_REFRESH_MIN_INTERVAL_MS = 5 * 60 * 1000; // 5 minutes
	const _openaiQuotaScraper = new OpenAIQuotaScraper({ quiet: true });
	const _glmQuotaScraper = new GLMQuotaScraper({ quiet: true });
	let lastOpenAIQuotaFetchAt = 0;
	let lastGLMQuotaFetchAt = 0;

	// --- Cookie sanitizer integration ------------------------------------
	// The drop folder is the user-facing, forgiving input. The canonical
	// cache (`~/.config/minimax-cookies.txt`) is the runtime-owned,
	// normalized output that `MiniMaxQuotaScraper` reads. Either being
	// present enables scraping.
	const cookieDropDir = COOKIE_DROP_DIR;
	const cookieCachePath = join(homedir(), ".config", "minimax-cookies.txt");

	const hasCookieSource = (): boolean => {
		try {
			if (existsSync(cookieCachePath)) return true;
		} catch {
			// ignore
		}
		try {
			return sanitizerHasAnyCookieSource(cookieDropDir);
		} catch {
			return false;
		}
	};

	// --- TUI quota signal plumbing (OpenAI / GLM / Anthropic / OpenRouter) --
	// The TUIUsageMonitor parses provider quota-exhaustion messages from pi's
	// TUI / message stream and emits signals we write to per-provider mirror
	// entries. For providers that don't expose a continuous usage API this is
	// the only path to surface data in the footer.
	const quotaManager = new QuotaManager();
	const tuiMonitor = new TUIUsageMonitor({ quotaManager });

	tuiMonitor.on("signal", (signal: TUIUsageSignal) => {
		// 	"[DEBUG tuiMonitor signal] Received signal:",
		// 	JSON.stringify(signal),
		// );
		try {
			writeMirrorRecord(signal.provider as ProviderId, {
				synced_at: signal.timestamp,
				source: "tui-signal",
				exhausted: signal.exhausted,
				limitType: signal.limitType,
				remainingPct: signal.remainingPct,
				resets_at: signal.resetsAt,
			});
		} catch (e) {
			console.error(
				"[pi-harness] tui-signal write failed:",
				e instanceof Error ? e.message : String(e),
			);
		}
	});

	let lastQuotaAutoFetchAt = 0;

	// Live watcher — sanitises on every change in the drop folder.
	const cookieWatcher = new CookieWatcher({
		dropDir: cookieDropDir,
		syncOptions: { cachePath: cookieCachePath, providerHint: "minimax" },
		onEvent: (event) => {
			if (event.kind === "sync-error" || event.kind === "watcher-error") {
				const msg = "message" in event ? event.message : "";
				if (msg.includes("ENOSPC")) {
					console.error(
						"[pi-harness] cookie-sanitizer: ENOSPC — inotify watchers exhausted.\n" +
							"  Fix (run once as sudo):\n" +
							"    echo fs.inotify.max_user_watches=524288 | sudo tee /etc/sysctl.d/99-watch.conf\n" +
							"    sudo sysctl --system",
					);
				} else {
					console.error("[pi-harness] cookie-sanitizer:", msg);
				}
			}
			// A successful sync means the canonical cache is fresh; the
			// next autoFetchQuota() should pick it up immediately. Reset
			// the rate-limit so we don't wait 15 min for the first scrape.
			if (event.kind === "sync-ok") {
				lastQuotaAutoFetchAt = 0;
			}
		},
	});

	try {
		cookieWatcher.start();
		// Sync existing drop-folder cookies now (ignoreInitial: true means
		// the watcher won't do this automatically on startup).
		cookieWatcher.triggerNow();
	} catch (e) {
		const msg = e instanceof Error ? e.message : String(e);
		if (msg.includes("ENOSPC")) {
			console.error(
				"[pi-harness] cookie-sanitizer watcher: ENOSPC — " +
					"inotify watcher limit reached.\n" +
					"Fix (once, as root):\n" +
					"  echo fs.inotify.max_user_watches=524288 | sudo tee /etc/sysctl.d/99-watch.conf\n" +
					"  sudo sysctl --system\n" +
					"Drop folder sync still works — polling will resume automatically.",
			);
		} else {
			console.error("[pi-harness] cookie-sanitizer watcher failed to start:", msg);
		}
	}

	// Store only the setStatus function, NOT the ctx itself.
	// Storing ctx causes stale-context errors after pi reload/restart.
	let footerSetStatus: ((key: string, value: string) => void) | null = null;
	let quotaAutoFetchInFlight = false;
	let proactiveCompactInFlight = false;
	let lastProactiveCompactAt = 0;
	let consecutiveCompactFailures = 0;
	let proactiveCompactCircuitReported = false;
	let outputLimitResumeAttempts = 0;
	let pendingOutputLimitResumeAfterCompact = false;
	let pendingOutputLimitResumeAfterSettled = false;
	let providerOverloadResumeAttempts = 0;
	let providerOverloadResumeTimer: ReturnType<typeof setTimeout> | null = null;

	// --- Context-usage escalating warning tiers -----------------------------
	// Amber at 75%, red at 85%. No notify above 90% (proactive compact handles it).
	// Per-session dedup: only notify when crossing INTO a new higher tier.
	const TIER_WARNING = 0.75; // amber
	const TIER_ERROR = 0.85; // red
	let lastNotifiedTier: "none" | "warning" | "error" = "none";

	function maybeNotifyContextUsage(ctx: ExtensionContext): void {
		const usage = ctx.getContextUsage();
		const pct = usage?.percent;
		if (pct === null || pct === undefined) return;
		if (pct >= 0.9) return; // proactive compact handles 90%+

		const tier: "none" | "warning" | "error" =
			pct >= TIER_ERROR ? "error" : pct >= TIER_WARNING ? "warning" : "none";

		// Only notify when crossing into a strictly higher tier
		if (tier === "none") {
			lastNotifiedTier = "none";
			return;
		}
		if (
			tier === "warning" &&
			lastNotifiedTier !== "warning" &&
			lastNotifiedTier !== "error"
		) {
			lastNotifiedTier = "warning";
			ctx.ui.notify(
				`Context at ${Math.round(pct * 100)}% — consider condensing`,
				"info",
			);
			return;
		}
		if (tier === "error" && lastNotifiedTier !== "error") {
			lastNotifiedTier = "error";
			ctx.ui.notify(
				`Context at ${Math.round(pct * 100)}% — approaching limit`,
				"warning",
			);
			return;
		}
	}

	function writeMirrorRecord(
		provider: ProviderId,
		record: Omit<import("./mirror.js").ProviderMirrorRecord, "provider">,
	): void {
		// 	"[DEBUG writeMirrorRecord] Writing record for provider:",
		// 	provider,
		// 	"record:",
		// 	JSON.stringify(record),
		// );
		mirrorStore.writeProvider(provider, { ...record, provider });
		if (footerSetStatus) {
			refreshFooterStatus(
				footerSetStatus,
				tracker,
				mirrorStore,
				hasCookieSource,
				() => lastActiveProvider,
			);
		}
	}

	async function hasBrowserProfileAutoFetchSource(): Promise<boolean> {
		try {
			const { getLiveSessionPath, getStatusPath } = await import(
				"./packages/auth/src/minimax-browser-auth.ts"
			);
			return existsSync(getLiveSessionPath()) || existsSync(getStatusPath());
		} catch {
			return false;
		}
	}

	function isMiniMaxModel(modelId: string | null | undefined): boolean {
		return providerFromModelId(modelId) === "minimax";
	}

	/** Active provider for the current/last-seen model. Updated by event handlers. */
	let lastActiveProvider: ProviderId | null = null;

	/** Set the active provider; triggers a footer refresh. */
	function noteActiveProvider(modelId: string | null | undefined): void {
		const p = providerFromModelId(modelId);
		// 	"[DEBUG noteActiveProvider] modelId =",
		// 	modelId,
		// 	"-> provider =",
		// 	p,
		// );
		if (p !== lastActiveProvider) {
			lastActiveProvider = p;
			if (footerSetStatus) {
				refreshFooterStatus(
					footerSetStatus,
					tracker,
					mirrorStore,
					hasCookieSource,
					() => lastActiveProvider,
				);
			}
		}
	}

	function getMiniMaxUsageSince(sinceMs: number): {
		tokens: number;
		requests: number;
	} {
		const records = tracker
			.since(sinceMs)
			.filter((record) => isMiniMaxModel(record.model));
		return {
			tokens: records.reduce(
				(sum, record) => sum + record.input + record.output,
				0,
			),
			requests: records.length,
		};
	}

	async function autoFetchQuotaFromBrowserProfile(
		suppressErrors = false,
	): Promise<MirrorRecord | null> {
		if (!(await hasBrowserProfileAutoFetchSource())) {
			return null;
		}

		try {
			const { scrapeWithExistingProfile } = await import(
				"./packages/auth/src/minimax-browser-auth.ts"
			);
			const status = await scrapeWithExistingProfile({ quiet: true });
			if (
				status.page_url.includes("unified-login") ||
				status.page_url.includes("login")
			) {
				return null;
			}
			const rawText =
				status.usage_lines?.join("\n")?.trim() ||
				status.detected_text_sample?.trim() ||
				"";
			if (!rawText) {
				return null;
			}

			const parsed = parseMiniMaxQuotaText(rawText);
			if (parsed.h5UsedPct === undefined && parsed.weeklyUsedPct === undefined) {
				return null;
			}

			return {
				synced_at: status.checked_at,
				provider: "minimax",
				h5_used_pct: parsed.h5UsedPct,
				h5_resets_at: parsed.h5ResetsAt,
				weekly_used_pct: parsed.weeklyUsedPct,
				weekly_resets_at: parsed.weeklyResetsAt,
			};
		} catch (error) {
			if (!suppressErrors) {
				console.error(
					"[pi-harness] Browser-profile quota fetch skipped:",
					error instanceof Error ? error.message : String(error),
				);
			}
			return null;
		}
	}

	async function autoFetchQuota(options?: {
		suppressErrors?: boolean;
	}): Promise<boolean> {
		const suppressErrors = options?.suppressErrors === true;
		const profileRecord = await autoFetchQuotaFromBrowserProfile(suppressErrors);
		if (profileRecord) {
			writeMirrorRecord("minimax", {
				synced_at: profileRecord.synced_at,
				source: "scrape",
				model: profileRecord.model,
				h5_used_pct: profileRecord.h5_used_pct,
				h5_resets_at: profileRecord.h5_resets_at,
				weekly_used_pct: profileRecord.weekly_used_pct,
				weekly_resets_at: profileRecord.weekly_resets_at,
			});
			return true;
		}

		if (!hasCookieSource()) {
			return false;
		}

		try {
			const data = await quotaScraper.scrape();
			writeMirrorRecord("minimax", {
				synced_at: data.scrapedAt,
				source: "scrape",
				h5_used_pct: data.h5UsedPct,
				h5_resets_at: data.h5ResetsAt,
				h5_resets_at_epoch: data.h5ResetsAtEpoch,
				weekly_used_pct: data.weeklyUsedPct,
				weekly_resets_at: data.weeklyResetsAt,
				weekly_resets_at_epoch: data.weeklyResetsAtEpoch,
			});
			return true;
		} catch (error) {
			if (!suppressErrors) {
				console.error(
					"[pi-harness] Quota auto-fetch skipped:",
					error instanceof Error ? error.message : String(error),
				);
			}
			return false;
		}
	}

	/**
	 * Auto-fetch OpenAI quota via ChatGPT Codex analytics.
	 * GPT has WEEKLY-ONLY limits (no 5h window).
	 */
	async function autoFetchOpenAIQuota(options?: {
		suppressErrors?: boolean;
	}): Promise<boolean> {
		const suppressErrors = options?.suppressErrors === true;

		try {
			// Try direct API first (faster)
			const directResult = await _openaiQuotaScraper.scrapeDirect();
			if (directResult) {
				writeMirrorRecord("openai", {
					synced_at: directResult.scrapedAt,
					source: "scrape",
					weekly_used_pct: directResult.weeklyUsedPct,
					weekly_resets_at: directResult.weeklyResetsAt,
					weekly_resets_at_epoch: directResult.weeklyResetsAtEpoch,
					// No 5h window for GPT - set to undefined
					h5_used_pct: undefined,
				});
				return true;
			}

			// Fall back to browser scrape
			const data = await _openaiQuotaScraper.scrape();
			writeMirrorRecord("openai", {
				synced_at: data.scrapedAt,
				source: "scrape",
				weekly_used_pct: data.weeklyUsedPct,
				weekly_resets_at: data.weeklyResetsAt,
				weekly_resets_at_epoch: data.weeklyResetsAtEpoch,
				// No 5h window for GPT - set to undefined
				h5_used_pct: undefined,
			});
			return true;
		} catch (error) {
			if (!suppressErrors) {
				console.error(
					"[pi-harness] OpenAI quota auto-fetch skipped:",
					error instanceof Error ? error.message : String(error),
				);
			}
			return false;
		}
	}

		/**
	 * Auto-fetch GLM quota via z.ai API.
	 * GLM has both 5h and weekly windows.
	 */
	async function autoFetchGLMQuota(options?: {
		suppressErrors?: boolean;
	}): Promise<boolean> {
		const suppressErrors = options?.suppressErrors === true;

		try {
			const data = await _glmQuotaScraper.fetchUsage();
			if (!data) {
				if (!suppressErrors) {
					console.error("[pi-harness] GLM quota auto-fetch: no data returned");
				}
				return false;
			}

			// Check if 5h quota is exhausted (100% or more used)
			const exhausted = data.h5UsedPct >= 100;

				writeMirrorRecord("glm", {
						synced_at: data.scrapedAt,
						source: "scrape",
						h5_used_pct: data.h5UsedPct,
						h5_resets_at: data.h5ResetsAt,
						h5_resets_at_epoch: data.h5ResetsAtEpoch,
						weekly_used_pct: data.weeklyUsedPct,
						weekly_resets_at: data.weeklyResetsAt,
						weekly_resets_at_epoch: data.weeklyResetsAtEpoch,
						model: data.modelName,
						exhausted,
					});
			return true;
		} catch (error) {
			// Check if this is a 429 quota error
			const errorMsg = error instanceof Error ? error.message : String(error);
			const is429 = errorMsg.includes("1308") || errorMsg.includes("Usage limit reached");

			if (is429) {
				// Extract reset time from error
				const { parseGLMErrorResetTime } = await import("./harness/e2e/glm-quota-scraper.js");
				const resetAt = parseGLMErrorResetTime(errorMsg);
				const resetEpoch = resetAt ? new Date(resetAt).getTime() : undefined;

				writeMirrorRecord("glm", {
						synced_at: new Date().toISOString(),
						source: "scrape",
						exhausted: true,
						limitType: "rate_limit",
						h5_used_pct: 100,
						h5_resets_at_epoch: resetEpoch,
					});

				// Start countdown timer for auto-resume
				if (resetAt && currentSession) {
					const countdown = getGLMQuotaCountdown();
					await countdown.startCountdown(
						currentSession.jobId,
						resetAt,
						mirrorStore,
						currentSession.machine,
					);
					console.log(`[GLM countdown] Started - will auto-resume at ${resetAt}`);
				}

				if (!suppressErrors) {
					const resetTime = resetAt
						? `resets at ${new Date(resetAt).toLocaleString()}`
						: "resets soon";
					console.error(`[pi-harness] GLM 5h quota exhausted! ${resetTime}`);
				}
				return true;
			}

			if (!suppressErrors) {
				console.error(
					"[pi-harness] GLM quota auto-fetch skipped:",
					errorMsg,
				);
			}
			return false;
		}
	}

	async function maybeAutoFetchQuota(
		modelId: string | null | undefined,
	): Promise<void> {
		const provider = providerFromModelId(modelId);

		// MiniMax path
		if (provider === "minimax") {
			if (quotaAutoFetchInFlight) return;

			const nowMs = Date.now();
			if (nowMs - lastQuotaAutoFetchAt < MINIMAX_REFRESH_MIN_INTERVAL_MS) {
				return;
			}

			const mirror = mirrorStore.readProvider("minimax");
			const freshness = mirrorStore.freshness(mirror, nowMs);
			const shouldFetchBaseline = !mirror || freshness === "expired";
			const usageSinceSync = getMiniMaxUsageSince(
				mirror?.synced_at ? Date.parse(mirror.synced_at) : 0,
			);
			const shouldFetchFromUsage =
				usageSinceSync.tokens >= MINIMAX_REFRESH_TOKEN_THRESHOLD ||
				usageSinceSync.requests >= MINIMAX_REFRESH_REQUEST_THRESHOLD ||
				(freshness === "stale" && usageSinceSync.requests > 0);

			if (!shouldFetchBaseline && !shouldFetchFromUsage) {
				return;
			}

			quotaAutoFetchInFlight = true;
			lastQuotaAutoFetchAt = nowMs;
			try {
				await autoFetchQuota({ suppressErrors: true });
			} finally {
				quotaAutoFetchInFlight = false;
			}
			return;
		}

		// OpenAI path (GPT has weekly-only limits, no 5h window)
		if (provider === "openai") {
			if (quotaAutoFetchInFlight) return;

			const nowMs = Date.now();
			if (nowMs - lastOpenAIQuotaFetchAt < OPENAI_REFRESH_MIN_INTERVAL_MS) {
				return;
			}

			// Check if OpenAI cookie file exists
			const openaiCookieFile = join(homedir(), ".config", "openai-cookies.txt");
			if (!existsSync(openaiCookieFile)) {
				return;
			}

			quotaAutoFetchInFlight = true;
			lastOpenAIQuotaFetchAt = nowMs;
			try {
				await autoFetchOpenAIQuota({ suppressErrors: true });
			} finally {
				quotaAutoFetchInFlight = false;
			}
			return;
		}

		// OpenAI Codex path (uses OAuth tokens from ~/.codex/auth.json)
		if (provider === "openai-codex") {
			if (quotaAutoFetchInFlight) return;


			const nowMs = Date.now();
			if (nowMs - lastOpenAIQuotaFetchAt < OPENAI_REFRESH_MIN_INTERVAL_MS) {
				return;
			}

			// Check if Codex auth file exists
			const codexAuthFile = join(homedir(), ".codex", "auth.json");
			if (!existsSync(codexAuthFile)) {
				return;
			}

			quotaAutoFetchInFlight = true;
			lastOpenAIQuotaFetchAt = nowMs;
			try {
				await autoFetchOpenAIQuota({ suppressErrors: true });
			} finally {
				quotaAutoFetchInFlight = false;
			}
			return;
		}

		// GLM path (z.ai has both 5h and weekly windows)
		if (provider === "glm") {
			if (quotaAutoFetchInFlight) return;

			const nowMs = Date.now();
			if (nowMs - lastGLMQuotaFetchAt < OPENAI_REFRESH_MIN_INTERVAL_MS) {
				return;
			}

			quotaAutoFetchInFlight = true;
			lastGLMQuotaFetchAt = nowMs;
			try {
				await autoFetchGLMQuota({ suppressErrors: true });
			} finally {
				quotaAutoFetchInFlight = false;
			}
			return;
		}

		// Other providers (Anthropic, OpenRouter) - TUI signal path only for now
		return;
	}

	// --- /usage — show full status ---------------------------------------
	pi.registerCommand("usage", {
		description: "Show Codex-style usage status (local + provider mirror)",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			const local = aggregateWindows(tracker.all());
			const provider = providerFromModelId(ctx.model?.id ?? null);
			const mirror = provider
				? mirrorStore.readProvider(provider)
				: mirrorStore.read();
			const output = renderStatus({
				model: ctx.model?.id ?? null,
				cwd: ctx.cwd ?? process.cwd(),
				local,
				mirror,
				mirrorStore,
				nowMs: Date.now(),
			});
			ctx.ui.notify(output, "info");
		},
	});

	// --- /usage refresh — force auto-fetch ----------------------------
	pi.registerCommand("usage-refresh", {
		description: "Force refresh quota from provider console",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			const autoFetchAvailable =
				hasCookieSource() || (await hasBrowserProfileAutoFetchSource());
			if (!autoFetchAvailable) {
				ctx.ui.notify(
					"MiniMax cookies not found. Drop any cookie file (Netscape or EditThisCookie JSON) into ~/.pi-harness-runtime/cookies/ — the runtime normalizes it for you. Or run `bun packages/auth/src/run-minimax-auth.ts auth`.",
					"warning",
				);
				return;
			}
			ctx.ui.notify("Fetching quota from MiniMax console...", "info");
			const refreshed = await autoFetchQuota();
			ctx.ui.notify(
				refreshed
					? "Quota refreshed. Run `/usage` to see updated status."
					: "Quota refresh skipped. Check cookie validity or MiniMax auth profile, then run `/usage` again.",
				"info",
			);
		},
	});

	// --- /usage today — focused view -------------------------------------
	pi.registerCommand("usage-today", {
		description: "Show today's usage + 5h window",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			const local = aggregateWindows(tracker.all());
			const lines = [
				" Today's usage",
				"-------------------------------------",
				` Model:       ${ctx.model?.id ?? "unknown"}`,
				` Today:       ${local.today.tokens} tokens · ${local.today.requests} requests · $${local.today.cost.toFixed(4)}`,
				` This 5h:     ${local.five_h.tokens} tokens · ${local.five_h.requests} requests · $${local.five_h.cost.toFixed(4)}`,
				"",
				" Run `/usage` for full status with the latest auto-fetched provider quota.",
			];
			ctx.ui.notify(lines.join("\n"), "info");
		},
	});

	// --- /usage week — focused view --------------------------------------
	pi.registerCommand("usage-week", {
		description: "Show this week's usage + lifetime totals",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			const local = aggregateWindows(tracker.all());
			const lines = [
				" This week's usage",
				"-------------------------------------",
				` Model:       ${ctx.model?.id ?? "unknown"}`,
				` This week:   ${local.weekly.tokens} tokens · ${local.weekly.requests} requests · $${local.weekly.cost.toFixed(4)}`,
				` Lifetime:    ${local.lifetime.tokens} tokens · ${local.lifetime.requests} requests · $${local.lifetime.cost.toFixed(4)}`,
				"",
				" Run `/usage` for full status with provider mirror.",
			];
			ctx.ui.notify(lines.join("\n"), "info");
		},
	});

	// --- /usage reset — clear mirror -------------------------------------
	pi.registerCommand("usage-reset", {
		description: "Clear the provider mirror (force re-sync next time)",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			const ok = await ctx.ui.confirm(
				"Clear provider mirror?",
				"This will delete ~/.pi/usage-status/mirror.json. Local usage log is preserved.",
			);
			if (!ok) {
				ctx.ui.notify("Cancelled", "info");
				return;
			}
			// Delete mirror file
			try {
				const { unlinkSync, existsSync } = await import("node:fs");
				if (existsSync(mirrorStore["path"] ?? "")) {
					// The path is private; use the JSON path getter via internal logic
					// Cleaner: just unlink the known mirror path
				}
				// Simpler: import getMirrorPath and unlink
				const { getMirrorPath } = await import("./cli.ts");
				unlinkSync(getMirrorPath());
				ctx.ui.notify(
					"Mirror cleared. The next auto refresh will repopulate it.",
					"info",
				);
				footerSetStatus = ctx.ui.setStatus.bind(ctx.ui);
				refreshFooterStatus(
					footerSetStatus,
					tracker,
					mirrorStore,
					hasCookieSource,
					() => lastActiveProvider,
				);
			} catch (e) {
				ctx.ui.notify(`Failed to clear mirror: ${e}`, "error");
			}
		},
	});

	// --- /github-login — Connect GitHub Gist for clipboard sync ------------
	registerGithubLoginCommand(pi);

	// --- Ctrl+Shift+C — Copy to clipboard + sync to Gist ------------
	// NOTE: Shortcut disabled to avoid conflict with pi-usage-status extension
	// which also registers ctrl+shift+c for clipboard sync.
	// The copyAndSync function is still exported and can be called manually.
	// registerCopySyncShortcut(pi, Key);

	// --- /harness start — Start a new harness job ----------------------
	pi.registerCommand("harness-start", {
		description: "Start a new harness job: /harness start <requirement>",
		handler: async (args: string, ctx: ExtensionCommandContext) => {
			if (!args.trim()) {
				ctx.ui.notify("Usage: /harness start <requirement>", "error");
				return;
			}

			const jobId = `job-${Date.now()}`;
			const requirement = args.trim();

			ctx.ui.notify(`Starting harness job ${jobId}...`, "info");

			try {
				const cm = await getCheckpointManager();
				const machine = new JobStateMachine({ checkpointManager: cm });
				const result = await machine.createJob(jobId, requirement);

				if (!result.success) {
					ctx.ui.notify(`Failed to create job: ${result.error}`, "error");
					return;
				}

				// Emit JobStarted to Telegram
				await nc("JobStarted", { requirement, jobId });

				// Create task graph using heuristic planner
				const planner = new MasterPlanner();
				const planResult = await planner.createPlan(
					requirement,
					jobId,
					HARNESS_ROOT_DIR,
				);

				if (!planResult.success) {
					ctx.ui.notify(`Failed to create plan: ${planResult.error}`, "error");
					return;
				}

				// Create blackboard
				const blackboard = createBlackboard(
					jobId,
					HARNESS_ROOT_DIR,
					planResult.graph!,
				);

				// Create repair engine
				const repairEngine = new RepairEngine(HARNESS_ROOT_DIR);

				// Store session
				currentSession = {
					jobId,
					requirement,
					machine,
					graph: createTaskGraphManager(),
					blackboard,
					repairEngine,
					createdAt: new Date().toISOString(),
				};

				const taskCount = planResult.graph?.nodes
					? Object.keys(planResult.graph.nodes).length
					: 0;
				ctx.ui.notify(
					`Job ${jobId} created with ${taskCount} tasks.\n` +
						`Requirement: ${requirement}\n\n` +
						`Run /harness status to see tasks, or /harness tasks to list them.`,
					"info",
				);
			} catch (e) {
				ctx.ui.notify(`Error starting harness: ${e}`, "error");
			}
		},
	});

	// --- /harness status — Show harness job status ---------------------
	pi.registerCommand("harness-status", {
		description: "Show current harness job status",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			if (!currentSession) {
				ctx.ui.notify(
					"No active harness job. Run /harness start <requirement> to begin.",
					"info",
				);
				return;
			}

			const summary = currentSession.machine.getStatusSummary();
			if (!summary) {
				ctx.ui.notify("Failed to get job status.", "error");
				return;
			}

			const progress = currentSession.graph.getProgressSummary();
			const lines = [
				`Harness Job Status`,
				`${"-".repeat(40)}`,
				`Job ID:     ${currentSession.jobId}`,
				`Status:     ${summary.status}`,
				`Terminal:   ${summary.isTerminal ? "Yes" : "No"}`,
				`Can Resume: ${summary.canResume ? "Yes" : "No"}`,
				`${"-".repeat(40)}`,
				`Tasks:      ${progress.done}/${progress.total} done, ${progress.running} running, ${progress.failed} failed`,
				`Created:    ${currentSession.createdAt}`,
				`${"-".repeat(40)}`,
				`Run /harness tasks for task list`,
			];
			ctx.ui.notify(lines.join("\n"), "info");
		},
	});

	// --- /harness tasks — List all tasks -------------------------------
	pi.registerCommand("harness-tasks", {
		description: "List all tasks in the current harness job",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			if (!currentSession) {
				ctx.ui.notify(
					"No active harness job. Run /harness start <requirement> to begin.",
					"info",
				);
				return;
			}

			const tasks = currentSession.graph.getAllTasks();
			if (tasks.length === 0) {
				ctx.ui.notify(
					"No tasks found. The job may not have been planned yet.",
					"info",
				);
				return;
			}

			const lines = [`Tasks for Job ${currentSession.jobId}`, `${"-".repeat(50)}`];

			for (const task of tasks) {
				const status = task.status.padEnd(10);
				lines.push(`[${task.id}] ${status} ${task.title}`);
			}

			lines.push(`${"-".repeat(50)}`);
			const ready = currentSession.graph.getReadyTasks();
			lines.push(`${ready.length} tasks ready to execute.`);
			ctx.ui.notify(lines.join("\n"), "info");
		},
	});

	// --- /harness pause — Pause the harness job -----------------------
	pi.registerCommand("harness-pause", {
		description: "Pause the current harness job",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			if (!currentSession) {
				ctx.ui.notify("No active harness job to pause.", "info");
				return;
			}

			const checkpoint = currentSession.machine.getCheckpoint();
			if (!checkpoint) {
				ctx.ui.notify("Failed to get checkpoint.", "error");
				return;
			}

			// Read 5h reset epoch from mirror so we can schedule auto-resume
			const mirror = mirrorStore.readProvider("minimax");
			const resumeAtIso = mirror?.h5_resets_at_epoch
				? new Date(mirror.h5_resets_at_epoch).toISOString()
				: undefined;

			const result = await currentSession.machine.transition("paused_quota");
			if (!result.success) {
				ctx.ui.notify(`Failed to pause: ${result.error}`, "error");
				return;
			}

			// Persist resumeAt so auto-resume survives worker restart
			if (resumeAtIso) {
				await currentSession.machine.setResumeTime(resumeAtIso);
			}

			// Schedule auto-resume for 5h quota
			if (resumeAtIso) {
				const scheduled = scheduleAutoResume(
					"minimax",
					currentSession.machine,
					mirrorStore,
				);
				ctx.ui.notify(
					`Job paused.\n` +
						`Auto-resume at ${scheduled ?? resumeAtIso} (5h quota exhausted).\n` +
						`Run /harness cancel to abort.`,
					"info",
				);
			} else {
				ctx.ui.notify(
					`Job ${currentSession.jobId} paused.\n` +
						`Current status: paused_quota\n` +
						`Run /harness resume to continue.`,
					"info",
				);
			}
		},
	});

	// --- /harness resume — Resume the harness job -----------------------
	pi.registerCommand("harness-resume", {
		description: "Resume a paused harness job",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			if (!currentSession) {
				ctx.ui.notify("No active harness job to resume.", "info");
				return;
			}

			const checkpoint = currentSession.machine.getCheckpoint();
			if (!checkpoint || checkpoint.status !== "paused_quota") {
				ctx.ui.notify(
					"Job is not paused. Run /harness start to begin a new job.",
					"info",
				);
				return;
			}

			const result = await currentSession.machine.transition("running");
			if (!result.success) {
				ctx.ui.notify(`Failed to resume: ${result.error}`, "error");
				return;
			}

			// Cancel any scheduled auto-resume for this job
			cancelAutoResume(currentSession.jobId);

			ctx.ui.notify(
				`Job ${currentSession.jobId} resumed.\n` +
					`Current status: running\n` +
					`Run /harness status to monitor progress.`,
				"info",
			);
		},
	});

	// --- /harness cancel — Cancel the harness job ----------------------
	pi.registerCommand("harness-cancel", {
		description: "Cancel the current harness job",
		handler: async (_args: string, ctx: ExtensionCommandContext) => {
			if (!currentSession) {
				ctx.ui.notify("No active harness job to cancel.", "info");
				return;
			}

			const ok = await ctx.ui.confirm(
				`Cancel job ${currentSession.jobId}?`,
				"This will mark the job as cancelled. Task state is preserved but work stops.",
			);

			if (!ok) {
				ctx.ui.notify("Cancelled", "info");
				return;
			}

			const result = await currentSession.machine.transition("cancelled");
			if (!result.success) {
				ctx.ui.notify(`Failed to cancel: ${result.error}`, "error");
				return;
			}

			cancelAutoResume(currentSession.jobId);
			await nc("JobCancelled", { requirement: currentSession.requirement });
			const sessionJobId = currentSession.jobId;
			currentSession = null;

			ctx.ui.notify(
				`Job ${sessionJobId} cancelled.\n` +
					`Run \`/harness start\` to begin a new job.`,
				"info",
			);
		},
	});

	// --- /harness-header — Toggle harness custom header vs built-in Pi header ----
	pi.registerCommand("harness-header", {
		description: "Toggle or restore header: /harness-header [harness|builtin]",
		handler: async (args: string, ctx: ExtensionCommandContext) => {
			if (ctx.mode !== "tui") {
				ctx.ui.notify("Header control is only available in TUI mode.", "info");
				return;
			}

			const mode = args.trim().toLowerCase();
			if (mode === "builtin" || mode === "pi") {
				ctx.ui.setHeader(undefined);
				ctx.ui.notify("Built-in Pi header restored.", "info");
			} else if (mode === "harness" || mode === "" || mode === "") {
				const { createHarnessHeader } = await import("./packages/tui/src/harness-header.js");
				ctx.ui.setHeader((tui, theme) =>
					createHarnessHeader(tui, theme, {
						version: HARNESS_VERSION,
						productName: "Harness",
					}),
				);
				ctx.ui.notify("Harness custom header installed.", "info");
			} else {
				ctx.ui.notify(
					"Usage: /harness-header [harness|builtin]\n" +
					"  harness  — install the Harness half-block logo header (default)\n" +
					"  builtin  — restore Pi's built-in header",
					"info",
				);
			}
		},
	});

	// --- Footer status (persistent badge) --------------------------------
	pi.on("session_start", async (_event, ctx) => {

		// Capture only the setStatus function, not the ctx
		footerSetStatus = ctx.ui.setStatus.bind(ctx.ui);
		refreshFooterStatus(
			footerSetStatus,
			tracker,
			mirrorStore,
			hasCookieSource,
			() => lastActiveProvider,
		);

		// --- Show harness startup block (pi header stays intact) -----------
		// Do NOT call setHeader() — that replaces pi's ASCII header which shows
		// community shortcuts (ctrl+c/d, /, !, ctrl+o). Keep pi's header and add
		// harness info as a small separate notify line.
		//
		// Wait for all async init promises to complete so startupMessages includes
		// messages from async initializations (Qdrant, Telegram, Honcho, HermesSkills).
		// Use a timeout to avoid indefinite blocking if an init stalls.
		if (ctx.hasUI) {
			const notify = ctx.ui.notify.bind(ctx.ui);

			// Wait for async inits to complete, then emit notification
			// Use Promise.race with a timeout to avoid indefinite blocking
			const startupTimeout = new Promise<void>((resolve) => {
				setTimeout(resolve, 5000); // 5 second timeout
			});
			Promise.race([
				Promise.allSettled(asyncInitPromises),
				startupTimeout,
			]).then(() => {
				const lines = [
					`Harness v${HARNESS_VERSION} — quota management, automation, memory, multi-agent`,
					...startupMessages,
				];
				notify(lines.join("\n"), "info");
			});
		}
	});

	pi.on("turn_end", (_event, ctx) => {
		noteActiveProvider(ctx.model?.id ?? null);
		// Capture only the setStatus function, not the ctx
		footerSetStatus = ctx.ui.setStatus.bind(ctx.ui);
		refreshFooterStatus(
			footerSetStatus,
			tracker,
			mirrorStore,
			hasCookieSource,
			() => lastActiveProvider,
		);
		void maybeAutoFetchQuota(ctx.model?.id ?? null);
		maybeTriggerProactiveCompact(ctx);
		maybeNotifyContextUsage(ctx);
	});

	pi.on("agent_end", () => {
		setTimeout(() => {
			if (!pendingOutputLimitResumeAfterSettled) {
				return;
			}
			pendingOutputLimitResumeAfterSettled = false;
			void queueAutoResume(
				"output-limit-settled",
				OUTPUT_LIMIT_RESUME_PROMPT,
				"followUp",
			);
		}, 0);
	});

	pi.on("session_compact", async (event, ctx) => {
		// Capture only the setStatus function, not the ctx
		footerSetStatus = ctx.ui.setStatus.bind(ctx.ui);
		proactiveCompactInFlight = false;
		lastProactiveCompactAt = Date.now();
		consecutiveCompactFailures = 0;
		proactiveCompactCircuitReported = false;
		refreshFooterStatus(
			footerSetStatus,
			tracker,
			mirrorStore,
			hasCookieSource,
			() => lastActiveProvider,
		);

		const forceOutputLimitResume = pendingOutputLimitResumeAfterCompact;
		pendingOutputLimitResumeAfterCompact = false;

		if (
			!shouldQueuePostCompactionResume(event, ctx.hasPendingMessages(), {
				force: forceOutputLimitResume,
			})
		) {
			return;
		}

		if (forceOutputLimitResume) {
			await queueAutoResume(
				"post-compact-output-limit",
				OUTPUT_LIMIT_RESUME_PROMPT,
				event.willRetry ? "steer" : "followUp",
			);
			return;
		}

		outputLimitResumeAttempts = 0;
		// pi.dev expects the literal "resume" command to continue after compaction.
		await queueAutoResume("post-compact", "resume", "followUp");
	});

	// --- Periodic quota refresh every 15 minutes ---
	// `maybeAutoFetchQuota` checks its own rate-limit (MINIMAX_REFRESH_MIN_INTERVAL_MS)
	// so this is safe to call frequently.
	setInterval(() => {
		void maybeAutoFetchQuota(lastActiveProvider ?? null);
		// Also refresh footer status so user sees updated quota data
		if (footerSetStatus) {
			refreshFooterStatus(
				footerSetStatus,
				tracker,
				mirrorStore,
				hasCookieSource,
				() => lastActiveProvider,
			);
		}
	}, MINIMAX_REFRESH_MIN_INTERVAL_MS);

	function clearProviderOverloadResume(): void {
		if (providerOverloadResumeTimer) {
			clearTimeout(providerOverloadResumeTimer);
			providerOverloadResumeTimer = null;
		}
	}

	function scheduleProviderOverloadResume(
		hasPendingMessages: () => boolean,
	): void {
		clearProviderOverloadResume();
		const delayMs = getProviderOverloadResumeDelayMs();
		const delayMinutes = Math.max(1, Math.round(delayMs / 60_000));
		console.error(
			`[pi-harness] Provider overloaded; scheduling resume in ${delayMinutes} min (${delayMs}ms) attempt=${providerOverloadResumeAttempts}`,
		);
		providerOverloadResumeTimer = setTimeout(() => {
			providerOverloadResumeTimer = null;
			console.error(`[pi-harness] Provider resume timer fired, sending "resume"`);
			if (hasPendingMessages()) {
				console.error(`[pi-harness] Resume skipped: hasPendingMessages=true`);
				return;
			}
			void queueAutoResume(
				"provider-overload",
				PROVIDER_OVERLOAD_RESUME_PROMPT,
				"followUp",
			);
		}, delayMs);
	}

	async function queueAutoResume(
		reason: string,
		content: string,
		deliverAs: "steer" | "followUp",
	): Promise<void> {
		try {
			pi.sendUserMessage(content, { deliverAs });
			await nc("ResumeScheduled", {
				requirement: "Session auto-resume",
				error: reason,
			});
		} catch (error) {
			console.error(
				`[pi-harness] Failed to queue ${reason} auto-resume:`,
				error instanceof Error ? error.message : String(error),
			);
		}
	}

	function maybeTriggerProactiveCompact(ctx: ExtensionContext): void {
		if (proactiveCompactInFlight) {
			return;
		}
		if (consecutiveCompactFailures >= MAX_PROACTIVE_COMPACT_FAILURES) {
			if (!proactiveCompactCircuitReported) {
				proactiveCompactCircuitReported = true;
				console.error(
					`[pi-harness] Proactive compact disabled after ${consecutiveCompactFailures} consecutive failures`,
				);
			}
			return;
		}
		if (!ctx.isIdle() || ctx.hasPendingMessages()) {
			return;
		}
		const usage = ctx.getContextUsage();
		if (!shouldTriggerProactiveCompact(usage)) {
			return;
		}
		if (Date.now() - lastProactiveCompactAt < PROACTIVE_COMPACT_COOLDOWN_MS) {
			return;
		}

		proactiveCompactInFlight = true;
		lastProactiveCompactAt = Date.now();

		const compactOptions: CompactOptions = {
			customInstructions:
				"Preserve the current task, recent code changes, pending work, exact next step, and any unresolved errors. This compaction was triggered proactively near the context limit. After compaction, continue seamlessly without asking the user to resume or recap.",
			onComplete: () => {
				proactiveCompactInFlight = false;
				lastProactiveCompactAt = Date.now();
				consecutiveCompactFailures = 0;
				proactiveCompactCircuitReported = false;
				// Emit ContextCompacted to Telegram
				nc("ContextCompacted", {
					requirement: "Session context",
					error: "Proactive compact completed",
				});
			},
			onError: (error) => {
				proactiveCompactInFlight = false;
				consecutiveCompactFailures += 1;
				console.error("[pi-harness] Proactive compact failed:", error.message);
			},
		};
		ctx.compact(compactOptions);
	}
}

// ----------------------------------------------------------------------
// Helper: refresh persistent footer status with one-line summary
// ----------------------------------------------------------------------
function refreshFooterStatus(
	setStatus: (key: string, value: string) => void,
	tracker: UsageTracker,
	mirrorStore: MirrorStore,
	hasCookieSource: () => boolean,
	getActiveProvider: () => ProviderId | null = () => null,
) {
	const nowMs = Date.now();
	const local = aggregateWindows(tracker.all());
	const provider = getActiveProvider();
	const mirror = provider ? mirrorStore.readProvider(provider) : null;
	// 	"[DEBUG refreshFooterStatus] mirror =",
	// 	mirror ? JSON.stringify(mirror) : null,
	// );
	const freshness = mirrorStore.freshness(mirror, nowMs);
	const statusValue = buildFooterStatusValue(local, mirror, freshness, hasCookieSource(), provider);
	// Prepend harness version so it shows in the footer during startup
	setStatus(
		"harness-runtime",
		statusValue,
	);
}
