From bc464905a52e083682ef387ba6917e4e3e1e3c8d Mon Sep 17 00:00:00 2001 From: kris Date: Tue, 31 Mar 2026 17:29:39 +0800 Subject: [PATCH] docs: add master-agent chat controls plan --- .../2026-03-31-master-agent-chat-controls.md | 650 ++++++++++++++++++ 1 file changed, 650 insertions(+) create mode 100644 docs/superpowers/plans/2026-03-31-master-agent-chat-controls.md diff --git a/docs/superpowers/plans/2026-03-31-master-agent-chat-controls.md b/docs/superpowers/plans/2026-03-31-master-agent-chat-controls.md new file mode 100644 index 0000000..afe253a --- /dev/null +++ b/docs/superpowers/plans/2026-03-31-master-agent-chat-controls.md @@ -0,0 +1,650 @@ +# 主 Agent 对话控制与异步回复 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:** 让 `master-agent` 单聊改成“快速入队 + 异步回流”,并为当前对话补上模型选择、推理强度选择和微信式右上角三点菜单。 + +**Architecture:** 后端在文件型状态中为 `master-agent` 会话新增对话级控制项,并把消息发送接口改成优先快速返回 `queued/running` 状态;主 Agent 继续走现有 `Master Codex Node / OpenAI API` 路线完成真实回复回写。Android 聊天页把右上角动作收成微信式 `...` 菜单,并用轮询项目详情的方式展示“思考中 / 失败 / 已回复”状态。 + +**Tech Stack:** Next.js App Router, TypeScript, file-backed state store, Android AppCompat/Java, node:test, Gradle unit tests + +--- + +### Task 1: 为 `master-agent` 会话补对话级模型与推理强度配置 + +**Files:** +- Modify: `/Users/kris/code/boss/src/lib/boss-data.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-projections.ts` +- Modify: `/Users/kris/code/boss/src/app/api/v1/projects/[projectId]/route.ts` +- Create: `/Users/kris/code/boss/src/app/api/v1/projects/[projectId]/agent-controls/route.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-chat-controls.test.ts` + +- [ ] **Step 1: 写失败测试,覆盖 `master-agent` 对话配置读写** + +```ts +import test from "node:test"; +import assert from "node:assert/strict"; +import { + readState, + updateProjectAgentControls, + getProjectAgentControls, +} from "@/lib/boss-data"; + +test("master-agent 会话可保存模型与推理强度覆盖", async () => { + await updateProjectAgentControls("master-agent", { + modelOverride: "gpt-5.4", + reasoningEffortOverride: "high", + }); + + const controls = await getProjectAgentControls("master-agent"); + assert.equal(controls?.modelOverride, "gpt-5.4"); + assert.equal(controls?.reasoningEffortOverride, "high"); + + const state = await readState(); + const project = state.projects.find((item) => item.id === "master-agent"); + assert.equal(project?.agentControls?.modelOverride, "gpt-5.4"); + assert.equal(project?.agentControls?.reasoningEffortOverride, "high"); +}); +``` + +- [ ] **Step 2: 跑测试确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-chat-controls.test.ts +``` + +Expected: + +```text +FAIL ... updateProjectAgentControls is not a function +``` + +- [ ] **Step 3: 在状态模型中增加 `agentControls` 和读写 helper** + +```ts +export type ReasoningEffort = "low" | "medium" | "high"; + +export interface ProjectAgentControls { + modelOverride?: string; + reasoningEffortOverride?: ReasoningEffort; + updatedAt: string; +} + +export interface Project { + // ... + agentControls?: ProjectAgentControls; +} + +export async function getProjectAgentControls(projectId: string) { + const state = await readState(); + return state.projects.find((item) => item.id === projectId)?.agentControls; +} + +export async function updateProjectAgentControls( + projectId: string, + payload: { + modelOverride?: string; + reasoningEffortOverride?: ReasoningEffort; + }, +) { + return withStateLock(async (state) => { + const project = state.projects.find((item) => item.id === projectId); + if (!project) throw new Error("PROJECT_NOT_FOUND"); + project.agentControls = { + modelOverride: payload.modelOverride?.trim() || undefined, + reasoningEffortOverride: payload.reasoningEffortOverride, + updatedAt: new Date().toISOString(), + }; + return project.agentControls; + }); +} +``` + +- [ ] **Step 4: 补投影和 API** + +```ts +// src/lib/boss-projections.ts +agentControls: project.id === "master-agent" ? project.agentControls ?? null : undefined, +``` + +```ts +// src/app/api/v1/projects/[projectId]/agent-controls/route.ts +export async function GET(_: NextRequest, context: { params: Promise<{ projectId: string }> }) { + const session = await requireRequestSession(_); + if (!session) return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 }); + const { projectId } = await context.params; + const controls = await getProjectAgentControls(projectId); + return NextResponse.json({ ok: true, controls: controls ?? null }); +} + +export async function POST(request: NextRequest, context: { params: Promise<{ projectId: string }> }) { + const session = await requireRequestSession(request); + if (!session) return NextResponse.json({ ok: false, message: "UNAUTHORIZED" }, { status: 401 }); + const { projectId } = await context.params; + const body = (await request.json()) as { modelOverride?: string; reasoningEffortOverride?: "low" | "medium" | "high" }; + const controls = await updateProjectAgentControls(projectId, body); + return NextResponse.json({ ok: true, controls }); +} +``` + +- [ ] **Step 5: 再跑测试确认通过** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-chat-controls.test.ts +``` + +Expected: + +```text +# tests 1 +# pass 1 +``` + +- [ ] **Step 6: Commit** + +```bash +cd /Users/kris/code/boss +git add tests/master-agent-chat-controls.test.ts src/lib/boss-data.ts src/lib/boss-projections.ts src/app/api/v1/projects/[projectId]/route.ts src/app/api/v1/projects/[projectId]/agent-controls/route.ts +git commit -m "feat: add master-agent chat controls state" +``` + +### Task 2: 把主 Agent 消息接口改成快速返回 `queued/running` + +**Files:** +- Modify: `/Users/kris/code/boss/src/app/api/v1/projects/[projectId]/messages/route.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-master-agent.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-message-queue.test.ts` + +- [ ] **Step 1: 写失败测试,覆盖发送后立即返回任务状态** + +```ts +import test from "node:test"; +import assert from "node:assert/strict"; +import { POST } from "@/app/api/v1/projects/[projectId]/messages/route"; + +test("master-agent 发送后优先返回 queued 状态", async () => { + const request = new Request("http://localhost/api/v1/projects/master-agent/messages", { + method: "POST", + headers: { + "content-type": "application/json", + cookie: "boss_session=test", + }, + body: JSON.stringify({ body: "帮我检查当前主控", kind: "text" }), + }); + + const response = await POST(request as never, { + params: Promise.resolve({ projectId: "master-agent" }), + }); + const json = await response.json(); + + assert.equal(json.ok, true); + assert.equal(json.task.taskType, "conversation_reply"); + assert.match(json.masterReplyState, /queued|running|completed/); +}); +``` + +- [ ] **Step 2: 跑测试确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-message-queue.test.ts +``` + +Expected: + +```text +FAIL ... masterReplyState is undefined +``` + +- [ ] **Step 3: 在 `boss-master-agent.ts` 中拆分“入队”和“完成回写”语义** + +```ts +export async function replyToMasterAgentUserMessage(params: { + requestMessageId: string; + requestText: string; + requestedBy?: string; + requestedByAccount?: string; + currentSessionExpiresAt?: string; +}) { + const runtime = await getMasterAgentRuntime(); + const task = await queueMasterAgentTask({ + taskType: "conversation_reply", + projectId: "master-agent", + requestMessageId: params.requestMessageId, + requestText: params.requestText, + requestedBy: params.requestedBy, + requestedByAccount: params.requestedByAccount, + model: runtime.model, + reasoningEffort: runtime.reasoningEffort, + }); + + if (runtime.provider === "openai_api") { + void runQueuedMasterAgentReply(task.taskId, params.currentSessionExpiresAt); + } + + return { + ok: true as const, + taskId: task.taskId, + state: runtime.provider === "openai_api" ? "running" as const : "queued" as const, + }; +} +``` + +- [ ] **Step 4: 在消息路由中统一返回 `masterReplyState`** + +```ts +if (projectId === "master-agent" && (body.kind ?? "text") === "text" && message.body.trim()) { + const queued = await replyToMasterAgentUserMessage({ + requestMessageId: message.id, + requestText: message.body, + requestedBy: session.displayName, + requestedByAccount: session.account, + currentSessionExpiresAt: session.expiresAt, + }); + + masterReply = { + ok: queued.ok, + taskId: queued.taskId, + }; + task = { + taskId: queued.taskId, + taskType: "conversation_reply", + status: queued.state === "queued" ? "queued" : "running", + }; + masterReplyState = queued.state; +} +``` + +- [ ] **Step 5: 再跑测试确认通过** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-message-queue.test.ts +``` + +Expected: + +```text +# tests 1 +# pass 1 +``` + +- [ ] **Step 6: Commit** + +```bash +cd /Users/kris/code/boss +git add tests/master-agent-message-queue.test.ts src/app/api/v1/projects/[projectId]/messages/route.ts src/lib/boss-master-agent.ts +git commit -m "feat: queue master-agent replies asynchronously" +``` + +### Task 3: 让主 Agent 实际调用优先读取当前对话模型与推理强度 + +**Files:** +- Modify: `/Users/kris/code/boss/src/lib/boss-master-agent.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-data.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-config-resolution.test.ts` + +- [ ] **Step 1: 写失败测试,覆盖当前对话 override 优先级** + +```ts +import test from "node:test"; +import assert from "node:assert/strict"; +import { resolveMasterAgentExecutionConfig } from "@/lib/boss-master-agent"; + +test("当前对话 override 优先于主控账号默认值", async () => { + const resolved = await resolveMasterAgentExecutionConfig("master-agent"); + assert.equal(resolved.model, "gpt-5.4"); + assert.equal(resolved.reasoningEffort, "high"); +}); +``` + +- [ ] **Step 2: 跑测试确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-config-resolution.test.ts +``` + +Expected: + +```text +FAIL ... resolveMasterAgentExecutionConfig is not a function +``` + +- [ ] **Step 3: 增加统一解析函数并接入 OpenAI / Master Node 执行路径** + +```ts +export async function resolveMasterAgentExecutionConfig(projectId: string) { + const runtime = await getMasterAgentRuntimeAccount(); + const controls = await getProjectAgentControls(projectId); + return { + provider: runtime.account.provider, + model: controls?.modelOverride || runtime.account.model || "gpt-5.4", + reasoningEffort: controls?.reasoningEffortOverride || runtime.account.reasoningEffort || "medium", + account: runtime.account, + }; +} +``` + +```ts +// generateOpenAiReply +body: JSON.stringify({ + model: params.model, + reasoning: { effort: params.reasoningEffort }, + instructions: buildMasterAgentInstructions(), + input: buildRuntimeDigest(state, params.requestText, params.currentSessionExpiresAt), +}), +``` + +- [ ] **Step 4: 再跑测试确认通过** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-config-resolution.test.ts +``` + +Expected: + +```text +# tests 1 +# pass 1 +``` + +- [ ] **Step 5: Commit** + +```bash +cd /Users/kris/code/boss +git add tests/master-agent-config-resolution.test.ts src/lib/boss-master-agent.ts src/lib/boss-data.ts +git commit -m "feat: apply per-chat model and reasoning controls" +``` + +### Task 4: Android 聊天页改成微信式三点菜单,并接入模型/推理强度设置 + +**Files:** +- Modify: `/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java` +- Modify: `/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossApiClient.java` +- Modify: `/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/BossScreenActivity.java` +- Test: `/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java` +- Test: `/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java` + +- [ ] **Step 1: 写失败测试,覆盖主 Agent 页显示三点菜单与设置请求** + +```java +@Test +public void masterAgentHeaderShowsWechatMoreMenu() { + ProjectDetailActivity.ChromeBindings bindings = + ProjectDetailActivity.computeChromeBindingsForTesting( + "master-agent", + false, + false, + true, + "主 Agent", + "" + ); + + assertThat(bindings.showHeaderAction).isTrue(); + assertThat(bindings.headerActionLabel).isEqualTo("..."); +} +``` + +```java +@Test +public void updateMasterAgentControlsPostsModelAndReasoning() throws Exception { + BossApiClient client = new BossApiClient(new InMemorySharedPreferences(), "https://boss.hyzq.net"); + // 断言 request body 带 modelOverride / reasoningEffortOverride +} +``` + +- [ ] **Step 2: 跑测试确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss/android +./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityUiTest --tests com.hyzq.boss.BossApiClientDispatchPlansTest --no-daemon +``` + +Expected: + +```text +FAIL ... headerActionLabel +FAIL ... updateMasterAgentControls +``` + +- [ ] **Step 3: 在 Android API 客户端里补 controls 读写** + +```java +public ApiResponse getProjectAgentControls(String projectId) throws IOException, JSONException { + return requestWithRestore("GET", "/api/v1/projects/" + encode(projectId) + "/agent-controls", null); +} + +public ApiResponse updateProjectAgentControls( + String projectId, + String modelOverride, + String reasoningEffortOverride +) throws IOException, JSONException { + JSONObject payload = new JSONObject(); + payload.put("modelOverride", modelOverride); + payload.put("reasoningEffortOverride", reasoningEffortOverride); + return requestWithRestore("POST", "/api/v1/projects/" + encode(projectId) + "/agent-controls", payload); +} +``` + +- [ ] **Step 4: 在聊天页把右上角改成 `...`,并弹出单选菜单** + +```java +if ("master-agent".equals(projectId)) { + setHeaderAction("...", v -> showMasterAgentMoreMenu()); +} + +private void showMasterAgentMoreMenu() { + String[] items = new String[] {"模型", "推理强度", "会话信息", "刷新"}; + new AlertDialog.Builder(this) + .setItems(items, (dialog, which) -> { + if (which == 0) openModelPicker(); + else if (which == 1) openReasoningPicker(); + else if (which == 2) openConversationInfo(); + else reload(); + }) + .show(); +} +``` + +- [ ] **Step 5: 再跑 Android 单测确认通过** + +Run: + +```bash +cd /Users/kris/code/boss/android +./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityUiTest --tests com.hyzq.boss.BossApiClientDispatchPlansTest --no-daemon +``` + +Expected: + +```text +BUILD SUCCESSFUL +``` + +- [ ] **Step 6: Commit** + +```bash +cd /Users/kris/code/boss +git add android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java android/app/src/main/java/com/hyzq/boss/BossApiClient.java android/app/src/main/java/com/hyzq/boss/BossScreenActivity.java android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityUiTest.java android/app/src/test/java/com/hyzq/boss/BossApiClientDispatchPlansTest.java +git commit -m "feat: add master-agent chat controls menu" +``` + +### Task 5: Android 主 Agent 聊天页改成“立即显示思考中,再异步收回复” + +**Files:** +- Modify: `/Users/kris/code/boss/android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java` +- Test: `/Users/kris/code/boss/android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityChatFlowTest.java` + +- [ ] **Step 1: 写失败测试,覆盖发送后立即显示等待态** + +```java +@Test +public void masterAgentSendShowsThinkingStateWhenTaskQueued() { + // 构造发送响应: { task: { taskId: "task-1", taskType: "conversation_reply", status: "queued" }, masterReplyState: "queued" } + // 断言聊天页出现“主 Agent 思考中” +} +``` + +- [ ] **Step 2: 跑测试确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss/android +./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityChatFlowTest --no-daemon +``` + +Expected: + +```text +FAIL ... expected "主 Agent 思考中" +``` + +- [ ] **Step 3: 实现等待态与轮询收束** + +```java +if ("master-agent".equals(projectId) && taskStatusQueuedOrRunning(response)) { + showPendingMasterAgentState("主 Agent 思考中"); + scheduleProjectReloadPoll(REPLY_WAIT_POLL_INTERVAL_MS, REPLY_WAIT_TIMEOUT_MS); + return; +} +``` + +```java +private void scheduleProjectReloadPoll(long intervalMs, long timeoutMs) { + long startedAt = System.currentTimeMillis(); + contentLayout.postDelayed(new Runnable() { + @Override + public void run() { + if (!isFinishing() && System.currentTimeMillis() - startedAt < timeoutMs) { + reload(false); + contentLayout.postDelayed(this, intervalMs); + } + } + }, intervalMs); +} +``` + +- [ ] **Step 4: 再跑测试确认通过** + +Run: + +```bash +cd /Users/kris/code/boss/android +./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityChatFlowTest --no-daemon +``` + +Expected: + +```text +BUILD SUCCESSFUL +``` + +- [ ] **Step 5: Commit** + +```bash +cd /Users/kris/code/boss +git add android/app/src/main/java/com/hyzq/boss/ProjectDetailActivity.java android/app/src/test/java/com/hyzq/boss/ProjectDetailActivityChatFlowTest.java +git commit -m "feat: show queued state for master-agent replies" +``` + +### Task 6: 文档、验证、部署 + +**Files:** +- Modify: `/Users/kris/code/boss/README.md` +- Modify: `/Users/kris/code/boss/docs/architecture/current_runtime_and_deploy_status_cn.md` +- Modify: `/Users/kris/code/boss/docs/architecture/api_and_service_inventory_cn.md` + +- [ ] **Step 1: 跑 Node 与 Android 测试** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/master-agent-chat-controls.test.ts tests/master-agent-message-queue.test.ts tests/master-agent-config-resolution.test.ts +cd /Users/kris/code/boss/android +./gradlew testDebugUnitTest --tests com.hyzq.boss.ProjectDetailActivityUiTest --tests com.hyzq.boss.BossApiClientDispatchPlansTest --tests com.hyzq.boss.ProjectDetailActivityChatFlowTest --no-daemon +``` + +Expected: + +```text +pass ... master-agent-chat-controls +pass ... master-agent-message-queue +pass ... master-agent-config-resolution +BUILD SUCCESSFUL +``` + +- [ ] **Step 2: 跑基础验证** + +Run: + +```bash +cd /Users/kris/code/boss +npm run lint +npm run build +curl -sS http://127.0.0.1:3000/api/health +curl -sS http://127.0.0.1:4317/health +``` + +Expected: + +```text +lint 通过 +build 通过 +{"ok":true,...} +{"ok":true,...} +``` + +- [ ] **Step 3: 更新文档** + +```md +- 主 Agent 单聊发送已改成快速入队;前台会立即显示“主 Agent 思考中”,不再同步长等待 +- `master-agent` 对话当前支持对话级 `模型 / 推理强度` 覆盖 +- 原生 Android 聊天页右上角已改成微信式 `...` 菜单,包含 `模型 / 推理强度 / 会话信息 / 刷新` +``` + +- [ ] **Step 4: 部署并验证公网** + +Run: + +```bash +cd /Users/kris/code/boss +./scripts/deploy-server.sh +"$HOME/.codex/skills/boss-server-debug/scripts/server_ssh.sh" exec "curl -sS http://127.0.0.1:3000/api/health" +curl -sS https://boss.hyzq.net/api/health +``` + +Expected: + +```text +{"ok":true,...} +{"ok":true,...} +``` + +- [ ] **Step 5: Commit** + +```bash +cd /Users/kris/code/boss +git add README.md docs/architecture/current_runtime_and_deploy_status_cn.md docs/architecture/api_and_service_inventory_cn.md +git commit -m "docs: document master-agent chat controls" +``` +