docs: add execution foundation implementation plan
This commit is contained in:
868
docs/superpowers/plans/2026-04-02-boss-execution-foundation.md
Normal file
868
docs/superpowers/plans/2026-04-02-boss-execution-foundation.md
Normal file
@@ -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> | 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;
|
||||
```
|
||||
|
||||
```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?**
|
||||
Reference in New Issue
Block a user