const require_stream_channel = require("../stream-channel.cjs");
const require_mux = require("../mux.cjs");
//#region src/stream/transformers/lifecycle.ts
/**
* Filter a lifecycle {@link StreamChannel} to only the entries whose
* namespace lies within the subtree rooted at {@link path}.
*
* Returns an `AsyncIterable` whose iterator yields every entry whose
* namespace either equals {@link path} or is a descendant of it.
* Iteration begins at {@link startAt}, so callers can capture the
* log's current size at construction time to skip entries emitted
* before the caller existed (e.g. a subgraph stream discovered
* mid-run shouldn't replay the root's `started`).
*
* @param log - The shared lifecycle log owned by the transformer.
* @param path - Namespace prefix to scope entries by (use `[]` for
*   the root subtree, i.e. everything).
* @param startAt - Zero-based index into the log to begin from.
* @returns An async iterable of matching lifecycle entries.
*/
function filterLifecycleEntries(log, path, startAt = 0) {
	return { [Symbol.asyncIterator]() {
		const base = log.iterate(startAt);
		return { async next() {
			while (true) {
				const result = await base.next();
				if (result.done) return {
					value: void 0,
					done: true
				};
				if (require_mux.hasPrefix(result.value.namespace, path)) return {
					value: result.value,
					done: false
				};
			}
		} };
	} };
}
const DEFAULT_ROOT_GRAPH_NAME = "root";
function defaultGuessGraphName(ns) {
	if (ns.length === 0) return DEFAULT_ROOT_GRAPH_NAME;
	const last = ns[ns.length - 1];
	const colon = last.indexOf(":");
	return colon === -1 ? last : last.slice(0, colon);
}
function defaultSerializeError(err) {
	if (err instanceof Error) return err.message;
	if (typeof err === "string") return err;
	try {
		return JSON.stringify(err);
	} catch {
		return String(err);
	}
}
function isRecord(value) {
	return typeof value === "object" && value !== null && !Array.isArray(value);
}
/**
* Extract an upstream `cause` from a `lifecycle.started` payload, if
* the shape matches one of the known variants. Shape validation is
* intentionally loose: any object with a string `type` is accepted, so
* future protocol variants flow through unchanged.
*/
function extractCause(data) {
	if (!isRecord(data)) return void 0;
	if (data.event !== "started") return void 0;
	const cause = data.cause;
	if (!isRecord(cause)) return void 0;
	if (typeof cause.type !== "string") return void 0;
	return cause;
}
function extractTaskResultCompletion(data) {
	if (!isRecord(data)) return void 0;
	if (!("result" in data)) return void 0;
	if (typeof data.name !== "string") return void 0;
	if (typeof data.id !== "string") return void 0;
	if (data.name.startsWith("__")) return void 0;
	return {
		name: data.name,
		id: data.id
	};
}
/**
* Create the built-in lifecycle transformer.
*
* Marked as a {@link NativeStreamTransformer} so the run stream
* factory can expose `_lifecycleLog` via a dedicated getter
* (`run.lifecycle`) rather than through `run.extensions`.
*/
function createLifecycleTransformer(options = {}) {
	const rootGraphName = options.rootGraphName ?? DEFAULT_ROOT_GRAPH_NAME;
	const initialStatus = options.initialStatus ?? "running";
	const emitRootOnRegister = options.emitRootOnRegister ?? true;
	const getGraphName = options.getGraphName ?? defaultGuessGraphName;
	const serializeError = options.serializeError ?? defaultSerializeError;
	const getTerminalStatusOverride = options.getTerminalStatusOverride;
	const log = require_stream_channel.StreamChannel.local();
	const namespaces = /* @__PURE__ */ new Map();
	const namespaceCause = /* @__PURE__ */ new Map();
	/**
	* `lc_agent_name` observed at each namespace (first task event wins). A
	* nested run carrying an `lc_agent_name` (set by `createAgent`) is treated
	* as a named subagent: its `graph_name` becomes that name and a tool-call
	* `cause` is recovered (see {@link deriveToolCallCause}). Namespaces without
	* one fall back to the parsed namespace segment, preserving the prior
	* product-agnostic behavior for plain subgraphs.
	*/
	const lcByNs = /* @__PURE__ */ new Map();
	/**
	* Pregel task id -> triggering LLM `tool_call_id`, harvested from a task
	* whose `input` is a `tool_call_with_context` dict (current shape) or a list
	* of tool-call dicts (legacy shape). The child subgraph's namespace segment
	* `node:<task_id>` shares this task id, so a named subagent recovers the tool
	* call that spawned it across payloads.
	*/
	const pendingToolCalls = /* @__PURE__ */ new Map();
	const pendingInterruptIds = /* @__PURE__ */ new Set();
	/**
	* Child namespaces whose parent just saw an `updates` event with a
	* `node` attribution. We defer the `lifecycle.completed` emission
	* until the *next* inbound event (or `finalize`) so the parent's
	* `updates` lands on the wire before its child is marked complete -
	* matching the previous session behavior.
	*/
	const pendingCompletions = [];
	let emitter;
	let inSelfEmit = 0;
	let finalized = false;
	const resolveGraphName = (ns) => {
		if (ns.length === 0) return rootGraphName;
		const lc = lcByNs.get(require_mux.nsKey(ns));
		if (typeof lc === "string" && lc.length > 0) return lc;
		return getGraphName(ns);
	};
	/**
	* Record a namespace's `lc_agent_name` from a task-start payload (first
	* event wins). The presence of a name is what marks the namespace a named
	* subagent; the value may be `undefined` for unnamed runs (still recorded so
	* a later event doesn't re-evaluate it).
	*/
	const recordIdentity = (ns, data) => {
		const key = require_mux.nsKey(ns);
		if (lcByNs.has(key)) return;
		const lc = (isRecord(data) && isRecord(data.metadata) ? data.metadata : void 0)?.lc_agent_name;
		lcByNs.set(key, typeof lc === "string" ? lc : void 0);
	};
	/**
	* Harvest a task's triggering `tool_call_id` keyed by its task id. The
	* spawned subgraph's namespace segment `node:<task_id>` shares that id, so a
	* subagent can later recover the tool call that caused it. Two input shapes
	* are handled: a `tool_call_with_context` object (`input.tool_call.id`) and a
	* legacy list of tool-call objects (first with a string `id`).
	*/
	const recordPendingToolCalls = (data) => {
		if (!isRecord(data)) return;
		const taskId = data.id;
		if (typeof taskId !== "string") return;
		const input = data.input;
		let toolCallId;
		if (isRecord(input) && isRecord(input.tool_call)) {
			const candidate = input.tool_call.id;
			if (typeof candidate === "string") toolCallId = candidate;
		} else if (Array.isArray(input)) {
			for (const toolCall of input) if (isRecord(toolCall) && typeof toolCall.id === "string") {
				toolCallId = toolCall.id;
				break;
			}
		}
		if (toolCallId != null) pendingToolCalls.set(taskId, toolCallId);
	};
	/**
	* Derive a `toolCall` cause for a named subagent namespace by joining the
	* namespace segment's task id (`node:<task_id>`) to a previously harvested
	* `tool_call_id`. Only fires for namespaces carrying an `lc_agent_name`, so
	* plain subgraphs never get a spurious cause.
	*/
	const deriveToolCallCause = (ns) => {
		if (ns.length === 0) return void 0;
		const lc = lcByNs.get(require_mux.nsKey(ns));
		if (typeof lc !== "string" || lc.length === 0) return void 0;
		const segment = ns[ns.length - 1];
		const colon = segment.indexOf(":");
		if (colon === -1) return void 0;
		const triggerCallId = segment.slice(colon + 1);
		if (triggerCallId.length === 0) return void 0;
		const toolCallId = pendingToolCalls.get(triggerCallId);
		if (typeof toolCallId !== "string" || toolCallId.length === 0) return;
		return {
			type: "toolCall",
			tool_call_id: toolCallId
		};
	};
	/**
	* Resolve the `cause` to attach to a namespace's `started`. An upstream
	* `cause` stashed from a product-specific transformer (e.g. deepagents'
	* SubagentTransformer) wins; otherwise a tool-call cause is recovered for
	* named subagents.
	*/
	const resolveStartCause = (ns) => namespaceCause.get(require_mux.nsKey(ns)) ?? deriveToolCallCause(ns);
	const emit = (ns, status, extras) => {
		const key = require_mux.nsKey(ns);
		let current = namespaces.get(key);
		const graphName = current?.graphName ?? resolveGraphName(ns);
		if (current != null && current.status === status && current.graphName === graphName && extras?.error == null) return;
		if (current == null) {
			current = {
				namespace: ns,
				graphName,
				status
			};
			namespaces.set(key, current);
		} else current.status = status;
		const data = {
			event: status,
			graph_name: graphName,
			...extras?.cause != null ? { cause: extras.cause } : {},
			...extras?.error != null ? { error: extras.error } : {}
		};
		const timestamp = Date.now();
		log.push({
			namespace: ns,
			timestamp,
			...data
		});
		if (ns.length === 0 && !emitRootOnRegister) return;
		if (emitter == null) return;
		inSelfEmit += 1;
		try {
			emitter.push(ns, {
				type: "event",
				seq: 0,
				method: "lifecycle",
				params: {
					namespace: ns,
					timestamp,
					data
				}
			});
		} finally {
			inSelfEmit -= 1;
		}
	};
	/**
	* Ensures a record exists for `ns` without mutating its status. Used
	* by hooks that need a canonical `graphName` for lookups before emit
	* writes the first status. Status remains `undefined` until `emit`
	* fires.
	*/
	const trackNamespace = (ns) => {
		const key = require_mux.nsKey(ns);
		let rec = namespaces.get(key);
		if (rec == null) {
			rec = {
				namespace: ns,
				graphName: resolveGraphName(ns),
				status: void 0
			};
			namespaces.set(key, rec);
		}
		return rec;
	};
	const flushPendingCompletions = () => {
		if (pendingCompletions.length === 0) return;
		const toFlush = pendingCompletions.splice(0, pendingCompletions.length);
		for (const completion of toFlush) {
			const key = require_mux.nsKey(completion.namespace);
			const rec = namespaces.get(key);
			if (rec == null || rec.status !== "started") continue;
			emit(completion.namespace, "completed");
		}
	};
	const enqueueCompletion = (completion) => {
		const key = require_mux.nsKey(completion.namespace);
		const rec = namespaces.get(key);
		if (rec == null || rec.status !== "started") return;
		if (pendingCompletions.some((pending) => require_mux.nsKey(pending.namespace) === key)) return;
		pendingCompletions.push(completion);
	};
	const removePendingNodeCompletions = (parent, node) => {
		for (let index = pendingCompletions.length - 1; index >= 0; index -= 1) {
			const pending = pendingCompletions[index];
			if (pending.source.type !== "node") continue;
			if (pending.source.node !== node) continue;
			if (require_mux.nsKey(pending.source.parent) !== require_mux.nsKey(parent)) continue;
			pendingCompletions.splice(index, 1);
		}
	};
	const ensureStarted = (ns) => {
		for (let length = 1; length <= ns.length; length += 1) {
			const prefix = ns.slice(0, length);
			const key = require_mux.nsKey(prefix);
			if (namespaces.has(key)) continue;
			trackNamespace(prefix);
			const cause = resolveStartCause(prefix);
			emit(prefix, "started", cause != null ? { cause } : void 0);
		}
	};
	const defaultTerminalStatus = () => pendingInterruptIds.size > 0 ? "interrupted" : "completed";
	const cascadeTerminalStatus = (status) => {
		for (const rec of namespaces.values()) {
			if (rec.namespace.length === 0) continue;
			if (rec.status !== "started") continue;
			emit(rec.namespace, status);
		}
		emit([], status);
		log.close();
	};
	const resolveTerminalStatusOverride = async () => {
		if (getTerminalStatusOverride == null) return defaultTerminalStatus();
		try {
			return await getTerminalStatusOverride() ?? defaultTerminalStatus();
		} catch {
			return defaultTerminalStatus();
		}
	};
	const findStartedChildForNode = (parentNamespace, node) => {
		const prefix = `${node}:`;
		for (const rec of namespaces.values()) {
			if (rec.namespace.length !== parentNamespace.length + 1) continue;
			if (rec.status !== "started") continue;
			if (!require_mux.hasPrefix(rec.namespace, parentNamespace)) continue;
			const last = rec.namespace[rec.namespace.length - 1];
			if (last === node || last.startsWith(prefix)) return rec.namespace;
		}
	};
	const findStartedChildForTask = (parentNamespace, task) => {
		const namespace = [...parentNamespace, `${task.name}:${task.id}`];
		return namespaces.get(require_mux.nsKey(namespace))?.status === "started" ? namespace : void 0;
	};
	return {
		__native: true,
		init() {
			return {
				_lifecycleLog: log,
				lifecycle: filterLifecycleEntries(log, [], 0)
			};
		},
		onRegister(handle) {
			emitter = handle;
			trackNamespace([]);
			if (emitRootOnRegister) emit([], initialStatus);
		},
		process(event) {
			const ns = event.params.namespace;
			if (inSelfEmit > 0) return true;
			const taskCompletion = event.method === "tasks" ? extractTaskResultCompletion(event.params.data) : void 0;
			if (taskCompletion != null) removePendingNodeCompletions(ns, taskCompletion.name);
			else if (event.method === "tasks") {
				recordIdentity(ns, event.params.data);
				recordPendingToolCalls(event.params.data);
			}
			flushPendingCompletions();
			if (event.method === "lifecycle") {
				const cause = extractCause(event.params.data);
				if (cause != null) namespaceCause.set(require_mux.nsKey(ns), cause);
				ensureStarted(ns);
				return false;
			}
			ensureStarted(ns);
			if (event.method === "input" && isRecord(event.params.data) && event.params.data.event === "requested") {
				const id = event.params.data.id;
				if (typeof id === "string") pendingInterruptIds.add(id);
			}
			if (taskCompletion != null) {
				const childNamespace = findStartedChildForTask(ns, taskCompletion);
				if (childNamespace != null) enqueueCompletion({
					namespace: childNamespace,
					source: { type: "task" }
				});
			}
			if (event.method === "updates") {
				const node = event.params.node;
				if (typeof node === "string" && !node.startsWith("__")) {
					const childNamespace = findStartedChildForNode(ns, node);
					if (childNamespace != null) enqueueCompletion({
						namespace: childNamespace,
						source: {
							type: "node",
							parent: ns,
							node
						}
					});
				}
			}
			return true;
		},
		finalize() {
			if (finalized) return;
			finalized = true;
			flushPendingCompletions();
			if (getTerminalStatusOverride == null) {
				cascadeTerminalStatus(defaultTerminalStatus());
				return;
			}
			return resolveTerminalStatusOverride().then(cascadeTerminalStatus).catch((err) => {
				log.fail(err);
			});
		},
		fail(err) {
			if (finalized) return;
			finalized = true;
			const errorMessage = serializeError(err);
			for (const rec of namespaces.values()) {
				if (rec.namespace.length === 0) continue;
				if (rec.status !== "started") continue;
				emit(rec.namespace, "failed");
			}
			emit([], "failed", { error: errorMessage });
			log.fail(err);
		}
	};
}
//#endregion
exports.createLifecycleTransformer = createLifecycleTransformer;
exports.filterLifecycleEntries = filterLifecycleEntries;

//# sourceMappingURL=lifecycle.cjs.map