Files
boss/docs/superpowers/plans/2026-04-02-boss-execution-foundation.md
2026-04-03 00:21:19 +08:00

890 lines
29 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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> | boolean;
execute(input: ExecutionRequest): Promise<ExecutionQueuedResult | ExecutionImmediateResult>;
describe(input: ExecutionRequest): Promise<ExecutionBackendDescription>;
}
```
```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;
```
为避免 `master-agent` 当前生产行为回归,允许在同一个 `/Users/kris/code/boss/src/lib/execution/memory-resolver.ts` 中额外提供一个 **runtime-safe helper**(例如 `resolveRuntimeRelevantMemories(...)`),专门给 `boss-master-agent.ts` 使用。约束如下:
- `resolveRelevantMemories(...)` 仍保持上面的最小 contract供基础测试与后续 contract 使用。
- runtime-safe helper 只负责把当前已存在的主 Agent 运行时保护内聚回执行模块,例如:
- `workflow_rule / user_preference` 优先;
- `master-agent` 非空请求但无 lexical 命中时,回退到前 6 个项目记忆。
- 不允许把这些运行时保护继续散落在 `boss-master-agent.ts` 中;如果需要保留生产行为,优先放进同一个 execution 模块。
```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;
}
```
如果在落地时发现当前生产审批流需要把“先生成推荐、再等待确认”的动作显式建模,允许在同一个 `tool-registry.ts` 中补一个 `group_dispatch_plan` 工具定义,并同步把 `PermissionPolicy` 和测试调整为**以真实运行时语义为准**。约束如下:
- `approval_required` 群聊不能再暴露与 `canDispatchDirectly=false` 矛盾的 `dispatch_execution` 直接权限;
- `toolPolicy` 必须能清楚表达“当前允许先生成推荐,但不允许直接跨线程执行”的状态;
- 任何新增工具定义都必须只服务于现有审批主链,不提前引入 Task 4+ 的执行后端能力。
`/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 }>;
}) {
const primaryBackend = resolveBackendByProvider(input.primary.provider);
if (input.primary.status === "ready") {
return primaryBackend;
}
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" };
}
const master = input.backups.find((item) => item.provider === "master_codex_node" && item.status === "ready");
if (master) {
return { backendId: "master-codex-node" };
}
return primaryBackend;
}
export const selectExecutionBackendForTesting = selectExecutionBackend;
```
补充说明:
- selector 的运行时语义以“`ready primary` 优先,否则按 `aliyun_qwen -> openai -> master_codex_node` 顺序回退,最后再回 primary 兜底”为准。
- 如果同一 provider 存在多个账号,只要其中任何一个是 `ready`,该 backend 就视为可选。
`/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?**