feat(gateway,admin-web): Skill 运行时安装 + backthink 打扰原因 + 建提醒入口(P0-19/P0-20/P0-22,审计 §4.3)
- P0-19:POST /admin/plugins/install 支持 `kind:"skill"`(此前一律 400)——yaml 或 path 二选一、 name 一致性校验、显式错误码(bad_request/name_mismatch/skill_parse_failed/name_conflict)、 装后以 kind:"skill" 出现在 GET /admin/plugins;**文件来源解析错误绝不回显文件内容**(变异验证实测确认 该泄露向量真实存在:yaml 解码器会带出出错值前几字符 ⇒ 只回显路径 + 通用原因,细节进日志) - P0-20:AdminBackthinkItem 补 route/detail/bypassed(DB 早已存,零迁移),面板 Backthink 展示 - P0-22:新增 POST /admin/automations/reminders(owner-only + 审计),at/message 必填、RFC3339 校验、 409 already_exists;过去时间不作 400(下次轮询立即触发) - 顺手修真缺陷:新建 id 原用 time.Now().UnixNano(),Windows 时钟粒度粗到毫秒 ⇒ 并行创建撞 id 得假 409; 改用 crypto/rand 128 位 hex(仓库既有 skills/store.go 范式) - 复核:gateway 包 build/vet/测试绿 + 前端 tsc 0 错;新增 12 用例(含 8 条非法输入显式码); **4 条变异反证**均已回滚 - 未做(登记):WS 帧侧的 route/detail 未在前端展示(`lib/wsParse.ts` 3 行,需放行);skill 未落 skills.Store(重启后消失,注入点在 cmd/cyrene);提醒无删除入口(CancelReminder 已存在、无路由)
This commit is contained in:
1 parent
3638ed7708
commit
decf59c1b4
12 files changed
+956
-24
No files matched your search
@@ -10,6 +10,22 @@ import { adminBackthink } from "../../lib/api";
|
||||
import { useAsyncData } from "../../lib/useAsyncData";
|
||||
import { BACKTHINK_SESSION_ID } from "../../store/sessionCache";
|
||||
|
||||
/** 推送路由的中文标签(后端 PushResult.Route 的取值域)。未知取值原样显示,
|
||||
* 绝不猜一个更好听的词。 */
|
||||
const PUSH_ROUTE_LABEL: Record<string, string> = {
|
||||
active_client: "活跃客户端",
|
||||
im_bridge: "IM 桥",
|
||||
rate_limited: "限频拦截",
|
||||
none: "无投递目标",
|
||||
};
|
||||
|
||||
/** 推送路由的色调:真的送出去了才用 ok,被限频拦住是 warn。 */
|
||||
function pushRouteTone(route: string): "ok" | "warn" | "neutral" {
|
||||
if (route === "active_client" || route === "im_bridge") return "ok";
|
||||
if (route === "rate_limited") return "warn";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
function BackthinkRow({ item }: { item: BackthinkItem }): React.ReactNode {
|
||||
const markPushed = useAppStore((s) => s.markBackthinkPushed);
|
||||
// 裁决 16.4.2:这条思考的链路图取自**后台思考会话**(`sess-backthink`,design/19 §1
|
||||
@@ -26,6 +42,8 @@ function BackthinkRow({ item }: { item: BackthinkItem }): React.ReactNode {
|
||||
<span className="font-medium">{item.trigger}</span>
|
||||
<Tag tone={item.shouldNotify ? "warn" : "neutral"}>{item.shouldNotify ? "需要打扰" : "静默"}</Tag>
|
||||
<Tag tone={item.pushed ? "ok" : "neutral"}>{item.pushed ? "已推送" : "未推送"}</Tag>
|
||||
{item.route ? <Tag tone={pushRouteTone(item.route)}>{PUSH_ROUTE_LABEL[item.route] ?? item.route}</Tag> : null}
|
||||
{item.bypassed ? <Tag tone="accent">事件直发</Tag> : null}
|
||||
<span className="ml-auto">
|
||||
<Button size="sm" variant="ghost" disabled={item.pushed} onClick={() => markPushed(item.id)}>
|
||||
{item.pushed ? "已推送" : "推送结果"}
|
||||
@@ -37,6 +55,9 @@ function BackthinkRow({ item }: { item: BackthinkItem }): React.ReactNode {
|
||||
{item.conclusion}
|
||||
</div>
|
||||
) : null}
|
||||
{item.detail ? (
|
||||
<div className="text-xs text-[var(--mu-text-lo)]">推送说明:{item.detail}</div>
|
||||
) : null}
|
||||
<button
|
||||
className="text-xs text-[var(--mu-accent-hi)]"
|
||||
onClick={() => setOpen((v) => !v)}
|
||||
@@ -117,7 +138,7 @@ export function BackthinkModule(): React.ReactNode {
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Notice>数据源:/ws/chat 下行 backthink / thinking 事件(实时,主驱动)+ 打开时一次拉取 GET /api/v1/admin/backthink 历史,两者按 id 去重、按时间倒序,新记录增量插入(不整页重渲)。链路导图取自后台思考会话(sess-backthink)的 graph_update 快照 —— 后端完整链路图始终发布,与管理端 Flow 页的「当前会话」图相互独立(裁决 16.4.2)。干预为后端 M10+ 提供,当前为只读展示。</Notice>
|
||||
<Notice>数据源:/ws/chat 下行 backthink / thinking 事件(实时,主驱动)+ 打开时一次拉取 GET /api/v1/admin/backthink 历史,两者按 id 去重、按时间倒序,新记录增量插入(不整页重渲)。「推送结果」的 route / detail / bypassed 由这次历史拉取带回(后端 PushResult 的如实投影);实时帧里同一信息裹在原始 PushResult 内、前端解析器尚未读取,因此本次会话新到的行可能暂时只显示「已推送/未推送」。链路导图取自后台思考会话(sess-backthink)的 graph_update 快照 —— 后端完整链路图始终发布,与管理端 Flow 页的「当前会话」图相互独立(裁决 16.4.2)。干预为后端 M10+ 提供,当前为只读展示。</Notice>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user