人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载导读本文基于 docs/ai/adr/2026-10-06-home-assistant-integration.md 这份已接受accepted的架构决策记录完整剖析 Airi 如何将 Home Assistant 变成 Stage Tamagotchi Electron 的一等公民服务让用户说出关掉客厅灯模型就能在同一轮对话内读取设备状态并执行控制。文章将带你理解四个候选方案channel 插件、extension、MCP 工具链、主进程服务为何被否决或保留并逐层展开三个类型化工具、IPC 契约、请求边界防护与配置存储的源码级实现读完可直接在 Stage Tamagotchi 中配置并使用该集成。背景与上下文Issue #1491 的诉求Airi 的核心形态是赛博生命灵魂容器模型需要的不只是聊天还有对真实世界的读写能力。Home Assistant 集成的起点是 Issue #1491用户希望说出turn off the living room light然后由 Airi 真正执行——注意这里的难点不在识别意图而在执行后把真实结果读回来。在方案确定之前plugins/airi-plugin-homeassistant只是一个脚手架提交4401b96c92025-12-29为它添加了package.json、tsconfig.json与 tsdown 配置以及一个仅含console.warn(WIP)的 src/index.ts同日出现的airi-plugin-bilibili-laplace也是同一形态。该脚手架依赖proj-airi/server-sdk暗示最初的构想是一个 channel 插件但没有任何代码真正 import 它本次工作将其移除因为集成最终落在了应用内部而不是一个独立进程。决策Home Assistant 是 Electron 主进程的一等公民服务ADR 给出的最终决策是Home Assistant 作为 Stage Tamagotchi Electron 主进程的一个一等公民服务存在通过**类型化工具typed tools**交给渲染进程renderer再挂载给模型。各关注点concern的落点如下关注点位置地址、令牌及所有出站请求apps/stage-tamagotchi/src/main/services/airi/home-assistant/IPC 契约apps/stage-tamagotchi/src/shared/eventa/home-assistant.tsHome Assistant API 客户端及其校验packages/stage-ui/src/libs/home-assistant/client.ts工具定义packages/stage-ui/src/tools/home-assistant.ts工具挂载与设置页apps/stage-tamagotchi/src/renderer/从源码看这一机制沿用两条既有先例computer-use与mcp-servers都是主进程服务 → 把工具交给模型的成熟路径。在 apps/stage-tamagotchi/src/main/index.ts 中可以看到setupComputerUse(context)与setupHomeAssistant(context)并列注册setupMcpManager则通过 build 阶段初始化。为什么不是 channel 插件这是整份 ADR 最关键的推理链。channel 插件在插件开始任何工作之前就回答了工具调用builtIn_emitSparkCommand通过一个类型化信封把命令发给模块但 action 是未类型化的文本见 packages/core-agent/src/agents/spark-command/tools.ts。只要 socket 接受事件工具就立刻返回spark:command sent (id) to ...——模型读不到 Home Assistant 调用的真实结果工具返回的成功是没有人验证过的成功。协议虽然携带 ack 形状spark:emit带命令的eventId但 packages/stage-ui/src/stores/character/orchestrator/store.ts 中的handleSparkEmit是一个空函数注释明确写着Currently no-op。结果只能通过context:update或spark:notify在**另一个轮次turn**才可能到达。channel 插件还要求用户手动启动进程且 channel 服务器不存任何配置插件必须自己持久化设置设置页只能推送一次实时更新。结论对执行并读取结果这个需求channel 插件的协议在架构上就不匹配。为什么不是 extensionproj-airi/plugin-sdk能提供带真实返回值的类型化工具表面上看满足需求但三个事实否定了它extension 无法接收配置。ExtensionSetupContext只携带extension、subscriptions、kits、modules见 packages/plugin-sdk/src/extension/shared.tsmanifest 解析器会拒绝未知字段因此 base URL 和 token没有入口路径。extension 代码直接跑在 Electron 主进程无沙箱、无进程边界而主进程没有uncaughtException处理器——setup之后一次 throw 就可能让整个应用退出。packages/plugin-sdk/src/index.ts 第一行就声明APIs may change without warningWIP 状态且仓库中没有任何示例 extension 注册过工具路径未被证明。为什么不是 MCP 工具链且保留这条路径模型已经拥有类型化工具builtIn_mcpListTools与builtIn_mcpCallTool见 packages/stage-ui/src/tools/mcp.ts主进程从mcp.json运行 stdio MCP 服务器。Home Assistant 官方自带 MCP 服务器用户今天就可以零代码接入。但这条路存在两个结构性问题因此本集成不替代它、而是并存Home Assistant 通过HTTP提供 MCP需要远程传输remote transportMCP 路径把实体解析entity resolution交给模型——客厅灯到底对应哪个entity_id得靠模型自己猜。本集成的存在意义恰恰是给模型一个真正属于 Home Assistant 的工具并让客厅灯的解析发生在插件里而不是发生在 prompt 里。模型调用什么三个类型化工具集成就绪后模型的工具列表中出现三个工具且每个结果都在同一轮内可读工具参数结果home_assistant_list_entitiesdomain可选实体及其状态带数量上限home_assistant_get_stateentity_id状态与属性home_assistant_call_servicedomain、service、entity_id可选、data可选Home Assistant 实际改变的状态三个工具定义在 packages/stage-ui/src/tools/home-assistant.ts 中通过createHomeAssistantTools(client)异步构造tool()需要把 Zod schema 异步转换为 JSON Schema。需要注意data是一个JSON 对象字符串而不是对象。这并非随意设计z.record()会生成propertyNames字段OpenAI 会拒绝它——同样的约束也出现在builtIn_mcpCallTool旁边。工具内通过parseServiceData做严格校验必须是扁平 JSON 对象否则抛出data must be a JSON object, for example {brightness: 200}。实体列表默认上限DEFAULT_ENTITY_LIMIT 60可配置范围 1200。这是有意的上下文保护大型 Home Assistant 实例可能上报数千实体全量塞进上下文只会让模型无法使用。列表返回紧凑摘要行entityId、state、friendly_name并在截断时附加提示引导模型用domain过滤。工具只在地址与令牌都已存储时才挂载。未配置时集成对模型完全不可见因为任何调用都必然失败——给模型一个注定失败的可见工具是设计上要避免的状态。挂载逻辑渲染进程的 store挂载由 apps/stage-tamagotchi/src/renderer/stores/tools/home-assistant.ts 中的useTamagotchiHomeAssistantStore完成工具 ID 带home-assistant:前缀refresh()先按前缀卸载旧工具再按需重挂通过useElectronEventaInvoke(homeAssistantRequest)构造 client 的 transport——渲染进程从不持有 base URL 或 token也不向 Home Assistant 直接开 socket只发送一个 path由主进程决定怎么执行watch(() settings.configured)监听开关 凭证的合成状态任一变化都触发刷新。store 在每个窗口创建refresh是同步动作由看到变化的窗口请求 leader 窗口挂载或卸载特别注意这些工具不带defaultActive: false或requiresExplicitSelection因为共享的activeTools过滤器会丢弃这两者需要让模型可见的工具不能设置它们。配置与凭证主进程独享秘密主进程持有地址与令牌存储在 Electronuser data 目录下的home-assistant-v1.json由createConfig(home-assistant, v1.json, ...)创建见 apps/stage-tamagotchi/src/main/services/airi/home-assistant/index.ts。配置 schema 用 valibot 校验仅含baseUrl与token两个字段。IPC 契约定义在 apps/stage-tamagotchi/src/shared/eventa/home-assistant.ts三个事件homeAssistantGetConfig渲染进程读回地址与是否已有令牌以及tokenPreview掩码永远不会读到令牌本身homeAssistantSetConfig写入配置。token 字段缺省表示保留原令牌update.token ?? current.token所以用户改地址时无需重新粘贴密钥传空字符串才清除homeAssistantRequest渲染进程提交一个 path method仅GET/POST 可选 body主进程执行。令牌掩码toTokenPreview保留最后 4 个字符、其余替换为 8 个圆点如••••••••abcd让用户能分辨存的是哪把钥匙但页面永远拿不到可用密钥。设置页 apps/stage-tamagotchi/src/renderer/pages/settings/modules/home-assistant.vue 提供开关、地址输入、令牌输入typepassword以及保存/测试两个按钮Test按钮先保存再调用testConnection()通过读取实体列表数量来证明连接可用并把实体计数显示在状态行。关于安全边界ADR 明确令牌以明文存储在配置文件中。仓库内其他所有设置文件都以同样方式存密钥且当前代码未使用 ElectronsafeStorage设置页会明确告知用户令牌保存在本机。令牌是否迁移到safeStorage被列为开放问题见后文。模块开关与凭证的分离packages/stage-ui/src/stores/modules/home-assistant.ts 刻意将开关与凭证分开存放enabled属于用户、留在渲染进程本地存储地址与令牌属于主进程所以 store 只镜像一个hasCredentials布尔标志。configured enabled hasCredentials这个合成值驱动工具的挂载。镜像标志让模块卡片无需 IPC 调用即可诚实显示状态同时给 Stage Web 一个可读值——但 Stage Web 从不写它因此集成在那里保持关闭。请求边界双重校验的路径防护渲染进程只发一个 path主进程负责拼接地址与令牌。这让请求处理器成为唯一决定请求去向的地方因此它检查路径两次实现在 apps/stage-tamagotchi/src/main/services/airi/home-assistant/request.ts文本检查assertApiPath必须以/api/开头不含..、不含//、不含空白、不含%。URL 构造后复查resolveRequestUrl拼出完整 URL 后解析后的pathname必须仍然位于 base path 之下。%规则是安全核心URL 解析器会把%2e%2e解码成父级段所以/api/%2e%2e/secrets.yaml能通过纯文本检查实际却解析到/api/之上。而客户端packages/stage-ui/src/libs/home-assistant/client.ts构造的每个路径都只含[a-z0-9_.]字符——出现%就说明调用方不是本应用客户端。resolveRequestUrl还支持base URL 带子路径的反向代理场景https://example.com/ha/api/states会正确解析为/ha/api/states复查逻辑因此把pathname与basePath /api/前缀比较而不是写死根路径。其他防护细节normalizeBaseUrl会 trim 并去掉尾部斜杠拒绝非http:/https:协议file:///etc/passwd、ws://都会被拒拒绝无法解析的 URL单请求超时requestTimeoutMs 15_00015 秒渲染进程工具调用会等待这个响应实例停止应答时必须让调用失败而不是无限挂住整个轮次readBody依次尝试 JSON 解析、纯文本、空响应三种情况toRequestError将失败响应转为模型可操作的错误消息401 明确提示Home Assistant 拒绝了访问令牌其他状态码附带statusText与响应详情。请求流程主进程 handlerindex.ts的完整流程读取配置若baseUrl为空抛错Home Assistant is not configured yet...若token为空抛错Home Assistant has no access token yet...校验 method 只能是GET/POSTresolveRequestUrl构造目标 URL内含两次路径校验fetch携带Authorization: Bearer tokenPOST 时补充Content-Type: application/json与 JSON 序列化 body并挂AbortSignal.timeout(requestTimeoutMs)网络层失败归一化为带 base URL 的错误非 2xx 响应交给toRequestError成功则readBody返回。测试如何锁住这些规则vitest 为每个安全规则提供了可验证依据拒绝file://与ws://协议、拒绝/secrets.yaml与/api非/api/前缀、拒绝..与//遍历、专门用/api/%2e%2e/secrets.yaml验证百分号逃逸被拦截注释点明客户端从不转义任何东西所以 % 意味着入侵者、拒绝可能拆分请求行的空白与\n、验证反向代理子路径保留、验证 token 掩码格式与 401 错误文案。平台范围Stage Tamagotchi 先行当前集成仅限 Stage TamagotchiElectron 桌面端Stage Web 与 Stage Pocket 暂缓浏览器页面无法直接访问http://192.168.x.x上的 Home Assistant 实例除非在 Home Assistant 侧配置cors_allowed_origins且 https 页面访问 http 地址会被**混合内容mixed content**拦截Stage Pocket 需要等待一台可用的 iOS 测试设备。不过架构上预留了扩展性客户端createHomeAssistantClient本身接收一个 transportHomeAssistantTransport类型见 client.ts浏览器直接 fetch、Electron 走主进程 IPC、移动端用原生 HTTP 插件。因此另一个平台只需新增一个 transport而无需再写一个客户端。决策留下的开放选项ADR 明确记录了几条仍未关闭的路径面向 Stage Web 的 channel 插件复用同一客户端但同样无法向模型返回结果extension待 plugin SDK 支持配置、且有可运行的示例之后MCP 路线完全不需要本仓库代码随时可用。非目标Non-goals与后续演进第一阶段的明确边界无实体白名单、无逐实体权限模型无 Stage Web 工作无 channel 插件plugins/下的脚手架已移除若未来 Stage Web 需要再新增一个包无事件订阅第一阶段是按请求读写设备自发状态变化属于后续特性。开放问题call_service是否需要像computer_use那样的requiresExplicitSelection门控共享 composer 不发送tools字段因此该门控只在 Tamagotchi 渲染进程内生效设置页是否需要实体别名映射entity alias map让客厅灯无需模型读取实体列表即可解析令牌是否迁移到 ElectronsafeStorageHome Assistant 工具集是否会扩展到 areas区域与 scenes场景小结一次主进程服务式集成的完整范式回看整份 ADR它示范了一个值得复用的决策模式当模型需要调用真实系统并读取结果时先核对结果能否在同一轮返回channel 插件因此出局、配置能否注入extension 因此出局、是否已有零代码路径MCP 因此保留。最终形态——主进程独享凭证、渲染进程只发 path、类型化工具同轮读写、路径双重校验——既是 Airi 对computer-use/mcp-servers先例的延续也为其他本地设备集成提供了现成的架构模板。如果你在 Stage Tamagotchi 中启用该模块并配置好地址与令牌就能让模型像操作自己的工具一样说出关掉客厅灯并真的看到灯灭。赞分享人工智能AI AgentMCP 服务工具调用GUI 自动化语音数字人交互助手【免费下载链接】airi 自托管、归你拥有的 Grok 风格 AI 伴侣与 waifu / 赛博生命灵魂容器目标是接近 Neuro-sama 的高度支持实时语音聊天、Minecraft 和 Factorio 游玩支持 Web / macOS / Windows。项目地址https://gitcode.com/moeru-ai/airi点击查看免费下载相关推荐OpenJarvis 外部 MCP 服务器集成实战从 Home Assistant 到本地 Stdio 工具OpenJarvis 外部 MCP 服务器集成实战从 Home Assistant 到本地 Stdio 工具 本指南围绕 OpenJarvis 的 tools10个实用技巧教你用awesome-home-assistant实现完美的第三方服务集成10个实用技巧教你用awesome home assistant实现完美的第三方服务集成 Home Assistant是一款开源家庭自动化系统它将本地控制和隐知识库文档教程上一篇Shenzen Smartpet P19 宠物训练项圈 Sub-GHz 信号库解析Flipper Zero 命名规范、通道与强度对照全指南下一篇gpui-kit Command 组件实战指南构建可搜索、可虚拟化的 ⌘K 命令面板创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考