chore(v2): 从兴邦预审v2白名单抽取独立可运行基座

按 docs/v2/05-implementation-plan.md §1 候选迁移表与 claude_use/T00 白名单,
从 55e2cdb 抽取 224 个文件(2.48 MB)到独立目录 F:/mini-OpenClaw-v2。

- 排除:凭据(.env/server-deploy.conf/旧DELIVERY.md)、运行数据(xingbang_data 等
  4 个 SQLite)、依赖(venv/node_modules 符号链接)、归档(_backup*/test-evidence/
  ori_file)、Office 临时文件
- 目标目录独立 git 仓库,初始恢复点;Gitea 远程与分支待 OQ-06 Q2 定案
- 未部署、未启动服务、未调用模型

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
2026-09-29 12:58:26 +08:00
co-authored by Claude Code
commit d56c699ce6
224 changed files with 33392 additions and 0 deletions
+49
View File
@@ -0,0 +1,49 @@
import {NextRequest, NextResponse} from "next/server";
export const dynamic = "force-dynamic";
export const runtime = "nodejs";
const allowed = new Set(["chat", "sessions", "files", "skills", "tokens", "config", "xingbang"]);
async function proxy(request:NextRequest, {params}:{params:{path:string[]}}) {
if (!allowed.has(params.path[0]) || params.path.some(p=>!p || p==="." || p===".." || /[\\/]/.test(p)))
return NextResponse.json({detail:"接口不存在"},{status:404});
const base = process.env.CORE_BACKEND_URL || process.env.RISK_AGENT_BACKEND_URL || "http://127.0.0.1:8002";
try {
// Production nextUrl can contain the bind address (0.0.0.0), not the client Host.
// Never use Host as authentication: all production core/tool access requires admin.
const authorization = request.headers.get("authorization") || "";
const host = request.headers.get("host") || "";
const requiresAuth = process.env.NODE_ENV === "production" || process.env.XINGBANG_REQUIRE_AUTH === "1" || !!process.env.XINGBANG_AUTH_USERS || !!authorization;
if (requiresAuth) {
if (!authorization.startsWith("Bearer ") || !authorization.slice(7).trim())
return NextResponse.json({detail:"请先在风险研判页面连接管理员访问身份(401)"},{status:401});
const identity = await fetch(`${base}/api/xingbang/risk-agent/access/context`, {
headers:{Authorization:authorization,"X-Xingbang-Client-Host":host},
cache:"no-store",redirect:"error",signal:request.signal,
});
if (!identity.ok) return NextResponse.json({detail:"访问身份校验失败"},{status:identity.status === 401 || identity.status === 403 ? identity.status : 503});
const user = await identity.json();
if (user.role !== "admin" || user.preview || !user.id)
return NextResponse.json({detail:"通用聊天与文件工具仅限已认证管理员"},{status:403});
} else {
let hostname = "";
try { hostname = new URL(`http://${host}`).hostname; } catch { /* reject malformed Host */ }
if (!["localhost","127.0.0.1","::1","[::1]"].includes(hostname))
return NextResponse.json({detail:"未配置身份认证,仅允许本机开发访问"},{status:403});
const origin = request.headers.get("origin");
if ((origin && new URL(origin).host !== host) || request.headers.get("sec-fetch-site") === "cross-site")
return NextResponse.json({detail:"不允许跨站访问开发工具"},{status:403});
}
const upstream = await fetch(`${base}/api/${params.path.map(encodeURIComponent).join("/")}${request.nextUrl.search}`, {
method:request.method,headers:{"Content-Type":request.headers.get("content-type")||"application/json",Authorization:authorization},
body:["GET","HEAD"].includes(request.method)?undefined:await request.arrayBuffer(),
cache:"no-store",redirect:"error",signal:request.signal,
});
const headers = new Headers({"Content-Type":upstream.headers.get("content-type")||"application/json", "Cache-Control":"no-cache, no-transform", "X-Accel-Buffering":"no"});
// Never arrayBuffer()/text() an SSE response: pass through chunks immediately.
return new NextResponse(upstream.body,{status:upstream.status,headers});
} catch {
return NextResponse.json({detail:"当前聊天后端未连接,请检查本地服务"},{status:502});
}
}
export {proxy as GET, proxy as POST, proxy as PUT, proxy as DELETE};
+18
View File
@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from "next/server";
async function proxy(request: NextRequest, {params}: {params:{path:string[]}}) {
const base=process.env.RISK_AGENT_BACKEND_URL || "http://127.0.0.1:8002";
try {
const response=await fetch(`${base}/api/xingbang/risk-agent/${params.path.map(encodeURIComponent).join("/")}${request.nextUrl.search}`,{
method:request.method,headers:{"Content-Type":request.headers.get("content-type")||"application/json", "Authorization":request.headers.get("authorization")||"", "X-Xingbang-Demo-Role":request.headers.get("x-xingbang-demo-role")||"", "X-Xingbang-Client-Host":request.headers.get("host")||"", "Idempotency-Key":request.headers.get("idempotency-key")||""},
body:["GET","HEAD"].includes(request.method)?undefined:await request.arrayBuffer(),cache:"no-store",
});
const headers=new Headers({"Content-Type":response.headers.get("content-type")||"application/json"});
const disposition=response.headers.get("content-disposition");
if(disposition)headers.set("Content-Disposition",disposition);
return new NextResponse(await response.arrayBuffer(),{status:response.status,headers});
} catch {
return NextResponse.json({detail:"风险 Agent 服务未连接,请检查后端运行状态"},{status:502});
}
}
export {proxy as GET,proxy as POST,proxy as PUT,proxy as DELETE};