890 lines
29 KiB
Markdown
890 lines
29 KiB
Markdown
# 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?**
|