import { createLogger } from "../../utils/logger/index.js";
import { init_logger } from "../../utils/logger.js";
import { buildSessionKey, init_session_key, parseSessionKey } from "../../routing/session-key.js";
import { getDefaultAgentId, init_resolve_route } from "../../routing/resolve-route.js";
import { AgentRunRelay } from "../agent-run-relay.js";
import { ClarifyBridge } from "../clarify-bridge.js";
import { runGatewayAgent } from "./run-gateway-agent.js";
//#region src/gateway/service/agent-runner.ts
init_session_key();
init_resolve_route();
init_logger();
const log = createLogger("GatewayAgentRunner");
var GatewayAgentRunner = class {
	opts;
	runRelay = new AgentRunRelay();
	/** Per-run abort for webchat (POST /api/agent/abort or client disconnect). */
	runAbortControllers = /* @__PURE__ */ new Map();
	clarifyBridge = new ClarifyBridge();
	/** Maps webchat session key → active `runId` for `clarify` tool routing. */
	activeWebchatRunBySession = /* @__PURE__ */ new Map();
	constructor(opts) {
		this.opts = opts;
	}
	/** True when a webchat agent run is currently in-flight for `sessionKey`. */
	hasActiveRun(sessionKey) {
		return this.activeWebchatRunBySession.has(sessionKey);
	}
	getActiveRunId(sessionKey) {
		return this.activeWebchatRunBySession.get(sessionKey);
	}
	getClarifyBridge() {
		return this.clarifyBridge;
	}
	/** Called from `GatewayService.stop()` so the bridge gets cleaned up. */
	disposeClarifyBridge() {
		this.clarifyBridge.dispose();
	}
	async *runAgent(message, channel, chatId, attachments, thinking, runOptions) {
		const iter = runGatewayAgent({
			config: this.opts.getConfig(),
			agentService: this.opts.getAgentService(),
			bus: this.opts.bus,
			runRelay: this.runRelay,
			runAbortControllers: this.runAbortControllers,
			activeWebchatRunBySession: this.activeWebchatRunBySession,
			sessionIndex: this.opts.sessionIndex,
			emit: this.opts.emit
		}, message, channel, chatId, attachments, thinking, runOptions);
		let step = await iter.next();
		while (!step.done) {
			yield step.value;
			step = await iter.next();
		}
		return step.value;
	}
	/** Abort an in-flight webchat agent run (matches `runId` from SSE `status`). */
	abortAgentRun(runId) {
		this.clarifyBridge.cancelForRun(runId);
		const keysToMark = [];
		for (const [sk, id] of this.activeWebchatRunBySession) if (id === runId) keysToMark.push(sk);
		for (const sk of keysToMark) this.activeWebchatRunBySession.delete(sk);
		const relaySk = this.runRelay.getSessionKey(runId);
		if (relaySk && !keysToMark.includes(relaySk)) keysToMark.push(relaySk);
		const c = this.runAbortControllers.get(runId);
		if (!c) return false;
		const cutoffTs = Date.now();
		for (const sk of keysToMark) {
			this.opts.sessionIndex.updateSessionMetadata(sk, { abortCutoffTimestamp: cutoffTs }).catch(() => {});
			this.opts.sessionIndex.appendTranscriptContextEntry(sk, {
				text: "Webchat agent run aborted",
				data: {
					runId,
					abortCutoffTimestamp: cutoffTs
				}
			}).catch(() => {});
		}
		c.abort();
		for (const sk of keysToMark) import("../../agent/embedded/runs.js").then(({ abortEmbeddedRun }) => abortEmbeddedRun(sk));
		return true;
	}
	/**
	* Queue steering text for an active webchat run (`Agent.steer` /
	* tool-boundary injection). `chatId` is the same as `POST /api/agent` body
	* (`sessionKey` or legacy peer id).
	*/
	async steerWebchatAgent(chatId, message) {
		const trimmed = message.trim();
		if (!trimmed) return {
			ok: false,
			code: "BAD_REQUEST"
		};
		const cfg = this.opts.getConfig();
		const sessionKey = parseSessionKey(chatId) ? chatId : buildSessionKey({
			agentId: getDefaultAgentId(cfg),
			source: "webchat",
			accountId: "default",
			peerKind: "direct",
			peerId: chatId
		});
		if (!this.activeWebchatRunBySession.has(sessionKey)) return {
			ok: false,
			code: "NO_ACTIVE_RUN"
		};
		if (!await this.opts.getAgentService().turnDispatcher.steerWebchatSession(sessionKey, trimmed)) return {
			ok: false,
			code: "STEER_FAILED"
		};
		return { ok: true };
	}
	/** Deliver a user's answer to a pending `clarify` tool call. */
	submitClarifyResponse(requestId, answer) {
		return this.clarifyBridge.handleResponse(requestId, answer);
	}
	/** Hermes-style: after HTTP sets a goal, enqueue the goal text as the next user turn. */
	enqueueWebchatPersistentGoalKickoff(sessionKey, goalText) {
		queueMicrotask(() => {
			this.drainScheduledWebchatContinuation(sessionKey, goalText);
		});
	}
	/** Background drain for extension-initiated webchat turns (`scheduleWebchatContinuation`). */
	async drainScheduledWebchatContinuation(sessionKey, message) {
		try {
			const gen = this.runAgent(message, "webchat", sessionKey, void 0, void 0, { clientCreatedAtMs: Date.now() });
			for await (const _ of gen);
		} catch (err) {
			log.warn({
				err,
				sessionKey
			}, "Scheduled webchat continuation failed");
		}
	}
	/**
	* Resolve clarify-bridge config for `sessionKey`: who delivers the question
	* (webchat SSE, Telegram message, or both), then start the bridge request.
	* Rejects when neither path is available (e.g. CLI without webchat or TG).
	*
	* `publishSseFor(runId)` is the bridge into AgentService's
	* `turnDispatcher.enqueueWebchatSseEvent`. We take it as a callback so the
	* runner does not import AgentService statically.
	*/
	requestClarification(opts) {
		const { sessionKey, request, publishSseFor } = opts;
		const runId = this.activeWebchatRunBySession.get(sessionKey);
		const publishSse = runId ? publishSseFor(runId) : void 0;
		const deliver = parseSessionKey(sessionKey)?.source === "telegram" ? async (ctx) => {
			await this.deliverTelegramClarify(ctx);
		} : void 0;
		if (!runId && !deliver) return Promise.reject(/* @__PURE__ */ new Error("Clarify is not available for this session (use webchat, Telegram, or CLI)"));
		return this.clarifyBridge.startRequest({
			sessionKey,
			runId,
			relay: this.runRelay,
			publishSse,
			request,
			deliver
		});
	}
	async deliverTelegramClarify(ctx) {
		const parsed = parseSessionKey(ctx.sessionKey);
		if (!parsed || parsed.source !== "telegram") return;
		let body = ctx.request.question;
		if (ctx.request.default) body += `\n\nDefault if unsure: ${ctx.request.default}`;
		const choices = ctx.request.choices;
		const buttonRows = choices && choices.length >= 2 ? choices.map((c, i) => [{
			text: c.length > 64 ? `${c.slice(0, 61)}…` : c,
			callback_data: `clarify:${ctx.requestId}:${i}`
		}]) : void 0;
		if (!buttonRows) body += "\n\nReply with your answer in this chat.";
		await this.opts.getChannelManager().send({
			channel: "telegram",
			chat_id: parsed.peerId,
			content: body,
			metadata: {
				accountId: parsed.accountId,
				...parsed.threadId ? { threadId: parsed.threadId } : {}
			},
			buttons: buttonRows
		});
	}
};
//#endregion
export { GatewayAgentRunner };

//# sourceMappingURL=agent-runner.js.map