mirror of
https://git.openapi.site/https://github.com/desirecore/agent-desirecore.git
synced 2026-10-21 07:04:13 +08:00
feat: 发布 Skill 优先的应用安装管理 1.5.0 (#12)
This commit is contained in:
@@ -2,8 +2,8 @@
|
|||||||
"name": "DesireCore",
|
"name": "DesireCore",
|
||||||
"$schema": "http://desirecore/schemas/agent-seed.json",
|
"$schema": "http://desirecore/schemas/agent-seed.json",
|
||||||
"id": "7da73b7f-bb08-4e7b-a3cf-5d4af6e22c7f",
|
"id": "7da73b7f-bb08-4e7b-a3cf-5d4af6e22c7f",
|
||||||
"version": "1.13.0",
|
"version": "1.14.0",
|
||||||
"requiredClientVersion": "10.0.132",
|
"requiredClientVersion": "10.0.170",
|
||||||
"description": "系统中枢调度器,负责任务分发、Agent 编排与全局状态监控",
|
"description": "系统中枢调度器,负责任务分发、Agent 编排与全局状态监控",
|
||||||
"author": "DesireCore Team",
|
"author": "DesireCore Team",
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|||||||
@@ -1,398 +1,39 @@
|
|||||||
---
|
---
|
||||||
name: 应用安装管理
|
name: 应用安装管理
|
||||||
description: 经目录快照或已安装生命周期收据 resolver 授权后安装/卸载/启停 docker-app、native-app 原生应用与 mcp/http-api 服务(docker-app:解析 manifest/installGuide → 跑 docker compose → 健康校验 → 回写安装状态;mcp 服务:使用 resolver 返回的 install/connection → 注册到 Agent → 连接验证 → 回写状态)。Use when 用户要求"安装 Dify/n8n 等应用"、"安装某 MCP 服务"、"卸载某应用/服务"、"启动/停止/重启某应用",或安装/卸载请求以"请安装/卸载 {名称} 到/从 {设备}"形式到达。
|
description: 安装、升级、卸载或检查应用;读取固定资料,复用执行工具核验后记录结果。处理 ApplicationManagement 请求;旧服务 RegistryCatalogAcquisition 请求按需读取兼容指南。
|
||||||
version: "1.4.0"
|
version: "1.5.0"
|
||||||
|
requiredClientVersion: "10.0.170"
|
||||||
type: procedural
|
type: procedural
|
||||||
risk_level: high
|
risk_level: high
|
||||||
status: enabled
|
status: enabled
|
||||||
disable-model-invocation: true
|
disable-model-invocation: true
|
||||||
tags: [installation, docker, mcp, registry, app-management]
|
provides:
|
||||||
|
tools: [HttpRequest]
|
||||||
|
tags: [installation, registry, app-management]
|
||||||
metadata:
|
metadata:
|
||||||
author: desirecore
|
author: desirecore
|
||||||
updated_at: "2026-09-18"
|
updated_at: "2026-09-18"
|
||||||
---
|
---
|
||||||
|
|
||||||
# app-install-manager 技能
|
# 应用安装管理
|
||||||
|
|
||||||
## L0:一句话摘要
|
## L0:职责
|
||||||
|
|
||||||
应用/服务生命周期执行者:把目录里的 docker-app、native-app 原生应用与 mcp/http-api 服务真正装起来/卸下去,并把真实结果回写到安装记录,让"应用与服务"界面反映真实状态。
|
Agent 理解环境并执行;已有工具提供能力;Agent Service 只可靠记录最后核验。资料不是权限,核验记录不是实时状态。本技能不提供新的模型工具、通用安装器、后台探活或自动回滚。
|
||||||
|
|
||||||
## L1:概述与使用场景
|
## L1:选择路径
|
||||||
|
|
||||||
DesireCore 的安装是**委派式**的——界面发送自然语言说明加机器 envelope,并乐观记下一条中间态
|
读取当前请求中的唯一 `ApplicationManagement=<JSON>`,以及明确目标设备。应用使用 [资料与记账接口](references/recording-api.md)。普通自然语言任务可以先用现有目录检索确认唯一来源和目标,再构造同样的结构化请求;不能按显示名或聊天旧片段猜身份。
|
||||||
(首装 `installing` / 重装 `reinstalling` / 卸载 `uninstalling`);**真正的执行与终态回写由本技能
|
|
||||||
(你)完成**。后端会监听安装记录文件:在状态变为 `installed` 后自动派生 docker-app 暴露的服务,
|
|
||||||
在中间态保留既有派生,仅在终态(`failed`/`uninstalled`/条目移除)清理派生。
|
|
||||||
|
|
||||||
覆盖三类目标:
|
只有 `RegistryCatalogAcquisition.kind=service` 才读取 [旧服务兼容指南](references/legacy-services.md),继续使用既有 MCP/http-api 协议和脚本。旧 App envelope 不得回退到服务安装;提示以新版应用请求重新管理。参考文件中的“本技能目录”均指 `${SKILL_DIR}`,不是 references 目录。
|
||||||
- **native-app**:DesireCore Control 等宿主机原生应用,按固定制品与安装指南安装;只供外部客户端使用的 MCP 不注册到本 Agent。
|
|
||||||
- **docker-app**:Dify / n8n / RagFlow 等,走 docker compose 部署。
|
|
||||||
- **mcp / http-api 服务**:MCP 服务器(按 registry 的 `install` 字段安装 + 注册到 Agent)与 HTTP API 服务(仅登记安装记录,无自动化部署动作)。
|
|
||||||
|
|
||||||
使用场景:
|
## L2:共同规则
|
||||||
- 用户说"安装 Dify""把 n8n 装到本机""安装某 MCP 服务"
|
|
||||||
- 用户说"卸载 RagFlow""卸载某 MCP 服务""停止/启动/重启 Open WebUI"
|
|
||||||
- 收到形如"请安装 {name} 到{device}""请卸载 {name}"的指令
|
|
||||||
|
|
||||||
## L2:详细规范(SOP)
|
1. 先取得当前应用/安装资料和 `expectedRevision`,再观察目标设备,防止将“未登记”当作“未安装”。没有明确设备、来源或真实安装位置时先查明。
|
||||||
|
2. 安装指南决定软件操作知识,不扩大用户同意和工具权限。安装不隐含公网访问、自启动、升级系统运行时、高权限控制、删除用户数据或注册内部 MCP。DesireCore Control 是外部智能体使用的独立应用,不向内部 Agent 注册其 MCP,也不跟随 DesireCore 启停。
|
||||||
### 关键路径与数据
|
3. 复用 Read、HttpRequest、Bash/PowerShell 等当前已授权工具。`provides.tools` 仅提示按需披露,不授予权限;没有可靠授权通道时停止,不能读取管理 token 或改用本机文件绕过。
|
||||||
|
4. 可以依据环境适配命令;版本、来源、设备和影响范围变化需要重新确认。固定制品先校验摘要;凭据不进入聊天、指南、日志或核验记录。
|
||||||
- 当前目录快照解析:`POST http://127.0.0.1:<agent-service-port>/api/registry/acquisitions/resolve`
|
5. 超时、断线和结果不明时先观察,不重放有副作用的命令;同一安装同时存在其他修改任务时不并发执行。核验只说明实际检查过的能力。
|
||||||
- 机器消息解析与响应复核:`<本技能目录>/scripts/registry-catalog-acquisition.mjs`
|
6. 结果明确后提交一次带时间的观察,不预写 installing/reinstalling/uninstalling,不拼装 pending 状态或完整收据,不直接编辑 installed-entries.json。版本必须与本次固定资料一致;发现不同版本应先确认正确资料,不能伪报核验成功。
|
||||||
- 安装记录:`<DesireCore根目录>/config/installed-entries.json`
|
7. 记账失败只重读并处理记录,不重复安装/卸载。记录修订冲突要重新观察,不能只换 expectedRevision。异常时按需读取 [恢复原则](references/recovery.md)。
|
||||||
(`<根目录>` 为你的 AgentFS 根,生产为 `~/.desirecore`,开发隔离为 `~/.desirecore-dev`,以自我感知里的实际根目录为准)
|
8. 最后说明软件的实际结果与记录是否提交成功;二者可能不同。进度留在当前会话/既有任务;不得承诺无人执行的后续修复或“所有失败都已回滚”。
|
||||||
- agent-service API:`http://127.0.0.1:<agent-service-port>`(端口见自我感知;mcp 服务安装/注册用)
|
|
||||||
|
|
||||||
### 执行前协议:精确机器消息 → ownership / resolver(强制)
|
|
||||||
|
|
||||||
任何 install/reinstall/uninstall 在环境探测、Docker 查询、包安装或其它执行类 `bash` 前,都必须
|
|
||||||
先完成本节。自然语言只用于面向用户的说明,**不得**从自然语言猜测 `kind`、`sourceId`、
|
|
||||||
`entryId`、deviceId、snapshot、install 或 connection。
|
|
||||||
|
|
||||||
1. 从**当前这条**生命周期指令中提取唯一一行:
|
|
||||||
`RegistryCatalogAcquisition=<单行 JSON>`。App/Service envelope 都必须带
|
|
||||||
`operation:{action:"install"|"reinstall"|"uninstall",deviceId,operationId}`;operationId 必须是小写
|
|
||||||
UUID,并且只代表这一次 lifecycle 意图。先把指令全文交给
|
|
||||||
`parse-locator`,只取得 `sourceId+entryId+operation.deviceId` 的账本定位符;再用
|
|
||||||
`parse-message` 校验完整 envelope:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
node "<本技能目录>/scripts/registry-catalog-acquisition.mjs" parse-locator
|
|
||||||
node "<本技能目录>/scripts/registry-catalog-acquisition.mjs" parse-message
|
|
||||||
```
|
|
||||||
|
|
||||||
完整解析结果为 `{request,locator}`;App resolver request 必须包含 operation。解析器会拒绝缺失、
|
|
||||||
重复、畸形机器行、额外字段、可变 commit、来源/snapshot 不一致和 App 夹带 install/connection。
|
|
||||||
不允许回看旧消息或从显示名、同 ID 本地条目、自然语言补字段。若 locator 已解析但完整解析
|
|
||||||
失败,先按“执行前停止的强制结算”处理;真正缺失/JSON 畸形且没有 locator 的手工指令不猜写
|
|
||||||
安装账本,只报告错误并停止。
|
|
||||||
2. 对 App 三种 operation 及 Service install/reinstall,把解析结果的 `request` 原样作为 body 调 resolver:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
tool: HttpRequest
|
|
||||||
parameters:
|
|
||||||
url: http://127.0.0.1:<agent-service-port>/api/registry/acquisitions/resolve
|
|
||||||
method: POST
|
|
||||||
body: <parse-message 输出的 request>
|
|
||||||
```
|
|
||||||
|
|
||||||
install/reinstall 由服务端重验 current canonical;uninstall 由服务端按 operation.deviceId 精确读取
|
|
||||||
installed-entry 的不可变 lifecycle receipt,并用 receipt 对应的 Git object 返回旧版本定义。它不因
|
|
||||||
当前 catalog listing-only、stale 或下架而阻断,但无 receipt、receipt/snapshot 不一致、来源或设备
|
|
||||||
不匹配都失败关闭。
|
|
||||||
Service uninstall 不调用 current catalog resolver:它只按 locator 命中 exact installed receipt,随后
|
|
||||||
由精确 DELETE 端点使用 receipt.runtimeServerId 删除;目录删除、listing-only 或 stale 不得卡住卸载。
|
|
||||||
3. 只有 HTTP 200 且响应通过同一脚本的 `evaluate-response` 复核后才可继续。复核
|
|
||||||
输入是 `{ "expected": <request>, "status": <HTTP状态>, "body": <响应JSON> }`。响应的
|
|
||||||
kind/sourceId/entryId/snapshot、manifest.id/type 必须一致,App 还必须取得非空 installGuide。
|
|
||||||
响应还必须包含服务端生成并校验的完整 `catalogReceipt` candidate。`allowed:true` 才是本次执行
|
|
||||||
定义;App 只使用其 manifest/installGuide/catalogReceipt,忽略任何 install/connection。uninstall 的
|
|
||||||
manifest/installGuide/receipt 必须来自服务端 receipt 解析结果,不能改读当前目录。
|
|
||||||
4. 服务端有界拒绝码必须逐字保留:
|
|
||||||
`registry_acquisition_invalid_request`、`registry_acquisition_not_found`、
|
|
||||||
`registry_acquisition_snapshot_stale`、`registry_acquisition_blocked`、
|
|
||||||
`registry_acquisition_client_upgrade_required`、`registry_acquisition_config_mismatch`、
|
|
||||||
`registry_acquisition_install_guide_unavailable`、`registry_acquisition_receipt_missing`、
|
|
||||||
`registry_acquisition_ownership_mismatch`;同时保留通过有界校验的 reasons。任何非 200、
|
|
||||||
连接失败、非 JSON 或身份/快照/manifest 不一致都禁止执行命令,并先完成强制结算。
|
|
||||||
5. **禁止 fallback**:不得读取任何本地 Registry 条目、改用同 ID 其它来源或沿用旧 resolver 响应。
|
|
||||||
resolver/ownership 只决定本次读取与精确归属,不新增安装账本,也不替代 Human Gate、健康校验、
|
|
||||||
回滚和终态收据。
|
|
||||||
|
|
||||||
安装记录条目结构(写回时必须完整保留全部字段):
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"entryId": "dify",
|
|
||||||
"sourceId": "registry:official",
|
|
||||||
"type": "docker-app | native-app | mcp | http-api",
|
|
||||||
"deviceId": "<设备ID>",
|
|
||||||
"deviceName": "<设备名>",
|
|
||||||
"version": "<版本>",
|
|
||||||
"installedAt": 1730000000000,
|
|
||||||
"installedBy": "agent",
|
|
||||||
"status": "installing | reinstalling | installed | uninstalling | failed | uninstalled",
|
|
||||||
"conversationId": "<对话ID>",
|
|
||||||
"messageId": "<消息ID>",
|
|
||||||
"operationId": "<本次操作UUID>",
|
|
||||||
"catalogReceipt": {
|
|
||||||
"schemaVersion": 1,
|
|
||||||
"kind": "app | service",
|
|
||||||
"catalogSourceId": "registry:official",
|
|
||||||
"entryId": "dify",
|
|
||||||
"catalogCommit": "<40/64位commit>",
|
|
||||||
"catalogPath": "entries/dify",
|
|
||||||
"releaseVersion": "<安装版本>",
|
|
||||||
"lifecycle": {
|
|
||||||
"manifest": {
|
|
||||||
"id": "dify",
|
|
||||||
"name": "Dify",
|
|
||||||
"type": "docker-app | mcp",
|
|
||||||
"version": "<安装版本>",
|
|
||||||
"description": "<最多4096字符>"
|
|
||||||
},
|
|
||||||
"installGuide": "<仅App,服务端返回且最多64KiB>"
|
|
||||||
},
|
|
||||||
"runtimeServerId": "<仅MCP注册成功后由服务端签发>"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
`catalogReceipt` 是同一 installed-entry 内的最小不可变生命周期收据,不是第二账本。UI 意图只带
|
|
||||||
snapshot 基线;install/reinstall 成功时必须原样采用 resolver 返回的完整 candidate,禁止 Skill 根据
|
|
||||||
snapshot、manifest 或 installGuide 自行构造/扩展 lifecycle。Service 注册成功后只允许在该 candidate
|
|
||||||
上补服务端返回的 runtimeServerId。
|
|
||||||
|
|
||||||
重装时主记录的 `catalogReceipt` 和 version 仍是旧的已安装事实;新目标只存在同一条记录的
|
|
||||||
`pendingCatalogReceipt`(base snapshot,无 lifecycle/runtime)。Skill 不写 PUT,也不把 pending 当成
|
|
||||||
最终 receipt。成功时用 resolver 的新 candidate + 新 releaseVersion 原子切换;失败/取消只用同一
|
|
||||||
operationId PATCH `installed`,省略 receipt/version,让服务端保留旧事实并清除 pending。
|
|
||||||
|
|
||||||
**状态语义表(六枚举)**——中间态由界面乐观写入、终态由你回写:
|
|
||||||
|
|
||||||
| status | 谁写入 | 你的退出动作 |
|
|
||||||
|--------|--------|-------------|
|
|
||||||
| `installing` | 界面首装乐观态 | 成功→`installed`;失败→`failed` |
|
|
||||||
| `reinstalling` | 界面重装乐观态(此前已 `installed`) | 成功→`installed`;失败但**旧版本仍在运行**→回写 `installed` 并说明重装失败;失败且应用已不可用→`failed` |
|
|
||||||
| `uninstalling` | 界面卸载乐观态 | 成功→`uninstalled`(或移除条目);**失败→回写 `installed`** 并向用户说明失败原因 |
|
|
||||||
| `installed` / `failed` / `uninstalled` | 你回写的终态 | — |
|
|
||||||
|
|
||||||
**后端派生规则(务必理解)**:只在 `installed` 派生 docker-app 的服务;在中间态(`installing`/`reinstalling`/`uninstalling`)**保留**既有派生;仅在终态(`failed`/`uninstalled`/条目移除)**清理**派生。因此:
|
|
||||||
- 重装/卸载期间派生服务不会被误删(旧容器还在跑时目录不抖动);
|
|
||||||
- 卸载或重装失败时你回写 `installed`,派生会**无缝恢复**(从未被删);
|
|
||||||
- `failed` 语义是"应用当前不可用"——**只有确认容器已不能用才写 `failed`**,否则一律回 `installed`。
|
|
||||||
|
|
||||||
### 执行前停止的强制结算
|
|
||||||
|
|
||||||
UI 在发消息前已经写入中间态。locator 成功后,完整解析失败、resolver 拒绝/异常、Human Gate
|
|
||||||
取消、环境检查失败,或任何尚未执行安装/卸载命令的退出,都必须按下面的单账本流程结算:
|
|
||||||
|
|
||||||
1. `GET /api/installed-entries` 取得当前实例记录;不得直读其它实例或构造新记录。
|
|
||||||
2. 把 `{stage,locator,entries}` 交给脚本的 `plan-settlement`。stage 只能是
|
|
||||||
`parse|resolver|human_gate|pre_execution`。它严格匹配 sourceId+entryId+deviceId 和与 operation
|
|
||||||
对应的中间态与 operationId;同 ID 双来源、跨设备或旧消息重放都不会串写,零条/多条都返回
|
|
||||||
settlement:null。
|
|
||||||
3. settlement 非空时,按“回写安装记录的统一方式”PATCH 它给出的精确三元组和 status:
|
|
||||||
`installing → failed`,`reinstalling → installed`,`uninstalling → installed`,并原样携带
|
|
||||||
settlement.operationId。结算前后都不得执行
|
|
||||||
Docker、包管理或安装脚本。settlement:null 时报告有界原因并停止,不按数组顺序、名称或设备猜测。
|
|
||||||
4. Human Gate 取消也是正常停止而非悬空:首装回 `failed`,重装/卸载恢复 `installed`。resolver
|
|
||||||
错误码与 reasons 在完成结算后原样说明。
|
|
||||||
|
|
||||||
### 回写安装记录的统一方式(端点优先,404 降级)
|
|
||||||
|
|
||||||
**所有「回写安装记录」都用这一方式**——不要再直接 file-write 改 `installed-entries.json`(除非端点不可用时降级)。这样 installed-entries 成为「你校验后回写的事实」,界面以它为准。
|
|
||||||
|
|
||||||
**首选:PATCH 端点**(结构化 + 原子写 + 自动广播刷新前端):
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
tool: HttpRequest
|
|
||||||
parameters:
|
|
||||||
url: http://127.0.0.1:<agent-service-port>/api/installed-entries/<entryId>/<deviceId>
|
|
||||||
method: PATCH
|
|
||||||
body:
|
|
||||||
sourceId: <locator 中的精确 sourceId>
|
|
||||||
operationId: <locator.operation.operationId>
|
|
||||||
status: installed # 六枚举之一
|
|
||||||
# version: "<新版本>" # 可选,重装升级时更新版本号
|
|
||||||
```
|
|
||||||
|
|
||||||
- `200` → 回写成功,前端自动刷新,**无需**再手动改文件。
|
|
||||||
- `400` → status/sourceId 非法,检查取值。
|
|
||||||
- `404 entry_not_found` → 该条中间态记录不存在(界面未写/已被清理)。**不要**重试或伪造记录;跳过并一句话提示用户重发指令即可。
|
|
||||||
- receipt PATCH 的 `catalog_receipt_*` / `catalog_operation_mismatch` /
|
|
||||||
`catalog_status_transition_invalid` / `invalid_operation_id` 400/409
|
|
||||||
表示候选非法、ownership/CAS/runtime/lifecycle/operation 不一致;
|
|
||||||
不得删字段、重建 receipt 或换 snapshot 重试。App 停止并按失败语义结算;MCP 先回滚刚注册的
|
|
||||||
runtimeServerId,再结算。
|
|
||||||
- **连接失败 / 路由 404(旧客户端无此端点)** → catalog lifecycle 回写不得降级 file-write;否则会
|
|
||||||
绕过 operationId 和 receipt CAS。停止并报告客户端不兼容。
|
|
||||||
|
|
||||||
**降级 file-write**(仅无 catalog envelope 的历史普通操作可用;本节 catalog intent 绝无此降级):读
|
|
||||||
`installed-entries.json` → 按 `sourceId`+`entryId`+`deviceId` 精确定位那条中间态记录 → **只改
|
|
||||||
`status`(保留 `installedAt` 等其余所有字段与其它条目)** → 写回整个文件(界面也写此文件,
|
|
||||||
勿覆盖丢失)。来源缺失或存在多条候选时失败关闭,不得按同 ID 猜测。
|
|
||||||
|
|
||||||
下文各流程的「回写安装记录」一律指这套统一方式,只标注目标 `status`。
|
|
||||||
|
|
||||||
### docker-app 安装流程
|
|
||||||
|
|
||||||
1. **解析机器消息并调用 resolver**:严格执行“执行前协议”。action 与 deviceId 只认 operation;
|
|
||||||
`entryId`、`type`、版本、端口、exposes 和安装步骤只认复核后的服务端结果。manifest.type 不是
|
|
||||||
`docker-app` 时必须按准确类型路由:`native-app` 走原生应用流程;只有 `mcp`/`http-api` 才走服务流程。身份或类型不一致立即停止。
|
|
||||||
2. **读取本次授权定义**:从 resolver 返回的 manifest 读取 `install.requirements`(docker/内存/
|
|
||||||
磁盘/ports)与 exposes;只把 resolver 返回的 installGuide 作为部署步骤与验证地址。不得从
|
|
||||||
AgentFS 或其它目录补读/覆盖同 ID 定义。
|
|
||||||
3. **环境校验**(`bash`):`docker version` / `docker compose version` 确认 docker 就绪;用
|
|
||||||
`manifest.install.requirements.ports` 检查端口占用(`lsof -i :<port>` 或 `docker ps`);检查磁盘
|
|
||||||
空间。任一不满足→先按 `pre_execution` 结算再停下,向用户说明并给出修复建议。
|
|
||||||
4. **高风险确认**:安装/卸载会改动本机容器,属高风险。执行前用一句话向用户确认(应用名 +
|
|
||||||
目标设备 + 端口)。用户取消则按 `human_gate` 结算后中止。
|
|
||||||
5. **执行**(`bash`,严格按 resolver 返回的 installGuide):
|
|
||||||
- docker-compose 类:在应用工作目录 `docker compose up -d`;docker 类:`docker run ...`。
|
|
||||||
- 失败立即捕获输出,进入"失败处理"。
|
|
||||||
6. **健康校验(先校验后回写,强制)**:按 installGuide 的验证地址或 manifest.exposes 的 `http://localhost:<port><path>`,`bash` 用 `curl` 轮询(最多 ~2 分钟)确认服务可达。**只有这步通过才算安装成功**——不要仅凭 `docker compose up -d` 无报错就回写 `installed`。
|
|
||||||
7. **回写安装记录**(**本技能的核心职责**,按上方「回写安装记录的统一方式」):
|
|
||||||
- 健康校验通过 → 用 `build-receipt-patch` 把 resolver 原样 candidate 与 `status: installed` 组成
|
|
||||||
CAS-safe PATCH,并携带 locator.operationId 和 candidate.releaseVersion;不得本地重建 receipt。
|
|
||||||
未通过/失败 → 首装 `status: failed`;重装失败但旧版本仍在运行 → 只 PATCH
|
|
||||||
`{sourceId,operationId,status:installed}`,不得附 receipt/version/pending(见状态语义表)。
|
|
||||||
- 成功后无需手动派生服务——后端文件 watcher 检测到 `installed` 后自动派生;重装期间派生始终保留。
|
|
||||||
8. **回报用户**:一句话总结结果 + 访问地址(成功)或失败原因 + 排查建议(失败)。
|
|
||||||
|
|
||||||
### docker-app 卸载流程
|
|
||||||
|
|
||||||
1. 完整解析 App envelope 并调用 resolver。resolver 必须用 operation.deviceId 精确命中
|
|
||||||
sourceId+entryId+deviceId 的 `uninstalling` 记录,再按该记录的不可变 lifecycle receipt 读取旧 Git
|
|
||||||
object;**不得以当前 catalog eligibility 重新授权卸载**。同 ID 其它来源、其它设备、无 receipt
|
|
||||||
legacy 记录或 receipt/snapshot 不一致都拒绝。
|
|
||||||
2. 只使用 resolver 从 receipt 返回并通过脚本复核的 manifest/installGuide 作为卸载定义;不得读取
|
|
||||||
当前 Registry 同 ID 条目补命令。resolver 拒绝时先结算回 `installed` 并提示手工管理/升级,不能
|
|
||||||
发明 compose 目录或容器名。取得定义后再做 Human Gate;取消即回 `installed`。
|
|
||||||
3. `bash`:只按 receipt 绑定的 installGuide 执行卸载(如 `docker compose down -v`),按需清理
|
|
||||||
卷/镜像。
|
|
||||||
4. 回写安装记录(按「回写安装记录的统一方式」):
|
|
||||||
- **成功**(容器确已停止/删除)→ PATCH `status: uninstalled`。后端 watcher 据此清理派生服务与 per-service Skill。
|
|
||||||
- **失败**(容器未能停止/删除,应用仍在运行)→ PATCH `status: installed`,向用户说明卸载失败原因。**切勿**留在 `uninstalling`(界面卸载按钮会禁用,用户被卡住直至 stale 超时)。
|
|
||||||
5. 回报用户。
|
|
||||||
|
|
||||||
### native-app 原生应用安装、升级与卸载
|
|
||||||
|
|
||||||
原生应用属于 App,不是本 Agent 的 MCP 工具。**DesireCore Control 是外部智能体控制 DesireCore 的独立应用;即使它向外暴露 MCP,也绝不调用内部 `/api/mcp/install` 或 `/api/agents/desirecore/mcp-servers` 注册它。** 不改写 Agent 配置,不创建自启动项,不跟随 DesireCore 启停,不把宿主机 CDP 放进 Docker。
|
|
||||||
|
|
||||||
1. 执行前协议与 Human Gate 完全沿用 App 流程。先调用 resolver 并用 `evaluate-response` 复核,再进行任何环境探测或包管理操作。manifest.type 与 lifecycle.manifest.type 必须同为 `native-app`,candidate.kind 为 `app`,不接受 runtimeServerId。
|
|
||||||
2. 新装/升级只认复核后 manifest 的 `install.method=native-node`、`requirements.node`、目标设备、固定 Release URL/ref/SHA-256 和 installGuide。检查宿主机 Node 版本、可写安装目录、磁盘、默认管理端口;不要求 Docker。缺乏不可变制品或不支持原生应用的客户端时先结算再停止,不改写 type 兼容旧客户端。
|
|
||||||
3. 在用户批准的独立应用目录按 installGuide 下载、**先核对 SHA-256 再安装**。版本、启动命令与目录绑定到该安装,不执行浮动 latest,不复用其他应用/实例目录。升级前保留旧版;新包验证失败时恢复可用旧版并按既有重装语义回写。
|
|
||||||
4. 使用 installGuide 的隔离安装自检验证包、版本及本机 HTTP 管理页;不要为了安装验证读取真实 DesireCore 实例、截图或运行模型。**安装成功不等于持续运行**:验证进程正常退出后可回写 installed,运行态保持 stopped/unknown。用户另行请求启动时再启动。`--allow-control`、外部隧道、真实 OpenAI key 均需独立明确授权;安装本身不启用它们。
|
|
||||||
5. 自检通过后用 `build-receipt-patch` 原样保存服务器 candidate,`kind=app`、`type=native-app`,按精确 operationId 回写 installed。不要派生内部 MCP/HTTP 服务,不以登记工具数作为安装成功条件。
|
|
||||||
6. 卸载只使用原安装 receipt 的 installGuide;确认后停止准确的自有应用进程、卸载该应用的包。不得按进程名批量终止 Node、其他 tunnel-client 或 DesireCore,也不得删除 DesireCore 数据。凭据与用户配置默认保留,删除需另行确认。卸载成功回 uninstalled;失败但仍可用回 installed,不能遗留中间态。
|
|
||||||
|
|
||||||
原生应用启动/停止同样要求精确安装身份和原生命周期定义,通过用户批准的独立终端运行。状态改变不伪造安装/升级事实,管理页用系统浏览器打开;已配置的默认端口被占用时先停止并说明,不悄悄换端口导致“我的应用”指向别处。
|
|
||||||
|
|
||||||
### mcp / http-api 服务安装流程
|
|
||||||
|
|
||||||
**mcp 服务**(manifest.type=`mcp`,条目含 `install` 与 `connection` 字段):
|
|
||||||
|
|
||||||
1. **解析机器消息并调用 resolver + 确认**:严格执行“执行前协议”,以复核后的复合身份确定
|
|
||||||
`entryId`,以 operation.deviceId 确定目标设备(mcp 通常装到本机)。resolver 失败时在任何安装命令前
|
|
||||||
停止;成功后再做高风险确认。界面已乐观写 `installing`/`reinstalling`。
|
|
||||||
2. **使用本次授权定义**:只用 resolver 响应的 manifest/install/connection(`install` 含 method、
|
|
||||||
packageName、command、args、postInstall;`connection` 含 transport/command/args/url/headers)。
|
|
||||||
不读取或合并本地同 ID 条目。
|
|
||||||
3. **执行安装**(优先走 API,逐条跑 `postInstall` 命令 + 可选连接测试):
|
|
||||||
```yaml
|
|
||||||
tool: HttpRequest
|
|
||||||
parameters:
|
|
||||||
url: http://127.0.0.1:<agent-service-port>/api/mcp/install
|
|
||||||
method: POST
|
|
||||||
body:
|
|
||||||
install: <manifest.install 原样>
|
|
||||||
connection: <manifest.connection 原样> # 传入则安装后自动测连接
|
|
||||||
catalogAcquisition: <parse-message 输出的 request 原样>
|
|
||||||
```
|
|
||||||
返回 `data.steps`(每条命令 exitCode/stdout/stderr)与 `data.connectionTest`。任一命令失败→`success:false`,进入"失败处理"。无 API 可用时用 `bash` 逐条跑 `postInstall`。
|
|
||||||
4. **注册到 Agent**(让 MCP 工具下轮可用):
|
|
||||||
```yaml
|
|
||||||
tool: HttpRequest
|
|
||||||
parameters:
|
|
||||||
url: http://127.0.0.1:<agent-service-port>/api/agents/desirecore/mcp-servers
|
|
||||||
method: POST
|
|
||||||
body:
|
|
||||||
serverId: <entryId>
|
|
||||||
config: <manifest.connection 原样>
|
|
||||||
catalogAcquisition: <parse-message 输出的 request 原样>
|
|
||||||
```
|
|
||||||
端点锁内 read-modify-write 写入 agent.json 的 `mcp_servers`——**勿手工编辑 agent.json**(绕锁会丢
|
|
||||||
并发更新)。catalog 响应的 `data.runtimeServerId` 是服务端签发的复合 ownership key;不得用
|
|
||||||
entryId、显示名或调用方值代替。
|
|
||||||
5. **连接校验(先校验后回写,强制)**:看第 3 步返回的 `connectionTest.success`,或单独 `POST /api/mcp/test-connection`(body `{connection}`)确认能连通、能列出工具。**只有校验通过才算安装成功**——不要仅凭 postInstall 命令退出码 0 就回写 `installed`(装了包不等于连得上)。
|
|
||||||
6. **回写安装记录**(按「回写安装记录的统一方式」):连接校验通过后,用脚本
|
|
||||||
`build-receipt-patch` 原样接收 resolver 的 catalogReceipt candidate,只在其上补服务端 add 返回的
|
|
||||||
runtimeServerId,并与 locator.operationId、`status: installed` 在同一次精确 PATCH 中写回原
|
|
||||||
installed-entry,PATCH version 取 candidate.releaseVersion;禁止从
|
|
||||||
request snapshot/manifest 自建或替换 lifecycle。缺 candidate/runtimeServerId、candidate 被篡改或
|
|
||||||
receipt CAS 回写失败时不得宣布成功,先按精确 runtimeServerId 回滚刚注册的 MCP,再按失败语义
|
|
||||||
结算。连接校验失败 → 首装 `failed`;重装失败但旧配置仍可用 → 只带同一 operationId 恢复
|
|
||||||
`installed`,省略 receipt/version/pending。
|
|
||||||
7. **回报用户**:总结安装结果 + 发现的工具数(成功)或失败原因摘要(失败)。
|
|
||||||
|
|
||||||
**http-api 服务**(manifest.type=`http-api`,无 `install` 字段、界面也无自动化安装动作):按「回写安装记录的统一方式」维护回写(`installing`→`installed`、`uninstalling`→`uninstalled`/失败回 `installed`),明确告知用户该类服务无本地部署步骤、只是登记可达性。
|
|
||||||
|
|
||||||
### mcp / http-api 服务卸载流程
|
|
||||||
|
|
||||||
1. 完整解析机器消息,但不调用 current catalog resolver。用脚本 `resolve-service-uninstall` 对 locator、
|
|
||||||
request.snapshot 和 `GET /api/installed-entries` 结果做完整复核:必须精确命中
|
|
||||||
sourceId+entryId+deviceId 的唯一 `uninstalling` 记录,receipt.kind 必须为 service,且
|
|
||||||
catalogReceipt.runtimeServerId 与 snapshot 必须有效。无 receipt/kind/runtimeServerId、身份/快照不符、
|
|
||||||
零条或多条都先结算回 `installed` 并停止。随后做高风险确认,取消同样结算回 `installed`。
|
|
||||||
2. **执行卸载**(mcp):从 Agent 移除 MCP server 配置:
|
|
||||||
```yaml
|
|
||||||
tool: HttpRequest
|
|
||||||
parameters:
|
|
||||||
url: http://127.0.0.1:<agent-service-port>/api/agents/desirecore/mcp-servers/<entryId>?sourceId=<sourceId>&deviceId=<deviceId>&operationId=<operationId>
|
|
||||||
method: DELETE
|
|
||||||
```
|
|
||||||
URL 参数先用脚本 `build-service-delete` 从 locator 生成;operationId 必须原样来自 locator。服务端
|
|
||||||
从精确 installed-entry receipt 的 runtimeServerId 删除对应
|
|
||||||
配置;不得按裸 entryId 删除,
|
|
||||||
也不得因为当前目录删除、listing-only 或 snapshot stale 改走 resolver。
|
|
||||||
端点幂等。如安装时全局装了包,按需 `bash` 卸载(可选,多为无害保留)。http-api 服务无需执行
|
|
||||||
动作,直接进第 3 步。
|
|
||||||
- **旧客户端降级**:该 DELETE 端点是较新客户端才有的能力。若返回 **404 / Not Found / 路由不存在**,说明当前客户端版本尚未包含 mcp 卸载端点——**不要**当作卸载成功。此时回写安装记录为 `installed`(保持"仍在用"),并一句话告知用户"当前客户端版本不支持 mcp 服务卸载,请升级客户端后重试"。切勿手工编辑 agent.json 绕过(绕锁会丢并发更新)。
|
|
||||||
3. **回写安装记录**(按「回写安装记录的统一方式」):成功 → 用同一 operationId PATCH
|
|
||||||
`status: uninstalled`;**失败(含 DELETE mcp-servers 端点 404 降级)→ 用同一 operationId PATCH
|
|
||||||
`status: installed`** 并说明原因(勿留在 `uninstalling`)。
|
|
||||||
4. 回报用户。
|
|
||||||
|
|
||||||
### 启动 / 停止 / 重启
|
|
||||||
|
|
||||||
收到"启动/停止/重启 {应用}"(docker-app / native-app)不走 catalog acquisition envelope:它只能以资源管理面
|
|
||||||
已经精确选中的 sourceId+entryId+deviceId 和实例级生命周期定义执行。缺精确实例或定义时停止,
|
|
||||||
不得用名称、本地 Registry 同 ID 条目或当前 catalog 猜容器。完成高风险确认后才用 `bash` 执行
|
|
||||||
该实例定义允许的 start/stop/restart;这类运行态切换不改变 installed-entry 的 install 状态。
|
|
||||||
|
|
||||||
### 失败处理
|
|
||||||
|
|
||||||
| 场景 | 处理 |
|
|
||||||
|------|------|
|
|
||||||
| 机器行缺失/畸形/重复 | 无 locator 时不猜写;有 locator 时先强制结算。都禁止从自然语言或本地目录补全 |
|
|
||||||
| resolver 400/404/409/连接失败 | 保留真实 `registry_acquisition_*` 与有界 reasons,先强制结算,再停止;禁止 Docker/bash/包管理和同 ID fallback |
|
|
||||||
| resolver 200 但身份/快照/manifest/指南不一致 | 视为不可信响应,先强制结算,不执行任何生命周期副作用 |
|
|
||||||
| resolver 200 缺失/篡改 catalogReceipt 或 App lifecycle guide 超过 64 KiB | 拒绝执行;禁止本地补建/截断 lifecycle,先强制结算 |
|
|
||||||
| receipt PATCH CAS/runtime/lifecycle 冲突 | 禁止修改候选绕过;MCP 回滚注册后结算,App 按失败语义结算 |
|
|
||||||
| operationId 缺失/畸形或旧消息重放 | 失败关闭;不得用当前 installed-entry 的新 operationId 替换旧消息,禁止执行或结算新意图 |
|
|
||||||
| Human Gate 取消 | 首装回 `failed`,重装/卸载回 `installed`,不得遗留中间态 |
|
|
||||||
| docker 未运行 | 提示用户启动 Docker;首装回 `failed`,重装回 `installed` |
|
|
||||||
| 端口被占用 | 列出占用进程,建议换端口或停占用,征求用户意见 |
|
|
||||||
| compose 启动失败 | `docker compose logs` 取错误,回写 `failed`,附日志摘要 |
|
|
||||||
| 健康校验超时 | 提示"可能仍在启动",给出查看日志的命令;如确认失败回写 `failed` |
|
|
||||||
| **卸载失败**(容器/配置未能移除,应用仍可用) | 回写 **`installed`**(不是 `failed`、不留 `uninstalling`),向用户说明卸载失败原因与排查建议 |
|
|
||||||
| **重装失败** | 旧版本仍在运行→回写 **`installed`** 并说明重装失败;旧版本已损坏不可用→`failed` |
|
|
||||||
| **mcp postInstall 失败** | 回写 `failed`,附失败命令的 stderr/exitCode 摘要;不注册到 Agent |
|
|
||||||
| **mcp 卸载端点 404**(旧客户端无 DELETE mcp-servers 能力) | 回写 `installed`(不是 `uninstalled`),告知用户升级客户端后重试 mcp 卸载;不手工改 agent.json |
|
|
||||||
|
|
||||||
### 边界与安全
|
|
||||||
|
|
||||||
- 新安装只处理当前机器消息与 resolver 共同确认的应用/服务;卸载只处理精确 installed ownership。
|
|
||||||
- `sourceId+entryId+snapshot+operation.deviceId+operation.operationId` 是本次边界;显示名、历史消息、本地同 ID 条目、
|
|
||||||
installed-entries 和调用方提供的 install/connection 都不能扩大它。
|
|
||||||
- App resolver 必须先于 Docker 探测、包管理和所有执行类 `bash`;非 200 或复核失败没有 fallback,
|
|
||||||
并必须先结算中间态。uninstall 不受当前 catalog listing/stale 阻断,但只能使用精确 ownership 与
|
|
||||||
lifecycle receipt 解析出的旧版本定义。
|
|
||||||
- 所有破坏性 docker 操作与 Agent 配置写入前必须有用户确认(risk_level: high)。
|
|
||||||
- 回写状态优先用 PATCH 端点(自带 status 枚举校验与原子写);仅端点不可用时降级 file-write,此时须自行保证结构合法(status 仅限六枚举值),否则界面加载会过滤掉脏条目。
|
|
||||||
- **先校验后回写**:docker-app 必须健康校验通过、mcp 必须连接校验通过,才回写 `installed`——installed-entries 是「你校验过的事实」,不是「执行过命令」。
|
|
||||||
- **中间态是过渡态,你必须回写终态或(卸载/重装失败时)回 `installed`**——绝不把记录停在 `installing`/`reinstalling`/`uninstalling`,否则界面对应操作按钮会禁用、用户被卡住。
|
|
||||||
|
|
||||||
## 与其他技能/系统的协作
|
|
||||||
|
|
||||||
- **后端 installed-entries watcher**:消费你回写的 status,只在 `installed` 派生 docker-app 服务、中间态保留派生、终态清理,无需你手动调派生接口。
|
|
||||||
- **Registry acquisition resolver**:`POST /api/registry/acquisitions/resolve`(install/reinstall 复核当前
|
|
||||||
canonical snapshot;App uninstall 按精确 ownership + lifecycle receipt 解析旧 Git object;只授权
|
|
||||||
本次读取,不记录安装事实)。
|
|
||||||
- **installed-entries 回写端点**:`PATCH /api/installed-entries/:entryId/:deviceId`(body 携带 sourceId,结构化回写状态,原子写 + 自动广播刷新前端;旧客户端 404 时才降级 file-write)。
|
|
||||||
- **agent-service mcp API**:`POST /api/mcp/install`(执行 postInstall + 连接测试)、`POST /api/agents/desirecore/mcp-servers`(注册)、`DELETE /api/agents/desirecore/mcp-servers/:serverId`(卸载)、`POST /api/mcp/test-connection`(验证)。
|
|
||||||
- **task-management**:长安装可登记为任务跟踪进度。
|
|
||||||
- **service-health**:派生出的服务由后端周期探活,你无需自行维护其健康。
|
|
||||||
|
|||||||
401
skills/app-install-manager/references/legacy-services.md
Normal file
401
skills/app-install-manager/references/legacy-services.md
Normal file
@@ -0,0 +1,401 @@
|
|||||||
|
---
|
||||||
|
name: 应用安装管理
|
||||||
|
description: 经目录快照或已安装生命周期收据 resolver 授权后安装/卸载/启停 docker-app、native-app 原生应用与 mcp/http-api 服务(docker-app:解析 manifest/installGuide → 跑 docker compose → 健康校验 → 回写安装状态;mcp 服务:使用 resolver 返回的 install/connection → 注册到 Agent → 连接验证 → 回写状态)。Use when 用户要求"安装 Dify/n8n 等应用"、"安装某 MCP 服务"、"卸载某应用/服务"、"启动/停止/重启某应用",或安装/卸载请求以"请安装/卸载 {名称} 到/从 {设备}"形式到达。
|
||||||
|
version: "1.4.0"
|
||||||
|
type: procedural
|
||||||
|
risk_level: high
|
||||||
|
status: enabled
|
||||||
|
disable-model-invocation: true
|
||||||
|
tags: [installation, docker, mcp, registry, app-management]
|
||||||
|
metadata:
|
||||||
|
author: desirecore
|
||||||
|
updated_at: "2026-09-18"
|
||||||
|
---
|
||||||
|
|
||||||
|
# app-install-manager 技能
|
||||||
|
|
||||||
|
> 兼容参考,不是自动加载的 Skill 正文。本文件保留旧版本协议以支持 MCP/http-api 服务;只在当前请求明确 kind=service 时读取。下文旧 App 状态机段落为历史材料,不能用于 application-observation-v1,也不能用于构造新的 App 中间态。应用按 ../SKILL.md 和 recording-api.md 执行;本文件中的“本技能目录”仍指 app-install-manager 根目录。
|
||||||
|
|
||||||
|
|
||||||
|
## L0:一句话摘要
|
||||||
|
|
||||||
|
应用/服务生命周期执行者:把目录里的 docker-app、native-app 原生应用与 mcp/http-api 服务真正装起来/卸下去,并把真实结果回写到安装记录,让"应用与服务"界面反映真实状态。
|
||||||
|
|
||||||
|
## L1:概述与使用场景
|
||||||
|
|
||||||
|
DesireCore 的安装是**委派式**的——界面发送自然语言说明加机器 envelope,并乐观记下一条中间态
|
||||||
|
(首装 `installing` / 重装 `reinstalling` / 卸载 `uninstalling`);**真正的执行与终态回写由本技能
|
||||||
|
(你)完成**。后端会监听安装记录文件:在状态变为 `installed` 后自动派生 docker-app 暴露的服务,
|
||||||
|
在中间态保留既有派生,仅在终态(`failed`/`uninstalled`/条目移除)清理派生。
|
||||||
|
|
||||||
|
覆盖三类目标:
|
||||||
|
- **native-app**:DesireCore Control 等宿主机原生应用,按固定制品与安装指南安装;只供外部客户端使用的 MCP 不注册到本 Agent。
|
||||||
|
- **docker-app**:Dify / n8n / RagFlow 等,走 docker compose 部署。
|
||||||
|
- **mcp / http-api 服务**:MCP 服务器(按 registry 的 `install` 字段安装 + 注册到 Agent)与 HTTP API 服务(仅登记安装记录,无自动化部署动作)。
|
||||||
|
|
||||||
|
使用场景:
|
||||||
|
- 用户说"安装 Dify""把 n8n 装到本机""安装某 MCP 服务"
|
||||||
|
- 用户说"卸载 RagFlow""卸载某 MCP 服务""停止/启动/重启 Open WebUI"
|
||||||
|
- 收到形如"请安装 {name} 到{device}""请卸载 {name}"的指令
|
||||||
|
|
||||||
|
## L2:详细规范(SOP)
|
||||||
|
|
||||||
|
### 关键路径与数据
|
||||||
|
|
||||||
|
- 当前目录快照解析:`POST http://127.0.0.1:<agent-service-port>/api/registry/acquisitions/resolve`
|
||||||
|
- 机器消息解析与响应复核:`<本技能目录>/scripts/registry-catalog-acquisition.mjs`
|
||||||
|
- 安装记录:`<DesireCore根目录>/config/installed-entries.json`
|
||||||
|
(`<根目录>` 为你的 AgentFS 根,生产为 `~/.desirecore`,开发隔离为 `~/.desirecore-dev`,以自我感知里的实际根目录为准)
|
||||||
|
- agent-service API:`http://127.0.0.1:<agent-service-port>`(端口见自我感知;mcp 服务安装/注册用)
|
||||||
|
|
||||||
|
### 执行前协议:精确机器消息 → ownership / resolver(强制)
|
||||||
|
|
||||||
|
任何 install/reinstall/uninstall 在环境探测、Docker 查询、包安装或其它执行类 `bash` 前,都必须
|
||||||
|
先完成本节。自然语言只用于面向用户的说明,**不得**从自然语言猜测 `kind`、`sourceId`、
|
||||||
|
`entryId`、deviceId、snapshot、install 或 connection。
|
||||||
|
|
||||||
|
1. 从**当前这条**生命周期指令中提取唯一一行:
|
||||||
|
`RegistryCatalogAcquisition=<单行 JSON>`。App/Service envelope 都必须带
|
||||||
|
`operation:{action:"install"|"reinstall"|"uninstall",deviceId,operationId}`;operationId 必须是小写
|
||||||
|
UUID,并且只代表这一次 lifecycle 意图。先把指令全文交给
|
||||||
|
`parse-locator`,只取得 `sourceId+entryId+operation.deviceId` 的账本定位符;再用
|
||||||
|
`parse-message` 校验完整 envelope:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node "<本技能目录>/scripts/registry-catalog-acquisition.mjs" parse-locator
|
||||||
|
node "<本技能目录>/scripts/registry-catalog-acquisition.mjs" parse-message
|
||||||
|
```
|
||||||
|
|
||||||
|
完整解析结果为 `{request,locator}`;App resolver request 必须包含 operation。解析器会拒绝缺失、
|
||||||
|
重复、畸形机器行、额外字段、可变 commit、来源/snapshot 不一致和 App 夹带 install/connection。
|
||||||
|
不允许回看旧消息或从显示名、同 ID 本地条目、自然语言补字段。若 locator 已解析但完整解析
|
||||||
|
失败,先按“执行前停止的强制结算”处理;真正缺失/JSON 畸形且没有 locator 的手工指令不猜写
|
||||||
|
安装账本,只报告错误并停止。
|
||||||
|
2. 对 App 三种 operation 及 Service install/reinstall,把解析结果的 `request` 原样作为 body 调 resolver:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tool: HttpRequest
|
||||||
|
parameters:
|
||||||
|
url: http://127.0.0.1:<agent-service-port>/api/registry/acquisitions/resolve
|
||||||
|
method: POST
|
||||||
|
body: <parse-message 输出的 request>
|
||||||
|
```
|
||||||
|
|
||||||
|
install/reinstall 由服务端重验 current canonical;uninstall 由服务端按 operation.deviceId 精确读取
|
||||||
|
installed-entry 的不可变 lifecycle receipt,并用 receipt 对应的 Git object 返回旧版本定义。它不因
|
||||||
|
当前 catalog listing-only、stale 或下架而阻断,但无 receipt、receipt/snapshot 不一致、来源或设备
|
||||||
|
不匹配都失败关闭。
|
||||||
|
Service uninstall 不调用 current catalog resolver:它只按 locator 命中 exact installed receipt,随后
|
||||||
|
由精确 DELETE 端点使用 receipt.runtimeServerId 删除;目录删除、listing-only 或 stale 不得卡住卸载。
|
||||||
|
3. 只有 HTTP 200 且响应通过同一脚本的 `evaluate-response` 复核后才可继续。复核
|
||||||
|
输入是 `{ "expected": <request>, "status": <HTTP状态>, "body": <响应JSON> }`。响应的
|
||||||
|
kind/sourceId/entryId/snapshot、manifest.id/type 必须一致,App 还必须取得非空 installGuide。
|
||||||
|
响应还必须包含服务端生成并校验的完整 `catalogReceipt` candidate。`allowed:true` 才是本次执行
|
||||||
|
定义;App 只使用其 manifest/installGuide/catalogReceipt,忽略任何 install/connection。uninstall 的
|
||||||
|
manifest/installGuide/receipt 必须来自服务端 receipt 解析结果,不能改读当前目录。
|
||||||
|
4. 服务端有界拒绝码必须逐字保留:
|
||||||
|
`registry_acquisition_invalid_request`、`registry_acquisition_not_found`、
|
||||||
|
`registry_acquisition_snapshot_stale`、`registry_acquisition_blocked`、
|
||||||
|
`registry_acquisition_client_upgrade_required`、`registry_acquisition_config_mismatch`、
|
||||||
|
`registry_acquisition_install_guide_unavailable`、`registry_acquisition_receipt_missing`、
|
||||||
|
`registry_acquisition_ownership_mismatch`;同时保留通过有界校验的 reasons。任何非 200、
|
||||||
|
连接失败、非 JSON 或身份/快照/manifest 不一致都禁止执行命令,并先完成强制结算。
|
||||||
|
5. **禁止 fallback**:不得读取任何本地 Registry 条目、改用同 ID 其它来源或沿用旧 resolver 响应。
|
||||||
|
resolver/ownership 只决定本次读取与精确归属,不新增安装账本,也不替代 Human Gate、健康校验、
|
||||||
|
回滚和终态收据。
|
||||||
|
|
||||||
|
安装记录条目结构(写回时必须完整保留全部字段):
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"entryId": "dify",
|
||||||
|
"sourceId": "registry:official",
|
||||||
|
"type": "docker-app | native-app | mcp | http-api",
|
||||||
|
"deviceId": "<设备ID>",
|
||||||
|
"deviceName": "<设备名>",
|
||||||
|
"version": "<版本>",
|
||||||
|
"installedAt": 1730000000000,
|
||||||
|
"installedBy": "agent",
|
||||||
|
"status": "installing | reinstalling | installed | uninstalling | failed | uninstalled",
|
||||||
|
"conversationId": "<对话ID>",
|
||||||
|
"messageId": "<消息ID>",
|
||||||
|
"operationId": "<本次操作UUID>",
|
||||||
|
"catalogReceipt": {
|
||||||
|
"schemaVersion": 1,
|
||||||
|
"kind": "app | service",
|
||||||
|
"catalogSourceId": "registry:official",
|
||||||
|
"entryId": "dify",
|
||||||
|
"catalogCommit": "<40/64位commit>",
|
||||||
|
"catalogPath": "entries/dify",
|
||||||
|
"releaseVersion": "<安装版本>",
|
||||||
|
"lifecycle": {
|
||||||
|
"manifest": {
|
||||||
|
"id": "dify",
|
||||||
|
"name": "Dify",
|
||||||
|
"type": "docker-app | mcp",
|
||||||
|
"version": "<安装版本>",
|
||||||
|
"description": "<最多4096字符>"
|
||||||
|
},
|
||||||
|
"installGuide": "<仅App,服务端返回且最多64KiB>"
|
||||||
|
},
|
||||||
|
"runtimeServerId": "<仅MCP注册成功后由服务端签发>"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`catalogReceipt` 是同一 installed-entry 内的最小不可变生命周期收据,不是第二账本。UI 意图只带
|
||||||
|
snapshot 基线;install/reinstall 成功时必须原样采用 resolver 返回的完整 candidate,禁止 Skill 根据
|
||||||
|
snapshot、manifest 或 installGuide 自行构造/扩展 lifecycle。Service 注册成功后只允许在该 candidate
|
||||||
|
上补服务端返回的 runtimeServerId。
|
||||||
|
|
||||||
|
重装时主记录的 `catalogReceipt` 和 version 仍是旧的已安装事实;新目标只存在同一条记录的
|
||||||
|
`pendingCatalogReceipt`(base snapshot,无 lifecycle/runtime)。Skill 不写 PUT,也不把 pending 当成
|
||||||
|
最终 receipt。成功时用 resolver 的新 candidate + 新 releaseVersion 原子切换;失败/取消只用同一
|
||||||
|
operationId PATCH `installed`,省略 receipt/version,让服务端保留旧事实并清除 pending。
|
||||||
|
|
||||||
|
**状态语义表(六枚举)**——中间态由界面乐观写入、终态由你回写:
|
||||||
|
|
||||||
|
| status | 谁写入 | 你的退出动作 |
|
||||||
|
|--------|--------|-------------|
|
||||||
|
| `installing` | 界面首装乐观态 | 成功→`installed`;失败→`failed` |
|
||||||
|
| `reinstalling` | 界面重装乐观态(此前已 `installed`) | 成功→`installed`;失败但**旧版本仍在运行**→回写 `installed` 并说明重装失败;失败且应用已不可用→`failed` |
|
||||||
|
| `uninstalling` | 界面卸载乐观态 | 成功→`uninstalled`(或移除条目);**失败→回写 `installed`** 并向用户说明失败原因 |
|
||||||
|
| `installed` / `failed` / `uninstalled` | 你回写的终态 | — |
|
||||||
|
|
||||||
|
**后端派生规则(务必理解)**:只在 `installed` 派生 docker-app 的服务;在中间态(`installing`/`reinstalling`/`uninstalling`)**保留**既有派生;仅在终态(`failed`/`uninstalled`/条目移除)**清理**派生。因此:
|
||||||
|
- 重装/卸载期间派生服务不会被误删(旧容器还在跑时目录不抖动);
|
||||||
|
- 卸载或重装失败时你回写 `installed`,派生会**无缝恢复**(从未被删);
|
||||||
|
- `failed` 语义是"应用当前不可用"——**只有确认容器已不能用才写 `failed`**,否则一律回 `installed`。
|
||||||
|
|
||||||
|
### 执行前停止的强制结算
|
||||||
|
|
||||||
|
UI 在发消息前已经写入中间态。locator 成功后,完整解析失败、resolver 拒绝/异常、Human Gate
|
||||||
|
取消、环境检查失败,或任何尚未执行安装/卸载命令的退出,都必须按下面的单账本流程结算:
|
||||||
|
|
||||||
|
1. `GET /api/installed-entries` 取得当前实例记录;不得直读其它实例或构造新记录。
|
||||||
|
2. 把 `{stage,locator,entries}` 交给脚本的 `plan-settlement`。stage 只能是
|
||||||
|
`parse|resolver|human_gate|pre_execution`。它严格匹配 sourceId+entryId+deviceId 和与 operation
|
||||||
|
对应的中间态与 operationId;同 ID 双来源、跨设备或旧消息重放都不会串写,零条/多条都返回
|
||||||
|
settlement:null。
|
||||||
|
3. settlement 非空时,按“回写安装记录的统一方式”PATCH 它给出的精确三元组和 status:
|
||||||
|
`installing → failed`,`reinstalling → installed`,`uninstalling → installed`,并原样携带
|
||||||
|
settlement.operationId。结算前后都不得执行
|
||||||
|
Docker、包管理或安装脚本。settlement:null 时报告有界原因并停止,不按数组顺序、名称或设备猜测。
|
||||||
|
4. Human Gate 取消也是正常停止而非悬空:首装回 `failed`,重装/卸载恢复 `installed`。resolver
|
||||||
|
错误码与 reasons 在完成结算后原样说明。
|
||||||
|
|
||||||
|
### 回写安装记录的统一方式(端点优先,404 降级)
|
||||||
|
|
||||||
|
**所有「回写安装记录」都用这一方式**——不要再直接 file-write 改 `installed-entries.json`(除非端点不可用时降级)。这样 installed-entries 成为「你校验后回写的事实」,界面以它为准。
|
||||||
|
|
||||||
|
**首选:PATCH 端点**(结构化 + 原子写 + 自动广播刷新前端):
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
tool: HttpRequest
|
||||||
|
parameters:
|
||||||
|
url: http://127.0.0.1:<agent-service-port>/api/installed-entries/<entryId>/<deviceId>
|
||||||
|
method: PATCH
|
||||||
|
body:
|
||||||
|
sourceId: <locator 中的精确 sourceId>
|
||||||
|
operationId: <locator.operation.operationId>
|
||||||
|
status: installed # 六枚举之一
|
||||||
|
# version: "<新版本>" # 可选,重装升级时更新版本号
|
||||||
|
```
|
||||||
|
|
||||||
|
- `200` → 回写成功,前端自动刷新,**无需**再手动改文件。
|
||||||
|
- `400` → status/sourceId 非法,检查取值。
|
||||||
|
- `404 entry_not_found` → 该条中间态记录不存在(界面未写/已被清理)。**不要**重试或伪造记录;跳过并一句话提示用户重发指令即可。
|
||||||
|
- receipt PATCH 的 `catalog_receipt_*` / `catalog_operation_mismatch` /
|
||||||
|
`catalog_status_transition_invalid` / `invalid_operation_id` 400/409
|
||||||
|
表示候选非法、ownership/CAS/runtime/lifecycle/operation 不一致;
|
||||||
|
不得删字段、重建 receipt 或换 snapshot 重试。App 停止并按失败语义结算;MCP 先回滚刚注册的
|
||||||
|
runtimeServerId,再结算。
|
||||||
|
- **连接失败 / 路由 404(旧客户端无此端点)** → catalog lifecycle 回写不得降级 file-write;否则会
|
||||||
|
绕过 operationId 和 receipt CAS。停止并报告客户端不兼容。
|
||||||
|
|
||||||
|
**降级 file-write**(仅无 catalog envelope 的历史普通操作可用;本节 catalog intent 绝无此降级):读
|
||||||
|
`installed-entries.json` → 按 `sourceId`+`entryId`+`deviceId` 精确定位那条中间态记录 → **只改
|
||||||
|
`status`(保留 `installedAt` 等其余所有字段与其它条目)** → 写回整个文件(界面也写此文件,
|
||||||
|
勿覆盖丢失)。来源缺失或存在多条候选时失败关闭,不得按同 ID 猜测。
|
||||||
|
|
||||||
|
下文各流程的「回写安装记录」一律指这套统一方式,只标注目标 `status`。
|
||||||
|
|
||||||
|
### docker-app 安装流程
|
||||||
|
|
||||||
|
1. **解析机器消息并调用 resolver**:严格执行“执行前协议”。action 与 deviceId 只认 operation;
|
||||||
|
`entryId`、`type`、版本、端口、exposes 和安装步骤只认复核后的服务端结果。manifest.type 不是
|
||||||
|
`docker-app` 时必须按准确类型路由:`native-app` 走原生应用流程;只有 `mcp`/`http-api` 才走服务流程。身份或类型不一致立即停止。
|
||||||
|
2. **读取本次授权定义**:从 resolver 返回的 manifest 读取 `install.requirements`(docker/内存/
|
||||||
|
磁盘/ports)与 exposes;只把 resolver 返回的 installGuide 作为部署步骤与验证地址。不得从
|
||||||
|
AgentFS 或其它目录补读/覆盖同 ID 定义。
|
||||||
|
3. **环境校验**(`bash`):`docker version` / `docker compose version` 确认 docker 就绪;用
|
||||||
|
`manifest.install.requirements.ports` 检查端口占用(`lsof -i :<port>` 或 `docker ps`);检查磁盘
|
||||||
|
空间。任一不满足→先按 `pre_execution` 结算再停下,向用户说明并给出修复建议。
|
||||||
|
4. **高风险确认**:安装/卸载会改动本机容器,属高风险。执行前用一句话向用户确认(应用名 +
|
||||||
|
目标设备 + 端口)。用户取消则按 `human_gate` 结算后中止。
|
||||||
|
5. **执行**(`bash`,严格按 resolver 返回的 installGuide):
|
||||||
|
- docker-compose 类:在应用工作目录 `docker compose up -d`;docker 类:`docker run ...`。
|
||||||
|
- 失败立即捕获输出,进入"失败处理"。
|
||||||
|
6. **健康校验(先校验后回写,强制)**:按 installGuide 的验证地址或 manifest.exposes 的 `http://localhost:<port><path>`,`bash` 用 `curl` 轮询(最多 ~2 分钟)确认服务可达。**只有这步通过才算安装成功**——不要仅凭 `docker compose up -d` 无报错就回写 `installed`。
|
||||||
|
7. **回写安装记录**(**本技能的核心职责**,按上方「回写安装记录的统一方式」):
|
||||||
|
- 健康校验通过 → 用 `build-receipt-patch` 把 resolver 原样 candidate 与 `status: installed` 组成
|
||||||
|
CAS-safe PATCH,并携带 locator.operationId 和 candidate.releaseVersion;不得本地重建 receipt。
|
||||||
|
未通过/失败 → 首装 `status: failed`;重装失败但旧版本仍在运行 → 只 PATCH
|
||||||
|
`{sourceId,operationId,status:installed}`,不得附 receipt/version/pending(见状态语义表)。
|
||||||
|
- 成功后无需手动派生服务——后端文件 watcher 检测到 `installed` 后自动派生;重装期间派生始终保留。
|
||||||
|
8. **回报用户**:一句话总结结果 + 访问地址(成功)或失败原因 + 排查建议(失败)。
|
||||||
|
|
||||||
|
### docker-app 卸载流程
|
||||||
|
|
||||||
|
1. 完整解析 App envelope 并调用 resolver。resolver 必须用 operation.deviceId 精确命中
|
||||||
|
sourceId+entryId+deviceId 的 `uninstalling` 记录,再按该记录的不可变 lifecycle receipt 读取旧 Git
|
||||||
|
object;**不得以当前 catalog eligibility 重新授权卸载**。同 ID 其它来源、其它设备、无 receipt
|
||||||
|
legacy 记录或 receipt/snapshot 不一致都拒绝。
|
||||||
|
2. 只使用 resolver 从 receipt 返回并通过脚本复核的 manifest/installGuide 作为卸载定义;不得读取
|
||||||
|
当前 Registry 同 ID 条目补命令。resolver 拒绝时先结算回 `installed` 并提示手工管理/升级,不能
|
||||||
|
发明 compose 目录或容器名。取得定义后再做 Human Gate;取消即回 `installed`。
|
||||||
|
3. `bash`:只按 receipt 绑定的 installGuide 执行卸载(如 `docker compose down -v`),按需清理
|
||||||
|
卷/镜像。
|
||||||
|
4. 回写安装记录(按「回写安装记录的统一方式」):
|
||||||
|
- **成功**(容器确已停止/删除)→ PATCH `status: uninstalled`。后端 watcher 据此清理派生服务与 per-service Skill。
|
||||||
|
- **失败**(容器未能停止/删除,应用仍在运行)→ PATCH `status: installed`,向用户说明卸载失败原因。**切勿**留在 `uninstalling`(界面卸载按钮会禁用,用户被卡住直至 stale 超时)。
|
||||||
|
5. 回报用户。
|
||||||
|
|
||||||
|
### native-app 原生应用安装、升级与卸载
|
||||||
|
|
||||||
|
原生应用属于 App,不是本 Agent 的 MCP 工具。**DesireCore Control 是外部智能体控制 DesireCore 的独立应用;即使它向外暴露 MCP,也绝不调用内部 `/api/mcp/install` 或 `/api/agents/desirecore/mcp-servers` 注册它。** 不改写 Agent 配置,不创建自启动项,不跟随 DesireCore 启停,不把宿主机 CDP 放进 Docker。
|
||||||
|
|
||||||
|
1. 执行前协议与 Human Gate 完全沿用 App 流程。先调用 resolver 并用 `evaluate-response` 复核,再进行任何环境探测或包管理操作。manifest.type 与 lifecycle.manifest.type 必须同为 `native-app`,candidate.kind 为 `app`,不接受 runtimeServerId。
|
||||||
|
2. 新装/升级只认复核后 manifest 的 `install.method=native-node`、`requirements.node`、目标设备、固定 Release URL/ref/SHA-256 和 installGuide。检查宿主机 Node 版本、可写安装目录、磁盘、默认管理端口;不要求 Docker。缺乏不可变制品或不支持原生应用的客户端时先结算再停止,不改写 type 兼容旧客户端。
|
||||||
|
3. 在用户批准的独立应用目录按 installGuide 下载、**先核对 SHA-256 再安装**。版本、启动命令与目录绑定到该安装,不执行浮动 latest,不复用其他应用/实例目录。升级前保留旧版;新包验证失败时恢复可用旧版并按既有重装语义回写。
|
||||||
|
4. 使用 installGuide 的隔离安装自检验证包、版本及本机 HTTP 管理页;不要为了安装验证读取真实 DesireCore 实例、截图或运行模型。**安装成功不等于持续运行**:验证进程正常退出后可回写 installed,运行态保持 stopped/unknown。用户另行请求启动时再启动。`--allow-control`、外部隧道、真实 OpenAI key 均需独立明确授权;安装本身不启用它们。
|
||||||
|
5. 自检通过后用 `build-receipt-patch` 原样保存服务器 candidate,`kind=app`、`type=native-app`,按精确 operationId 回写 installed。不要派生内部 MCP/HTTP 服务,不以登记工具数作为安装成功条件。
|
||||||
|
6. 卸载只使用原安装 receipt 的 installGuide;确认后停止准确的自有应用进程、卸载该应用的包。不得按进程名批量终止 Node、其他 tunnel-client 或 DesireCore,也不得删除 DesireCore 数据。凭据与用户配置默认保留,删除需另行确认。卸载成功回 uninstalled;失败但仍可用回 installed,不能遗留中间态。
|
||||||
|
|
||||||
|
原生应用启动/停止同样要求精确安装身份和原生命周期定义,通过用户批准的独立终端运行。状态改变不伪造安装/升级事实,管理页用系统浏览器打开;已配置的默认端口被占用时先停止并说明,不悄悄换端口导致“我的应用”指向别处。
|
||||||
|
|
||||||
|
### mcp / http-api 服务安装流程
|
||||||
|
|
||||||
|
**mcp 服务**(manifest.type=`mcp`,条目含 `install` 与 `connection` 字段):
|
||||||
|
|
||||||
|
1. **解析机器消息并调用 resolver + 确认**:严格执行“执行前协议”,以复核后的复合身份确定
|
||||||
|
`entryId`,以 operation.deviceId 确定目标设备(mcp 通常装到本机)。resolver 失败时在任何安装命令前
|
||||||
|
停止;成功后再做高风险确认。界面已乐观写 `installing`/`reinstalling`。
|
||||||
|
2. **使用本次授权定义**:只用 resolver 响应的 manifest/install/connection(`install` 含 method、
|
||||||
|
packageName、command、args、postInstall;`connection` 含 transport/command/args/url/headers)。
|
||||||
|
不读取或合并本地同 ID 条目。
|
||||||
|
3. **执行安装**(优先走 API,逐条跑 `postInstall` 命令 + 可选连接测试):
|
||||||
|
```yaml
|
||||||
|
tool: HttpRequest
|
||||||
|
parameters:
|
||||||
|
url: http://127.0.0.1:<agent-service-port>/api/mcp/install
|
||||||
|
method: POST
|
||||||
|
body:
|
||||||
|
install: <manifest.install 原样>
|
||||||
|
connection: <manifest.connection 原样> # 传入则安装后自动测连接
|
||||||
|
catalogAcquisition: <parse-message 输出的 request 原样>
|
||||||
|
```
|
||||||
|
返回 `data.steps`(每条命令 exitCode/stdout/stderr)与 `data.connectionTest`。任一命令失败→`success:false`,进入"失败处理"。无 API 可用时用 `bash` 逐条跑 `postInstall`。
|
||||||
|
4. **注册到 Agent**(让 MCP 工具下轮可用):
|
||||||
|
```yaml
|
||||||
|
tool: HttpRequest
|
||||||
|
parameters:
|
||||||
|
url: http://127.0.0.1:<agent-service-port>/api/agents/desirecore/mcp-servers
|
||||||
|
method: POST
|
||||||
|
body:
|
||||||
|
serverId: <entryId>
|
||||||
|
config: <manifest.connection 原样>
|
||||||
|
catalogAcquisition: <parse-message 输出的 request 原样>
|
||||||
|
```
|
||||||
|
端点锁内 read-modify-write 写入 agent.json 的 `mcp_servers`——**勿手工编辑 agent.json**(绕锁会丢
|
||||||
|
并发更新)。catalog 响应的 `data.runtimeServerId` 是服务端签发的复合 ownership key;不得用
|
||||||
|
entryId、显示名或调用方值代替。
|
||||||
|
5. **连接校验(先校验后回写,强制)**:看第 3 步返回的 `connectionTest.success`,或单独 `POST /api/mcp/test-connection`(body `{connection}`)确认能连通、能列出工具。**只有校验通过才算安装成功**——不要仅凭 postInstall 命令退出码 0 就回写 `installed`(装了包不等于连得上)。
|
||||||
|
6. **回写安装记录**(按「回写安装记录的统一方式」):连接校验通过后,用脚本
|
||||||
|
`build-receipt-patch` 原样接收 resolver 的 catalogReceipt candidate,只在其上补服务端 add 返回的
|
||||||
|
runtimeServerId,并与 locator.operationId、`status: installed` 在同一次精确 PATCH 中写回原
|
||||||
|
installed-entry,PATCH version 取 candidate.releaseVersion;禁止从
|
||||||
|
request snapshot/manifest 自建或替换 lifecycle。缺 candidate/runtimeServerId、candidate 被篡改或
|
||||||
|
receipt CAS 回写失败时不得宣布成功,先按精确 runtimeServerId 回滚刚注册的 MCP,再按失败语义
|
||||||
|
结算。连接校验失败 → 首装 `failed`;重装失败但旧配置仍可用 → 只带同一 operationId 恢复
|
||||||
|
`installed`,省略 receipt/version/pending。
|
||||||
|
7. **回报用户**:总结安装结果 + 发现的工具数(成功)或失败原因摘要(失败)。
|
||||||
|
|
||||||
|
**http-api 服务**(manifest.type=`http-api`,无 `install` 字段、界面也无自动化安装动作):按「回写安装记录的统一方式」维护回写(`installing`→`installed`、`uninstalling`→`uninstalled`/失败回 `installed`),明确告知用户该类服务无本地部署步骤、只是登记可达性。
|
||||||
|
|
||||||
|
### mcp / http-api 服务卸载流程
|
||||||
|
|
||||||
|
1. 完整解析机器消息,但不调用 current catalog resolver。用脚本 `resolve-service-uninstall` 对 locator、
|
||||||
|
request.snapshot 和 `GET /api/installed-entries` 结果做完整复核:必须精确命中
|
||||||
|
sourceId+entryId+deviceId 的唯一 `uninstalling` 记录,receipt.kind 必须为 service,且
|
||||||
|
catalogReceipt.runtimeServerId 与 snapshot 必须有效。无 receipt/kind/runtimeServerId、身份/快照不符、
|
||||||
|
零条或多条都先结算回 `installed` 并停止。随后做高风险确认,取消同样结算回 `installed`。
|
||||||
|
2. **执行卸载**(mcp):从 Agent 移除 MCP server 配置:
|
||||||
|
```yaml
|
||||||
|
tool: HttpRequest
|
||||||
|
parameters:
|
||||||
|
url: http://127.0.0.1:<agent-service-port>/api/agents/desirecore/mcp-servers/<entryId>?sourceId=<sourceId>&deviceId=<deviceId>&operationId=<operationId>
|
||||||
|
method: DELETE
|
||||||
|
```
|
||||||
|
URL 参数先用脚本 `build-service-delete` 从 locator 生成;operationId 必须原样来自 locator。服务端
|
||||||
|
从精确 installed-entry receipt 的 runtimeServerId 删除对应
|
||||||
|
配置;不得按裸 entryId 删除,
|
||||||
|
也不得因为当前目录删除、listing-only 或 snapshot stale 改走 resolver。
|
||||||
|
端点幂等。如安装时全局装了包,按需 `bash` 卸载(可选,多为无害保留)。http-api 服务无需执行
|
||||||
|
动作,直接进第 3 步。
|
||||||
|
- **旧客户端降级**:该 DELETE 端点是较新客户端才有的能力。若返回 **404 / Not Found / 路由不存在**,说明当前客户端版本尚未包含 mcp 卸载端点——**不要**当作卸载成功。此时回写安装记录为 `installed`(保持"仍在用"),并一句话告知用户"当前客户端版本不支持 mcp 服务卸载,请升级客户端后重试"。切勿手工编辑 agent.json 绕过(绕锁会丢并发更新)。
|
||||||
|
3. **回写安装记录**(按「回写安装记录的统一方式」):成功 → 用同一 operationId PATCH
|
||||||
|
`status: uninstalled`;**失败(含 DELETE mcp-servers 端点 404 降级)→ 用同一 operationId PATCH
|
||||||
|
`status: installed`** 并说明原因(勿留在 `uninstalling`)。
|
||||||
|
4. 回报用户。
|
||||||
|
|
||||||
|
### 启动 / 停止 / 重启
|
||||||
|
|
||||||
|
收到"启动/停止/重启 {应用}"(docker-app / native-app)不走 catalog acquisition envelope:它只能以资源管理面
|
||||||
|
已经精确选中的 sourceId+entryId+deviceId 和实例级生命周期定义执行。缺精确实例或定义时停止,
|
||||||
|
不得用名称、本地 Registry 同 ID 条目或当前 catalog 猜容器。完成高风险确认后才用 `bash` 执行
|
||||||
|
该实例定义允许的 start/stop/restart;这类运行态切换不改变 installed-entry 的 install 状态。
|
||||||
|
|
||||||
|
### 失败处理
|
||||||
|
|
||||||
|
| 场景 | 处理 |
|
||||||
|
|------|------|
|
||||||
|
| 机器行缺失/畸形/重复 | 无 locator 时不猜写;有 locator 时先强制结算。都禁止从自然语言或本地目录补全 |
|
||||||
|
| resolver 400/404/409/连接失败 | 保留真实 `registry_acquisition_*` 与有界 reasons,先强制结算,再停止;禁止 Docker/bash/包管理和同 ID fallback |
|
||||||
|
| resolver 200 但身份/快照/manifest/指南不一致 | 视为不可信响应,先强制结算,不执行任何生命周期副作用 |
|
||||||
|
| resolver 200 缺失/篡改 catalogReceipt 或 App lifecycle guide 超过 64 KiB | 拒绝执行;禁止本地补建/截断 lifecycle,先强制结算 |
|
||||||
|
| receipt PATCH CAS/runtime/lifecycle 冲突 | 禁止修改候选绕过;MCP 回滚注册后结算,App 按失败语义结算 |
|
||||||
|
| operationId 缺失/畸形或旧消息重放 | 失败关闭;不得用当前 installed-entry 的新 operationId 替换旧消息,禁止执行或结算新意图 |
|
||||||
|
| Human Gate 取消 | 首装回 `failed`,重装/卸载回 `installed`,不得遗留中间态 |
|
||||||
|
| docker 未运行 | 提示用户启动 Docker;首装回 `failed`,重装回 `installed` |
|
||||||
|
| 端口被占用 | 列出占用进程,建议换端口或停占用,征求用户意见 |
|
||||||
|
| compose 启动失败 | `docker compose logs` 取错误,回写 `failed`,附日志摘要 |
|
||||||
|
| 健康校验超时 | 提示"可能仍在启动",给出查看日志的命令;如确认失败回写 `failed` |
|
||||||
|
| **卸载失败**(容器/配置未能移除,应用仍可用) | 回写 **`installed`**(不是 `failed`、不留 `uninstalling`),向用户说明卸载失败原因与排查建议 |
|
||||||
|
| **重装失败** | 旧版本仍在运行→回写 **`installed`** 并说明重装失败;旧版本已损坏不可用→`failed` |
|
||||||
|
| **mcp postInstall 失败** | 回写 `failed`,附失败命令的 stderr/exitCode 摘要;不注册到 Agent |
|
||||||
|
| **mcp 卸载端点 404**(旧客户端无 DELETE mcp-servers 能力) | 回写 `installed`(不是 `uninstalled`),告知用户升级客户端后重试 mcp 卸载;不手工改 agent.json |
|
||||||
|
|
||||||
|
### 边界与安全
|
||||||
|
|
||||||
|
- 新安装只处理当前机器消息与 resolver 共同确认的应用/服务;卸载只处理精确 installed ownership。
|
||||||
|
- `sourceId+entryId+snapshot+operation.deviceId+operation.operationId` 是本次边界;显示名、历史消息、本地同 ID 条目、
|
||||||
|
installed-entries 和调用方提供的 install/connection 都不能扩大它。
|
||||||
|
- App resolver 必须先于 Docker 探测、包管理和所有执行类 `bash`;非 200 或复核失败没有 fallback,
|
||||||
|
并必须先结算中间态。uninstall 不受当前 catalog listing/stale 阻断,但只能使用精确 ownership 与
|
||||||
|
lifecycle receipt 解析出的旧版本定义。
|
||||||
|
- 所有破坏性 docker 操作与 Agent 配置写入前必须有用户确认(risk_level: high)。
|
||||||
|
- 回写状态优先用 PATCH 端点(自带 status 枚举校验与原子写);仅端点不可用时降级 file-write,此时须自行保证结构合法(status 仅限六枚举值),否则界面加载会过滤掉脏条目。
|
||||||
|
- **先校验后回写**:docker-app 必须健康校验通过、mcp 必须连接校验通过,才回写 `installed`——installed-entries 是「你校验过的事实」,不是「执行过命令」。
|
||||||
|
- **中间态是过渡态,你必须回写终态或(卸载/重装失败时)回 `installed`**——绝不把记录停在 `installing`/`reinstalling`/`uninstalling`,否则界面对应操作按钮会禁用、用户被卡住。
|
||||||
|
|
||||||
|
## 与其他技能/系统的协作
|
||||||
|
|
||||||
|
- **后端 installed-entries watcher**:消费你回写的 status,只在 `installed` 派生 docker-app 服务、中间态保留派生、终态清理,无需你手动调派生接口。
|
||||||
|
- **Registry acquisition resolver**:`POST /api/registry/acquisitions/resolve`(install/reinstall 复核当前
|
||||||
|
canonical snapshot;App uninstall 按精确 ownership + lifecycle receipt 解析旧 Git object;只授权
|
||||||
|
本次读取,不记录安装事实)。
|
||||||
|
- **installed-entries 回写端点**:`PATCH /api/installed-entries/:entryId/:deviceId`(body 携带 sourceId,结构化回写状态,原子写 + 自动广播刷新前端;旧客户端 404 时才降级 file-write)。
|
||||||
|
- **agent-service mcp API**:`POST /api/mcp/install`(执行 postInstall + 连接测试)、`POST /api/agents/desirecore/mcp-servers`(注册)、`DELETE /api/agents/desirecore/mcp-servers/:serverId`(卸载)、`POST /api/mcp/test-connection`(验证)。
|
||||||
|
- **task-management**:长安装可登记为任务跟踪进度。
|
||||||
|
- **service-health**:派生出的服务由后端周期探活,你无需自行维护其健康。
|
||||||
65
skills/app-install-manager/references/recording-api.md
Normal file
65
skills/app-install-manager/references/recording-api.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# 应用资料与最后核验记录 API
|
||||||
|
|
||||||
|
仅应用 `application-observation-v1` 使用。本协议不新增模型工具,不创建持久安装中意图。API 未支持时停止并提示客户端需更新;不能降级 file-write 或旧 App receipt 流程。
|
||||||
|
|
||||||
|
## 目标与资料
|
||||||
|
|
||||||
|
API base 使用当前会话已确认的活动 Agent Service;不要猜固定端口。`deviceId=local` 指该 Agent Service 主机,不一定是界面所在电脑。远程设备使用已授权的现有设备执行工具;接口接受设备定位不代表它验证了远程证据。身份/位置不清楚就停止。
|
||||||
|
|
||||||
|
通过已有 HttpRequest 调用 `POST /api/registry/acquisitions/resolve`,body 为 `ApplicationManagement` 原对象:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"protocol": "application-observation-v1",
|
||||||
|
"sourceId": "registry:official",
|
||||||
|
"entryId": "desirecore-control",
|
||||||
|
"deviceId": "local",
|
||||||
|
"action": "manage"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`action=install|update` 必须加上由精确目录条目取得的 `snapshot`:schemaVersion、catalogSourceId、完整 catalogCommit、catalogPath、releaseVersion,及存在时的 contentRef/contentSha256。只允许明确固定版本,不填写 latest,不自行拼下载地址。`action=uninstall|manage` 不传 snapshot,服务端读取原安装资料。
|
||||||
|
|
||||||
|
成功 `data` 包含 `material`、`expectedRevision`、`manifest`、`installGuide` 和 `record`。读取不写账本、不执行软件、不代替审批。复用返回的 material,不复制整份指南进提交请求。服务端内部复核不需要模型重复读全部目录。
|
||||||
|
|
||||||
|
目录的无关条目更新不替换选定版本;资料必须在受信分支可达,当前目标条目仍允许获取。下架/撤回可以阻止新装,原安装的 manage/uninstall 使用历史指南,不依赖目录在线。历史安装缺乏维护资料时明确报告,不能用同名最新指南猜卸载命令。
|
||||||
|
|
||||||
|
## 执行与核验
|
||||||
|
|
||||||
|
依照应用自己的 install.md 和目标环境执行。固定版本指南中的旧 bookkeeping 部分不覆盖本协议:软件命令按该指南,记账只能使用下面的观察请求。不要为了兼容旧说明伪造安装中记录或改走 MCP 注册。
|
||||||
|
|
||||||
|
先检查实际包名、版本、位置与指南要求的自检。只能提交当时确认的 `present` 或 `absent`;失败、不明、设备离线不构成“已移除”。没有结果就保留旧历史记录。升级核验失败不自动宣称旧版可用。
|
||||||
|
|
||||||
|
## 提交观察
|
||||||
|
|
||||||
|
`PATCH /api/installed-entries/<entryId>/<deviceId>`,路径段应正常 URL 编码,body:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"material": { "protocol": "application-observation-v1", "sourceId": "registry:official", "entryId": "desirecore-control", "deviceId": "local", "action": "manage" },
|
||||||
|
"expectedRevision": "使用此次读取返回的修订值",
|
||||||
|
"requestId": "为本次提交生成一个 UUID,重试保持不变",
|
||||||
|
"deviceName": "本机",
|
||||||
|
"observation": {
|
||||||
|
"result": "present",
|
||||||
|
"observedAt": "实际核验时取得的 ISO 时间",
|
||||||
|
"location": "实际核验的安装目录或部署位置",
|
||||||
|
"evidenceRefs": ["当前工具执行结果或已有会话日志的可追溯引用"],
|
||||||
|
"notes": "可选的简短维护信息,不含秘密或自动执行指令"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
示例中的占位文字不能直接提交。首次安装前没有精确记录时,expectedRevision 为服务端返回的 `none`。observedAt 由实际观察决定;recordedAt 由服务端记录,不要把任务开始时间当核验时间。版本来自固定资料,只有实际匹配时才提交。
|
||||||
|
|
||||||
|
限制:location 最多 2048 字符、不能含换行/NUL;evidenceRefs 1–8 个,每项最多 512 字符;notes 最多 2000 字符。不填 runtimeStatus、operationId、pending 字段或 catalogReceipt。证据引用由 Agent 报告,服务端校验结构和记录一致性,不声称验证引用内容或软件真实安全。
|
||||||
|
|
||||||
|
成功返回新的 revision、replayed 和当前记录摘要。相同 requestId+相同 body 的当前结果可重复读取,且不重写时间;同 ID 不同 body 拒绝。旧提交已被后续修订取代时,不换 ID/修订强行重提,也不承诺任意历史请求的全局去重。
|
||||||
|
|
||||||
|
## 重读和失败
|
||||||
|
|
||||||
|
使用 `GET /api/installed-entries/<entryId>/<deviceId>?sourceId=<精确来源>` 取得小范围 revision/record;不要反复拉取全部账本和完整指南。`record=null` 表示未登记,不证明软件不存在;`legacy=true` 是未迁移历史记录。
|
||||||
|
|
||||||
|
提交响应丢失:先重读;当前 requestId 相同可用原正文重试。修订变化:观察实际软件再决定是否发起新的核验,禁止只替换 expectedRevision。写入失败:说明软件可能已修改但记录未完成,不重执行安装。具体恢复见 recovery.md。
|
||||||
|
|
||||||
|
应用正常停止后无需改观察记录。卸载默认保留用户数据和凭据;删除这些材料需要独立确认。注册内部服务也属于单独任务,应用安装不派生新的内部 MCP/HTTP 服务。
|
||||||
18
skills/app-install-manager/references/recovery.md
Normal file
18
skills/app-install-manager/references/recovery.md
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# 应用管理恢复原则
|
||||||
|
|
||||||
|
结果不明不等于失败,更不等于没有副作用。下面是判断规则,不是自动恢复状态机。
|
||||||
|
|
||||||
|
| 观察 | 处理 |
|
||||||
|
| --- | --- |
|
||||||
|
| Shell 超时、设备断线 | 重新观察已执行命令的结果和进程;不直接重跑 |
|
||||||
|
| 下载摘要不符 | 不安装,不换来源绕过;报告并等待明确选择 |
|
||||||
|
| 应用已安装,PATCH 失败/响应丢失 | 重读精确记录,必要时原 requestId+原 body 重试记账;不重新安装 |
|
||||||
|
| 卸载完成但 PATCH 失败 | 先确认已移除,再补记;不再次清理用户目录 |
|
||||||
|
| application_revision_conflict | 重新读记录和软件现状;不拿旧证据只替换 expectedRevision |
|
||||||
|
| application_request_id_conflict | 同 ID 参数不一致,停止;不能自动换 ID 掩盖错误 |
|
||||||
|
| application_legacy_source_unresolved | 旧记录来源不明确,不能静默认领为官方应用 |
|
||||||
|
| 资料或 API 不可用 | 保留已有历史记录,报告不支持/离线;不绕过 API 写文件 |
|
||||||
|
| 升级中途失败 | 检查新旧软件现状后提出恢复措施;不宣称自动回滚 |
|
||||||
|
| 用户取消、任务中断 | 不结算安装中状态,因为应用请求没有预写;后续恢复时重新核验 |
|
||||||
|
|
||||||
|
软件副作用、证据判断、记录写入并非同一事务。服务端保证的是受支持写入口下的精确定位、修订比较及原子提交;不要把成功记录夸大为软件运行保证或命令恰好执行一次。
|
||||||
@@ -640,8 +640,22 @@ test('Service uninstall authorizes only exact active service receipt and runtime
|
|||||||
}).deleteAllowed, false)
|
}).deleteAllowed, false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('Skill-first 正文短而完整、按需提供既有 HTTP 工具、不加载旧 App 状态机', async () => {
|
||||||
|
const text = await readFile(skillPath, 'utf8')
|
||||||
|
assert.ok(text.split('\n').length <= 60)
|
||||||
|
assert.match(text, /disable-model-invocation: true/)
|
||||||
|
assert.match(text, /tools: \[HttpRequest\]/)
|
||||||
|
assert.match(text, /不预写 installing\/reinstalling\/uninstalling/)
|
||||||
|
assert.match(text, /资料不是权限/)
|
||||||
|
assert.match(text, /先观察/)
|
||||||
|
assert.doesNotMatch(text, /tools:.*ResolveApplication|tools:.*RecordApplication/)
|
||||||
|
for (const name of ['recording-api.md', 'recovery.md', 'legacy-services.md']) {
|
||||||
|
assert.ok((await readFile(join(here, '..', 'references', name), 'utf8')).length > 0)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
test('Skill contract has no fixed official/local Registry fallback and resolves before execution', async () => {
|
test('Skill contract has no fixed official/local Registry fallback and resolves before execution', async () => {
|
||||||
const skill = await readFile(skillPath, 'utf8')
|
const skill = await readFile(join(here, '..', 'references', 'legacy-services.md'), 'utf8')
|
||||||
assert.doesNotMatch(skill, /registry\/official\/entries/)
|
assert.doesNotMatch(skill, /registry\/official\/entries/)
|
||||||
assert.match(skill, /POST \/api\/registry\/acquisitions\/resolve/)
|
assert.match(skill, /POST \/api\/registry\/acquisitions\/resolve/)
|
||||||
assert.match(skill, /禁止 fallback/)
|
assert.match(skill, /禁止 fallback/)
|
||||||
|
|||||||
Reference in New Issue
Block a user