const require_message_coercion = require("../message-coercion.cjs");
const require_store = require("../store.cjs");
const require_namespace = require("../namespace.cjs");
//#region src/stream/discovery/subagents.ts
/** Stable empty map — reused on {@link SubagentDiscovery.reset}. */
const EMPTY_SUBAGENT_MAP = /* @__PURE__ */ new Map();
var SubagentDiscovery = class {
	store = new require_store.StreamStore(/* @__PURE__ */ new Map());
	#map = /* @__PURE__ */ new Map();
	#taskIdByObservedNamespace = /* @__PURE__ */ new Map();
	#observedOwnNamespaces = /* @__PURE__ */ new Set();
	#toolCallIdByTaskInput = /* @__PURE__ */ new Map();
	/** Feed a single root event. Non-discovery events are ignored. */
	push(event) {
		if (event.method === "tools") this.#onToolEvent(event);
		else if (event.method === "values") this.#onValuesEvent(event);
	}
	/**
	* Seed discovery from a checkpoint's root `messages` array (as
	* returned by `client.threads.getState().values.messages`) so deep
	* agent cards render on thread refresh without waiting for SSE
	* replay.
	*
	* Drives the existing root `values` path via a synthetic event so it
	* reuses task discovery + completion marking with no new parsing
	* logic. Root namespace `[]` keeps namespaces at the default
	* `tools:<toolCallId>`; the always-on root pump (and {@link
	* applyExecutionNamespace}) promote them to the execution namespace
	* later. Idempotent by construction: re-driving root values for
	* already-discovered tasks is a no-op (the FIFO `taskInput` queue is
	* only populated on first discovery), so no `snapshot.size` guard is
	* needed.
	*/
	seedFromCheckpointMessages(messages) {
		if (!Array.isArray(messages) || messages.length === 0) return;
		this.push({
			type: "event",
			method: "values",
			params: {
				namespace: [],
				timestamp: Date.now(),
				data: { messages }
			}
		});
	}
	/**
	* Promote a discovered subagent to its execution namespace, derived
	* from checkpoint history (see `namespace-from-history`).
	*
	* Routes through the same guarded promotion machinery the live SSE
	* replay uses ({@link #recordTaskNamespaceCandidate} + the
	* `#observedOwnNamespaces` no-demote rule) so a getHistory-derived
	* namespace and an SSE-derived one cannot disagree. A no-op when the
	* subagent is unknown or already sits on the target namespace.
	*
	* @param toolCallId - Parent `task` tool-call id (the subagent's discovery key).
	* @param executionSegment - Single execution namespace segment, e.g. `tools:<uuid>`.
	*/
	applyExecutionNamespace(toolCallId, executionSegment) {
		if (typeof executionSegment !== "string" || executionSegment.length === 0) return;
		const entry = this.#map.get(toolCallId);
		if (entry == null) return;
		const namespace = [executionSegment];
		const namespaceKeyed = require_namespace.namespaceKey(namespace);
		const ownNamespaceKey = `tools:${toolCallId}`;
		this.#recordTaskNamespaceCandidate(toolCallId, namespace);
		if (namespaceKeyed !== ownNamespaceKey && this.#observedOwnNamespaces.has(toolCallId)) return;
		if (require_namespace.namespaceKey(entry.namespace) === namespaceKeyed) return;
		entry.namespace = namespace;
		this.#commit();
	}
	/** Current snapshot map. */
	get snapshot() {
		return this.store.getSnapshot();
	}
	/**
	* Drop all discovery state. Called on thread rebind / dispose so a
	* new thread's subagents cannot bleed into the previous UI.
	*/
	reset() {
		this.#map.clear();
		this.#taskIdByObservedNamespace.clear();
		this.#observedOwnNamespaces.clear();
		this.#toolCallIdByTaskInput.clear();
		this.store.setValue(EMPTY_SUBAGENT_MAP);
	}
	discoverFromMessage(message, namespace) {
		let changed = false;
		for (const toolCall of getTaskToolCalls(message)) changed = this.#upsertTaskToolCall(toolCall.id, toolCall.input, namespace) || changed;
		if (changed) this.#commit();
	}
	#commit() {
		this.store.setValue(new Map([...this.#map.values()].map((entry) => [entry.id, toSnapshot(entry)])));
	}
	#onToolEvent(event) {
		const data = event.params.data;
		const toolCallId = data.tool_call_id;
		const toolName = data.tool_name;
		if (data.event === "tool-started" && toolName === "task") {
			const input = parseTaskInput(data.input);
			if (toolCallId == null) return;
			this.#upsertTaskToolCall(toolCallId, input, event.params.namespace);
			this.#commit();
			return;
		}
		if (toolCallId == null) return;
		const entry = this.#map.get(toolCallId);
		if (entry == null) return;
		if (data.event === "tool-finished") {
			entry.status = "complete";
			entry.output = data.output;
			entry.completedAt = /* @__PURE__ */ new Date();
			this.#commit();
			return;
		}
		if (data.event === "tool-error") {
			entry.status = "error";
			entry.error = data.message ?? "Subagent failed";
			entry.completedAt = /* @__PURE__ */ new Date();
			this.#commit();
		}
	}
	#onValuesEvent(event) {
		const data = event.params.data;
		if (data == null || typeof data !== "object" || Array.isArray(data)) return;
		const messages = data.messages;
		if (!Array.isArray(messages)) return;
		this.#bindNamespaceByTaskInput(event.params.namespace, messages);
		let changed = this.#recordObservedWorkNamespace(event.params.namespace);
		for (const message of messages) {
			for (const toolCall of getTaskToolCalls(message)) changed = this.#upsertTaskToolCall(toolCall.id, toolCall.input, event.params.namespace) || changed;
			const toolCallId = getToolMessageCallId(message);
			if (toolCallId == null) continue;
			const existing = this.#map.get(toolCallId);
			if (existing == null) continue;
			existing.status = "complete";
			existing.output = message;
			existing.completedAt = /* @__PURE__ */ new Date();
			changed = true;
		}
		if (changed) this.#commit();
	}
	#upsertTaskToolCall(toolCallId, input, eventNamespace) {
		const namespace = taskWorkNamespace(toolCallId, eventNamespace);
		const existing = this.#map.get(toolCallId);
		if (existing != null) {
			let changed = false;
			this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);
			const nextName = input.subagent_type ?? existing.name;
			const nextTaskInput = input.description ?? existing.taskInput;
			if (existing.name !== nextName) {
				existing.name = nextName;
				changed = true;
			}
			if (existing.taskInput !== nextTaskInput) {
				existing.taskInput = nextTaskInput;
				changed = true;
			}
			const namespaceKeyed = require_namespace.namespaceKey(existing.namespace);
			const ownNamespaceKey = `tools:${toolCallId}`;
			const nextNamespaceKey = require_namespace.namespaceKey(namespace);
			if (require_namespace.isConcreteToolNamespace(eventNamespace) || namespaceKeyed === ownNamespaceKey) {
				if (namespaceKeyed === ownNamespaceKey && nextNamespaceKey !== ownNamespaceKey && this.#observedOwnNamespaces.has(toolCallId)) return changed;
				if (namespaceKeyed !== nextNamespaceKey) {
					existing.namespace = namespace;
					changed = true;
				}
			}
			if (existing.status !== "complete" && existing.status !== "error") {
				if (existing.status !== "running") {
					existing.status = "running";
					changed = true;
				}
			}
			return changed;
		}
		const { parentId, depth } = lineageFromNamespace(eventNamespace);
		this.#recordTaskNamespaceCandidate(toolCallId, eventNamespace);
		if (input.description != null) {
			const queue = this.#toolCallIdByTaskInput.get(input.description) ?? [];
			queue.push(toolCallId);
			this.#toolCallIdByTaskInput.set(input.description, queue);
		}
		this.#map.set(toolCallId, {
			id: toolCallId,
			name: input.subagent_type ?? "unknown",
			namespace,
			parentId,
			depth,
			status: "running",
			taskInput: input.description,
			output: void 0,
			error: void 0,
			startedAt: /* @__PURE__ */ new Date(),
			completedAt: null
		});
		return true;
	}
	#recordObservedWorkNamespace(namespace) {
		if (!require_namespace.isConcreteToolNamespace(namespace)) return false;
		const last = namespace.at(-1);
		if (last == null) return false;
		const namespaceKeyed = require_namespace.namespaceKey(namespace);
		const toolCallId = this.#taskIdByObservedNamespace.get(namespaceKeyed) ?? last.slice(6);
		const existing = this.#map.get(toolCallId);
		if (existing == null) return false;
		if (namespaceKeyed === `tools:${toolCallId}`) this.#observedOwnNamespaces.add(toolCallId);
		else if (this.#observedOwnNamespaces.has(toolCallId) || !this.#taskIdByObservedNamespace.has(namespaceKeyed) && !shouldPromoteToObservedNamespace(existing)) return false;
		if (require_namespace.namespaceKey(existing.namespace) === namespaceKeyed) return false;
		existing.namespace = [...namespace];
		return true;
	}
	#recordTaskNamespaceCandidate(toolCallId, namespace) {
		if (!require_namespace.isConcreteToolNamespace(namespace)) return;
		this.#taskIdByObservedNamespace.set(require_namespace.namespaceKey(namespace), toolCallId);
	}
	/**
	* Bind a `tools:<id>` namespace to a registered subagent by looking
	* up the first HumanMessage content in the `taskInput` index.
	*/
	#bindNamespaceByTaskInput(namespace, messages) {
		if (!require_namespace.isConcreteToolNamespace(namespace)) return;
		const namespaceKeyed = require_namespace.namespaceKey(namespace);
		if (this.#taskIdByObservedNamespace.has(namespaceKeyed)) return;
		const text = getFirstHumanMessageText(messages);
		if (text == null) return;
		const toolCallId = this.#toolCallIdByTaskInput.get(text)?.shift();
		if (toolCallId == null) return;
		this.#taskIdByObservedNamespace.set(namespaceKeyed, toolCallId);
	}
};
function getFirstHumanMessageText(messages) {
	for (const message of messages) {
		if (message == null || typeof message !== "object") continue;
		const record = message;
		if ((record.type ?? record.role ?? record.kwargs?.type ?? record.lc_kwargs?.type) !== "human") continue;
		const content = record.content ?? record.kwargs?.content ?? record.lc_kwargs?.content;
		return typeof content === "string" && content.length > 0 ? content : null;
	}
	return null;
}
function shouldPromoteToObservedNamespace(entry) {
	return entry.name === "fanout-worker" || /^Worker worker-\d+/i.test(entry.taskInput ?? "");
}
function taskWorkNamespace(toolCallId, eventNamespace) {
	const last = eventNamespace.at(-1);
	if (last != null && require_namespace.isToolNamespaceSegment(last)) return [...eventNamespace];
	return [`tools:${toolCallId}`];
}
function toSnapshot(entry) {
	return {
		id: entry.id,
		name: entry.name,
		namespace: entry.namespace,
		parentId: entry.parentId,
		depth: entry.depth,
		status: entry.status,
		taskInput: entry.taskInput,
		output: entry.output,
		error: entry.error,
		startedAt: entry.startedAt,
		completedAt: entry.completedAt
	};
}
function parseTaskInput(raw) {
	if (raw == null) return {};
	if (typeof raw === "string") try {
		const parsed = JSON.parse(raw);
		return {
			description: typeof parsed.description === "string" ? parsed.description : void 0,
			subagent_type: typeof parsed.subagent_type === "string" ? parsed.subagent_type : void 0
		};
	} catch {
		return {};
	}
	if (typeof raw === "object" && !Array.isArray(raw)) {
		const obj = raw;
		return {
			description: typeof obj.description === "string" ? obj.description : void 0,
			subagent_type: typeof obj.subagent_type === "string" ? obj.subagent_type : void 0
		};
	}
	return {};
}
function getTaskToolCalls(message) {
	if (message == null || typeof message !== "object" || Array.isArray(message)) return [];
	const record = require_message_coercion.normalizeAIMessageToolCalls(message);
	const toolCalls = record.tool_calls ?? record.kwargs?.tool_calls ?? record.lc_kwargs?.tool_calls;
	if (!Array.isArray(toolCalls)) return [];
	const result = [];
	for (const toolCall of toolCalls) {
		if (toolCall == null || typeof toolCall !== "object" || Array.isArray(toolCall)) continue;
		const record = toolCall;
		if (typeof record.id !== "string" || record.name !== "task") continue;
		result.push({
			id: record.id,
			input: parseTaskInput(record.args)
		});
	}
	return result;
}
function getToolMessageCallId(message) {
	if (message == null || typeof message !== "object" || Array.isArray(message)) return;
	const record = message;
	const id = record.tool_call_id ?? record.kwargs?.tool_call_id ?? record.lc_kwargs?.tool_call_id;
	return typeof id === "string" && id.length > 0 ? id : void 0;
}
/**
* Derive (parentId, depth) from a namespace like
* `["subagents:abc:def"]`. Namespaces form a rooted tree; the last
* `:` segment of the deepest namespace element is the current node's
* call-id and the one before it is the parent.
*/
function lineageFromNamespace(namespace) {
	if (require_namespace.isRootNamespace(namespace)) return {
		parentId: null,
		depth: 1
	};
	const last = namespace[namespace.length - 1];
	if (last == null) return {
		parentId: null,
		depth: 1
	};
	const trimmed = last.split(":").filter((part) => part.length > 0).slice(1);
	const depth = Math.max(1, trimmed.length);
	return {
		parentId: (trimmed.length >= 2 ? trimmed[trimmed.length - 2] : null) ?? null,
		depth
	};
}
//#endregion
exports.SubagentDiscovery = SubagentDiscovery;

//# sourceMappingURL=subagents.cjs.map