/* METADATA { name: "operit_editor" display_name: { zh: "Operit平台编辑器" en: "Operit Platform Editor" } description: { zh: '''Operit 平台配置直改工具包:提供一组可直接读取与修改 Operit 平台设置的工具,覆盖 MCP、Skill、Sandbox Package、功能模型绑定、模型参数、上下文总结与 TTS/STT 语音服务配置。''' en: '''Direct Operit platform configuration toolkit: a collection of tools for reading and directly modifying Operit platform settings, covering MCP, Skill, Sandbox Package, function-model bindings, model parameters, context-summary settings, and TTS/STT speech-service configuration.''' } enabledByDefault: true "category": "Chat", tools: [ { name: "operit_editor" description: { zh: '''配置排查手册。 【触发条件】 - 用户提到 MCP/Skill 安装失败、无法启动、工具不出现、导入失败、重名冲突、配置文件怎么改 - 用户提到沙盒包(Package)开关、内置包列表、导入删除路径、包启用状态异常 - 用户让你排查 Operit 的插件配置路径、部署目录、开关状态、环境变量 - 用户提到功能模型绑定、模型配置新增/删除/修改、模型连接测试 - 用户提到 TTS/STT 语音服务不会配置、参数太多不会填、语音播报/语音识别不可用 - 问题核心是“配置和部署链路”,而不是普通问答 【MCP:安装与排查】 1) 配置目录:/sdcard/Download/Operit/mcp_plugins/ - 主配置:mcp_config.json - 运行状态缓存:server_status.json(非实时快照,仅用于状态记录与工具缓存,不作为排查判定依据) 2) 两侧路径要严格区分: - Android 侧是源目录(用户导入/存放目录),不是最终运行目录 - Linux 侧是最终运行目录:~/mcp_plugins/ - MCP 真正启动时,执行目录与命令都以 Linux 侧为准 3) 本地部署实际行为(代码逻辑): - 创建目标目录 - 将 Android 侧插件目录复制到 Linux 侧目录 - 执行自动分析出的安装/构建命令(会跳过启动命令) 3.1) 本地插件识别规则: - 这样识别是为兼容存储位置外层套目录的情况(如 `/-main/...`);Android 侧目录内至少要命中一个标志文件:`README.md`、`package.json`、`mcp.config.json`、`main.js`、`main.py`、`index.js`、`index.py`,否则可能被判定为未安装,进不了启动列表。 4) 命令型插件判定: - 对于 command 为 npx/uvx/uv 的命令型插件,系统按“已部署”处理,仅做最小目录准备。 - 配置 Node 类命令型 MCP 时,mcp_config.json 里仍应按上游常见写法填写 `command: "npx"`;不要自行改写成 `pnpm` 或 `npm`。 - 软件内部启动这类 `npx` MCP 时,会自动把 `npx` 改写为 `pnpm dlx` 执行,并去掉 `-y`/`--yes` 一类确认参数。 - 因此这类 MCP 的实际运行依赖是 `pnpm`;Linux 终端里必须装有 `pnpm`,不能只装 `npm`。 - 不要把 command 改成 `npm`;当前已知这样可能触发 `double free` 报错。现阶段约定是:配置时按 `npx` 填,运行时由软件内部转成 `pnpm dlx`。 5) 系统启动行为(必须理解): - 本地插件:系统会读取 mcp_config.json 中该插件的 command/args/env,使用 cwd=~/mcp_plugins/ 启动进程;可用性以“工具可调用/服务可响应”为准 - 远程插件:系统按 endpoint + connectionType(可选 bearerToken/headers)发起连接并校验连通 6) command 的实际执行位置(必须按此理解): - 本地插件启动时,系统在 Linux 终端环境中启动进程;执行工作目录固定是 ~/mcp_plugins/ - mcp_config.json 里填写的 command 就是在这个 Linux 工作目录上下文中被执行,不会在 Android 源目录执行 - args 里的相对路径,统一按该 cwd 解析 6.1) node 命令示例(按插件ID): - 例:pluginId=owner/my-plugin,则 shortName=my-plugin,cwd=~/mcp_plugins/my-plugin - 如果入口文件在 ~/mcp_plugins/my-plugin/dist/index.js,则写: "command": "node" "args": ["dist/index.js"] - 这里 args 必须按 cwd 写相对路径,不要写 /sdcard/... 的 Android 路径 7) mcp_config.json 字段规范: - 顶层必须有 mcpServers(对象) - mcpServers 的 key 是 serverId(通常与插件ID一致,避免随意改名) - pluginId(即 mcpServers 的 key)只允许 a-zA-Z_ 和空格 - 每个 server 常用字段: - command(必填,启动命令) - args(可选,字符串数组) - env(可选,键值对) - autoApprove(可选,数组) - disabled(可选,true=禁用) - MCP 的环境变量必须写在 mcpServers..env;`read_environment_variable` / `write_environment_variable` 不会读写这里。 8) mcp_config.json 路径写法规则: - 不要把 Android 绝对路径写进本地插件启动参数(例如 /sdcard/...) - 本地插件命令应按 Linux 运行目录编写,优先相对路径(因为 cwd 已固定到 ~/mcp_plugins/) - 如果必须写绝对路径,也应是 Linux 侧路径,不应写 Android 侧路径 9) 启用开关: - 本地插件使用 mcpServers..disabled - 远程插件使用 pluginMetadata..disabled 10) MCP 排查顺序(按顺序执行): - 检查开关是否启用 - 检查本地部署目录是否存在且非空 - 检查 mcp_config.json 的 command/args/env 字段是否完整 - 检查 args 是否误写 Android 路径 - 检查 env 中所需 key/token - 不要把 server_status.json 的 active 当作唯一依据;优先看工具是否可拉取、可调用 - 检查终端依赖(node/pnpm/python/uv)与 MCP 服务状态;其中 Node 类 `npx` MCP 实际依赖 `pnpm`,若缺少 `pnpm` 将无法启动 【Skill:安装与排查】 1) 目录:/sdcard/Download/Operit/skills/ 2) 识别规则:每个 Skill 必须是一个文件夹,且包含 SKILL.md(skill.md 也可)。 3) 添加方式(按这个做): - 先从可信来源下载 Skill(zip 或仓库源码均可) - 把下载内容解压后,直接放到 /sdcard/Download/Operit/skills/ - 最终目录结构必须是 /sdcard/Download/Operit/skills//,且该目录内有 SKILL.md 4) 元数据解析: - 优先读取 frontmatter 中的 name/description - 若缺失,回退读取文件前40行里的 name:/description: 5) AI 可见性: - 列表开关关闭后,Skill 仍在本地,但 AI 不会使用 6) Skill 排查顺序: - 先确认路径和 SKILL.md - 再确认是否重名冲突 - 再确认开关是否开启 - 最后检查 SKILL.md 内容是否完整(步骤/约束/输出) 【Sandbox Package:安装与排查】 1) 沙盒包目录(外部): - Android/data/com.ai.assistance.operit/files/packages 2) 内置包: - 内置包来自应用内置资源,不在上述外部目录;删除内置包文件不是常规操作,通常只做开关管理。 3) 导入与删除: - 导入:把 `.js` 或 `.toolpkg` 放入/导入到外部 packages 目录。 - 删除:外部包可直接按文件路径删除;删除前先确认是否仍被依赖。 4) 开关管理(优先使用工具): - 先调用 list_sandbox_packages 获取“内置+外部”包列表与当前 enabled 状态。 - 再调用 set_sandbox_package_enabled(package_name, enabled) 执行启停。 5) 制作包文档: - https://cdn.jsdelivr.net/gh/AAswordman/Operit@main/docs/SCRIPT_DEV_SKILL.md - 该地址可直接通过 HTTP GET 请求访问,用于拉取原始 Markdown 文档内容。 6) 软件内调试烧录: - 普通 `.js` 沙盒包优先用 `debug_install_js_package`。 - `ToolPkg` 优先用 `debug_install_toolpkg`;它会处理目录/manifest/.toolpkg 的打包或安装,并触发 ToolPkg 的刷新链路。 【Package 兼容模型(重要)】 - AI 看到的 package 是统一抽象,底层可能来自 MCP、Skill、Sandbox Package 任意一种。 - 可用包列表中的条目,不保证同类型;可能是三种类型混合出现。 - `use_package` 是三兼容入口:对 MCP/Skill/Sandbox Package 都可统一调用。 - `ping_mcp` 工具是 `use_package` 的直通封装,用于快速探测指定包是否可被加载。 【市场 Agent API(HTTP,只读)】 - 基础前缀:`https://api.operit.app/market-stats` - 搜索:`/agent/search?q=<关键词>&type=mcp|skill|package|script&limit=10` - 详情:`/agent/items//` - 安装计划:`/agent/items///install-plan` - `package` / `script` 的 install_plan 通常会返回 `download_url`、`tracked_download_url`、`sha256`、`runtime_package_id`。 - `skill` 的 install_plan 通常会返回 `repository_url`。 - `mcp` 的 install_plan 可能返回 `config`(可直接作为 installConfig 参考)或 `repository_url`。 - 当前软件未对 JS 暴露统一的一键安装市场接口;需要安装时,按条目类型自行下载、解压、导入,必要时配合 `debug_install_js_package` / `debug_install_toolpkg` / `use_package`。 【功能模型与模型配置】 1) 模型配置(Model Config): - 每个模型配置是一套完整连接参数(provider / endpoint / api key / model_name / custom_headers / 各能力开关)。 - 可以新增、删除、修改;删除默认配置 `default` 是禁止的。 2) 功能模型(Function Model): - 每个功能类型(如 CHAT/SUMMARY/TRANSLATION 等)会绑定到一个模型配置。 - 当一个配置里 `model_name` 写了多个模型(逗号分隔),还要指定 `model_index` 选择第几个。 3) 关键工具(优先使用): - `list_model_configs`:查看全部模型配置 + 当前功能绑定。 - `create_model_config`:新增模型配置(可带初始 provider/endpoint/key/model_name/custom_headers)。 - `update_model_config`:修改已有配置(按 config_id)。 - `delete_model_config`:删除配置(默认配置不可删)。 - `list_function_model_configs`:仅列出功能 -> 配置绑定关系(轻量)。 - `get_function_model_config`:查看某个功能当前绑定的单个配置详情。 - `set_function_model_config`:为功能指定配置与模型索引。 - `test_model_config_connection`:按设置页同等逻辑测试配置连通与多模态能力。 4) 配置修改后: - 若该配置被某些功能使用,系统会刷新对应功能服务;绑定变更也会刷新目标功能服务。 5) 用户抱怨“输出总被截断”时: - 可先用 `get_function_model_config` 查看对应功能绑定配置里的 `max_tokens` 参数。 - 对 DeepSeek 来说,默认常见是 4096;可将 `max_tokens_enabled` 打开并把 `max_tokens` 设到 8192 再测试。 - 对其他模型,先联网确认该模型可支持的输出上限后再设置。 【上下文总结模块(Context Summary)】 1) 用途: - 控制会话上下文窗口与“何时触发总结”,用于降低上下文膨胀导致的丢信息、跑偏或响应不稳定。 - 只有当前对话功能模型(CHAT)所绑定配置里的上下文总结参数,会在实际对话中生效。 2) 核心机制说明: - 软件内有一个 max 开关,即 `enable_max_context_mode`。 - 开启时使用 `max_context_length`,关闭时使用 `context_length`,目标是为用户节约 token,并按场景动态决定使用长度。 - `enable_summary` 控制是否自动总结;当上下文超过 `summary_token_threshold * 当前可用上下文长度` 时,会触发总结。 3) 建议排查顺序: - 先用 `get_context_summary_config` 看当前功能绑定配置的上下文总结参数。 - 再用 `set_context_summary_config` 设置上下文总结参数(可传入参数覆盖默认值)。 - 若仍异常,再结合具体模型能力与业务负载做细调。 【TTS/STT 语音服务配置】 1) 配置入口: - 在软件设置里的 Speech Services(语音服务)页面配置。 - 该页面会自动保存;改完后语音服务实例会自动重建。 2) TTS(文本转语音)可选引擎: - `SIMPLE_TTS`:系统 TTS,基本无需填网络参数。 - `HTTP_TTS`:需重点填写 `url_template`、`headers`、`http_method`、`content_type`、`request_body`;若服务先返回 JSON / 字段 / 下载链接,再额外填写 `response_pipeline`。 - `OPENAI_WS_TTS`:需填写 `url_template`、`api_key`、`model_name`、`voice_id`。其中 `url_template` 应为 Realtime WebSocket 地址(例如 `wss://api.openai.com/v1/realtime`)。 - `SILICONFLOW_TTS`:需填写 `api_key`、`model_name`、`voice_id`。 - `MINIMAX_TTS`:需填写 `api_key`,可选填写 `url_template`、`model_name`、`voice_id`。默认接口为 `https://api.minimaxi.com/v1/t2a_v2`,内部固定按 `data.audio -> http_get` 解析音频。 - `MIMO_TTS`:需填写 `api_key`,可选填写 `url_template`、`model_name`、`voice_id`。预置音色使用 `mimo-v2.5-tts` 和短 `voice_id`;声音克隆使用 `mimo-v2.5-tts-voiceclone`,`voice_id` 填完整 `data:audio/...;base64,...` 音频样本。默认接口为 `https://api.xiaomimimo.com/v1/chat/completions`,内部固定按 `choices[0].message.audio.data -> base64_decode` 解析音频。 - `DOUBAO_TTS`:豆包 TTS,需填写 `url_template`、`api_key`(Token)、`model_name`(App ID)、`voice_id`(voice_type)。默认接口为 `https://openspeech.bytedance.com/api/v1/tts`,继承 HTTP TTS 队列和响应管线,内部固定按 `data -> base64_decode` 解析音频。 - `OPENAI_TTS`:需填写 `url_template`、`api_key`、`model_name`、`voice_id`。 - `VITS_TTS`:本地 VITS/Piper TTS。`tts_vits_package_path` 填本地模型包 `.zip` 或已解压目录,`tts_vits_speaker_id` 可选填数字 speaker id,`tts_vits_options` 可选填写 `sample_rate`、`threads`、`noise_scale`、`length_scale`、`noise_w`、`frontend`、`text_mode`、`speaker_count`、输入名和 blank/bos/eos token 等本地参数。 3) STT(语音转文本)可选引擎: - `SHERPA_NCNN`:本地识别,通常无需 API Key。 - `OPENAI_STT`:需填写 `endpoint_url`、`api_key`、`model_name`。 - `DEEPGRAM_STT`:需填写 `endpoint_url`、`api_key`、`model_name`。 4) 最常见填错点(优先检查): - `HTTP_TTS` 的 `headers` 不是合法 JSON(必须是对象)。 - `HTTP_TTS` 的模板没放 `{text}` 占位符(GET 通常在 URL,POST 通常在 body)。 - `HTTP_TTS` 的 `response_pipeline` 不是合法 JSON 数组,或步骤名 / `path` 填错。 - `OPENAI_WS_TTS` 把 HTTP 地址填成了 WebSocket 地址,或把 WebSocket 地址误填成 HTTP 地址。 - `VITS_TTS` 的 `tts_vits_package_path` 不是本地 `.zip` 模型包或已解压目录,或文件不存在。 - `VITS_TTS` 模型包里没有可识别的 `.onnx` / config JSON / lexicon,或配置里缺少 `sample_rate` / token 映射。 - `VITS_TTS` 的 `tts_vits_options` 不是合法 JSON(必须是对象),或把本地参数名 / 数值类型写错。 - TTS/STT 的 endpoint 路径写错(比如把 chat/completions 写成 audio 接口)。 - `model_name` 填了不存在的模型或与接口不匹配。 - 改完配置后没有重新测试语音播报或语音识别。 5) HTTP_TTS 占位符说明(按代码逻辑): - 必填占位符:`{text}`。 - 当 `http_method=GET` 时,`{text}` 必须在 `url_template` 里。 - 当 `http_method=POST` 时,`{text}` 必须在 `request_body` 里。 - 可选占位符:`{rate}`、`{pitch}`、`{voice}`。 - 当前对外可稳定使用的占位符:`{text}`、`{rate}`、`{pitch}`、`{voice}`、`{apiKey}`、`{model}`、`{locale}`、`{uuid}`。 6) HTTP_TTS 响应处理说明(已发布版本兼容): - `response_pipeline` 留空或传 `[]`:保持旧行为,直接把首个响应体当音频。 - 当服务端先返回 JSON、字段或下载链接时,再填写 `response_pipeline`,按步骤解析。 - 当前可用步骤:`parse_json`、`pick`、`parse_json_string`、`http_get`、`http_request_from_object`、`base64_decode`。 - 常见 JSON 下载链接场景: - `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"audio_uri"},{"type":"http_get"}]` - 若字段里还是 JSON 字符串: - `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"data.payload"},{"type":"parse_json_string"},{"type":"pick","path":"audio.url"},{"type":"http_get"}]` 7) 可直接参考的最小模板: - HTTP TTS(GET): - `url_template`: `https://example.com/tts?text={text}` - `headers`: `{}` - `http_method`: `GET` - `content_type`: `application/json` - HTTP TTS(POST): - `url_template`: `https://example.com/tts` - `headers`: `{"Authorization":"Bearer "}` - `http_method`: `POST` - `content_type`: `application/json` - `request_body`: `{"text":"{text}"}` - OpenAI STT(默认常见): - `endpoint_url`: `https://api.openai.com/v1/audio/transcriptions` - `model_name`: `whisper-1` 8) 排查顺序(建议): - 先确认选中的引擎类型是否正确(TTS 与 STT 分开看)。 - 再检查 endpoint / key / model 三件套是否完整。 - 再检查 HTTP 模板字段(headers JSON、method、body、占位符、response_pipeline)。 - 至少做一次真实 TTS 播报测试;若还要排查 STT,再另外走语音识别链路。 9) 对应工具(可直接用): - `get_speech_services_config`:获取当前 TTS/STT 配置快照(含引擎类型与关键字段)。 - `set_speech_services_config`:按字段修改 TTS/STT 配置(支持只改部分字段)。 - `test_tts_playback`:按当前 TTS 配置播放一次测试文本(支持临时覆盖语速/音调)。 【多模态输入规则】 1) 能力开关含义: - 模型配置里的“支持 Tool Call / 识图 / 音频 / 视频”等开关,只是软件侧能力标识,不等于模型真实能力。 - 实际配置时,必须依据模型真实支持情况来开关,不能乱开。 2) 软件识图主链路: - 当“对话功能模型”的模型配置开启识图且模型真实支持时:聊天附图会直接发给该模型识别。 - 当对话模型不支持识图时:软件会尝试走 OCR,或走“识图功能模型”进行识图中转。 3) 用户有识图需求时的可行条件: - 条件 A:对话模型支持识图。 - 条件 B:对话模型不支持识图,但识图功能模型支持识图。 - 若识图功能模型也不支持识图:最终回退到 OCR。 【绘图输出说明】 - 绘图通过工具包实现。 - 软件内置了一些绘图包,可调用 list_sandbox_packages 查看。 - 通常只需启用其中一个可用绘图包即可,无需全部开启。 【执行原则】 - 严格按用户明确指示执行,不自行定义“问题”或追加未被要求的目标。 - 任何会改动用户配置的操作(开关、导入/删除、写环境变量、模型配置增删改、重启)都必须先得到用户明确确认。 - 用户没有明确要求执行某个工具时,不主动调用写入类工具。 - 回答优先基于本手册的路径与规则,避免泛化推断。''' en: '''Configuration troubleshooting guide. [Trigger conditions] - The user mentions MCP/Skill install failure, startup failure, tools not appearing, import failure, duplicate name conflicts, or config editing - The user mentions sandbox package toggles, built-in package listing, import/delete paths, or package enable-state issues - The user asks to troubleshoot plugin config paths, deploy directories, enable switches, or environment variables - The user mentions function model bindings, adding/deleting/updating model configs, or testing model connectivity - The user says TTS/STT setup is confusing, too many fields to fill, or speech playback/recognition is not working - The core issue is configuration/deployment flow rather than normal Q&A [MCP: install and troubleshooting] 1) Config directory: /sdcard/Download/Operit/mcp_plugins/ - Main config: mcp_config.json - Runtime status cache: server_status.json (non-realtime snapshot for status/tool cache only; not a troubleshooting source of truth) 2) Keep Android-side and Linux-side paths strictly separated: - Android side is the source/import location, not the final runtime location - Linux runtime directory is: ~/mcp_plugins/ - Actual MCP startup always runs from the Linux side 3) Real deployment behavior (from implementation): - Create target directory - Copy plugin files from Android side to Linux side - Execute auto-generated install/build commands (startup commands are skipped) 3.1) Local plugin recognition rule: - This exists to handle nested storage layouts (for example `/-main/...`): the Android-side plugin directory must contain at least one marker file such as `README.md`, `package.json`, `mcp.config.json`, `main.js`, `main.py`, `index.js`, or `index.py`; otherwise it may be treated as not installed and never enter the startup list. 4) Command-based plugin handling: - For command-based plugins using npx/uvx/uv, the system treats them as deployed and only performs minimal directory preparation. - For Node-style command MCPs, keep the config in mcp_config.json aligned with upstream examples and still write `command: "npx"`; do not rewrite it to `pnpm` or `npm` yourself. - When the app starts this kind of `npx` MCP, it internally rewrites `npx` to `pnpm dlx` and strips confirmation flags such as `-y` / `--yes`. - That means the real runtime dependency for this kind of MCP is `pnpm`; `pnpm` must exist in the Linux terminal, and having only `npm` is not enough. - Do not change the command to `npm`; this is known to trigger `double free` errors. The current convention is: configure it as `npx`, and let the app run it internally as `pnpm dlx`. 5) System startup behavior (critical): - Local plugins: the app reads command/args/env from mcp_config.json and starts the process with cwd=~/mcp_plugins/; availability should be judged by real tool call/response - Remote plugins: the app connects using endpoint + connectionType (optional bearerToken/headers) and verifies connectivity 6) Actual execution location of command (must be understood this way): - For local plugins, the process starts in the Linux terminal environment with fixed working directory: ~/mcp_plugins/ - The command from mcp_config.json is executed in that Linux working-directory context, not in the Android source directory - Any relative paths in args are resolved against that cwd 6.1) node command example (by plugin ID): - Example: pluginId=owner/my-plugin, so shortName=my-plugin and cwd=~/mcp_plugins/my-plugin - If the entry file is ~/mcp_plugins/my-plugin/dist/index.js, write: "command": "node" "args": ["dist/index.js"] - args must be relative to cwd; do not use Android paths such as /sdcard/... 7) mcp_config.json field rules: - Top level must contain mcpServers (object) - Each key in mcpServers is a serverId (normally keep it aligned with plugin ID) - pluginId (the mcpServers key) only allows letters a-zA-Z, underscore (_), and spaces - Common server fields: - command (required, startup command) - args (optional, string array) - env (optional, key-value map) - autoApprove (optional, array) - disabled (optional, true means disabled) - MCP environment variables must be configured in mcpServers..env; `read_environment_variable` / `write_environment_variable` do not read or write these MCP env entries. 8) Path-writing rules in mcp_config.json: - Do not put Android absolute paths (for example /sdcard/...) in local plugin startup args - Write local plugin command/args for Linux runtime; prefer relative paths because cwd is fixed to ~/mcp_plugins/ - If an absolute path is required, it must be a Linux path, not an Android path 9) Enable switch: - Local plugin uses mcpServers..disabled - Remote plugin uses pluginMetadata..disabled 10) MCP troubleshooting order: - Check enable switch - Check local deployed directory exists and is non-empty - Check whether command/args/env in mcp_config.json are complete - Check whether args incorrectly use Android paths - Check required key/token in env - Do not treat `active` in server_status.json as the single source of truth; prioritize whether tools can be fetched/called successfully - Check terminal dependencies (node/pnpm/python/uv) and MCP service status; Node-style `npx` MCPs actually depend on `pnpm`, so missing `pnpm` will prevent startup [Skill: install and troubleshooting] 1) Directory: /sdcard/Download/Operit/skills/ 2) Recognition rule: each Skill must be a folder containing SKILL.md (skill.md is also accepted). 3) How to add a skill (use this workflow): - Download the skill from a trusted source (zip or repository source code). - Extract it, then place the folder directly under /sdcard/Download/Operit/skills/ - Final structure must be /sdcard/Download/Operit/skills// and that folder must contain SKILL.md 4) Metadata parsing: - Prefer frontmatter name/description - Fallback to name:/description: in the first 40 lines 5) AI visibility: - If the list switch is off, the Skill remains local but is hidden from AI usage 6) Skill troubleshooting order: - Verify path and SKILL.md - Check duplicate-name conflict - Check enable switch - Check whether SKILL.md instructions are complete (steps/constraints/outputs) [Sandbox Package: install and troubleshooting] 1) External sandbox packages directory: - Android/data/com.ai.assistance.operit/files/packages 2) Built-in packages: - Built-in packages come from app bundled assets, not from the external directory above; usually manage via enable/disable instead of file deletion. 3) Import and delete: - Import: place/import `.js` or `.toolpkg` into the external packages directory. - Delete: for external packages, delete by file path after confirming dependency impact. 4) Toggle management (prefer tools): - Call list_sandbox_packages first to get built-in + external package list and current enabled state. - Then call set_sandbox_package_enabled(package_name, enabled) to apply changes. 5) Package authoring guide: - https://cdn.jsdelivr.net/gh/AAswordman/Operit@main/docs/SCRIPT_DEV_SKILL.md - This URL can be accessed directly with an HTTP GET request to fetch the raw Markdown document. 6) In-app debug install: - For plain `.js` sandbox packages, prefer `debug_install_js_package`. - For `ToolPkg`, prefer `debug_install_toolpkg`; it handles packaging/install flow for folder/manifest/.toolpkg sources and triggers the ToolPkg refresh path. [Package compatibility model (important)] - The package list seen by AI is a unified abstraction, and each item may come from MCP, Skill, or Sandbox Package. - Available package entries are not guaranteed to be a single type; mixed types are expected. - `use_package` is tri-compatible and can be called uniformly for MCP/Skill/Sandbox Package. - `ping_mcp` is a thin wrapper over `use_package` for quick package availability probing. [Market agent API (HTTP, read-only)] - Base prefix: `https://api.operit.app/market-stats` - Search: `/agent/search?q=&type=mcp|skill|package|script&limit=10` - Detail: `/agent/items//` - Install plan: `/agent/items///install-plan` - `package` / `script` install_plan usually returns `download_url`, `tracked_download_url`, `sha256`, and `runtime_package_id`. - `skill` install_plan usually returns `repository_url`. - `mcp` install_plan may return `config` (usable as installConfig reference) or `repository_url`. - The app does not currently expose a unified one-click market install API to JS; when installation is needed, download/extract/import by item type and use `debug_install_js_package`, `debug_install_toolpkg`, or `use_package` when appropriate. [Function model and model config] 1) Model config: - Each model config is one full connection profile (provider / endpoint / api key / model_name / custom_headers / capability switches). - Configs can be created, updated, and deleted; deleting the default `default` config is forbidden. 2) Function model binding: - Each function type (for example CHAT/SUMMARY/TRANSLATION) is bound to one model config. - If `model_name` contains multiple models (comma-separated), `model_index` selects which one to use. 3) Key tools (prefer these): - `list_model_configs`: list all model configs and current function bindings. - `create_model_config`: create a new model config (with optional initial provider/endpoint/key/model_name/custom_headers). - `update_model_config`: update an existing config by config_id. - `delete_model_config`: delete a config (default cannot be deleted). - `list_function_model_configs`: list function -> config bindings only (lightweight). - `get_function_model_config`: get one bound config detail by function type. - `set_function_model_config`: assign config and model index for a function. - `test_model_config_connection`: run settings-UI-equivalent connectivity/capability checks for a config. 4) After config changes: - If a config is used by functions, corresponding function services are refreshed; binding updates also refresh target function service. 5) When users complain about truncated outputs: - Use `get_function_model_config` to inspect `max_tokens` in the bound config for that function. - For DeepSeek, a common default is 4096; enable `max_tokens_enabled` and try setting `max_tokens` to 8192. - For other models, verify the supported output-token limit online before changing values. [Context summary module] 1) Purpose: - Controls context window behavior and summary-trigger conditions to reduce context bloat, drift, and unstable long-chat responses. - Only context-summary settings on the config currently bound to CHAT are effective in real conversation. 2) Core mechanism: - There is a max switch in the app: `enable_max_context_mode`. - When enabled, it uses `max_context_length`; when disabled, it uses `context_length`, so context length can be chosen dynamically to save tokens. - `enable_summary` controls automatic summarization. When context exceeds `summary_token_threshold * current available context length`, summarization is triggered. 3) Recommended troubleshooting flow: - Use `get_context_summary_config` to inspect current context-summary fields for a function. - Use `set_context_summary_config` to set context-summary fields (you can override default values via params). - If issues remain, fine-tune based on real model capability and workload. [TTS/STT speech-service configuration] 1) Where to configure: - Configure it on the Speech Services page in app settings. - The page auto-saves; after changes, speech-service instances are rebuilt automatically. 2) TTS (text-to-speech) engines: - `SIMPLE_TTS`: system TTS, usually no network fields required. - `HTTP_TTS`: mainly fill `url_template`, `headers`, `http_method`, `content_type`, `request_body`; if the service first returns JSON / fields / a download link, also fill `response_pipeline`. - `OPENAI_WS_TTS`: fill `url_template`, `api_key`, `model_name`, `voice_id`. `url_template` should be a Realtime WebSocket endpoint such as `wss://api.openai.com/v1/realtime`. - `SILICONFLOW_TTS`: fill `api_key`, `model_name`, `voice_id`. - `MINIMAX_TTS`: fill `api_key`; optionally set `url_template`, `model_name`, and `voice_id`. Default endpoint is `https://api.minimaxi.com/v1/t2a_v2`, and audio is resolved from `data.audio` automatically. - `MIMO_TTS`: fill `api_key`; optionally set `url_template`, `model_name`, and `voice_id`. Use `mimo-v2.5-tts` with a short `voice_id` for preset voices. Use `mimo-v2.5-tts-voiceclone` with a full `data:audio/...;base64,...` audio sample in `voice_id` for voice cloning. Default endpoint is `https://api.xiaomimimo.com/v1/chat/completions`, and audio is resolved from `choices[0].message.audio.data` with Base64 decoding. - `DOUBAO_TTS`: Doubao TTS. Fill `url_template`, `api_key` (token), `model_name` (App ID), and `voice_id` (voice_type). Default endpoint is `https://openspeech.bytedance.com/api/v1/tts`; it inherits the HTTP TTS queue and response pipeline and resolves audio from `data` with Base64 decoding. - `OPENAI_TTS`: fill `url_template`, `api_key`, `model_name`, `voice_id`. - `VITS_TTS`: local VITS/Piper TTS. Set `tts_vits_package_path` to the local model package `.zip` or extracted package directory, optionally set `tts_vits_speaker_id` to a numeric speaker id, and use `tts_vits_options` for local options such as `sample_rate`, `threads`, `noise_scale`, `length_scale`, `noise_w`, `frontend`, `text_mode`, `speaker_count`, input names, and blank/bos/eos token settings. 3) STT (speech-to-text) engines: - `SHERPA_NCNN`: local recognition, usually no API key required. - `OPENAI_STT`: fill `endpoint_url`, `api_key`, `model_name`. - `DEEPGRAM_STT`: fill `endpoint_url`, `api_key`, `model_name`. 4) Most common mistakes (check first): - `headers` in `HTTP_TTS` is not valid JSON (must be an object). - Missing `{text}` placeholder in HTTP TTS template (typically in URL for GET, in body for POST). - `response_pipeline` in `HTTP_TTS` is not a valid JSON array, or a step name / `path` is incorrect. - `OPENAI_WS_TTS` is configured with an HTTP URL instead of a WebSocket URL, or vice versa. - `tts_vits_package_path` in `VITS_TTS` is not a local `.zip` model package or extracted package directory, or it does not exist. - The `VITS_TTS` package has no recognizable `.onnx` / config JSON / lexicon, or the config is missing `sample_rate` / token mappings. - `tts_vits_options` in `VITS_TTS` is not valid JSON, or local option names / numeric values are invalid. - Wrong endpoint path for TTS/STT (for example using chat/completions instead of audio endpoints). - `model_name` does not exist or does not match the API. - No real retest after saving config. 5) HTTP_TTS placeholder rules (from implementation): - Required placeholder: `{text}`. - When `http_method=GET`, `{text}` must appear in `url_template`. - When `http_method=POST`, `{text}` must appear in `request_body`. - Optional placeholders: `{rate}`, `{pitch}`, `{voice}`. - Stable placeholders for external configuration: `{text}`, `{rate}`, `{pitch}`, `{voice}`, `{apiKey}`, `{model}`, `{locale}`, `{uuid}`. 6) HTTP_TTS response handling notes (forward-compatible with released configs): - Leave `response_pipeline` empty or use `[]` to keep the old behavior: the first response body is treated as audio directly. - Only fill `response_pipeline` when the service returns JSON, nested fields, or a follow-up download URL before audio is available. - Currently supported steps: `parse_json`, `pick`, `parse_json_string`, `http_get`, `http_request_from_object`, `base64_decode`. - Common JSON download-link case: - `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"audio_uri"},{"type":"http_get"}]` - If the picked field is itself a JSON string: - `response_pipeline`: `[{"type":"parse_json"},{"type":"pick","path":"data.payload"},{"type":"parse_json_string"},{"type":"pick","path":"audio.url"},{"type":"http_get"}]` 7) Minimal templates you can copy: - HTTP TTS (GET): - `url_template`: `https://example.com/tts?text={text}` - `headers`: `{}` - `http_method`: `GET` - `content_type`: `application/json` - HTTP TTS (POST): - `url_template`: `https://example.com/tts` - `headers`: `{"Authorization":"Bearer "}` - `http_method`: `POST` - `content_type`: `application/json` - `request_body`: `{"text":"{text}"}` - OpenAI STT (common default): - `endpoint_url`: `https://api.openai.com/v1/audio/transcriptions` - `model_name`: `whisper-1` 8) Recommended troubleshooting order: - Confirm the selected engine type first (TTS and STT separately). - Check endpoint/key/model as a bundle. - Then verify HTTP template fields (headers JSON, method, body, placeholder, response_pipeline). - Run at least one real TTS playback test; if STT also needs troubleshooting, verify recognition in a separate speech flow. 9) Related tools: - `get_speech_services_config`: fetch current TTS/STT config snapshot (engine types + key fields). - `set_speech_services_config`: update TTS/STT config fields (partial update supported). - `test_tts_playback`: play one test utterance with the current TTS config (supports temporary rate/pitch overrides). [Multimodal input rules] 1) Meaning of capability switches: - Switches like Tool Call / image / audio / video in model config are software-side capability markers, not proof of real model capability. - Configure these switches according to actual model support; do not enable blindly. 2) Image understanding main path in app: - If the chat-function model config enables image understanding and the model truly supports it, attached images are sent directly to that chat model. - If the chat model does not support image understanding, the app will try OCR, or use the dedicated image-recognition function model as a relay. 3) Valid conditions when users need image understanding: - Condition A: chat model supports image understanding. - Condition B: chat model does not support it, but image-recognition function model supports it. - If the image-recognition model also does not support it, the final fallback is OCR. [Drawing output note] - Drawing is implemented via package tools. - The app includes several built-in drawing packages; use list_sandbox_packages to inspect them. - Usually enabling one available drawing package is enough; no need to enable all of them. [Execution principles] - Follow the user's explicit instructions strictly; do not define your own "problem" or add unrequested goals. - Any configuration-changing action (toggle/import/delete/write env/model config CRUD/restart) requires explicit user confirmation first. - If the user did not explicitly ask to execute a specific tool, do not proactively run write-type tools. - Answer with concrete paths/rules from this guide and avoid generic assumptions.''' } parameters: [] advice: true }, { name: "how_make_skill" description: { zh: '''返回如何制作 skill 的双语说明。''' en: '''Return a bilingual guide for creating a skill.''' } parameters: [] }, { name: "list_sandbox_packages" description: { zh: '''获取沙盒包列表(内置+外部)及当前启用状态、管理路径。''' en: '''Get sandbox package list (built-in + external), current enabled states, and management paths.''' } parameters: [] }, { name: "set_sandbox_package_enabled" description: { zh: '''设置沙盒包开关状态。''' en: '''Set sandbox package enabled state.''' } parameters: [ { name: "package_name" description: { zh: "沙盒包名称" en: "Sandbox package name" } type: string required: true }, { name: "enabled" description: { zh: "是否启用(true/false)" en: "Enable state (true/false)" } type: boolean required: true } ] }, { name: "debug_install_js_package" description: { zh: '''将 Android 侧的普通 `.js` 沙盒包直接烧录到外部 packages 目录,并刷新、启用、重新加载,便于在软件内调试。''' en: '''Install an Android-side plain `.js` sandbox package into the external packages directory, refresh it, enable it, and reload it for in-app debugging.''' } parameters: [ { name: "source_path" description: { zh: "Android 侧 `.js` 源文件路径" en: "Android-side `.js` source file path" } type: string required: true }, { name: "enable_after_install" description: { zh: "安装后是否自动启用,默认 true" en: "Whether to enable it automatically after install, default true" } type: boolean required: false }, { name: "activate_after_install" description: { zh: "安装后是否自动 use_package 重新加载,默认 true" en: "Whether to automatically call use_package after install, default true" } type: boolean required: false } ] }, { name: "debug_install_toolpkg" description: { zh: '''根据 Android 侧的 ToolPkg 目录、manifest 或现成 `.toolpkg`,直接打包/烧录到外部 packages 目录,并触发 ToolPkg 安装与刷新链路。''' en: '''Package or install an Android-side ToolPkg folder, manifest, or existing `.toolpkg` into the external packages directory and trigger the ToolPkg install/refresh flow.''' } parameters: [ { name: "source_path" description: { zh: "Android 侧 ToolPkg 目录、manifest.json/manifest.hjson 或 `.toolpkg` 路径" en: "Android-side ToolPkg folder, manifest.json/manifest.hjson, or `.toolpkg` path" } type: string required: true }, { name: "reset_subpackage_states" description: { zh: "是否按 manifest 默认值重置子包启用状态,默认 true" en: "Whether to reset subpackage enable states from manifest defaults, default true" } type: boolean required: false }, { name: "activate_subpackages" description: { zh: "可选,逗号或换行分隔的子包 ID;安装后会自动启用并 use_package" en: "Optional comma/newline separated subpackage IDs; they will be enabled and activated after install" } type: string required: false }, { name: "wait_ms" description: { zh: "安装广播后等待并轮询刷新的毫秒数,默认 1500" en: "Milliseconds to wait and poll for refresh after sending the install broadcast, default 1500" } type: integer required: false } ] }, { name: "debug_run_sandbox_script" description: { zh: '''直接运行一段 sandbox script。可传 Android 侧 `source_path`,也可直接传 `source_code` 内联代码;会返回结构化执行结果与日志事件。''' en: '''Run a sandbox script directly. Accepts either an Android-side `source_path` or inline `source_code`, and returns structured execution results with log events.''' } parameters: [ { name: "source_path" description: { zh: "Android 侧脚本文件路径;与 source_code 二选一" en: "Android-side script file path; use either this or source_code" } type: string required: false }, { name: "source_code" description: { zh: "直接执行的内联 JavaScript 代码;与 source_path 二选一" en: "Inline JavaScript code to execute directly; use either this or source_path" } type: string required: false }, { name: "params_json" description: { zh: "传给脚本运行时的 JSON 参数字符串,默认 {}" en: "JSON parameter string passed to the script runtime, default {}" } type: string required: false }, { name: "env_file_path" description: { zh: "可选,Android 侧 env 文件路径" en: "Optional Android-side env file path" } type: string required: false }, { name: "script_label" description: { zh: "可选,仅用于内联代码模式下生成结果文件名和显示标识" en: "Optional label used only for inline-code mode to name the result file and display path" } type: string required: false }, { name: "wait_ms" description: { zh: "等待结构化结果文件的毫秒数,默认 15000" en: "Milliseconds to wait for the structured result file, default 15000" } type: integer required: false } ] }, { name: "read_environment_variable" description: { zh: '''读取指定环境变量当前值(仅用于沙盒包脚本环境变量排查,不用于 MCP 配置)。''' en: '''Read current value of a specified environment variable (sandbox-package script env troubleshooting only, not MCP config env).''' } parameters: [ { name: "key" description: { zh: "环境变量名" en: "Environment variable key" } type: string required: true } ] }, { name: "write_environment_variable" description: { zh: '''写入指定环境变量;value 为空时会清除该变量(仅用于沙盒包脚本环境变量,不用于 MCP 配置 env)。''' en: '''Write a specified environment variable; empty value clears it (sandbox-package script env only, not MCP config env).''' } parameters: [ { name: "key" description: { zh: "环境变量名" en: "Environment variable key" } type: string required: true }, { name: "value" description: { zh: "变量值;为空时清除该变量" en: "Variable value; empty clears the variable" } type: string required: false } ] }, { name: "restart_mcp_with_logs" description: { zh: '''触发一次 MCP 重启流程,返回每个插件的启动日志与状态摘要。''' en: '''Trigger one MCP restart flow and return per-plugin startup logs with status summary.''' } parameters: [ { name: "timeout_ms" description: { zh: "可选,最大等待时长(毫秒)" en: "Optional max wait time in milliseconds" } type: integer required: false } ] }, { name: "get_speech_services_config" description: { zh: '''获取当前 TTS/STT 语音服务配置快照。''' en: '''Get current TTS/STT speech services config snapshot.''' } parameters: [] }, { name: "set_speech_services_config" description: { zh: '''按字段更新 TTS/STT 语音服务配置(支持部分字段更新)。''' en: '''Update TTS/STT speech services config by fields (partial update supported).''' } parameters: [ { name: "tts_service_type" description: { zh: "可选,SIMPLE_TTS/HTTP_TTS/OPENAI_WS_TTS/SILICONFLOW_TTS/MINIMAX_TTS/MIMO_TTS/DOUBAO_TTS/OPENAI_TTS/VITS_TTS" en: "Optional, SIMPLE_TTS/HTTP_TTS/OPENAI_WS_TTS/SILICONFLOW_TTS/MINIMAX_TTS/MIMO_TTS/DOUBAO_TTS/OPENAI_TTS/VITS_TTS" } type: string required: false }, { name: "tts_url_template" description: { zh: "可选,HTTP 类 TTS 的 URL 模板。HTTP 系列仅支持 `{text}`、`{rate}`、`{pitch}`、`{voice}`" en: "Optional URL template for HTTP-style TTS providers. HTTP-style providers support only `{text}`, `{rate}`, `{pitch}`, `{voice}`" } type: string required: false }, { name: "tts_api_key" description: { zh: "可选,TTS API Key" en: "Optional TTS API key" } type: string required: false }, { name: "tts_headers" description: { zh: "可选,HTTP 类 TTS headers 的 JSON 对象字符串" en: "Optional JSON object string for HTTP-style TTS headers" } type: string required: false }, { name: "tts_http_method" description: { zh: "可选,GET/POST" en: "Optional, GET/POST" } type: string required: false }, { name: "tts_request_body" description: { zh: "可选,HTTP 类 TTS 的 POST body 模板。仅支持 `{text}`、`{rate}`、`{pitch}`、`{voice}`" en: "Optional POST body template for HTTP-style TTS providers. Supports only `{text}`, `{rate}`, `{pitch}`, `{voice}`" } type: string required: false }, { name: "tts_content_type" description: { zh: "可选,TTS Content-Type" en: "Optional TTS content type" } type: string required: false }, { name: "tts_locale" description: { zh: "可选,TTS 语言标签,例如 zh-CN 或 en-US" en: "Optional TTS locale tag, for example zh-CN or en-US" } type: string required: false }, { name: "tts_voice_id" description: { zh: "可选,TTS 音色 ID。MIMO voiceclone 可填写完整 data:audio/...;base64,... 音频样本" en: "Optional TTS voice id. For MIMO voiceclone, this may be the full data:audio/...;base64,... audio sample" } type: string required: false }, { name: "tts_model_name" description: { zh: "可选,TTS 模型名" en: "Optional TTS model name" } type: string required: false }, { name: "tts_vits_package_path" description: { zh: "可选,本地 VITS/Piper TTS 模型包路径,支持 .zip 文件或已解压目录" en: "Optional local VITS/Piper TTS package path, supporting a .zip file or extracted package directory" } type: string required: false }, { name: "tts_vits_speaker_id" description: { zh: "可选,VITS/Piper TTS 模型包需要的数字 speaker id" en: "Optional numeric speaker id required by the VITS/Piper TTS package" } type: string required: false }, { name: "tts_vits_options" description: { zh: "可选,VITS/Piper TTS 模型包参数 JSON 对象字符串" en: "Optional JSON object string for VITS/Piper TTS package options" } type: string required: false }, { name: "tts_response_pipeline" description: { zh: "可选,HTTP TTS 响应处理管线 JSON 数组字符串。留空或 `[]` 时保持旧行为,直接把响应体当音频" en: "Optional HTTP TTS response pipeline JSON array string. Leave empty or use `[]` to keep the old direct-audio behavior" } type: string required: false }, { name: "tts_cleaner_regexs" description: { zh: "可选,TTS 清理正则列表 JSON 数组字符串" en: "Optional JSON array string for TTS cleaner regex list" } type: string required: false }, { name: "tts_speech_rate" description: { zh: "可选,TTS 语速" en: "Optional TTS speech rate" } type: number required: false }, { name: "tts_pitch" description: { zh: "可选,TTS 音调" en: "Optional TTS pitch" } type: number required: false }, { name: "stt_service_type" description: { zh: "可选,SHERPA_NCNN/OPENAI_STT/DEEPGRAM_STT" en: "Optional, SHERPA_NCNN/OPENAI_STT/DEEPGRAM_STT" } type: string required: false }, { name: "stt_endpoint_url" description: { zh: "可选,STT endpoint URL" en: "Optional STT endpoint URL" } type: string required: false }, { name: "stt_api_key" description: { zh: "可选,STT API Key" en: "Optional STT API key" } type: string required: false }, { name: "stt_model_name" description: { zh: "可选,STT 模型名" en: "Optional STT model name" } type: string required: false } ] }, { name: "test_tts_playback" description: { zh: '''按当前 TTS 配置播放一次测试文本。''' en: '''Play one TTS test utterance with the current configuration.''' } parameters: [ { name: "text" description: { zh: "必填,要播放的测试文本" en: "Required test text to play" } type: string required: true }, { name: "interrupt" description: { zh: "可选,播放前是否先中断当前播报" en: "Optional, interrupt current playback before this test" } type: boolean required: false }, { name: "speech_rate" description: { zh: "可选,仅本次测试生效的语速覆盖值" en: "Optional speech-rate override for this test only" } type: number required: false }, { name: "pitch" description: { zh: "可选,仅本次测试生效的音调覆盖值" en: "Optional pitch override for this test only" } type: number required: false } ] }, { name: "list_model_configs" description: { zh: '''列出全部模型配置及功能模型绑定关系。''' en: '''List all model configs and function-model bindings.''' } parameters: [] }, { name: "create_model_config" description: { zh: '''新增模型配置(可带初始化字段)。''' en: '''Create a model config (optional initialization fields).''' } parameters: [ { name: "name" description: { zh: "可选,配置名称" en: "Optional config name" } type: string required: false }, { name: "api_provider_type" description: { zh: "可选,提供商枚举名(如 OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP;其中 LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP 为本地模型链路)" en: "Optional provider enum name (e.g. OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP; LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP are local-model providers)" } type: string required: false }, { name: "api_endpoint" description: { zh: "可选,API端点" en: "Optional API endpoint" } type: string required: false }, { name: "api_key" description: { zh: "可选,API Key" en: "Optional API key" } type: string required: false }, { name: "model_name" description: { zh: "可选,模型名(多个可逗号分隔)" en: "Optional model name (comma-separated for multiple models)" } type: string required: false }, { name: "max_tokens_enabled" description: { zh: "可选,是否启用 max_tokens 参数" en: "Optional switch for max_tokens" } type: boolean required: false }, { name: "max_tokens" description: { zh: "可选,max_tokens 数值" en: "Optional max_tokens value" } type: integer required: false }, { name: "temperature_enabled" description: { zh: "可选,是否启用 temperature 参数" en: "Optional switch for temperature" } type: boolean required: false }, { name: "temperature" description: { zh: "可选,temperature 数值" en: "Optional temperature value" } type: number required: false }, { name: "top_p_enabled" description: { zh: "可选,是否启用 top_p 参数" en: "Optional switch for top_p" } type: boolean required: false }, { name: "top_p" description: { zh: "可选,top_p 数值" en: "Optional top_p value" } type: number required: false }, { name: "top_k_enabled" description: { zh: "可选,是否启用 top_k 参数" en: "Optional switch for top_k" } type: boolean required: false }, { name: "top_k" description: { zh: "可选,top_k 数值" en: "Optional top_k value" } type: integer required: false }, { name: "presence_penalty_enabled" description: { zh: "可选,是否启用 presence_penalty 参数" en: "Optional switch for presence_penalty" } type: boolean required: false }, { name: "presence_penalty" description: { zh: "可选,presence_penalty 数值" en: "Optional presence_penalty value" } type: number required: false }, { name: "frequency_penalty_enabled" description: { zh: "可选,是否启用 frequency_penalty 参数" en: "Optional switch for frequency_penalty" } type: boolean required: false }, { name: "frequency_penalty" description: { zh: "可选,frequency_penalty 数值" en: "Optional frequency_penalty value" } type: number required: false }, { name: "repetition_penalty_enabled" description: { zh: "可选,是否启用 repetition_penalty 参数" en: "Optional switch for repetition_penalty" } type: boolean required: false }, { name: "repetition_penalty" description: { zh: "可选,repetition_penalty 数值" en: "Optional repetition_penalty value" } type: number required: false }, { name: "custom_parameters" description: { zh: "可选,自定义参数 JSON 字符串" en: "Optional custom parameters JSON string" } type: string required: false }, { name: "custom_headers" description: { zh: "可选,自定义请求头 JSON 对象字符串" en: "Optional custom request headers JSON object string" } type: string required: false }, { name: "context_length" description: { zh: "可选,上下文长度倍率" en: "Optional context length multiplier" } type: number required: false }, { name: "max_context_length" description: { zh: "可选,最大上下文长度倍率" en: "Optional max context length multiplier" } type: number required: false }, { name: "enable_max_context_mode" description: { zh: "可选,是否启用最大上下文模式" en: "Optional max-context mode switch" } type: boolean required: false }, { name: "summary_token_threshold" description: { zh: "可选,总结触发阈值" en: "Optional summary trigger threshold" } type: number required: false }, { name: "enable_summary" description: { zh: "可选,是否启用总结" en: "Optional summary switch" } type: boolean required: false }, { name: "enable_summary_by_message_count" description: { zh: "可选,是否按消息数触发总结" en: "Optional summary-by-message-count switch" } type: boolean required: false }, { name: "summary_message_count_threshold" description: { zh: "可选,按消息数总结的阈值" en: "Optional message-count threshold for summary" } type: integer required: false }, { name: "enable_direct_image_processing" description: { zh: "可选,是否启用直接图片处理" en: "Optional direct image processing switch" } type: boolean required: false }, { name: "enable_direct_audio_processing" description: { zh: "可选,是否启用直接音频处理" en: "Optional direct audio processing switch" } type: boolean required: false }, { name: "enable_direct_video_processing" description: { zh: "可选,是否启用直接视频处理" en: "Optional direct video processing switch" } type: boolean required: false }, { name: "enable_google_search" description: { zh: "可选,是否启用 Google Search" en: "Optional Google Search switch" } type: boolean required: false }, { name: "enable_claude_1h_prompt_cache" description: { zh: "可选,是否启用 Claude 1h Prompt Cache" en: "Optional Claude 1h prompt cache switch" } type: boolean required: false }, { name: "enable_tool_call" description: { zh: "可选,是否开启Tool Call" en: "Optional tool-call switch" } type: boolean required: false }, { name: "mnn_forward_type" description: { zh: "可选,MNN 前向类型" en: "Optional MNN forward type" } type: integer required: false }, { name: "mnn_thread_count" description: { zh: "可选,MNN 线程数" en: "Optional MNN thread count" } type: integer required: false }, { name: "llama_thread_count" description: { zh: "可选,llama.cpp 线程数" en: "Optional llama.cpp thread count" } type: integer required: false }, { name: "llama_context_size" description: { zh: "可选,llama.cpp 上下文长度" en: "Optional llama.cpp context size" } type: integer required: false }, { name: "llama_gpu_layers" description: { zh: "可选,llama.cpp GPU 层数" en: "Optional llama.cpp GPU layer count" } type: integer required: false }, { name: "request_limit_per_minute" description: { zh: "可选,每分钟请求限制" en: "Optional request-per-minute limit" } type: integer required: false }, { name: "max_concurrent_requests" description: { zh: "可选,最大并发请求数" en: "Optional max concurrent requests" } type: integer required: false } ] }, { name: "update_model_config" description: { zh: '''按 config_id 修改模型配置。''' en: '''Update model config by config_id.''' } parameters: [ { name: "config_id" description: { zh: "目标配置ID" en: "Target config id" } type: string required: true }, { name: "name" description: { zh: "可选,配置名称" en: "Optional config name" } type: string required: false }, { name: "api_provider_type" description: { zh: "可选,提供商枚举名(如 OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP;其中 LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP 为本地模型链路)" en: "Optional provider enum name (e.g. OPENAI_GENERIC/OPENAI_LOCAL/OPENAI_RESPONSES_GENERIC/DEEPSEEK/GEMINI_GENERIC/LMSTUDIO/OLLAMA/MNN/LLAMA_CPP; LMSTUDIO/OLLAMA/OPENAI_LOCAL/MNN/LLAMA_CPP are local-model providers)" } type: string required: false }, { name: "api_endpoint" description: { zh: "可选,API端点" en: "Optional API endpoint" } type: string required: false }, { name: "api_key" description: { zh: "可选,API Key" en: "Optional API key" } type: string required: false }, { name: "model_name" description: { zh: "可选,模型名(多个可逗号分隔)" en: "Optional model name (comma-separated for multiple models)" } type: string required: false }, { name: "max_tokens_enabled" description: { zh: "可选,是否启用 max_tokens 参数" en: "Optional switch for max_tokens" } type: boolean required: false }, { name: "max_tokens" description: { zh: "可选,max_tokens 数值" en: "Optional max_tokens value" } type: integer required: false }, { name: "temperature_enabled" description: { zh: "可选,是否启用 temperature 参数" en: "Optional switch for temperature" } type: boolean required: false }, { name: "temperature" description: { zh: "可选,temperature 数值" en: "Optional temperature value" } type: number required: false }, { name: "top_p_enabled" description: { zh: "可选,是否启用 top_p 参数" en: "Optional switch for top_p" } type: boolean required: false }, { name: "top_p" description: { zh: "可选,top_p 数值" en: "Optional top_p value" } type: number required: false }, { name: "top_k_enabled" description: { zh: "可选,是否启用 top_k 参数" en: "Optional switch for top_k" } type: boolean required: false }, { name: "top_k" description: { zh: "可选,top_k 数值" en: "Optional top_k value" } type: integer required: false }, { name: "presence_penalty_enabled" description: { zh: "可选,是否启用 presence_penalty 参数" en: "Optional switch for presence_penalty" } type: boolean required: false }, { name: "presence_penalty" description: { zh: "可选,presence_penalty 数值" en: "Optional presence_penalty value" } type: number required: false }, { name: "frequency_penalty_enabled" description: { zh: "可选,是否启用 frequency_penalty 参数" en: "Optional switch for frequency_penalty" } type: boolean required: false }, { name: "frequency_penalty" description: { zh: "可选,frequency_penalty 数值" en: "Optional frequency_penalty value" } type: number required: false }, { name: "repetition_penalty_enabled" description: { zh: "可选,是否启用 repetition_penalty 参数" en: "Optional switch for repetition_penalty" } type: boolean required: false }, { name: "repetition_penalty" description: { zh: "可选,repetition_penalty 数值" en: "Optional repetition_penalty value" } type: number required: false }, { name: "custom_parameters" description: { zh: "可选,自定义参数 JSON 字符串" en: "Optional custom parameters JSON string" } type: string required: false }, { name: "custom_headers" description: { zh: "可选,自定义请求头 JSON 对象字符串" en: "Optional custom request headers JSON object string" } type: string required: false }, { name: "context_length" description: { zh: "可选,上下文长度倍率" en: "Optional context length multiplier" } type: number required: false }, { name: "max_context_length" description: { zh: "可选,最大上下文长度倍率" en: "Optional max context length multiplier" } type: number required: false }, { name: "enable_max_context_mode" description: { zh: "可选,是否启用最大上下文模式" en: "Optional max-context mode switch" } type: boolean required: false }, { name: "summary_token_threshold" description: { zh: "可选,总结触发阈值" en: "Optional summary trigger threshold" } type: number required: false }, { name: "enable_summary" description: { zh: "可选,是否启用总结" en: "Optional summary switch" } type: boolean required: false }, { name: "enable_summary_by_message_count" description: { zh: "可选,是否按消息数触发总结" en: "Optional summary-by-message-count switch" } type: boolean required: false }, { name: "summary_message_count_threshold" description: { zh: "可选,按消息数总结的阈值" en: "Optional message-count threshold for summary" } type: integer required: false }, { name: "enable_direct_image_processing" description: { zh: "可选,是否启用直接图片处理" en: "Optional direct image processing switch" } type: boolean required: false }, { name: "enable_direct_audio_processing" description: { zh: "可选,是否启用直接音频处理" en: "Optional direct audio processing switch" } type: boolean required: false }, { name: "enable_direct_video_processing" description: { zh: "可选,是否启用直接视频处理" en: "Optional direct video processing switch" } type: boolean required: false }, { name: "enable_google_search" description: { zh: "可选,是否启用 Google Search" en: "Optional Google Search switch" } type: boolean required: false }, { name: "enable_claude_1h_prompt_cache" description: { zh: "可选,是否启用 Claude 1h Prompt Cache" en: "Optional Claude 1h prompt cache switch" } type: boolean required: false }, { name: "enable_tool_call" description: { zh: "可选,是否开启Tool Call" en: "Optional tool-call switch" } type: boolean required: false }, { name: "mnn_forward_type" description: { zh: "可选,MNN 前向类型" en: "Optional MNN forward type" } type: integer required: false }, { name: "mnn_thread_count" description: { zh: "可选,MNN 线程数" en: "Optional MNN thread count" } type: integer required: false }, { name: "llama_thread_count" description: { zh: "可选,llama.cpp 线程数" en: "Optional llama.cpp thread count" } type: integer required: false }, { name: "llama_context_size" description: { zh: "可选,llama.cpp 上下文长度" en: "Optional llama.cpp context size" } type: integer required: false }, { name: "llama_gpu_layers" description: { zh: "可选,llama.cpp GPU 层数" en: "Optional llama.cpp GPU layer count" } type: integer required: false }, { name: "request_limit_per_minute" description: { zh: "可选,每分钟请求限制" en: "Optional request-per-minute limit" } type: integer required: false }, { name: "max_concurrent_requests" description: { zh: "可选,最大并发请求数" en: "Optional max concurrent requests" } type: integer required: false } ] }, { name: "delete_model_config" description: { zh: '''按 config_id 删除模型配置(默认配置不可删)。''' en: '''Delete model config by config_id (default cannot be deleted).''' } parameters: [ { name: "config_id" description: { zh: "目标配置ID" en: "Target config id" } type: string required: true } ] }, { name: "list_function_model_configs" description: { zh: '''仅列出功能模型绑定关系(功能 -> 配置 + 模型索引)。''' en: '''List function model bindings only (function -> config + model index).''' } parameters: [] }, { name: "get_function_model_config" description: { zh: '''查看某个功能当前绑定的单个模型配置详情。''' en: '''Get one function's currently bound model config detail.''' } parameters: [ { name: "function_type" description: { zh: "功能类型枚举名" en: "Function type enum name" } type: string required: true } ] }, { name: "get_context_summary_config" description: { zh: '''获取某个功能当前绑定模型配置中的上下文总结参数。''' en: '''Get context-summary settings from the model config bound to a function.''' } parameters: [ { name: "function_type" description: { zh: "可选,功能类型;默认 CHAT" en: "Optional function type; default CHAT" } type: string required: false } ] }, { name: "set_context_summary_config" description: { zh: '''为某个功能绑定配置设置上下文总结参数(可选覆盖默认值)。''' en: '''Set context-summary settings for a function binding (optional overrides supported).''' } parameters: [ { name: "function_type" description: { zh: "可选,功能类型;默认 CHAT" en: "Optional function type; default CHAT" } type: string required: false }, { name: "context_length" description: { zh: "可选,基础上下文长度" en: "Optional base context length" } type: number required: false }, { name: "max_context_length" description: { zh: "可选,最大上下文长度" en: "Optional max context length" } type: number required: false }, { name: "enable_max_context_mode" description: { zh: "可选,是否启用最大上下文模式" en: "Optional max-context-mode switch" } type: boolean required: false }, { name: "summary_token_threshold" description: { zh: "可选,总结触发 token 阈值(0~1)" en: "Optional token-ratio threshold for summary trigger (0~1)" } type: number required: false }, { name: "enable_summary" description: { zh: "可选,是否启用上下文总结" en: "Optional context-summary switch" } type: boolean required: false }, { name: "enable_summary_by_message_count" description: { zh: "可选,是否启用按消息条数触发总结" en: "Optional message-count summary trigger switch" } type: boolean required: false }, { name: "summary_message_count_threshold" description: { zh: "可选,按消息条数触发总结阈值" en: "Optional message-count threshold for summary trigger" } type: integer required: false } ] }, { name: "set_function_model_config" description: { zh: '''为功能指定模型配置与模型索引。''' en: '''Set model config and model index for a function.''' } parameters: [ { name: "function_type" description: { zh: "功能类型枚举名" en: "Function type enum name" } type: string required: true }, { name: "config_id" description: { zh: "模型配置ID" en: "Model config id" } type: string required: true }, { name: "model_index" description: { zh: "可选,模型索引" en: "Optional model index" } type: integer required: false } ] }, { name: "test_model_config_connection" description: { zh: '''按设置页同等逻辑测试某个模型配置。''' en: '''Run settings-UI-equivalent tests for one model config.''' } parameters: [ { name: "config_id" description: { zh: "目标配置ID" en: "Target config id" } type: string required: true }, { name: "model_index" description: { zh: "可选,模型索引" en: "Optional model index" } type: integer required: false } ] }, { name: "ping_mcp" description: { zh: '''直通 use_package 的探测工具:用于快速测试某个 package 是否可被加载(MCP/Skill/Sandbox 三兼容)。''' en: '''A pass-through probe for use_package: quickly test whether a package can be loaded (tri-compatible across MCP/Skill/Sandbox).''' } parameters: [ { name: "package_name" description: { zh: "要探测的包名" en: "Package name to probe" } type: string required: true } ] } ] }*/ const operitEditorPackage = (function () { async function operit_editor(params: { query?: string }) { try { const { query } = params ?? {}; complete({ success: true, message: "配置排查手册已加载(MCP/Skill/Sandbox Package/沙盒包调试烧录/功能模型与模型配置/TTS-STT语音服务),将按配置链路执行排查。", data: { query: query ?? "" } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function how_make_skill() { try { const locale = (getLang() ?? "").toLowerCase(); const lang: "zh" | "en" | "both" = locale.startsWith("zh") ? "zh" : locale.startsWith("en") ? "en" : "both"; const zh = `如何制作 skill(简版) 1. 先创建目录:/sdcard/Download/Operit/skills// 2. 必备文件:SKILL.md 3. 在 SKILL.md 顶部用 Markdown 元数据(frontmatter)写 name、description,例如: --- name: your_skill_name description: 用一句话说明这个 skill 做什么 --- 4. 元数据后再写正文:适用场景、执行步骤、约束边界、期望输出 5. 可选内容:scripts/、templates/、examples/、assets/;在 SKILL.md 里用相对路径引用 6. 实践建议:优先下载现成 skill,直接解压过来,并确保目录下有 SKILL.md。`; const en = `How to make a skill (quick guide) 1. Create a directory: /sdcard/Download/Operit/skills// 2. Required file: SKILL.md 3. At the top of SKILL.md, use Markdown metadata (frontmatter) for name and description, for example: --- name: your_skill_name description: one-line summary of what this skill does --- 4. After metadata, write the main sections: use cases, workflow steps, constraints, expected outputs 5. Optional content: scripts/, templates/, examples/, assets/; reference them from SKILL.md using relative paths 6. Practical tip: download an existing skill, extract it directly, and ensure the directory contains SKILL.md`; const message = lang === "zh" ? zh : lang === "en" ? en : `${zh}\n\n---\n\n${en}`; complete({ success: true, message, data: { lang, zh, en } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function list_sandbox_packages() { try { const result = await Tools.SoftwareSettings.listSandboxPackages(); complete({ success: true, message: `Sandbox package list fetched: ${String(result.totalCount)} package(s).`, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function set_sandbox_package_enabled(params?: { package_name?: string; enabled?: boolean | string | number }) { try { const packageName = params?.package_name ?? ""; const enabled = params?.enabled ?? false; const result = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageName, enabled); complete({ success: true, message: result.message || "Sandbox package switch updated.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } const SANDBOX_EXTERNAL_PACKAGES_DIR = "/sdcard/Android/data/com.ai.assistance.operit/files/packages"; const TOOLPKG_DEBUG_INSTALL_ACTION = "com.ai.assistance.operit.DEBUG_INSTALL_TOOLPKG"; const TOOLPKG_DEBUG_INSTALL_COMPONENT = "com.ai.assistance.operit/.core.tools.packTool.ToolPkgDebugInstallReceiver"; const SANDBOX_SCRIPT_EXECUTION_ACTION = "com.ai.assistance.operit.EXECUTE_JS"; const SANDBOX_SCRIPT_EXECUTION_COMPONENT = "com.ai.assistance.operit/com.ai.assistance.operit.core.tools.javascript.ScriptExecutionReceiver"; const SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT = "script"; const SANDBOX_SCRIPT_EXECUTION_MODE_CODE = "code"; const SANDBOX_JS_TEMP_DIR = "/sdcard/Android/data/com.ai.assistance.operit/js_temp"; const DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS = 1500; const DEFAULT_TOOLPKG_INSTALL_WAIT_MS = 1500; const DEFAULT_SANDBOX_SCRIPT_WAIT_MS = 15000; const JS_METADATA_BLOCK_PATTERN = /\/\*\s*METADATA([\s\S]*?)\*\//m; const JS_PACKAGE_NAME_PATTERN = /^\s*["']?name["']?\s*:\s*["']([^"']+)["']/m; const TOOLPKG_ID_PATTERN = /^\s*["']?toolpkg_id["']?\s*:\s*["']([^"']+)["']/m; const TOOLPKG_MAIN_PATTERN = /^\s*["']?main["']?\s*:\s*["']([^"']+)["']/m; const TOOLPKG_SUBPACKAGE_ID_PATTERN = /^\s*["']?id["']?\s*:\s*["']([^"']+)["']/gm; const TOOLPKG_SKIP_DIR_NAMES = new Set([".git", "__pycache__"]); const TOOLPKG_SKIP_FILE_NAMES = new Set([".DS_Store", "Thumbs.db"]); type SandboxPackageEntry = SandboxPackageResultItem; type SandboxPackagesPayload = SandboxPackagesResultData; type SandboxRefreshResult = { payload: SandboxPackagesPayload | null; packageEntry: SandboxPackageEntry | null; }; function collect_related_package_load_errors( payload: SandboxPackagesPayload | null, packageName: string, ...relatedPaths: Array ): Record { const normalizedPackageName = normalize_package_key(packageName); const normalizedPaths = relatedPaths.map((path) => String(path ?? "").trim()).filter(Boolean); const packageLoadErrors = payload?.packageLoadErrors; if (!packageLoadErrors) { return {}; } return Object.fromEntries( Object.entries(packageLoadErrors).filter(([key, value]) => { const normalizedKey = normalize_package_key(key); const message = String(value ?? ""); return ( normalizedKey === normalizedPackageName || message.toLowerCase().includes(normalizedPackageName) || normalizedPaths.some((path) => message.includes(path)) ); }) ); } type JsPackageSourceInfo = { packageName: string; metadataBlock: string; }; type ToolPkgManifestSummary = { packageId: string; mainEntry: string; subpackageIds: string[]; }; type ToolPkgResolvedSource = { sourceKind: "folder" | "archive"; sourcePath: string; folderPath: string; manifestPath: string; packageId: string; mainEntry: string; subpackageIds: string[]; archivePath?: string; temporaryPaths: string[]; }; function normalize_android_path(raw?: string): string { const normalized = String(raw ?? "").trim().replace(/\\/g, "/"); if (!normalized) return ""; if (/^[a-zA-Z]+:\/\//.test(normalized)) return normalized; if (normalized.startsWith("/")) return normalized; if (normalized.startsWith("sdcard/")) return `/${normalized}`; if (normalized.startsWith("Android/") || normalized.startsWith("Download/")) { return `/sdcard/${normalized}`; } return normalized; } function normalize_package_key(raw?: string): string { return String(raw ?? "").trim().toLowerCase(); } function normalize_directory_path(path: string): string { const normalized = normalize_android_path(path).replace(/\/+/g, "/"); if (normalized === "/") return normalized; return normalized.replace(/\/+$/, ""); } function same_android_path(left: string, right: string): boolean { return normalize_directory_path(left) === normalize_directory_path(right); } function path_dirname(path: string): string { const normalized = normalize_directory_path(path); const index = normalized.lastIndexOf("/"); if (index < 0) return ""; if (index === 0) return "/"; return normalized.slice(0, index); } function path_basename(path: string): string { const normalized = normalize_directory_path(path); const index = normalized.lastIndexOf("/"); return index >= 0 ? normalized.slice(index + 1) : normalized; } function path_join(...parts: string[]): string { const filtered = parts .map((part) => String(part ?? "").trim().replace(/\\/g, "/")) .filter(Boolean); if (filtered.length === 0) return ""; const leadingSlash = filtered[0].startsWith("/"); const joined = filtered.map((part) => part.replace(/^\/+|\/+$/g, "")).filter(Boolean).join("/"); return leadingSlash ? `/${joined}` : joined; } function safe_debug_file_stem(raw: string, fallback: string): string { const normalized = String(raw ?? "").trim().replace(/[^A-Za-z0-9._-]+/g, "_").replace(/^[_\.]+|[_\.]+$/g, ""); return normalized || fallback; } function parse_boolean_like(value: unknown, defaultValue: boolean): boolean { if (value === undefined || value === null || value === "") return defaultValue; if (typeof value === "boolean") return value; if (typeof value === "number") return value !== 0; const normalized = String(value).trim().toLowerCase(); if (!normalized) return defaultValue; if (["true", "1", "yes", "on"].includes(normalized)) return true; if (["false", "0", "no", "off"].includes(normalized)) return false; return defaultValue; } function parse_integer_like(value: unknown, defaultValue: number): number { if (value === undefined || value === null || value === "") return defaultValue; const parsed = Number(value); return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : defaultValue; } async function android_path_exists(path: string): Promise { const result = await Tools.Files.exists(path, "android"); return result.exists; } async function get_android_file_type(path: string): Promise { const result = await Tools.Files.info(path, "android"); return result.fileType.trim().toLowerCase(); } async function ensure_android_directory(path: string) { await Tools.Files.mkdir(path, true, "android"); } async function delete_android_path_if_exists(path: string) { if (!path) return; if (!(await android_path_exists(path))) return; await Tools.Files.deleteFile(path, true, "android"); } async function cleanup_android_paths(paths: string[]) { const uniquePaths = Array.from(new Set(paths.map((path) => normalize_android_path(path)).filter(Boolean))); uniquePaths.sort((left, right) => right.length - left.length); for (const path of uniquePaths) { try { await delete_android_path_if_exists(path); } catch { // Ignore cleanup failures. } } } async function read_android_text_file(path: string): Promise { const result = await Tools.Files.read({ path, environment: "android" }); return result.content; } function parse_json_record(raw: string): Record | null { if (!raw) return null; try { const parsed = JSON.parse(raw); if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null; return parsed as Record; } catch { return null; } } function find_sandbox_package_entry( payload: SandboxPackagesPayload | null, packageName: string ): SandboxPackageEntry | null { const targetKey = normalize_package_key(packageName); const packages = payload?.packages ?? []; return ( packages.find((entry) => normalize_package_key(entry?.packageName) === targetKey) ?? null ); } async function refresh_sandbox_packages_until( packageName: string, timeoutMs: number ): Promise { const deadline = Date.now() + Math.max(0, timeoutMs); let lastPayload: SandboxPackagesPayload | null = null; let lastEntry: SandboxPackageEntry | null = null; while (true) { lastPayload = await Tools.SoftwareSettings.listSandboxPackages(); lastEntry = find_sandbox_package_entry(lastPayload, packageName); if (lastEntry) { return { payload: lastPayload, packageEntry: lastEntry }; } if (Date.now() >= deadline) { return { payload: lastPayload, packageEntry: lastEntry }; } await Tools.System.sleep(Math.min(300, Math.max(50, deadline - Date.now()))); } } async function wait_for_android_file(path: string, timeoutMs: number): Promise { const deadline = Date.now() + Math.max(0, timeoutMs); while (true) { if (await android_path_exists(path)) { return true; } if (Date.now() >= deadline) { return false; } await Tools.System.sleep(Math.min(300, Math.max(50, deadline - Date.now()))); } } function parse_json_text(raw: string): unknown | null { const text = String(raw ?? "").trim(); if (!text) return null; try { return JSON.parse(text); } catch { return null; } } function extract_js_metadata_block(sourceText: string, sourcePath: string): string { const match = JS_METADATA_BLOCK_PATTERN.exec(sourceText); if (!match) { throw new Error(`Missing METADATA block: ${sourcePath}`); } return match[1].trim(); } function parse_js_package_source(sourceText: string, sourcePath: string): JsPackageSourceInfo { const metadataBlock = extract_js_metadata_block(sourceText, sourcePath); const packageName = JS_PACKAGE_NAME_PATTERN.exec(metadataBlock)?.[1]?.trim() ?? ""; if (!packageName) { throw new Error(`Missing package metadata name: ${sourcePath}`); } return { packageName, metadataBlock }; } async function delete_duplicate_external_js_package_files(packageName: string, keepPath: string) { const removedPaths: string[] = []; const listing = await Tools.Files.list(SANDBOX_EXTERNAL_PACKAGES_DIR, "android"); for (const entry of listing?.entries ?? []) { const entryName = String(entry?.name ?? "").trim(); if (!entryName || entry?.isDirectory || !entryName.toLowerCase().endsWith(".js")) { continue; } const candidatePath = path_join(SANDBOX_EXTERNAL_PACKAGES_DIR, entryName); if (same_android_path(candidatePath, keepPath)) { continue; } try { const candidateText = await read_android_text_file(candidatePath); const candidateInfo = parse_js_package_source(candidateText, candidatePath); if (normalize_package_key(candidateInfo.packageName) !== normalize_package_key(packageName)) { continue; } await Tools.Files.deleteFile(candidatePath, false, "android"); removedPaths.push(candidatePath); } catch { // Ignore files that cannot be parsed as sandbox packages. } } return removedPaths; } function parse_toolpkg_manifest_text(text: string, manifestPath: string): ToolPkgManifestSummary { let packageId = ""; let mainEntry = ""; let subpackageIds: string[] = []; try { const parsed = JSON.parse(text) as { toolpkg_id?: unknown; main?: unknown; subpackages?: Array<{ id?: unknown }>; }; if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) { packageId = String(parsed.toolpkg_id ?? "").trim(); mainEntry = String(parsed.main ?? "").trim(); if (Array.isArray(parsed.subpackages)) { subpackageIds = parsed.subpackages .map((subpackage) => String(subpackage?.id ?? "").trim()) .filter(Boolean); } } } catch { // HJSON-like manifests will fall back to regex parsing below. } if (!packageId) { packageId = TOOLPKG_ID_PATTERN.exec(text)?.[1]?.trim() ?? ""; } if (!mainEntry) { mainEntry = TOOLPKG_MAIN_PATTERN.exec(text)?.[1]?.trim() ?? ""; } if (subpackageIds.length === 0) { const matches: string[] = []; const pattern = new RegExp(TOOLPKG_SUBPACKAGE_ID_PATTERN.source, TOOLPKG_SUBPACKAGE_ID_PATTERN.flags); let match: RegExpExecArray | null; while ((match = pattern.exec(text)) !== null) { const subpackageId = match[1]?.trim() ?? ""; if (subpackageId) { matches.push(subpackageId); } } subpackageIds = matches; } if (!packageId) { throw new Error(`manifest.toolpkg_id is required: ${manifestPath}`); } if (!mainEntry) { throw new Error(`manifest.main is required: ${manifestPath}`); } return { packageId, mainEntry: mainEntry.replace(/\\/g, "/").replace(/^\/+/, ""), subpackageIds: Array.from(new Set(subpackageIds)) }; } async function find_toolpkg_manifest_in_folder(folderPath: string): Promise { const manifestJson = path_join(folderPath, "manifest.json"); if (await android_path_exists(manifestJson)) { return manifestJson; } const manifestHjson = path_join(folderPath, "manifest.hjson"); if (await android_path_exists(manifestHjson)) { return manifestHjson; } throw new Error(`Missing manifest.json or manifest.hjson in folder: ${folderPath}`); } async function resolve_toolpkg_source(rawSourcePath: string): Promise { const sourcePath = normalize_android_path(rawSourcePath); if (!sourcePath) { throw new Error("Missing required parameter: source_path"); } if (!(await android_path_exists(sourcePath))) { throw new Error(`Source path does not exist: ${sourcePath}`); } const sourceType = await get_android_file_type(sourcePath); let sourceKind: "folder" | "archive" = "folder"; let folderPath = sourcePath; let archivePath: string | undefined; const temporaryPaths: string[] = []; const lowerBaseName = path_basename(sourcePath).toLowerCase(); if (sourceType === "directory") { folderPath = sourcePath; } else if (sourceType === "file" && (lowerBaseName === "manifest.json" || lowerBaseName === "manifest.hjson")) { folderPath = path_dirname(sourcePath); } else if (sourceType === "file" && lowerBaseName.endsWith(".toolpkg")) { sourceKind = "archive"; archivePath = sourcePath; const tempExtractDir = path_join( OPERIT_CLEAN_ON_EXIT_DIR, `operit_editor_toolpkg_extract_${safe_debug_file_stem(lowerBaseName.replace(/\.toolpkg$/i, ""), "toolpkg")}_${Date.now()}` ); await ensure_android_directory(tempExtractDir); await Tools.Files.unzip(sourcePath, tempExtractDir, "android"); folderPath = tempExtractDir; temporaryPaths.push(tempExtractDir); } else { throw new Error( "ToolPkg source must be a folder, manifest.json/manifest.hjson, or an existing .toolpkg file" ); } const manifestPath = await find_toolpkg_manifest_in_folder(folderPath); const manifestText = await read_android_text_file(manifestPath); const manifest = parse_toolpkg_manifest_text(manifestText, manifestPath); const mainPath = path_join(folderPath, manifest.mainEntry); if (!(await android_path_exists(mainPath))) { throw new Error(`manifest.main file does not exist: ${manifestPath} -> ${manifest.mainEntry}`); } return { sourceKind, sourcePath, folderPath, manifestPath, packageId: manifest.packageId, mainEntry: manifest.mainEntry, subpackageIds: manifest.subpackageIds, archivePath, temporaryPaths }; } async function build_toolpkg_archive_from_folder(source: ToolPkgResolvedSource) { const tempBuildDir = path_join( OPERIT_CLEAN_ON_EXIT_DIR, `operit_editor_toolpkg_build_${safe_debug_file_stem(source.packageId, "toolpkg")}_${Date.now()}` ); await ensure_android_directory(tempBuildDir); const archivePath = path_join(tempBuildDir, `${safe_debug_file_stem(source.packageId, "toolpkg")}.toolpkg`); await Tools.Files.zip(source.folderPath, archivePath, "android", false); if (!(await android_path_exists(archivePath))) { throw new Error(`Failed to create ToolPkg archive: ${archivePath}`); } return { archivePath, temporaryPaths: [tempBuildDir] }; } function parse_requested_package_ids(raw?: string): string[] { const input = String(raw ?? "").trim(); if (!input) return []; return Array.from(new Set(input.split(/[\r\n,]+/).map((item) => item.trim()).filter(Boolean))); } async function debug_install_js_package(params?: { source_path?: string; enable_after_install?: boolean | string | number; activate_after_install?: boolean | string | number; }) { const logs: string[] = []; const logStep = (message: string) => { logs.push(message); }; const finish = (payload: { success: boolean; message: string; data?: Record }) => complete({ ...payload, data: { ...(payload.data ?? {}), logs } }); try { const sourcePath = normalize_android_path(params?.source_path); logStep(`Resolved source_path -> ${sourcePath || ""}`); if (!sourcePath) { finish({ success: false, message: "Missing required parameter: source_path" }); return; } const sourceType = await get_android_file_type(sourcePath); logStep(`Source type detected -> ${sourceType}`); if (sourceType !== "file") { finish({ success: false, message: `JS source must be a file: ${sourcePath}` }); return; } if (!sourcePath.toLowerCase().endsWith(".js")) { finish({ success: false, message: `JS debug install only supports .js files: ${sourcePath}` }); return; } const sourceText = await read_android_text_file(sourcePath); const packageInfo = parse_js_package_source(sourceText, sourcePath); logStep(`Parsed package info -> packageName=${packageInfo.packageName}`); const enableAfterInstall = parse_boolean_like(params?.enable_after_install, true); const activateAfterInstall = parse_boolean_like(params?.activate_after_install, true); const shouldEnable = enableAfterInstall || activateAfterInstall; logStep( `Install options -> enableAfterInstall=${String(enableAfterInstall)}, activateAfterInstall=${String( activateAfterInstall )}, shouldEnable=${String(shouldEnable)}` ); await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR); const targetPath = path_join( SANDBOX_EXTERNAL_PACKAGES_DIR, `${safe_debug_file_stem(packageInfo.packageName, "debug_js_package")}.js` ); logStep(`Target install path -> ${targetPath}`); const copied = !same_android_path(sourcePath, targetPath); if (copied) { logStep("Source and target differ; replacing target file before copy."); await delete_android_path_if_exists(targetPath); await Tools.Files.copy(sourcePath, targetPath, false, "android", "android"); logStep("Package file copied to external sandbox directory."); } else { logStep("Source path already matches target path; skipping file copy."); } if (!(await android_path_exists(targetPath))) { finish({ success: false, message: `Installed JS package file is missing after copy: ${targetPath}` }); return; } logStep("Verified installed JS package file exists."); const removedDuplicateFiles = await delete_duplicate_external_js_package_files(packageInfo.packageName, targetPath); logStep(`Duplicate cleanup completed -> removed ${removedDuplicateFiles.length} file(s).`); const refresh = await refresh_sandbox_packages_until( packageInfo.packageName, DEFAULT_SANDBOX_REFRESH_TIMEOUT_MS ); logStep( `Sandbox refresh completed -> found=${String(Boolean(refresh.packageEntry))}, builtIn=${String( refresh.packageEntry?.isBuiltIn ?? false )}` ); if (!refresh.packageEntry) { finish({ success: false, message: `Sandbox package did not appear after refresh: ${packageInfo.packageName}`, data: { package_name: packageInfo.packageName, source_path: sourcePath, target_path: targetPath, refresh_result: refresh.payload } }); return; } if (refresh.packageEntry.isBuiltIn) { finish({ success: false, message: `External JS package '${packageInfo.packageName}' did not take precedence over a built-in package with the same name.`, data: { package: refresh.packageEntry, source_path: sourcePath, target_path: targetPath } }); return; } let enableResult: SandboxPackageUpdateResultData | null = null; if (shouldEnable) { logStep(`Enabling sandbox package -> ${packageInfo.packageName}`); enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(packageInfo.packageName, true); logStep(`Enable result -> ${enableResult.message || ""}`); } else { logStep("Enable step skipped by configuration."); } let activateResult: string | null = null; if (activateAfterInstall) { logStep(`Activating package via use_package -> ${packageInfo.packageName}`); activateResult = await Tools.System.usePackage(packageInfo.packageName); logStep(`Activation result -> ${activateResult || ""}`); } else { logStep("Activation step skipped by configuration."); } finish({ success: true, message: `Debug JS package installed: ${packageInfo.packageName}`, data: { package_name: packageInfo.packageName, source_path: sourcePath, target_path: targetPath, copied, removed_duplicate_files: removedDuplicateFiles, package: refresh.packageEntry, enable_after_install: shouldEnable, activate_after_install: activateAfterInstall, enable_result: enableResult, activate_result: activateResult, refresh_result: refresh.payload } }); } catch (error: unknown) { logStep(`Execution failed -> ${get_error_message(error)}`); finish({ success: false, message: get_error_message(error) }); } } async function debug_install_toolpkg(params?: { source_path?: string; reset_subpackage_states?: boolean | string | number; activate_subpackages?: string; wait_ms?: number | string; }) { const cleanupPaths: string[] = []; const logs: string[] = []; const logStep = (message: string) => { logs.push(message); }; const finish = (payload: { success: boolean; message: string; data?: Record }) => complete({ ...payload, data: { ...(payload.data ?? {}), logs } }); let finalPayload: { success: boolean; message: string; data?: Record } | null = null; try { const resolvedSource = await resolve_toolpkg_source(params?.source_path ?? ""); logStep( `Resolved ToolPkg source -> kind=${resolvedSource.sourceKind}, packageId=${resolvedSource.packageId}, sourcePath=${resolvedSource.sourcePath}` ); cleanupPaths.push(...resolvedSource.temporaryPaths); if (resolvedSource.temporaryPaths.length > 0) { logStep(`Registered temporary paths -> ${resolvedSource.temporaryPaths.join(", ")}`); } let archivePath = resolvedSource.archivePath ?? ""; if (resolvedSource.sourceKind === "folder") { logStep("Source is a folder; building temporary .toolpkg archive."); const builtArchive = await build_toolpkg_archive_from_folder(resolvedSource); archivePath = builtArchive.archivePath; cleanupPaths.push(...builtArchive.temporaryPaths); logStep(`Built archive -> ${archivePath}`); } await ensure_android_directory(SANDBOX_EXTERNAL_PACKAGES_DIR); const targetPath = path_join( SANDBOX_EXTERNAL_PACKAGES_DIR, `${safe_debug_file_stem(resolvedSource.packageId, "toolpkg")}.toolpkg` ); logStep(`Target install path -> ${targetPath}`); if (!same_android_path(archivePath, targetPath)) { logStep("Archive path differs from target; replacing target archive before copy."); await delete_android_path_if_exists(targetPath); await Tools.Files.copy(archivePath, targetPath, false, "android", "android"); logStep("ToolPkg archive copied to external sandbox directory."); } else { logStep("Archive path already matches target path; skipping archive copy."); } if (!(await android_path_exists(targetPath))) { finalPayload = { success: false, message: `Installed ToolPkg archive is missing after copy: ${targetPath}` }; return; } logStep("Verified installed ToolPkg archive exists."); const resetSubpackageStates = parse_boolean_like(params?.reset_subpackage_states, true); const waitMs = parse_integer_like(params?.wait_ms, DEFAULT_TOOLPKG_INSTALL_WAIT_MS); logStep( `Install options -> resetSubpackageStates=${String(resetSubpackageStates)}, waitMs=${String(waitMs)}` ); const broadcastResult = await Tools.System.sendBroadcast({ action: TOOLPKG_DEBUG_INSTALL_ACTION, component: TOOLPKG_DEBUG_INSTALL_COMPONENT, extras: { package_name: resolvedSource.packageId, file_path: targetPath, reset_subpackage_states: resetSubpackageStates } }); logStep(`Debug install broadcast dispatched -> ${broadcastResult.result || ""}`); const refresh = await refresh_sandbox_packages_until(resolvedSource.packageId, waitMs); const relatedLoadErrors = collect_related_package_load_errors( refresh.payload, resolvedSource.packageId, resolvedSource.sourcePath, archivePath, targetPath ); logStep( `Sandbox refresh completed -> found=${String(Boolean(refresh.packageEntry))}, builtIn=${String( refresh.packageEntry?.isBuiltIn ?? false )}` ); if (Object.keys(relatedLoadErrors).length > 0) { logStep(`Related load errors -> ${JSON.stringify(relatedLoadErrors)}`); } if (!refresh.packageEntry) { finalPayload = { success: false, message: `ToolPkg container did not appear after debug install: ${resolvedSource.packageId}`, data: { package_name: resolvedSource.packageId, source_path: resolvedSource.sourcePath, archive_path: targetPath, broadcast_result: broadcastResult, refresh_result: refresh.payload, related_load_errors: relatedLoadErrors } }; return; } if (refresh.packageEntry.isBuiltIn) { finalPayload = { success: false, message: `Debug ToolPkg '${resolvedSource.packageId}' is shadowed by a built-in package with the same name.`, data: { package: refresh.packageEntry, broadcast_result: broadcastResult, refresh_result: refresh.payload, related_load_errors: relatedLoadErrors } }; return; } const requestedSubpackages = parse_requested_package_ids(params?.activate_subpackages); const knownSubpackageKeys = new Set(resolvedSource.subpackageIds.map(normalize_package_key)); const unknownRequestedSubpackages = knownSubpackageKeys.size === 0 ? [] : requestedSubpackages.filter((subpackageId) => !knownSubpackageKeys.has(normalize_package_key(subpackageId))); const activationTargets = requestedSubpackages.filter( (subpackageId) => !unknownRequestedSubpackages.includes(subpackageId) ); logStep( `Subpackage activation plan -> requested=${requestedSubpackages.join(", ") || ""}, targets=${activationTargets.join(", ") || ""}, unknown=${unknownRequestedSubpackages.join(", ") || ""}` ); const subpackageResults: Array<{ subpackage_id: string; enable_result: SandboxPackageUpdateResultData; activate_result: string; }> = []; for (const subpackageId of activationTargets) { logStep(`Enabling subpackage -> ${subpackageId}`); const enableResult = await Tools.SoftwareSettings.setSandboxPackageEnabled(subpackageId, true); logStep(`Subpackage enable result [${subpackageId}] -> ${enableResult.message || ""}`); const activateResult = await Tools.System.usePackage(subpackageId); logStep(`Subpackage activate result [${subpackageId}] -> ${activateResult || ""}`); subpackageResults.push({ subpackage_id: subpackageId, enable_result: enableResult, activate_result: activateResult }); } finalPayload = { success: true, message: `Debug ToolPkg installed: ${resolvedSource.packageId}`, data: { package_name: resolvedSource.packageId, source_kind: resolvedSource.sourceKind, source_path: resolvedSource.sourcePath, manifest_path: resolvedSource.manifestPath, main_entry: resolvedSource.mainEntry, archive_path: targetPath, subpackage_ids: resolvedSource.subpackageIds, reset_subpackage_states: resetSubpackageStates, requested_activate_subpackages: requestedSubpackages, unknown_requested_subpackages: unknownRequestedSubpackages, subpackage_results: subpackageResults, package: refresh.packageEntry, broadcast_result: broadcastResult, refresh_result: refresh.payload, related_load_errors: relatedLoadErrors } }; } catch (error: unknown) { logStep(`Execution failed -> ${get_error_message(error)}`); finalPayload = { success: false, message: get_error_message(error) }; } finally { if (cleanupPaths.length > 0) { logStep(`Cleaning temporary paths -> ${cleanupPaths.join(", ")}`); } await cleanup_android_paths(cleanupPaths); if (cleanupPaths.length > 0) { logStep("Temporary path cleanup completed."); } if (finalPayload) { finish(finalPayload); } } } async function debug_run_sandbox_script(params?: { source_path?: string; source_code?: string; params_json?: string; env_file_path?: string; script_label?: string; wait_ms?: number | string; }) { const logs: string[] = []; const logStep = (message: string) => { logs.push(message); }; const finish = (payload: { success: boolean; message: string; data?: Record }) => complete({ ...payload, data: { ...(payload.data ?? {}), logs } }); let finalPayload: { success: boolean; message: string; data?: Record } | null = null; try { const sourcePath = normalize_android_path(params?.source_path); const sourceCode = typeof params?.source_code === "string" ? params.source_code : ""; const hasInlineCode = sourceCode.trim().length > 0; const waitMs = parse_integer_like(params?.wait_ms, DEFAULT_SANDBOX_SCRIPT_WAIT_MS); const paramsJson = String(params?.params_json ?? "{}").trim() || "{}"; const parsedParams = parse_json_text(paramsJson); const envFilePath = normalize_android_path(params?.env_file_path); const scriptLabel = safe_debug_file_stem(String(params?.script_label ?? "").trim(), "sandbox_script"); logStep( `Resolved input -> sourcePath=${sourcePath || ""}, hasInlineCode=${String(hasInlineCode)}, waitMs=${String(waitMs)}` ); if (!sourcePath && !hasInlineCode) { finalPayload = { success: false, message: "Either source_path or source_code is required." }; return; } if (sourcePath) { const sourceType = await get_android_file_type(sourcePath); logStep(`Source type detected -> ${sourceType}`); if (sourceType !== "file") { finalPayload = { success: false, message: `Sandbox script source must be a file: ${sourcePath}` }; return; } } if (!parsedParams || typeof parsedParams !== "object" || Array.isArray(parsedParams)) { finalPayload = { success: false, message: `params_json must be a JSON object: ${paramsJson}` }; return; } logStep("params_json parsed successfully."); if (envFilePath) { const envType = await get_android_file_type(envFilePath); logStep(`Env file type detected -> ${envType}`); if (envType !== "file") { finalPayload = { success: false, message: `env_file_path must be a file: ${envFilePath}` }; return; } } const executionMode = hasInlineCode ? SANDBOX_SCRIPT_EXECUTION_MODE_CODE : SANDBOX_SCRIPT_EXECUTION_MODE_SCRIPT; const scriptIdentityPath = sourcePath || path_join(SANDBOX_JS_TEMP_DIR, `${scriptLabel}_${Date.now()}.inline.js`); logStep(`Execution mode -> ${executionMode}`); logStep(`Execution target -> ${scriptIdentityPath}`); const executionResult = await Tools.SoftwareSettings.executeSandboxScriptDirect({ source_path: sourcePath || undefined, source_code: hasInlineCode ? sourceCode : undefined, params_json: paramsJson, env_file_path: envFilePath || undefined, script_label: scriptLabel, wait_ms: waitMs }); logStep( `Direct execution tool completed -> success=${String(Boolean(executionResult.success))}, durationMs=${String( executionResult.durationMs ?? "" )}` ); finalPayload = { success: executionResult.success, message: executionResult.success ? "Sandbox script executed successfully." : String(executionResult.error ?? "Sandbox script execution failed."), data: { execution_mode: executionMode, source_path: sourcePath, has_inline_code: hasInlineCode, env_file_path: envFilePath || null, params_json: paramsJson, execution_result: executionResult } }; } catch (error: unknown) { logStep(`Execution failed -> ${get_error_message(error)}`); finalPayload = { success: false, message: get_error_message(error) }; } finally { finish( finalPayload ?? { success: false, message: "Sandbox script execution did not produce a final result." } ); } } async function read_environment_variable(params?: { key?: string }) { try { const key = (params?.key ?? "").trim(); if (!key) { complete({ success: false, message: "Missing required parameter: key" }); return; } const result = await Tools.SoftwareSettings.readEnvironmentVariable(key); complete({ success: true, message: result.exists ? `Environment variable read: ${key}` : `Environment variable not set: ${key}`, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function write_environment_variable(params?: { key?: string; value?: string }) { try { const key = (params?.key ?? "").trim(); if (!key) { complete({ success: false, message: "Missing required parameter: key" }); return; } const value = params?.value ?? ""; const result = await Tools.SoftwareSettings.writeEnvironmentVariable(key, String(value)); complete({ success: true, message: result.cleared ? `Environment variable cleared: ${key}` : `Environment variable written: ${key}`, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function restart_mcp_with_logs(params?: { timeout_ms?: number | string }) { try { const timeoutMs = params?.timeout_ms; const result = await Tools.SoftwareSettings.restartMcpWithLogs(timeoutMs); complete({ success: true, message: result.timedOut ? `MCP restart timed out after ${String(result.elapsedMs)}ms.` : `MCP restart completed: ${String(result.successCount)} success, ${String(result.failedCount)} failed.`, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function get_speech_services_config() { try { const result = await Tools.SoftwareSettings.getSpeechServicesConfig(); const parsed = result; complete({ success: true, message: "Speech services config fetched.", data: { parsed } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } type TtsServiceType = | "SIMPLE_TTS" | "HTTP_TTS" | "OPENAI_WS_TTS" | "SILICONFLOW_TTS" | "MINIMAX_TTS" | "MIMO_TTS" | "DOUBAO_TTS" | "OPENAI_TTS" | "VITS_TTS"; type SttServiceType = | "SHERPA_NCNN" | "OPENAI_STT" | "DEEPGRAM_STT"; type SpeechServicesConfigUpdateParams = { tts_service_type?: TtsServiceType; tts_url_template?: string; tts_api_key?: string; tts_headers?: string | Record; tts_http_method?: string; tts_request_body?: string; tts_content_type?: string; tts_locale?: string; tts_voice_id?: string; tts_model_name?: string; tts_vits_package_path?: string; tts_vits_speaker_id?: string; tts_vits_options?: string | Record; tts_response_pipeline?: string | Array<{ type: string; path?: string; headers?: Record; }>; tts_cleaner_regexs?: string | string[]; tts_speech_rate?: number; tts_pitch?: number; stt_service_type?: SttServiceType; stt_endpoint_url?: string; stt_api_key?: string; stt_model_name?: string; }; async function set_speech_services_config(params?: SpeechServicesConfigUpdateParams) { try { const updates = { ...(params ?? {}) }; const result = await Tools.SoftwareSettings.setSpeechServicesConfig(updates); const parsed = result; complete({ success: true, message: "Speech services config updated.", data: { updates, parsed } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function test_tts_playback(params?: { text?: string; interrupt?: boolean; speech_rate?: number; pitch?: number; }) { try { const text = (params?.text ?? "").trim(); if (!text) { complete({ success: false, message: "Missing required parameter: text" }); return; } const options = { ...(params ?? {}) }; delete options.text; const result = await Tools.SoftwareSettings.testTtsPlayback(text, options); const success = result.playbackTriggered; const detailMessage = result.errorMessage?.trim() || "TTS playback test failed."; complete({ success, message: success ? "TTS playback test triggered." : detailMessage, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function list_model_configs() { try { const result = await Tools.SoftwareSettings.listModelConfigs(); complete({ success: true, message: "Model configs listed.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } type ModelConfigProviderType = | "OPENAI" | "OPENAI_RESPONSES" | "OPENAI_RESPONSES_GENERIC" | "OPENAI_GENERIC" | "ANTHROPIC" | "ANTHROPIC_GENERIC" | "GOOGLE" | "GEMINI_GENERIC" | "BAIDU" | "ALIYUN" | "XUNFEI" | "ZHIPU" | "BAICHUAN" | "MOONSHOT" | "DEEPSEEK" | "MISTRAL" | "SILICONFLOW" | "IFLOW" | "OPENROUTER" | "FOUR_ROUTER" | "NOUS_PORTAL" | "INFINIAI" | "ALIPAY_BAILING" | "DOUBAO" | "NVIDIA" | "LMSTUDIO" | "OLLAMA" | "OPENAI_LOCAL" | "MNN" | "LLAMA_CPP" | "PPINFRA" | "NOVITA" | "OTHER" | (string & {}); async function create_model_config(params?: { name?: string; api_provider_type?: ModelConfigProviderType; api_endpoint?: string; api_key?: string; model_name?: string; max_tokens_enabled?: boolean; max_tokens?: number; temperature_enabled?: boolean; temperature?: number; top_p_enabled?: boolean; top_p?: number; top_k_enabled?: boolean; top_k?: number; presence_penalty_enabled?: boolean; presence_penalty?: number; frequency_penalty_enabled?: boolean; frequency_penalty?: number; repetition_penalty_enabled?: boolean; repetition_penalty?: number; custom_parameters?: string | Record; custom_headers?: string | Record; context_length?: number; max_context_length?: number; enable_max_context_mode?: boolean; summary_token_threshold?: number; enable_summary?: boolean; enable_summary_by_message_count?: boolean; summary_message_count_threshold?: number; enable_direct_image_processing?: boolean; enable_direct_audio_processing?: boolean; enable_direct_video_processing?: boolean; enable_google_search?: boolean; enable_claude_1h_prompt_cache?: boolean; enable_tool_call?: boolean; mnn_forward_type?: number; mnn_thread_count?: number; llama_thread_count?: number; llama_context_size?: number; llama_gpu_layers?: number; request_limit_per_minute?: number; max_concurrent_requests?: number; }) { try { const options = { ...(params ?? {}) }; const result = await Tools.SoftwareSettings.createModelConfig(options); complete({ success: true, message: "Model config created.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function update_model_config(params?: { config_id?: string; name?: string; api_provider_type?: ModelConfigProviderType; api_endpoint?: string; api_key?: string; model_name?: string; max_tokens_enabled?: boolean; max_tokens?: number; temperature_enabled?: boolean; temperature?: number; top_p_enabled?: boolean; top_p?: number; top_k_enabled?: boolean; top_k?: number; presence_penalty_enabled?: boolean; presence_penalty?: number; frequency_penalty_enabled?: boolean; frequency_penalty?: number; repetition_penalty_enabled?: boolean; repetition_penalty?: number; custom_parameters?: string | Record; custom_headers?: string | Record; context_length?: number; max_context_length?: number; enable_max_context_mode?: boolean; summary_token_threshold?: number; enable_summary?: boolean; enable_summary_by_message_count?: boolean; summary_message_count_threshold?: number; enable_direct_image_processing?: boolean; enable_direct_audio_processing?: boolean; enable_direct_video_processing?: boolean; enable_google_search?: boolean; enable_claude_1h_prompt_cache?: boolean; enable_tool_call?: boolean; mnn_forward_type?: number; mnn_thread_count?: number; llama_thread_count?: number; llama_context_size?: number; llama_gpu_layers?: number; request_limit_per_minute?: number; max_concurrent_requests?: number; }) { try { const configId = (params?.config_id ?? "").trim(); if (!configId) { complete({ success: false, message: "Missing required parameter: config_id" }); return; } const { config_id: _ignoredConfigId, ...updates } = params ?? {}; const result = await Tools.SoftwareSettings.updateModelConfig(configId, updates); complete({ success: true, message: "Model config updated.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function delete_model_config(params?: { config_id?: string }) { try { const configId = (params?.config_id ?? "").trim(); if (!configId) { complete({ success: false, message: "Missing required parameter: config_id" }); return; } const result = await Tools.SoftwareSettings.deleteModelConfig(configId); complete({ success: true, message: "Model config deleted.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function list_function_model_configs() { try { const result = await Tools.SoftwareSettings.listFunctionModelConfigs(); complete({ success: true, message: "Function model bindings listed.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function get_function_model_config(params?: { function_type?: string }) { try { const functionType = (params?.function_type ?? "").trim(); if (!functionType) { complete({ success: false, message: "Missing required parameter: function_type" }); return; } const result = await Tools.SoftwareSettings.getFunctionModelConfig(functionType); const config = result?.config ?? {}; const { contextLength: _contextLength, maxContextLength: _maxContextLength, enableMaxContextMode: _enableMaxContextMode, summaryTokenThreshold: _summaryTokenThreshold, enableSummary: _enableSummary, enableSummaryByMessageCount: _enableSummaryByMessageCount, summaryMessageCountThreshold: _summaryMessageCountThreshold, ...configWithoutContextSummary } = config; const filteredResult = { ...result, config: configWithoutContextSummary }; complete({ success: true, message: "Function model config fetched.", data: filteredResult }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } type ContextSummaryConfigSnapshot = { id?: string; name?: string; contextLength?: number; maxContextLength?: number; enableMaxContextMode?: boolean; summaryTokenThreshold?: number; enableSummary?: boolean; enableSummaryByMessageCount?: boolean; summaryMessageCountThreshold?: number; }; type FunctionModelConfigDetailLike = { configId?: string; configName?: string; modelIndex?: number; actualModelIndex?: number; selectedModel?: string; config?: ContextSummaryConfigSnapshot; }; type ModelConfigUpdateResultLike = { config?: ContextSummaryConfigSnapshot; }; function get_error_message(error: unknown): string { return error instanceof Error ? error.message : "Unknown error"; } function pick_context_summary_fields(config?: ContextSummaryConfigSnapshot) { return { context_length: config?.contextLength, max_context_length: config?.maxContextLength, enable_max_context_mode: config?.enableMaxContextMode, summary_token_threshold: config?.summaryTokenThreshold, enable_summary: config?.enableSummary, enable_summary_by_message_count: config?.enableSummaryByMessageCount, summary_message_count_threshold: config?.summaryMessageCountThreshold }; } async function get_context_summary_config(params?: { function_type?: string }) { try { const functionType = (params?.function_type ?? "CHAT").trim().toUpperCase(); if (!functionType) { complete({ success: false, message: "Missing required parameter: function_type" }); return; } const result = (await Tools.SoftwareSettings.getFunctionModelConfig( functionType )) as FunctionModelConfigDetailLike; const config = result.config; if (!config) { complete({ success: false, message: `No bound config found for function_type: ${functionType}`, data: result }); return; } complete({ success: true, message: `Context summary config fetched for ${functionType}.`, data: { function_type: functionType, config_id: result.configId ?? config.id ?? "", config_name: result.configName ?? config.name ?? "", model_index: result.modelIndex ?? 0, actual_model_index: result.actualModelIndex ?? 0, selected_model: result.selectedModel ?? "", context_summary: pick_context_summary_fields(config), raw: result } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function set_context_summary_config(params?: { function_type?: string; context_length?: number; max_context_length?: number; enable_max_context_mode?: boolean; summary_token_threshold?: number; enable_summary?: boolean; enable_summary_by_message_count?: boolean; summary_message_count_threshold?: number; }) { try { const functionType = (params?.function_type ?? "CHAT").trim().toUpperCase(); if (!functionType) { complete({ success: false, message: "Missing required parameter: function_type" }); return; } const binding = (await Tools.SoftwareSettings.getFunctionModelConfig( functionType )) as FunctionModelConfigDetailLike; const configId = (binding.configId ?? binding.config?.id ?? "").trim(); if (!configId) { complete({ success: false, message: `No bound config found for function_type: ${functionType}`, data: binding }); return; } const defaultUpdates = { context_length: 48, max_context_length: 128, enable_max_context_mode: true, summary_token_threshold: 0.7, enable_summary: true, enable_summary_by_message_count: true, summary_message_count_threshold: 16 }; const updates = { ...defaultUpdates, ...(params?.context_length === undefined ? {} : { context_length: params.context_length }), ...(params?.max_context_length === undefined ? {} : { max_context_length: params.max_context_length }), ...(params?.enable_max_context_mode === undefined ? {} : { enable_max_context_mode: params.enable_max_context_mode }), ...(params?.summary_token_threshold === undefined ? {} : { summary_token_threshold: params.summary_token_threshold }), ...(params?.enable_summary === undefined ? {} : { enable_summary: params.enable_summary }), ...(params?.enable_summary_by_message_count === undefined ? {} : { enable_summary_by_message_count: params.enable_summary_by_message_count }), ...(params?.summary_message_count_threshold === undefined ? {} : { summary_message_count_threshold: params.summary_message_count_threshold }) }; const updateResult = (await Tools.SoftwareSettings.updateModelConfig( configId, updates )) as ModelConfigUpdateResultLike; complete({ success: true, message: `Context summary config updated for ${functionType}.`, data: { function_type: functionType, config_id: configId, applied_updates: updates, before: pick_context_summary_fields(binding.config), after: pick_context_summary_fields(updateResult.config), update_result: updateResult } }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function set_function_model_config(params?: { function_type?: string; config_id?: string; model_index?: number | string; }) { try { const functionType = (params?.function_type ?? "").trim(); const configId = (params?.config_id ?? "").trim(); if (!functionType) { complete({ success: false, message: "Missing required parameter: function_type" }); return; } if (!configId) { complete({ success: false, message: "Missing required parameter: config_id" }); return; } const result = await Tools.SoftwareSettings.setFunctionModelConfig( functionType, configId, params?.model_index ); complete({ success: true, message: "Function model binding updated.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function test_model_config_connection(params?: { config_id?: string; model_index?: number | string }) { try { const configId = (params?.config_id ?? "").trim(); if (!configId) { complete({ success: false, message: "Missing required parameter: config_id" }); return; } const result = await Tools.SoftwareSettings.testModelConfigConnection(configId, params?.model_index); const success = !!result?.success; complete({ success, message: success ? "Model config connection tests passed." : "Model config connection tests have failures.", data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } async function ping_mcp(params?: { package_name?: string }) { try { const packageName = (params?.package_name ?? "").trim(); if (!packageName) { complete({ success: false, message: "Missing required parameter: package_name" }); return; } const result = await Tools.System.usePackage(packageName); complete({ success: true, message: `Package probe finished: ${packageName}`, data: result }); } catch (error: unknown) { complete({ success: false, message: get_error_message(error) }); } } return { operit_editor, how_make_skill, list_sandbox_packages, set_sandbox_package_enabled, debug_install_js_package, debug_install_toolpkg, debug_run_sandbox_script, read_environment_variable, write_environment_variable, restart_mcp_with_logs, get_speech_services_config, set_speech_services_config, test_tts_playback, list_model_configs, create_model_config, update_model_config, delete_model_config, list_function_model_configs, get_function_model_config, get_context_summary_config, set_context_summary_config, set_function_model_config, test_model_config_connection, ping_mcp }; })(); exports.operit_editor = operitEditorPackage.operit_editor; exports.how_make_skill = operitEditorPackage.how_make_skill; exports.list_sandbox_packages = operitEditorPackage.list_sandbox_packages; exports.set_sandbox_package_enabled = operitEditorPackage.set_sandbox_package_enabled; exports.debug_install_js_package = operitEditorPackage.debug_install_js_package; exports.debug_install_toolpkg = operitEditorPackage.debug_install_toolpkg; exports.debug_run_sandbox_script = operitEditorPackage.debug_run_sandbox_script; exports.read_environment_variable = operitEditorPackage.read_environment_variable; exports.write_environment_variable = operitEditorPackage.write_environment_variable; exports.restart_mcp_with_logs = operitEditorPackage.restart_mcp_with_logs; exports.get_speech_services_config = operitEditorPackage.get_speech_services_config; exports.set_speech_services_config = operitEditorPackage.set_speech_services_config; exports.test_tts_playback = operitEditorPackage.test_tts_playback; exports.list_model_configs = operitEditorPackage.list_model_configs; exports.create_model_config = operitEditorPackage.create_model_config; exports.update_model_config = operitEditorPackage.update_model_config; exports.delete_model_config = operitEditorPackage.delete_model_config; exports.list_function_model_configs = operitEditorPackage.list_function_model_configs; exports.get_function_model_config = operitEditorPackage.get_function_model_config; exports.get_context_summary_config = operitEditorPackage.get_context_summary_config; exports.set_context_summary_config = operitEditorPackage.set_context_summary_config; exports.set_function_model_config = operitEditorPackage.set_function_model_config; exports.test_model_config_connection = operitEditorPackage.test_model_config_connection; exports.ping_mcp = operitEditorPackage.ping_mcp;