From 6c999fb9510f1e44a22f0f54cba9e1be568aa569 Mon Sep 17 00:00:00 2001 From: kris Date: Fri, 3 Apr 2026 01:41:29 +0800 Subject: [PATCH] chore: add claw smoke runtime sample --- .env.server.example | 20 ++++++ README.md | 1 + .../api_and_service_inventory_cn.md | 1 + .../current_runtime_and_deploy_status_cn.md | 1 + scripts/claw-runtime-smoke.mjs | 62 +++++++++++++++++++ tests/claw-runtime-smoke-script.test.ts | 61 ++++++++++++++++++ 6 files changed, 146 insertions(+) create mode 100644 .env.server.example create mode 100644 scripts/claw-runtime-smoke.mjs create mode 100644 tests/claw-runtime-smoke-script.test.ts diff --git a/.env.server.example b/.env.server.example new file mode 100644 index 0000000..35ee0d8 --- /dev/null +++ b/.env.server.example @@ -0,0 +1,20 @@ +BOSS_AUTH_VERIFICATION_MODE=fixed +BOSS_AUTH_FIXED_CODE=000000 +BOSS_STATE_FILE=/opt/boss/data/boss-state.json + +# 切到真实邮件验证码时,改成: +# BOSS_AUTH_VERIFICATION_MODE=email +# BOSS_AUTH_FIXED_CODE= + +BOSS_MAIL_DOMAIN=boss.hyzq.net +BOSS_MAIL_FROM_ADDRESS=verify@boss.hyzq.net +BOSS_MAIL_FROM_NAME=Boss Verify +BOSS_SENDMAIL_PATH=/usr/sbin/sendmail + +# 可选:启用 ClawBackendAdapter(默认关闭) +# BOSS_CLAW_ENABLED=true +# BOSS_CLAW_COMMAND=node +# BOSS_CLAW_ARGS=scripts/claw-runtime-smoke.mjs +# BOSS_CLAW_WORKDIR=/opt/boss +# BOSS_CLAW_TIMEOUT_MS=45000 +# BOSS_CLAW_DEFAULT_MODEL=gpt-5.4 diff --git a/README.md b/README.md index 71d103e..e8aa286 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,7 @@ - 当前 Boss 已新增 `src/lib/execution/` 执行底座抽象层;当前生产主链仍然沿用 `local-agent -> codex exec resume`,只是执行责任已开始通过 `ExecutionBackend / PromptAssembler / PermissionPolicy / RemoteRuntimeAdapter / OrchestrationBackend` 默认实现收束 - 当前 `claw-code` 已以最小 `ClawBackendAdapter` 形式接入执行底座,但默认关闭;只有在显式配置 `BOSS_CLAW_*` 并在 `master-agent` 当前对话里显式选择 `claw-runtime` 时才会参与执行候选 - 当前 `oh-my-codex` 仍未正式接入生产执行链;当前状态是 orchestration-ready,后续将通过独立 adapter 接入 +- 当前仓库已自带一个本地 smoke runtime:`scripts/claw-runtime-smoke.mjs`。在还没有真实 `claw-code` 可执行文件时,可以先用它验证 `ClawBackendAdapter -> backendOverride -> 异步回流` 整条链 - `GET http://127.0.0.1:4317/api/v1/skills` 正常,已返回本机扫描到的 Codex Skill - `POST http://127.0.0.1:4317/api/v1/heartbeat` 正常,且会顺带触发 `thread-context` 上报 - `launchd` 已加载:`~/Library/LaunchAgents/com.hyzq.boss.local-agent.plist` diff --git a/docs/architecture/api_and_service_inventory_cn.md b/docs/architecture/api_and_service_inventory_cn.md index 4f651cc..944feef 100644 --- a/docs/architecture/api_and_service_inventory_cn.md +++ b/docs/architecture/api_and_service_inventory_cn.md @@ -178,6 +178,7 @@ - 已在生产代码中被 `boss-master-agent.ts`、`local-agent/server.mjs` 和 `master-agent task complete route` 使用 - 当前仍服务 Boss 自身执行链 - 当前已最小接入 `ClawBackendAdapter`,但默认关闭,仅在显式配置和显式选择时参与执行 + - 当前仓库自带 `scripts/claw-runtime-smoke.mjs` 作为兼容 JSON 协议的 smoke runtime,可用于本地和服务器验证 `ClawBackendAdapter` - 当前尚未接入 `oh-my-codex` ### 3.2 认证相关 diff --git a/docs/architecture/current_runtime_and_deploy_status_cn.md b/docs/architecture/current_runtime_and_deploy_status_cn.md index 98d17f7..3a9066d 100644 --- a/docs/architecture/current_runtime_and_deploy_status_cn.md +++ b/docs/architecture/current_runtime_and_deploy_status_cn.md @@ -29,6 +29,7 @@ - 当前执行底座抽象层已落地在 `src/lib/execution/`,并已补齐 `ExecutionBackend / PromptAssembler / PermissionPolicy / RemoteRuntimeAdapter / OrchestrationBackend` 默认实现 - 当前生产主链仍然沿用 `local-agent -> codex exec resume -> /api/v1/master-agent/tasks/[taskId]/complete`,执行底座重构以“先抽象、不改行为”为准 - 当前 `claw-code` 已以最小 `ClawBackendAdapter` 形式接入执行底座,但默认关闭;只有显式配置 `BOSS_CLAW_*` 并在 `master-agent` 当前对话中选择 `claw-runtime` 时才会参与执行候选 +- 当前仓库已自带 `scripts/claw-runtime-smoke.mjs` 作为本地 smoke runtime;在没有真实 `claw-code` 可执行文件时,可先用 `BOSS_CLAW_COMMAND=node` 与 `BOSS_CLAW_ARGS=scripts/claw-runtime-smoke.mjs` 验证整条链 - 当前 `oh-my-codex` 还未正式接入生产链,只是已经具备 orchestration adapter-ready 的 contract 基础 本地已知运行方式: diff --git a/scripts/claw-runtime-smoke.mjs b/scripts/claw-runtime-smoke.mjs new file mode 100644 index 0000000..8a81451 --- /dev/null +++ b/scripts/claw-runtime-smoke.mjs @@ -0,0 +1,62 @@ +#!/usr/bin/env node + +function writeJson(payload) { + process.stdout.write(`${JSON.stringify(payload)}\n`); +} + +async function readStdin() { + const chunks = []; + for await (const chunk of process.stdin) { + chunks.push(typeof chunk === "string" ? chunk : chunk.toString("utf8")); + } + return chunks.join("").trim(); +} + +function normalizePayload(raw) { + try { + const parsed = JSON.parse(raw); + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) { + return { + ok: false, + error: "INVALID_CLAW_PAYLOAD: expected object", + }; + } + return { + ok: true, + payload: parsed, + }; + } catch { + return { + ok: false, + error: "INVALID_CLAW_PAYLOAD: invalid json", + }; + } +} + +const raw = await readStdin(); +const normalized = normalizePayload(raw); + +if (!normalized.ok) { + writeJson({ + status: "failed", + error: normalized.error, + }); + process.exit(0); +} + +const payload = normalized.payload; +const requestKind = typeof payload.requestKind === "string" ? payload.requestKind : "unknown"; +const model = typeof payload.model === "string" && payload.model.trim() ? payload.model.trim() : "default"; +const reasoningEffort = + typeof payload.reasoningEffort === "string" && payload.reasoningEffort.trim() + ? payload.reasoningEffort.trim() + : "default"; +const executionPrompt = + typeof payload.executionPrompt === "string" && payload.executionPrompt.trim() + ? payload.executionPrompt.trim() + : "链路正常"; + +writeJson({ + status: "completed", + output: `Claw smoke completed: ${executionPrompt} (kind=${requestKind}, model=${model}, reasoning=${reasoningEffort})`, +}); diff --git a/tests/claw-runtime-smoke-script.test.ts b/tests/claw-runtime-smoke-script.test.ts new file mode 100644 index 0000000..e2eda87 --- /dev/null +++ b/tests/claw-runtime-smoke-script.test.ts @@ -0,0 +1,61 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import path from "node:path"; +import { spawn } from "node:child_process"; + +function runSmoke(payload: unknown) { + return new Promise<{ + exitCode: number | null; + stdout: string; + stderr: string; + }>((resolve, reject) => { + const scriptPath = path.resolve("scripts/claw-runtime-smoke.mjs"); + const child = spawn(process.execPath, [scriptPath], { + cwd: "/Users/kris/code/boss", + stdio: ["pipe", "pipe", "pipe"], + }); + + let stdout = ""; + let stderr = ""; + child.stdout.setEncoding("utf8"); + child.stderr.setEncoding("utf8"); + child.stdout.on("data", (chunk) => { + stdout += chunk; + }); + child.stderr.on("data", (chunk) => { + stderr += chunk; + }); + child.on("error", reject); + child.on("close", (exitCode) => { + resolve({ exitCode, stdout, stderr }); + }); + + child.stdin.write(JSON.stringify(payload)); + child.stdin.end(); + }); +} + +test("claw runtime smoke script emits completed JSON for valid payload", async () => { + const result = await runSmoke({ + requestKind: "master_agent_reply", + executionPrompt: "请回复链路正常", + model: "gpt-5.4", + reasoningEffort: "medium", + }); + + assert.equal(result.exitCode, 0); + assert.equal(result.stderr, ""); + const parsed = JSON.parse(result.stdout); + assert.equal(parsed.status, "completed"); + assert.match(parsed.output, /链路正常/); + assert.match(parsed.output, /gpt-5\.4/); +}); + +test("claw runtime smoke script emits failed JSON for invalid payload", async () => { + const result = await runSmoke("not-an-object"); + + assert.equal(result.exitCode, 0); + const parsed = JSON.parse(result.stdout); + assert.equal(parsed.status, "failed"); + assert.match(parsed.error, /INVALID_CLAW_PAYLOAD/); +});