import { randomUUID } from "node:crypto";
import { getAllTools } from "../llm/tools.js";
import { runBrowserTool, capToolResultText } from "./tool-runner.js";
import { ToolLoopDetector, detectorOptionsForProtocol, } from "./tool-loop-detection.js";
import { buildLlmContext, compactMessagesWithState, CONTEXT_BUDGETS, ContextTranscript, createEmptyContextState, estimateMessagesChars, truncateContextSection, } from "./context.js";
import { executeMemoryAction } from "./executors/memory-executor.js";
import { executeStoreAction } from "./executors/store-executor.js";
import { executeSkillAction } from "./executors/skill-executor.js";
import { executeSchedulerAction } from "./executors/scheduler-executor.js";
import { executeAttachmentAction } from "./executors/attachment-executor.js";
import { executeOrgAction } from "./executors/org-executor.js";
import { executeWebFetchAction } from "./executors/web-fetch-executor.js";
/** Short loggable preview of chat input (string or multimodal). */
export function summarizeChatInput(message) {
    if (typeof message === "string")
        return message;
    const kinds = {};
    for (const b of message)
        kinds[b.type] = (kinds[b.type] || 0) + 1;
    const text = message
        .filter((b) => b.type === "text" && b.text)
        .map((b) => b.text)
        .join(" ");
    const kindSummary = Object.entries(kinds)
        .map(([k, v]) => `${v} ${k}`)
        .join(", ");
    return text
        ? `[multimodal: ${kindSummary}] ${text.slice(0, 500)}`
        : `[multimodal: ${kindSummary}]`;
}
/**
 * Owns the LLM chat loop: history, tool dispatch, summarization, system
 * prompt assembly. The browser lock and per-turn logging are intentionally
 * NOT here — they belong to the caller (Agent) so the same engine can be
 * shared if we ever spawn a second chat surface.
 */
