mirror of
https://gitcode.com/JianFeeeee/homeagent-sdk.git
synced 2026-10-31 14:27:30 +00:00
## 核心缺陷:插件往 stdout 打日志 = 污染 JSON-RPC 协议通道
子进程插件与内核用 stdout 传 NDJSON 帧,stderr 才是日志通道。
脚手架模板(及 4 个示例插件)用 `fmt.Printf` 打日志 ⇒ 内核收到非 JSON 文本:
[proc] weather 收到非法 JSON 帧(21 字节): invalid character 'i'
**生产实测 1038 条**(huawei_smarthome 720 / weather 113 / rss 103 / calendar 102)。
后果不只是日志噪音:那一行日志被**丢弃**,插件看起来"没启动"。
修法:
- hmapdev 模板 `fmt.Printf` → `log.Printf`(9 处)
- 示例插件 4 个 14 处:ai_image(2) calendar(5) rss(4) weather(3)
+ 补 "log" import
- 内核侧(主仓)同步让报错可定位:内容摘要 + 可能原因 + 修法
判别法:`package main` = 子进程(协议通道,禁用 stdout 打日志);
普通库包 = 进程内插件(编译进内核,写真实终端,**不是**缺陷)——
`internal/plugins/cli` 的 `fmt.Println` 属后者。
## hmapdev
- `cmd_build.go` +136:平台产物精确匹配(`--target` 是**append** 语义,
原脚本 `ls dist/*.hmap | head -1` 会按字典序抓到 amd64,
**导致 arm64 包里装的是 x86-64 二进制**,且退出码 0、校验和通过)
- `cmd_init.go` +46:脚手架 stdio 纪律
- `templates/proc_main.go.tmpl`:补齐 mgrStop/mgrStart/mgrIsRuntimeStopped
三态能力(插件管理器运行态停启)
- `templates/plg.json.tmpl` 删除(合并进 cmd_init)
## 判据
- `scaffold_stdout_test.go`(3 条):扫模板生成的代码不得写 stdout。
★ 判据自身先假红过一次:**模板的注释里就写了 `fmt.Printf`** ⇒
必须 `stripTemplateComments()` 之后才判
- `example_stdout_test.go`(2 条):扫全部 20 个子进程示例插件,
报错给**文件:行号**;变异咬住并给出准确行号
- `package_examples_test.go`(2 条):**执行真实打包脚本**
(第一版测的是 hmapdev 而不是脚本 ⇒ 变异不咬)
- `cmd_build` / `cmd_init` 新判据
## 其他
- sdk/settings.go +15(三态能力透传)
- `docs/versions.md` +§示例插件协议通道纪律(含生产 1038 条实测表格)
- `skills/homeagent-plugin-dev/SKILL.md` +§协议通道纪律(正误代码对比)
57 lines
2.2 KiB
Go
57 lines
2.2 KiB
Go
package sdk
|
||
|
||
// RawFilesAPI 让插件读写**随包分发的资源**(rawfile)。
|
||
//
|
||
// ## 是什么
|
||
//
|
||
// `hmapdev build` 会把工程根 `rawfile/` 下的全部内容(以及 plg.json
|
||
// `rawfiles` 声明的外部路径)打进 .hmap;内核解包时保留路径,落到:
|
||
//
|
||
// <插件目录>/rawfile/<原相对路径>
|
||
//
|
||
// 本接口就是插件读这些资源的唯一入口。
|
||
//
|
||
// ## 为什么不让插件自己拼路径
|
||
//
|
||
// 插件**不知道自己的安装目录**:内核的 `PluginDir()` 是给插件管理器用的,
|
||
// 不暴露给插件自身。而且"包内资源"与"运行期生成的文件"必须分开 ——
|
||
// 前者只读、随升级替换,后者可写、跨升级保留。混在一个目录里,
|
||
// 升级时会误删运行期数据,或把旧资源留在原地。
|
||
//
|
||
// 所以:
|
||
//
|
||
// RawFiles().Read("main/prompt.txt") → 读**包内**资源(只读)
|
||
// RawFiles().WorkDir() → 拿**可写**工作目录(运行期数据放这)
|
||
//
|
||
// ## 安全边界
|
||
//
|
||
// 所有路径都相对 rawfile/ 根,**必须拒绝路径穿越**(`../`)。
|
||
// 资源文件来自包,包可能被替换 —— 让插件能借它读到 `/etc/shadow`
|
||
// 就是一个真实的提权面。
|
||
type RawFilesAPI interface {
|
||
// Read 读一个包内资源(路径相对 rawfile/ 根)。
|
||
//
|
||
// 例:Read("main/prompt.txt") 读 <插件目录>/rawfile/main/prompt.txt。
|
||
Read(rel string) ([]byte, error)
|
||
|
||
// List 列出某个目录下的条目(相对 rawfile/ 根;"" 表示根)。
|
||
//
|
||
// 返回的名字是**相对 path 的单层名**(不是全路径),目录名带 `/` 后缀。
|
||
List(rel string) ([]string, error)
|
||
|
||
// Exists 报告某个资源是否存在。
|
||
Exists(rel string) bool
|
||
|
||
// Dir 返回包内资源的根目录(只读)。
|
||
//
|
||
// 给需要把整个目录交给外部工具的插件用(如把 node 目录的路径
|
||
// 传给 exec.Command)。**不要往这里写东西** —— 它在包里,随升级替换。
|
||
Dir() string
|
||
|
||
// WorkDir 返回一个**可写**目录,供插件放运行期生成的文件。
|
||
//
|
||
// 与 Dir() 分开的原因:Dir() 里的东西随升级被整体替换,
|
||
// 运行期数据放那里会在下次升级时丢失。
|
||
WorkDir() string
|
||
}
|