import { h as __exportAll } from "./pi-embedded-utils-CPkknmhn.js";
import { u as resolveSessionAgentId } from "./agent-scope-BzfnY0io.js";
import { C as parseDurationMs } from "./config-CCV5uD38.js";
import { d as imageMimeFromFormat } from "./image-ops-CkVtcgS9.js";
import { i as resolveImageSanitizationLimits, r as sanitizeToolResultImages } from "./tool-images-tBLbhG0w.js";
import { a as jsonResult, u as readStringParam } from "./common-D7lLwt9D.js";
import { n as readGatewayCallOptions, t as callGatewayTool } from "./gateway-CKFg1Dnv.js";
import { n as resolveNodeId, r as resolveNodeIdFromList, t as listNodes } from "./nodes-utils-BKzhXH7q.js";
import { i as stringEnum, r as optionalStringEnum } from "./typebox-Dqngjw-j.js";
import { a as writeCameraClipPayloadToFile, c as asString, i as writeBase64ToFile, l as resolveTempPathParts, n as parseCameraClipPayload, o as writeUrlToFile, r as parseCameraSnapPayload, s as asRecord, t as cameraTempPath } from "./nodes-camera-VOSANyOd.js";
import * as path$1 from "node:path";
import crypto from "node:crypto";
import { Type } from "@sinclair/typebox";

//#region src/cli/parse-timeout.ts
function parseTimeoutMs(raw) {
	if (raw === void 0 || raw === null) return;
	let value = NaN;
	if (typeof raw === "number") value = raw;
	else if (typeof raw === "bigint") value = Number(raw);
	else if (typeof raw === "string") {
		const trimmed = raw.trim();
		if (!trimmed) return;
		value = Number.parseInt(trimmed, 10);
	}
	return Number.isFinite(value) ? value : void 0;
}

//#endregion
//#region src/cli/nodes-run.ts
function parseEnvPairs(pairs) {
	if (!Array.isArray(pairs) || pairs.length === 0) return;
	const env = {};
	for (const pair of pairs) {
		if (typeof pair !== "string") continue;
		const idx = pair.indexOf("=");
		if (idx <= 0) continue;
		const key = pair.slice(0, idx).trim();
		if (!key) continue;
		env[key] = pair.slice(idx + 1);
	}
	return Object.keys(env).length > 0 ? env : void 0;
}

//#endregion
//#region src/cli/nodes-screen.ts
function parseScreenRecordPayload(value) {
	const obj = asRecord(value);
	const format = asString(obj.format);
	const base64 = asString(obj.base64);
	if (!format || !base64) throw new Error("invalid screen.record payload");
	return {
		format,
		base64,
		durationMs: typeof obj.durationMs === "number" ? obj.durationMs : void 0,
		fps: typeof obj.fps === "number" ? obj.fps : void 0,
		screenIndex: typeof obj.screenIndex === "number" ? obj.screenIndex : void 0,
		hasAudio: typeof obj.hasAudio === "boolean" ? obj.hasAudio : void 0
	};
}
function screenRecordTempPath(opts) {
	const { tmpDir, id, ext } = resolveTempPathParts(opts);
	return path$1.join(tmpDir, `minion-screen-record-${id}${ext}`);
}
async function writeScreenRecordToFile(filePath, base64) {
	return writeBase64ToFile(filePath, base64);
}

