export const browserTool = {
    name: "browser",
    description: `Control the web browser.

Ref lifecycle (IMPORTANT):
- Refs like [e1],[e2]... come from snapshot and resolve to DOM nodes via the accessibility tree.
- Refs are invalidated by: the next snapshot call, navigation, dialog open/close, and any major DOM re-render (common for rich editors and SPAs).
- Rule of thumb: use the ref from the MOST RECENT snapshot. If you snapshotted, then acted, then snapshotted again, the first batch of refs is gone.
- For stable elements with a known testid/CSS hook (e.g. [data-testid="submit"], [data-testid="search-input"]), prefer \`selector\` over \`ref\`. Selectors re-resolve on every use.
- If you get "reference '...' not found", take a fresh snapshot or switch to a selector — do not retry with the same ref.

Top-level actions:
- navigate: go to a URL (url)
- snapshot: get page structure/content/action tree with selector hints (mode?, selector?, compact?, depth?, maxChars?, snapshotFormat?, refs?). Use mode="outline" to map unknown/large pages. Use mode="efficient" for actionable refs on large pages. Use selector-scoped mode="full" for content detail. maxChars is capped by the worker; do not increase it to inspect a large page. Narrow selector instead. Pass refs="role" with full/interactive/efficient snapshots to additionally get re-resolvable [role-ref="button|Post"] handles that survive rerenders.
- screenshot: capture page image (fullPage?, ref?, element?, type?, save?, saveAs?). Pass save:true (or saveAs:"name.png") to write the image into workspace/attachments/ so you can then deliver it via attachment.send.
- scroll: scroll the page or a scrollable element with real wheel input (selector/ref?, direction?, amount?, deltaX?, deltaY?).
- tabs: list open browser tabs
- open: open a new tab (url)
- focus: switch to tab (index)
- close: close tab (index?) or current tab
- console: read browser console messages (level?)
- dialog: arm handler for next browser dialog/alert (accept, promptText?, timeoutMs?)
- upload: upload file(s) to a page input or file chooser (paths, ref?, selector?, inputRef?, inputSelector?, timeoutMs?). Pass a snapshot ref (eN), a CSS selector (e.g. input[type="file"] or [data-testid="upload"]), or omit both to arm the next file-chooser dialog. CSS-shaped strings in the ref slot are auto-detected and routed to selector. Paths passed as bare filenames or "attachments/<name>" are auto-resolved against workspace/attachments/, so a file the user uploaded via Telegram can be forwarded to a webpage upload input by just referencing the name from the \`[user uploaded file: attachments/…]\` preamble. Absolute paths are honored as-is; an unknown relative path returns a "not found" error instead of silently succeeding.
- act: perform an interaction on the page (kind + params, or request object). Optionally pass \`expect\` (see below) to verify page state after the action.
- verify: run an \`expect\` block standalone, asserting the page is in a desired state without performing any other action. Returns "[verified]" or "[verification failed]".
- batch: run an array of browser actions in sequence, stopping on first failure (steps:[{action, params?}, ...], stopOnFailure?:true). Saves LLM round-trips when several steps depend on the same DOM state — e.g. focus search box → type query → wait for result cells.

expect verification (action="act" / "verify", field "expect"):
Pass an \`expect\` object to assert post-action page state. Without it, "click" returns success the moment Playwright's click resolves — even if the page didn't actually transition. With \`expect\`, the result includes "[verified]" or "[verification failed]" so the LLM doesn't move on assuming success.
REQUIRED for any click that submits, posts, sends, saves, publishes, or otherwise commits state — without \`expect\`, you cannot tell whether the click took effect, and you MUST NOT mark a task done or update memory based on the bare success message. Suggested signals depending on the flow: urlContains (post→permalink), selectorHidden (modal/compose closes), text (success toast appears), selectorVisible (new content rendered), inputValue (editor cleared).
Fields (all optional, all must pass; unknown field names FAIL the expect block, so spell carefully):
- text: substring of visible page text appears (string)
- textGone: substring disappears (string)
- selectorVisible: CSS selector resolves to a visible element (string). Alias: selector.
- selectorHidden: CSS selector is missing or hidden (string). Alias: selectorGone.
- urlContains: location.href contains substring (string)
- urlMatches: location.href matches regex (string)
- inputValue: { selector?|ref?, contains? | equals? } — verify input/textarea/contenteditable text
- timeoutMs: total budget (default 8000)

act interaction kinds:
- click: click element (ref or selector, doubleClick?, button?, modifiers?, dispatch?, fallbackDispatch?). Default dispatch="mouse" sends real CDP mouse events. dispatch="dom" and fallbackDispatch="dom" are disabled by default unless the operator explicitly enables browser.allowEvaluateFallbacks; prefer changing selector, taking a fresh snapshot, or using high-level actions instead of DOM click fallback. Do not use raw evaluate() to click.
- type: type text into element (ref or selector, text, clear?, submit?, method?, fallback?). Default sends real keystrokes; use method="paste" only for rich-text editors (Lexical/ProseMirror/Tiptap) where individual keystrokes don't trigger the editor's insertion logic. Plain input/textarea paste and alternate-method paste fallback are disabled by default unless browser.allowEvaluateFallbacks is explicitly enabled. With default clear=true, already-empty inputs avoid synthetic clear events; pass clear:false when appending or when you know the field is empty and want only key events. Result includes a "[url-not-visible: ...]" tag when body text landed but a URL was rewritten by the site into a link card / shortener and can't be verified verbatim — this is best-effort, NOT a reason to retype without the URL. Verifier errors are tooling issues, not content issues: do NOT bypass them by deleting URLs, shortening text, or rewriting the user's content.
- press: press keyboard key (key, delayMs?)
- hover: hover over element for tooltips/popups (ref or selector)
- scroll: scroll the page or a scrollable element with real wheel input (selector/ref?, direction?, amount?, deltaX?, deltaY?). Prefer this over evaluate(window.scrollBy).
- drag: drag between elements (startRef, endRef, startSelector?, endSelector?)
- select: select dropdown option(s) (ref or selector, values)
- fill: batch fill form fields (fields[])
- resize: resize browser viewport (width, height)
- wait: wait for condition. Prefer UI-state conditions over legacy selector/loadState ones — SPAs often never reach networkidle. Available: selectorVisible (selector + waits for visibility), selectorHidden, selectorEnabled, selectorDisabled, urlMatches (regex), domQuietMs (no DOM mutations for N ms), locatorStableMs (selector bounding box stable for N ms; pair with selector), textCountAtLeast ({selector, count}). Legacy: selector (just exists), text, textGone, url, loadState, fn, timeMs (sleep). Common: timeoutMs (default 10000).
- evaluate: run JavaScript (fn). Disabled by default unless browser.allowEvaluateFallbacks is explicitly enabled. For common patterns prefer the helpers below instead of writing raw JS.
- extractVisibleText: return bounded visible text from the page or a CSS region (selector?, maxChars?). Replaces ad-hoc innerText scraping.
- queryByText: find visible elements whose innerText matches \`text\` (text, fuzzy?, limit?). Returns selectors when present so you can chain into click/type.
- findClickableNearText: find clickable controls (button/link/role=button/menuitem) at or near elements containing \`text\` (text, maxAncestors?, limit?). Returns \`selector\` when available, otherwise \`ref\` may contain a re-resolvable role handle like \`menuitem|Quote\` for click/hover. Use when the visible text isn't itself the click target (e.g. an avatar next to a name).
- extractLinks: list visible anchor links from the page or a CSS region (selector?, limit?).
- extractTweetCards: site-specific social-post helper; use only when an installed or auto-loaded skill instructs you to use it (limit?).
- close: close current page`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: [
                    "navigate",
                    "snapshot",
                    "screenshot",
                    "scroll",
                    "tabs",
                    "open",
                    "focus",
                    "close",
                    "console",
                    "dialog",
                    "upload",
                    "act",
                    "verify",
                    "batch",
                ],
                description: "The browser action to perform",
            },
            // -- navigate / open --
            url: { type: "string", description: "URL for navigate or open" },
            // -- snapshot --
            mode: {
                type: "string",
                enum: ["outline", "full", "interactive", "efficient"],
                description: "Snapshot mode: 'outline' maps unknown/large pages, 'efficient' returns compact actionable refs for large pages, 'interactive' returns actionable elements, and 'full' returns bounded content detail. maxChars is capped by the worker; narrow selector instead of increasing it.",
            },
            selector: {
                type: "string",
                description: "CSS selector: scopes snapshot subtree, or alternative to ref for act interactions, or wait condition",
            },
            compact: {
                type: "boolean",
                description: "Strip whitespace from snapshot",
            },
            depth: {
                type: "number",
                description: "Max tree depth for snapshot",
            },
            maxChars: {
                type: "number",
                description: "Max chars for snapshot output",
            },
            snapshotFormat: {
                type: "string",
                enum: ["ai", "aria"],
                description: "snapshot only: 'ai' (default) compact LLM-friendly summary; 'aria' verbose accessibility tree.",
            },
            refs: {
                type: "string",
                enum: ["aria", "role"],
                description: "snapshot only: 'aria' (default) eN refs invalidated on each snapshot; 'role' adds re-resolvable role|name handles.",
            },
            // -- screenshot --
            fullPage: {
                type: "boolean",
                description: "Full page screenshot",
            },
            element: {
                type: "string",
                description: "CSS selector for element screenshot",
            },
            type: {
                type: "string",
                enum: ["png", "jpeg"],
                description: "Image type for screenshot (default: jpeg)",
            },
            save: {
                type: "boolean",
                description: "Screenshot only: write the captured image into workspace/attachments/ under an auto-generated filename. The returned text includes the filename for use with attachment.send.",
            },
            saveAs: {
                type: "string",
                description: "Screenshot only: explicit basename for the saved file (e.g. 'login-page.png'). Implies save=true. Collision-safe (appends timestamp if the name is already taken).",
            },
            // -- tab management --
            index: {
                type: "number",
                description: "Tab index for focus/close",
            },
            // -- console --
            level: {
                type: "string",
                description: "Console message level filter (error, warning, log, debug)",
            },
            // -- dialog --
            accept: {
                type: "boolean",
                description: "Accept or dismiss dialog",
            },
            promptText: {
                type: "string",
                description: "Text to enter in prompt dialog",
            },
            // -- upload --
            paths: {
                type: "array",
                items: { type: "string" },
                description: "File paths for upload",
            },
            inputRef: {
                type: "string",
                description: "Element ref for file input (upload). Alias of `ref`.",
            },
            inputSelector: {
                type: "string",
                description: "CSS selector for the file input element (upload). Alias of `selector`. Use when a stable selector is known (e.g. input[type=\"file\"], [data-testid=\"upload\"]).",
            },
            // -- shared --
            timeoutMs: {
                type: "number",
                description: "Timeout in ms (for wait, dialog, upload)",
            },
            // -- act (interaction) --
            kind: {
                type: "string",
                enum: [
                    "click",
                    "type",
                    "press",
                    "hover",
                    "scroll",
                    "drag",
                    "select",
                    "fill",
                    "resize",
                    "wait",
                    "evaluate",
                    "extractVisibleText",
                    "queryByText",
                    "findClickableNearText",
                    "extractLinks",
                    "extractTweetCards",
                    "close",
                ],
                description: "Interaction kind for act action",
            },
            ref: {
                type: "string",
                description: "Element reference from snapshot (e.g., 'e1') for act interactions, screenshot, or upload",
            },
            // -- act: nested request object (alternative to flattened params) --
            request: {
                type: "object",
                description: "Nested act request object {kind, ref?, ...}. Alternative to flattened params.",
            },
            // -- act / verify: post-action assertion --
            expect: {
                type: "object",
                description: "Verify page state after the action (or, with action='verify', without any preceding action). Fields: text, textGone, selector, selectorGone, urlContains, urlMatches, inputValue:{selector?|ref?, contains?|equals?}, timeoutMs. All set fields must pass.",
            },
            // -- batch --
            steps: {
                type: "array",
                items: {
                    type: "object",
                    properties: {
                        action: { type: "string" },
                        params: { type: "object" },
                    },
                },
                description: "batch only: ordered [{action, params?}] to run. Stops on first failure unless stopOnFailure=false.",
            },
            stopOnFailure: {
                type: "boolean",
                description: "batch only: stop on first failed step (default true). Set false to keep running and accumulate per-step results.",
            },
            // -- act:click --
            doubleClick: {
                type: "boolean",
                description: "Double-click instead of single click",
            },
            button: {
                type: "string",
                enum: ["left", "right", "middle"],
                description: "Mouse button for click",
            },
            modifiers: {
                type: "array",
                items: { type: "string" },
                description: "Modifier keys for click (Alt, Control, Meta, Shift)",
            },
            dispatch: {
                type: "string",
                enum: ["mouse", "dom"],
                description: "click only: dispatch method. Default 'mouse' sends real CDP mouse events. 'dom' calls guarded HTMLElement.click() and is disabled by default unless browser.allowEvaluateFallbacks=true.",
            },
            fallbackDispatch: {
                type: "string",
                enum: ["dom"],
                description: "click only: optional DOM fallback. Disabled by default unless browser.allowEvaluateFallbacks=true. Prefer a fresh snapshot, better selector, or high-level recovery.",
            },
            // -- act:type --
            text: {
                type: "string",
                description: "Text for type action or wait text condition",
            },
            submit: {
                type: "boolean",
                description: "Press Enter after entering the text",
            },
            method: {
                type: "string",
                enum: ["keyboard", "paste"],
                description: "Input method preference. 'keyboard' (default for plain inputs) sends real per-key keystrokes. 'paste' remains available for rich-text editors where per-key input gets dropped; plain input/textarea paste is disabled by default unless browser.allowEvaluateFallbacks=true.",
            },
            fallback: {
                type: "boolean",
                description: "type only: when false, do NOT retry with the alternate insertion method on verifier failure. Default true, but alternate paste into plain inputs is disabled unless browser.allowEvaluateFallbacks=true.",
            },
            clear: {
                type: "boolean",
                description: "type only: clear existing value before typing. Default true, but already-empty targets skip synthetic clear events. Use false for append mode or when typing into a known-empty field should produce only keyboard input.",
            },
            // -- act:press --
            key: {
                type: "string",
                description: "Key for press (e.g., 'Enter', 'Tab', 'Escape')",
            },
            delayMs: {
                type: "number",
                description: "Delay in ms before press",
            },
            // -- act:drag --
            startRef: {
                type: "string",
                description: "Start element ref for drag",
            },
            endRef: {
                type: "string",
                description: "End element ref for drag",
            },
            startSelector: {
                type: "string",
                description: "Start CSS selector for drag (alternative to startRef)",
            },
            endSelector: {
                type: "string",
                description: "End CSS selector for drag (alternative to endRef)",
            },
            // -- act:select --
            values: {
                type: "array",
                items: { type: "string" },
                description: "Values for select dropdown",
            },
            // -- act:fill --
            fields: {
                type: "array",
                items: {
                    type: "object",
                    properties: {
                        ref: { type: "string" },
                        value: { type: "string" },
                        type: { type: "string" },
                    },
                },
                description: 'Form fields for batch fill [{ref, value, type?}]. type: "checkbox"/"radio" uses setChecked',
            },
            // -- act:resize --
            width: { type: "number", description: "Viewport width for resize" },
            height: { type: "number", description: "Viewport height for resize" },
            // -- act:scroll --
            direction: {
                type: "string",
                enum: ["up", "down", "left", "right"],
                description: "scroll kind: direction for wheel scrolling. Default down.",
            },
            amount: {
                type: "number",
                description: "scroll kind: wheel distance in pixels when deltaX/deltaY are omitted. Default 500.",
            },
            deltaX: {
                type: "number",
                description: "scroll kind: explicit horizontal wheel delta.",
            },
            deltaY: {
                type: "number",
                description: "scroll kind: explicit vertical wheel delta.",
            },
            // -- act:wait --
            textGone: {
                type: "string",
                description: "Wait for text to disappear",
            },
            loadState: {
                type: "string",
                enum: ["load", "domcontentloaded", "networkidle"],
                description: "Wait for page load state. Avoid networkidle on SPAs.",
            },
            timeMs: {
                type: "number",
                description: "Simple delay in ms for wait (max 30000)",
            },
            // -- act:wait additions (P1-2) --
            selectorVisible: {
                type: "string",
                description: "wait kind: CSS selector resolves to a visible element. Preferred over `selector` for visibility-sensitive waits.",
            },
            selectorHidden: {
                type: "string",
                description: "wait kind: CSS selector becomes missing or hidden.",
            },
            selectorEnabled: {
                type: "string",
                description: "wait kind: CSS selector resolves to an enabled control.",
            },
            selectorDisabled: {
                type: "string",
                description: "wait kind: CSS selector resolves to a disabled control.",
            },
            urlMatches: {
                type: "string",
                description: "wait kind: location.href matches this regex string.",
            },
            domQuietMs: {
                type: "number",
                description: "wait kind: DOM has had no mutations for N ms (50–10000). Useful when the network keeps spinning.",
            },
            locatorStableMs: {
                type: "number",
                description: "wait kind: pair with `selector` — bounding box stable for N ms (50–10000). Useful before screenshot or click on animated UI.",
            },
            textCountAtLeast: {
                type: "object",
                description: "wait kind: at least N elements match a selector — {selector, count}. Useful for waiting on result lists.",
                properties: {
                    selector: { type: "string" },
                    count: { type: "number" },
                },
            },
            // -- act:evaluate + act:wait --
            fn: {
                type: "string",
                description: "JavaScript for evaluate, or custom JS function for wait condition. act:evaluate is disabled by default unless browser.allowEvaluateFallbacks=true.",
            },
            // -- DOM helpers (P1-4) --
            fuzzy: {
                type: "boolean",
                description: "queryByText only: substring + case-insensitive match instead of exact.",
            },
            limit: {
                type: "number",
                description: "DOM helpers: cap result count (queryByText/findClickableNearText/extractLinks/site-specific helpers).",
            },
            maxAncestors: {
                type: "number",
                description: "findClickableNearText only: max levels to walk up the DOM looking for a clickable ancestor.",
            },
        },
        required: ["action"],
    },
};
export const memoryTool = {
    name: "memory",
    description: `Freeform notes the user has asked to be remembered.

Actions:
- save: store/overwrite a note (key, title, content). ONLY when the user
  explicitly asks ("记住这个", "remember that ..."). memory.save is REJECTED
  during scheduled tasks — use the store tool for structured cross-run state.
- read: read a note by key.
- list: list all keys.

NOT for: structured histories, processed item ids, visited URLs, tickets,
observations, counters, cursors, or any cross-run lookup that can be keyed.
For those, use the store tool — store.has is O(1) and returns "yes"/"no",
whereas memory.read pulls the whole file and bloats every iteration.

When saving on user request: choose a stable, descriptive key (no dates, no
timestamps, no ".md" suffix; date-keyed or timestamped keys are rejected).
Re-save with the SAME key to overwrite. Each note body is capped at 8KB.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["save", "read", "list"],
                description: "Memory action",
            },
            key: {
                type: "string",
                description: "Stable memory key, no date/timestamp, no .md suffix (e.g. 'site-github', 'task1-rules')",
            },
            title: {
                type: "string",
                description: "Short title for index (<100 chars)",
            },
            content: {
                type: "string",
                description: "Memory content in markdown (8KB max)",
            },
        },
        required: ["action"],
    },
};
/**
 * Read-only variant of memoryTool surfaced to scheduled tasks. Scheduled
 * tasks must not write freeform memory; cross-run state belongs in `store`.
 * Surfacing the same memoryTool with `save` still in its action enum lets
 * the LLM attempt save calls that the task-runner dispatcher rejects —
 * burning a tool iteration per attempt. Hiding `save` from the schema is
 * the more honest contract.
 *
 * chat-engine continues to use the full `memoryTool` (which keeps `save`).
 * task-runner pulls this variant by passing `taskMode: true` to
 * getAllTools().
 */
export const memoryToolTaskMode = {
    name: "memory",
    description: `Read-only access to freeform notes the user has saved via chat.

Actions:
- read: read a note by key.
- list: list all keys.

You CANNOT save memories during a scheduled task — memory.save is not
exposed here. For structured cross-run state (processed ids, visited URLs,
tickets, observations, counters, cursors) use the store tool — store.has /
store.get / store.put. For freeform notes the user wants remembered, the
user will save them via chat.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["read", "list"],
                description: "Memory action",
            },
            key: {
                type: "string",
                description: "Stable memory key, no date/timestamp, no .md suffix (e.g. 'site-github', 'task1-rules')",
            },
        },
        required: ["action"],
    },
};
export const storeTool = {
    name: "store",
    description: `Persistent structured key-value store. Use whenever a task needs
structured cross-run state that can be addressed by stable ids.

Typical uses (not exhaustive):
  - processed ids you've already acted on
  - urls you've already visited and may need to recognize later
  - tickets / posts / files you've already handled
  - observations, counters, cursors, and compact structured histories
  - any cross-run KV state that doesn't fit a single freeform note

Collections are flat namespaces (lowercase, digits, '-' or '_', 1-64 chars,
e.g. "processed-items", "visited-urls", "price-observations"). Inside a
collection, ids are unique strings (<=200 chars). Optional value (string)
and meta (JSON object) are each capped at 4KB.

Actions:
  has(collection, id)                      -> "yes" / "no" (O(1) existence check)
  put(collection, id, value?, meta?)        -> "ok" or "updated"
  get(collection, id)                       -> full JSON record or "not found"
  list(collection, limit?, sinceMs?)        -> recent ids, newest first
                                              (default limit 20, max 100)
  count(collection)                         -> total entries as a number
  delete(collection, id)                    -> "ok"

Prefer store over memory.read + grep for any structured lookup. memory is
for freeform human-curated notes only, and memory.save is REJECTED in
scheduled tasks.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["has", "put", "get", "list", "count", "delete"],
                description: "Store action",
            },
            collection: {
                type: "string",
                description: "Collection name. Lowercase letters/digits/'-'/'_', 1-64 chars, starts with letter or digit (e.g. 'processed-items', 'visited-urls').",
            },
            id: {
                type: "string",
                description: "Entry id within the collection (<=200 chars). Required for has/put/get/delete.",
            },
            value: {
                type: "string",
                description: "Optional string payload for 'put' (<=4KB).",
            },
            meta: {
                type: "object",
                description: "Optional structured metadata object for 'put' (JSON, <=4KB serialized). Example: { url, author, ts }.",
                additionalProperties: true,
            },
            limit: {
                type: "number",
                description: "Cap on entries returned by 'list'. Default 20, max 100.",
            },
            sinceMs: {
                type: "number",
                description: "Optional unix ms timestamp; 'list' returns only entries updated at or after this.",
            },
        },
        required: ["action"],
    },
};
export const webFetchTool = {
    name: "web_fetch",
    description: `Fetch a public HTTP(S) resource directly and return bounded text.

Use this for read-only REST APIs, markdown skill URLs, JSON endpoints, and
instruction skills that mention WebFetch or Bash(curl). This tool only supports
GET, follows redirects, rejects URL credentials, and caps response size.`,
    parameters: {
        type: "object",
        properties: {
            url: {
                type: "string",
                description: "HTTP or HTTPS URL to fetch.",
            },
            method: {
                type: "string",
                enum: ["GET"],
                description: "Only GET is supported. Omit this unless needed.",
            },
            headers: {
                type: "object",
                additionalProperties: { type: "string" },
                description: "Optional string headers. Cookie, Host, Content-Length, and proxy headers are rejected.",
            },
            maxBytes: {
                type: "number",
                description: "Maximum response bytes to read. Default 65536, hard cap 524288.",
            },
            timeoutMs: {
                type: "number",
                description: "Request timeout in milliseconds. Default 15000, hard cap 30000.",
            },
        },
        required: ["url"],
    },
};
export const schedulerTool = {
    name: "scheduler",
    description: `Manage scheduled tasks. Actions:
- list: list all tasks
- add: create task (name, cron, prompt, optional timezone)
- update: modify an existing task (id, and any of name/cron/prompt/timezone/enabled/storagePolicyMode — only supplied fields change; pass timezone="" to clear)
- remove: delete task (id)
- toggle: enable/disable task (id, enabled)`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["list", "add", "update", "remove", "toggle"],
                description: "Scheduler action",
            },
            id: { type: "string", description: "Task ID" },
            name: { type: "string", description: "Task name" },
            cron: {
                type: "string",
                description: "Cron expression (e.g., '0 9 * * *')",
            },
            prompt: { type: "string", description: "Task prompt" },
            storagePolicyMode: {
                type: "string",
                enum: ["auto", "off"],
                description: "Optional. 'auto' lets dipclaw infer structured storage policies from the task prompt; 'off' disables automatic storage policy inference.",
            },
            timezone: {
                type: "string",
                description: "Optional IANA timezone for cron (e.g. 'UTC', 'Asia/Shanghai'). Falls back to scheduler default / system timezone.",
            },
            enabled: { type: "boolean", description: "Enable or disable" },
        },
        required: ["action"],
    },
};
export const attachmentTool = {
    name: "attachment",
    description: `Manage files and images under <workspace>/attachments/.

This directory stores files uploaded by the user via Telegram and files you
intend to send back. All paths MUST be relative to the attachments/ directory
(e.g., "1700000000000-ab12-report.md"). Absolute paths and ../ traversal are
rejected.

Actions:
- list: list files in the attachments directory (no args)
- read_text: read a text file's contents (path). Rejects binary files and
  anything larger than 256 KB. Use this to inspect user-uploaded text, JSON,
  markdown, CSV, or config files.
- send: send a file from the attachments directory back to the user via the
  current frontend (Telegram). Args: path; optional caption; optional asImage
  (default: auto-detected from extension — .png/.jpg/.jpeg/.webp/.gif → image,
  otherwise document). Use this to deliver screenshots, downloaded files, or
  generated artifacts the user asked for.
- install_skill: parse an uploaded markdown skill file and install it into the
  agent's skill library. Supports dipclaw JSON-step skills and instruction
  markdown skills. Args: path; optional skillName (overrides the name from
  frontmatter/filename); optional overwrite (default false). On name collision:
  fails with an error unless overwrite=true, in which case the existing skill is
  replaced and its usageCount/successRate stats are preserved.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["list", "read_text", "send", "install_skill"],
                description: "Attachment action",
            },
            path: {
                type: "string",
                description: "Filename relative to attachments/ directory. Required for read_text, send, install_skill.",
            },
            caption: {
                type: "string",
                description: "Optional caption text accompanying the file (send only).",
            },
            asImage: {
                type: "boolean",
                description: "Send as photo (sendPhoto) vs. document (sendDocument). If omitted, inferred from the file extension.",
            },
            skillName: {
                type: "string",
                description: "Override the skill name on install. Falls back to frontmatter `name:` then the filename stem.",
            },
            overwrite: {
                type: "boolean",
                description: "install_skill only: replace an existing skill with the same name (preserving its usage stats). Default false → collision returns an error.",
            },
        },
        required: ["action"],
    },
};
export const skillTool = {
    name: "skill",
    description: `Manage browser automation skills (markdown files in workspace/skills/, plus packaged read-only built-in skills).

Actions:
- list: list all skills with stats (no args)
- read: return capped markdown content of a skill, including the JSON steps block when it fits (name). Use this before save to edit an existing skill.
- save: create or OVERWRITE a skill (name, description, steps, optional notes). On overwrite, existing usageCount/successRate stats are preserved. Names are sanitized: characters outside [a-zA-Z0-9_-] become "_".
- install: fetch or store a markdown skill from a URL/content (url or content; optional name/skillName; optional overwrite). Supports both dipclaw JSON-step skills and instruction skills. When an instruction skill mentions WebFetch or curl, use web_fetch for the actual HTTP requests.
- delete: remove a skill file by name (name)

Steps schema: each step is {action, params, description?}. action/params mirror the browser tool (e.g. action="navigate" with params={url}, action="click" with params={ref:"css selector"}). Use {{variable_name}} inside params for runtime-substituted values.

Built-in skills are packaged with dipclaw and read-only. They are listed as
builtin:<name> and can be read that way. A user skill may use the same plain
name; plain read/load prefers the user skill, while builtin:<name> reads the
packaged skill.

Instruction skills are markdown procedures, not replay scripts. Read them with action="read", follow their instructions, and map WebFetch/curl examples to the web_fetch tool.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["list", "read", "save", "install", "delete"],
                description: "Skill action",
            },
            name: {
                type: "string",
                description: "Skill name (required for read, save, delete)",
            },
            description: {
                type: "string",
                description: "Skill description (save only)",
            },
            url: {
                type: "string",
                description: "Remote markdown skill URL to fetch and install (install only).",
            },
            content: {
                type: "string",
                description: "Raw markdown skill content to install when no URL is available (install only).",
            },
            steps: {
                type: "array",
                items: {
                    type: "object",
                    required: ["action", "params"],
                    properties: {
                        action: { type: "string" },
                        params: { type: "object" },
                        description: { type: "string" },
                    },
                },
                description: "Ordered steps (save only). Non-empty array of {action, params, description?}. action must be a non-empty string; params must be an object (use {} for parameter-less steps).",
            },
            notes: {
                type: "array",
                items: { type: "string" },
                description: "Optional free-form notes (save only)",
            },
            skillName: {
                type: "string",
                description: "Override installed skill name. Alias of name for install.",
            },
            overwrite: {
                type: "boolean",
                description: "Install only: replace an existing skill with the same name, preserving stats.",
            },
            headers: {
                type: "object",
                additionalProperties: { type: "string" },
                description: "Install from URL only: optional string headers for fetching the markdown.",
            },
            maxBytes: {
                type: "number",
                description: "Install from URL only: maximum bytes to fetch. Default 262144, hard cap 524288.",
            },
            timeoutMs: {
                type: "number",
                description: "Install from URL only: request timeout in milliseconds. Default 15000, hard cap 30000.",
            },
        },
        required: ["action"],
    },
};
export const skillToolTaskMode = {
    name: "skill",
    description: `Read-only access to installed browser automation skills during scheduled tasks.

Actions:
- list: list all skills with stats (no args)
- read: return capped markdown content of one installed skill (name)

Scheduled tasks cannot create, overwrite, install, or delete skills. If an
instruction skill mentions WebFetch or Bash(curl), map that request to the
web_fetch tool instead of trying to run shell commands.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["list", "read"],
                description: "Read-only skill action",
            },
            name: {
                type: "string",
                description: "Skill name (required for read)",
            },
        },
        required: ["action"],
    },
};
export const imageGenerationTool = {
    name: "image_generation",
    description: `Generate or edit images via an OpenAI-compatible image API.
Outputs are saved to <workspace>/attachments/ and, in a chat context,
auto-delivered to the user. In scheduled tasks (no chat frontend), auto-send
is a no-op and the filename is returned instead — it will appear in the task
completion notification.

Actions:

- generate: text-to-image via /v1/images/generations.
  Args:
  - prompt (required): text description of the image.
  - size (optional): e.g. "1024x1024", "512x512".
  - caption (optional): caption shown when delivered via Telegram.
  - autoSend (optional, default true): set false to only save to disk so
    the LLM can decide when/how to deliver via attachment.send.

- edit: image-to-image via /v1/images/edits (multipart). Use this to
  modify an existing image, do localized inpainting with a mask, or fuse
  several reference images.
  Args:
  - prompt (required): edit instruction.
  - image (required): a path under attachments/ (string) OR an array of
    such paths for multi-image fusion (max 3). Accepts .png/.jpg/.jpeg/.webp.
  - mask (optional): path under attachments/ to a mask image; transparent
    pixels mark the region to repaint.
  - size, caption, autoSend: same as generate.`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["generate", "edit"],
                description: "Image generation action",
            },
            prompt: {
                type: "string",
                description: "Text prompt (generate) or edit instruction (edit).",
            },
            image: {
                description: "Edit only. A path under attachments/ (string) or an array of paths for multi-image fusion.",
                oneOf: [
                    { type: "string" },
                    { type: "array", items: { type: "string" } },
                ],
            },
            mask: {
                type: "string",
                description: "Edit only. Optional path under attachments/ to a mask image; transparent pixels are repainted.",
            },
            size: {
                type: "string",
                description: "Image size, e.g. '1024x1024'. Optional.",
            },
            caption: {
                type: "string",
                description: "Caption shown when the image is delivered.",
            },
            autoSend: {
                type: "boolean",
                description: "Deliver immediately if a frontend is registered. Default true.",
            },
        },
        required: ["action", "prompt"],
    },
};
export const orgTool = {
    name: "org",
    description: `管理企业组织架构、部门以及任务分发。
Actions（可用操作）:
- list_team: 列出你所管辖部门内的所有员工及其当前状态（如在线、空闲/忙碌）。作为老板，在派发任务前应使用此功能了解有哪些可用的员工资源。
- assign: 指派任务。你可以指定单个员工的名字，或者使用 'all' 将任务群发给部门内的所有人。(注意：仅 boss 角色可用)
- list_departments: 列出组织注册中心里存在的所有部门名称。

派发任务时 (assign)，请在 instruction 中给出极其清晰详细的任务描述。员工收到并执行完毕后，会将最终结果回传给你。`,
    parameters: {
        type: "object",
        properties: {
            action: {
                type: "string",
                enum: ["list_team", "assign", "list_departments"],
                description: "Organization action (组织管理动作)",
            },
            target: {
                type: "string",
                description: "员工的精确名称，或者填写 'all' 表示派发给全员。(仅用于 assign 动作)",
            },
            instruction: {
                type: "string",
                description: "要发送给员工的具体任务指令。",
            },
        },
        required: ["action"],
    },
};
export function getAllTools(opts) {
    const tools = [
        browserTool,
        webFetchTool,
        opts?.taskMode ? memoryToolTaskMode : memoryTool,
        storeTool,
        schedulerTool,
        attachmentTool,
        opts?.taskMode ? skillToolTaskMode : skillTool,
    ];
    if (opts?.imageGeneration)
        tools.push(imageGenerationTool);
    if (opts?.org)
        tools.push(orgTool);
    return tools;
}
//# sourceMappingURL=tools.js.map