import { n as __exportAll } from "./chunk-2rV9d50f.js";
import { n as signalProcessTree, t as killProcessTree } from "./kill-tree-kSm0C74g.js";
import { a as writeJsonlEntriesSync, r as appendJsonlEntrySync, t as appendJsonlEntriesSync } from "./transcript-jsonl-LFU2CQ2E.js";
import { i as streamSimple, n as completeSimple } from "./stream-D_qzEOs4.js";
import { i as validateToolCall, r as validateToolArguments } from "./llm-CzDYq74M.js";
import { $ as COMPACTION_SUMMARY_SUFFIX, A as parseCommandArgs, B as estimateTokens, C as AgentHarness, Ct as resolveAgentCoreStreamFn, D as formatPromptTemplateInvocation, E as loadSourcedSkills, F as prepareBranchEntries, G as prepareCompaction, H as findTurnStartIndex, I as DEFAULT_COMPACTION_SETTINGS, J as Session, K as shouldCompact, L as calculateContextTokens, M as collectEntriesForBranchSummary, N as collectEntriesForBranchSummaryFromBranches, O as loadPromptTemplates, P as generateBranchSummary, Q as COMPACTION_SUMMARY_PREFIX, R as compact, S as formatSkillsForSystemPrompt, St as resolveAgentCoreCompleteFn, T as loadSkills, U as generateSummary, V as findCutPoint, W as getLastAssistantUsage, X as BRANCH_SUMMARY_PREFIX, Y as buildSessionContext$1, Z as BRANCH_SUMMARY_SUFFIX, _ as createTimestamp, _t as Agent$1, a as DEFAULT_MAX_LINES, at as NodeExecutionEnv, b as toSession, bt as runAgentLoop, c as truncateHead, ct as CompactionError, d as InMemorySessionRepo, dt as SessionError, et as bashExecutionToText, f as InMemorySessionStorage, ft as err, g as createSessionId$1, gt as toError, h as loadJsonlSessionMetadata, ht as ok, i as DEFAULT_MAX_BYTES, it as createCustomMessage, j as substituteArgs, k as loadSourcedPromptTemplates, l as truncateLine, lt as ExecutionError, m as JsonlSessionStorage, mt as getOrUndefined, n as executeShellWithCapture, nt as createBranchSummaryMessage, ot as AgentHarnessError, p as JsonlSessionRepo, pt as getOrThrow, q as serializeConversation, r as sanitizeBinaryOutput, rt as createCompactionSummaryMessage, s as formatSize, st as BranchSummaryError, t as streamProxy, tt as convertToLlm, u as truncateTail, ut as FileError, v as getEntriesToFork, vt as agentLoop, w as formatSkillInvocation, x as uuidv7, xt as runAgentLoopContinue, y as getFileSystemResultOrThrow, yt as agentLoopContinue, z as estimateContextTokens } from "./proxy-BrzL_Db3.js";
import { fileURLToPath } from "node:url";
import { closeSync, existsSync, mkdirSync, openSync, readFileSync, readSync, readdirSync, statSync } from "node:fs";
import { dirname, join, resolve } from "node:path";
import { readFile, readdir, stat } from "node:fs/promises";
import { homedir } from "node:os";
import "node:child_process";
import { randomUUID } from "node:crypto";
import "cross-spawn";
//#region src/agents/utils/child-process.ts
const EXIT_STDIO_GRACE_MS = 100;
/**
* Wait for a child process to terminate without hanging on inherited stdio handles.
*
* On Windows, daemonized descendants can inherit the child's stdout/stderr pipe
* handles. In that case the child emits `exit`, but `close` can hang forever even
* though the original process is already gone. We wait briefly for stdio to end,
* then forcibly stop tracking the inherited handles.
*/
function waitForChildProcess(child) {
	return new Promise((resolve, reject) => {
		let settled = false;
		let exited = false;
		let exitCode = null;
		let postExitTimer;
		let stdoutEnded = child.stdout === null;
		let stderrEnded = child.stderr === null;
		const cleanup = () => {
			if (postExitTimer) {
				clearTimeout(postExitTimer);
				postExitTimer = void 0;
			}
			child.removeListener("error", onError);
			child.removeListener("exit", onExit);
			child.removeListener("close", onClose);
			child.stdout?.removeListener("end", onStdoutEnd);
			child.stderr?.removeListener("end", onStderrEnd);
		};
		const finalize = (code) => {
			if (settled) return;
			settled = true;
			cleanup();
			child.stdout?.destroy();
			child.stderr?.destroy();
			resolve(code);
		};
		const maybeFinalizeAfterExit = () => {
			if (!exited || settled) return;
			if (stdoutEnded && stderrEnded) finalize(exitCode);
		};
		const onStdoutEnd = () => {
			stdoutEnded = true;
			maybeFinalizeAfterExit();
		};
		const onStderrEnd = () => {
			stderrEnded = true;
			maybeFinalizeAfterExit();
		};
		const onError = (err) => {
			if (settled) return;
			settled = true;
			cleanup();
			reject(err);
		};
		const onExit = (code) => {
			exited = true;
			exitCode = code;
			maybeFinalizeAfterExit();
			if (!settled) postExitTimer = setTimeout(() => finalize(code), EXIT_STDIO_GRACE_MS);
		};
		const onClose = (code) => {
			finalize(code);
		};
		child.stdout?.once("end", onStdoutEnd);
		child.stderr?.once("end", onStderrEnd);
		child.once("error", onError);
		child.once("exit", onExit);
		child.once("close", onClose);
	});
}
//#endregion
//#region src/agents/config.ts
const currentDir = dirname(fileURLToPath(import.meta.url));
/**
* Detect if we're running as a Bun compiled binary.
* Bun binaries have import.meta.url containing "$bunfs", "~BUN", or "%7EBUN" (Bun's virtual filesystem path)
*/
const isBunBinary = import.meta.url.includes("$bunfs") || import.meta.url.includes("~BUN") || import.meta.url.includes("%7EBUN");
process.versions.bun;
/**
* Get the base directory for resolving package assets (themes, package.json, README.md, CHANGELOG.md).
* - For Bun binary: returns the directory containing the executable
* - For Node.js (dist/): returns currentDir (the dist/ directory)
* - For tsx (src/): returns parent directory (the package root)
*/
function getPackageDir() {
	const envDir = process.env.NODMIX_PACKAGE_DIR;
	if (envDir) {
		if (envDir === "~") return homedir();
		if (envDir.startsWith("~/")) return homedir() + envDir.slice(1);
		return envDir;
	}
	if (isBunBinary) return dirname(process.execPath);
	let dir = currentDir;
	while (dir !== dirname(dir)) {
		if (existsSync(join(dir, "package.json"))) return dir;
		dir = dirname(dir);
	}
	return currentDir;
}
/** Get path to package.json */
function getPackageJsonPath() {
	return join(getPackageDir(), "package.json");
}
/** Get path to README.md */
function getReadmePath() {
	return resolve(join(getPackageDir(), "README.md"));
}
/** Get path to docs directory */
function getDocsPath() {
	return resolve(join(getPackageDir(), "docs"));
}
/** Get path to examples directory */
function getExamplesPath() {
	return resolve(join(getPackageDir(), "examples"));
}
const pkg = JSON.parse(readFileSync(getPackageJsonPath(), "utf-8"));
const openClawConfigName = pkg.nodmixConfig?.name;
pkg.name;
const APP_NAME = openClawConfigName || "nodmix";
const CONFIG_DIR_NAME = pkg.nodmixConfig?.configDir || ".nodmix";
const VERSION = pkg.version || "0.0.0";
const ENV_AGENT_DIR = `${APP_NAME.toUpperCase()}_AGENT_DIR`;
`${APP_NAME.toUpperCase()}`;
function expandTildePath(path) {
	if (path === "~") return homedir();
	if (path.startsWith("~/")) return homedir() + path.slice(1);
	return path;
}
/** Get the agent config directory (e.g., ~/.nodmix/agent/) */
function getAgentDir() {
	const envDir = process.env[ENV_AGENT_DIR];
	if (envDir) return expandTildePath(envDir);
	return join(homedir(), CONFIG_DIR_NAME, "agent");
}
/** Get path to managed binaries directory (fd, rg) */
function getBinDir() {
	return join(getAgentDir(), "bin");
}
/** Get path to sessions directory */
function getSessionsDir() {
	return join(getAgentDir(), "sessions");
}
//#endregion
//#region src/agents/runtime/index.ts
var runtime_exports = /* @__PURE__ */ __exportAll({
	Agent: () => Agent,
	AgentHarness: () => AgentHarness,
	AgentHarnessError: () => AgentHarnessError,
	BRANCH_SUMMARY_PREFIX: () => BRANCH_SUMMARY_PREFIX,
	BRANCH_SUMMARY_SUFFIX: () => BRANCH_SUMMARY_SUFFIX,
	BranchSummaryError: () => BranchSummaryError,
	COMPACTION_SUMMARY_PREFIX: () => COMPACTION_SUMMARY_PREFIX,
	COMPACTION_SUMMARY_SUFFIX: () => COMPACTION_SUMMARY_SUFFIX,
	CompactionError: () => CompactionError,
	DEFAULT_COMPACTION_SETTINGS: () => DEFAULT_COMPACTION_SETTINGS,
	DEFAULT_MAX_BYTES: () => DEFAULT_MAX_BYTES,
	DEFAULT_MAX_LINES: () => DEFAULT_MAX_LINES,
	ExecutionError: () => ExecutionError,
	FileError: () => FileError,
	GREP_MAX_LINE_LENGTH: () => 500,
	InMemorySessionRepo: () => InMemorySessionRepo,
	InMemorySessionStorage: () => InMemorySessionStorage,
	JsonlSessionRepo: () => JsonlSessionRepo,
	JsonlSessionStorage: () => JsonlSessionStorage,
	NodeExecutionEnv: () => NodeExecutionEnv,
	Session: () => Session,
	SessionError: () => SessionError,
	agentLoop: () => agentLoop,
	agentLoopContinue: () => agentLoopContinue,
	bashExecutionToText: () => bashExecutionToText,
	buildSessionContext: () => buildSessionContext$1,
	calculateContextTokens: () => calculateContextTokens,
	collectEntriesForBranchSummary: () => collectEntriesForBranchSummary,
	collectEntriesForBranchSummaryFromBranches: () => collectEntriesForBranchSummaryFromBranches,
	compact: () => compact,
	convertToLlm: () => convertToLlm,
	createBranchSummaryMessage: () => createBranchSummaryMessage,
	createCompactionSummaryMessage: () => createCompactionSummaryMessage,
	createCustomMessage: () => createCustomMessage,
	createSessionId: () => createSessionId$1,
	createTimestamp: () => createTimestamp,
	err: () => err,
	estimateContextTokens: () => estimateContextTokens,
	estimateTokens: () => estimateTokens,
	executeShellWithCapture: () => executeShellWithCapture,
	findCutPoint: () => findCutPoint,
	findTurnStartIndex: () => findTurnStartIndex,
	formatPromptTemplateInvocation: () => formatPromptTemplateInvocation,
	formatSize: () => formatSize,
	formatSkillInvocation: () => formatSkillInvocation,
	formatSkillsForSystemPrompt: () => formatSkillsForSystemPrompt,
	generateBranchSummary: () => generateBranchSummary,
	generateSummary: () => generateSummary,
	getEntriesToFork: () => getEntriesToFork,
	getFileSystemResultOrThrow: () => getFileSystemResultOrThrow,
	getLastAssistantUsage: () => getLastAssistantUsage,
	getOrThrow: () => getOrThrow,
	getOrUndefined: () => getOrUndefined,
	killProcessTree: () => killProcessTree,
	loadJsonlSessionMetadata: () => loadJsonlSessionMetadata,
	loadPromptTemplates: () => loadPromptTemplates,
	loadSkills: () => loadSkills,
	loadSourcedPromptTemplates: () => loadSourcedPromptTemplates,
	loadSourcedSkills: () => loadSourcedSkills,
	ok: () => ok,
	openClawAgentCoreRuntime: () => openClawAgentCoreRuntime,
	parseCommandArgs: () => parseCommandArgs,
	prepareBranchEntries: () => prepareBranchEntries,
	prepareCompaction: () => prepareCompaction,
	resolveAgentCoreCompleteFn: () => resolveAgentCoreCompleteFn,
	resolveAgentCoreStreamFn: () => resolveAgentCoreStreamFn,
	runAgentLoop: () => runAgentLoop,
	runAgentLoopContinue: () => runAgentLoopContinue,
	sanitizeBinaryOutput: () => sanitizeBinaryOutput,
	serializeConversation: () => serializeConversation,
	shouldCompact: () => shouldCompact,
	signalProcessTree: () => signalProcessTree,
	streamProxy: () => streamProxy,
	substituteArgs: () => substituteArgs,
	toError: () => toError,
	toSession: () => toSession,
	truncateHead: () => truncateHead,
	truncateLine: () => truncateLine,
	truncateTail: () => truncateTail,
	uuidv7: () => uuidv7,
	validateToolArguments: () => validateToolArguments,
	validateToolCall: () => validateToolCall
});
const openClawAgentCoreRuntime = {
	completeSimple,
	streamSimple
};
var Agent = class extends Agent$1 {
	constructor(options = {}) {
		super({
			runtime: openClawAgentCoreRuntime,
			...options
		});
	}
};
//#endregion
//#region src/agents/sessions/session-manager.ts
function createSessionId() {
	return uuidv7();
}
/** Generate a unique short ID (8 hex chars, collision-checked) */
function generateId(byId) {
	for (let i = 0; i < 100; i++) {
		const id = randomUUID().slice(0, 8);
		if (!byId.has(id)) return id;
	}
	return randomUUID();
}
/** Migrate v1 → v2: add id/parentId tree structure. Mutates in place. */
function migrateV1ToV2(entries) {
	const ids = /* @__PURE__ */ new Set();
	let prevId = null;
	for (const entry of entries) {
		if (entry.type === "session") {
			entry.version = 2;
			continue;
		}
		entry.id = generateId(ids);
		entry.parentId = prevId;
		prevId = entry.id;
		if (entry.type === "compaction") {
			const comp = entry;
			if (typeof comp.firstKeptEntryIndex === "number") {
				const targetEntry = entries[comp.firstKeptEntryIndex];
				if (targetEntry && targetEntry.type !== "session") comp.firstKeptEntryId = targetEntry.id;
				delete comp.firstKeptEntryIndex;
			}
		}
	}
}
/** Migrate v2 → v3: rename hookMessage role to custom. Mutates in place. */
function migrateV2ToV3(entries) {
	for (const entry of entries) {
		if (entry.type === "session") {
			entry.version = 3;
			continue;
		}
		if (entry.type === "message") {
			const msgEntry = entry;
			if (msgEntry.message && msgEntry.message.role === "hookMessage") msgEntry.message.role = "custom";
		}
	}
}
/**
* Run all necessary migrations to bring entries to current version.
* Mutates entries in place. Returns true if any migration was applied.
*/
function migrateToCurrentVersion(entries) {
	const version = entries.find((e) => e.type === "session")?.version ?? 1;
	if (version >= 3) return false;
	if (version < 2) migrateV1ToV2(entries);
	if (version < 3) migrateV2ToV3(entries);
	return true;
}
/** Exported for testing */
function migrateSessionEntries(entries) {
	migrateToCurrentVersion(entries);
}
/** Exported for compaction.test.ts */
function parseSessionEntries(content) {
	const entries = [];
	const lines = content.trim().split("\n");
	for (const line of lines) {
		if (!line.trim()) continue;
		try {
			const entry = JSON.parse(line);
			entries.push(entry);
		} catch {}
	}
	return entries;
}
function getLatestCompactionEntry(entries) {
	for (let i = entries.length - 1; i >= 0; i--) if (entries[i].type === "compaction") return entries[i];
	return null;
}
/**
* Build the session context from entries using tree traversal.
* If leafId is provided, walks from that entry to root.
* Handles compaction and branch summaries along the path.
*/
function buildSessionContext(entries, leafId, byId) {
	if (!byId) {
		byId = /* @__PURE__ */ new Map();
		for (const entry of entries) byId.set(entry.id, entry);
	}
	let leaf;
	if (leafId === null) return {
		messages: [],
		thinkingLevel: "off",
		model: null
	};
	if (leafId) leaf = byId.get(leafId);
	if (!leaf) leaf = entries[entries.length - 1];
	if (!leaf) return {
		messages: [],
		thinkingLevel: "off",
		model: null
	};
	const path = [];
	let current = leaf;
	while (current) {
		path.unshift(current);
		current = current.parentId ? byId.get(current.parentId) : void 0;
	}
	return buildSessionContext$1(path);
}
/**
* Compute the default session directory for a cwd.
* Encodes cwd into a safe directory name under ~/.nodmix/agent/sessions/.
*/
function getDefaultSessionDir(cwd, agentDir = getAgentDir()) {
	const sessionDir = join(agentDir, "sessions", `--${cwd.replace(/^[/\\]/, "").replace(/[/\\:]/g, "-")}--`);
	if (!existsSync(sessionDir)) mkdirSync(sessionDir, { recursive: true });
	return sessionDir;
}
/** Exported for testing */
function loadEntriesFromFile(filePath) {
	if (!existsSync(filePath)) return [];
	const content = readFileSync(filePath, "utf8");
	const entries = [];
	const lines = content.trim().split("\n");
	for (const line of lines) {
		if (!line.trim()) continue;
		try {
			const entry = JSON.parse(line);
			entries.push(entry);
		} catch {}
	}
	if (entries.length === 0) return entries;
	const header = entries[0];
	if (header.type !== "session" || typeof header.id !== "string") return [];
	return entries;
}
function isValidSessionFile(filePath) {
	try {
		const fd = openSync(filePath, "r");
		const buffer = Buffer.alloc(512);
		const bytesRead = readSync(fd, buffer, 0, 512, 0);
		closeSync(fd);
		const firstLine = buffer.toString("utf8", 0, bytesRead).split("\n")[0];
		if (!firstLine) return false;
		const header = JSON.parse(firstLine);
		return header.type === "session" && typeof header.id === "string";
	} catch {
		return false;
	}
}
/** Exported for testing */
function findMostRecentSession(sessionDir) {
	try {
		return readdirSync(sessionDir).filter((f) => f.endsWith(".jsonl")).map((f) => join(sessionDir, f)).filter(isValidSessionFile).map((path) => ({
			path,
			mtime: statSync(path).mtime
		})).toSorted((a, b) => b.mtime.getTime() - a.mtime.getTime())[0]?.path || null;
	} catch {
		return null;
	}
}
function isMessageWithContent(message) {
	return typeof message.role === "string" && "content" in message;
}
function extractTextContent(message) {
	const content = message.content;
	if (typeof content === "string") return content;
	return content.filter((block) => block.type === "text").map((block) => block.text).join(" ");
}
function getLastActivityTime(entries) {
	let lastActivityTime;
	for (const entry of entries) {
		if (entry.type !== "message") continue;
		const message = entry.message;
		if (!isMessageWithContent(message)) continue;
		if (message.role !== "user" && message.role !== "assistant") continue;
		const msgTimestamp = message.timestamp;
		if (typeof msgTimestamp === "number") {
			lastActivityTime = Math.max(lastActivityTime ?? 0, msgTimestamp);
			continue;
		}
		const entryTimestamp = entry.timestamp;
		if (typeof entryTimestamp === "string") {
			const t = new Date(entryTimestamp).getTime();
			if (!Number.isNaN(t)) lastActivityTime = Math.max(lastActivityTime ?? 0, t);
		}
	}
	return lastActivityTime;
}
function getSessionModifiedDate(entries, header, statsMtime) {
	const lastActivityTime = getLastActivityTime(entries);
	if (typeof lastActivityTime === "number" && lastActivityTime > 0) return new Date(lastActivityTime);
	const headerTime = typeof header.timestamp === "string" ? new Date(header.timestamp).getTime() : NaN;
	return !Number.isNaN(headerTime) ? new Date(headerTime) : statsMtime;
}
async function buildSessionInfo(filePath) {
	try {
		const content = await readFile(filePath, "utf8");
		const entries = [];
		const lines = content.trim().split("\n");
		for (const line of lines) {
			if (!line.trim()) continue;
			try {
				entries.push(JSON.parse(line));
			} catch {}
		}
		if (entries.length === 0) return null;
		const header = entries[0];
		if (header.type !== "session") return null;
		const stats = await stat(filePath);
		let messageCount = 0;
		let firstMessage = "";
		const allMessages = [];
		let name;
		for (const entry of entries) {
			if (entry.type === "session_info") name = entry.name?.trim() || void 0;
			if (entry.type !== "message") continue;
			messageCount++;
			const message = entry.message;
			if (!isMessageWithContent(message)) continue;
			if (message.role !== "user" && message.role !== "assistant") continue;
			const textContent = extractTextContent(message);
			if (!textContent) continue;
			allMessages.push(textContent);
			if (!firstMessage && message.role === "user") firstMessage = textContent;
		}
		const cwd = typeof header.cwd === "string" ? header.cwd : "";
		const parentSessionPath = header.parentSession;
		const modified = getSessionModifiedDate(entries, header, stats.mtime);
		return {
			path: filePath,
			id: header.id,
			cwd,
			name,
			parentSessionPath,
			created: new Date(header.timestamp),
			modified,
			messageCount,
			firstMessage: firstMessage || "(no messages)",
			allMessagesText: allMessages.join(" ")
		};
	} catch {
		return null;
	}
}
const MAX_CONCURRENT_SESSION_INFO_LOADS = 10;
async function buildSessionInfosWithConcurrency(files, onLoaded) {
	const results = Array.from({ length: files.length }, () => null);
	const inFlight = /* @__PURE__ */ new Set();
	let nextIndex = 0;
	const startNext = () => {
		const index = nextIndex++;
		const file = files[index];
		if (!file) return;
		let task;
		task = buildSessionInfo(file).then((info) => {
			results[index] = info;
		}).catch(() => {
			results[index] = null;
		}).finally(() => {
			inFlight.delete(task);
			onLoaded();
		});
		inFlight.add(task);
	};
	while (nextIndex < files.length || inFlight.size > 0) {
		while (nextIndex < files.length && inFlight.size < MAX_CONCURRENT_SESSION_INFO_LOADS) startNext();
		if (inFlight.size > 0) await Promise.race(inFlight);
	}
	return results;
}
async function listSessionsFromDir(dir, onProgress, progressOffset = 0, progressTotal) {
	const sessions = [];
	if (!existsSync(dir)) return sessions;
	try {
		const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl")).map((f) => join(dir, f));
		const total = progressTotal ?? files.length;
		let loaded = 0;
		const results = await buildSessionInfosWithConcurrency(files, () => {
			loaded++;
			onProgress?.(progressOffset + loaded, total);
		});
		for (const info of results) if (info) sessions.push(info);
	} catch {}
	return sessions;
}
/**
* Manages conversation sessions as append-only trees stored in JSONL files.
*
* Each session entry has an id and parentId forming a tree structure. The "leaf"
* pointer tracks the current position. Appending creates a child of the current leaf.
* Branching moves the leaf to an earlier entry, allowing new branches without
* modifying history.
*
* Use buildSessionContext() to get the resolved message list for the LLM, which
* handles compaction summaries and follows the path from root to current leaf.
*/
var SessionManager = class SessionManager {
	constructor(cwd, sessionDir, sessionFile, persist) {
		this.sessionId = "";
		this.flushed = false;
		this.fileEntries = [];
		this.byId = /* @__PURE__ */ new Map();
		this.labelsById = /* @__PURE__ */ new Map();
		this.labelTimestampsById = /* @__PURE__ */ new Map();
		this.leafId = null;
		this.cwd = cwd;
		this.sessionDir = sessionDir;
		this.shouldPersist = persist;
		if (persist && sessionDir && !existsSync(sessionDir)) mkdirSync(sessionDir, { recursive: true });
		if (sessionFile) this.setSessionFile(sessionFile);
		else this.newSession();
	}
	/** Switch to a different session file (used for resume and branching) */
	setSessionFile(sessionFile) {
		this.sessionFile = resolve(sessionFile);
		if (existsSync(this.sessionFile)) {
			this.fileEntries = loadEntriesFromFile(this.sessionFile);
			if (this.fileEntries.length === 0) {
				const explicitPath = this.sessionFile;
				this.newSession();
				this.sessionFile = explicitPath;
				this.rewriteFile();
				this.flushed = true;
				return;
			}
			const header = this.fileEntries.find((e) => e.type === "session");
			this.sessionId = header?.id ?? createSessionId();
			if (migrateToCurrentVersion(this.fileEntries)) this.rewriteFile();
			this.buildIndex();
			this.flushed = true;
		} else {
			const explicitPath = this.sessionFile;
			this.newSession();
			this.sessionFile = explicitPath;
		}
	}
	newSession(options) {
		this.sessionId = options?.id ?? createSessionId();
		const timestamp = (/* @__PURE__ */ new Date()).toISOString();
		const header = {
			type: "session",
			version: 3,
			id: this.sessionId,
			timestamp,
			cwd: this.cwd,
			parentSession: options?.parentSession
		};
		this.fileEntries = [header];
		this.byId.clear();
		this.labelsById.clear();
		this.leafId = null;
		this.flushed = false;
		if (this.shouldPersist) {
			const fileTimestamp = timestamp.replace(/[:.]/g, "-");
			this.sessionFile = join(this.getSessionDir(), `${fileTimestamp}_${this.sessionId}.jsonl`);
		}
		return this.sessionFile;
	}
	buildIndex() {
		this.byId.clear();
		this.labelsById.clear();
		this.labelTimestampsById.clear();
		this.leafId = null;
		for (const entry of this.fileEntries) {
			if (entry.type === "session") continue;
			this.byId.set(entry.id, entry);
			this.leafId = entry.id;
			if (entry.type === "label") if (entry.label) {
				this.labelsById.set(entry.targetId, entry.label);
				this.labelTimestampsById.set(entry.targetId, entry.timestamp);
			} else {
				this.labelsById.delete(entry.targetId);
				this.labelTimestampsById.delete(entry.targetId);
			}
		}
	}
	rewriteFile() {
		if (!this.shouldPersist || !this.sessionFile) return;
		writeJsonlEntriesSync(this.sessionFile, this.fileEntries);
	}
	isPersisted() {
		return this.shouldPersist;
	}
	getCwd() {
		return this.cwd;
	}
	getSessionDir() {
		return this.sessionDir;
	}
	getSessionId() {
		return this.sessionId;
	}
	getSessionFile() {
		return this.sessionFile;
	}
	persist(entry) {
		if (!this.shouldPersist || !this.sessionFile) return;
		if (!this.fileEntries.some((e) => e.type === "message" && e.message.role === "assistant")) {
			this.flushed = false;
			return;
		}
		if (!this.flushed) {
			appendJsonlEntriesSync(this.sessionFile, this.fileEntries);
			this.flushed = true;
		} else appendJsonlEntrySync(this.sessionFile, entry);
	}
	appendEntry(entry) {
		this.fileEntries.push(entry);
		this.byId.set(entry.id, entry);
		this.leafId = entry.id;
		this.persist(entry);
	}
	/** Append a message as child of current leaf, then advance leaf. Returns entry id.
	* Does not allow writing CompactionSummaryMessage and BranchSummaryMessage directly.
	* Reason: we want these to be top-level entries in the session, not message session entries,
	* so it is easier to find them.
	* These need to be appended via appendCompaction() and appendBranchSummary() methods.
	*/
	appendMessage(message) {
		const entry = {
			type: "message",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			message
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Append a thinking level change as child of current leaf, then advance leaf. Returns entry id. */
	appendThinkingLevelChange(thinkingLevel) {
		const entry = {
			type: "thinking_level_change",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			thinkingLevel
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Append a model change as child of current leaf, then advance leaf. Returns entry id. */
	appendModelChange(provider, modelId) {
		const entry = {
			type: "model_change",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			provider,
			modelId
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Append a compaction summary as child of current leaf, then advance leaf. Returns entry id. */
	appendCompaction(summary, firstKeptEntryId, tokensBefore, details, fromHook) {
		const entry = {
			type: "compaction",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			summary,
			firstKeptEntryId,
			tokensBefore,
			details,
			fromHook
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Append a custom entry (for extensions) as child of current leaf, then advance leaf. Returns entry id. */
	appendCustomEntry(customType, data) {
		const entry = {
			type: "custom",
			customType,
			data,
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString()
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Append a session info entry (e.g., display name). Returns entry id. */
	appendSessionInfo(name) {
		const entry = {
			type: "session_info",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			name: name.trim()
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/** Get the current session name from the latest session_info entry, if any. */
	getSessionName() {
		const entries = this.getEntries();
		for (let i = entries.length - 1; i >= 0; i--) {
			const entry = entries[i];
			if (entry.type === "session_info") return entry.name?.trim() || void 0;
		}
	}
	/**
	* Append a custom message entry (for extensions) that participates in LLM context.
	* @param customType Extension identifier for filtering on reload
	* @param content Message content (string or TextContent/ImageContent array)
	* @param display Whether to show in TUI (true = styled display, false = hidden)
	* @param details Optional extension-specific metadata (not sent to LLM)
	* @returns Entry id
	*/
	appendCustomMessageEntry(customType, content, display, details) {
		const entry = {
			type: "custom_message",
			customType,
			content,
			display,
			details,
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString()
		};
		this.appendEntry(entry);
		return entry.id;
	}
	getLeafId() {
		return this.leafId;
	}
	getLeafEntry() {
		return this.leafId ? this.byId.get(this.leafId) : void 0;
	}
	getEntry(id) {
		return this.byId.get(id);
	}
	/**
	* Get all direct children of an entry.
	*/
	getChildren(parentId) {
		const children = [];
		for (const entry of this.byId.values()) if (entry.parentId === parentId) children.push(entry);
		return children;
	}
	/**
	* Get the label for an entry, if any.
	*/
	getLabel(id) {
		return this.labelsById.get(id);
	}
	/**
	* Set or clear a label on an entry.
	* Labels are user-defined markers for bookmarking/navigation.
	* Pass undefined or empty string to clear the label.
	*/
	appendLabelChange(targetId, label) {
		if (!this.byId.has(targetId)) throw new Error(`Entry ${targetId} not found`);
		const entry = {
			type: "label",
			id: generateId(this.byId),
			parentId: this.leafId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			targetId,
			label
		};
		this.appendEntry(entry);
		if (label) {
			this.labelsById.set(targetId, label);
			this.labelTimestampsById.set(targetId, entry.timestamp);
		} else {
			this.labelsById.delete(targetId);
			this.labelTimestampsById.delete(targetId);
		}
		return entry.id;
	}
	/**
	* Walk from entry to root, returning all entries in path order.
	* Includes all entry types (messages, compaction, model changes, etc.).
	* Use buildSessionContext() to get the resolved messages for the LLM.
	*/
	getBranch(fromId) {
		const path = [];
		const startId = fromId ?? this.leafId;
		let current = startId ? this.byId.get(startId) : void 0;
		while (current) {
			path.unshift(current);
			current = current.parentId ? this.byId.get(current.parentId) : void 0;
		}
		return path;
	}
	/**
	* Build the session context (what gets sent to the LLM).
	* Uses tree traversal from current leaf.
	*/
	buildSessionContext() {
		return buildSessionContext(this.getEntries(), this.leafId, this.byId);
	}
	/**
	* Get session header.
	*/
	getHeader() {
		const h = this.fileEntries.find((e) => e.type === "session");
		return h ? h : null;
	}
	/**
	* Get all session entries (excludes header). Returns a shallow copy.
	* The session is append-only: use appendXXX() to add entries, branch() to
	* change the leaf pointer. Entries cannot be modified or deleted.
	*/
	getEntries() {
		return this.fileEntries.filter((e) => e.type !== "session");
	}
	/**
	* Get the session as a tree structure. Returns a shallow defensive copy of all entries.
	* A well-formed session has exactly one root (first entry with parentId === null).
	* Orphaned entries (broken parent chain) are also returned as roots.
	*/
	getTree() {
		const entries = this.getEntries();
		const nodeMap = /* @__PURE__ */ new Map();
		const roots = [];
		for (const entry of entries) {
			const label = this.labelsById.get(entry.id);
			const labelTimestamp = this.labelTimestampsById.get(entry.id);
			nodeMap.set(entry.id, {
				entry,
				children: [],
				label,
				labelTimestamp
			});
		}
		for (const entry of entries) {
			const node = nodeMap.get(entry.id);
			if (entry.parentId === null || entry.parentId === entry.id) roots.push(node);
			else {
				const parent = nodeMap.get(entry.parentId);
				if (parent) parent.children.push(node);
				else roots.push(node);
			}
		}
		const stack = [...roots];
		while (stack.length > 0) {
			const node = stack.pop();
			node.children.sort((a, b) => new Date(a.entry.timestamp).getTime() - new Date(b.entry.timestamp).getTime());
			stack.push(...node.children);
		}
		return roots;
	}
	/**
	* Start a new branch from an earlier entry.
	* Moves the leaf pointer to the specified entry. The next appendXXX() call
	* will create a child of that entry, forming a new branch. Existing entries
	* are not modified or deleted.
	*/
	branch(branchFromId) {
		if (!this.byId.has(branchFromId)) throw new Error(`Entry ${branchFromId} not found`);
		this.leafId = branchFromId;
	}
	/**
	* Reset the leaf pointer to null (before any entries).
	* The next appendXXX() call will create a new root entry (parentId = null).
	* Use this when navigating to re-edit the first user message.
	*/
	resetLeaf() {
		this.leafId = null;
	}
	/**
	* Start a new branch with a summary of the abandoned path.
	* Same as branch(), but also appends a branch_summary entry that captures
	* context from the abandoned conversation path.
	*/
	branchWithSummary(branchFromId, summary, details, fromHook) {
		if (branchFromId !== null && !this.byId.has(branchFromId)) throw new Error(`Entry ${branchFromId} not found`);
		this.leafId = branchFromId;
		const entry = {
			type: "branch_summary",
			id: generateId(this.byId),
			parentId: branchFromId,
			timestamp: (/* @__PURE__ */ new Date()).toISOString(),
			fromId: branchFromId ?? "root",
			summary,
			details,
			fromHook
		};
		this.appendEntry(entry);
		return entry.id;
	}
	/**
	* Create a new session file containing only the path from root to the specified leaf.
	* Useful for extracting a single conversation path from a branched session.
	* Returns the new session file path, or undefined if not persisting.
	*/
	createBranchedSession(leafId) {
		const previousSessionFile = this.sessionFile;
		const path = this.getBranch(leafId);
		if (path.length === 0) throw new Error(`Entry ${leafId} not found`);
		const pathWithoutLabels = path.filter((e) => e.type !== "label");
		const newSessionId = createSessionId();
		const timestamp = (/* @__PURE__ */ new Date()).toISOString();
		const fileTimestamp = timestamp.replace(/[:.]/g, "-");
		const newSessionFile = join(this.getSessionDir(), `${fileTimestamp}_${newSessionId}.jsonl`);
		const header = {
			type: "session",
			version: 3,
			id: newSessionId,
			timestamp,
			cwd: this.cwd,
			parentSession: this.shouldPersist ? previousSessionFile : void 0
		};
		const pathEntryIds = new Set(pathWithoutLabels.map((e) => e.id));
		const labelsToWrite = [];
		for (const [targetId, label] of this.labelsById) if (pathEntryIds.has(targetId)) labelsToWrite.push({
			targetId,
			label,
			timestamp: this.labelTimestampsById.get(targetId)
		});
		if (this.shouldPersist) {
			let parentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;
			const labelEntries = [];
			for (const { targetId, label, timestamp: labelTimestamp } of labelsToWrite) {
				const labelEntry = {
					type: "label",
					id: generateId(new Set(pathEntryIds)),
					parentId,
					timestamp: labelTimestamp,
					targetId,
					label
				};
				pathEntryIds.add(labelEntry.id);
				labelEntries.push(labelEntry);
				parentId = labelEntry.id;
			}
			this.fileEntries = [
				header,
				...pathWithoutLabels,
				...labelEntries
			];
			this.sessionId = newSessionId;
			this.sessionFile = newSessionFile;
			this.buildIndex();
			if (this.fileEntries.some((e) => e.type === "message" && e.message.role === "assistant")) {
				this.rewriteFile();
				this.flushed = true;
			} else this.flushed = false;
			return newSessionFile;
		}
		const labelEntries = [];
		let parentId = pathWithoutLabels[pathWithoutLabels.length - 1]?.id || null;
		for (const { targetId, label, timestamp: labelTimestamp } of labelsToWrite) {
			const labelEntry = {
				type: "label",
				id: generateId(new Set([...pathEntryIds, ...labelEntries.map((e) => e.id)])),
				parentId,
				timestamp: labelTimestamp,
				targetId,
				label
			};
			labelEntries.push(labelEntry);
			parentId = labelEntry.id;
		}
		this.fileEntries = [
			header,
			...pathWithoutLabels,
			...labelEntries
		];
		this.sessionId = newSessionId;
		this.buildIndex();
	}
	/**
	* Create a new session.
	* @param cwd Working directory (stored in session header)
	* @param sessionDir Optional session directory. If omitted, uses default (~/.nodmix/agent/sessions/<encoded-cwd>/).
	*/
	static create(cwd, sessionDir) {
		return new SessionManager(cwd, sessionDir ?? getDefaultSessionDir(cwd), void 0, true);
	}
	/**
	* Open a specific session file.
	* @param path Path to session file
	* @param sessionDir Optional session directory for /new or /branch. If omitted, derives from file's parent.
	* @param cwdOverride Optional cwd override instead of the session header cwd.
	*/
	static open(path, sessionDir, cwdOverride) {
		const header = loadEntriesFromFile(path).find((e) => e.type === "session");
		return new SessionManager(cwdOverride ?? header?.cwd ?? process.cwd(), sessionDir ?? resolve(path, ".."), path, true);
	}
	/**
	* Continue the most recent session, or create new if none.
	* @param cwd Working directory
	* @param sessionDir Optional session directory. If omitted, uses default (~/.nodmix/agent/sessions/<encoded-cwd>/).
	*/
	static continueRecent(cwd, sessionDir) {
		const dir = sessionDir ?? getDefaultSessionDir(cwd);
		const mostRecent = findMostRecentSession(dir);
		if (mostRecent) return new SessionManager(cwd, dir, mostRecent, true);
		return new SessionManager(cwd, dir, void 0, true);
	}
	/** Create an in-memory session (no file persistence) */
	static inMemory(cwd = process.cwd()) {
		return new SessionManager(cwd, "", void 0, false);
	}
	/**
	* Fork a session from another project directory into the current project.
	* Creates a new session in the target cwd with the full history from the source session.
	* @param sourcePath Path to the source session file
	* @param targetCwd Target working directory (where the new session will be stored)
	* @param sessionDir Optional session directory. If omitted, uses default for targetCwd.
	*/
	static forkFrom(sourcePath, targetCwd, sessionDir) {
		const sourceEntries = loadEntriesFromFile(sourcePath);
		if (sourceEntries.length === 0) throw new Error(`Cannot fork: source session file is empty or invalid: ${sourcePath}`);
		if (!sourceEntries.find((e) => e.type === "session")) throw new Error(`Cannot fork: source session has no header: ${sourcePath}`);
		const dir = sessionDir ?? getDefaultSessionDir(targetCwd);
		if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
		const newSessionId = createSessionId();
		const timestamp = (/* @__PURE__ */ new Date()).toISOString();
		const newSessionFile = join(dir, `${timestamp.replace(/[:.]/g, "-")}_${newSessionId}.jsonl`);
		appendJsonlEntrySync(newSessionFile, {
			type: "session",
			version: 3,
			id: newSessionId,
			timestamp,
			cwd: targetCwd,
			parentSession: sourcePath
		});
		for (const entry of sourceEntries) if (entry.type !== "session") appendJsonlEntrySync(newSessionFile, entry);
		return new SessionManager(targetCwd, dir, newSessionFile, true);
	}
	/**
	* List all sessions for a directory.
	* @param cwd Working directory (used to compute default session directory)
	* @param sessionDir Optional session directory. If omitted, uses default (~/.nodmix/agent/sessions/<encoded-cwd>/).
	* @param onProgress Optional callback for progress updates (loaded, total)
	*/
	static async list(cwd, sessionDir, onProgress) {
		const sessions = await listSessionsFromDir(sessionDir ?? getDefaultSessionDir(cwd), onProgress);
		sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
		return sessions;
	}
	/**
	* List all sessions across all project directories.
	* @param onProgress Optional callback for progress updates (loaded, total)
	*/
	static async listAll(onProgress) {
		const sessionsDir = getSessionsDir();
		try {
			if (!existsSync(sessionsDir)) return [];
			const dirs = (await readdir(sessionsDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => join(sessionsDir, e.name));
			let totalFiles = 0;
			const dirFiles = [];
			for (const dir of dirs) try {
				const files = (await readdir(dir)).filter((f) => f.endsWith(".jsonl"));
				dirFiles.push(files.map((f) => join(dir, f)));
				totalFiles += files.length;
			} catch {
				dirFiles.push([]);
			}
			let loaded = 0;
			const sessions = [];
			const results = await buildSessionInfosWithConcurrency(dirFiles.flat(), () => {
				loaded++;
				onProgress?.(loaded, totalFiles);
			});
			for (const info of results) if (info) sessions.push(info);
			sessions.sort((a, b) => b.modified.getTime() - a.modified.getTime());
			return sessions;
		} catch {
			return [];
		}
	}
};
//#endregion
export { getDocsPath as _, getLatestCompactionEntry as a, isBunBinary as b, parseSessionEntries as c, runtime_exports as d, APP_NAME as f, getBinDir as g, getAgentDir as h, getDefaultSessionDir as i, Agent as l, VERSION as m, buildSessionContext as n, loadEntriesFromFile as o, CONFIG_DIR_NAME as p, findMostRecentSession as r, migrateSessionEntries as s, SessionManager as t, openClawAgentCoreRuntime as u, getExamplesPath as v, waitForChildProcess as x, getReadmePath as y };