//#endregion
//#region src/agents/tools/misc/nodes-tool.ts
var nodes_tool_exports = /* @__PURE__ */ __exportAll({ createNodesTool: () => createNodesTool });
const NodesToolSchema = Type.Object({
	action: stringEnum([
		"status",
		"describe",
		"pending",
		"approve",
		"reject",
		"notify",
		"camera_snap",
		"camera_list",
		"camera_clip",
		"screen_record",
		"location_get",
		"run",
		"invoke"
	]),
	gatewayUrl: Type.Optional(Type.String()),
	gatewayToken: Type.Optional(Type.String()),
	timeoutMs: Type.Optional(Type.Number()),
	node: Type.Optional(Type.String()),
	requestId: Type.Optional(Type.String()),
	title: Type.Optional(Type.String()),
	body: Type.Optional(Type.String()),
	sound: Type.Optional(Type.String()),
	priority: optionalStringEnum([
		"passive",
		"active",
		"timeSensitive"
	]),
	delivery: optionalStringEnum([
		"system",
		"overlay",
		"auto"
	]),
	facing: optionalStringEnum([
		"front",
		"back",
		"both"
	], { description: "camera_snap: front/back/both; camera_clip: front/back only." }),
	maxWidth: Type.Optional(Type.Number()),
	quality: Type.Optional(Type.Number()),
	delayMs: Type.Optional(Type.Number()),
	deviceId: Type.Optional(Type.String()),
	duration: Type.Optional(Type.String()),
	durationMs: Type.Optional(Type.Number()),
	includeAudio: Type.Optional(Type.Boolean()),
	fps: Type.Optional(Type.Number()),
	screenIndex: Type.Optional(Type.Number()),
	outPath: Type.Optional(Type.String()),
	maxAgeMs: Type.Optional(Type.Number()),
	locationTimeoutMs: Type.Optional(Type.Number()),
	desiredAccuracy: optionalStringEnum([
		"coarse",
		"balanced",
		"precise"
	]),
	command: Type.Optional(Type.Array(Type.String())),
	cwd: Type.Optional(Type.String()),
	env: Type.Optional(Type.Array(Type.String())),
	commandTimeoutMs: Type.Optional(Type.Number()),
	invokeTimeoutMs: Type.Optional(Type.Number()),
	needsScreenRecording: Type.Optional(Type.Boolean()),
	invokeCommand: Type.Optional(Type.String()),
	invokeParamsJson: Type.Optional(Type.String())
});
function createNodesTool(options) {
	const sessionKey = options?.agentSessionKey?.trim() || void 0;
	const agentId = resolveSessionAgentId({
		sessionKey: options?.agentSessionKey,
		config: options?.config
	});
	const imageSanitization = resolveImageSanitizationLimits(options?.config);
	return {
		label: "Nodes",
		name: "nodes",
		description: "Discover and control paired nodes (status/describe/pairing/notify/camera/screen/location/run/invoke).",
		parameters: NodesToolSchema,
		execute: async (_toolCallId, args) => {
			const params = args;
			const action = readStringParam(params, "action", { required: true });
			const gatewayOpts = readGatewayCallOptions(params);
			try {
				switch (action) {
					case "status": return jsonResult(await callGatewayTool("node.list", gatewayOpts, {}));
					case "describe": return jsonResult(await callGatewayTool("node.describe", gatewayOpts, { nodeId: await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true })) }));
					case "pending": return jsonResult(await callGatewayTool("node.pair.list", gatewayOpts, {}));
					case "approve": return jsonResult(await callGatewayTool("node.pair.approve", gatewayOpts, { requestId: readStringParam(params, "requestId", { required: true }) }));
					case "reject": return jsonResult(await callGatewayTool("node.pair.reject", gatewayOpts, { requestId: readStringParam(params, "requestId", { required: true }) }));
					case "notify": {
						const node = readStringParam(params, "node", { required: true });
						const title = typeof params.title === "string" ? params.title : "";
						const body = typeof params.body === "string" ? params.body : "";
						if (!title.trim() && !body.trim()) throw new Error("title or body required");
						await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId: await resolveNodeId(gatewayOpts, node),
							command: "system.notify",
							params: {
								title: title.trim() || void 0,
								body: body.trim() || void 0,
								sound: typeof params.sound === "string" ? params.sound : void 0,
								priority: typeof params.priority === "string" ? params.priority : void 0,
								delivery: typeof params.delivery === "string" ? params.delivery : void 0
							},
							idempotencyKey: crypto.randomUUID()
						});
						return jsonResult({ ok: true });
					}
					case "camera_snap": {
						const nodeId = await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true }));
						const facingRaw = typeof params.facing === "string" ? params.facing.toLowerCase() : "both";
						const facings = facingRaw === "both" ? ["front", "back"] : facingRaw === "front" || facingRaw === "back" ? [facingRaw] : (() => {
							throw new Error("invalid facing (front|back|both)");
						})();
						const maxWidth = typeof params.maxWidth === "number" && Number.isFinite(params.maxWidth) ? params.maxWidth : void 0;
						const quality = typeof params.quality === "number" && Number.isFinite(params.quality) ? params.quality : void 0;
						const delayMs = typeof params.delayMs === "number" && Number.isFinite(params.delayMs) ? params.delayMs : void 0;
						const deviceId = typeof params.deviceId === "string" && params.deviceId.trim() ? params.deviceId.trim() : void 0;
						const content = [];
						const details = [];
						for (const facing of facings) {
							const payload = parseCameraSnapPayload((await callGatewayTool("node.invoke", gatewayOpts, {
								nodeId,
								command: "camera.snap",
								params: {
									facing,
									maxWidth,
									quality,
									format: "jpg",
									delayMs,
									deviceId
								},
								idempotencyKey: crypto.randomUUID()
							}))?.payload);
							const normalizedFormat = payload.format.toLowerCase();
							if (normalizedFormat !== "jpg" && normalizedFormat !== "jpeg" && normalizedFormat !== "png") throw new Error(`unsupported camera.snap format: ${payload.format}`);
							const isJpeg = normalizedFormat === "jpg" || normalizedFormat === "jpeg";
							const filePath = cameraTempPath({
								kind: "snap",
								facing,
								ext: isJpeg ? "jpg" : "png"
							});
							if (payload.url) await writeUrlToFile(filePath, payload.url);
							else if (payload.base64) await writeBase64ToFile(filePath, payload.base64);
							content.push({
								type: "text",
								text: `MEDIA:${filePath}`
							});
							if (payload.base64) content.push({
								type: "image",
								data: payload.base64,
								mimeType: imageMimeFromFormat(payload.format) ?? (isJpeg ? "image/jpeg" : "image/png")
							});
							details.push({
								facing,
								path: filePath,
								width: payload.width,
								height: payload.height
							});
						}
						return await sanitizeToolResultImages({
							content,
							details
						}, "nodes:camera_snap", imageSanitization);
					}
					case "camera_list": {
						const raw = await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId: await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true })),
							command: "camera.list",
							params: {},
							idempotencyKey: crypto.randomUUID()
						});
						return jsonResult(raw && typeof raw.payload === "object" && raw.payload !== null ? raw.payload : {});
					}
					case "camera_clip": {
						const nodeId = await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true }));
						const facing = typeof params.facing === "string" ? params.facing.toLowerCase() : "front";
						if (facing !== "front" && facing !== "back") throw new Error("invalid facing (front|back)");
						const payload = parseCameraClipPayload((await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId,
							command: "camera.clip",
							params: {
								facing,
								durationMs: typeof params.durationMs === "number" && Number.isFinite(params.durationMs) ? params.durationMs : typeof params.duration === "string" ? parseDurationMs(params.duration) : 3e3,
								includeAudio: typeof params.includeAudio === "boolean" ? params.includeAudio : true,
								format: "mp4",
								deviceId: typeof params.deviceId === "string" && params.deviceId.trim() ? params.deviceId.trim() : void 0
							},
							idempotencyKey: crypto.randomUUID()
						}))?.payload);
						const filePath = await writeCameraClipPayloadToFile({
							payload,
							facing
						});
						return {
							content: [{
								type: "text",
								text: `FILE:${filePath}`
							}],
							details: {
								facing,
								path: filePath,
								durationMs: payload.durationMs,
								hasAudio: payload.hasAudio
							}
						};
					}
					case "screen_record": {
						const nodeId = await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true }));
						const durationMs = typeof params.durationMs === "number" && Number.isFinite(params.durationMs) ? params.durationMs : typeof params.duration === "string" ? parseDurationMs(params.duration) : 1e4;
						const fps = typeof params.fps === "number" && Number.isFinite(params.fps) ? params.fps : 10;
						const payload = parseScreenRecordPayload((await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId,
							command: "screen.record",
							params: {
								durationMs,
								screenIndex: typeof params.screenIndex === "number" && Number.isFinite(params.screenIndex) ? params.screenIndex : 0,
								fps,
								format: "mp4",
								includeAudio: typeof params.includeAudio === "boolean" ? params.includeAudio : true
							},
							idempotencyKey: crypto.randomUUID()
						}))?.payload);
						const written = await writeScreenRecordToFile(typeof params.outPath === "string" && params.outPath.trim() ? params.outPath.trim() : screenRecordTempPath({ ext: payload.format || "mp4" }), payload.base64);
						return {
							content: [{
								type: "text",
								text: `FILE:${written.path}`
							}],
							details: {
								path: written.path,
								durationMs: payload.durationMs,
								fps: payload.fps,
								screenIndex: payload.screenIndex,
								hasAudio: payload.hasAudio
							}
						};
					}
					case "location_get": return jsonResult((await callGatewayTool("node.invoke", gatewayOpts, {
						nodeId: await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true })),
						command: "location.get",
						params: {
							maxAgeMs: typeof params.maxAgeMs === "number" && Number.isFinite(params.maxAgeMs) ? params.maxAgeMs : void 0,
							desiredAccuracy: params.desiredAccuracy === "coarse" || params.desiredAccuracy === "balanced" || params.desiredAccuracy === "precise" ? params.desiredAccuracy : void 0,
							timeoutMs: typeof params.locationTimeoutMs === "number" && Number.isFinite(params.locationTimeoutMs) ? params.locationTimeoutMs : void 0
						},
						idempotencyKey: crypto.randomUUID()
					}))?.payload ?? {});
					case "run": {
						const node = readStringParam(params, "node", { required: true });
						const nodes = await listNodes(gatewayOpts);
						if (nodes.length === 0) throw new Error("system.run requires a paired companion app or node host (no nodes available).");
						const nodeId = resolveNodeIdFromList(nodes, node);
						const nodeInfo = nodes.find((entry) => entry.nodeId === nodeId);
						if (!(Array.isArray(nodeInfo?.commands) ? nodeInfo?.commands?.includes("system.run") : false)) throw new Error("system.run requires a companion app or node host; the selected node does not support system.run.");
						const commandRaw = params.command;
						if (!commandRaw) throw new Error("command required (argv array, e.g. ['echo', 'Hello'])");
						if (!Array.isArray(commandRaw)) throw new Error("command must be an array of strings (argv), e.g. ['echo', 'Hello']");
						const command = commandRaw.map((c) => String(c));
						if (command.length === 0) throw new Error("command must not be empty");
						const cwd = typeof params.cwd === "string" && params.cwd.trim() ? params.cwd.trim() : void 0;
						const env = parseEnvPairs(params.env);
						const commandTimeoutMs = parseTimeoutMs(params.commandTimeoutMs);
						const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs);
						const runParams = {
							command,
							cwd,
							env,
							timeoutMs: commandTimeoutMs,
							needsScreenRecording: typeof params.needsScreenRecording === "boolean" ? params.needsScreenRecording : void 0,
							agentId,
							sessionKey
						};
						try {
							return jsonResult((await callGatewayTool("node.invoke", gatewayOpts, {
								nodeId,
								command: "system.run",
								params: runParams,
								timeoutMs: invokeTimeoutMs,
								idempotencyKey: crypto.randomUUID()
							}))?.payload ?? {});
						} catch (firstErr) {
							if (!(firstErr instanceof Error ? firstErr.message : String(firstErr)).includes("SYSTEM_RUN_DENIED: approval required")) throw firstErr;
						}
						const APPROVAL_TIMEOUT_MS = 12e4;
						const cmdText = command.join(" ");
						const approvalId = crypto.randomUUID();
						const approvalResult = await callGatewayTool("exec.approval.request", {
							...gatewayOpts,
							timeoutMs: APPROVAL_TIMEOUT_MS + 5e3
						}, {
							id: approvalId,
							command: cmdText,
							cwd,
							host: "node",
							agentId,
							sessionKey,
							timeoutMs: APPROVAL_TIMEOUT_MS
						});
						const decisionRaw = approvalResult && typeof approvalResult === "object" ? approvalResult.decision : void 0;
						const approvalDecision = decisionRaw === "allow-once" || decisionRaw === "allow-always" ? decisionRaw : null;
						if (!approvalDecision) {
							if (decisionRaw === "deny") throw new Error("exec denied: user denied");
							if (decisionRaw === void 0 || decisionRaw === null) throw new Error("exec denied: approval timed out");
							throw new Error("exec denied: invalid approval decision");
						}
						return jsonResult((await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId,
							command: "system.run",
							params: {
								...runParams,
								runId: approvalId,
								approved: true,
								approvalDecision
							},
							timeoutMs: invokeTimeoutMs,
							idempotencyKey: crypto.randomUUID()
						}))?.payload ?? {});
					}
					case "invoke": {
						const nodeId = await resolveNodeId(gatewayOpts, readStringParam(params, "node", { required: true }));
						const invokeCommand = readStringParam(params, "invokeCommand", { required: true });
						const invokeParamsJson = typeof params.invokeParamsJson === "string" ? params.invokeParamsJson.trim() : "";
						let invokeParams = {};
						if (invokeParamsJson) try {
							invokeParams = JSON.parse(invokeParamsJson);
						} catch (err) {
							const message = err instanceof Error ? err.message : String(err);
							throw new Error(`invokeParamsJson must be valid JSON: ${message}`, { cause: err });
						}
						const invokeTimeoutMs = parseTimeoutMs(params.invokeTimeoutMs);
						return jsonResult(await callGatewayTool("node.invoke", gatewayOpts, {
							nodeId,
							command: invokeCommand,
							params: invokeParams,
							timeoutMs: invokeTimeoutMs,
							idempotencyKey: crypto.randomUUID()
						}) ?? {});
					}
					default: throw new Error(`Unknown action: ${action}`);
				}
			} catch (err) {
				const nodeLabel = typeof params.node === "string" && params.node.trim() ? params.node.trim() : "auto";
				const gatewayLabel = gatewayOpts.gatewayUrl && gatewayOpts.gatewayUrl.trim() ? gatewayOpts.gatewayUrl.trim() : "default";
				const agentLabel = agentId ?? "unknown";
				const message = err instanceof Error ? err.message : String(err);
				throw new Error(`agent=${agentLabel} node=${nodeLabel} gateway=${gatewayLabel} action=${action}: ${message}`, { cause: err });
			}
		}
	};
}

//#endregion
export { nodes_tool_exports as n, createNodesTool as t };