import { createLogger } from "../utils/logger/index.js";
import { init_logger } from "../utils/logger.js";
import { init_agent_scope, resolveAgentHomeDir, resolveAgentProfileDir, resolveDefaultAgentId } from "./agent-scope.js";
import { getAgentDefaultModelRef, init_schema } from "../config/schema.js";
import { applyConfigOverrides } from "../config/runtime-overrides.js";
import { extractProfileAgentId, resolveEffectiveAgentProfileForSession } from "../config/agent-profile.js";
import { extractTextContent } from "./context/workspace.js";
import { onSessionTranscriptUpdate } from "../session/transcript-events.js";
import { getWorkflowProgressBroker } from "./workflow/progress-broker.js";
import "./workflow/index.js";
import { AgentManager } from "./agent-manager.js";
import { persistInboundAttachmentsToWorkspace } from "../channels/attachments/inbound-persist.js";
import { maybeAutoTitleSessionStore } from "../session/session-title.js";
import { SessionStore } from "../session/store.js";
import { SessionConfigStore } from "../session/config-store.js";
import { effectiveWorkspacePathForSession } from "../session/session-workspace.js";
import "../session/index.js";
import { ExtensionHookRunner } from "../extensions/hooks.js";
import "../extensions/index.js";
import { SessionTracker } from "./session/tracker.js";
import { ModelManager } from "./models/manager.js";
import "./models/index.js";
import { initializeCommands } from "../chat-commands/index.js";
import { ProgressFeedbackManager } from "./lifecycle/progress.js";
import { HookHandler } from "./lifecycle/hook-handler.js";
import { ToolErrorTracker } from "./tools/error-tracker.js";
import { RequestLimiter } from "./models/request-limiter.js";
import { SystemReminder } from "./prompt/system-reminder.js";
import { ToolUsageAnalyzer } from "./tools/usage-analyzer.js";
import { ToolChainTracker } from "./tools/chain-tracker.js";
import { ErrorPatternMatcher } from "./tools/error-pattern-matcher.js";
import { SelfVerifyMiddleware } from "./middleware/self-verify.js";
import { ContextMiddleware } from "./middleware/context.js";
import "./middleware/index.js";
import { LifecycleManager } from "./lifecycle/manager.js";
import "./lifecycle/index.js";
import { CompactionLifecycleHandler } from "./lifecycle/handlers/compaction.js";
import { MessageRouter } from "./messaging/message-router.js";
import { CommandHandler } from "./messaging/command-handler.js";
import { StreamManager } from "./messaging/stream-manager.js";
import { OutboundCoordinator } from "./messaging/outbound-coordinator.js";
import "./messaging/index.js";
import { InboundLoop } from "./inbound/inbound-loop.js";
import { TurnDispatcher } from "./inbound/turn-dispatcher.js";
import { SessionContextManager } from "./session/session-context.js";
import { SessionLifecycleManager } from "./session/session-lifecycle.js";
import { SessionStateBag } from "./session/session-state-bag.js";
import { SessionConfigService } from "./session/session-config-service.js";
import { SessionHydrator } from "./session/session-hydrator.js";
import { SessionInspector } from "./session/session-inspector.js";
import "./session/index.js";
import { AgentOrchestrator } from "./orchestration/agent-orchestrator.js";
import { AgentEventHandler } from "./orchestration/agent-event-handler.js";
import "./orchestration/index.js";
import { FeedbackCoordinator } from "./feedback/feedback-coordinator.js";
import "./feedback/index.js";
import { PersistentGoalService } from "./goals/persistent-goal-service.js";
import { reconcileManagedDreamingCronJobs } from "./service/reconcile-dreaming-cron.js";
import { parseOutboundSessionKey } from "./service/parse-outbound-session-key.js";
import { cleanTrailingErrors } from "./memory/message-sanitizer.js";
import { tryApplySessionTranscriptHygiene } from "./transcript/transcript-hygiene.js";
import { existsSync, readFileSync } from "node:fs";
import { join } from "node:path";
//#region src/agent/service.ts
init_schema();
init_logger();
init_agent_scope();
const log = createLogger("AgentService");
var AgentService = class {
	/**
	* Persistent transcript + session-metadata store. Public so the gateway/TUI
	* can read sessions, delete them, etc. without forcing every CRUD-style
	* operation through a delegation method on `AgentService`.
	*/
	sessionStore;
	sessionConfigStore;
	hookRunner;
	agentId;
	workspaceDir;
	channelManagerRef = null;
	bus;
	config;
	sessionTracker;
	modelManager;
	progressManager;
	hookHandler;
	lifecycleManager;
	errorTracker;
	requestLimiter;
	systemReminder;
	toolUsageAnalyzer;
	toolChainTracker;
	errorPatternMatcher;
	selfVerifyMiddleware;
	contextMiddleware;
	messageRouter;
	commandHandler;
	streamManager;
	/**
	* Outbound pipeline: typing controller, silence guard, final response publish,
	* extension `message_sending`/`message_sent` hooks, post-turn `webchat_turn_complete`
	* event. Public so the gateway / channels can drive it directly.
	*/
	outboundCoordinator;
	inboundLoop;
	/**
	* Direct-turn entry points: `processDirect` (one-shot), `processDirectStreaming`
	* (SSE generator), webchat steering and SSE injection. Public so the gateway,
	* TUI, CLI, and cron jobs do not need to thread every call through `AgentService`.
	*/
	turnDispatcher;
	/**
	* `/goal` runtime: continuation scheduling, persistent-goal API factory,
	* stream-outcome state, post-turn verdict. Public so the gateway can wire
	* the webchat continuation scheduler and read stream outcomes directly.
	*/
	persistentGoals;
	/**
	* Per-session config writes (model / thinking / reasoning / working directory).
	* Public so REST endpoints and CLI flows can hit it without going through a
	* monolithic patch entrypoint on `AgentService`.
	*/
	sessionConfig;
	/**
	* Hydration — read persisted per-session config and apply it to the runtime
	* (AgentManager / ModelManager). The mirror image of `sessionConfig`: writes
	* go through `sessionConfig`, reads-into-runtime go through `sessionHydrator`.
	*/
	sessionHydrator;
	/**
	* Read-only introspection (compaction, /context report, /btw, contextUsage,
	* agentConfig view). Public so REST endpoints and CLI flows can query a
	* session's view without going through delegating methods on `AgentService`.
	*/
	sessionInspector;
	sessionContextManager;
	sessionLifecycleManager;
	agentOrchestrator;
	agentEventHandler;
	workflowProgressBrokerHandle = null;
	feedbackCoordinator;
	agentManager;
	/**
	* Unified per-session state container (replaces six ad-hoc Maps). Owns webchat
	* publishers, last assistant text, embedded stream buffer, persistent-goal stream
	* outcomes, concurrent-turn depth, and event-listener unsubscribers; runs a TTL
	* sweep for slots that have no explicit owner.
	*/
	sessionState = new SessionStateBag();
	/** Gateway: notify UI after direct `SessionStore.updateMetadata` (no SessionManager emit). */
	onSessionMetadataUpdated;
	onSessionTranscriptUpdated;
	effectiveAppConfig() {
		const base = this.config.config;
		return base ? applyConfigOverrides(base) : void 0;
	}
	constructor(bus, config) {
		this.bus = bus;
		this.config = config;
		this.onSessionMetadataUpdated = config.onSessionMetadataUpdated;
		this.onSessionTranscriptUpdated = config.onSessionTranscriptUpdated;
		this.agentId = `agent-${Date.now()}`;
		this.workspaceDir = config.workspace;
		this.sessionTracker = new SessionTracker();
		this.modelManager = new ModelManager({
			defaultModel: config.model,
			config: config.config
		});
		initializeCommands();
		log.debug("Command system initialized");
		this.sessionStore = config.sessionStore ?? this.createSessionStore();
		onSessionTranscriptUpdate((update) => {
			this.sessionStore.syncSessionsJsonFromTranscriptUpdate(update).catch((err) => {
				log.warn({
					err,
					sessionFile: update.sessionFile,
					sessionKey: update.sessionKey
				}, "Transcript index sync failed");
			});
			const sk = update.sessionKey?.trim();
			if (sk) this.onSessionTranscriptUpdated?.(sk);
		});
		const appCfgForPaths = this.config.config;
		if (!appCfgForPaths) throw new Error("AgentService requires config.config for session paths");
		const defaultAgentHome = resolveAgentHomeDir(appCfgForPaths, resolveDefaultAgentId(appCfgForPaths));
		this.sessionConfigStore = new SessionConfigStore(defaultAgentHome);
		this.hookRunner = this.createHookRunner();
		this.hookHandler = new HookHandler({
			hookRunner: this.hookRunner,
			agentId: this.agentId,
			get sessionKey() {
				return this.currentContext?.sessionKey;
			}
		});
		this.progressManager = this.createProgressManager();
		this.initializeReliabilityModules();
		this.lifecycleManager = new LifecycleManager();
		this.initializeLifecycleHandlers();
		this.streamManager = new StreamManager();
		this.sessionContextManager = new SessionContextManager();
		this.feedbackCoordinator = new FeedbackCoordinator({
			progressManager: this.progressManager,
			bus
		});
		this.agentManager = new AgentManager({
			workspace: config.workspace,
			model: config.model,
			config: config.config,
			extensionRegistry: config.extensionRegistry,
			hookRunner: this.hookRunner,
			bus,
			getCurrentContext: () => this.sessionContextManager.getContext(),
			getSessionStore: () => this.sessionStore,
			getModelManager: () => this.modelManager,
			thinkingLevel: config.thinkingLevel,
			reasoningLevel: config.reasoningLevel,
			verboseLevel: config.verboseLevel,
			gatewayClarify: config.gatewayClarify,
			getCronService: config.getCronService
		});
		this.agentEventHandler = new AgentEventHandler({
			progressManager: this.progressManager,
			errorTracker: this.errorTracker,
			requestLimiter: this.requestLimiter,
			lifecycleManager: this.lifecycleManager,
			toolChainTracker: this.toolChainTracker,
			selfVerifyMiddleware: this.selfVerifyMiddleware,
			systemReminder: this.systemReminder,
			toolUsageAnalyzer: this.toolUsageAnalyzer,
			errorPatternMatcher: this.errorPatternMatcher
		});
		this.workflowProgressBrokerHandle = getWorkflowProgressBroker().attachTo(this.agentEventHandler);
		this.sessionHydrator = new SessionHydrator({
			sessionConfigStore: this.sessionConfigStore,
			agentManager: this.agentManager,
			modelManager: this.modelManager,
			getConfig: () => this.effectiveAppConfig()
		});
		this.agentOrchestrator = new AgentOrchestrator({
			agentManager: this.agentManager,
			sessionStore: this.sessionStore,
			modelManager: this.modelManager,
			eventHandler: this.agentEventHandler,
			feedbackCoordinator: this.feedbackCoordinator,
			sessionConfigStore: this.sessionConfigStore,
			sessionHydrator: this.sessionHydrator,
			getConfig: () => this.effectiveAppConfig(),
			getThinkingDefault: () => this.effectiveAppConfig()?.agents?.defaults?.thinkingDefault,
			getThinkingDefaultForSession: (sessionKey) => this.agentManager.getThinkingDefaultForSession(sessionKey),
			workspaceRoot: this.workspaceDir,
			getWorkspaceRootForSession: (sessionKey) => this.agentManager.getResolvedWorkspaceForSession(sessionKey),
			getAgentInternalStorageRootForSession: (sessionKey) => resolveAgentHomeDir(this.config.config, extractProfileAgentId(sessionKey, this.config.config)),
			enqueueAutoTitle: (sessionKey) => this.enqueueMaybeAutoTitleAfterPersist(sessionKey),
			onEmbeddedStreamEvent: (sessionKey, event) => {
				const ctx = this.sessionContextManager.getContext();
				if (!ctx || ctx.sessionKey !== sessionKey) return;
				if (event.type === "token") {
					const next = this.sessionState.appendEmbeddedStreamText(sessionKey, event.content);
					this.streamManager.update(next);
				}
			},
			onEmbeddedTurnComplete: (sessionKey, text) => {
				if (text) this.sessionState.setLastAssistantText(sessionKey, text);
				this.sessionState.clearEmbeddedStreamText(sessionKey);
			}
		});
		this.messageRouter = new MessageRouter();
		this.commandHandler = new CommandHandler({
			config: config.config,
			bus,
			sessionStore: this.sessionStore,
			sessionConfigStore: this.sessionConfigStore,
			getPersistentGoalApisForCommand: (routing) => this.persistentGoals.buildApisForRouting(routing),
			applySessionThinkingLevel: (sessionKey, level) => {
				this.agentManager.setThinkingLevel(sessionKey, level);
			},
			getCurrentModel: () => this.agentOrchestrator.getCurrentModel(),
			switchModelForSession: (sessionKey, modelId) => this.switchModelForSession(sessionKey, modelId),
			invalidateAgentSession: (sessionKey) => {
				this.agentManager.removeAgent(sessionKey);
			},
			resetSession: (sessionKey) => this.resetSession(sessionKey),
			abortSessionTurn: async (sessionKey) => {
				await this.streamManager.abort();
				this.agentOrchestrator.abort(sessionKey);
			},
			compactSession: (sessionKey, options) => this.sessionInspector.compact(sessionKey, options),
			btwQuery: (sessionKey, question) => this.sessionInspector.btwQuery(sessionKey, question),
			getSessionContextReport: (sessionKey, mode) => this.sessionInspector.report(sessionKey, mode)
		});
		this.sessionLifecycleManager = new SessionLifecycleManager(this.sessionStore, this.sessionTracker, this.lifecycleManager);
		this.persistentGoals = new PersistentGoalService({
			bus,
			sessionStore: this.sessionStore,
			modelManager: this.modelManager,
			sessionState: this.sessionState,
			getConfig: () => this.effectiveAppConfig(),
			getResolvedWorkspaceForSession: (sk) => this.agentManager.getResolvedWorkspaceForSession(sk),
			onSessionMetadataUpdated: this.onSessionMetadataUpdated,
			notifyWebchatTranscriptAppend: (sk, text) => this.turnDispatcher.notifyWebchatTranscriptAppend(sk, text)
		});
		this.sessionConfig = new SessionConfigService({
			sessionStore: this.sessionStore,
			sessionConfigStore: this.sessionConfigStore,
			modelManager: this.modelManager,
			agentManager: this.agentManager,
			getConfig: () => this.effectiveAppConfig()
		});
		this.sessionInspector = new SessionInspector({
			sessionStore: this.sessionStore,
			sessionConfigStore: this.sessionConfigStore,
			modelManager: this.modelManager,
			agentManager: this.agentManager,
			sessionHydrator: this.sessionHydrator,
			getConfig: () => this.effectiveAppConfig(),
			getContextWindow: () => this.getContextWindow()
		});
		this.outboundCoordinator = new OutboundCoordinator({
			bus,
			hookHandler: this.hookHandler,
			streamManager: this.streamManager,
			getConfig: () => this.effectiveAppConfig(),
			getLastAssistantPlainText: (sk) => this.getLastAssistantPlainText(sk),
			runPersistentGoalPostTurn: (payload) => this.persistentGoals.runPostTurn(payload)
		});
		this.turnDispatcher = new TurnDispatcher({
			log,
			agentManager: this.agentManager,
			sessionStore: this.sessionStore,
			modelManager: this.modelManager,
			sessionConfigStore: this.sessionConfigStore,
			sessionState: this.sessionState,
			commandHandler: this.commandHandler,
			getConfig: () => this.effectiveAppConfig(),
			requireConfig: () => {
				const c = this.config.config;
				if (!c) throw new Error("AgentService requires config.config");
				return c;
			},
			parseSessionKey: (sk) => this.parseSessionKey(sk),
			initSessionContext: (sk, channel, chatId) => this.initSessionContext(sk, channel, chatId),
			sessionHydrator: this.sessionHydrator,
			attachmentRootsForSession: (sk) => this.attachmentRootsForSession(sk),
			prepareInboundAttachments: (sk, att) => this.prepareInboundAttachments(sk, att),
			enqueueMaybeAutoTitleAfterPersist: (sk) => this.enqueueMaybeAutoTitleAfterPersist(sk),
			endDirectRequestContext: () => this.endDirectRequestContext(),
			onSessionTranscriptUpdated: this.onSessionTranscriptUpdated,
			resetSession: (sk) => this.resetSession(sk)
		});
		this.inboundLoop = new InboundLoop({
			log,
			agentId: this.agentId,
			bus,
			hookHandler: this.hookHandler,
			messageRouter: this.messageRouter,
			commandHandler: this.commandHandler,
			sessionContextManager: this.sessionContextManager,
			feedbackCoordinator: this.feedbackCoordinator,
			agentManager: this.agentManager,
			sessionLifecycleManager: this.sessionLifecycleManager,
			agentOrchestrator: this.agentOrchestrator,
			outboundCoordinator: this.outboundCoordinator,
			streamManager: this.streamManager,
			sessionState: this.sessionState,
			sessionStore: this.sessionStore,
			modelManager: this.modelManager,
			setupSessionEventHandling: (sk) => this.setupSessionEventHandling(sk),
			sessionHydrator: this.sessionHydrator,
			getLastAssistantPlainText: (sk) => this.getLastAssistantPlainText(sk),
			checkAndCompact: (sk, msgs) => this.checkAndCompact(sk, msgs),
			enqueueMaybeAutoTitleAfterPersist: (sk) => this.enqueueMaybeAutoTitleAfterPersist(sk),
			getConfig: () => this.effectiveAppConfig(),
			resetSession: (sk) => this.resetSession(sk),
			setStreamHandle: (handle) => this.setStreamHandle(handle)
		});
		if (!!!process.env.ELECTRON_RUN_AS_NODE) {
			process.on("SIGINT", () => this.dispose());
			process.on("SIGTERM", () => this.dispose());
		}
		log.info("AgentService initialized");
	}
	attachmentRootsForSession(sessionKey) {
		const cfg = this.config.config;
		return { agentHome: resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)) };
	}
	createSessionStore() {
		const sessionStoreDefaults = this.config.agentDefaults || this.config.config?.agents?.defaults;
		const windowConfig = {
			maxMessages: 100,
			keepRecentMessages: sessionStoreDefaults?.maxToolIterations || 20,
			preserveSystemMessages: true
		};
		const compactionConfig = {
			enabled: sessionStoreDefaults?.compaction?.enabled ?? true,
			mode: sessionStoreDefaults?.compaction?.mode || "abstractive",
			reserveTokens: sessionStoreDefaults?.compaction?.reserveTokens || 8e3,
			triggerThreshold: sessionStoreDefaults?.compaction?.triggerThreshold || .8,
			minMessagesBeforeCompact: sessionStoreDefaults?.compaction?.minMessagesBeforeCompact || 10,
			keepRecentMessages: sessionStoreDefaults?.compaction?.keepRecentMessages || 10,
			evictionWindow: sessionStoreDefaults?.compaction?.evictionWindow || .2,
			retentionWindow: sessionStoreDefaults?.compaction?.retentionWindow || 6
		};
		const appCfg = this.config.config;
		if (!appCfg) throw new Error("AgentService requires config.config for session store paths");
		return new SessionStore({
			config: appCfg,
			agentId: resolveDefaultAgentId(appCfg)
		}, windowConfig, compactionConfig);
	}
	createHookRunner() {
		if (!this.config.extensionRegistry) return void 0;
		return new ExtensionHookRunner(this.config.extensionRegistry, {
			catchErrors: true,
			logger: {
				info: (msg) => log.info({ hook: true }, msg),
				warn: (msg) => log.warn({ hook: true }, msg),
				error: (msg) => log.error({ hook: true }, msg)
			}
		});
	}
	createProgressManager() {
		return new ProgressFeedbackManager({
			level: "normal",
			showThinking: true,
			streamToolProgress: true,
			heartbeatEnabled: true,
			heartbeatIntervalMs: 2e4,
			longTaskThresholdMs: 3e4
		});
	}
	initializeReliabilityModules() {
		const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;
		this.errorTracker = new ToolErrorTracker({
			maxFailuresPerTool: defaults?.maxToolFailuresPerTurn || 3,
			maxTotalFailures: defaults?.maxToolFailuresPerTurn ? defaults.maxToolFailuresPerTurn + 2 : 5,
			resetOnTurnEnd: true
		});
		this.selfVerifyMiddleware = new SelfVerifyMiddleware({
			maxEditsPerFile: 5,
			enablePreCompletionCheck: true,
			minTurnsForVerification: 4,
			resetOnVerification: true
		});
		this.requestLimiter = new RequestLimiter({
			maxRequestsPerTurn: defaults?.maxRequestsPerTurn || 50,
			warnThreshold: .8,
			softLimit: false
		});
		this.systemReminder = new SystemReminder({
			enabled: true,
			appendToToolResults: true,
			maxRemindersPerTurn: 3
		});
		this.toolUsageAnalyzer = new ToolUsageAnalyzer({
			enabled: true,
			lowUsageThreshold: 5,
			veryLowUsageThreshold: 1,
			minCallsForAnalysis: 100,
			reportIntervalMs: 3600 * 1e3
		});
		this.toolChainTracker = new ToolChainTracker({
			enabled: true,
			maxChainsPerSession: 10,
			maxNodesPerChain: 100,
			trackParams: true,
			trackResults: true,
			autoPrune: true
		});
		this.errorPatternMatcher = new ErrorPatternMatcher({
			enabled: true,
			defaultMaxRetries: 1,
			logMatches: true
		});
		this.contextMiddleware = new ContextMiddleware();
	}
	initializeLifecycleHandlers() {
		this.lifecycleManager.on("llm_response", new CompactionLifecycleHandler({
			minMessages: 20,
			maxTokens: 8e3,
			preserveReasoning: true,
			accumulateUsage: true
		}));
		log.debug({ handlers: this.lifecycleManager.getRegisteredHandlers() }, "Lifecycle handlers initialized");
	}
	setChannelManager(channelManager) {
		this.modelManager.setChannelManager(channelManager);
		this.channelManagerRef = channelManager;
		this.inboundLoop.setChannelManager(channelManager);
	}
	/**
	* Apply config after save or hot reload so the default model updates without restarting the gateway.
	*/
	applyAgentDefaultsFromConfig(config) {
		this.config.config = config;
		const ref = getAgentDefaultModelRef(config);
		this.config.model = ref;
		this.modelManager.updateFromConfig(config);
		this.agentManager.updateAgentDefaults(config);
		this.commandHandler.updateAgentConfig(config);
	}
	getSkillCatalog(lang) {
		return this.agentManager.getSkillCatalog(lang);
	}
	getSkillMarkdownSource(skillName, lang) {
		return this.agentManager.getSkillMarkdownSource(skillName, lang);
	}
	refreshSkillsAfterDiskChange() {
		this.agentManager.refreshSkillsAfterDiskChange();
	}
	refreshSkillsAfterSkillConfigChange() {
		this.agentManager.refreshSkillsAfterSkillConfigChange();
	}
	getModelForSession(sessionKey) {
		return this.modelManager.getModelForSession(sessionKey);
	}
	async switchModelForSession(sessionKey, modelId) {
		if (!await this.modelManager.switchModelForSession(sessionKey, modelId)) return false;
		await this.sessionConfigStore.update(sessionKey, { modelOverride: modelId });
		if (this.agentManager.setModelForSession(sessionKey, modelId)) this.sessionTracker.touchSession(sessionKey);
		return true;
	}
	/**
	* Clears per-session model override so the next turn uses the configured agent default
	* (e.g. cron isolated job with no explicit model).
	*/
	async resetSessionModelToAgentDefault(sessionKey) {
		await this.sessionConfig.clearModelOverride(sessionKey);
	}
	setStreamHandle(handle) {
		this.streamManager.setHandle(handle);
		this.feedbackCoordinator.setStreamHandle(handle);
	}
	clearStreamHandle() {
		this.streamManager.clearHandle();
		this.feedbackCoordinator.endTask();
	}
	/** Last assistant visible plain text for a session (e.g. after a webchat stream). */
	getLastAssistantPlainText(sessionKey) {
		return this.sessionState.getLastAssistantText(sessionKey) ?? this.agentManager.getLastAssistantContent(sessionKey) ?? "";
	}
	beginInboundTurn(sessionKey) {
		this.sessionState.beginInboundTurn(sessionKey);
	}
	endInboundTurn(sessionKey) {
		this.sessionState.endInboundTurn(sessionKey);
	}
	getInboundTurnDepth(sessionKey) {
		return this.sessionState.getInboundTurnDepth(sessionKey);
	}
	async start() {
		await this.sessionConfigStore.initialize();
		await this.hookHandler.trigger("gateway_start", {
			port: 0,
			host: "cli"
		});
		await this.reconcileDreamingCronJob().catch((err) => {
			const em = err instanceof Error ? err.message : String(err);
			log.warn({
				err,
				errorMessage: em
			}, `Dreaming cron reconcile failed: ${em}`);
		});
		log.debug("Agent service started");
		await this.inboundLoop.start();
	}
	stop() {
		this.inboundLoop.stop();
		this.agentManager.dispose();
		this.dispose();
		this.hookHandler.trigger("gateway_stop", { reason: "stopped" });
		log.debug("Agent service stopped");
		return Promise.resolve();
	}
	/**
	* Reconcile managed Dreaming cron job against the current effective config.
	* Safe to call after config saves to apply changes without restarting the process.
	*/
	async reconcileDreamingNow() {
		await this.reconcileDreamingCronJob();
	}
	async reconcileDreamingCronJob() {
		const cron = this.config.getCronService?.();
		if (!cron) return;
		await reconcileManagedDreamingCronJobs(cron, this.effectiveAppConfig());
	}
	/**
	* Persist agent messages with the same sanitizer + transcript hygiene as AgentOrchestrator.
	* Uses persistence hygiene so `thinking` blocks remain on disk for the web UI (LLM load path still drops them).
	*/
	/**
	* Fire-and-forget: `maybeAutoTitleSessionStore` no-ops for cron/heartbeat keys.
	* Runs after persist so the store has the latest transcript; does not block SSE / callers.
	*/
	enqueueMaybeAutoTitleAfterPersist(sessionKey) {
		(async () => {
			try {
				let modelRef = getAgentDefaultModelRef(this.config.config ?? {}) ?? this.config.model;
				if (!modelRef?.trim()) try {
					modelRef = this.modelManager.getModelForSession(sessionKey);
				} catch {
					modelRef = void 0;
				}
				await maybeAutoTitleSessionStore(this.sessionStore, sessionKey, modelRef?.trim() || void 0);
			} catch (err) {
				log.warn({
					err,
					sessionKey
				}, "Auto session title failed");
			}
		})();
	}
	prepareLoadedSessionMessages(sessionKey, messages) {
		let out = cleanTrailingErrors(messages);
		try {
			const model = this.modelManager.getResolvedModelForSession(sessionKey);
			out = tryApplySessionTranscriptHygiene(out, model);
		} catch (err) {
			log.warn({
				err,
				sessionKey
			}, "Transcript hygiene on load skipped");
		}
		return out;
	}
	parseSessionKey(sessionKey) {
		return parseOutboundSessionKey(sessionKey, this.config.config);
	}
	initSessionContext(sessionKey, channel, chatId, senderId = "") {
		const context = {
			sessionKey,
			channel,
			chatId,
			senderId,
			isGroup: false
		};
		this.contextMiddleware.onRequest({
			sessionKey,
			userId: context.senderId,
			channel,
			chatId
		});
		this.sessionContextManager.enter(context);
		this.feedbackCoordinator.setContext(context);
		this.agentManager.getOrCreateAgent(sessionKey);
		this.setupSessionEventHandling(sessionKey);
		return context;
	}
	/**
	* Persist inbound file attachments under agent home `inbound/` (non-images with data).
	* Idempotent if `workspaceRelativePath` is already set on an attachment.
	*/
	async prepareInboundAttachments(sessionKey, attachments) {
		const cfg = this.config.config;
		return persistInboundAttachmentsToWorkspace(resolveAgentHomeDir(cfg, extractProfileAgentId(sessionKey, cfg)), sessionKey, attachments);
	}
	endDirectRequestContext() {
		this.feedbackCoordinator.clearContext();
		this.contextMiddleware.onResponse();
	}
	/**
	* Reset a session's transcript and drop the in-memory agent so the next turn
	* reloads from disk. Combines two collaborators (sessionStore + agentManager)
	* so it stays on `AgentService`; pure sessionStore reads should use
	* `agentService.sessionStore.*` directly.
	*/
	async clearSessionMessages(key) {
		await this.sessionStore.saveMessages(key, []);
		this.agentManager.removeAgent(key);
	}
	/**
	* Reset session transcript (archive + new session id) and evict in-memory agent state.
	* Preserves the session key and persisted per-session overrides.
	*/
	async resetSession(key) {
		const { abortEmbeddedRun } = await import("./embedded/runs.js");
		const { retireSessionMcpRuntimeForSessionKey } = await import("./mcp/bundle-mcp-tools.js");
		await abortEmbeddedRun(key);
		const outcome = await this.sessionStore.reset(key);
		if (!outcome) return null;
		this.agentManager.removeAgent(key);
		await retireSessionMcpRuntimeForSessionKey({
			sessionKey: key,
			reason: "session-reset"
		});
		return outcome;
	}
	/**
	* Drop in-memory agent so the next turn reloads transcript from disk (e.g. after checkpoint restore).
	*/
	evictSessionAgent(sessionKey) {
		this.agentManager.removeAgent(sessionKey);
	}
	/**
	* Load session working directory override into AgentManager, ensure directory exists.
	* Call before AgentManager.getOrCreateAgent for this session.
	*/
	/** Workspace root for UI file tree / editor (same as agent tools after hydration). */
	async getEffectiveWorkspacePathForSession(sessionKey) {
		await this.sessionHydrator.workspace(sessionKey);
		const cfg = this.config.config;
		return effectiveWorkspacePathForSession(cfg, sessionKey, await this.sessionConfigStore.get(sessionKey));
	}
	/**
	* Best-effort timezone resolution for webchat envelope timestamps.
	* Reads `USER.md` under the agent `profile/` directory and extracts a `Timezone:` line.
	*/
	resolveUserTimezoneForSession(sessionKey) {
		try {
			const cfg = this.effectiveAppConfig();
			if (!cfg) return void 0;
			const { agentId } = resolveEffectiveAgentProfileForSession(cfg, sessionKey);
			const userPath = join(resolveAgentProfileDir(cfg, agentId), "USER.md");
			if (!existsSync(userPath)) return void 0;
			return readFileSync(userPath, "utf-8").match(/Timezone:\s*(.+)/i)?.[1]?.trim() || void 0;
		} catch {
			return;
		}
	}
	/**
	* Setup event handling for a specific session
	*/
	setupSessionEventHandling(sessionKey) {
		if (this.sessionState.hasSessionEventUnsubscriber(sessionKey)) return;
		const unsubscribe = this.agentManager.subscribeToSession(sessionKey, (event) => {
			this.handleSessionEvent(sessionKey, event);
		});
		if (unsubscribe) this.sessionState.setSessionEventUnsubscriber(sessionKey, unsubscribe);
	}
	/**
	* Handle events from a specific session's agent
	*/
	handleSessionEvent(sessionKey, event) {
		const currentContext = this.sessionContextManager.getContext();
		if (!currentContext) return;
		if (currentContext.sessionKey !== sessionKey) {
			this.agentEventHandler.handle(event, currentContext);
			return;
		}
		if (event.type === "message_update") {
			const msgEvent = event;
			if (msgEvent.message?.role === "assistant") {
				const content = msgEvent.message.content;
				const text = Array.isArray(content) ? extractTextContent(content) : String(content);
				this.streamManager.update(text);
			}
		}
		this.agentEventHandler.handle(event, currentContext);
	}
	async checkAndCompact(sessionKey, messages) {
		const contextWindow = this.getContextWindow();
		const prep = this.sessionStore.prepareCompaction(sessionKey, messages, contextWindow);
		if (!prep.needsCompaction) return;
		log.info({
			sessionKey,
			reason: prep.stats?.reason,
			usagePercent: prep.stats?.usagePercent
		}, "Session needs compaction");
		const result = await this.sessionStore.compact(sessionKey, messages, contextWindow, void 0, false);
		await this.hookHandler.trigger("after_compaction", {
			messageCount: messages.length,
			tokenCount: result.tokensBefore,
			compactedCount: messages.length - result.firstKeptIndex
		});
		log.info({
			sessionKey,
			tokensBefore: result.tokensBefore,
			tokensAfter: result.tokensAfter
		}, "Session compacted");
	}
	getContextWindow() {
		const defaults = this.config.agentDefaults || this.config.config?.agents?.defaults;
		return defaults?.maxTokens ? defaults.maxTokens * 4 : 128e3;
	}
	dispose() {
		this.sessionTracker.dispose();
		this.sessionState.disposeAll();
		this.agentManager.dispose();
		this.workflowProgressBrokerHandle?.dispose();
		this.workflowProgressBrokerHandle = null;
	}
};
//#endregion
export { AgentService };

//# sourceMappingURL=service.js.map