docs: add claw backend adapter plan
This commit is contained in:
760
docs/superpowers/plans/2026-04-03-claw-backend-adapter.md
Normal file
760
docs/superpowers/plans/2026-04-03-claw-backend-adapter.md
Normal file
@@ -0,0 +1,760 @@
|
||||
# Boss ClawBackendAdapter 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 新增一个默认关闭、可显式启用的 `ClawBackendAdapter`,让 `claw-code` 能作为单次执行后端候选接入 `master_agent_reply / thread_reply`。
|
||||
|
||||
**Architecture:** 先在 `src/lib/execution/backends/` 下增加 `claw-config / claw-runner / claw-backend` 三个 focused 模块,再把 `ExecutionBackendSelector` 扩成支持 `claw` 候选但默认不参与。整个过程坚持“先最小单次执行,不改群聊审批和设备导入行为”,并通过环境变量驱动启用状态与外部命令配置。
|
||||
|
||||
**Tech Stack:** Next.js App Router、TypeScript、Node.js child_process、现有 `src/lib/execution/*` 抽象层、`tsx --test`。
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 定义 Claw backend 配置与最小运行时可用性判断
|
||||
|
||||
**Files:**
|
||||
- Create: `/Users/kris/code/boss/src/lib/execution/backends/claw-config.ts`
|
||||
- Test: `/Users/kris/code/boss/tests/claw-backend-config.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,固定默认关闭与配置解析行为**
|
||||
|
||||
```ts
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import {
|
||||
getClawBackendConfigForTesting,
|
||||
isClawBackendConfiguredForTesting,
|
||||
} from "../src/lib/execution/backends/claw-config.ts";
|
||||
|
||||
test("Claw backend 在未配置时默认关闭", () => {
|
||||
const previous = { ...process.env };
|
||||
delete process.env.BOSS_CLAW_ENABLED;
|
||||
delete process.env.BOSS_CLAW_COMMAND;
|
||||
|
||||
const config = getClawBackendConfigForTesting();
|
||||
assert.equal(config.enabled, false);
|
||||
assert.equal(isClawBackendConfiguredForTesting(config), false);
|
||||
|
||||
process.env = previous;
|
||||
});
|
||||
|
||||
test("Claw backend 在配置完整时返回 command、args 和 timeout", () => {
|
||||
const previous = { ...process.env };
|
||||
process.env.BOSS_CLAW_ENABLED = "true";
|
||||
process.env.BOSS_CLAW_COMMAND = "claw";
|
||||
process.env.BOSS_CLAW_ARGS = "run --json";
|
||||
process.env.BOSS_CLAW_WORKDIR = "/tmp/claw";
|
||||
process.env.BOSS_CLAW_TIMEOUT_MS = "45000";
|
||||
|
||||
const config = getClawBackendConfigForTesting();
|
||||
assert.equal(config.enabled, true);
|
||||
assert.equal(config.command, "claw");
|
||||
assert.deepEqual(config.args, ["run", "--json"]);
|
||||
assert.equal(config.cwd, "/tmp/claw");
|
||||
assert.equal(config.timeoutMs, 45000);
|
||||
assert.equal(isClawBackendConfiguredForTesting(config), true);
|
||||
|
||||
process.env = previous;
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-backend-config.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
ERR_MODULE_NOT_FOUND
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 写最小配置解析实现**
|
||||
|
||||
```ts
|
||||
// /Users/kris/code/boss/src/lib/execution/backends/claw-config.ts
|
||||
export interface ClawBackendConfig {
|
||||
enabled: boolean;
|
||||
command?: string;
|
||||
args: string[];
|
||||
cwd?: string;
|
||||
timeoutMs: number;
|
||||
defaultModel?: string;
|
||||
}
|
||||
|
||||
function parseBoolean(value: string | undefined) {
|
||||
return value?.trim().toLowerCase() === "true";
|
||||
}
|
||||
|
||||
function parseArgs(value: string | undefined) {
|
||||
return String(value || "")
|
||||
.trim()
|
||||
.split(/\s+/)
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
export function getClawBackendConfig(): ClawBackendConfig {
|
||||
return {
|
||||
enabled: parseBoolean(process.env.BOSS_CLAW_ENABLED),
|
||||
command: process.env.BOSS_CLAW_COMMAND?.trim() || undefined,
|
||||
args: parseArgs(process.env.BOSS_CLAW_ARGS),
|
||||
cwd: process.env.BOSS_CLAW_WORKDIR?.trim() || undefined,
|
||||
timeoutMs: Number.parseInt(process.env.BOSS_CLAW_TIMEOUT_MS || "45000", 10) || 45000,
|
||||
defaultModel: process.env.BOSS_CLAW_DEFAULT_MODEL?.trim() || undefined,
|
||||
};
|
||||
}
|
||||
|
||||
export function isClawBackendConfigured(config: ClawBackendConfig) {
|
||||
return config.enabled && Boolean(config.command);
|
||||
}
|
||||
|
||||
export const getClawBackendConfigForTesting = getClawBackendConfig;
|
||||
export const isClawBackendConfiguredForTesting = isClawBackendConfigured;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-backend-config.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
# pass 2
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
git add tests/claw-backend-config.test.ts src/lib/execution/backends/claw-config.ts
|
||||
git commit -m "feat: add claw backend config"
|
||||
```
|
||||
|
||||
### Task 2: 抽出 Claw runner,统一外部命令协议
|
||||
|
||||
**Files:**
|
||||
- Create: `/Users/kris/code/boss/src/lib/execution/backends/claw-runner.ts`
|
||||
- Test: `/Users/kris/code/boss/tests/claw-runner.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,固定 JSON 协议映射**
|
||||
|
||||
```ts
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createClawProcessResultForTesting } from "../src/lib/execution/backends/claw-runner.ts";
|
||||
|
||||
test("Claw runner 会把成功 JSON 映射成 completed", () => {
|
||||
const result = createClawProcessResultForTesting({
|
||||
exitCode: 0,
|
||||
stdout: JSON.stringify({ status: "completed", output: "链路正常" }),
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
assert.equal(result.status, "completed");
|
||||
assert.equal(result.output, "链路正常");
|
||||
});
|
||||
|
||||
test("Claw runner 会把非法 JSON 映射成 failed", () => {
|
||||
const result = createClawProcessResultForTesting({
|
||||
exitCode: 0,
|
||||
stdout: "not-json",
|
||||
stderr: "",
|
||||
});
|
||||
|
||||
assert.equal(result.status, "failed");
|
||||
assert.match(result.error, /INVALID_CLAW_RESPONSE/);
|
||||
});
|
||||
|
||||
test("Claw runner 会把非零退出码映射成 failed", () => {
|
||||
const result = createClawProcessResultForTesting({
|
||||
exitCode: 2,
|
||||
stdout: "",
|
||||
stderr: "claw crashed",
|
||||
});
|
||||
|
||||
assert.equal(result.status, "failed");
|
||||
assert.match(result.error, /claw crashed/);
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-runner.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
ERR_MODULE_NOT_FOUND
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 写最小 runner 实现**
|
||||
|
||||
```ts
|
||||
// /Users/kris/code/boss/src/lib/execution/backends/claw-runner.ts
|
||||
import { spawn } from "node:child_process";
|
||||
import type { ExecutionImmediateResult } from "@/lib/execution/types";
|
||||
import type { ClawBackendConfig } from "@/lib/execution/backends/claw-config";
|
||||
|
||||
function normalizeClawProcessResult(input: {
|
||||
exitCode: number;
|
||||
stdout: string;
|
||||
stderr: string;
|
||||
}): ExecutionImmediateResult {
|
||||
if (input.exitCode !== 0) {
|
||||
return {
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: input.stderr.trim() || `CLAW_EXIT_${input.exitCode}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(input.stdout);
|
||||
if (parsed?.status === "completed" && typeof parsed.output === "string") {
|
||||
return {
|
||||
status: "completed",
|
||||
backendId: "claw-runtime",
|
||||
output: parsed.output,
|
||||
};
|
||||
}
|
||||
if (parsed?.status === "failed" && typeof parsed.error === "string") {
|
||||
return {
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: parsed.error,
|
||||
};
|
||||
}
|
||||
return {
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: "INVALID_CLAW_RESPONSE",
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: "INVALID_CLAW_RESPONSE",
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function runClawCommand(input: {
|
||||
config: ClawBackendConfig;
|
||||
payload: unknown;
|
||||
}): Promise<ExecutionImmediateResult> {
|
||||
if (!input.config.command) {
|
||||
return {
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: "CLAW_COMMAND_NOT_CONFIGURED",
|
||||
};
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const child = spawn(input.config.command, input.config.args, {
|
||||
cwd: input.config.cwd,
|
||||
env: process.env,
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
});
|
||||
|
||||
let stdout = "";
|
||||
let stderr = "";
|
||||
const timer = setTimeout(() => {
|
||||
child.kill("SIGKILL");
|
||||
resolve({
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: "CLAW_TIMEOUT",
|
||||
});
|
||||
}, input.config.timeoutMs);
|
||||
|
||||
child.stdout.on("data", (chunk) => {
|
||||
stdout += String(chunk);
|
||||
});
|
||||
child.stderr.on("data", (chunk) => {
|
||||
stderr += String(chunk);
|
||||
});
|
||||
child.on("error", (error) => {
|
||||
clearTimeout(timer);
|
||||
resolve({
|
||||
status: "failed",
|
||||
backendId: "claw-runtime",
|
||||
error: error.message,
|
||||
});
|
||||
});
|
||||
child.on("close", (code) => {
|
||||
clearTimeout(timer);
|
||||
resolve(normalizeClawProcessResult({ exitCode: code ?? 1, stdout, stderr }));
|
||||
});
|
||||
|
||||
child.stdin.end(JSON.stringify(input.payload));
|
||||
});
|
||||
}
|
||||
|
||||
export const createClawProcessResultForTesting = normalizeClawProcessResult;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-runner.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
# pass 3
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
git add tests/claw-runner.test.ts src/lib/execution/backends/claw-runner.ts
|
||||
git commit -m "feat: add claw runtime runner"
|
||||
```
|
||||
|
||||
### Task 3: 新增 `ClawBackendAdapter`,只承接单次执行
|
||||
|
||||
**Files:**
|
||||
- Create: `/Users/kris/code/boss/src/lib/execution/backends/claw-backend.ts`
|
||||
- Test: `/Users/kris/code/boss/tests/claw-backend.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,固定可处理范围和描述信息**
|
||||
|
||||
```ts
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { createExecutionRequest } from "../src/lib/execution/types.ts";
|
||||
import { createClawBackendForTesting } from "../src/lib/execution/backends/claw-backend.ts";
|
||||
|
||||
test("ClawBackendAdapter 未启用时不可处理任何请求", async () => {
|
||||
const backend = createClawBackendForTesting({ enabled: false, args: [], timeoutMs: 1000 });
|
||||
const canHandle = await backend.canHandle(
|
||||
createExecutionRequest({
|
||||
kind: "master_agent_reply",
|
||||
projectId: "master-agent",
|
||||
requestMessageId: "msg-1",
|
||||
body: "继续",
|
||||
}),
|
||||
);
|
||||
|
||||
assert.equal(canHandle, false);
|
||||
});
|
||||
|
||||
test("ClawBackendAdapter 仅接 master_agent_reply 和 thread_reply", async () => {
|
||||
const backend = createClawBackendForTesting({ enabled: true, command: "claw", args: [], timeoutMs: 1000 });
|
||||
assert.equal(
|
||||
await backend.canHandle(createExecutionRequest({ kind: "master_agent_reply", projectId: "master-agent", requestMessageId: "1", body: "a" })),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
await backend.canHandle(createExecutionRequest({ kind: "thread_reply", projectId: "p1", requestMessageId: "2", body: "b" })),
|
||||
true,
|
||||
);
|
||||
assert.equal(
|
||||
await backend.canHandle(createExecutionRequest({ kind: "dispatch_execution", projectId: "g1", requestMessageId: "3", body: "c" })),
|
||||
false,
|
||||
);
|
||||
});
|
||||
|
||||
test("ClawBackendAdapter describe 返回 claw backend 元信息", async () => {
|
||||
const backend = createClawBackendForTesting({ enabled: true, command: "claw", args: [], timeoutMs: 1000 });
|
||||
const description = await backend.describe(
|
||||
createExecutionRequest({ kind: "master_agent_reply", projectId: "master-agent", requestMessageId: "1", body: "a" }),
|
||||
);
|
||||
|
||||
assert.equal(description.backendId, "claw-runtime");
|
||||
assert.equal(description.mode, "remote");
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-backend.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
ERR_MODULE_NOT_FOUND
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 写最小 backend 实现**
|
||||
|
||||
```ts
|
||||
// /Users/kris/code/boss/src/lib/execution/backends/claw-backend.ts
|
||||
import type { ExecutionBackend } from "@/lib/execution/execution-backend";
|
||||
import type { ExecutionRequest } from "@/lib/execution/types";
|
||||
import { getClawBackendConfig, isClawBackendConfigured } from "@/lib/execution/backends/claw-config";
|
||||
import { runClawCommand } from "@/lib/execution/backends/claw-runner";
|
||||
|
||||
function isSupportedKind(kind: ExecutionRequest["kind"]) {
|
||||
return kind === "master_agent_reply" || kind === "thread_reply";
|
||||
}
|
||||
|
||||
export function createClawBackend(config = getClawBackendConfig()): ExecutionBackend {
|
||||
return {
|
||||
backendId: "claw-runtime",
|
||||
async canHandle(input) {
|
||||
return isClawBackendConfigured(config) && isSupportedKind(input.kind);
|
||||
},
|
||||
async execute(input) {
|
||||
return runClawCommand({
|
||||
config,
|
||||
payload: {
|
||||
kind: input.kind,
|
||||
projectId: input.projectId,
|
||||
requestMessageId: input.requestMessageId,
|
||||
body: input.body,
|
||||
targetProjectId: input.targetProjectId,
|
||||
targetThreadId: input.targetThreadId,
|
||||
modelOverride: input.modelOverride ?? config.defaultModel,
|
||||
reasoningEffortOverride: input.reasoningEffortOverride,
|
||||
},
|
||||
});
|
||||
},
|
||||
async describe() {
|
||||
return {
|
||||
backendId: "claw-runtime",
|
||||
label: "Claw Runtime",
|
||||
mode: "remote",
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const CLAW_BACKEND = createClawBackend();
|
||||
export const createClawBackendForTesting = createClawBackend;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: 再跑测试确认通过**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-backend.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
# pass 3
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
git add tests/claw-backend.test.ts src/lib/execution/backends/claw-backend.ts
|
||||
git commit -m "feat: add claw backend adapter"
|
||||
```
|
||||
|
||||
### Task 4: 扩展 selector,让 `claw` 仅在显式启用时参与候选
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/kris/code/boss/src/lib/execution/backend-selector.ts`
|
||||
- Modify: `/Users/kris/code/boss/src/lib/execution/backends/openai-backend.ts`
|
||||
- Modify: `/Users/kris/code/boss/src/lib/execution/backends/master-codex-node-backend.ts`
|
||||
- Modify: `/Users/kris/code/boss/src/lib/execution/backends/aliyun-qwen-backend.ts`
|
||||
- Create: `/Users/kris/code/boss/tests/claw-backend-selector.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,固定 selector 行为不被 claw 默认影响**
|
||||
|
||||
```ts
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { listExecutionBackendChoices } from "../src/lib/execution/backend-selector.ts";
|
||||
|
||||
test("claw 未启用时 selector 行为保持不变", () => {
|
||||
delete process.env.BOSS_CLAW_ENABLED;
|
||||
delete process.env.BOSS_CLAW_COMMAND;
|
||||
|
||||
const choices = listExecutionBackendChoices({
|
||||
primary: { provider: "master_codex_node", status: "ready" },
|
||||
backups: [{ provider: "openai_api", status: "ready" }],
|
||||
});
|
||||
|
||||
assert.equal(choices[0]?.backendId, "master-codex-node");
|
||||
assert.equal(choices.some((item) => item.backendId === "claw-runtime"), false);
|
||||
});
|
||||
|
||||
test("claw 启用且显式选中时会进入候选列表顶部", () => {
|
||||
process.env.BOSS_CLAW_ENABLED = "true";
|
||||
process.env.BOSS_CLAW_COMMAND = "claw";
|
||||
|
||||
const choices = listExecutionBackendChoices({
|
||||
primary: { provider: "master_codex_node", status: "ready" },
|
||||
backups: [],
|
||||
requestedBackendId: "claw-runtime",
|
||||
requestKind: "master_agent_reply",
|
||||
});
|
||||
|
||||
assert.equal(choices[0]?.backendId, "claw-runtime");
|
||||
});
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试确认当前失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/claw-backend-selector.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
FAIL
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 扩展 selector 输入与 claw 候选插入规则**
|
||||
|
||||
```ts
|
||||
// /Users/kris/code/boss/src/lib/execution/backend-selector.ts
|
||||
// 在现有 ExecutionBackendSelectionInput 上补:
|
||||
requestedBackendId?: string;
|
||||
requestKind?: "master_agent_reply" | "thread_reply" | "dispatch_execution" | "attachment_analysis";
|
||||
|
||||
// 新增 Claw backend descriptor,并在 listExecutionBackendChoices 中:
|
||||
// 1. 默认不插入 claw
|
||||
// 2. 只有当 requestedBackendId === "claw-runtime" 且 requestKind 可支持 且 claw 配置完整时,才 push 到最前面
|
||||
```
|
||||
|
||||
要求:
|
||||
- 不改变当前不带 `requestedBackendId` 的所有既有行为
|
||||
- 不让 `dispatch_execution` 进入 claw 候选
|
||||
- 不让未启用的 claw 影响任何现有选择顺序
|
||||
|
||||
- [ ] **Step 4: 再跑 selector 测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/execution-backend-selector.test.ts tests/claw-backend-selector.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
# pass
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
git add tests/claw-backend-selector.test.ts tests/execution-backend-selector.test.ts src/lib/execution/backend-selector.ts src/lib/execution/backends/openai-backend.ts src/lib/execution/backends/master-codex-node-backend.ts src/lib/execution/backends/aliyun-qwen-backend.ts
|
||||
git commit -m "refactor: support optional claw backend selection"
|
||||
```
|
||||
|
||||
### Task 5: 把 `boss-master-agent` 接到可选 claw backend,但默认保持原主链
|
||||
|
||||
**Files:**
|
||||
- Modify: `/Users/kris/code/boss/src/lib/boss-master-agent.ts`
|
||||
- Test: `/Users/kris/code/boss/tests/master-agent-claw-backend.test.ts`
|
||||
|
||||
- [ ] **Step 1: 写失败测试,固定“默认不走 claw,显式选中才走”**
|
||||
|
||||
```ts
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readState, saveAiAccount } from "../src/lib/boss-data.ts";
|
||||
import { replyToMasterAgentUserMessage } from "../src/lib/boss-master-agent.ts";
|
||||
|
||||
test("master-agent 默认不因为 claw 启用而改变现有后端选择", async () => {
|
||||
process.env.BOSS_CLAW_ENABLED = "true";
|
||||
process.env.BOSS_CLAW_COMMAND = "claw";
|
||||
|
||||
const result = await replyToMasterAgentUserMessage({
|
||||
requestMessageId: "msg-1",
|
||||
requestText: "继续",
|
||||
requestedBy: "Boss 超级管理员",
|
||||
requestedByAccount: "17600003315",
|
||||
});
|
||||
|
||||
assert.notEqual(result.accountId, "claw-runtime");
|
||||
});
|
||||
```
|
||||
|
||||
再补一条可测试 helper 场景:
|
||||
|
||||
```ts
|
||||
// 新增一个 resolveMasterAgentBackendForTesting(...) helper
|
||||
// 显式传入 requestedBackendId="claw-runtime" 时,返回 claw backend
|
||||
```
|
||||
|
||||
- [ ] **Step 2: 运行测试确认失败**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/master-agent-claw-backend.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
FAIL
|
||||
```
|
||||
|
||||
- [ ] **Step 3: 在 boss-master-agent 中引入 requestedBackendId 但不改变默认行为**
|
||||
|
||||
实现要求:
|
||||
- 当前默认调用路径不传 `requestedBackendId`
|
||||
- 只在后续显式会话配置存在时,才把 `requestedBackendId="claw-runtime"` 传给 selector
|
||||
- 本轮不新增前台 UI,只把内部能力接好
|
||||
- 如果 selector 选中了 `claw-runtime`,则调用 `CLAW_BACKEND.execute(...)`
|
||||
- 如果 claw 返回 `failed`,继续沿用现有后端回退逻辑
|
||||
|
||||
- [ ] **Step 4: 再跑 master-agent 相关测试**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
npx --yes tsx --test tests/master-agent-claw-backend.test.ts tests/master-agent-openai-fallback.test.ts tests/master-agent-message-queue.test.ts tests/master-agent-config-resolution.test.ts
|
||||
```
|
||||
|
||||
Expected:
|
||||
|
||||
```text
|
||||
# pass
|
||||
```
|
||||
|
||||
- [ ] **Step 5: 提交**
|
||||
|
||||
```bash
|
||||
cd /Users/kris/code/boss
|
||||
git add tests/master-agent-claw-backend.test.ts src/lib/boss-master-agent.ts
|
||||
git commit -m "feat: wire optional claw backend into master agent"
|
||||
```
|
||||
|
||||
### 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/claw-backend-config.test.ts \
|
||||
tests/claw-runner.test.ts \
|
||||
tests/claw-backend.test.ts \
|
||||
tests/claw-backend-selector.test.ts \
|
||||
tests/master-agent-claw-backend.test.ts \
|
||||
tests/execution-backend-selector.test.ts \
|
||||
tests/master-agent-openai-fallback.test.ts \
|
||||
tests/master-agent-message-queue.test.ts \
|
||||
tests/master-agent-config-resolution.test.ts \
|
||||
tests/remote-runtime-adapter.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: 更新文档,明确 claw 已接入为可选 backend,但默认不启用**
|
||||
|
||||
至少补充:
|
||||
|
||||
```md
|
||||
- 当前 Boss 已新增 `ClawBackendAdapter`
|
||||
- 当前 `claw-code` 仅作为可选单次执行 backend
|
||||
- 当前默认主链仍不是 claw
|
||||
- 当前 claw 只覆盖 `master_agent_reply / thread_reply`
|
||||
- 当前未接群聊审批、设备导入、多租户账本
|
||||
```
|
||||
|
||||
- [ ] **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 optional claw backend support"
|
||||
```
|
||||
|
||||
## Self-Review
|
||||
|
||||
### Spec coverage
|
||||
|
||||
- 已覆盖 `ClawBackendAdapter` 默认关闭
|
||||
- 已覆盖最小接入范围只限单次执行
|
||||
- 已覆盖 CLI/JSON 协议
|
||||
- 已覆盖 selector 显式选择与失败回退
|
||||
- 已覆盖文档同步
|
||||
|
||||
### Placeholder scan
|
||||
|
||||
- 无 TBD / TODO / implement later
|
||||
- 每个任务都有明确文件、测试、命令和提交边界
|
||||
|
||||
### Type consistency
|
||||
|
||||
- `ClawBackendConfig / ExecutionRequest / ExecutionImmediateResult / requestedBackendId` 命名保持一致
|
||||
- `claw-runtime` backendId 在任务间保持一致
|
||||
|
||||
## Execution Handoff
|
||||
|
||||
Plan complete and saved to `/Users/kris/code/boss/docs/superpowers/plans/2026-04-03-claw-backend-adapter.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