From 5e23e1d408d5a171773737ff08481370b397f84d Mon Sep 17 00:00:00 2001 From: kris Date: Thu, 2 Apr 2026 20:43:59 +0800 Subject: [PATCH] docs: add execution foundation implementation plan --- .../2026-04-02-boss-execution-foundation.md | 868 ++++++++++++++++++ 1 file changed, 868 insertions(+) create mode 100644 docs/superpowers/plans/2026-04-02-boss-execution-foundation.md diff --git a/docs/superpowers/plans/2026-04-02-boss-execution-foundation.md b/docs/superpowers/plans/2026-04-02-boss-execution-foundation.md new file mode 100644 index 0000000..a7dd37c --- /dev/null +++ b/docs/superpowers/plans/2026-04-02-boss-execution-foundation.md @@ -0,0 +1,868 @@ +# 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、普通线程回复、群聊分发的底层执行职责从散落逻辑中抽出,为后续接入 `ClawBackendAdapter` 和 `OmxTeamBackendAdapter` 做准备。 + +**Architecture:** 先新增 `src/lib/execution/` 下的接口与默认实现,再把 `boss-master-agent.ts`、`local-agent/server.mjs` 和关键 API route 逐步映射到这些抽象。整个过程坚持“先平移、不改行为”,所有已有主链继续沿用 `boss-state.json + master-agent queue + local-agent + codex exec resume`。 + +**Tech Stack:** Next.js App Router、TypeScript、Node.js、文件型状态 `data/boss-state.json`、原生 `local-agent`、现有测试栈 `tsx --test`、Android/前台不改行为。 + +--- + +### Task 1: 定义执行底座类型与默认 contract + +**Files:** +- Create: `/Users/kris/code/boss/src/lib/execution/types.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/execution-backend.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/orchestration-backend.ts` +- Test: `/Users/kris/code/boss/tests/execution-foundation-contracts.test.ts` + +- [ ] **Step 1: 写失败测试,定义执行底座 contract 期望** + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; +import { + createExecutionRequest, + isQueuedExecutionResult, + isImmediateExecutionResult, +} from "@/lib/execution/types"; + +test("ExecutionRequest 工厂会生成稳定默认字段", () => { + const request = createExecutionRequest({ + kind: "master_agent_reply", + projectId: "master-agent", + requestMessageId: "msg-1", + body: "你好", + }); + + assert.equal(request.kind, "master_agent_reply"); + assert.equal(request.projectId, "master-agent"); + assert.equal(request.requestMessageId, "msg-1"); + assert.equal(request.body, "你好"); + assert.equal(request.targetProjectId, undefined); + assert.equal(request.targetThreadId, undefined); +}); + +test("ExecutionResult 类型守卫能区分 queued 与 immediate", () => { + const queued = { + status: "queued", + taskId: "task-1", + backendId: "master-codex-node", + }; + const completed = { + status: "completed", + backendId: "openai-api", + output: "done", + }; + + assert.equal(isQueuedExecutionResult(queued), true); + assert.equal(isImmediateExecutionResult(queued), false); + assert.equal(isQueuedExecutionResult(completed), false); + assert.equal(isImmediateExecutionResult(completed), true); +}); +``` + +- [ ] **Step 2: 运行测试,确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-foundation-contracts.test.ts +``` + +Expected: + +```text +ERR_MODULE_NOT_FOUND +``` + +- [ ] **Step 3: 写最小 contract 类型与工厂函数** + +```ts +// /Users/kris/code/boss/src/lib/execution/types.ts +import type { ReasoningEffort } from "@/lib/boss-data"; + +export type ExecutionRequestKind = + | "master_agent_reply" + | "thread_reply" + | "dispatch_execution" + | "attachment_analysis"; + +export interface ExecutionRequest { + kind: ExecutionRequestKind; + projectId: string; + requestMessageId: string; + body: string; + requestedByAccount?: string; + requestedByLabel?: string; + taskId?: string; + targetThreadId?: string; + targetProjectId?: string; + modelOverride?: string; + reasoningEffortOverride?: ReasoningEffort; +} + +export interface ExecutionQueuedResult { + status: "queued" | "running"; + taskId: string; + backendId: string; + sessionId?: string; +} + +export interface ExecutionImmediateResult { + status: "completed" | "failed"; + backendId: string; + output?: string; + error?: string; +} + +export interface ExecutionBackendDescription { + backendId: string; + label: string; + mode: "local" | "remote" | "api"; +} + +export function createExecutionRequest(input: ExecutionRequest): ExecutionRequest { + return { ...input }; +} + +export function isQueuedExecutionResult( + value: ExecutionQueuedResult | ExecutionImmediateResult, +): value is ExecutionQueuedResult { + return value.status === "queued" || value.status === "running"; +} + +export function isImmediateExecutionResult( + value: ExecutionQueuedResult | ExecutionImmediateResult, +): value is ExecutionImmediateResult { + return value.status === "completed" || value.status === "failed"; +} +``` + +```ts +// /Users/kris/code/boss/src/lib/execution/execution-backend.ts +import type { + ExecutionBackendDescription, + ExecutionImmediateResult, + ExecutionQueuedResult, + ExecutionRequest, +} from "@/lib/execution/types"; + +export interface ExecutionBackend { + backendId: string; + canHandle(input: ExecutionRequest): Promise | boolean; + execute(input: ExecutionRequest): Promise; + describe(input: ExecutionRequest): Promise; +} +``` + +```ts +// /Users/kris/code/boss/src/lib/execution/orchestration-backend.ts +export interface OrchestrationBackend { + backendId: string; + describe(): Promise<{ backendId: string; label: string }>; +} +``` + +- [ ] **Step 4: 再跑测试,确认 contract 生效** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-foundation-contracts.test.ts +``` + +Expected: + +```text +# pass 2 +``` + +- [ ] **Step 5: 提交** + +```bash +cd /Users/kris/code/boss +git add tests/execution-foundation-contracts.test.ts src/lib/execution/types.ts src/lib/execution/execution-backend.ts src/lib/execution/orchestration-backend.ts +git commit -m "feat: add execution foundation contracts" +``` + +### Task 2: 抽离 PromptAssembler 与 MemoryResolver + +**Files:** +- Create: `/Users/kris/code/boss/src/lib/execution/prompt-assembler.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/memory-resolver.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-master-agent.ts` +- Test: `/Users/kris/code/boss/tests/execution-prompt-assembler.test.ts` +- Test: `/Users/kris/code/boss/tests/execution-memory-resolver.test.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-config-resolution.test.ts` + +- [ ] **Step 1: 写失败测试,固定记忆筛选与 prompt 拼装顺序** + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; +import { + buildExecutionPromptForTesting, + resolveRelevantMemoriesForTesting, +} from "@/lib/execution/prompt-assembler"; + +test("PromptAssembler 会按固定顺序拼管理员提示词、用户提示词、对话提示词和记忆", () => { + const prompt = buildExecutionPromptForTesting({ + globalPrompt: "GLOBAL", + userPrompt: "USER", + conversationPrompt: "CONVERSATION", + projectMemories: [{ title: "项目记忆", content: "PROJECT", projectId: "boss-console", tags: [] }], + userMemories: [{ title: "用户记忆", content: "USER_MEMORY", tags: [] }], + requestText: "继续", + }); + + assert.match(prompt, /GLOBAL/); + assert.match(prompt, /USER/); + assert.match(prompt, /CONVERSATION/); + assert.match(prompt, /PROJECT/); + assert.match(prompt, /USER_MEMORY/); + assert.ok(prompt.indexOf("GLOBAL") < prompt.indexOf("USER")); + assert.ok(prompt.indexOf("USER") < prompt.indexOf("CONVERSATION")); +}); + +test("MemoryResolver 在 master-agent 会话下优先挑当前请求命中的项目记忆", () => { + const resolved = resolveRelevantMemoriesForTesting({ + projectId: "master-agent", + requestText: "boss-console 的审批流", + memories: [ + { memoryId: "m1", scope: "project", projectId: "boss-console", title: "审批流", content: "boss-console approval", tags: ["approval"] }, + { memoryId: "m2", scope: "project", projectId: "wenshenapp", title: "UI", content: "wechat ui", tags: ["ui"] }, + ], + }); + + assert.equal(resolved.projectMemories.length, 1); + assert.equal(resolved.projectMemories[0]?.projectId, "boss-console"); +}); +``` + +- [ ] **Step 2: 运行测试,确认当前失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-prompt-assembler.test.ts tests/execution-memory-resolver.test.ts +``` + +Expected: + +```text +ERR_MODULE_NOT_FOUND +``` + +- [ ] **Step 3: 写最小实现,并让 boss-master-agent 改用新模块** + +```ts +// /Users/kris/code/boss/src/lib/execution/memory-resolver.ts +export function resolveRelevantMemories(input: { + projectId: string; + requestText?: string; + memories: Array<{ + memoryId: string; + scope: "global" | "project"; + projectId?: string; + title: string; + content: string; + tags?: string[]; + }>; +}) { + const lowered = input.requestText?.toLowerCase() ?? ""; + const projectMemories = input.memories.filter((memory) => { + if (memory.scope !== "project") return false; + if (input.projectId !== "master-agent") { + return memory.projectId === input.projectId; + } + if (!lowered) return true; + return [memory.projectId, memory.title, memory.content, ...(memory.tags ?? [])] + .filter(Boolean) + .some((value) => value!.toLowerCase().includes(lowered) || lowered.includes(value!.toLowerCase())); + }); + const userMemories = input.memories.filter((memory) => memory.scope === "global"); + return { + projectMemories: projectMemories.slice(0, 6), + userMemories: userMemories.slice(0, 8), + }; +} + +export const resolveRelevantMemoriesForTesting = resolveRelevantMemories; +``` + +```ts +// /Users/kris/code/boss/src/lib/execution/prompt-assembler.ts +import { resolveRelevantMemories } from "@/lib/execution/memory-resolver"; + +export function buildExecutionPrompt(input: { + globalPrompt?: string | null; + userPrompt?: string | null; + conversationPrompt?: string | null; + projectMemories: Array<{ title: string; content: string; projectId?: string; tags?: string[] }>; + userMemories: Array<{ title: string; content: string; tags?: string[] }>; + requestText: string; +}) { + return [ + input.globalPrompt?.trim() ? `管理员全局主提示词:\n${input.globalPrompt.trim()}` : null, + input.userPrompt?.trim() ? `用户私有主提示词:\n${input.userPrompt.trim()}` : null, + input.conversationPrompt?.trim() ? `当前对话附加提示词:\n${input.conversationPrompt.trim()}` : null, + input.projectMemories.length + ? `项目记忆:\n${input.projectMemories.map((item) => `- [${item.projectId ?? "unknown"}] ${item.title}: ${item.content}`).join("\n")}` + : null, + input.userMemories.length + ? `用户通用记忆:\n${input.userMemories.map((item) => `- ${item.title}: ${item.content}`).join("\n")}` + : null, + `当前消息:\n${input.requestText}`, + ].filter(Boolean).join("\n\n"); +} + +export const buildExecutionPromptForTesting = buildExecutionPrompt; +export { resolveRelevantMemoriesForTesting } from "@/lib/execution/memory-resolver"; +``` + +在 `/Users/kris/code/boss/src/lib/boss-master-agent.ts` 中,把当前内联的 memory selection 与 prompt assembly 改成调用: + +```ts +import { buildExecutionPrompt } from "@/lib/execution/prompt-assembler"; +import { resolveRelevantMemories } from "@/lib/execution/memory-resolver"; +``` + +并在 `resolveMasterAgentExecutionConfig(...)` 中改成: + +```ts +const memoryScope = listUserMasterMemoriesView(state, resolvedAccountId, { includeArchived: false }); +const { projectMemories, userMemories } = resolveRelevantMemories({ + projectId, + requestText, + memories: memoryScope, +}); + +const executionPrompt = buildExecutionPrompt({ + globalPrompt: promptPolicy?.globalPrompt ?? null, + userPrompt: userPrompt?.content ?? null, + conversationPrompt: scopedAgentControls?.promptOverride ?? null, + projectMemories, + userMemories, + requestText: requestText ?? "", +}); +``` + +- [ ] **Step 4: 跑新测试和既有主 Agent 测试** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-prompt-assembler.test.ts tests/execution-memory-resolver.test.ts tests/master-agent-config-resolution.test.ts +``` + +Expected: + +```text +# pass +``` + +- [ ] **Step 5: 提交** + +```bash +cd /Users/kris/code/boss +git add tests/execution-prompt-assembler.test.ts tests/execution-memory-resolver.test.ts tests/master-agent-config-resolution.test.ts src/lib/execution/prompt-assembler.ts src/lib/execution/memory-resolver.ts src/lib/boss-master-agent.ts +git commit -m "refactor: extract execution prompt assembly" +``` + +### Task 3: 抽离 PermissionPolicy 与 ToolRegistry + +**Files:** +- Create: `/Users/kris/code/boss/src/lib/execution/permission-policy.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/tool-registry.ts` +- Modify: `/Users/kris/code/boss/src/app/api/v1/projects/[projectId]/messages/route.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-data.ts` +- Test: `/Users/kris/code/boss/tests/execution-permission-policy.test.ts` +- Test: `/Users/kris/code/boss/tests/group-message-dispatch-plan.test.ts` +- Test: `/Users/kris/code/boss/tests/dispatch-plan-confirmation.test.ts` + +- [ ] **Step 1: 写失败测试,固定 approval_required 和 tool policy 语义** + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; +import { evaluatePermissionPolicyForTesting } from "@/lib/execution/permission-policy"; + +test("approval_required 群聊在已有待确认推荐时拒绝继续直接执行", () => { + const result = evaluatePermissionPolicyForTesting({ + project: { + id: "group-1", + isGroup: true, + collaborationMode: "approval_required", + approvalState: "pending_user", + }, + hasPendingDispatchPlan: true, + }); + + assert.equal(result.allowed, false); + assert.equal(result.requiresApproval, true); + assert.match(result.reason ?? "", /等待确认/); +}); +``` + +- [ ] **Step 2: 运行测试确认失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-permission-policy.test.ts +``` + +Expected: + +```text +ERR_MODULE_NOT_FOUND +``` + +- [ ] **Step 3: 写权限策略与工具注册最小实现,并在消息 route 中复用** + +```ts +// /Users/kris/code/boss/src/lib/execution/permission-policy.ts +export function evaluatePermissionPolicy(input: { + project?: { + id: string; + isGroup: boolean; + collaborationMode: "development" | "approval_required"; + approvalState: "not_required" | "pending_agent" | "pending_user" | "approved" | "rejected"; + }; + hasPendingDispatchPlan?: boolean; +}) { + const project = input.project; + if (!project) { + return { + allowed: true, + requiresApproval: false, + toolPolicy: { allowedTools: [], deniedTools: [] }, + collaborationPolicy: { + mode: "development" as const, + canDispatchDirectly: true, + canCrossThreadTalk: true, + }, + }; + } + + if (project.isGroup && project.collaborationMode === "approval_required" && input.hasPendingDispatchPlan) { + return { + allowed: false, + requiresApproval: true, + reason: "当前还有一条主 Agent 推荐等待确认,请先确认或拒绝后再继续发送新指令。", + toolPolicy: { allowedTools: [], deniedTools: ["dispatch_execution"] }, + collaborationPolicy: { + mode: "approval_required" as const, + canDispatchDirectly: false, + canCrossThreadTalk: false, + }, + }; + } + + return { + allowed: true, + requiresApproval: project.isGroup && project.collaborationMode === "approval_required", + toolPolicy: { allowedTools: ["conversation_reply", "dispatch_execution"], deniedTools: [] }, + collaborationPolicy: { + mode: project.collaborationMode, + canDispatchDirectly: project.collaborationMode === "development", + canCrossThreadTalk: project.collaborationMode === "development", + }, + }; +} + +export const evaluatePermissionPolicyForTesting = evaluatePermissionPolicy; +``` + +```ts +// /Users/kris/code/boss/src/lib/execution/tool-registry.ts +export function listExecutionTools() { + return [ + { name: "conversation_reply", kind: "execution" }, + { name: "dispatch_execution", kind: "execution" }, + { name: "attachment_analysis", kind: "analysis" }, + ] as const; +} +``` + +在 `/Users/kris/code/boss/src/app/api/v1/projects/[projectId]/messages/route.ts` 中,把 `approval_required + pending plan` 判断替换成: + +```ts +const permission = evaluatePermissionPolicy({ + project, + hasPendingDispatchPlan: Boolean(pendingPlan), +}); + +if (!permission.allowed) { + return NextResponse.json( + { + ok: false, + message: permission.reason, + pendingPlan, + collaborationGate: buildCollaborationGate(project), + }, + { status: 409 }, + ); +} +``` + +- [ ] **Step 4: 跑权限测试与现有 dispatch 测试** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-permission-policy.test.ts tests/group-message-dispatch-plan.test.ts tests/dispatch-plan-confirmation.test.ts +``` + +Expected: + +```text +# pass +``` + +- [ ] **Step 5: 提交** + +```bash +cd /Users/kris/code/boss +git add tests/execution-permission-policy.test.ts tests/group-message-dispatch-plan.test.ts tests/dispatch-plan-confirmation.test.ts src/lib/execution/permission-policy.ts src/lib/execution/tool-registry.ts src/app/api/v1/projects/[projectId]/messages/route.ts +git commit -m "refactor: extract execution permission policy" +``` + +### Task 4: 抽离 ExecutionBackendSelector 与默认执行后端 + +**Files:** +- Create: `/Users/kris/code/boss/src/lib/execution/backend-selector.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/backends/master-codex-node-backend.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/backends/openai-backend.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/backends/aliyun-qwen-backend.ts` +- Modify: `/Users/kris/code/boss/src/lib/boss-master-agent.ts` +- Test: `/Users/kris/code/boss/tests/execution-backend-selector.test.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-openai-fallback.test.ts` +- Test: `/Users/kris/code/boss/tests/master-agent-message-queue.test.ts` + +- [ ] **Step 1: 写失败测试,固定后端选择优先级** + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; +import { selectExecutionBackendForTesting } from "@/lib/execution/backend-selector"; + +test("主控可用时优先选择当前主控 backend", async () => { + const backend = await selectExecutionBackendForTesting({ + primary: { provider: "master_codex_node", status: "ready" }, + backups: [{ provider: "aliyun_qwen_api", status: "ready" }], + }); + + assert.equal(backend.backendId, "master-codex-node"); +}); + +test("主控 degraded 时回退到可用阿里备用 backend", async () => { + const backend = await selectExecutionBackendForTesting({ + primary: { provider: "master_codex_node", status: "degraded" }, + backups: [{ provider: "aliyun_qwen_api", status: "ready" }], + }); + + assert.equal(backend.backendId, "aliyun-qwen"); +}); +``` + +- [ ] **Step 2: 运行测试确认失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-backend-selector.test.ts +``` + +Expected: + +```text +ERR_MODULE_NOT_FOUND +``` + +- [ ] **Step 3: 写 selector 和默认 backend 壳,并让 boss-master-agent 通过 selector 取 backend** + +```ts +// /Users/kris/code/boss/src/lib/execution/backend-selector.ts +export async function selectExecutionBackend(input: { + primary: { provider: string; status: string }; + backups: Array<{ provider: string; status: string }>; +}) { + if (input.primary.provider === "master_codex_node" && input.primary.status === "ready") { + return { backendId: "master-codex-node" }; + } + const qwen = input.backups.find((item) => item.provider === "aliyun_qwen_api" && item.status === "ready"); + if (qwen) { + return { backendId: "aliyun-qwen" }; + } + const openai = input.backups.find((item) => item.provider === "openai_api" && item.status === "ready"); + if (openai) { + return { backendId: "openai-api" }; + } + return { backendId: "master-codex-node" }; +} + +export const selectExecutionBackendForTesting = selectExecutionBackend; +``` + +在 `/Users/kris/code/boss/src/lib/boss-master-agent.ts` 中,把 provider fallback 的选择逻辑收进一个单独调用: + +```ts +const selectedBackend = await selectExecutionBackend({ + primary: { provider: runtime.account.provider, status: runtime.account.status }, + backups: state.aiAccounts + .filter((item) => item.accountId !== runtime.account.accountId) + .map((item) => ({ provider: item.provider, status: item.status })), +}); +``` + +并让后续 provider-specific 分支改成按 `selectedBackend.backendId` 分派,而不是继续直接拼条件。 + +- [ ] **Step 4: 跑 selector 测试和既有 fallback 测试** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/execution-backend-selector.test.ts tests/master-agent-openai-fallback.test.ts tests/master-agent-message-queue.test.ts +``` + +Expected: + +```text +# pass +``` + +- [ ] **Step 5: 提交** + +```bash +cd /Users/kris/code/boss +git add tests/execution-backend-selector.test.ts tests/master-agent-openai-fallback.test.ts tests/master-agent-message-queue.test.ts src/lib/execution/backend-selector.ts src/lib/execution/backends/master-codex-node-backend.ts src/lib/execution/backends/openai-backend.ts src/lib/execution/backends/aliyun-qwen-backend.ts src/lib/boss-master-agent.ts +git commit -m "refactor: add execution backend selector" +``` + +### Task 5: 抽离 RemoteRuntimeAdapter 与 OrchestrationBackend + +**Files:** +- Create: `/Users/kris/code/boss/src/lib/execution/remote-runtime-adapter.ts` +- Create: `/Users/kris/code/boss/src/lib/execution/backends/boss-native-orchestrator.ts` +- Modify: `/Users/kris/code/boss/local-agent/server.mjs` +- Modify: `/Users/kris/code/boss/src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts` +- Test: `/Users/kris/code/boss/tests/remote-runtime-adapter.test.ts` +- Test: `/Users/kris/code/boss/tests/dispatch-execution-result.test.ts` +- Test: `/Users/kris/code/boss/tests/local-agent-codex-task-runner.test.mjs` + +- [ ] **Step 1: 写失败测试,固定远程执行结果和编排分层** + +```ts +import assert from "node:assert/strict"; +import test from "node:test"; +import { normalizeRemoteExecutionResultForTesting } from "@/lib/execution/remote-runtime-adapter"; + +test("RemoteRuntimeAdapter 会把 local-agent 回写标准化成统一结果", () => { + const normalized = normalizeRemoteExecutionResultForTesting({ + status: "completed", + dispatchExecutionId: "dx-1", + targetProjectId: "project-1", + targetThreadId: "thread-1", + rawThreadReply: "链路正常", + }); + + assert.equal(normalized.status, "completed"); + assert.equal(normalized.targetProjectId, "project-1"); + assert.equal(normalized.rawThreadReply, "链路正常"); +}); +``` + +- [ ] **Step 2: 运行测试确认失败** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/remote-runtime-adapter.test.ts +``` + +Expected: + +```text +ERR_MODULE_NOT_FOUND +``` + +- [ ] **Step 3: 写最小 adapter,并让 complete route 和 local-agent 对齐这个结构** + +```ts +// /Users/kris/code/boss/src/lib/execution/remote-runtime-adapter.ts +export function normalizeRemoteExecutionResult(input: { + status: "completed" | "failed"; + dispatchExecutionId?: string; + targetProjectId?: string; + targetThreadId?: string; + rawThreadReply?: string; + replyBody?: string; + errorMessage?: string; +}) { + return { + status: input.status, + dispatchExecutionId: input.dispatchExecutionId, + targetProjectId: input.targetProjectId, + targetThreadId: input.targetThreadId, + rawThreadReply: input.rawThreadReply, + replyBody: input.replyBody, + errorMessage: input.errorMessage, + }; +} + +export const normalizeRemoteExecutionResultForTesting = normalizeRemoteExecutionResult; +``` + +在 `/Users/kris/code/boss/src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts` 中增加: + +```ts +import { normalizeRemoteExecutionResult } from "@/lib/execution/remote-runtime-adapter"; +``` + +并把 body 传入 `completeMasterAgentTask` 前先标准化: + +```ts +const normalized = normalizeRemoteExecutionResult(body); +``` + +在 `/Users/kris/code/boss/local-agent/server.mjs` 中,把 `conversation_reply / dispatch_execution` 结果拼装收进一个小 helper,输出结构与 `normalizeRemoteExecutionResult` 对齐。 + +- [ ] **Step 4: 跑 adapter 测试和既有 dispatch/local-agent 测试** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test tests/remote-runtime-adapter.test.ts tests/dispatch-execution-result.test.ts +node --test tests/local-agent-codex-task-runner.test.mjs +``` + +Expected: + +```text +# pass +``` + +- [ ] **Step 5: 提交** + +```bash +cd /Users/kris/code/boss +git add tests/remote-runtime-adapter.test.ts tests/dispatch-execution-result.test.ts tests/local-agent-codex-task-runner.test.mjs src/lib/execution/remote-runtime-adapter.ts src/lib/execution/backends/boss-native-orchestrator.ts src/app/api/v1/master-agent/tasks/[taskId]/complete/route.ts local-agent/server.mjs +git commit -m "refactor: add remote runtime adapter" +``` + +### 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: 跑本轮完整验证矩阵** + +Run: + +```bash +cd /Users/kris/code/boss +npx --yes tsx --test \ + tests/execution-foundation-contracts.test.ts \ + tests/execution-prompt-assembler.test.ts \ + tests/execution-memory-resolver.test.ts \ + tests/execution-permission-policy.test.ts \ + tests/execution-backend-selector.test.ts \ + tests/remote-runtime-adapter.test.ts \ + tests/master-agent-config-resolution.test.ts \ + tests/master-agent-openai-fallback.test.ts \ + tests/master-agent-message-queue.test.ts \ + tests/group-message-dispatch-plan.test.ts \ + tests/dispatch-plan-confirmation.test.ts \ + tests/dispatch-execution-result.test.ts + +node --test tests/local-agent-codex-task-runner.test.mjs +npm run lint +npm run build +``` + +Expected: + +```text +All tests pass +Next.js build succeeds +``` + +- [ ] **Step 2: 更新文档,说明执行底座抽象层已落地,但不改变生产主链** + +在文档中至少补下面这些事实: + +```md +- 当前 Boss 已新增 `src/lib/execution/` 执行底座抽象层 +- 当前生产主链仍然沿用 `local-agent -> codex exec resume` +- 当前已完成 `ExecutionBackend / PromptAssembler / PermissionPolicy / RemoteRuntimeAdapter / OrchestrationBackend` 的默认实现 +- 当前 `claw-code` 与 `oh-my-codex` 尚未正式接入生产链,只是 contract ready +``` + +- [ ] **Step 3: 再跑一次 lint/build 确认文档与代码最终状态一致** + +Run: + +```bash +cd /Users/kris/code/boss +npm run lint +npm run build +``` + +Expected: + +```text +No new errors +``` + +- [ ] **Step 4: 提交** + +```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: record execution foundation refactor" +``` + +## Self-Review + +### Spec coverage + +- 已覆盖执行底座抽象层总目标 +- 已覆盖 `ExecutionBackend / Selector / SessionRuntime / PermissionPolicy / ToolRegistry / PromptAssembler / MemoryResolver / RemoteRuntimeAdapter / OrchestrationBackend` +- 已覆盖现有主链不变的约束 +- 已覆盖后续接 `claw-code / oh-my-codex` 前的 contract-ready 目标 + +### Placeholder scan + +- 无 `TODO / TBD / implement later` +- 每个任务都给了明确文件、测试、命令和 commit 边界 + +### Type consistency + +- `ExecutionRequest / ExecutionQueuedResult / ExecutionImmediateResult / PermissionCheckResult` 在各任务中保持一致 +- `ExecutionBackend` 与 `OrchestrationBackend` 已分层,没有混用 + +## Execution Handoff + +Plan complete and saved to `/Users/kris/code/boss/docs/superpowers/plans/2026-04-02-boss-execution-foundation.md`. Two execution options: + +**1. Subagent-Driven (recommended)** - I dispatch a fresh subagent per task, review between tasks, fast iteration + +**2. Inline Execution** - Execute tasks in this session using executing-plans, batch execution with checkpoints + +**Which approach?**