feat: 薄殼原則落地 + seed 下沉 API + MCP 進主庫 + 部署一致性
壓測四橫向問題修正(docs 壓測報告):
① 薄殼原則成鐵律:能力長在 API,CLI/MCP/lib 只暴露
- seed 下沉成 API 行為:cypher-executor POST /init/seed(一次灌 API+auth recipe),
種子資料移到 server src/lib/api-recipe-seeds.ts,CLI 改薄殼一次呼叫
- 解除 deployFullyOk 連坐 + init 補 seed auth recipe + update 補 seed/全 KV
- registry SUBMISSIONS_KV 補進 REQUIRED_KV_NAMESPACES(修 20/21)
② MCP 統一帳號來源(單一 remote MCP + .env 切 MCP URL)
- MCP 從 sibling repo 搬進 arcrun/mcp/(remote Worker,route 改 mcp.arcrun.dev)
- config 加 mcp_url 三層解析 + getMcpUrl + DEFAULT_MCP_URL
- 新增 acr mcp-setup:依 config 寫專案 .mcp.json(接案切資料夾自動切 MCP)
- acr --version 改動態讀 package.json(根治漂移)
③ Deploy 一致性
- tests/release.feature + scripts/check-release.sh
- local-deploy.sh:CLI npm publish + auto patch bump + CHANGELOG
- local-deploy.sh bash 3.2 相容修正(mapfile / 空陣列 set -u)
- builtins/pnpm-lock.yaml
④ README self-hosted 同步現況(移除 R2 殘留、加 flag/env、多帳號)
CLI bump → 1.3.0
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+36
-35
@@ -16,8 +16,8 @@ import {
|
||||
downloadAndDeploy,
|
||||
type DeployContext,
|
||||
} from '../lib/deploy.js';
|
||||
import { API_RECIPE_SEEDS } from '../lib/api-recipe-seeds.js';
|
||||
import { cmdInstallHarness } from './install-harness.js';
|
||||
import { cmdMcpSetup } from './mcp-setup.js';
|
||||
|
||||
const ARCRUN_REGISTER_URL = 'https://cypher.arcrun.dev/register';
|
||||
|
||||
@@ -57,6 +57,14 @@ export async function cmdInit(options: InitOptions): Promise<void> {
|
||||
} catch (e) {
|
||||
console.log(chalk.gray(` (harness 安裝略過:${e instanceof Error ? e.message : e};可稍後跑 acr install-harness)`));
|
||||
}
|
||||
|
||||
// 順便寫專案 .mcp.json,讓 Claude Code 連對的 MCP(依 config 的 mcp_url,SDD mcp-account-source.md)。
|
||||
// 失敗不影響 init(可事後 acr mcp-setup 補)。
|
||||
try {
|
||||
cmdMcpSetup();
|
||||
} catch (e) {
|
||||
console.log(chalk.gray(` (.mcp.json 略過:${e instanceof Error ? e.message : e};可稍後跑 acr mcp-setup)`));
|
||||
}
|
||||
}
|
||||
|
||||
async function initLocal(): Promise<void> {
|
||||
@@ -129,7 +137,7 @@ async function initStandard(rl: ReturnType<typeof createInterface>): Promise<voi
|
||||
|
||||
/**
|
||||
* Self-hosted installer:用戶只提供 CF Account ID + API Token,其餘自動。
|
||||
* 驗 token → 建 7 KV(冪等)→ 查 subdomain → 下載 release 部署 Worker
|
||||
* 驗 token → 建 KV(冪等,數量見 REQUIRED_KV_NAMESPACES)→ 查 subdomain → 下載 release 部署 Worker
|
||||
* → seed auth+api recipe → 寫 config → 印手動 secret 提示。
|
||||
* SDD:.agents/specs/arcrun/sdk-and-website/self-hosted-init.md
|
||||
*/
|
||||
@@ -208,6 +216,8 @@ async function initSelfHosted(
|
||||
const deploy = await downloadAndDeploy(deployCtx);
|
||||
const cypherUrl = deploy.cypherExecutorUrl
|
||||
?? (workerSubdomain ? `https://arcrun-cypher-executor.${workerSubdomain}.workers.dev` : '');
|
||||
// 誠實回報部署結果;但**不**用「全部成功」字串 gate 後續 seed(壓測 §4.1:
|
||||
// registry 一個無關 worker 失敗就連坐讓 seed 永遠被跳過)。seed 只看 cypher-executor 是否可達。
|
||||
const deployFullyOk = /全部成功/.test(deploy.message);
|
||||
console.log(deployFullyOk ? chalk.green(` ✓ ${deploy.message}`) : chalk.yellow(` ⚠ ${deploy.message}`));
|
||||
|
||||
@@ -224,15 +234,14 @@ async function initSelfHosted(
|
||||
saveConfig(config);
|
||||
createCredentialsYamlIfMissing();
|
||||
|
||||
// 6. seed API recipe(部署成功 + 有 cypher URL 才打;否則提示稍後 acr update 後再 seed)
|
||||
if (deployFullyOk && cypherUrl) {
|
||||
await seedApiRecipes(cypherUrl);
|
||||
} else if (cypherUrl) {
|
||||
console.log(chalk.gray(` → recipe seed 待部署穩定後再執行(${API_RECIPE_SEEDS.length} 個;acr update 會重試)`));
|
||||
// 6. seed recipe(薄殼:呼叫 API 的 /init/seed 一次,由 API 灌 API recipe + auth recipe)。
|
||||
// 只要 cypher-executor 可達就 seed——不被無關 worker(registry)的失敗連坐(壓測 §4.1)。
|
||||
if (cypherUrl) {
|
||||
await callSeedEndpoint(cypherUrl);
|
||||
}
|
||||
|
||||
// 結果回報(誠實:部分失敗時明說,不假綠 — mindset §7)
|
||||
console.log(chalk.green('\n ✓ Cloudflare 資源就緒(7 KV,免費額度即可,無需綁卡)'));
|
||||
console.log(chalk.green(`\n ✓ Cloudflare 資源就緒(${REQUIRED_KV_NAMESPACES.length} KV,免費額度即可,無需綁卡)`));
|
||||
console.log(chalk.green(' ✓ 設定寫入 ~/.arcrun/config.yaml'));
|
||||
console.log(chalk.green(' ✓ 建立 credentials.yaml'));
|
||||
|
||||
@@ -245,35 +254,27 @@ async function initSelfHosted(
|
||||
console.log(chalk.gray(' 生成:node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"\n'));
|
||||
}
|
||||
|
||||
/** seed API recipe 到目標 cypher-executor(部署完成後)。*/
|
||||
async function seedApiRecipes(cypherUrl: string): Promise<void> {
|
||||
process.stdout.write(chalk.gray(` → seed ${API_RECIPE_SEEDS.length} 個 API recipe...`));
|
||||
let ok = 0;
|
||||
for (const r of API_RECIPE_SEEDS) {
|
||||
try {
|
||||
const res = await fetch(`${cypherUrl}/recipes`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
canonical_id: r.canonical_id,
|
||||
display_name: r.display_name,
|
||||
description: r.description,
|
||||
endpoint: r.endpoint,
|
||||
method: r.method,
|
||||
auth_service: r.auth_service,
|
||||
exposure_consent: {
|
||||
confirmed_by_human: true,
|
||||
understood: `platform seed recipe: ${r.canonical_id} → ${r.endpoint}`,
|
||||
confirmed_at: new Date().toISOString(),
|
||||
},
|
||||
}),
|
||||
});
|
||||
if (res.ok) ok++;
|
||||
} catch {
|
||||
// 單筆失敗不中斷整個 init;最終回報數量
|
||||
/**
|
||||
* 薄殼:呼叫 API 的 /init/seed 一次(rule 07)。
|
||||
* seed 的編排 + 種子資料全在 cypher-executor(routes/init-seed.ts),CLI 不自己迴圈 POST。
|
||||
* 之前 seedApiRecipes 在 CLI 迴圈 POST + deployFullyOk gate 是壓測 §4.1 的反例,已移除。
|
||||
*/
|
||||
async function callSeedEndpoint(cypherUrl: string): Promise<void> {
|
||||
process.stdout.write(chalk.gray(' → seed recipe(API recipe + auth recipe,由 API 灌入)...'));
|
||||
try {
|
||||
const res = await fetch(`${cypherUrl}/init/seed`, { method: 'POST' });
|
||||
const body = await res.json().catch(() => null) as
|
||||
| { success?: boolean; message?: string }
|
||||
| null;
|
||||
if (res.ok && body?.success) {
|
||||
console.log(chalk.green(` ✓ ${body.message ?? ''}`));
|
||||
} else {
|
||||
// 誠實:不假綠。seed 沒全成就明說,提示 acr update 可重跑(冪等)。
|
||||
console.log(chalk.yellow(` ⚠ ${body?.message ?? `HTTP ${res.status}`}(可 acr update 重跑,seed 冪等)`));
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(chalk.yellow(` ⚠ seed 端點呼叫失敗(${e instanceof Error ? e.message : e});cypher 穩定後 acr update 重跑`));
|
||||
}
|
||||
console.log(ok === API_RECIPE_SEEDS.length ? chalk.green(' ✓') : chalk.yellow(` ${ok}/${API_RECIPE_SEEDS.length}`));
|
||||
}
|
||||
|
||||
function createHelloYamlIfMissing(): void {
|
||||
|
||||
Reference in New Issue
Block a user