import { t as createSubsystemLogger } from "./subsystem-qDUB67UD.js";
import { p as resolveDefaultModelForAgent } from "./model-selection-DCVjDyLz.js";
import { n as prepareSimpleCompletionModel } from "./simple-completion-runtime-BkSLbXlj.js";
import { completeSimple } from "@earendil-works/pi-ai";
//#region src/cognitive/persona/sentiment-detector.ts
const SENTIMENT_PATTERNS = [
	{
		label: "frustrated",
		patterns: [
			/烦死/,
			/受不了/,
			/崩溃/,
			/真烦/,
			/太烦/,
			/气死/,
			/怎么又/,
			/怎么总是/,
			/怎么还是/,
			/不靠谱/,
			/\bfucking\b/i,
			/\bdamn\b/i
		],
		confidence: .85
	},
	{
		label: "excited",
		patterns: [
			/太棒/,
			/太好[了啦]/,
			/太酷/,
			/太牛/,
			/太厉害了/,
			/终于搞定/,
			/终于解决/,
			/终于完成/,
			/原来如此/,
			/\bamazing\b/i,
			/\bawesome\b/i
		],
		confidence: .8
	},
	{
		label: "confused",
		patterns: [
			/不明白/,
			/不理解/,
			/不懂/,
			/什么意思/,
			/没看懂/,
			/怎么回事/,
			/\bconfused\b/i,
			/能解释一下吗/
		],
		confidence: .75
	}
];
/**
* Detect strong sentiment in a user message.
* Returns undefined for neutral messages.
*/
function detectSentiment(userMessage) {
	for (const { label, patterns, confidence } of SENTIMENT_PATTERNS) for (const pattern of patterns) {
		const match = userMessage.match(pattern);
		if (match) return {
			label,
			confidence,
			evidence: match[0]
		};
	}
}
//#endregion
//#region src/cognitive/persona/extractor.ts
const BLACKLIST_PATTERNS = [
	{ pattern: /永远不要跟我提(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /别再跟我说(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /以后别提(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /我受够了(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /不想再听到(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /拉黑(.{1,30}?)(?:了|吧|了啊)?$/ },
	{ pattern: /never mention (?:about )?(.{1,40}?) again/i },
	{ pattern: /stop talking about (.+?)$/i },
	{ pattern: /don'?t ever mention (.+?)$/i },
	{ pattern: /i'?m sick of (.+?)$/i },
	{ pattern: /blacklist (.+?)$/i },
	{ pattern: /ban (.+?)$/i }
];
function detectBlacklistIntent(userMessage) {
	const results = [];
	for (const { pattern } of BLACKLIST_PATTERNS) {
		const match = userMessage.match(pattern);
		if (match?.[1]) results.push(match[1].trim());
	}
	return results;
}
/**
* Extract persona attributes from a conversation turn using rule-based analysis.
*
* This is the Phase 2 version — purely rule-based, no LLM call.
* Phase 4 will add LLM-based deep extraction.
*
* This function MUST be cheap and fast (<50ms).
*/
function extractFromMessage(userMessage, _assistantMessage, _existingPersona) {
	const attributes = [];
	const domains = [];
	const recentFocus = [];
	for (const { pattern, trait, source } of [
		{
			pattern: /我(?:是|做|在|负责|擅长|专注于?)\s*(.{2,20})/,
			trait: "自我描述",
			source: "explicit"
		},
		{
			pattern: /我(?:的工作|职业|岗位|职位)\s*(?:是|做)?\s*(.{2,20})/,
			trait: "职业",
			source: "explicit"
		},
		{
			pattern: /(?:我的|我们的?)\s*(?:项目|团队|公司)\s*(.{2,30})/,
			trait: "项目/组织",
			source: "explicit"
		}
	]) {
		const match = userMessage.match(pattern);
		if (match?.[1]) attributes.push({
			field: `identity.coreTraits.${trait}`,
			value: match[1].trim(),
			confidence: source === "explicit" ? .8 : .5,
			source,
			evidence: match[0]
		});
	}
	const nouns = extractKeyPhrases(userMessage);
	recentFocus.push(...nouns.slice(0, 5));
	const sentiment = detectSentiment(userMessage);
	const blacklistRequests = detectBlacklistIntent(userMessage);
	return {
		attributes,
		domains,
		recentFocus,
		blacklistRequests: blacklistRequests.length > 0 ? blacklistRequests : void 0,
		sentiment
	};
}
const NOISE_PATTERNS = [
	/^```.*$/,
	/^`[^`]+`$/,
	/^\s*$/,
	/^\d+$/,
	/^(yes|no|ok|okay|好的|嗯|是|不是|对|不对|哈|啊|呢|吧|么|吗|哦|噢)$/i,
	/^(if|the|a|an|is|are|was|were|be|been|being|do|does|did|will|would|can|could|should|may|might|shall|to|of|in|for|on|with|at|by|from|as|into|about|this|that|these|those|it|its|or|and|but|not|no|so|than|too|very)$/i
];
function isNoisePhrase(s) {
	for (const pat of NOISE_PATTERNS) if (pat.test(s)) return true;
	if (/^[\s\p{P}\p{S}\p{C}]+$/u.test(s)) return true;
	if (/^[\d\s,.\-+/\\#@$%^&*(){}[\]|~`]+$/.test(s)) return true;
	return false;
}
/**
* Simple key phrase extraction — split on common delimiters and filter.
*/
function extractKeyPhrases(text) {
	const phrases = text.replace(/```[\s\S]*?```/g, "").replace(/`[^`]+`/g, "").split(/[，。！？,.!?\n\r;；:：\s—–\-_=+|/\\{}[\]()]+/).map((s) => s.trim()).filter((s) => s.length >= 2 && s.length <= 30 && !isNoisePhrase(s));
	return [...new Set(phrases)].slice(0, 5);
}
//#endregion
//#region src/cognitive/persona/llm-extractor.ts
const log = createSubsystemLogger("cognitive/persona-extractor");
const VALID_INSIGHT_CATEGORIES = new Set([
	"domain_knowledge",
	"behavioral_pattern",
	"stated_preference",
	"tool_config",
	"contextual_fact",
	"goal_or_aspiration"
]);
const DEFAULT_TIMEOUT_MS = 1e4;
const DEFAULT_MAX_TOKENS = 1500;
function isTextContentBlock(block) {
	return block.type === "text";
}
function splitModelRef(modelRef) {
	const idx = modelRef.indexOf("/");
	if (idx > 0 && idx < modelRef.length - 1) return {
		provider: modelRef.slice(0, idx),
		modelId: modelRef.slice(idx + 1)
	};
	return null;
}
function createDefaultDeps() {
	return {
		complete: completeSimple,
		prepareModel: async (cfg, modelRef) => {
			if (modelRef) {
				const split = splitModelRef(modelRef);
				if (split) return prepareSimpleCompletionModel({
					cfg,
					provider: split.provider,
					modelId: split.modelId
				});
			}
			const resolved = resolveDefaultModelForAgent({ cfg });
			return prepareSimpleCompletionModel({
				cfg,
				provider: resolved.provider,
				modelId: resolved.model
			});
		}
	};
}
/**
* LLM-based persona extraction with rule-based fallback.
*
* Sends the conversation turn + existing persona to the LLM and asks it to
* extract structured persona attributes. Falls back to the rule-based
* `extractFromMessage()` on timeout, error, or empty/unparseable response.
*
* This function **never throws**.
*/
async function extractFromMessageLLM(userMessage, assistantMessage, existingPersona, config, deps, options) {
	const prompt = buildExtractionPrompt(userMessage, assistantMessage, existingPersona);
	try {
		const modelRef = options?.modelRef ?? config.cognitive?.persona?.extractionModel;
		const prepared = await deps.prepareModel(config, modelRef);
		if ("error" in prepared) {
			log.warn("persona extraction fell back to rule-based", { reason: "error" });
			return extractFromMessage(userMessage, assistantMessage, existingPersona);
		}
		const controller = new AbortController();
		const timeout = setTimeout(() => controller.abort(), options?.timeout ?? DEFAULT_TIMEOUT_MS);
		let result;
		try {
			result = await deps.complete(prepared.model, { messages: [{
				role: "user",
				content: prompt,
				timestamp: Date.now()
			}] }, {
				apiKey: prepared.auth.apiKey,
				maxTokens: options?.maxTokens ?? DEFAULT_MAX_TOKENS,
				temperature: .2,
				signal: controller.signal
			});
		} finally {
			clearTimeout(timeout);
		}
		const text = result.content.filter(isTextContentBlock).map((block) => block.text).join("").trim();
		if (!text) {
			log.warn("persona extraction fell back to rule-based", { reason: "empty" });
			return extractFromMessage(userMessage, assistantMessage, existingPersona);
		}
		const parsed = parseLLMExtraction(text);
		if (parsed.attributes.length === 0 && parsed.domains.length === 0 && parsed.recentFocus.length === 0) {
			log.warn("persona extraction fell back to rule-based", {
				reason: "parse-failed",
				rawPreview: text.slice(0, 200)
			});
			return extractFromMessage(userMessage, assistantMessage, existingPersona);
		}
		log.info("persona extraction completed", {
			method: "llm",
			domainsFound: parsed.domains.length,
			attributesFound: Object.keys(parsed.attributes).length,
			hasFocus: parsed.recentFocus.length > 0
		});
		return parsed;
	} catch {
		log.warn("persona extraction fell back to rule-based", { reason: "timeout" });
		return extractFromMessage(userMessage, assistantMessage, existingPersona);
	}
}
function buildExtractionPrompt(userMessage, assistantMessage, persona) {
	const personaContext = persona ? `\n\nKnown persona:\n- Domains: ${Object.keys(persona.domains).join(", ") || "none"}\n- Focus: ${persona.recentFocus.slice(0, 5).join(", ") || "none"}` : "";
	return `You are a persona extraction system. Analyze the conversation turn below and extract structured persona attributes ABOUT THE HUMAN USER ONLY.

USER MESSAGE (from the human):
${userMessage.slice(-1e3)}

ASSISTANT MESSAGE (from the AI assistant, for context only):
${assistantMessage.slice(-500)}
${personaContext}

Extract and respond with ONLY a JSON object in this exact format (no markdown, no code fences):
{
  "attributes": [
    {"field": "identity.coreTraits.职业", "value": "...", "confidence": 0.8, "source": "explicit", "evidence": "..."},
    {"field": "identity.communicationStyle.technicalLevel", "value": "expert", "confidence": 0.7, "source": "inferred", "evidence": "..."}
  ],
  "domains": [
    {
      "name": "AI/机器学习",
      "depth": 3,
      "insights": ["user understands transformer architecture"],
      "typedInsights": [
        {"text": "user understands transformer architecture", "category": "domain_knowledge", "confidence": 0.8, "source": "inferred"},
        {"text": "user prefers Rust for systems programming", "category": "stated_preference", "confidence": 0.9, "source": "explicit"}
      ],
      "questions": []
    }
  ],
  "recentFocus": ["topic1", "topic2"]
}

Rules:
- ONLY extract attributes about the HUMAN USER, never about the AI assistant
- If the assistant says "I am an AI" or "I don't have consciousness", do NOT extract that as a user trait
- The ASSISTANT MESSAGE is provided for conversational context only — it tells you what the user was responding to, but you must NOT extract the assistant's self-descriptions as user attributes
- Only extract what the USER clearly states or strongly implies about THEMSELVES
- confidence: 0-1, where 1 is absolutely certain
- source: "explicit" (user stated it), "inferred" (strongly implied from user's words), "observed" (from user behavior)
- domain depth: 1=mentioned, 3=familiar, 5=expert
- Only include domains that are actually discussed by the USER
- Discover domains dynamically — do NOT limit to a predefined list. Name domains naturally in Chinese or English based on the conversation content (e.g. "前端开发", "DevOps", "量化交易", "游戏设计", "区块链")
- Each domain MUST include typedInsights. Classify every insight into exactly one category:
  - domain_knowledge: Factual knowledge the user demonstrates about a domain
  - behavioral_pattern: Repeated behaviors or thinking patterns observed in the user
  - stated_preference: Explicit preferences the user has expressed
  - tool_config: Configuration or usage of specific tools and technologies
  - contextual_fact: Situational, short-lived facts (e.g. "currently at work", "using laptop")
  - goal_or_aspiration: Long-term goals, career aspirations, or learning objectives
- typedInsights must have: text (string), category (one of the 6 above), confidence (0-1), source (explicit/inferred/observed)
- If nothing meaningful can be extracted, return empty arrays
- Respond with ONLY the JSON, nothing else`;
}
function parseLLMExtraction(text) {
	try {
		const jsonStr = extractJSONFromMixedText(text);
		if (!jsonStr) {
			log.warn("parseLLMExtraction: no JSON object found in response", { raw: text.slice(0, 200) });
			return {
				attributes: [],
				domains: [],
				recentFocus: []
			};
		}
		let parsed;
		try {
			parsed = JSON.parse(jsonStr);
		} catch {
			log.warn("parseLLMExtraction: JSON parse failed", { raw: jsonStr.slice(0, 200) });
			return {
				attributes: [],
				domains: [],
				recentFocus: []
			};
		}
		return {
			attributes: Array.isArray(parsed.attributes) ? parsed.attributes.map((a) => ({
				field: String(a.field ?? ""),
				value: String(a.value ?? ""),
				confidence: clamp01(Number(a.confidence ?? .5)),
				source: validSource(a.source),
				evidence: String(a.evidence ?? "")
			})) : [],
			domains: Array.isArray(parsed.domains) ? parsed.domains.map((d) => ({
				name: String(d.name ?? ""),
				depth: clampDepth(Number(d.depth ?? 1)),
				insights: Array.isArray(d.insights) ? d.insights.map(String) : [],
				typedInsights: Array.isArray(d.typedInsights) ? d.typedInsights.filter((ti) => typeof ti.text === "string" && ti.text.length > 0).map((ti) => ({
					text: String(ti.text),
					category: validInsightCategory(ti.category),
					confidence: clamp01(Number(ti.confidence ?? .5)),
					source: validSource(ti.source)
				})) : void 0,
				questions: Array.isArray(d.questions) ? d.questions.map(String) : []
			})) : [],
			recentFocus: Array.isArray(parsed.recentFocus) ? parsed.recentFocus.map(String).slice(0, 5) : []
		};
	} catch {
		return {
			attributes: [],
			domains: [],
			recentFocus: []
		};
	}
}
function extractJSONFromMixedText(text) {
	const trimmed = text.trim();
	try {
		JSON.parse(trimmed);
		return trimmed;
	} catch {}
	const stripped = trimmed.replace(/^```(?:json)?\s*\n?/i, "").replace(/\n?\s*```\s*$/i, "").trim();
	try {
		JSON.parse(stripped);
		return stripped;
	} catch {}
	const firstBrace = stripped.indexOf("{");
	const lastBrace = stripped.lastIndexOf("}");
	if (firstBrace !== -1 && lastBrace !== -1 && lastBrace > firstBrace) {
		const candidate = stripped.slice(firstBrace, lastBrace + 1);
		try {
			JSON.parse(candidate);
			return candidate;
		} catch {
			const fixed = tryFixTruncatedJSON(candidate);
			if (fixed) return fixed;
		}
	}
	return null;
}
/**
* Attempt to fix truncated JSON by closing open brackets.
*/
function tryFixTruncatedJSON(jsonStr) {
	let openBraces = 0;
	let openBrackets = 0;
	let inString = false;
	let escape = false;
	for (const ch of jsonStr) {
		if (escape) {
			escape = false;
			continue;
		}
		if (ch === "\\") {
			if (inString) escape = true;
			continue;
		}
		if (ch === "\"") {
			inString = !inString;
			continue;
		}
		if (inString) continue;
		if (ch === "{") openBraces++;
		else if (ch === "}") openBraces--;
		else if (ch === "[") openBrackets++;
		else if (ch === "]") openBrackets--;
	}
	if (openBraces < 0 || openBrackets < 0) return null;
	let fixed = jsonStr;
	if (inString) fixed += "\"";
	fixed = fixed.replace(/,\s*"[^"]*"\s*:\s*$/s, "");
	for (let i = 0; i < openBrackets; i++) fixed += "]";
	for (let i = 0; i < openBraces; i++) fixed += "}";
	try {
		JSON.parse(fixed);
		return fixed;
	} catch {
		return null;
	}
}
function clamp01(n) {
	return Math.max(0, Math.min(1, n));
}
function clampDepth(n) {
	return Math.max(1, Math.min(5, n));
}
function validSource(value) {
	const s = String(value);
	if (s === "explicit" || s === "inferred" || s === "observed") return s;
	return "inferred";
}
function validInsightCategory(value) {
	const s = String(value);
	if (VALID_INSIGHT_CATEGORIES.has(s)) return s;
	return "domain_knowledge";
}
//#endregion
export { createDefaultDeps, extractFromMessageLLM };
