feat: ship enterprise control and desktop governance
This commit is contained in:
@@ -0,0 +1,75 @@
|
||||
# Telegram Gateway Integration Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 为 Boss 增加可用的 Telegram 对话入口,让 Telegram 用户能安全地与主 Agent 对话,并在主 Agent 异步完成任务后把结果回推回 Telegram。
|
||||
|
||||
**Architecture:** 在 `src/lib` 新增一个轻量 Telegram gateway,负责 update 归一化、访问控制、消息分流和 Telegram Bot API 调用;Next.js 暴露 webhook 与管理员配置接口,仍然复用现有 `boss-master-agent` 与 `boss-data` 主链,不复制对话业务。异步回复依赖现有 `/api/v1/master-agent/tasks/[taskId]/complete` 完成回调,在任务落盘后立即尝试发回 Telegram。
|
||||
|
||||
**Tech Stack:** Next.js App Router、TypeScript、文件型状态 `data/boss-state.json`、原生 `fetch`、Node test runner + `tsx --test`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 定义 Telegram 状态与配置模型
|
||||
|
||||
**Files:**
|
||||
- Create: `src/lib/telegram-gateway.ts`
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Test: `tests/telegram-gateway.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写 Telegram 配置/状态的失败测试**
|
||||
- [ ] **Step 2: 运行测试确认因接口缺失而失败**
|
||||
- [ ] **Step 3: 在 `boss-data.ts` 增加 Telegram 配置与任务外部回推字段**
|
||||
- [ ] **Step 4: 在 `telegram-gateway.ts` 增加归一化、mask、session key、chunk 等纯函数**
|
||||
- [ ] **Step 5: 重新运行测试确认通过**
|
||||
|
||||
### Task 2: 打通 webhook 与主 Agent 桥接
|
||||
|
||||
**Files:**
|
||||
- Create: `src/app/api/v1/integrations/telegram/webhook/route.ts`
|
||||
- Modify: `src/lib/telegram-gateway.ts`
|
||||
- Modify: `src/lib/boss-master-agent.ts`
|
||||
- Test: `tests/telegram-gateway.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写 webhook 接收、secret 校验、allowlist、主 Agent 快速回复 的失败测试**
|
||||
- [ ] **Step 2: 跑测试确认 RED**
|
||||
- [ ] **Step 3: 实现 webhook handler,把 Telegram 文本桥接到 `master-agent`**
|
||||
- [ ] **Step 4: 对快速回复直接回 Telegram;对排队任务保存外部回推目标**
|
||||
- [ ] **Step 5: 跑测试确认 GREEN**
|
||||
|
||||
### Task 3: 打通任务完成后的 Telegram 异步回推
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Modify: `src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts`
|
||||
- Modify: `src/lib/telegram-gateway.ts`
|
||||
- Test: `tests/telegram-gateway.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写任务完成后自动回推 Telegram 的失败测试**
|
||||
- [ ] **Step 2: 跑测试确认 RED**
|
||||
- [ ] **Step 3: 在任务模型中加入 `externalReplyTarget` 并在 complete route 中触发 Telegram 发信**
|
||||
- [ ] **Step 4: 补充发送成功/失败去重保护**
|
||||
- [ ] **Step 5: 跑测试确认 GREEN**
|
||||
|
||||
### Task 4: 增加管理员配置接口
|
||||
|
||||
**Files:**
|
||||
- Create: `src/app/api/v1/integrations/telegram/route.ts`
|
||||
- Modify: `src/lib/telegram-gateway.ts`
|
||||
- Test: `tests/telegram-integration-route.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写 GET/POST 配置接口失败测试**
|
||||
- [ ] **Step 2: 跑测试确认 RED**
|
||||
- [ ] **Step 3: 实现管理员鉴权、配置读取、保存、token 掩码与 getMe 探测**
|
||||
- [ ] **Step 4: 跑测试确认 GREEN**
|
||||
|
||||
### Task 5: 文档与回归验证
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
- Modify: `docs/architecture/api_and_service_inventory_cn.md`
|
||||
- Modify: `docs/architecture/current_runtime_and_deploy_status_cn.md`
|
||||
|
||||
- [ ] **Step 1: 更新 Boss 当前能力文档,写清 Telegram 接入方式、能力边界与部署方式**
|
||||
- [ ] **Step 2: 运行 `tsx --test`、`npm run lint`、`npm run build`**
|
||||
- [ ] **Step 3: 记录未完成项与后续扩展点(群聊策略、pairing、Feishu/Telegram 复用层)**
|
||||
274
docs/superpowers/plans/2026-04-21-codex-desktop-thread-sync.md
Normal file
274
docs/superpowers/plans/2026-04-21-codex-desktop-thread-sync.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# Codex Desktop Thread Sync Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 让 Boss App 发往单线程 Codex 会话的用户消息,在继续现有 `conversation_reply -> codex exec resume` 主链前,同步镜像进本机 Codex Desktop 的同一个线程历史。
|
||||
|
||||
**Architecture:** 服务端继续以 Boss 项目账本为主真相,但给普通单线程 `conversation_reply` 任务补齐 `sourceMessage*` 元数据和显式镜像开关。`local-agent` 在执行 `codex exec resume` 前,按 `targetCodexThreadRef` 解析目标 rollout 文件,先做一次本地 user_message append + 去重,再继续原有执行链。rollout 定位优先使用 `state_5.sqlite`,若本机 Codex CLI/Desktop 版本导致状态库不可用,则回退扫描 `~/.codex/sessions`;rollout 写入后尽量刷新 `threads.updated_at / updated_at_ms / has_user_event`,但不依赖 GUI 自动化。
|
||||
|
||||
**Tech Stack:** Next.js App Router, TypeScript, Node.js, sqlite, local-agent Node runtime, `tsx --test`, Node test runner
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 给 conversation task 补齐 Desktop 镜像元数据
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Modify: `src/lib/boss-master-agent.ts`
|
||||
- Modify: `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- Test: `tests/single-thread-message-execution.test.ts`
|
||||
|
||||
- [x] **Step 1: 写失败测试,要求普通单线程消息返回的任务带 sourceMessage 元数据**
|
||||
|
||||
```ts
|
||||
assert.equal(task?.sourceMessageId, message.id);
|
||||
assert.equal(task?.sourceMessageBody, "请同步一下当前阻塞情况");
|
||||
assert.equal(task?.sourceMessageSentAt, message.sentAt);
|
||||
assert.equal(task?.mirrorBossUserMessageToCodexDesktop, true);
|
||||
```
|
||||
|
||||
- [x] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `npx tsx --test tests/single-thread-message-execution.test.ts`
|
||||
Expected: FAIL,提示 `sourceMessageBody/sourceMessageSentAt/mirrorBossUserMessageToCodexDesktop` 不存在或断言失败。
|
||||
|
||||
- [x] **Step 3: 写最小实现**
|
||||
|
||||
在 `MasterAgentTask` 和状态序列化/反序列化里补字段:
|
||||
|
||||
```ts
|
||||
sourceMessageId?: string;
|
||||
sourceMessageBody?: string;
|
||||
sourceMessageSentAt?: string;
|
||||
mirrorBossUserMessageToCodexDesktop?: boolean;
|
||||
```
|
||||
|
||||
在 `queueThreadConversationReplyTask` 中透传:
|
||||
|
||||
```ts
|
||||
sourceMessageId: params.sourceMessageId,
|
||||
sourceMessageBody: params.sourceMessageBody,
|
||||
sourceMessageSentAt: params.sourceMessageSentAt,
|
||||
mirrorBossUserMessageToCodexDesktop:
|
||||
params.relayViaMasterAgent ? undefined : true,
|
||||
```
|
||||
|
||||
在消息 route 调用时补:
|
||||
|
||||
```ts
|
||||
const queuedTask = await queueThreadConversationReplyTask({
|
||||
projectId,
|
||||
requestMessageId: message.id,
|
||||
requestText: message.body,
|
||||
requestedBy: session.displayName || session.account,
|
||||
requestedByAccount: session.account,
|
||||
sourceMessageId: message.id,
|
||||
sourceMessageBody: message.body,
|
||||
sourceMessageSentAt: message.sentAt,
|
||||
});
|
||||
```
|
||||
|
||||
- [x] **Step 4: 运行测试确认通过**
|
||||
|
||||
Run: `npx tsx --test tests/single-thread-message-execution.test.ts`
|
||||
Expected: PASS
|
||||
|
||||
### Task 2: 新增 rollout writer,并确保重复任务不重复写 Desktop 线程
|
||||
|
||||
**Files:**
|
||||
- Create: `local-agent/codex-thread-rollout-writer.mjs`
|
||||
- Test: `tests/local-agent-codex-rollout-writer.test.mjs`
|
||||
|
||||
- [x] **Step 1: 写失败测试,约束 writer 会写入 user_message 且按 sourceMessageId 去重**
|
||||
|
||||
```js
|
||||
test("appendBossUserMessageToCodexThreadRollout writes one user_message event and dedupes by source message id", async () => {
|
||||
const first = await appendBossUserMessageToCodexThreadRollout({ ... });
|
||||
const second = await appendBossUserMessageToCodexThreadRollout({ ... });
|
||||
assert.equal(first.status, "written");
|
||||
assert.equal(second.status, "duplicate");
|
||||
});
|
||||
```
|
||||
|
||||
- [x] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `node --test tests/local-agent-codex-rollout-writer.test.mjs`
|
||||
Expected: FAIL,提示模块不存在或导出函数不存在。
|
||||
|
||||
- [x] **Step 3: 写最小实现**
|
||||
|
||||
实现 writer 逻辑:
|
||||
|
||||
```js
|
||||
export async function appendBossUserMessageToCodexThreadRollout(params) {
|
||||
const rolloutPath = await resolveThreadRolloutPath(params);
|
||||
const duplicate = await hasBossSourceMessageInRolloutTail(rolloutPath, params.sourceMessageId);
|
||||
if (duplicate) return { status: "duplicate", rolloutPath };
|
||||
const responseItem = JSON.stringify({
|
||||
timestamp: params.sentAt,
|
||||
type: "response_item",
|
||||
payload: {
|
||||
type: "message",
|
||||
role: "user",
|
||||
content: [{ type: "input_text", text: params.message }],
|
||||
},
|
||||
});
|
||||
const event = JSON.stringify({
|
||||
timestamp: params.sentAt,
|
||||
type: "event_msg",
|
||||
payload: {
|
||||
type: "user_message",
|
||||
message: params.message,
|
||||
images: [],
|
||||
local_images: [],
|
||||
text_elements: [],
|
||||
metadata: {
|
||||
bossSourceMessageId: params.sourceMessageId,
|
||||
bossMirroredFrom: "boss-app",
|
||||
},
|
||||
},
|
||||
});
|
||||
await appendFile(rolloutPath, `${responseItem}\n${event}\n`, "utf8");
|
||||
return { status: "written", rolloutPath };
|
||||
}
|
||||
```
|
||||
|
||||
- [x] **Step 4: 运行测试确认通过**
|
||||
|
||||
Run: `node --test tests/local-agent-codex-rollout-writer.test.mjs`
|
||||
Expected: PASS
|
||||
|
||||
### Task 3: 在 local-agent 执行 resume 前追加 Desktop 线程镜像
|
||||
|
||||
**Files:**
|
||||
- Modify: `local-agent/codex-task-runner.mjs`
|
||||
- Modify: `tests/local-agent-codex-task-runner.test.mjs`
|
||||
|
||||
- [x] **Step 1: 写失败测试,要求 prepare 阶段保留镜像计划,且 relay task 不启用**
|
||||
|
||||
```js
|
||||
assert.deepEqual(result.execution.desktopMirror, {
|
||||
enabled: true,
|
||||
sourceMessageId: "msg-1",
|
||||
sourceMessageBody: "请继续推进",
|
||||
});
|
||||
```
|
||||
|
||||
- [x] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `node --test tests/local-agent-codex-task-runner.test.mjs`
|
||||
Expected: FAIL,提示 `desktopMirror` 不存在。
|
||||
|
||||
- [x] **Step 3: 写最小实现**
|
||||
|
||||
在 `buildCodexTaskExecution` 返回值中增加:
|
||||
|
||||
```js
|
||||
desktopMirror: shouldMirrorBossUserMessageToDesktop(task)
|
||||
? {
|
||||
enabled: true,
|
||||
sourceMessageId: task.sourceMessageId,
|
||||
sourceMessageBody: task.sourceMessageBody,
|
||||
sourceMessageSentAt: task.sourceMessageSentAt,
|
||||
targetThreadRef,
|
||||
}
|
||||
: { enabled: false }
|
||||
```
|
||||
|
||||
其中 `shouldMirrorBossUserMessageToDesktop(task)` 需要保证:
|
||||
|
||||
- `task.taskType === "conversation_reply"`
|
||||
- `task.mirrorBossUserMessageToCodexDesktop === true`
|
||||
- `task.relayViaMasterAgent !== true`
|
||||
- `targetThreadRef/sourceMessageId/sourceMessageBody` 全部存在
|
||||
|
||||
- [x] **Step 4: 运行测试确认通过**
|
||||
|
||||
Run: `node --test tests/local-agent-codex-task-runner.test.mjs`
|
||||
Expected: PASS
|
||||
|
||||
### Task 4: 在实际任务执行前调用 rollout writer,并保持现有 resume/complete 主链不回归
|
||||
|
||||
**Files:**
|
||||
- Modify: `local-agent/server.mjs`
|
||||
- Modify: `tests/local-agent-codex-task-runner.test.mjs`
|
||||
- Modify: `tests/single-thread-message-execution.test.ts`
|
||||
|
||||
- [x] **Step 1: 写失败测试,要求 server 在 spawn codex 前先执行 rollout 镜像**
|
||||
|
||||
```js
|
||||
assert.equal(writerCalls.length, 1);
|
||||
assert.equal(writerCalls[0].sourceMessageId, "msg-1");
|
||||
assert.equal(writerCalls[0].message, "请继续推进");
|
||||
```
|
||||
|
||||
- [x] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run: `node --test tests/local-agent-codex-task-runner.test.mjs`
|
||||
Expected: FAIL,提示 writer 未被调用。
|
||||
|
||||
- [x] **Step 3: 写最小实现**
|
||||
|
||||
在 `runMasterAgentTask` 的 `spawn("codex", ...)` 前增加:
|
||||
|
||||
```js
|
||||
if (codexExecution.desktopMirror?.enabled) {
|
||||
await appendBossUserMessageToCodexThreadRollout({
|
||||
stateDbPath: config.codexStateDbPath,
|
||||
targetThreadRef: codexExecution.desktopMirror.targetThreadRef,
|
||||
sourceMessageId: codexExecution.desktopMirror.sourceMessageId,
|
||||
message: codexExecution.desktopMirror.sourceMessageBody,
|
||||
sentAt: codexExecution.desktopMirror.sourceMessageSentAt ?? new Date().toISOString(),
|
||||
});
|
||||
}
|
||||
```
|
||||
|
||||
如果镜像失败:
|
||||
|
||||
- 不吞掉错误
|
||||
- 直接按任务失败返回,让链路保持 fail-closed
|
||||
|
||||
- [x] **Step 4: 运行定向测试确认通过**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node --test tests/local-agent-codex-rollout-writer.test.mjs
|
||||
node --test tests/local-agent-codex-task-runner.test.mjs
|
||||
npx tsx --test tests/single-thread-message-execution.test.ts
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
### Task 5: 回归验证与文档同步
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`(如需补一句运行时说明)
|
||||
- Modify: `docs/architecture/current_runtime_and_deploy_status_cn.md`
|
||||
- Modify: `docs/architecture/api_and_service_inventory_cn.md`
|
||||
|
||||
- [x] **Step 1: 跑仓库要求的基线验证**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: PASS
|
||||
|
||||
- [x] **Step 2: 补文档**
|
||||
|
||||
在运行时/服务清单文档补一句:
|
||||
|
||||
- Boss 普通线程单聊现在会在 local-agent 执行 `codex exec resume` 前,把 Boss 用户消息镜像进目标 Codex Desktop 线程 rollout
|
||||
- 该能力仅针对已绑定 `codexThreadRef` 的单线程会话
|
||||
|
||||
- [x] **Step 3: 完成最终自检**
|
||||
|
||||
检查:
|
||||
|
||||
- 没有把主 Agent 会话或 takeover relay 错写进 Desktop 子线程
|
||||
- 没有重复写 rollout
|
||||
- 现有 heartbeat 读取 recent desktop replies 仍可工作
|
||||
176
docs/superpowers/plans/2026-04-22-boss-computer-control-hub.md
Normal file
176
docs/superpowers/plans/2026-04-22-boss-computer-control-hub.md
Normal file
@@ -0,0 +1,176 @@
|
||||
# Boss 统一电脑控制中枢 Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 让 Boss 聊天成为统一电脑控制入口。用户在主 Agent 或线程聊天里提出需求后,系统能在“普通讨论 / Codex 开发 / 浏览器自动化 / 桌面控制”之间自动选路,并通过本机 `local-agent` 执行。
|
||||
|
||||
**Architecture:** 继续复用 Boss 现有消息账本、`MasterAgentTask` 队列、执行底座和 `local-agent`。本次新增 `browser_control / desktop_control` 两类正式任务与 runtime,并让主 Agent 先做执行意图判断,再把请求路由到 Codex 线程、browser automation、computer use 或直接回复。
|
||||
|
||||
**Tech Stack:** Next.js App Router, TypeScript, Node.js local-agent, existing execution abstraction, `tsx --test`, Node test runner
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 扩展执行类型与任务模型
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/execution/types.ts`
|
||||
- Modify: `src/lib/execution/tool-registry.ts`
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Test: `tests/computer-control-task-model.test.ts`
|
||||
|
||||
- [ ] **Step 1: 先写失败测试,要求任务模型支持 browser_control / desktop_control**
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/computer-control-task-model.test.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 实现最小模型扩展**
|
||||
|
||||
补齐:
|
||||
|
||||
- `ExecutionRequestKind`
|
||||
- `ExecutionToolName`
|
||||
- `MasterAgentTaskType`
|
||||
- `MasterAgentTask` 的 `intentCategory / runtimeKind / riskLevel / confirmationPolicy` 等字段
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
### Task 2: 给主 Agent 增加控制意图分类
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-master-agent.ts`
|
||||
- Modify: `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- Test: `tests/master-agent-control-intent-routing.test.ts`
|
||||
|
||||
- [ ] **Step 1: 先写失败测试,覆盖讨论 / 开发 / 浏览器 / 桌面四类消息**
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/master-agent-control-intent-routing.test.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 实现最小路由逻辑**
|
||||
|
||||
要求:
|
||||
|
||||
- 讨论类继续直接回复
|
||||
- 开发类仍优先走现有 `conversation_reply`
|
||||
- 浏览器类排 `browser_control`
|
||||
- 桌面类排 `desktop_control`
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
### Task 3: 给 local-agent 增加 browser/computer use runtime 分流骨架
|
||||
|
||||
**Files:**
|
||||
- Create: `local-agent/browser-control-task-runner.mjs`
|
||||
- Create: `local-agent/computer-use-task-runner.mjs`
|
||||
- Modify: `local-agent/server.mjs`
|
||||
- Test: `tests/local-agent-browser-control-runner.test.mjs`
|
||||
- Test: `tests/local-agent-computer-use-runner.test.mjs`
|
||||
|
||||
- [ ] **Step 1: 先写失败测试,要求 local-agent 能识别新任务类型并分流**
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node --test tests/local-agent-browser-control-runner.test.mjs
|
||||
node --test tests/local-agent-computer-use-runner.test.mjs
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 实现最小分流骨架**
|
||||
|
||||
第一版先做:
|
||||
|
||||
- browser_control: 返回标准化占位结果或接入现有 browser runtime
|
||||
- desktop_control: 返回标准化占位结果或接入 computer-use runtime
|
||||
|
||||
要求:
|
||||
|
||||
- 不能影响现有 `conversation_reply / dispatch_execution`
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
### Task 4: 增加风险分级与确认策略骨架
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/execution/permission-policy.ts`
|
||||
- Modify: `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- Test: `tests/computer-control-permission-policy.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,验证 low/medium/high 三档行为**
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/computer-control-permission-policy.test.ts
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 实现最小确认策略**
|
||||
|
||||
要求:
|
||||
|
||||
- `low` 默认可执行
|
||||
- `medium` 标记需轻确认
|
||||
- `high` 标记需强确认
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
### Task 5: 前台返回执行模式元数据并做回归
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Test: `tests/project-message-execution-mode.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,要求消息返回 executionMode/riskLevel**
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
- [ ] **Step 3: 实现最小返回结构**
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
### Task 6: 基线验证与文档同步
|
||||
|
||||
**Files:**
|
||||
- Modify: `README.md`
|
||||
- Modify: `docs/architecture/current_runtime_and_deploy_status_cn.md`
|
||||
- Modify: `docs/architecture/api_and_service_inventory_cn.md`
|
||||
|
||||
- [ ] **Step 1: 同步运行时文档**
|
||||
|
||||
- [ ] **Step 2: 跑仓库基线验证**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 跑新增与相关回归测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/computer-control-task-model.test.ts
|
||||
npx tsx --test tests/master-agent-control-intent-routing.test.ts
|
||||
npx tsx --test tests/computer-control-permission-policy.test.ts
|
||||
npx tsx --test tests/project-message-execution-mode.test.ts
|
||||
node --test tests/local-agent-browser-control-runner.test.mjs
|
||||
node --test tests/local-agent-computer-use-runner.test.mjs
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 如涉及 Android 前台行为,再补真机复核**
|
||||
168
docs/superpowers/plans/2026-04-27-boss-enterprise-hardening.md
Normal file
168
docs/superpowers/plans/2026-04-27-boss-enterprise-hardening.md
Normal file
@@ -0,0 +1,168 @@
|
||||
# Boss Enterprise Hardening Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** 把 Boss 从 MVP 管理后台推进到 To B 可交付的第一批企业化硬化版本。
|
||||
|
||||
**Architecture:** 保留当前 `BossState` 文件账本作为兼容层,同时新增可切换的状态存储适配层、企业认证默认值、租户隔离守卫、风险 SLA 扫描和增强审计字段。所有高危行为先用测试锁住,再通过 `/admin` 和 `/api/v1/*` 渐进暴露。
|
||||
|
||||
**Tech Stack:** Next.js App Router、TypeScript、Node `node:test`、现有文件型状态、可选 PostgreSQL JSONB 单行快照、Ant Design 管理后台。
|
||||
|
||||
---
|
||||
|
||||
## 执行状态
|
||||
|
||||
- 2026-04-27:已补齐认证安全默认值、状态存储适配层、租户强隔离、风险 SLA 通知账本、后台高危操作、增强审计字段和文档更新。
|
||||
- 待最终收口:全量 `tests/*.test.ts`、`npm run lint`、`npm run build`、`npm audit` 和服务器 smoke。
|
||||
|
||||
### Task 1: 认证安全默认值
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/app/api/auth/login/route.ts`
|
||||
- Modify: `src/app/auth/login/page.tsx`
|
||||
- Modify: `src/components/app-ui.tsx`
|
||||
- Test: `tests/auth-login-hardening-route.test.ts`
|
||||
|
||||
- [x] **Step 1: Write failing tests**
|
||||
|
||||
覆盖默认关闭免验证登录、显式 `BOSS_AUTH_AUTO_LOGIN=1` 才允许免验证、账号密码登录仍可用。
|
||||
|
||||
- [ ] **Step 2: Implement minimal auth hardening**
|
||||
|
||||
把 `shouldAllowTemporaryAutoLogin()` 改成只接受 `1 / true / yes`;登录页文案从“临时免验证”改成企业登录;固定验证码提示只在 fixed delivery 模式下展示。
|
||||
|
||||
- [ ] **Step 3: Verify**
|
||||
|
||||
Run: `npx tsx --test tests/auth-login-hardening-route.test.ts tests/auth-session-governance.test.ts`
|
||||
|
||||
### Task 2: 状态存储适配层
|
||||
|
||||
**Files:**
|
||||
- Create: `src/lib/boss-state-store.ts`
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Create: `scripts/postgres-state-schema.sql`
|
||||
- Test: `tests/boss-state-store.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
覆盖默认 `file` 模式、`BOSS_STATE_STORE=postgres` 但无 `BOSS_DATABASE_URL` 时 fail closed、PostgreSQL SQL schema 包含 `boss_state_snapshots` 和 `jsonb`。
|
||||
|
||||
- [ ] **Step 2: Implement adapter**
|
||||
|
||||
新增 `createBossStateStore()`,默认文件读写;PostgreSQL 模式先通过动态 `pg` 依赖实现单行 JSONB 快照,未安装或未配置时给出明确错误。
|
||||
|
||||
- [ ] **Step 3: Wire boss-data**
|
||||
|
||||
让 `readState/writeState/loadPersistedStateRaw` 通过 store 读写,保持 `mutateState` 事务队列不变。
|
||||
|
||||
### Task 3: 租户强隔离
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-permissions.ts`
|
||||
- Modify: `src/lib/boss-admin-overview.ts`
|
||||
- Test: `tests/rbac-tenant-isolation.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
同公司账号能访问授权设备 / 项目;不同公司账号即使误授 grant 也不能访问;未绑定公司历史数据继续按 owner/grant 兼容。
|
||||
|
||||
- [ ] **Step 2: Implement tenant guard**
|
||||
|
||||
在非 `highest_admin` 路径中加入 `companyId` 比对。设备优先读 `device.companyId`,账号读 `authAccount.companyId`,项目通过绑定设备推导公司集合。
|
||||
|
||||
### Task 4: 风险 SLA 和通知账本
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Create: `src/lib/boss-risk-notifications.ts`
|
||||
- Create: `src/app/api/v1/admin/risks/scan/route.ts`
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
- Test: `tests/admin-risk-sla-notifications-route.test.ts`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
设置过期 SLA 后扫描会生成通知;重复扫描不重复生成同一风险通知;通知会进入管理后台总览。
|
||||
|
||||
- [x] **Step 2: Implement notification model**
|
||||
|
||||
新增 `adminNotifications`,字段包含 `notificationId / kind / severity / companyId / riskId / title / body / status / createdAt / acknowledgedAt`。
|
||||
|
||||
- [x] **Step 3: Implement scanner**
|
||||
|
||||
- [x] **Step 4: Implement dispatch and timeline**
|
||||
|
||||
新增 `/api/v1/admin/notifications/dispatch`,支持 sendmail 邮件通道或 disabled 模式状态落账;新增 `adminRiskTimeline` 记录通知生成、派发和人工处置。
|
||||
|
||||
扫描 `opsFaults` 和 `threadContextAlerts`,对 `slaDueAt < now` 且未关闭的风险生成 `risk_sla_overdue` 通知。
|
||||
|
||||
### Task 5: 后台高危操作补齐
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Modify: `src/app/api/v1/admin/access/route.ts`
|
||||
- Modify: `src/components/admin/admin-access-panel.tsx`
|
||||
- Test: `tests/admin-access-enterprise-ops-route.test.ts`
|
||||
|
||||
- [x] **Step 1: Write failing tests**
|
||||
|
||||
最高管理员可停用公司、重置子账号密码、批量导入预检;停用公司会禁用该公司普通子账号并撤销会话;最高管理员账号不可被公司停用波及。
|
||||
|
||||
- [x] **Step 2: Implement actions**
|
||||
|
||||
新增 `set_company_status / reset_account_password / preview_bulk_import_accounts` 三个 action,并接到 PC 管理后台。
|
||||
|
||||
- [x] **Step 3: Implement enterprise UX polish**
|
||||
|
||||
补齐公司套餐、合同到期、客户成功、CSV 导入、危险操作确认和子账号 MFA 开关。
|
||||
|
||||
### Task 6: 增强审计字段
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Modify: admin action routes under `src/app/api/v1/admin/*`
|
||||
- Test: `tests/admin-audit-compliance.test.ts`
|
||||
|
||||
- [x] **Step 1: Write failing tests**
|
||||
|
||||
高危动作写入 `ipAddress / userAgent / beforeJson / afterJson / requestId`;API 响应不泄露 `passwordHash / apiKey / sessionToken / restoreToken`。
|
||||
|
||||
- [x] **Step 2: Implement audit metadata**
|
||||
|
||||
扩展 `PermissionAuditLog`,新增 `buildRequestAuditMeta(request)`,所有 admin mutation route 传入审计上下文。
|
||||
|
||||
- [x] **Step 3: Implement auth hardening**
|
||||
|
||||
补齐浏览器 CSRF 基础防护、restore token 轮换和子账号 MFA 校验。
|
||||
|
||||
### Task 7: Docs, regression, deploy
|
||||
|
||||
**Files:**
|
||||
- Modify: `docs/architecture/current_runtime_and_deploy_status_cn.md`
|
||||
- Modify: `docs/architecture/api_and_service_inventory_cn.md`
|
||||
- Modify: `docs/architecture/admin_refine_backoffice_cn.md`
|
||||
|
||||
- [x] **Step 1: Update docs**
|
||||
|
||||
记录企业登录默认值、PostgreSQL 切换方式、租户隔离规则、风险 SLA 扫描和后台高危操作。
|
||||
|
||||
- [ ] **Step 2: Full verification**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/*.test.ts
|
||||
npm run lint
|
||||
npm run build
|
||||
npm audit
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Deploy and smoke**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
./scripts/deploy-server.sh
|
||||
curl -fsS https://boss.hyzq.net/api/health
|
||||
```
|
||||
|
||||
Post-deploy verify `/admin`、`/api/v1/admin/access`、`/api/v1/admin/overview`、`/api/v1/admin/risks/scan`。
|
||||
158
docs/superpowers/plans/2026-04-30-admin-backoffice-redesign.md
Normal file
158
docs/superpowers/plans/2026-04-30-admin-backoffice-redesign.md
Normal file
@@ -0,0 +1,158 @@
|
||||
# Boss Admin Backoffice Redesign Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Rebuild `/admin` into a PC To B operations backoffice with a dashboard, customer workspace, permission workspace, and risk/governance command center.
|
||||
|
||||
**Architecture:** Keep the existing Next.js App Router route and existing admin APIs. Refactor the current client shell into focused React components under `src/components/admin/`, reusing `/api/v1/admin/overview`, `/api/v1/admin/access`, `/api/v1/admin/risks/actions`, and `/api/v1/admin/skills/requests`.
|
||||
|
||||
**Tech Stack:** Next.js 16 App Router, React 19, Ant Design, `@refinedev/core`, TypeScript source tests with `node:test`.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Source Tests For New Admin Structure
|
||||
|
||||
**Files:**
|
||||
- Modify: `tests/admin-refine-page.test.ts`
|
||||
|
||||
- [ ] **Step 1: Update the admin structure assertions**
|
||||
|
||||
Replace the old page shell expectations with assertions for these strings:
|
||||
|
||||
```ts
|
||||
for (const title of ["平台运营驾驶舱", "客户与账号", "授权工作台", "风险与治理"]) {
|
||||
assert.match(source, new RegExp(title));
|
||||
}
|
||||
for (const title of ["今日待处理", "客户健康排行", "关键风险队列", "节点健康"]) {
|
||||
assert.match(source, new RegExp(title));
|
||||
}
|
||||
assert.doesNotMatch(source, /window\.prompt/);
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the focused test**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/admin-refine-page.test.ts
|
||||
```
|
||||
|
||||
Expected: fail until the component is refactored.
|
||||
|
||||
### Task 2: Admin Shell And Navigation
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
|
||||
- [ ] **Step 1: Replace top tabs with PC backoffice navigation**
|
||||
|
||||
Implement a left-side navigation with four keys: `dashboard`, `customers`, `permissions`, `governance`.
|
||||
|
||||
- [ ] **Step 2: Keep Refine data provider mounted**
|
||||
|
||||
Keep:
|
||||
|
||||
```tsx
|
||||
<Refine dataProvider={createBossAdminDataProvider(initialOverview ?? undefined)} resources={resources}>
|
||||
```
|
||||
|
||||
Expected: existing data provider tests continue to pass.
|
||||
|
||||
### Task 3: Dashboard
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
|
||||
- [ ] **Step 1: Build metric cards**
|
||||
|
||||
Use existing `summary`, `companies`, `devices`, `risks`, `notifications`.
|
||||
|
||||
- [ ] **Step 2: Build key queues**
|
||||
|
||||
Dashboard must include:
|
||||
|
||||
```tsx
|
||||
"今日待处理"
|
||||
"客户健康排行"
|
||||
"关键风险队列"
|
||||
"节点健康"
|
||||
"最近事件"
|
||||
```
|
||||
|
||||
Expected: no big full-width table as the first visual object.
|
||||
|
||||
### Task 4: Customer And Permission Workspaces
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
- Reuse: `src/components/admin/admin-access-panel.tsx`
|
||||
|
||||
- [ ] **Step 1: Add customer overview section**
|
||||
|
||||
Show company table, account table, and customer onboarding hints.
|
||||
|
||||
- [ ] **Step 2: Mount `AdminAccessPanel` under 授权工作台**
|
||||
|
||||
Keep the existing working mutation path, but wrap it in clearer page copy and narrower visual hierarchy.
|
||||
|
||||
### Task 5: Risk Command Center
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
|
||||
- [ ] **Step 1: Replace prompt-based actions**
|
||||
|
||||
Remove `window.prompt` for assigning owner and SLA. Use controlled inline inputs and buttons.
|
||||
|
||||
- [ ] **Step 2: Keep existing risk actions**
|
||||
|
||||
Continue posting:
|
||||
|
||||
```ts
|
||||
{ riskId, action: "assign_owner", ownerAccount }
|
||||
{ riskId, action: "set_sla", slaDueAt }
|
||||
{ riskId, action: "ack" }
|
||||
{ riskId, action: "resolve" }
|
||||
{ riskId, action: "create_repair_ticket" }
|
||||
```
|
||||
|
||||
### Task 6: Skill Governance
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/components/admin/boss-admin-app.tsx`
|
||||
- Reuse: `src/components/admin/admin-skill-lifecycle-panel.tsx`
|
||||
|
||||
- [ ] **Step 1: Move Skill lifecycle panel under 风险与治理**
|
||||
|
||||
Use a nested Ant Design `Tabs` with `风险战情室` and `Skill 生命周期`.
|
||||
|
||||
### Task 7: Verification
|
||||
|
||||
**Files:**
|
||||
- Test: `tests/admin-refine-page.test.ts`
|
||||
- Test: `tests/admin-overview-route.test.ts`
|
||||
- Test: `tests/admin-risk-actions-route.test.ts`
|
||||
- Test: `tests/admin-skill-lifecycle-panel-source.test.ts`
|
||||
|
||||
- [ ] **Step 1: Run focused admin tests**
|
||||
|
||||
```bash
|
||||
npx tsx --test tests/admin-refine-page.test.ts tests/admin-overview-route.test.ts tests/admin-risk-actions-route.test.ts tests/admin-skill-lifecycle-panel-source.test.ts
|
||||
```
|
||||
|
||||
Expected: all pass.
|
||||
|
||||
- [ ] **Step 2: Run lint/build**
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: both pass.
|
||||
|
||||
## Self-review
|
||||
|
||||
- Spec coverage: covers dashboard, customer workspace, permission workspace, risk command center, Skill governance, testing.
|
||||
- Placeholder scan: no TBD/TODO language.
|
||||
- Type consistency: component names and existing endpoints match current code.
|
||||
24
docs/superpowers/plans/2026-04-30-yudao-independent-admin.md
Normal file
24
docs/superpowers/plans/2026-04-30-yudao-independent-admin.md
Normal file
@@ -0,0 +1,24 @@
|
||||
# YuDao 风格企业后台独立化实施计划
|
||||
|
||||
日期:2026-04-30
|
||||
|
||||
## 执行批次
|
||||
|
||||
第一批只做独立后台骨架和 BFF 契约,确保可以继续迭代而不影响现有 `/admin`。
|
||||
|
||||
## 步骤
|
||||
|
||||
1. 新增 `/api/v1/admin/backoffice` 的测试,覆盖 `highest_admin` 鉴权、YuDao 风格菜单、租户/账号/角色/资源/风险/审计数据,以及敏感字段不泄露。
|
||||
2. 新增 `apps/boss-admin-web` 源码测试,覆盖 Vue/Vite/Ant Design Vue 工程骨架、API 地址、登录态携带、核心页面文案和根工程隔离。
|
||||
3. 实现 Admin BFF,把 `buildAdminOverview(state)`、`BOSS_PERMISSION_TEMPLATES`、设备、项目、Skill、审计记录聚合为独立后台契约。
|
||||
4. 搭建独立 Vue 后台工程,提供工作台、租户、账号、角色权限、资源授权、Skill 中心、风险告警和审计日志页面骨架。
|
||||
5. 修改根工程 `tsconfig.json`、`eslint.config.mjs`,避免尚未安装 Vue 依赖时影响 Next 主站构建。
|
||||
6. 更新架构文档,说明独立后台、现有 `/admin` fallback、BFF 契约和后续部署方向。
|
||||
7. 运行专项测试、lint 和 build;如失败,修到通过再交付。
|
||||
|
||||
## 成功标准
|
||||
|
||||
- `/api/v1/admin/backoffice` 可被测试调用并返回稳定结构。
|
||||
- `apps/boss-admin-web` 具备可独立安装运行的 Vue/Vite 工程文件。
|
||||
- 根工程 lint/build 不受新独立前端影响。
|
||||
- 文档能说明为什么不整套引入 YuDao 后端,以及后续如何独立部署。
|
||||
405
docs/superpowers/plans/2026-05-09-desktop-dialog-guard.md
Normal file
405
docs/superpowers/plans/2026-05-09-desktop-dialog-guard.md
Normal file
@@ -0,0 +1,405 @@
|
||||
# Desktop Dialog Guard Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Build a cross-platform dialog guard foundation so Boss desktop control can classify macOS and Windows popups, auto-handle safe prompts, and pause for user confirmation on risky prompts.
|
||||
|
||||
**Architecture:** Add a local-agent dialog policy engine with platform-neutral snapshots and signatures, expose `needs_user_action` from the computer-use runner, and wire the smoke runtime through the guard before executing desktop actions. macOS and Windows share policy/consent semantics while platform adapters provide snapshots through the same JSON shape.
|
||||
|
||||
**Tech Stack:** Node.js ESM, `node:test`, local-agent runtime scripts, Boss `desktop_control` payloads.
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Policy Engine
|
||||
|
||||
**Files:**
|
||||
- Create: `local-agent/desktop-dialog-guard.mjs`
|
||||
- Test: `tests/local-agent-desktop-dialog-guard.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write failing tests**
|
||||
|
||||
```js
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
buildDialogInterventionResult,
|
||||
createDialogSignature,
|
||||
evaluateDialogSnapshot,
|
||||
normalizeDialogSnapshot,
|
||||
} from "../local-agent/desktop-dialog-guard.mjs";
|
||||
|
||||
test("dialog guard auto-handles safe welcome prompts on macOS and Windows", () => {
|
||||
for (const platform of ["darwin", "win32"]) {
|
||||
const decision = evaluateDialogSnapshot({
|
||||
platform,
|
||||
appName: platform === "darwin" ? "Google Chrome" : "Microsoft Edge",
|
||||
title: "Welcome",
|
||||
text: "Welcome. Not now",
|
||||
buttons: ["Get started", "Not now"],
|
||||
});
|
||||
|
||||
assert.equal(decision.disposition, "auto_action");
|
||||
assert.equal(decision.action, "click_button");
|
||||
assert.equal(decision.button, "Not now");
|
||||
assert.equal(decision.risk, "safe");
|
||||
}
|
||||
});
|
||||
|
||||
test("dialog guard pauses for sensitive permission prompts", () => {
|
||||
const decision = evaluateDialogSnapshot({
|
||||
platform: "darwin",
|
||||
appName: "System Settings",
|
||||
title: "Screen Recording",
|
||||
text: "BossComputerUseHelper would like to record this computer's screen",
|
||||
buttons: ["Allow", "Don't Allow"],
|
||||
});
|
||||
|
||||
assert.equal(decision.disposition, "needs_user_action");
|
||||
assert.equal(decision.risk, "blocked");
|
||||
assert.equal(decision.kind, "permission_required");
|
||||
});
|
||||
|
||||
test("dialog guard generates stable signatures from normalized content", () => {
|
||||
const a = createDialogSignature({
|
||||
platform: "darwin",
|
||||
deviceId: "macbook-air",
|
||||
appBundleId: "com.google.Chrome",
|
||||
title: " Welcome ",
|
||||
text: "Not now",
|
||||
buttons: ["Not now", "OK"],
|
||||
});
|
||||
const b = createDialogSignature({
|
||||
platform: "darwin",
|
||||
deviceId: "macbook-air",
|
||||
appBundleId: "com.google.Chrome",
|
||||
title: "Welcome",
|
||||
text: " Not now ",
|
||||
buttons: ["Not now", "OK"],
|
||||
});
|
||||
|
||||
assert.equal(a.id, b.id);
|
||||
assert.equal(a.scopeKey, "darwin:macbook-air:com.google.Chrome");
|
||||
});
|
||||
|
||||
test("dialog guard emits app-safe intervention payload", () => {
|
||||
const snapshot = normalizeDialogSnapshot({
|
||||
platform: "win32",
|
||||
deviceId: "win-node",
|
||||
appName: "Installer",
|
||||
title: "User Account Control",
|
||||
text: "Do you want to allow this app to make changes to your device?",
|
||||
buttons: ["Yes", "No"],
|
||||
});
|
||||
const decision = evaluateDialogSnapshot(snapshot);
|
||||
const result = buildDialogInterventionResult({
|
||||
requestId: "desktop-task-1",
|
||||
snapshot,
|
||||
decision,
|
||||
});
|
||||
|
||||
assert.equal(result.status, "needs_user_action");
|
||||
assert.equal(result.kind, "dialog_intervention_required");
|
||||
assert.equal(result.risk, "blocked");
|
||||
assert.deepEqual(result.availableActions, ["handled_on_device", "cancel_task"]);
|
||||
assert.match(result.summary, /Installer/);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `node --test tests/local-agent-desktop-dialog-guard.test.mjs`
|
||||
|
||||
Expected: FAIL because `local-agent/desktop-dialog-guard.mjs` does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement minimal policy engine**
|
||||
|
||||
Create `local-agent/desktop-dialog-guard.mjs` with:
|
||||
|
||||
```js
|
||||
import { createHash } from "node:crypto";
|
||||
|
||||
const SAFE_DISMISS_BUTTONS = ["稍后", "跳过", "以后再说", "Not now", "Skip", "Later", "Cancel"];
|
||||
const BLOCKED_TEXT_PATTERNS = [
|
||||
/screen recording/i,
|
||||
/accessibility/i,
|
||||
/input monitoring/i,
|
||||
/full disk access/i,
|
||||
/keychain/i,
|
||||
/administrator/i,
|
||||
/apple id/i,
|
||||
/user account control/i,
|
||||
/make changes to your device/i,
|
||||
/屏幕录制/,
|
||||
/辅助功能/,
|
||||
/输入监控/,
|
||||
/完整磁盘访问/,
|
||||
/钥匙串/,
|
||||
/管理员密码/,
|
||||
];
|
||||
|
||||
export function normalizeDialogText(value) {
|
||||
return String(value || "").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
|
||||
export function normalizeDialogSnapshot(input = {}) {
|
||||
const buttons = Array.isArray(input.buttons)
|
||||
? input.buttons.map(normalizeDialogText).filter(Boolean)
|
||||
: [];
|
||||
return {
|
||||
platform: normalizeDialogText(input.platform || process.platform || "unknown"),
|
||||
deviceId: normalizeDialogText(input.deviceId || "unknown-device"),
|
||||
appName: normalizeDialogText(input.appName || input.app || "Unknown App"),
|
||||
appBundleId: normalizeDialogText(input.appBundleId || input.appId || input.appName || input.app || "unknown-app"),
|
||||
title: normalizeDialogText(input.title),
|
||||
text: normalizeDialogText(input.text),
|
||||
buttons,
|
||||
};
|
||||
}
|
||||
|
||||
function hash(value) {
|
||||
return createHash("sha256").update(value).digest("hex").slice(0, 16);
|
||||
}
|
||||
|
||||
export function createDialogSignature(snapshotInput = {}) {
|
||||
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
||||
const titleHash = hash(snapshot.title.toLowerCase());
|
||||
const textHash = hash(snapshot.text.toLowerCase());
|
||||
const buttonHash = hash(snapshot.buttons.join("|").toLowerCase());
|
||||
return {
|
||||
id: hash([snapshot.platform, snapshot.deviceId, snapshot.appBundleId, titleHash, textHash, buttonHash].join("|")),
|
||||
scopeKey: [snapshot.platform, snapshot.deviceId, snapshot.appBundleId].join(":"),
|
||||
platform: snapshot.platform,
|
||||
deviceId: snapshot.deviceId,
|
||||
appBundleId: snapshot.appBundleId,
|
||||
titleHash,
|
||||
textHash,
|
||||
buttonHash,
|
||||
};
|
||||
}
|
||||
|
||||
function findSafeDismissButton(buttons) {
|
||||
return buttons.find((button) =>
|
||||
SAFE_DISMISS_BUTTONS.some((candidate) => candidate.toLowerCase() === button.toLowerCase()),
|
||||
);
|
||||
}
|
||||
|
||||
function isBlockedPrompt(snapshot) {
|
||||
const combined = `${snapshot.title} ${snapshot.text}`;
|
||||
return BLOCKED_TEXT_PATTERNS.some((pattern) => pattern.test(combined));
|
||||
}
|
||||
|
||||
export function evaluateDialogSnapshot(snapshotInput = {}) {
|
||||
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
||||
const signature = createDialogSignature(snapshot);
|
||||
if (isBlockedPrompt(snapshot)) {
|
||||
return {
|
||||
disposition: "needs_user_action",
|
||||
kind: "permission_required",
|
||||
risk: "blocked",
|
||||
action: "pause_for_user",
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
const safeButton = findSafeDismissButton(snapshot.buttons);
|
||||
if (safeButton) {
|
||||
return {
|
||||
disposition: "auto_action",
|
||||
kind: "safe_dismiss",
|
||||
risk: "safe",
|
||||
action: "click_button",
|
||||
button: safeButton,
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
disposition: "needs_user_action",
|
||||
kind: "unknown_dialog",
|
||||
risk: "medium",
|
||||
action: "pause_for_user",
|
||||
signature,
|
||||
};
|
||||
}
|
||||
|
||||
export function buildDialogInterventionResult({ requestId, snapshot: snapshotInput, decision }) {
|
||||
const snapshot = normalizeDialogSnapshot(snapshotInput);
|
||||
const signature = decision?.signature || createDialogSignature(snapshot);
|
||||
const blocked = decision?.risk === "blocked";
|
||||
return {
|
||||
status: "needs_user_action",
|
||||
requestId: requestId || undefined,
|
||||
kind: "dialog_intervention_required",
|
||||
dialogId: signature.id,
|
||||
risk: decision?.risk || "medium",
|
||||
summary: `${snapshot.appName} 弹窗需要确认:${snapshot.title || snapshot.text || "未知弹窗"}`,
|
||||
recommendedAction: blocked ? "handle_on_device" : "review",
|
||||
availableActions: blocked
|
||||
? ["handled_on_device", "cancel_task"]
|
||||
: ["allow_once", "allow_for_device_dialog", "deny"],
|
||||
platform: snapshot.platform,
|
||||
appName: snapshot.appName,
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `node --test tests/local-agent-desktop-dialog-guard.test.mjs`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 2: Runner Result Support
|
||||
|
||||
**Files:**
|
||||
- Modify: `local-agent/computer-use-task-runner.mjs`
|
||||
- Test: `tests/local-agent-computer-use-runner.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write failing parser test**
|
||||
|
||||
Append this test:
|
||||
|
||||
```js
|
||||
test("computer use runner parses dialog intervention runtime payload", () => {
|
||||
const result = parseComputerUseTaskResult(
|
||||
JSON.stringify({
|
||||
status: "needs_user_action",
|
||||
requestId: "desktop-task-dialog",
|
||||
kind: "dialog_intervention_required",
|
||||
dialogId: "dialog-1",
|
||||
risk: "medium",
|
||||
summary: "QQ 弹窗需要确认",
|
||||
recommendedAction: "review",
|
||||
availableActions: ["allow_once", "deny"],
|
||||
platform: "darwin",
|
||||
appName: "QQ",
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(result.status, "needs_user_action");
|
||||
assert.equal(result.requestId, "desktop-task-dialog");
|
||||
assert.equal(result.kind, "dialog_intervention_required");
|
||||
assert.equal(result.dialogId, "dialog-1");
|
||||
assert.equal(result.risk, "medium");
|
||||
assert.equal(result.summary, "QQ 弹窗需要确认");
|
||||
assert.deepEqual(result.availableActions, ["allow_once", "deny"]);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
Run: `node --test tests/local-agent-computer-use-runner.test.mjs`
|
||||
|
||||
Expected: FAIL because `needs_user_action` is not parsed.
|
||||
|
||||
- [ ] **Step 3: Implement parser branch**
|
||||
|
||||
Update `parseComputerUseTaskResult` so `status === "needs_user_action"` returns a structured object containing requestId, kind, dialogId, risk, summary, recommendedAction, availableActions, platform and appName.
|
||||
|
||||
- [ ] **Step 4: Run test to verify it passes**
|
||||
|
||||
Run: `node --test tests/local-agent-computer-use-runner.test.mjs`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 3: Runtime Guard Integration
|
||||
|
||||
**Files:**
|
||||
- Modify: `scripts/computer-use-smoke.mjs`
|
||||
- Test: `tests/browser-desktop-smoke-runtime-scripts.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write failing runtime tests**
|
||||
|
||||
Add tests that run `scripts/computer-use-smoke.mjs` with `BOSS_DIALOG_GUARD_ENABLED=true` and `BOSS_DIALOG_GUARD_SNAPSHOT_JSON`.
|
||||
|
||||
The safe test should assert the runtime completes and includes a `dialogGuard` artifact entry. The blocked test should assert the runtime returns `needs_user_action` before executing the desktop action.
|
||||
|
||||
- [ ] **Step 2: Run tests to verify failure**
|
||||
|
||||
Run: `node --test tests/browser-desktop-smoke-runtime-scripts.test.mjs`
|
||||
|
||||
Expected: FAIL because the smoke runtime ignores dialog guard env vars.
|
||||
|
||||
- [ ] **Step 3: Implement runtime preflight**
|
||||
|
||||
Import the dialog guard module, parse `BOSS_DIALOG_GUARD_ENABLED`, parse `BOSS_DIALOG_GUARD_SNAPSHOT_JSON`, evaluate it before desktop automation, return `needs_user_action` for pause decisions, and include auto-action audit info in artifacts for safe decisions.
|
||||
|
||||
- [ ] **Step 4: Run tests to verify pass**
|
||||
|
||||
Run: `node --test tests/browser-desktop-smoke-runtime-scripts.test.mjs`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 4: Config Defaults
|
||||
|
||||
**Files:**
|
||||
- Modify: `local-agent/config.example.json`
|
||||
- Modify: `local-agent/config.cloud.json`
|
||||
- Test: `tests/browser-desktop-runtime-config-defaults.test.mjs`
|
||||
|
||||
- [ ] **Step 1: Write failing config test**
|
||||
|
||||
Extend the default config test to assert `dialogGuardEnabled`, `dialogGuardPlatformAdapters`, and `dialogGuardConsentRequired` are present.
|
||||
|
||||
- [ ] **Step 2: Run test to verify failure**
|
||||
|
||||
Run: `node --test tests/browser-desktop-runtime-config-defaults.test.mjs`
|
||||
|
||||
Expected: FAIL because the config keys are absent.
|
||||
|
||||
- [ ] **Step 3: Add defaults**
|
||||
|
||||
Add:
|
||||
|
||||
```json
|
||||
"dialogGuardEnabled": true,
|
||||
"dialogGuardConsentRequired": true,
|
||||
"dialogGuardPlatformAdapters": ["darwin", "win32"]
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run test to verify pass**
|
||||
|
||||
Run: `node --test tests/browser-desktop-runtime-config-defaults.test.mjs`
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
### Task 5: Focused Verification
|
||||
|
||||
**Files:**
|
||||
- No production files.
|
||||
|
||||
- [ ] **Step 1: Run focused tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
node --test tests/local-agent-desktop-dialog-guard.test.mjs \
|
||||
tests/local-agent-computer-use-runner.test.mjs \
|
||||
tests/browser-desktop-smoke-runtime-scripts.test.mjs \
|
||||
tests/browser-desktop-runtime-config-defaults.test.mjs
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 2: Run project checks**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
npm run lint
|
||||
npm run build
|
||||
```
|
||||
|
||||
Expected: both PASS.
|
||||
|
||||
## Self-review
|
||||
|
||||
Spec coverage:
|
||||
|
||||
1. macOS and Windows are represented by platform-neutral snapshots and adapter-ready config keys.
|
||||
2. Safe auto handling, sensitive pause, signatures, APP-friendly intervention payloads and audit artifacts are covered.
|
||||
3. Full native AX/UIA helpers are intentionally deferred behind the adapter interface because this batch establishes the runtime contract first.
|
||||
|
||||
Placeholder scan: no unresolved placeholders.
|
||||
|
||||
Type consistency: `needs_user_action`, `dialogId`, `risk`, `summary`, `availableActions`, `platform`, and `appName` are consistent across plan tasks.
|
||||
@@ -0,0 +1,75 @@
|
||||
# Ruflo Governance And Dialog Guard Completion Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Finish the next Boss control-plane layer: Desktop Dialog Guard user confirmation/audit, plus Ruflo-inspired task ownership, capability grouping, and device trust foundations.
|
||||
|
||||
**Architecture:** Keep Ruflo as a reference only, not a runtime dependency. Add small Boss-native modules and route contracts that fit the current file-backed state store, Android realtime channel, local-agent desktop runtime, and existing RBAC/audit patterns.
|
||||
|
||||
**Tech Stack:** Next.js route handlers, TypeScript state helpers, Node test runner/tsx tests, Android Java/Robolectric, Boss SSE events, local-agent runtime payloads.
|
||||
|
||||
---
|
||||
|
||||
## Task A: Dialog Guard Backend Completion
|
||||
|
||||
**Files:**
|
||||
- Modify: `src/lib/boss-data.ts`
|
||||
- Modify: `src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts`
|
||||
- Create: `src/app/api/v1/dialog-guard/interventions/[interventionId]/decision/route.ts`
|
||||
- Test: `tests/dialog-guard-interventions-route.test.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
- [ ] Add a failing route test proving `status: "needs_user_action"` with `kind: "dialog_intervention_required"` creates a pending intervention and publishes `desktop.dialog_guard.intervention_required`.
|
||||
- [ ] Add a failing route test proving a user decision updates the intervention, writes a permission audit log, and publishes `desktop.dialog_guard.intervention_resolved`.
|
||||
- [ ] Add `DialogGuardIntervention` state shape and migration default.
|
||||
- [ ] Extend the master-agent completion route to preserve `needs_user_action` instead of normalizing it to completed.
|
||||
- [ ] Implement decision route with allowed decisions: `allow_once`, `allow_for_device_dialog`, `deny`, `handled_on_device`, `cancel_task`.
|
||||
- [ ] Run the focused backend tests.
|
||||
|
||||
## Task B: Android Dialog Guard Confirmation UI
|
||||
|
||||
**Files:**
|
||||
- Modify: `android/app/src/main/java/com/hyzq/boss/BossApiClient.java`
|
||||
- Modify: `android/app/src/main/java/com/hyzq/boss/BossNotificationRouter.java`
|
||||
- Modify: `android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java`
|
||||
- Modify: `android/app/src/main/java/com/hyzq/boss/BossUi.java`
|
||||
- Test: `android/app/src/test/java/com/hyzq/boss/DialogGuardInterventionUiTest.java`
|
||||
|
||||
**Steps:**
|
||||
|
||||
- [ ] Add failing Robolectric test for handling `desktop.dialog_guard.intervention_required`.
|
||||
- [ ] Render a compact confirmation card/dialog using the current Boss/微信-style visual system.
|
||||
- [ ] For `blocked` risk, show only `我已在电脑上处理` and `取消任务`.
|
||||
- [ ] For medium/safe risk, show `允许本次`, `当前设备此弹窗允许`, `拒绝` based on `availableActions`.
|
||||
- [ ] Call `POST /api/v1/dialog-guard/interventions/{interventionId}/decision`.
|
||||
- [ ] Remove/refresh the card on `desktop.dialog_guard.intervention_resolved`.
|
||||
- [ ] Run the focused Android test.
|
||||
|
||||
## Task C: Ruflo-Inspired Governance Foundation
|
||||
|
||||
**Files:**
|
||||
- Create: `src/lib/boss-work-claims.ts`
|
||||
- Create: `src/lib/boss-capability-groups.ts`
|
||||
- Create: `src/lib/boss-device-trust.ts`
|
||||
- Test: `tests/ruflo-governance-foundation.test.ts`
|
||||
|
||||
**Steps:**
|
||||
|
||||
- [ ] Add failing tests for claim, handoff, stale claim detection, and stealable work.
|
||||
- [ ] Add failing tests for grouped capabilities: computer control, Codex development, browser automation, skill operations, admin ops.
|
||||
- [ ] Add failing tests for device trust tiers and budget/hop limit checks.
|
||||
- [ ] Implement pure Boss-native modules without importing Ruflo.
|
||||
- [ ] Keep the API persistence-ready but not UI-bound.
|
||||
- [ ] Run the focused governance test.
|
||||
|
||||
## Integration Verification
|
||||
|
||||
- [ ] Run focused backend tests.
|
||||
- [ ] Run focused Android test if local Gradle supports it.
|
||||
- [ ] Run `npm run lint`.
|
||||
- [ ] Run `npm run build`.
|
||||
|
||||
## Notes
|
||||
|
||||
Ruflo is used as architecture reference only. The Boss implementation must stay deterministic, auditable, RBAC-aware, and safe for multi-tenant enterprise deployment.
|
||||
@@ -0,0 +1,181 @@
|
||||
# Codex Desktop 同线程消息镜像设计
|
||||
|
||||
目标:当用户在 Boss App 里对一个已绑定 `codexThreadRef` 的单线程会话发消息时,这条用户消息不仅进入 Boss 自己的项目账本和 `conversation_reply` 执行队列,也要被镜像进本机 Codex Desktop 的同一个线程历史里。这样用户稍后回到 Codex Desktop,看见的是同一个线程下连续的聊天记录,而不是 Boss 与 Desktop 两套割裂历史。
|
||||
|
||||
## 背景与现状
|
||||
|
||||
当前 Boss 的普通线程单聊主链是:
|
||||
|
||||
- Web / Android 调 `POST /api/v1/projects/[projectId]/messages`
|
||||
- 服务端写入 Boss 项目消息账本
|
||||
- 服务端排一个 `conversation_reply` 任务
|
||||
- 本机 `local-agent` 认领任务后调用 `codex exec resume <targetCodexThreadRef>`
|
||||
- Codex 线程完成后,再把线程回复回写到 Boss 项目账本
|
||||
|
||||
这条链现在已经能做到“Desktop 回复被 Boss 看见”,因为 heartbeat 扫描 `~/.codex/sessions/.../rollout-*.jsonl` 时,会把最近桌面 assistant 回复镜像回 Boss。缺口在反方向:
|
||||
|
||||
- Boss App 发起的用户消息只存在于 Boss 项目账本
|
||||
- `codex exec resume` 虽然会把 prompt 交给目标线程继续执行,但 Boss 发起的这条消息并不会先出现在 Desktop 线程历史
|
||||
- 结果就是用户在 APP 和 Desktop 里看到的“同一个线程”并不是同一份完整聊天记录
|
||||
|
||||
## 方案对比
|
||||
|
||||
### 方案 1:直接操控 Codex Desktop GUI 输入并发送
|
||||
|
||||
优点:
|
||||
|
||||
- 理论上最贴近“像用户在桌面端亲自发了一条消息”
|
||||
|
||||
缺点:
|
||||
|
||||
- 依赖窗口前台、焦点、输入法、系统权限
|
||||
- 极易被 Codex Desktop UI 更新打断
|
||||
- 无法稳定支持后台运行和多线程并发
|
||||
|
||||
不推荐作为主方案。
|
||||
|
||||
### 方案 2:直接把 Boss 用户消息写入对应 Codex rollout JSONL,再继续现有 `codex exec resume`
|
||||
|
||||
优点:
|
||||
|
||||
- 与当前 Desktop/CLI 共用的真实线程存储一致
|
||||
- 不需要操控 GUI
|
||||
- 可以保持现有 `local-agent -> codex exec resume` 主链不变
|
||||
- 能与现有 heartbeat 读取 rollout 的能力形成闭环
|
||||
|
||||
缺点:
|
||||
|
||||
- 需要谨慎贴合 Codex rollout 事件格式
|
||||
- 需要处理重复写入和 Desktop 刷新感知
|
||||
|
||||
这是本次推荐方案。
|
||||
|
||||
### 方案 3:单独给 Desktop 再建一条镜像线程
|
||||
|
||||
优点:
|
||||
|
||||
- 对现有线程文件侵入最小
|
||||
|
||||
缺点:
|
||||
|
||||
- 用户要的是“同一个线程”,不是“另一个镜像线程”
|
||||
- 历史会继续分叉
|
||||
|
||||
不满足目标。
|
||||
|
||||
## 本次设计
|
||||
|
||||
### 1. 保持 Boss 账本为移动端/UI 主真相
|
||||
|
||||
Boss 的项目消息账本、会话排序、未读数、主 Agent 协同逻辑继续基于现有 `boss-state.json`。这次不把 Boss UI 改成直接读取 `~/.codex`。
|
||||
|
||||
### 2. 对单线程 `conversation_reply` 任务增加“写入 Desktop 线程历史”的镜像步骤
|
||||
|
||||
当任务满足以下条件时,在 `local-agent` 侧做一次 rollout 镜像:
|
||||
|
||||
- `task.taskType === "conversation_reply"`
|
||||
- 存在 `targetCodexThreadRef`
|
||||
- 存在用户原始消息文本
|
||||
- 不属于 `relayViaMasterAgent === true` 的接管中转任务
|
||||
|
||||
镜像行为:
|
||||
|
||||
- 优先通过 `state_5.sqlite` 的 `threads.rollout_path` 定位目标 rollout 文件
|
||||
- 如果本机 Codex 因版本/迁移差异无法稳定解析 `state_5.sqlite`,则回退扫描 `~/.codex/sessions/**/rollout-*-<threadId>.jsonl`
|
||||
- 向该 rollout 文件追加一组 Codex 用户消息记录:`response_item / message(role=user)` 和 `event_msg / user_message`
|
||||
- 事件内容使用 Boss 原始用户消息文本,而不是执行 prompt
|
||||
- 事件时间优先使用 Boss 消息的 `sentAt`
|
||||
- 事件写入成功后,再继续现有 `codex exec resume`
|
||||
|
||||
### 3. 任务负载补齐“Boss 原始消息”字段
|
||||
|
||||
现在任务里只有:
|
||||
|
||||
- `requestMessageId`
|
||||
- `requestText`
|
||||
- `executionPrompt`
|
||||
|
||||
这还不够稳,因为后续去重和 Desktop 镜像需要区分:
|
||||
|
||||
- 哪条 Boss 用户消息已经镜像过
|
||||
- 这次镜像的真实显示文本是什么
|
||||
- 这条消息的原始时间戳是什么
|
||||
|
||||
因此为 `MasterAgentTask` 增加:
|
||||
|
||||
- `sourceMessageId?: string`
|
||||
- `sourceMessageBody?: string`
|
||||
- `sourceMessageSentAt?: string`
|
||||
- `mirrorBossUserMessageToCodexDesktop?: boolean`
|
||||
|
||||
对普通线程单聊:
|
||||
|
||||
- `sourceMessageId = message.id`
|
||||
- `sourceMessageBody = message.body`
|
||||
- `sourceMessageSentAt = message.sentAt`
|
||||
- `mirrorBossUserMessageToCodexDesktop = true`
|
||||
|
||||
对主 Agent 直聊、`@主Agent`、托管中转等不应写进子线程 Desktop 历史的场景,不开启这个标记。
|
||||
|
||||
### 4. 去重策略
|
||||
|
||||
同一条 Boss 消息可能因为:
|
||||
|
||||
- 任务重试
|
||||
- local-agent 重启
|
||||
- claim / complete 重放
|
||||
|
||||
而被多次处理。为避免在 Desktop 线程里重复写入同一条用户消息,本次采用“rollout 末尾去重”:
|
||||
|
||||
- 生成稳定镜像 key:`boss-user:<threadRef>:<sourceMessageId>`
|
||||
- 写入的 `event_msg` 中带上 `payload.metadata.bossSourceMessageId`
|
||||
- 写入前读取 rollout 尾部固定窗口,检查最近是否已经存在同一 `bossSourceMessageId`
|
||||
- 若存在,则跳过写入,仅继续 `codex exec resume`
|
||||
|
||||
这样不需要引入新的状态库,也能与 Codex 原始线程文件保持局部自洽。
|
||||
|
||||
### 5. 刷新感知
|
||||
|
||||
第一版只写 rollout 还不够稳,因为 Desktop 的线程列表排序和“最近活跃”判断通常还依赖 `threads.updated_at / updated_at_ms / has_user_event`。因此本次实现改为:
|
||||
|
||||
- rollout append 成功后,若 `state_5.sqlite` 可写且能命中该 thread,则同步刷新:
|
||||
- `updated_at`
|
||||
- `updated_at_ms`
|
||||
- `has_user_event = 1`
|
||||
- 如果当前机器上的 Codex 状态库不可用、字段不兼容或压根没有这条 thread 记录,则只保留 rollout 写入,不把整条消息链路判成失败
|
||||
|
||||
这样做的取舍是:
|
||||
|
||||
- 先保证 Boss -> Codex Desktop 同线程历史不丢
|
||||
- 再尽可能提升 Desktop 侧的列表刷新和最近活跃感知
|
||||
- 不引入 GUI 自动化,不依赖桌面窗口前台
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- 新增 `local-agent/codex-thread-rollout-writer.mjs`
|
||||
- 修改 `local-agent/codex-task-runner.mjs`
|
||||
- 修改 `local-agent/server.mjs`
|
||||
- 修改 `src/lib/boss-data.ts`
|
||||
- 修改 `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- 修改 `src/lib/boss-master-agent.ts`(如果当前普通线程任务创建逻辑在这里有共用 helper,也一起补齐)
|
||||
- 新增测试 `tests/local-agent-codex-rollout-writer.test.mjs`
|
||||
- 修改测试 `tests/local-agent-codex-task-runner.test.mjs`
|
||||
- 修改测试 `tests/single-thread-message-execution.test.ts`
|
||||
|
||||
## 边界
|
||||
|
||||
- 本次只处理“Boss App -> 已绑定 Codex Desktop 同线程”的用户消息镜像
|
||||
- 不处理群聊镜像到 Desktop
|
||||
- 不处理主 Agent 自己的回复写入 Desktop 子线程
|
||||
- 不做 Codex Desktop GUI 自动输入
|
||||
- 不把 Boss 会话列表直接改成读取 Desktop 原始线程文件
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 普通单线程会话发消息后,生成的 `conversation_reply` 任务带有完整 `sourceMessage*` 字段
|
||||
- local-agent 在执行 `codex exec resume` 前,能把这条 Boss 用户消息写进目标 rollout
|
||||
- 同一 `sourceMessageId` 重试时不会重复写入 rollout
|
||||
- 若状态库可用,镜像后会同步刷新 thread 的活跃时间和 `has_user_event`
|
||||
- 若状态库不可用或这台机器上的线程索引不完整,仍可通过 `sessions` 回退找到 rollout 并完成消息镜像
|
||||
- 现有普通线程回复链不回归,Boss 仍能收到 Codex 线程回复
|
||||
- 若目标线程缺失、只读或 cwd 不合法,仍保持现有 fail-closed 行为
|
||||
@@ -0,0 +1,399 @@
|
||||
# Boss 聊天统一电脑控制中枢设计
|
||||
|
||||
目标:让用户在 Boss App 里,无论是和 `主 Agent` 对话,还是和某个具体线程对话,都可以稳定驱动这台 Mac/Windows 设备完成三类事情:
|
||||
|
||||
- 项目开发与代码执行
|
||||
- 浏览器/桌面 GUI 操作
|
||||
- 普通产品讨论、调研和任务协同
|
||||
|
||||
并且这三类能力不再割裂成几条旁路,而是统一挂在 Boss 现有的聊天、执行底座和设备心跳体系下面。
|
||||
|
||||
## 背景与现状
|
||||
|
||||
当前 Boss 已经具备几条关键基础链路:
|
||||
|
||||
- `master-agent` 单聊可以通过 `local-agent -> codex exec` 真实产出回复
|
||||
- 普通单线程聊天已经可以排 `conversation_reply` 任务,并恢复到真实 Codex 线程执行
|
||||
- 群聊已有 `group_dispatch_plan -> dispatch_execution` 的编排链
|
||||
- 设备模型已经支持同一台机器的 `GUI + CLI` 双能力声明与默认执行模式切换
|
||||
- 本机 `local-agent` 已能做 Codex 线程发现、task claim、task complete、Desktop rollout 镜像
|
||||
|
||||
但目前缺的不是“再加一个按钮”,而是统一控制中枢:
|
||||
|
||||
- `conversation_reply` 只适合“把消息转给 Codex 线程继续聊”
|
||||
- `dispatch_execution` 主要面向群聊下发和线程编排
|
||||
- 还没有正式的“桌面控制 / 浏览器控制”任务类型
|
||||
- 主 Agent 也没有显式的能力路由模型,无法稳定判断当前消息应该走:
|
||||
- Codex 开发
|
||||
- Browser automation
|
||||
- Computer Use
|
||||
- 单纯讨论/总结
|
||||
|
||||
这会导致现在的体验像“能做一些事”,但还不是“可以靠 Boss 聊天控制电脑做事”。
|
||||
|
||||
## 目标边界
|
||||
|
||||
### 本次要达到的能力
|
||||
|
||||
1. 主 Agent 能把用户消息识别成四类执行意图:
|
||||
- `project_development`
|
||||
- `thread_collaboration`
|
||||
- `browser_control`
|
||||
- `desktop_control`
|
||||
|
||||
2. Boss 执行底座能显式表达这四类请求,并把它们路由到正确 runtime。
|
||||
|
||||
3. `local-agent` 增加两条新 runtime:
|
||||
- `browser-automation-runtime`
|
||||
- `computer-use-runtime`
|
||||
|
||||
4. Web / Android 前台至少能拿到任务执行方式和当前状态,知道这条消息是:
|
||||
- 交给 Codex 线程
|
||||
- 交给浏览器自动化
|
||||
- 交给桌面控制
|
||||
- 仅由主 Agent 直接回复
|
||||
|
||||
5. 对高风险桌面动作建立最小确认机制,避免“发一句话就直接在电脑上乱点/乱删”。
|
||||
|
||||
### 本次不做的事情
|
||||
|
||||
- 不做完整远控桌面产品
|
||||
- 不做视频流式屏幕回传
|
||||
- 不做跨设备键鼠镜像
|
||||
- 不把 Codex Desktop 自己纳入 Computer Use 自动点击目标
|
||||
- 不依赖 GUI 自动化去操控 Codex 自己的窗口
|
||||
|
||||
## 方案原则
|
||||
|
||||
### 原则 1:复用 Boss 现有执行底座,不另起一套“远控系统”
|
||||
|
||||
如果我们再单独造一层 `remote-control service`,会把:
|
||||
|
||||
- 会话账本
|
||||
- 任务队列
|
||||
- 权限与确认
|
||||
- 前台状态展示
|
||||
- 设备能力发现
|
||||
|
||||
全部再复制一遍。成本高,而且会和当前 Boss 的“聊天即控制入口”相冲突。
|
||||
|
||||
所以本次明确采用:
|
||||
|
||||
- 用户入口仍然是 Boss 聊天
|
||||
- 任务记录仍然是 `MasterAgentTask`
|
||||
- 路由仍然收敛进 `src/lib/execution`
|
||||
- 执行仍然由绑定设备上的 `local-agent` 落地
|
||||
|
||||
### 原则 2:先把“控制判断”标准化,再扩 runtime
|
||||
|
||||
现在最大的问题不是工具不够,而是没有统一的“这条消息该怎么执行”判断结果。
|
||||
|
||||
因此要先补一层执行意图:
|
||||
|
||||
- `discussion_only`
|
||||
- `thread_reply`
|
||||
- `browser_control`
|
||||
- `desktop_control`
|
||||
- `development_execution`
|
||||
|
||||
然后让不同后端只关心自己该执行哪一种。
|
||||
|
||||
### 原则 3:危险动作永远要显式分级
|
||||
|
||||
Boss 最终要能“做任何事”,但不能把“任何事”理解成“任何时候都自动执行”。
|
||||
|
||||
所以本次引入最小风险分级:
|
||||
|
||||
- `low`
|
||||
- 打开页面
|
||||
- 搜索信息
|
||||
- 读取项目文件
|
||||
- 运行只读检查
|
||||
- `medium`
|
||||
- 登录态网页操作
|
||||
- 浏览器表单提交
|
||||
- 桌面应用点击导航
|
||||
- 修改非代码业务内容
|
||||
- `high`
|
||||
- 删除/覆盖文件
|
||||
- 系统设置改动
|
||||
- 批量提交/发布
|
||||
- 不可逆外部操作
|
||||
|
||||
策略:
|
||||
|
||||
- `low`:默认直接执行
|
||||
- `medium`:默认轻确认,可在项目/会话级放行
|
||||
- `high`:必须明确确认
|
||||
|
||||
## 控制中枢设计
|
||||
|
||||
## 1. 新的执行意图模型
|
||||
|
||||
在当前 `ExecutionRequestKind` 基础上新增:
|
||||
|
||||
- `browser_control`
|
||||
- `desktop_control`
|
||||
|
||||
并补充一个统一意图字段,供主 Agent 和前台共用:
|
||||
|
||||
- `intentCategory`
|
||||
- `discussion_only`
|
||||
- `project_development`
|
||||
- `thread_collaboration`
|
||||
- `browser_control`
|
||||
- `desktop_control`
|
||||
|
||||
其中:
|
||||
|
||||
- `project_development` 继续优先走现有 Codex 线程 / CLI 执行链
|
||||
- `thread_collaboration` 继续走 `conversation_reply`
|
||||
- `browser_control` 新增浏览器自动化 runtime
|
||||
- `desktop_control` 新增 Computer Use runtime
|
||||
|
||||
## 2. 新的 runtime 层
|
||||
|
||||
### 2.1 browser-automation-runtime
|
||||
|
||||
用途:
|
||||
|
||||
- 打开网页
|
||||
- 登录指定后台
|
||||
- 提交表单
|
||||
- 抓取页面信息
|
||||
- 复现 Web bug
|
||||
|
||||
第一版实现直接复用现有 Playwright 能力,不重新造驱动协议。
|
||||
|
||||
建议协议:
|
||||
|
||||
- 输入:
|
||||
- `taskId`
|
||||
- `projectId`
|
||||
- `requestText`
|
||||
- `executionPrompt`
|
||||
- `targetUrl?`
|
||||
- `riskLevel`
|
||||
- 输出:
|
||||
- `status`
|
||||
- `replyBody`
|
||||
- `structuredResult?`
|
||||
- `artifacts?`
|
||||
|
||||
落地约束:
|
||||
|
||||
- `local-agent/browser-control-task-runner.mjs` 先收口成外部 runtime 桥,不把 Playwright 逻辑硬编码进 `server.mjs`
|
||||
- 通过 `browserControlEnabled / browserControlCommand / browserControlArgs / browserControlWorkdir / browserControlTimeoutMs` 配置启用
|
||||
- runtime 进程只需要遵守单行 JSON stdout 协议,后续可以平滑替换成真实 Playwright/OpenClaw/browser adapter
|
||||
|
||||
### 2.2 computer-use-runtime
|
||||
|
||||
用途:
|
||||
|
||||
- 打开本机应用
|
||||
- 在桌面 GUI 上点击、输入、切换
|
||||
- 配合浏览器外的桌面软件完成操作
|
||||
|
||||
第一版实现直接对接 Codex App 现有的 Computer Use 能力约束:
|
||||
|
||||
- 只能操作普通桌面应用
|
||||
- 需要系统 Screen Recording + Accessibility
|
||||
- 不把终端/Codex 自己当作自动点击目标
|
||||
|
||||
这意味着:
|
||||
|
||||
- 项目开发仍然优先走 Codex CLI/线程
|
||||
- Computer Use 负责 GUI 世界
|
||||
- 两者由主 Agent 在同一条聊天链里自动选择
|
||||
|
||||
落地约束:
|
||||
|
||||
- `local-agent/computer-use-task-runner.mjs` 同样先做成外部 runtime 桥
|
||||
- 通过 `computerUseEnabled / computerUseCommand / computerUseArgs / computerUseWorkdir / computerUseTimeoutMs` 配置启用
|
||||
- 先统一 Boss 与 runtime 的协议,再按设备情况接 Codex App Computer Use、OpenClaw 或其他 GUI runtime
|
||||
|
||||
## 3. MasterAgentTask 扩展
|
||||
|
||||
当前 `MasterAgentTaskType` 只有:
|
||||
|
||||
- `conversation_reply`
|
||||
- `attachment_analysis`
|
||||
- `group_dispatch_plan`
|
||||
- `dispatch_execution`
|
||||
- `device_import_resolution`
|
||||
|
||||
本次新增:
|
||||
|
||||
- `browser_control`
|
||||
- `desktop_control`
|
||||
|
||||
新增字段:
|
||||
|
||||
- `intentCategory?`
|
||||
- `runtimeKind?`
|
||||
- `riskLevel?`
|
||||
- `confirmationPolicy?`
|
||||
- `requiresUserConfirmation?`
|
||||
- `confirmationScopeKey?`
|
||||
|
||||
目的:
|
||||
|
||||
- 前台能展示“当前这条消息要走哪条执行链”
|
||||
- 服务端能统一处理确认/拦截
|
||||
- `local-agent` 能按 runtimeKind 正确分流
|
||||
|
||||
## 4. 主 Agent 路由逻辑
|
||||
|
||||
主 Agent 不再简单分成“自己答”或“排 conversation_reply”,而是多一步意图判断。
|
||||
|
||||
推荐判断顺序:
|
||||
|
||||
1. 如果是明显的项目讨论、总结、目标/版本记录、普通问答
|
||||
- `discussion_only`
|
||||
|
||||
2. 如果是“继续开发 / 改代码 / 跑测试 / 看项目状态 / 接手某线程”
|
||||
- `project_development` 或 `thread_collaboration`
|
||||
|
||||
3. 如果是“打开网站 / 点网页 / 查后台 / 提交表单 / 看页面”
|
||||
- `browser_control`
|
||||
|
||||
4. 如果是“打开电脑软件 / 操作桌面 / 系统 GUI / 非浏览器界面”
|
||||
- `desktop_control`
|
||||
|
||||
路由结果:
|
||||
|
||||
- `discussion_only`
|
||||
- 主 Agent 直接回复
|
||||
- `thread_collaboration`
|
||||
- 继续 `conversation_reply`
|
||||
- `project_development`
|
||||
- 优先真实 Codex 线程 / CLI
|
||||
- `browser_control`
|
||||
- 排 `browser_control` 任务
|
||||
- `desktop_control`
|
||||
- 排 `desktop_control` 任务
|
||||
|
||||
## 5. 设备能力模型
|
||||
|
||||
当前设备只有:
|
||||
|
||||
- `gui`
|
||||
- `cli`
|
||||
|
||||
这对“统一控制电脑”不够精确,所以建议在设备 heartbeat 能力里细化为:
|
||||
|
||||
- `cli`
|
||||
- `gui`
|
||||
- `browserAutomation`
|
||||
- `computerUse`
|
||||
|
||||
其中:
|
||||
|
||||
- `browserAutomation` 可由本机 Playwright/runtime 探测
|
||||
- `computerUse` 由本机配置和权限状态探测
|
||||
|
||||
这样前台与主 Agent 都能知道:
|
||||
|
||||
- 当前机器只能写代码
|
||||
- 还是也能控浏览器
|
||||
- 还是能做完整桌面 GUI 操作
|
||||
|
||||
## 6. 前台产品表现
|
||||
|
||||
### 会话页 / 聊天页
|
||||
|
||||
每条“触发执行”的用户消息,服务端返回时增加:
|
||||
|
||||
- `executionMode`
|
||||
- `discussion`
|
||||
- `thread`
|
||||
- `development`
|
||||
- `browser`
|
||||
- `desktop`
|
||||
- `riskLevel`
|
||||
- `requiresConfirmation`
|
||||
|
||||
前台展示原则:
|
||||
|
||||
- 不做厚重控制台 UI
|
||||
- 保持当前微信式聊天界面
|
||||
- 只在消息下方补一条轻状态:
|
||||
- `已交给主 Agent`
|
||||
- `正在调用浏览器自动化`
|
||||
- `正在调用桌面控制`
|
||||
- `等待你确认后执行`
|
||||
|
||||
### 会话信息 / 设备详情
|
||||
|
||||
补一个轻量能力区:
|
||||
|
||||
- `默认开发模式:CLI / GUI`
|
||||
- `浏览器自动化:可用 / 不可用`
|
||||
- `桌面控制:可用 / 不可用`
|
||||
|
||||
不把这些塞回聊天主界面。
|
||||
|
||||
## 7. 风险确认设计
|
||||
|
||||
### 会话级别
|
||||
|
||||
如果当前会话在某个项目下已经对中风险动作做过一次确认,则可以对这个项目保留:
|
||||
|
||||
- `禁止`
|
||||
- `允许本次`
|
||||
- `当前项目永久放行`
|
||||
|
||||
这和现有 GUI/CLI 并行冲突的项目级策略一致,避免用户多学一套规则。
|
||||
|
||||
### 任务级别
|
||||
|
||||
当主 Agent 判断为高风险时:
|
||||
|
||||
- 不直接执行
|
||||
- 先在聊天里给出极简确认卡
|
||||
- 用户点确认后再排任务
|
||||
|
||||
## 8. 本次实施顺序
|
||||
|
||||
### 第一批
|
||||
|
||||
- 写设计与计划文档
|
||||
- 扩展任务类型、执行请求类型、设备能力类型
|
||||
- 接入 `browser_control / desktop_control` 两类任务基础骨架
|
||||
- `local-agent` 增加 runtime 分流占位
|
||||
- 前台返回 `executionMode/riskLevel` 元数据
|
||||
|
||||
### 第二批
|
||||
|
||||
- 接入 browser automation 真执行
|
||||
- 接入 computer use 真执行
|
||||
- 完成确认链
|
||||
|
||||
### 第三批
|
||||
|
||||
- Android/Web 前台补状态展示
|
||||
- 真机回归
|
||||
- 文档回写
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- 修改 `src/lib/execution/types.ts`
|
||||
- 修改 `src/lib/execution/tool-registry.ts`
|
||||
- 修改 `src/lib/execution/permission-policy.ts`
|
||||
- 修改 `src/lib/boss-data.ts`
|
||||
- 修改 `src/lib/boss-master-agent.ts`
|
||||
- 修改 `src/app/api/v1/projects/[projectId]/messages/route.ts`
|
||||
- 修改 `local-agent/codex-task-runner.mjs`
|
||||
- 修改 `local-agent/server.mjs`
|
||||
- 新增 `local-agent/browser-control-task-runner.mjs`
|
||||
- 新增 `local-agent/computer-use-task-runner.mjs`
|
||||
- 新增对应 tests
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 主 Agent 能把聊天输入稳定区分成讨论、开发、浏览器控制、桌面控制四类
|
||||
- `browser_control / desktop_control` 能以正式任务进入 Boss 队列
|
||||
- `local-agent` 能识别并分流这两类任务
|
||||
- 前台能看到当前消息是走哪条执行链
|
||||
- 中高风险动作不会静默直接执行
|
||||
- 现有 `conversation_reply / dispatch_execution` 主链不回归
|
||||
151
docs/superpowers/specs/2026-04-30-admin-backoffice-redesign.md
Normal file
151
docs/superpowers/specs/2026-04-30-admin-backoffice-redesign.md
Normal file
@@ -0,0 +1,151 @@
|
||||
# Boss To B 总后台重构设计
|
||||
|
||||
日期:2026-04-30
|
||||
|
||||
## 背景
|
||||
|
||||
当前 `/admin` 已经具备最高管理员访问控制、总览聚合、账号授权、风险处理和 Skill 生命周期治理能力,但页面仍像“几个数据表拼在一起”。对于 To B 交付场景,平台侧需要的是一套能服务客户成功、运维值守和权限开通的 PC 总后台,而不是一个调试看板。
|
||||
|
||||
本次重构不新增大业务边界,优先重组现有 `/api/v1/admin/overview`、`/api/v1/admin/access`、`/api/v1/admin/risks/actions`、`/api/v1/admin/skills/requests` 数据和动作,把后台做成可用、可读、可处置的运营控制台。
|
||||
|
||||
## 目标
|
||||
|
||||
1. 最高管理员进入后台后,能在 10 秒内看出哪些客户、设备、主 Agent 任务或线程风险需要处理。
|
||||
2. 客户开通从“多个分散表单”收口成可理解的工作台:公司、老板账号、子账号、设备、项目、Skill 授权有清晰入口和状态。
|
||||
3. 风险处理从“表格按钮”升级为战情室:按严重程度、客户影响、负责人、SLA 和下一步动作组织。
|
||||
4. Skill 治理保留安全约束,但展示成可追踪的生命周期队列。
|
||||
5. UI 风格从移动端微信效率风改为 PC To B 管理后台:高密度、强层级、清晰状态、少装饰。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不引入新的 Umi / Ant Design Pro 工程。
|
||||
- 不切换 PostgreSQL 或重写状态存储。
|
||||
- 不改 Android APP 端交互。
|
||||
- 不绕过 local-agent 的 Skill allowlist、checksum、备份和回滚约束。
|
||||
- 不把客户侧 Web 控制台和平台总后台混成一个产品。
|
||||
|
||||
## 信息架构
|
||||
|
||||
后台改为 4 个一级区:
|
||||
|
||||
1. `驾驶舱`:平台全局健康、关键风险、客户影响、在线设备、主 Agent 失败、待处理通知。
|
||||
2. `客户与账号`:公司列表、客户详情、账号开通、角色状态、登录与安全概览。
|
||||
3. `授权工作台`:设备、项目、Skill 授权,权限模板,过期授权,离职回收和审计。
|
||||
4. `风险与治理`:风险战情室、SLA、负责人、修复工单、风险时间线、Skill 生命周期请求。
|
||||
|
||||
现有 `账号与授权` 和 `Skill 治理` 不是删除,而是拆到更合理的上下文里:账号归客户,授权归权限,风险和 Skill 请求归治理。
|
||||
|
||||
## 页面设计
|
||||
|
||||
### 驾驶舱
|
||||
|
||||
顶部保留平台身份和刷新动作,但标题从“Boss 管理后台”升级为“平台运营驾驶舱”。主区域按优先级展示:
|
||||
|
||||
- `今日待处理`:关键风险数、超 SLA 通知、离线设备、主 Agent 失败。
|
||||
- `客户健康排行`:按开放风险、在线设备比例、合同/套餐状态排序。
|
||||
- `关键风险队列`:只展示最值得处理的风险,提供负责人、SLA、确认、关闭、工单动作。
|
||||
- `设备与节点健康`:GUI/CLI、Browser、Computer Use 能力状态集中展示。
|
||||
- `最近事件`:风险时间线和权限审计摘要。
|
||||
|
||||
驾驶舱默认不展示大分页表,避免用户一打开就被表格淹没。
|
||||
|
||||
### 客户与账号
|
||||
|
||||
采用左侧客户列表 + 右侧详情的结构:
|
||||
|
||||
- 客户列表显示公司名、套餐、账号数、设备数、开放风险、客户成功负责人。
|
||||
- 右侧详情显示老板账号、子账号、绑定设备、项目数量和最近风险。
|
||||
- 新建客户流程拆成三步:创建公司、创建老板账号、绑定设备/项目。
|
||||
- 子账号管理支持启用/停用、重置密码、MFA 状态和登录会话摘要。
|
||||
|
||||
这部分复用现有 `/api/v1/admin/access`,但前台从表单堆叠改成任务流。
|
||||
|
||||
### 授权工作台
|
||||
|
||||
授权页面按“给谁授权”而不是“授权类型”组织:
|
||||
|
||||
- 先选择账号或客户。
|
||||
- 再选择设备、项目、Skill。
|
||||
- 最后套用权限模板或手动勾选权限。
|
||||
|
||||
页面底部保留最近授权审计和过期授权提醒。高危动作继续二次确认。
|
||||
|
||||
### 风险与治理
|
||||
|
||||
风险页面采用“战情室”结构:
|
||||
|
||||
- 左侧风险队列:按 `critical / warning / info`、客户、负责人、SLA 筛选。
|
||||
- 中间风险详情:影响对象、错误摘要、最近时间线、建议动作。
|
||||
- 右侧处理面板:指派、设置 SLA、确认、关闭、创建工单。
|
||||
|
||||
Skill 生命周期治理放在同一区域的第二页签,展示为请求队列:
|
||||
|
||||
- 待认领、执行中、成功、失败分栏。
|
||||
- 每条请求展示设备、Skill、动作、来源、checksum、结果摘要。
|
||||
- 创建请求表单保留,但根据动作动态收敛字段。
|
||||
|
||||
## 组件边界
|
||||
|
||||
建议拆出以下组件,降低当前 `boss-admin-app.tsx` 的复杂度:
|
||||
|
||||
- `AdminShell`:PC 后台壳、顶部栏、一级导航。
|
||||
- `AdminDashboard`:驾驶舱。
|
||||
- `AdminCustomerWorkspace`:客户与账号工作台。
|
||||
- `AdminPermissionWorkspace`:授权工作台。
|
||||
- `AdminRiskCommandCenter`:风险战情室。
|
||||
- `AdminSkillGovernance`:Skill 生命周期治理,可复用并改造当前组件。
|
||||
- `AdminStatusBadge`、`AdminMetricCard`、`AdminActionRail`:统一状态、指标和动作区。
|
||||
|
||||
数据请求先继续使用现有 fetch,不强制引入新的客户端状态库。
|
||||
|
||||
## 数据和接口
|
||||
|
||||
第一批不要求新增后端字段,但前台应完整使用现有字段:
|
||||
|
||||
- `summary`
|
||||
- `companies`
|
||||
- `accounts`
|
||||
- `devices`
|
||||
- `risks`
|
||||
- `notifications`
|
||||
- `riskTimeline`
|
||||
- `grantsSummary`
|
||||
|
||||
如果发现页面需要客户健康分数,可先在前端由 `openRiskCount / onlineDeviceCount / deviceCount / status` 计算,不改状态 schema。
|
||||
|
||||
## 错误处理
|
||||
|
||||
- 后台总览读取失败时展示一张明确的恢复卡,提供重试按钮。
|
||||
- 风险动作失败时保留原行状态,不做乐观关闭。
|
||||
- 指派负责人和 SLA 不再使用 `window.prompt`,改成右侧处理面板或弹窗表单。
|
||||
- 空状态要表达下一步,例如“暂无风险,可以查看设备在线情况”,不要只写“暂无数据”。
|
||||
|
||||
## 测试策略
|
||||
|
||||
- 保留并更新 `tests/admin-refine-page.test.ts`,验证新的一级区和关键文案。
|
||||
- 增加组件 source 测试,确认不再使用 `window.prompt` 做风险指派和 SLA。
|
||||
- 复跑 `tests/admin-overview-route.test.ts`、`tests/admin-risk-actions-route.test.ts`、`tests/admin-skill-lifecycle-panel-source.test.ts`。
|
||||
- 最后跑 `npm run lint` 和相关 Node 测试。
|
||||
|
||||
## 分批落地
|
||||
|
||||
第一批直接做到可用:
|
||||
|
||||
1. 重构 `BossAdminApp` 外壳和一级导航。
|
||||
2. 做新版驾驶舱。
|
||||
3. 做风险战情室,替换 `window.prompt`。
|
||||
4. 账号授权和 Skill 治理先迁入新结构,并压缩视觉层级。
|
||||
|
||||
第二批再增强:
|
||||
|
||||
1. 客户详情抽屉。
|
||||
2. 新建客户三步流程。
|
||||
3. 风险筛选和搜索。
|
||||
4. 客户健康分数和趋势。
|
||||
|
||||
## 自检
|
||||
|
||||
- 无 TBD / TODO。
|
||||
- 范围聚焦 `/admin` PC 总后台,不触碰 APP。
|
||||
- 没有要求新增大后端能力,优先复用现有接口。
|
||||
- 关键交互从数据表改成工作台与战情室,解决“后台管理不太好”的主要问题。
|
||||
@@ -0,0 +1,75 @@
|
||||
# YuDao 风格企业后台独立化设计
|
||||
|
||||
日期:2026-04-30
|
||||
|
||||
## 背景
|
||||
|
||||
Boss 需要从“客户也能用的 Web 页面”升级为平台侧 To B 总后台。这个后台用于平台运营人员管理公司、老板账号、子账号、电脑节点、Skill 授权、风险告警和审计记录。现有 `/admin` 已能展示核心数据,但仍运行在 Next 主站内,信息架构不够像成熟企业后台,后续不适合承载更复杂的租户、权限和治理能力。
|
||||
|
||||
调研 `YunaiV/yudao-cloud` 后,结论是:不直接引入它的 Spring Cloud 微服务后端;借鉴它的租户、用户、角色、菜单、日志、工作台和独立前端思路。前端形态参考 YuDao 的 Vben/Vue 管理后台,数据仍由 Boss 现有状态账本和 Admin BFF 提供。
|
||||
|
||||
## 目标
|
||||
|
||||
第一批目标是完成企业后台独立化的可运行骨架:
|
||||
|
||||
- 新增独立 PC 后台工程 `apps/boss-admin-web`,使用 Vue + Vite + Ant Design Vue。
|
||||
- 新增 `/api/v1/admin/backoffice` 聚合接口,输出 YuDao 风格的菜单、工作台、租户、账号、角色权限、资源授权、风险和审计数据。
|
||||
- 保留现有 `/admin`,作为 Boss 主站内 fallback,不和独立后台互相替代。
|
||||
- 后台权限继续只允许 `highest_admin` 访问,不暴露密码哈希、MFA 密钥和会话令牌。
|
||||
- 新后台先复用 Boss Cookie 登录态,后续再接独立域名 `admin.boss.hyzq.net`。
|
||||
|
||||
## 非目标
|
||||
|
||||
- 不引入 YuDao Java 后端、MySQL 表结构或微服务网关。
|
||||
- 不在第一批替换所有现有后台 mutation 页面。
|
||||
- 不重新设计 Android APP。
|
||||
- 不改变当前 Boss 文件存储运行时。
|
||||
|
||||
## 架构
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
A["Boss Admin Web\nVue + Ant Design Vue"] --> B["/api/v1/admin/backoffice\nAdmin BFF"]
|
||||
B --> C["boss-state.json\n当前状态账本"]
|
||||
B --> D["buildAdminOverview\n现有后台聚合"]
|
||||
B --> E["BOSS_PERMISSION_TEMPLATES\n权限模板"]
|
||||
F["现有 /admin\nNext fallback"] --> G["/api/v1/admin/overview"]
|
||||
```
|
||||
|
||||
`apps/boss-admin-web` 是独立前端工程。它只消费 BFF,不直接读取本地文件,也不复制业务规则。`/api/v1/admin/backoffice` 是企业后台的新契约层,负责把 Boss 当前状态翻译为更稳定的后台管理模型。
|
||||
|
||||
## 数据模型
|
||||
|
||||
第一批 BFF 返回:
|
||||
|
||||
- `menuTree`:工作台、租户管理、账号管理、角色权限、资源授权、Skill 中心、风险告警、审计日志、系统设置。
|
||||
- `workbench`:总览指标、客户健康、设备健康、风险摘要。
|
||||
- `tenants`:公司列表、套餐、负责人、账号数、设备数、风险数。
|
||||
- `users`:账号、昵称、角色、状态、公司、最近登录。
|
||||
- `roles`:内置角色和权限模板。
|
||||
- `resourceGroups`:设备、项目线程和 Skill 目录。
|
||||
- `audit`:风险、风险时间线和权限审计。
|
||||
- `yudaoMapping`:Boss 账本字段到后台概念的映射,便于后续迁移数据库或接 YuDao 风格模块。
|
||||
|
||||
## UI 方向
|
||||
|
||||
第一批 UI 只做高保真骨架,不新增业务动作:
|
||||
|
||||
- 左侧固定菜单,右侧工作区。
|
||||
- 顶部展示当前账号、后台说明和刷新入口。
|
||||
- 工作台使用指标卡、风险横幅、客户健康和节点表。
|
||||
- 租户、账号、角色、资源、风险、审计分别使用独立区块或表格。
|
||||
- Skill 中心聚合展示 Skill 目录、来源、设备数和治理状态,后续再接完整安装向导。
|
||||
|
||||
## 权限与安全
|
||||
|
||||
- 未登录返回 `401`。
|
||||
- 非 `highest_admin` 返回 `403`。
|
||||
- BFF 只返回安全账号字段,不返回 `passwordHash`、`mfaSecret`、`authSessions` 或任何 session token。
|
||||
- 所有返回头使用 `private, no-store`,避免后台数据被缓存。
|
||||
|
||||
## 验证
|
||||
|
||||
- 新增 BFF 路由测试,验证鉴权、菜单结构、数据聚合和敏感字段过滤。
|
||||
- 新增独立前端源代码测试,验证工程骨架、API 契约、核心页面模块和根工程隔离。
|
||||
- 跑 `npm run lint` 和 `npm run build`,确认不会破坏现有 Next 主站。
|
||||
Reference in New Issue
Block a user