export class ChatEngine {
    deps;
    chatHistory = [];
    contextState = createEmptyContextState();
    transcript;
    summarizationCooldown = 0;
    /**
     * Identifies this chat as a logical session to the upstream gateway
     * (e.g. sub2api's sticky-session router). Regenerated on every reset()
     * so a cleared conversation gets a new sticky binding.
     */
    sessionId = randomUUID();
    constructor(deps) {
        this.deps = deps;
        this.transcript = new ContextTranscript(deps.workspace, "chat");
    }
    /** Clear chat history and earlier-summary context. */
    reset() {
        this.chatHistory = [];
        this.contextState = createEmptyContextState();
        this.transcript = new ContextTranscript(this.deps.workspace, "chat");
        this.summarizationCooldown = 0;
        this.sessionId = randomUUID();
    }
    /** For logging metadata — how many messages currently in history. */
    getHistoryLength() {
        return this.chatHistory.length;
    }
    /** Non-streaming chat turn. Returns the final assistant text. */
    async chat(message) {
        this.pushHistory({ role: "user", content: message }, { source: "user" });
        await this.compressHistory();
        // Read memory + skills indices ONCE per user turn so the cacheable static
        // system prefix stays byte-identical across every tool iteration of this
        // turn. If a tool inside the loop modifies memory or skills, those edits
        // surface on the *next* user turn — that one-turn lag is the price of a
        // stable cache prefix and is fine in practice.
        const loadedInstructionSkills = this.deps.skillGenerator.getAutoLoadedInstructionSkills(summarizeChatInput(message));
        const systemPrompt = this.buildChatSystemPrompt(this.loadChatMemoryIndex(), loadedInstructionSkills);
        const tools = getAllTools({
            imageGeneration: !!this.deps.imageGenerator,
            org: !!this.deps.orgManager,
        });
        const loopDetector = new ToolLoopDetector(detectorOptionsForProtocol(this.deps.llmProtocol));
        let llmContext = this.buildRequestForLlm(systemPrompt);
        let response = await this.deps.llmClient.chat(llmContext.messages, tools, llmContext.systemPrompt, this.cacheHintsFor(llmContext, tools.length));
        let iterations = 0;
        let forceStop = false;
        const maxIter = this.deps.maxIterations;
        while (response.toolCalls.length > 0 && iterations < maxIter && !forceStop) {
            iterations++;
            this.pushHistory({
                role: "assistant",
                content: response.content,
                toolCalls: response.toolCalls,
            }, { source: "llm" });
            for (const tc of response.toolCalls) {
                let result;
                try {
                    result = await this.executeToolCall(tc);
                }
                catch (err) {
                    result = `Error: ${err instanceof Error ? err.message : String(err)}`;
                }
                result = capToolResultText(tc.name, result);
                this.pushHistory({
                    role: "tool",
                    content: result,
                    toolCallId: tc.id,
                }, { source: "tool", toolName: tc.name });
                const action = tc.arguments?.action || tc.name;
                loopDetector.record(tc.name, action, tc.arguments || {}, result);
            }
            const loopCheck = loopDetector.check();
            if (loopCheck.status === "critical") {
                console.log(`  Loop detected (${loopCheck.pattern}): forcing stop`);
                this.pushHistory({ role: "user", content: loopCheck.message }, { source: "loop_detector" });
                forceStop = true;
                break;
            }
            else if (loopCheck.status === "warning") {
                console.log(`  Loop warning (${loopCheck.pattern}): injecting hint`);
                this.pushHistory({ role: "user", content: loopCheck.message }, { source: "loop_detector" });
            }
            if (response.content && response.content.length > 100) {
                loopDetector.notifySubstantiveResponse();
            }
            await this.compressHistory();
            llmContext = this.buildRequestForLlm(systemPrompt);
            response = await this.deps.llmClient.chat(llmContext.messages, tools, llmContext.systemPrompt, this.cacheHintsFor(llmContext, tools.length));
        }
        if ((iterations >= maxIter && response.toolCalls.length > 0) || forceStop) {
            if (!forceStop && response.content) {
                this.pushHistory({ role: "assistant", content: response.content }, { source: "llm" });
            }
            const stopReason = forceStop
                ? "A repetitive action loop was detected and stopped."
                : "You've reached the maximum number of tool-calling iterations allowed.";
            this.pushHistory({
                role: "user",
                content: `${stopReason} Please provide a final response summarizing what you've found and accomplished so far, without calling any more tools.`,
            }, { source: "iteration_limit" });
            await this.compressHistory();
            llmContext = this.buildRequestForLlm(systemPrompt);
            const summary = await this.deps.llmClient.chat(llmContext.messages, undefined, llmContext.systemPrompt, this.cacheHintsFor(llmContext, 0));
            const finalContent = summary.content +
                `\n\n⚠️ ${forceStop ? "Loop detected and stopped" : "Reached the maximum number of steps"}. Send a message to continue where I left off.`;
            this.pushHistory({ role: "assistant", content: finalContent }, { source: "llm" });
            return finalContent;
        }
        this.pushHistory({
            role: "assistant",
            content: response.content,
        }, { source: "llm" });
        return response.content;
    }
    /** Streaming chat turn. Yields text deltas; tool calls happen between yields. */
    async *chatStream(message) {
        this.pushHistory({ role: "user", content: message }, { source: "user" });
        await this.compressHistory();
        // Static system prefix is fixed for the entire turn — see chat() above for
        // the rationale (cache stability).
        const loadedInstructionSkills = this.deps.skillGenerator.getAutoLoadedInstructionSkills(summarizeChatInput(message));
        const systemPrompt = this.buildChatSystemPrompt(this.loadChatMemoryIndex(), loadedInstructionSkills);
        const tools = getAllTools({
            imageGeneration: !!this.deps.imageGenerator,
            org: !!this.deps.orgManager,
        });
        const loopDetector = new ToolLoopDetector(detectorOptionsForProtocol(this.deps.llmProtocol));
        let iterations = 0;
        let forceStop = false;
        const maxIter = this.deps.maxIterations;
        while (iterations < maxIter && !forceStop) {
            iterations++;
            let fullContent = "";
            const toolCalls = [];
            let hadToolCalls = false;
            const llmContext = this.buildRequestForLlm(systemPrompt);
            for await (const event of this.deps.llmClient.chatStream(llmContext.messages, tools, llmContext.systemPrompt, this.cacheHintsFor(llmContext, tools.length))) {
                if (event.type === "text_delta" && event.text) {
                    fullContent += event.text;
                    yield event.text;
                }
                else if (event.type === "tool_call_end" && event.toolCall) {
                    hadToolCalls = true;
                    toolCalls.push({
                        id: event.toolCall.id,
                        name: event.toolCall.name,
                        arguments: event.toolCall.arguments || {},
                    });
                }
            }
            if (!hadToolCalls) {
                this.pushHistory({ role: "assistant", content: fullContent }, { source: "llm" });
                return;
            }
            if (fullContent.length > 100) {
                loopDetector.notifySubstantiveResponse();
            }
            this.pushHistory({
                role: "assistant",
                content: fullContent,
                toolCalls,
            }, { source: "llm" });
            for (const tc of toolCalls) {
                let result;
                try {
                    result = await this.executeToolCall(tc);
                }
                catch (err) {
                    result = `Error: ${err instanceof Error ? err.message : String(err)}`;
                }
                result = capToolResultText(tc.name, result);
                this.pushHistory({
                    role: "tool",
                    content: result,
                    toolCallId: tc.id,
                }, { source: "tool", toolName: tc.name });
                const action = tc.arguments?.action || tc.name;
                loopDetector.record(tc.name, action, tc.arguments || {}, result);
            }
            const loopCheck = loopDetector.check();
            if (loopCheck.status === "critical") {
                console.log(`  Loop detected (${loopCheck.pattern}): forcing stop`);
                this.pushHistory({ role: "user", content: loopCheck.message }, { source: "loop_detector" });
                forceStop = true;
                break;
            }
            else if (loopCheck.status === "warning") {
                console.log(`  Loop warning (${loopCheck.pattern}): injecting hint`);
                this.pushHistory({ role: "user", content: loopCheck.message }, { source: "loop_detector" });
            }
            await this.compressHistory();
            yield "\n";
        }
        const stopReason = forceStop
            ? "A repetitive action loop was detected and stopped."
            : "You've reached the maximum number of tool-calling iterations allowed.";
        this.pushHistory({
            role: "user",
            content: `${stopReason} Please provide a final response summarizing what you've found and accomplished so far, without calling any more tools.`,
        }, { source: "iteration_limit" });
        await this.compressHistory();
        let summaryContent = "";
        const summaryContext = this.buildRequestForLlm(systemPrompt);
        for await (const event of this.deps.llmClient.chatStream(summaryContext.messages, undefined, summaryContext.systemPrompt, this.cacheHintsFor(summaryContext, 0))) {
            if (event.type === "text_delta" && event.text) {
                summaryContent += event.text;
                yield event.text;
            }
        }
        const suffix = `\n\n⚠️ ${forceStop ? "Loop detected and stopped" : "Reached the maximum number of steps"}. Send a message to continue where I left off.`;
        this.pushHistory({ role: "assistant", content: summaryContent + suffix }, { source: "llm" });
        yield suffix;
    }
    // ---- Tool dispatch ----
    async executeToolCall(tc) {
        const args = tc.arguments;
        const action = args.action;
        switch (tc.name) {
            case "browser": {
                // `args` already carries `action`; runBrowserTool reads it. Pass the
                // full args object so the shared runner can capture timing + ok bit
                // consistently with TaskRunner.
                const r = await runBrowserTool(this.deps.browserActions, {
                    ...args,
                    action,
                });
                return r.text;
            }
            case "memory":
                return executeMemoryAction(this.deps.memory, action, args);
            case "store":
                return executeStoreAction(this.deps.store, action, args);
            case "scheduler":
                return executeSchedulerAction(this.deps.scheduler, action, args);
            case "web_fetch":
                return await executeWebFetchAction(args);
            case "attachment":
                return await executeAttachmentAction({
                    workspace: this.deps.workspace,
                    skillGenerator: this.deps.skillGenerator,
                    getAttachmentSender: this.deps.getAttachmentSender,
                }, action, args);
            case "skill":
                return await executeSkillAction(this.deps.skillGenerator, action, args);
            case "image_generation":
                if (!this.deps.imageGenerator) {
                    return "Error: image_generation is not configured (set config.imageGeneration in your config.json).";
                }
                if (action === "generate") {
                    return this.deps.imageGenerator.generate(args, this.deps.getAttachmentSender(), this.deps.getProgressNotifier());
                }
                if (action === "edit") {
                    return this.deps.imageGenerator.edit(args, this.deps.getAttachmentSender(), this.deps.getProgressNotifier());
                }
                return `Unknown image_generation action: ${action}`;
            case "org":
                return executeOrgAction(this.deps.orgManager, this.deps.orgConfig, action, args);
            default:
                return `Unknown tool: ${tc.name}`;
        }
    }
    // ---- History sizing ----
    estimateHistoryChars() {
        return estimateMessagesChars(this.chatHistory);
    }
    pushHistory(message, meta) {
        this.chatHistory.push(message);
        this.transcript.append(message, meta);
    }
    /**
     * Compress chat history into structured context state. Raw messages are
     * always retained in the transcript; if summarization fails we keep the live
     * history until the hard limit requires a last-resort trim.
     */
    async compressHistory() {
        const limits = CONTEXT_BUDGETS.chat;
        const currentChars = this.estimateHistoryChars();
        if (currentChars <= limits.softLimitChars)
            return;
        if (this.summarizationCooldown <= 0) {
            const compacted = await compactMessagesWithState({
                messages: this.chatHistory,
                state: this.contextState,
                llmClient: this.deps.llmClient,
                charBudget: limits.targetChars,
                keepRatio: limits.keepRatio,
                preserveFirst: false,
                transcript: this.transcript,
                sessionId: this.sessionId,
            });
            if (compacted.changed) {
                console.log(`📝 Context compacted: summarized ${compacted.summarizedCount} messages. History now: ${estimateMessagesChars(compacted.messages)} chars.`);
                this.chatHistory = compacted.messages;
                this.contextState = compacted.state;
                return;
            }
            this.summarizationCooldown = 3;
            console.error(`⚠️ Context compaction skipped (${compacted.reason ?? "unknown"}); preserving history.`);
        }
        if (this.summarizationCooldown > 0) {
            this.summarizationCooldown--;
        }
        if (this.estimateHistoryChars() > limits.hardLimitChars) {
            this.simpleTrim(limits.hardLimitChars);
        }
    }
    /**
     * Drop old messages at user-message boundaries to fit budget. Fallback
     * when summarization is unavailable.
     */
    simpleTrim(maxChars) {
        if (this.estimateHistoryChars() <= maxChars)
            return;
        const history = this.chatHistory;
        const safeCuts = [];
        for (let i = 0; i < history.length; i++) {
            if (history[i].role === "user") {
                safeCuts.push(i);
            }
        }
        for (let ci = safeCuts.length - 1; ci >= 0; ci--) {
            const cutIdx = safeCuts[ci];
            if (cutIdx === 0)
                continue;
            const candidate = history.slice(cutIdx);
            let chars = 0;
            for (const msg of candidate) {
                const c = typeof msg.content === "string"
                    ? msg.content
                    : JSON.stringify(msg.content);
                chars += c.length;
                if (msg.toolCalls)
                    chars += JSON.stringify(msg.toolCalls).length;
            }
            if (chars <= maxChars) {
                this.chatHistory = candidate;
                return;
            }
        }
        // Fallback: keep only the last user message and following messages
        for (let i = history.length - 1; i >= 0; i--) {
            if (history[i].role === "user") {
                this.chatHistory = history.slice(i);
                return;
            }
        }
        this.chatHistory = [history[history.length - 1]];
    }
    buildRequestForLlm(systemPrompt) {
        return buildLlmContext({
            baseSystemPrompt: systemPrompt,
            state: this.contextState,
            messages: this.chatHistory,
            contextTitle: "Earlier Conversation State",
            transcript: this.transcript,
        });
    }
    /**
     * Translate the static/dynamic split into LLM provider cache hints. Anthropic
     * uses these for explicit cache_control breakpoints; OpenAI ignores them.
     */
    cacheHintsFor(llmContext, toolCount) {
        return {
            systemStaticChars: llmContext.systemPromptStatic.length,
            cacheTools: toolCount > 0,
            cacheMessages: llmContext.messages.length > 0,
            sessionId: this.sessionId,
        };
    }
    loadChatMemoryIndex() {
        return this.deps.memory.loadIndexCapped({
            maxEntries: CONTEXT_BUDGETS.chat.memoryIndexMaxEntries,
            maxBytes: CONTEXT_BUDGETS.chat.memoryIndexMaxBytes,
        });
    }
    loadInstalledSkillsIndex() {
        try {
            const skills = this.deps.skillGenerator.listSkills();
            return truncateContextSection(skills, CONTEXT_BUDGETS.chat.installedSkillsMaxChars, "installed skills list truncated");
        }
        catch (err) {
            const msg = err instanceof Error ? err.message : String(err);
            return `(could not load installed skills: ${msg})`;
        }
    }
    buildChatSystemPrompt(memoryIndex, loadedInstructionSkills) {
        const installedSkills = this.loadInstalledSkillsIndex();
        const autoLoadedSkills = this.deps.skillGenerator.renderLoadedInstructionSkillSection(loadedInstructionSkills);
        let prompt = `You are "${this.deps.agentName}", a browser automation assistant. You help users by controlling a web browser and managing scheduled tasks.

## Capabilities
- Browse the web: navigate, click, type, take screenshots, extract data
- Fetch read-only HTTP(S) resources directly with web_fetch for JSON, text,
  markdown, and public REST APIs
- Manage scheduled tasks: add, remove, enable/disable cron-based tasks
- Remember things: save and recall important information across sessions
- Handle attachments: list, read text files, install skills, and send files/images back to the user via the current frontend (Telegram)
- Manage skills: install markdown skills from attachments or URLs, read
  instruction skills, and save replayable JSON-step skills

## How to use attachment tool
When the user uploads a file or image via Telegram it is saved under
workspace/attachments/ and referenced in the user message (e.g.
\`attachments/<filename>\`). Uploaded images also arrive as inline multimodal
content — you can see them without any tool call.

Paths accepted by this tool are the file's name inside attachments/; a
leading \`attachments/\` is stripped automatically, so both
\`path="foo.txt"\` and \`path="attachments/foo.txt"\` work. Absolute paths and
\`..\` escapes are rejected.

- Use \`action="list"\` to see what's in attachments/.
- Use \`action="read_text"\` with \`path\` to read a text file. Rejects
  binary files and anything over 256 KB.
- Use \`action="send"\` with \`path\` to deliver a file back to the user.
  \`asImage\` is inferred from the extension (.png/.jpg/.jpeg/.webp/.gif →
  photo, otherwise document); set it explicitly to override. The tool only
  returns success after the frontend confirms delivery.
- Use \`action="install_skill"\` with a markdown skill file to add it to the
  skill library.

## How to use instruction skills and web_fetch
Some installed skills are markdown instructions rather than replayable browser
scripts. Use \`skill.list\` and \`skill.read\` to inspect them. If an
instruction skill mentions WebFetch or Bash(curl), map that request to the
\`web_fetch\` tool instead of trying to run shell commands. For remote skill
URLs, use \`skill.install\` with \`url\`.

## Installed Skills Routing
The installed skills list below is authoritative routing context. Before asking
the user which website/platform/source to use, check whether an installed skill
already matches the request. If it matches, use that skill first.

- For article/news/material/content queries involving tags, sources, language,
  keywords, or time windows, prefer an installed source/material library skill
  over generic web browsing.
- If \`sourcepool\` is installed, use it first for requests about articles,
  news, materials, tags, sources, languages, or "today/recent" content. Read
  \`sourcepool\` with \`skill.read\`, then call its read-only REST endpoints
  with \`web_fetch\`. Do not ask which platform to search unless sourcepool
  does not cover the request or the user explicitly asks for another site.

Installed skills:
${installedSkills}

${autoLoadedSkills}

To send the user a screenshot, call \`browser.screenshot\` with \`save: true\`
(or \`saveAs\` to pick a filename) — that writes the image into attachments/
and the text result gives you the filename to pass to \`attachment.send\`.

## How to use browser tool
The browser tool has two levels:
- **Top-level actions**: navigate, snapshot, screenshot, tabs, open, focus, close, console, dialog, upload
- **Interactions via act**: action="act" with kind="click", "type", "press", "hover", "drag", "select", "fill", "resize", "wait", "evaluate" (disabled by default), "close"

1. Use action "navigate" to go to a URL
2. Use action "snapshot" to see the page; full/interactive snapshots give element refs like [e1], [e2], while outline snapshots give selector hints
3. Use action "act" with kind "click", "type", etc. using element refs or CSS selectors
4. After interactions, snapshot again to verify results

Example: { action: "act", kind: "click", ref: "e5" }
Example: { action: "act", kind: "type", ref: "e3", text: "hello" }

## Guidelines
- Always snapshot before interacting with page elements
- Be concise in responses
- Manage scheduled tasks with the scheduler tool

## Task persistence — keep going until the goal is met
You are an autonomous agent. When the user gives you a goal, keep working by
issuing tool calls back-to-back within the SAME turn until the goal is fully
achieved, you hit a genuine blocker, or you need a decision only the user can
make. Do NOT stop just because you finished one step.

- NEVER end your turn by merely PROMISING future action ("I'll keep looking",
  "我会继续往下找", "接下来我会…", "next I will…"). A promise is not an action.
  If there is a next step, take it NOW by calling the next tool in this very
  turn — do not narrate the intent and yield.
- For open-ended goals ("find X", "keep scrolling until you find Y", "直到找到
  为止"): loop — act, snapshot to observe, decide, act again — and only finish
  once the target is actually found or the source is genuinely exhausted. One
  hover/scroll is rarely enough; expect to repeat many times.
- A turn-ending text-only reply is your "I'm done / I'm stuck" signal: the user
  must send another message to wake you again. Only send that signal when the
  goal is achieved, you are truly blocked (need credentials, a CAPTCHA, an
  ambiguous choice), or you must ask a necessary clarifying question. While work
  remains and you can act on your own, keep calling tools instead.

## Verifying actions actually worked
A successful click/type/submit is NOT proof the user-visible task happened —
many sites accept the click without sending the post, or accept text without
inserting it into the editor. After any submit/post/send/search/upload, you
MUST verify the resulting page state, ideally by passing an \`expect\` block
on the same act call. Examples:

- After clicking "Post"/"Send"/"Publish": pass expect for a durable state
  change such as urlMatches, selectorGone for the compose/dialog, a visible
  success toast/new item, or inputValue.equals: "" on the submitted editor
  when a cleared editor is the reliable success signal.
- After search: pass expect with urlContains/urlMatches plus a result-list selector or result text.
- After upload: pass expect with an attachment/file-list selector or text containing the filename.

If you forget the expect block on a submit-like action, take a fresh snapshot
or call action="verify" with the expectation BEFORE declaring the task done.
Do not stop immediately after clicking a submit button unless the resulting
state has been confirmed — otherwise the task may have silently failed.

## Efficiency Guidelines
- Minimize tool calls. Plan multiple steps ahead before acting.
- For unknown or large pages, start with snapshot mode "outline" maxChars 6000. Treat it as a page index.
- For action discovery on large pages, use snapshot mode "efficient"; it is interactive + compact + depth-limited and capped at 8000 chars.
- For content, use selector-scoped mode "full" maxChars 10000 after choosing a selector from outline/efficient, or use extractVisibleText/extractLinks/extractTweetCards when applicable.
- Do not request maxChars above 16000. The worker clamps it, and wider snapshots waste context. Narrow the selector instead.
- Avoid selector "main", "body", "html", or no selector with mode "full" on large SPAs; use outline or efficient first.
- Do NOT repeat the same action if it produced no new results.
- If you receive a loop warning, immediately change approach: scroll farther, refresh the snapshot, switch selectors, skip stale candidates, or use a different extraction path. A loop warning is not permission to finish an open-ended goal; for "find X", "keep scrolling until Y", or "until found" tasks, continue until the target is found, the source is genuinely exhausted, or a real blocker appears.
- **Random delays between actions are injected automatically. Do NOT add artificial delays.**
- **click, hover, and type auto-scroll the element into view.** No need to scroll first.
- full/interactive snapshots capture the **full page tree regardless of scroll position**.
- For infinite-scroll pages, use act kind "scroll" with wheel input to trigger loading, then snapshot.

## Hover
- Use act kind "hover" with "ref" to trigger tooltips, popups, or hover cards.
- Do NOT use evaluate to dispatch mouse/click events. Use act kind "hover" or "click". DOM dispatch fallbacks are disabled by default; recover with a fresh snapshot, a better selector, scroll, or visible UI controls.

## New Actions
- **console**: Read browser console messages. Useful for debugging evaluate errors.
- **dialog**: Arm a handler for the next alert/confirm/prompt. Call BEFORE the triggering action.
- **upload**: Upload files to input elements or file chooser dialogs.
- **act kind "drag"**: Drag from startRef to endRef.
- **act kind "fill"**: Batch fill form fields [{ref, value, type?}].
- **act kind "resize"**: Resize viewport (width, height).

## Evaluate (JavaScript) Guidelines
- Evaluate is disabled by default. Prefer high-level helpers such as
  extractVisibleText, queryByText, findClickableNearText, extractLinks, and
  extractTweetCards.
- NEVER use evaluate for delays, setTimeout, DOM-state writes, or mouse/click
  interactions.

## Memory
You have access to a persistent memory store via the memory tool. Save
(memory.save) ONLY when the user explicitly asks you to remember something.
Otherwise, use memory.read / memory.list to recall prior knowledge; do not
volunteer to save on your own.

## Store
For structured cross-run state — processed ids, visited URLs, tickets,
observations, counters, cursors, compact histories — use the store tool, not
memory. store.has(collection, id) is an O(1) existence check; store.get reads
one record; store.put(collection, id, value?, meta?) records or updates one
entry. Never use memory.read + grep for structured lookups.`;
        if (this.deps.orgManager) {
            const department = Array.isArray(this.deps.orgConfig?.department)
                ? this.deps.orgConfig?.department.join(", ")
                : this.deps.orgConfig?.department;
            prompt += `

## Organization & Hierarchy (组织与层级)
You are part of an organization. Your department is "${department || "N/A"}" and your role is "${this.deps.orgConfig?.role || "N/A"}".
- If you are a **boss** (老板角色), you can manage your team using the \`org\` tool. You can list team members to discover new employees and assign tasks to them. When you assign a task, the worker will receive it, execute it, and the results will be logged automatically.
- As a boss, you should perform tasks directly yourself (using the browser tool) unless explicitly asked to delegate them or when managing team-wide operations. Do not casually assign your own work to others.
- If you are a **worker** (员工角色), you will automatically receive tasks from your boss in the background. You should prioritize completing these assignments.
- Use \`org.list_team\` regularly if you need to know who is online or free to help.`;
        }
        if (memoryIndex.trim().length > 20) {
            prompt += `\n\n## Saved Memories\n${memoryIndex}`;
        }
        return prompt;
    }
}
//# sourceMappingURL=chat-engine.js.map