停用/恢复文案说清密钥不会自动回来 + 原子部署脚本
## 起因 本会话踩到一次:浏览器测试点了 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:
@ -411,6 +411,8 @@ export async function adminSetAgentStatus(agentName: string, disabled: boolean)
|
||||
agent_name: string;
|
||||
disabled: boolean;
|
||||
keys_revoked?: number;
|
||||
/** 恢复路径为 true:停用时撤销的密钥不会自动回来,必须重新签发 */
|
||||
needs_new_key?: boolean;
|
||||
detail: string;
|
||||
}>(
|
||||
'PUT',
|
||||
|
||||
@ -77,12 +77,17 @@ export default function QuotaPanel() {
|
||||
const result = await api.adminSetAgentStatus(name, !currentDisabled);
|
||||
await load();
|
||||
setConfirming(null);
|
||||
// 撤销了几把密钥是操作结果里最有信息量的部分 —— 恢复后要重新签发几把,
|
||||
// 撤销了几把密钥是停用操作里最有信息量的部分 —— 恢复后要重新签发几把,
|
||||
// 只说「已停用」的话用户不知道还有这一步。
|
||||
const revoked = typeof result.keys_revoked === 'number' && result.keys_revoked > 0
|
||||
? `(已撤销 ${result.keys_revoked} 把密钥)`
|
||||
: '';
|
||||
setNotice(`${name} ${result.disabled ? '已停用' : '已恢复'}${revoked}`);
|
||||
// 恢复路径必须把「密钥不会自动回来」说出口。否则用户点完恢复就算完事,
|
||||
// 而插件拿着已撤销的密钥无限重试并被 401 —— 本会话就踩过:
|
||||
// opencode 被停用后拿旧密钥重试了 18 小时,gateway 日志里 2690 次 401。
|
||||
setNotice(result.disabled
|
||||
? `${name} 已停用${revoked}`
|
||||
: `${name} 已恢复为离线。密钥不会自动回来 —— 请到「密钥」面板重新签发一把并写进插件配置,否则它会一直被拒(401)。`);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
@ -115,8 +120,8 @@ export default function QuotaPanel() {
|
||||
插件自动转发的最终总结与权限询问不占用预算。
|
||||
<br />
|
||||
<strong className="font-medium text-gray-600">停用</strong>:撤销该 Agent 的全部密钥、
|
||||
从地址补全与联系人里隐藏,并拒绝它重新注册。
|
||||
<span className="text-gray-400">邮件、会话、模型范围全部保留,随时可恢复。</span>
|
||||
从地址补全与联系人里隐藏,拒绝它重新注册,并让别人发信给它时收到 409。
|
||||
<span className="text-gray-400">邮件、会话、模型范围全部保留,随时可恢复 —— 但密钥不会自动回来,恢复后需重新签发。</span>
|
||||
<br />
|
||||
<strong className="font-medium text-gray-600">删除</strong>:在停用的基础上清掉运行态(日历事件置 cancelled)。
|
||||
<span className="text-gray-400">邮件与会话保留(历史是审计凭据),此名字今后不可再注册。</span>
|
||||
@ -195,7 +200,7 @@ export default function QuotaPanel() {
|
||||
onClick={() => { setConfirming(s.agent_name); setConfirmAction('toggle'); }}
|
||||
disabled={busy === s.agent_name}
|
||||
title={isDisabled
|
||||
? '恢复此 Agent(恢复为离线;密钥需重新签发)'
|
||||
? '恢复此 Agent(恢复为离线;停用时撤销的密钥不会自动回来,必须重新签发)'
|
||||
: '停用此 Agent(撤销全部密钥并从补全里隐藏;邮件与会话保留,可恢复)'}
|
||||
className={`tap shrink-0 inline-flex items-center gap-1 text-[11px] px-1.5 py-0.5 rounded border ${
|
||||
isDisabled
|
||||
|
||||
Reference in New Issue
Block a user