停用/恢复文案说清密钥不会自动回来 + 原子部署脚本
## 起因 本会话踩到一次:浏览器测试点了 opencode 的「停用」按钮验证确认流程, 停用连带撤销全部密钥。插件从此拿着已撤销的密钥重试了 18 小时 (gateway 日志 2690 次 401),而 UI 只说了「已停用」。 恢复时同样没提示「密钥不会自动回来」,点完恢复以为就完事了。 ## 文案 后端 `AdminSetAgentStatus`: - 停用 detail 补一句「停用期间别人发信给它会收到 409」 - 恢复 detail 改成「停用时撤销的密钥不会自动回来 —— 必须在密钥面板 重新签发一把并写进该插件的配置,否则它会一直拿旧密钥重试并被拒(401)」 - 恢复响应加 `needs_new_key: true` 字段,前端可据此做更强的提示 前端 QuotaPanel:恢复成功的 notice 不再是「已恢复」四个字, 把重新签发这一步说全;面板说明与按钮 title 同步。 ## AdminDeleteAgent 两处修正 - 硬编码 `name == "jianf"` 改成按 `repo.IsHumanUser` 判定 —— 换管理员时 硬编码会失效,而人类账号不该走 Agent 删除端点 - detail 原来说「日历事件已保留」,实际 `DeleteAgent` 把 active 事件置为 cancelled(留着会由调度器一直触发,而发信人已不存在)。文案改成实际行为 ## deploy/redeploy-gateway.sh(新) 日常改后端不必重跑 install.sh(它重装 npm 依赖、重写 systemd 单元、 重新生成 env)。这个脚本做手工 `stop → cp → start` 不做的四件事: - `sqlite3 .backup` 备份数据库 + 立即 `PRAGMA integrity_check` 复核。 不用 cp:WAL 模式下 cp 会拿到主库与 -wal 不同步的快照 - `install -m 0755` 原子替换二进制。install 本质是 rename,要么完整 换掉要么原样不动;cp 是就地写入,中途失败会留下半截文件且旧的已被覆盖 - 旧二进制留档并打印可直接粘贴的回滚命令 - 后置验证清单:服务 active / /health 可达 / 近 2 分钟无 panic / SSE 重连计数。任一项不过**自动回滚**,不「先上着再修」 `--dry-run` 只打印动作,`--skip-tests` 急救用,`--skip-web` 跳过前端同步。 纪律来自 git-release-discipline skill 第五章。 ## 验证 - 脚本 dry-run + 真实跑通一次:备份 integrity_check=ok、原子替换、 9 个 SSE 客户端重连、验证四项全绿 - 停用/恢复文案线上实测;`DELETE /admin/agents/jianf` → 403「是人类用户」 - 端到端:jianf → opencode「部署脚本验收」→ 回信「部署验收 OK」 - 全量测试:gateway 全包 / web 176 / opencode 217 / pi 288 / dsh 241 / homeagent go ok;三方共用模块同源检查通过
This commit is contained in:
@ -239,15 +239,14 @@ type setAgentStatusRequest struct {
|
||||
|
||||
// PUT /api/v1/admin/agents/{name}/status —— 停用或恢复一个 Agent
|
||||
//
|
||||
// 停用是可逆的「归档」,不是删除:
|
||||
// 停用是可逆的「归档」,比 DELETE 轻一档:
|
||||
// - 邮件、会话、权限记录、转发幂等键全部保留(往来里有一半是人自己写的)
|
||||
// - 从地址补全、GET /agents、可授权范围里消失
|
||||
// - 全部密钥被撤销,插件拿不到新任务也发不出信
|
||||
// - 重新注册会被拒(否则插件下次启动就把它复活了)
|
||||
// - 别人发信给它得到 409(见 repo.RecipientDeliverable)
|
||||
//
|
||||
// 不提供彻底删除:Agent 名与人类用户名共用命名空间,删掉之后历史邮件的
|
||||
// from_name 指向一个不存在的名字,此时有人注册同名 Agent(或人类账号),
|
||||
// 那些旧邮件会看起来像是他发的。
|
||||
// 与 DELETE 的分工:停用留着运行态随时可恢复,删除清掉运行态且名字退役。
|
||||
func AdminSetAgentStatus(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.TrimSpace(chi.URLParam(r, "name"))
|
||||
if name == "" {
|
||||
@ -277,9 +276,12 @@ func AdminSetAgentStatus(w http.ResponseWriter, r *http.Request) {
|
||||
if req.Disabled {
|
||||
resp["keys_revoked"] = revoked
|
||||
resp["detail"] = "已停用。邮件与会话保留;该 Agent 的密钥已全部撤销," +
|
||||
"恢复后需要重新签发。"
|
||||
"恢复后需要重新签发。停用期间别人发信给它会收到 409。"
|
||||
} else {
|
||||
resp["detail"] = "已恢复为离线状态。需要重新签发密钥,插件连上后自动转为在线。"
|
||||
resp["needs_new_key"] = true
|
||||
resp["detail"] = "已恢复为离线状态。停用时撤销的密钥不会自动回来 —— " +
|
||||
"必须在「密钥」面板重新签发一把并写进该插件的配置," +
|
||||
"否则它会一直拿旧密钥重试并被拒(401)。"
|
||||
}
|
||||
JSON(w, http.StatusOK, resp)
|
||||
}
|
||||
@ -289,18 +291,24 @@ func AdminSetAgentStatus(w http.ResponseWriter, r *http.Request) {
|
||||
// 删除 Agent 的全部运行态,保留邮件历史。
|
||||
//
|
||||
// 取舍(见 PLUGIN-CONTRACT.md):
|
||||
// - 不做彻底删除(邮件是审计凭据,且 Agent 名与人类用户名共用命名空间)
|
||||
// - 名字立即不可重建(避免同名新注册冒用历史署名)
|
||||
// - 保留的邮件、会话与日历事件不会被级联删除
|
||||
// - 邮件与会话不删(是审计凭据,且往来里有一半是人自己写的)
|
||||
// - 它建的日历事件置 cancelled(留着会由调度器一直触发,发信人却已不存在)
|
||||
// - 名字立即不可重建(Agent 名与人类用户名共用命名空间,
|
||||
// 否则下一个同名注册者会看起来像是历史邮件的发信人)
|
||||
func AdminDeleteAgent(w http.ResponseWriter, r *http.Request) {
|
||||
name := strings.TrimSpace(chi.URLParam(r, "name"))
|
||||
if name == "" {
|
||||
Error(w, http.StatusBadRequest, "Missing agent name")
|
||||
return
|
||||
}
|
||||
// 内置管理员不可删
|
||||
if name == "jianf" {
|
||||
Error(w, http.StatusForbidden, "内置管理员不可删除")
|
||||
// 人类账号不走这个端点。硬编码某个用户名会在换管理员时失效,
|
||||
// 所以按「是不是人类用户」判定。
|
||||
if isHuman, hErr := repo.IsHumanUser(r.Context(), name); hErr != nil {
|
||||
Error(w, http.StatusInternalServerError, "Failed to check recipient")
|
||||
return
|
||||
} else if isHuman {
|
||||
Error(w, http.StatusForbidden,
|
||||
"\""+name+"\" 是人类用户,不能用这个端点删除(请去用户管理)")
|
||||
return
|
||||
}
|
||||
|
||||
@ -315,10 +323,10 @@ func AdminDeleteAgent(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
JSON(w, http.StatusOK, map[string]any{
|
||||
"agent_name": name,
|
||||
"agent_name": name,
|
||||
"keys_revoked": keysRevoked,
|
||||
"detail": "已删除。邮件、会话与日历事件已保留;" +
|
||||
"密钥与平台会话镜像已清除。" +
|
||||
"detail": "已删除。邮件与会话保留(审计凭据);" +
|
||||
"密钥、平台会话镜像、模型范围已清除;它建的日历事件已置为取消。" +
|
||||
"此名字今后不可再注册(历史邮件的署名由此不会被冒用)。",
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user