# 信号哨兵 Shell 脚本探针技术方案 - 状态:待实施(2026-11-10 定稿) - 日期:2026-11-10 - 需求来源:调研文档 `docs/research/2026-11-10-shell-script-signal-watcher-research.md`(含全部代码证据);调研 §9 四个待拍板项已定案,见本文 §8 - 规模:后端约 600 行(迁移 4 列 + `script-probe.ts` 执行器约 150 行 + service 分流/校验 + 2 个内置工具)+ desktop 约 250 行(表单分流 + 路径选择对话框)+ admin 约 30 行 + 契约/测试;**一次数据库迁移(V189)、零新依赖** - 关联:`backend-ts/src/signal/`、`shared/contracts/src/signal.ts`、`desktop/src/views/settings/SignalWatchersView.vue`、`desktop/src/views/settings/signal-watcher-form.ts`、`admin/src/views/signal/SignalWatcherListView.vue`、`harness/tool/{tool-registry,impl}/`、`USER_GUIDE.md`、`skills/mao-cli/reference/desktop.md` - 勘误(2026-11-10):§2.2 第 2 条与 §8 第 1 条「不做 docker 运行模式的容器内执行」已作废,由 [脚本探针容器内执行技术方案](2026-11-10-script-probe-container-isolation-technical-design.md) 取代。本文其余条款仍有效。 ## 1. 需求背景 信号哨兵(`signal_watcher`,V148)当前只支持单个 HTTP 接口探针:`executeProbe`(node:http)单请求、取响应体、按 HASH / JSON_FIELD / STATUS_CODE 三种模式与上次哈希比对,变化即以 `{diff}` 渲染提示词触发 CLOUD 会话。该模型覆盖不了复杂哨兵场景:多步编排(先登录取 token 再查数据)、多源聚合(对比两个接口的差值)、本地计算(jq / python 处理)、依赖服务器本机状态的检查(磁盘剩余、进程存活、文件变更)。 本批次新增 **Shell 脚本探针**:用户在项目工作区上传脚本,或在会话中让 Agent 编写脚本并配置哨兵;运行逻辑与 HTTP 完全一致——按轮询间隔定期执行脚本,stdout 作为判定值进入既有判定管线,结果变化触发会话任务。 硬约束:**存量 HTTP 哨兵零行为变化**(`probe_type` 默认 `'HTTP'`,所有新列可空);脚本探针复用调度、判定、退避、自动停用、触发、记录、清理七条既有链路,不改调度器与触发面。 ## 2. 需求描述 ### 2.1 目标(全部要做) 1. **数据模型**:V189 迁移——`signal_watcher` 加 `probe_type`(`'HTTP' | 'SCRIPT'`,默认 `'HTTP'`)、`script_path`(项目工作区内相对路径)、`script_timeout_seconds`(NULL=默认 60);`signal_watcher_check` 加 `exit_code`。 2. **脚本执行器 `signal/script-probe.ts`(新建)**:`bash <绝对路径>` 执行、白名单环境变量、硬超时进程组击杀、stdout 1MB 上限、exit code 语义映射为 `ProbeResult`。 3. **service 分流与校验**:`runCheck` / `test` 按 `probeType` 分流;`normalizeInput` 扩展 SCRIPT 分支校验(互斥、路径属主、存在性、大小、超时范围、间隔下限);`AFFECTS_BASELINE` 加 `probeType`/`scriptPath`。 4. **契约三处同步**:`shared/contracts/src/signal.ts` 加 `SignalProbeType`;后端 `types.ts` 加 `SIGNAL_PROBE_TYPES as const satisfies`;desktop 与 admin 各加 `Record` 汉化表。 5. **Agent 内置工具 2 个**:`list_signal_watchers`(列自己名下哨兵)与 `create_signal_watcher`(在会话中创建绑当前 Agent 的哨兵,支持脚本探针),按 `create_scheduled_task` 既有模式注册与接线。 6. **desktop UI**:哨兵表单探针类型二选一(HTTP / Shell 脚本),SCRIPT 态显示脚本路径选择对话框(新建组件)+ 超时秒数,隐藏 URL/方法/请求头/请求体;列表卡显示探针类型与脚本路径;「立即测试」结果展示 exit code 与 stdout 样例。 7. **admin 展示**:`SignalWatcherListView` 加「类型」列,「探测地址」列对脚本哨兵显示脚本路径。 8. **测试与文档**:`script-probe.spec.ts`、service 分流用例、工具用例、toolDisplay/summarizer 同步、Playwright 补充;`USER_GUIDE.md`、`skills/mao-cli/reference/desktop.md`、`CHANGELOG.md`(0.0.494)同步。 ### 2.2 非目标(明确不做) 1. **不做凭据环境变量注入**:`{{MAO_TOKEN}}` / `{{ECP_TOKEN}}` 占位符机制不扩展到脚本探针;脚本 env 只注入 `MAO_WATCHER_ID` / `MAO_WATCHER_NAME`。脚本需要凭据时自行从工作区文件读取或访问免凭据目标。将来要支持须新提案(泄露面需要专门设计:脚本可 echo / 落日志)。 2. **不做 docker 运行模式的容器内执行**:脚本探针与 HTTP 探针同为服务端基础设施行为,统一在后端宿主执行,不随运行环境模式(host / docker)分流。docker 运行模式部署的隔离姿态差异在 DEPLOY.md 声明(§6 R1)。 3. **不做脚本内容静态审查/白名单命令过滤**:无法对任意 bash 做可靠静态分析;风险姿态与云端 Agent shell 对齐(见 §6 R1)。 4. **不做 update / delete 内置工具**:哨兵的修改、启停、删除是 UI 职责(表单已有完整编辑能力);Agent 工具面只开放「列表 + 创建」,避免 Agent 擅自改用户既有哨兵的判定语义。将来要加须新提案。 5. **不做专用 `sentinels/` 目录与展示面**:脚本只允许放在项目工作区(`{workspaceRoot}/{userId}/projects//`)内,文件管理器与上传链路全部复用既有能力。 6. **不做 LOCAL / 桌面端执行**:哨兵触发链路 CLOUD-only(`open-run.service.ts:143-146` 拒绝 LOCAL),脚本探针同为服务端执行。 7. **不做 STATUS_CODE 判定模式对脚本的开放**:脚本侧 exit code 是成败语义(非 0 = 探针失败计连败),不是变化判定的输入;SCRIPT 哨兵只允许 HASH / JSON_FIELD。 8. **不做并发执行上限、CPU/内存限额**:每哨兵单飞(既有 `inFlight` Set)+ 硬超时 + 线程池天然排队已覆盖;系统级 ulimit 属运维范畴,写入 DEPLOY.md 建议,不做产品化配额。 ## 3. 技术选型与决策 ### 3.1 决策表 | 决策点 | 定案 | 理由 / 证据 | |---|---|---| | 执行位置 | 后端宿主进程统一 spawn,不随运行环境模式分流 | HTTP 探针本就在后端进程发起(`probe-client.ts`),运行模式只管会话工具执行(`app-config.ts:297-310`);云端 Agent shell 在 host 模式本就宿主执行(`shell-session-manager.ts:751`),脚本探针不构成新的执行性质 | | 调用方式 | `spawn('bash', ['