Files
hunjing/frontend/src/lib/mock/index.ts
T
chenxin 684261f4b4 M5:百工与司命——能力注册表、授契行契请命、禁域裁决与观察标证
- 百工域 domains/baigong:capabilities(统一能力契约:执行类型 builtin/qiankun/mcp/http + 四维风险标注 + 来源版本)、mandates(授契 grant / 行契 charter 预算时间窗,撤销立即生效)、tool_calls(净化摘要 + 观察标证);请命复用 pending_confirmations 加 expires_at/brief_json(超时=拒绝执行 HJ-1302,六要素说明)
- 司命裁决器 siming.py:四维取最高档(能力/数据/后果/情境);禁域为代码常量不可配置(递归删根/格式化/写裸设备/fork 炸弹、file:// 协议、密钥材料外发)——专项测试断言恶意标注与篡改对象仍拒 HJ-1301
- 执行链:禁域→裁决(HJ-1303 未授契拒且双留痕)→请命→执行(乾坤经 SandboxClient HMAC;MCP 经 call_tool;内置无副作用)→净化标证(观察非事实,敏感遮蔽+体量截断)→行动账
- MCP 登记/启停/刷新自动派生与撤下 capabilities(博闻登记接入点);内置能力种子(echo/datetime/qiankun.shell/web.fetch)
- 端点:/capabilities /mandates /petitions /tool-calls + /tools 兼容投影;前端百工页四区(能力目录四维标注/授契行契/请命批准拒绝/行动账观察标证)
- 退役:modules/safety|skills|tool_runtime|agent、application/tool_runtime、services/skills|safety、tool_policies/skills/model_endpoint_overrides 表(迁移 006)、前端 SkillManager/ToolsConsole/旧 tools 页
- 验收(test_baigong_siming 四用例):未授契被拒留痕、请命超时不执行、禁域不可关闭、结果为观察不入忆;后端 454、前端 220、BFF 4、神识 5 包全绿(2 个预存环境失败不变)
2026-08-27 19:17:21 +08:00

1230 lines
37 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Mock 拦截层入口。
*
* 由环境变量 NEXT_PUBLIC_ENABLE_MOCK 开关:为 "true" 时,apiRequest 命中下列路由表
* 即返回 Mock 数据,不再发起真实请求;未命中的端点放行至真实请求(并打印 warn)。
* SSE 流的 Mock 见 lib/sse-client.ts。接入真实数据后,关闭开关即可,无需改组件。
*/
import type {
CreateAgentRequest,
CreateCharacterRequest,
CreateGoalRequest,
CreateImportRequest,
CreateMemoryRequest,
CreatePerceptionChannelRequest,
CreatePlanRequest,
CreateSafetyPolicyRequest,
CreateSessionRequest,
CreateToolRequest,
CreateWorldEntityRequest,
BatchReviewRequest,
CreateMcpServerRequest,
CreateSkillRequest,
CreateWorldRelationRequest,
UpdateGenerationPreferencesRequest,
UpdateMemoryRequest,
UpdateModelPolicyRequest,
UpdateProfileRequest,
UpdateSessionRequest,
} from "@/types/api";
import * as data from "./data";
/** 是否启用 Mock(NEXT_PUBLIC_ 前缀使其在服务端与浏览器均可用) */
export const USE_MOCK = process.env.NEXT_PUBLIC_ENABLE_MOCK === "true";
/** 模拟网络延迟,让加载/过渡更接近真实 */
export const MOCK_LATENCY_MS = 180;
/** mock 内存态:当前激活人物 id,支持切换后 getActiveCharacter 正确返回 */
let _mockActiveCharacterId = data.MOCK_CHARACTER_ID;
/** mock 内存态:纪念日列表,支持 CRUD */
const _annivList: Array<Record<string, unknown>> = [];
export const mockDelay = (ms: number): Promise<void> =>
new Promise((resolve) => setTimeout(resolve, ms));
export interface MockResult {
status: number;
data: unknown;
}
const ok = (body: unknown, status = 200): MockResult => ({
status,
data: body,
});
function paginate<T>(items: T[]) {
return {
items,
page: 1,
page_size: items.length,
total: items.length,
};
}
/**
* 路由匹配:命中返回 MockResult,未命中返回 undefined。
* @param method HTTP 方法
* @param path 不含 /api/v1 前缀的路径(如 /characters/active)
* @param query 查询参数
* @param json 请求体
*/
export function resolveMock(
method: string,
path: string,
query?: Record<string, unknown>,
json?: unknown,
): MockResult | undefined {
const m = method.toUpperCase();
// —— 健康检查 ——
if (m === "GET" && path === "/health") return ok(data.health);
// —— 百工:能力目录 / 授契 / 请命 / 行动账 ——
if (m === "GET" && path === "/capabilities") {
return ok([
{
id: "cap_mock_001",
name: "builtin.echo",
description: "回显输入(无副作用演示能力)",
executor_type: "builtin",
executor_ref: "echo",
risk_dims: {
capability: "L0",
data: "L0",
consequence: "L0",
context: "L0",
},
data_scope: "",
destinations: "",
source: "builtin",
version: "1",
consent_mode: "allow",
enabled: true,
mandated: true,
},
{
id: "cap_mock_002",
name: "qiankun.shell",
description: "在乾坤沙箱中执行命令(隔离执行,HMAC 授契)",
executor_type: "qiankun",
executor_ref: "",
risk_dims: {
capability: "L2",
data: "L1",
consequence: "L3",
context: "L1",
},
data_scope: "沙箱工作目录",
destinations: "本机沙箱进程",
source: "builtin",
version: "1",
consent_mode: "ask",
enabled: true,
mandated: false,
},
]);
}
if (m === "GET" && path === "/mandates") {
return ok([
{
id: "mdt_mock_001",
capability_id: "cap_mock_001",
character_id: null,
kind: "charter",
scope: {},
budget_total: 20,
budget_used: 3,
status: "active",
granted_at: "2026-08-27T10:00:00Z",
expires_at: null,
},
]);
}
if (m === "POST" && path === "/mandates") {
return ok({ id: "mdt_mock_002", status: "active" }, 201);
}
if (m === "DELETE" && path.startsWith("/mandates/")) {
return ok(null, 204);
}
if (m === "GET" && path === "/petitions") {
return ok([
{
id: "confirm_mock_001",
capability: "qiankun.shell",
risk_level: "L3",
reason: "请命:高风险动作需当次确认",
brief: {
who: "character:character_mock",
what: "在乾坤沙箱中执行命令",
capability: "qiankun.shell",
executor: "qiankun",
destinations: "本机沙箱进程",
consequences: "风险等级 L3",
alternatives: "拒绝后可调整参数或改用低风险能力",
},
run_id: "run_mock_001",
character_id: "character_mock",
created_at: "2026-08-27T10:05:00Z",
expires_at: "2026-08-27T10:15:00Z",
},
]);
}
if (
m === "POST" &&
path.startsWith("/petitions/") &&
path.endsWith("/approve")
) {
return ok({ status: "resolved", approved: true });
}
if (
m === "POST" &&
path.startsWith("/petitions/") &&
path.endsWith("/deny")
) {
return ok({ status: "cancelled", approved: false });
}
if (m === "GET" && path === "/tool-calls") {
return ok([
{
id: "tc_mock_001",
capability_id: "cap_mock_001",
run_id: "run_mock_001",
character_id: "character_mock",
trace_id: "trace_mock_001",
decision: "allow",
mandate_id: "mdt_mock_001",
status: "ok",
error_code: null,
input_digest: '{"text":"琉璃之镜"}',
output_digest: "[echo] 琉璃之镜",
evidence: {
kind: "observation",
capability: "builtin.echo",
epistemic: "observation_not_fact",
},
duration_ms: 3,
created_at: "2026-08-27T10:06:00Z",
},
{
id: "tc_mock_002",
capability_id: "cap_mock_002",
run_id: null,
character_id: null,
trace_id: null,
decision: "denied_unmandated",
mandate_id: null,
status: "rejected",
error_code: "HJ-1303",
input_digest: '{"command":"ls"}',
output_digest: "",
evidence: { kind: "observation", capability: "qiankun.shell" },
duration_ms: 0,
created_at: "2026-08-27T10:07:00Z",
},
]);
}
// —— 神识阁:BYOK 密钥(脱敏)——
if (m === "GET" && path === "/me/byok-keys") {
return ok({
keys: [
{
id: "byok_mock_001",
name: "主力 DeepSeek",
type: "openai_compatible",
base_url: "https://api.deepseek.com",
model: "deepseek-v4-flash",
api_key_mask: "sk-****",
},
],
});
}
if (m === "POST" && path === "/me/byok-keys") {
const body = (json ?? {}) as Record<string, unknown>;
return ok(
{
id: "byok_mock_002",
name: String(body.name ?? ""),
type: String(body.type ?? ""),
base_url: String(body.base_url ?? ""),
model: String(body.model ?? ""),
api_key_mask: "sk-****",
},
201,
);
}
if (m === "DELETE" && path.startsWith("/me/byok-keys/")) {
return ok(null, 204);
}
// —— 神识阁:用量 ——
if (m === "GET" && path === "/me/usage") {
return ok({
rows: [
{
user_id: "user_mock",
model: "deepseek-v4-flash",
endpoint: "deepseek",
requests: 12,
ok: 11,
prompt_tokens: 8400,
completion_tokens: 3100,
},
],
quota: { budget: 1_000_000, used: 11_500, reserved: 0 },
});
}
// —— 设置:生成参数偏好(GET / PATCH /settings/preferences)——
if (m === "GET" && path === "/settings/preferences") {
return ok(data.generationPreferences);
}
if (m === "PATCH" && path === "/settings/preferences") {
return ok(
data.updateGenerationPreferences(
(json ?? {}) as UpdateGenerationPreferencesRequest,
),
);
}
// —— 司命台 · 九枢健康度聚合(API §19.1)——
if (m === "GET" && path === "/admin/pivots/health") {
return ok(data.pivotHealthReport);
}
// —— 司命台 · 朱批待办(API §19.2)——
if (m === "GET" && path === "/admin/safety/pending") {
return ok(paginate(data.pendingApprovals));
}
// —— 安全审计(GET /safety/audit-events,按人物/枢过滤)——
if (m === "GET" && path === "/safety/audit-events") {
const pivot = query?.pivot as string | undefined;
const items = pivot
? data.auditTimeline.filter((e) => e.pivot === pivot)
: data.auditTimeline;
return ok(paginate(items));
}
// —— 安全策略(§16;开放集合:增删改,随工具增长;后端返回全量 list 非分页)——
if (m === "GET" && path === "/safety/policies") {
return ok(data.safetyPolicies);
}
if (m === "POST" && path === "/safety/policies") {
return ok(data.createPolicy((json ?? {}) as CreateSafetyPolicyRequest));
}
{
const mt = path.match(/^\/safety\/policies\/([^/]+)$/);
if (mt && m === "PATCH") {
const body = (json ?? {}) as Record<string, unknown>;
const found = data.safetyPolicies.find((p) => p.tool === mt[1]);
// 乐观:新增策略不在初始列表,find 不到时合成 base 再合并
const base =
found ??
data.createPolicy({
tool: mt[1],
risk_level: "L2",
permission_policy: "ask",
});
return ok({ ...base, ...body, tool: mt[1] });
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// —— 司命台 · 起居注审计(API §19.3),支持 pivot 过滤 ——
if (m === "GET" && path === "/admin/audit/timeline") {
const pivot = query?.pivot as string | undefined;
const items = pivot
? data.auditTimeline.filter((e) => e.pivot === pivot)
: data.auditTimeline;
return ok(paginate(items));
}
// —— 司命台 · 后续阶段治理端点(API §19.4 预留)——
// 太一枢 · 目标层级(开放集合:增删改)
if (m === "GET" && path === "/admin/goals") {
const characterId = query?.character_id as string | undefined;
return ok(
characterId
? data.goals.filter((g) => g.character_id === characterId)
: data.goals,
);
}
if (m === "POST" && path === "/admin/goals") {
return ok(data.createGoal((json ?? {}) as CreateGoalRequest));
}
{
const mt = path.match(/^\/admin\/goals\/([^/]+)$/);
if (mt && m === "PATCH") {
const found = data.goals.find((g) => g.id === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
// 乐观:新增项不在初始列表,find 不到时合成 base 再合并,保证可继续编辑
const base =
found ??
data.createGoal({
title: String(body.title ?? "未命名目标"),
level: "task",
});
return ok({
...base,
...body,
id: mt[1],
updated_at: new Date().toISOString(),
});
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// 烛照枢 · 感知通道授权(开放集合:增删改;默认六类,可自定义感知源)
if (m === "GET" && path === "/admin/perception/channels")
return ok(data.perceptionChannels);
if (m === "POST" && path === "/admin/perception/channels") {
return ok(
data.createPerceptionChannel(
(json ?? {}) as CreatePerceptionChannelRequest,
),
);
}
{
const mt = path.match(/^\/admin\/perception\/channels\/([^/]+)$/);
if (mt && m === "PATCH") {
const found = data.perceptionChannels.find((c) => c.channel === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
const base =
found ??
data.createPerceptionChannel({
channel: mt[1],
name: String(body.name ?? mt[1]),
description: "自定义感知源",
});
return ok({ ...base, ...body, channel: mt[1] });
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// 河图枢 · 世界实体(开放集合:增删改)
if (m === "GET" && path === "/admin/world/entities")
return ok(data.worldEntities);
if (m === "POST" && path === "/admin/world/entities") {
return ok(data.createWorldEntity((json ?? {}) as CreateWorldEntityRequest));
}
{
const mt = path.match(/^\/admin\/world\/entities\/([^/]+)$/);
if (mt && m === "PATCH") {
const found = data.worldEntities.find((e) => e.id === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
const base =
found ??
data.createWorldEntity({
type: "concept",
name: String(body.name ?? "未命名实体"),
});
return ok({
...base,
...body,
id: mt[1],
updated_at: new Date().toISOString(),
});
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// 洛书枢 · 计划(开放集合:增删改)
if (m === "GET" && path === "/admin/plans") return ok(data.plans);
if (m === "POST" && path === "/admin/plans") {
return ok(data.createPlan((json ?? {}) as CreatePlanRequest));
}
{
const mt = path.match(/^\/admin\/plans\/([^/]+)$/);
if (mt && m === "PATCH") {
const found = data.plans.find((p) => p.id === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
const base =
found ?? data.createPlan({ title: String(body.title ?? "未命名计划") });
return ok({
...base,
...body,
id: mt[1],
updated_at: new Date().toISOString(),
});
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// 天工阁枢 · 角色在岗(开放集合:增删改;默认九角色,可自定义新角色)
if (m === "GET" && path === "/admin/agents/roster")
return ok(data.agentRoster);
if (m === "POST" && path === "/admin/agents/roster") {
return ok(data.createAgent((json ?? {}) as CreateAgentRequest));
}
{
const mt = path.match(/^\/admin\/agents\/roster\/([^/]+)$/);
if (mt && m === "PATCH") {
const found = data.agentRoster.find((a) => a.role === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
const base =
found ??
data.createAgent({
role: mt[1],
name: String(body.name ?? mt[1]),
responsibility: "自定义角色",
});
return ok({ ...base, ...body, role: mt[1] });
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// —— 司命台 · 链路追踪 GET /admin/traces(§19.5)——
if (m === "GET" && path === "/admin/traces") {
return ok({ items: [], total: 0 });
}
{
const mt = path.match(/^\/admin\/traces\/([^/]+)$/);
if (mt && m === "GET") {
return ok({
trace_id: mt[1],
events: [],
audit_entries: [],
idempotency_entries: [],
session_ids: [],
character_ids: [],
});
}
}
// —— 司命台 · 时间事件 GET /admin/time-events(§19.5)——
if (m === "GET" && path === "/admin/time-events") {
return ok({ items: [], total: 0, page: 1, page_size: 50 });
}
// —— 司命台 · 租户管理(管理员用)——
if (m === "GET" && path === "/admin/tenants/overview") {
return ok([]);
}
{
const mt = path.match(/^\/admin\/tenants\/([^/]+)\/characters$/);
if (mt && m === "GET") return ok([]);
}
{
const mt = path.match(/^\/admin\/tenants\/([^/]+)\/memories$/);
if (mt && m === "GET") return ok([]);
}
// —— 用户管理(§19.6)——
if (m === "GET" && path === "/admin/users") {
return ok([]);
}
if (m === "POST" && path === "/admin/users") {
return ok({
id: `user-${Date.now()}`,
username: (json as Record<string, unknown>)?.username ?? "new_user",
email: "",
is_active: true,
is_admin: false,
created_at: new Date().toISOString(),
});
}
{
const mt = path.match(/^\/admin\/users\/([^/]+)$/);
if (mt && m === "PATCH") {
return ok({ id: mt[1], ...(json as Record<string, unknown>) });
}
}
// —— 工具(§15;开放集合:增删改,器用治理)——
if (m === "GET" && path === "/tools") {
return ok(data.tools);
}
if (m === "POST" && path === "/tools") {
return ok(data.createTool((json ?? {}) as CreateToolRequest));
}
// 工具调用详情(GET /tools/calls/{tool_call_id})
{
const mt = path.match(/^\/tools\/calls\/([^/]+)$/);
if (mt && m === "GET") {
return ok({
id: mt[1],
tool: "unknown",
status: "completed",
started_at: new Date().toISOString(),
completed_at: new Date().toISOString(),
});
}
}
// 工具调用(POST /tools/{tool_name}/invoke,非流式)
{
const mt = path.match(/^\/tools\/([^/]+)\/invoke$/);
if (mt && m === "POST") {
return ok({
status: "completed",
result: {
ok: true,
exit_code: 0,
stdout: "",
stderr: "",
duration_ms: 100,
timeout: false,
truncated: false,
},
trace_id: `trace-${mt[1]}-${Date.now()}`,
});
}
}
{
const mt = path.match(/^\/tools\/([^/]+)$/);
if (mt && m === "PATCH") {
const body = (json ?? {}) as Record<string, unknown>;
const found = data.tools.find((t) => t.name === mt[1]);
const base =
found ??
data.createTool({
name: mt[1],
description: "自定义工具",
risk_level: "L2",
permission_policy: "ask",
side_effect: false,
audit_required: false,
});
return ok({ ...base, ...body, name: mt[1] });
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
{
const mt = path.match(/^\/safety\/confirmations\/([^/]+)\/(approve|deny)$/);
if (mt && m === "POST") {
return mt[2] === "approve"
? ok({
status: "completed",
result: {
ok: true,
exit_code: 0,
stdout: "",
stderr: "",
duration_ms: 120,
timeout: false,
truncated: false,
trace_id: `trace-${mt[1]}`,
},
})
: ok({ status: "cancelled", confirmation_id: mt[1] });
}
}
// —— 人物 ——
if (m === "GET" && path === "/characters/active") {
const found = data.characters.find((c) => c.id === _mockActiveCharacterId);
return ok(found ?? data.activeCharacter);
}
if (m === "GET" && path === "/characters") {
return ok(paginate(data.characterList));
}
if (m === "GET" && path === "/characters/templates") {
return ok(data.characterTemplates);
}
{
const mt = path.match(/^\/characters\/([^/]+)$/);
if (mt && m === "GET") {
const found = data.characters.find((c) => c.id === mt[1]);
return ok(found ?? data.activeCharacter);
}
}
{
const mt = path.match(/^\/characters\/([^/]+)\/activate$/);
if (mt && m === "POST") {
_mockActiveCharacterId = mt[1];
return ok({ active_character_id: mt[1], status: "active" });
}
}
if (m === "POST" && path === "/characters") {
const created = data.createCharacter(
(json ?? {}) as CreateCharacterRequest,
);
// 入库:加入 mock 列表,使后续查询可访问
data.addCharacterToList(created);
// 模板创建的人物自动设为激活,与前端的创建后跳转表现一致
if ((json as CreateCharacterRequest)?.template_key) {
created.status = "active";
// 同步更新列表项的状态
const item = data.characterList.find((c) => c.id === created.id);
if (item) item.status = "active";
_mockActiveCharacterId = created.id;
}
return ok(created);
}
{
const mt = path.match(/^\/characters\/([^/]+)\/profile$/);
if (mt && m === "PATCH") {
return ok(
data.updateCharacterProfile(
mt[1],
(json ?? {}) as UpdateProfileRequest,
),
);
}
}
{
const mt = path.match(/^\/characters\/([^/]+)\/model-policy$/);
if (mt && m === "PATCH") {
return ok(
data.updateCharacterModelPolicy(
mt[1],
(json ?? {}) as UpdateModelPolicyRequest,
),
);
}
}
{
const mt = path.match(/^\/characters\/([^/]+)\/(pause|resume|archive)$/);
if (mt && m === "POST") {
const found = data.characters.find((c) => c.id === mt[1]);
const base = found ?? data.activeCharacter;
const status =
mt[2] === "pause"
? "paused"
: mt[2] === "resume"
? "active"
: "archived";
return ok({
...base,
id: mt[1],
status,
updated_at: new Date().toISOString(),
});
}
}
// —— 记忆 ——
if (m === "GET" && path === "/memory") {
return ok(paginate(data.memories));
}
if (m === "POST" && path === "/memory") {
return ok(data.createMemory(json as CreateMemoryRequest));
}
{
const mt = path.match(/^\/memory\/([^/]+)$/);
if (mt && m === "PATCH") {
return ok(data.updateMemory(mt[1], json as UpdateMemoryRequest));
}
if (mt && m === "DELETE") {
return ok(undefined, 204);
}
}
// —— 人格 ——
if (m === "GET" && path === "/personality/current") {
return ok(data.personality);
}
if (m === "GET" && path === "/personality/versions") {
return ok(paginate(data.personalityVersions));
}
if (m === "POST" && path === "/personality/propose-update") {
const body = (json ?? {}) as {
reason?: string;
fields?: Record<string, unknown>;
};
return ok({
proposal_id: `personality_proposal_${Date.now()}`,
character_id: data.MOCK_CHARACTER_ID,
status: "pending",
reason: body.reason ?? "",
fields: body.fields ?? {},
created_at: new Date().toISOString(),
});
}
{
const mt = path.match(
/^\/personality\/proposals\/([^/]+)\/(approve|reject)$/,
);
if (mt && m === "POST") {
const approved = mt[2] === "approve";
return ok({
proposal_id: mt[1],
character_id: data.MOCK_CHARACTER_ID,
status: approved ? "approved" : "rejected",
reason: "",
fields: {},
resulting_version_id: approved
? `pv-jingwu-00${data.personalityVersions.length + 1}`
: null,
reviewed_at: new Date().toISOString(),
created_at: new Date().toISOString(),
});
}
}
{
const mt = path.match(/^\/personality\/versions\/([^/]+)\/rollback$/);
if (mt && m === "POST") {
const found = data.personalityVersions.find((v) => v.id === mt[1]);
return ok(found ?? data.personality);
}
}
// —— 对话 ——
if (m === "GET" && path === "/chat/sessions") {
const characterId = query?.character_id as string | undefined;
const status = query?.status as string | undefined;
return ok(
paginate(
data.sessions.filter(
(s) =>
s.status !== "deleted" &&
(!characterId || s.character_id === characterId) &&
(!status || s.status === status),
),
),
);
}
if (m === "POST" && path === "/chat/sessions") {
return ok(data.createSession((json ?? {}) as CreateSessionRequest));
}
{
const mt = path.match(/^\/chat\/sessions\/([^/]+)$/);
if (mt && m === "PATCH") {
const updated = data.updateSession(
mt[1],
(json ?? {}) as UpdateSessionRequest,
);
return ok(
updated ?? {
session_id: mt[1],
character_id: data.MOCK_CHARACTER_ID,
status: "active",
created_at: new Date().toISOString(),
},
);
}
if (mt && m === "DELETE") {
data.deleteSession(mt[1]);
return ok(undefined, 204);
}
}
{
const mt = path.match(/^\/chat\/sessions\/([^/]+)\/messages$/);
if (mt && m === "POST") {
return ok(data.sendMessage(mt[1]));
}
if (mt && m === "GET") {
// mock 不自动注入预设历史——进入/新建会话均为空,消息由本次对话在内存持有。
return ok(paginate([]));
}
}
{
const mt = path.match(
/^\/chat\/sessions\/([^/]+)\/messages\/([^/]+)\/version$/,
);
if (mt && m === "PATCH") {
const body = (json ?? {}) as { version_idx?: number };
return ok({
message_id: mt[2],
active_version_idx: body.version_idx ?? 0,
versions: [],
});
}
}
// —— 运行 ——
// 列表(GET /runs,支持分页参数)
if (m === "GET" && path === "/runs") {
return ok(paginate([data.getRun("run-mock-001")]));
}
{
const mt = path.match(/^\/runs\/([^/]+)$/);
if (mt && m === "GET") return ok(data.getRun(mt[1]));
}
{
const mt = path.match(/^\/runs\/([^/]+)\/cancel$/);
if (mt && m === "POST") return ok({ id: mt[1], status: "cancelled" });
}
{
const mt = path.match(/^\/runs\/([^/]+)\/events$/);
if (mt && m === "GET") return ok(data.listRunEvents(mt[1]));
}
// —— 世界模型(独立 /world 域:实体 + 关系;实体编辑/删除仍走 admin)——
if (m === "GET" && path === "/world/entities") {
return ok(paginate(data.worldEntities));
}
if (m === "POST" && path === "/world/entities") {
return ok(data.createWorldEntity((json ?? {}) as CreateWorldEntityRequest));
}
{
const mt = path.match(/^\/world\/entities\/([^/]+)$/);
if (mt && m === "GET") {
const found = data.worldEntities.find((e) => e.id === mt[1]);
return ok(found ?? data.worldEntities[0]);
}
}
if (m === "GET" && path === "/world/relations") {
return ok(paginate(data.mockWorldRelations));
}
if (m === "POST" && path === "/world/relations") {
return ok(
data.createWorldRelation((json ?? {}) as CreateWorldRelationRequest),
);
}
{
const mt = path.match(/^\/world\/relations\/([^/]+)$/);
if (mt && m === "DELETE") return ok(undefined, 204);
}
// —— 候选审核(跨来源统一入口:记忆候选 / 世界实体候选)——
{
const mt = path.match(/^\/characters\/([^/]+)\/memory-candidates$/);
if (mt && m === "GET") {
const source = query?.source as string | undefined;
const items = source
? data.mockMemoryCandidates.filter((c) =>
source === "cycle" ? c.cycle_id : c.import_job_id,
)
: data.mockMemoryCandidates;
return ok(paginate(items));
}
}
{
const mt = path.match(/^\/memory-candidates\/([^/]+)\/(approve|reject)$/);
if (mt && m === "POST") {
return ok({
id: mt[1],
status: mt[2] === "approve" ? "approved" : "rejected",
});
{
const mt = path.match(/^\/world\/relations\/([^/]+)$/);
if (mt && m === "DELETE") return ok(undefined, 204);
}
}
}
{
const mt = path.match(/^\/characters\/([^/]+)\/world\/entity-proposals$/);
if (mt && m === "GET") return ok(paginate(data.mockWorldEntityProposals));
}
{
const mt = path.match(/^\/world\/entities\/([^/]+)\/(approve|reject)$/);
if (mt && m === "POST") {
return ok({
id: mt[1],
status: mt[2] === "approve" ? "confirmed" : "rejected",
});
}
}
// —— 资料导入流水线(§10,挂在 /characters/{id}/imports 下)——
{
const mt = path.match(/^\/characters\/([^/]+)\/imports$/);
if (mt && m === "POST") {
return ok(data.createImportJob((json ?? {}) as CreateImportRequest));
}
if (mt && m === "GET") return ok(paginate([data.mockImportJob]));
}
{
const mt = path.match(/^\/characters\/([^/]+)\/imports\/([^/]+)$/);
if (mt && m === "GET") return ok(data.mockImportJob);
}
{
const mt = path.match(/^\/characters\/([^/]+)\/imports\/([^/]+)\/items$/);
if (mt && m === "GET") return ok(paginate(data.mockImportItems));
}
{
const mt = path.match(
/^\/characters\/([^/]+)\/imports\/([^/]+)\/profile-candidates$/,
);
if (mt && m === "POST") return ok({ status: "ok" });
if (mt && m === "GET") return ok(paginate(data.mockProfileCandidates));
}
{
const mt = path.match(
/^\/characters\/([^/]+)\/imports\/([^/]+)\/memory-candidates$/,
);
if (mt && m === "POST") return ok({ status: "ok" });
if (mt && m === "GET") return ok(paginate(data.mockMemoryCandidates));
}
{
const mt = path.match(
/^\/characters\/([^/]+)\/imports\/([^/]+)\/personality-proposals$/,
);
if (mt && m === "POST") return ok({ status: "ok" });
}
{
const mt = path.match(
/^\/characters\/([^/]+)\/profile-candidates\/batch-review$/,
);
if (mt && m === "POST") {
const body = (json ?? {}) as BatchReviewRequest;
const approve =
body.decisions?.filter((d) => d.decision === "approve").length ?? 0;
const reject =
body.decisions?.filter((d) => d.decision === "reject").length ?? 0;
return ok({ applied: approve, rejected: reject, skipped: 0 });
}
}
{
const mt = path.match(
/^\/characters\/([^/]+)\/memory-candidates\/batch-review$/,
);
if (mt && m === "POST") {
const body = (json ?? {}) as BatchReviewRequest;
const approve =
body.decisions?.filter((d) => d.decision === "approve").length ?? 0;
const reject =
body.decisions?.filter((d) => d.decision === "reject").length ?? 0;
return ok({ applied: approve, rejected: reject, skipped: 0 });
}
}
// —— Skill 能力包(百工枢,/tools/skills)——
if (m === "GET" && path === "/tools/skills") {
const status = query?.status as string | undefined;
const items = status
? data.mockSkills.filter((s) => s.status === status)
: data.mockSkills;
return ok(items);
}
if (m === "POST" && path === "/tools/skills") {
return ok(data.createSkillData((json ?? {}) as CreateSkillRequest));
}
{
const mt = path.match(/^\/tools\/skills\/([^/]+)$/);
if (mt && m === "PATCH") {
const body = (json ?? {}) as Record<string, unknown>;
const found = data.mockSkills.find((s) => s.id === mt[1]);
const base =
found ?? data.createSkillData({ name: String(body.name ?? mt[1]) });
return ok({
...base,
...body,
id: mt[1],
updated_at: new Date().toISOString(),
});
}
if (mt && m === "DELETE") return ok(undefined, 204);
}
// —— MCP server(百工枢,/tools/mcp-servers)——
if (m === "GET" && path === "/tools/mcp-servers") {
const status = query?.status as string | undefined;
const items = status
? data.mockMcpServers.filter((s) => s.status === status)
: data.mockMcpServers;
return ok(items);
}
if (m === "POST" && path === "/tools/mcp-servers") {
return ok(data.createMcpServerData((json ?? {}) as CreateMcpServerRequest));
}
{
const mt = path.match(/^\/tools\/mcp-servers\/([^/]+)$/);
if (mt && m === "DELETE") return ok(undefined, 204);
if (mt && m === "PATCH") {
const found = data.mockMcpServers.find((s) => s.id === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
const base =
found ?? data.createMcpServerData({ name: String(body.name ?? mt[1]) });
return ok({ ...base, ...body, id: mt[1] });
}
}
{
const mt = path.match(/^\/tools\/mcp-servers\/([^/]+)\/status$/);
if (mt && m === "GET") {
return ok({
server_id: mt[1],
process: "stopped",
pid: null,
uptime_seconds: null,
});
}
}
{
const mt = path.match(
/^\/tools\/mcp-servers\/([^/]+)\/(start|stop|restart|refresh)$/,
);
if (mt && m === "POST") {
if (mt[2] === "start") {
return ok({
server_id: mt[1],
process: "running",
pid: 12345,
uptime_seconds: 0,
});
}
if (mt[2] === "stop") {
return ok({
server_id: mt[1],
process: "stopped",
pid: null,
uptime_seconds: null,
});
}
if (mt[2] === "restart") {
return ok({
server_id: mt[1],
process: "running",
pid: 12346,
uptime_seconds: 0,
});
}
if (mt[2] === "refresh") {
return ok(
data.mockMcpServers.find((s) => s.id === mt[1]) ??
data.mockMcpServers[0],
);
}
}
}
// —— 统一能力列举(GET /tools/capabilities,builtin/mcp/skill 三源)——
if (m === "GET" && path === "/tools/capabilities") {
return ok(data.mockCapabilities);
}
// —— 辰正时态(/time/*)——
if (m === "GET" && path === "/time/snapshot") {
const now = new Date();
const h = now.getHours();
const dayPeriod =
h < 5
? "凌晨"
: h < 8
? "清晨"
: h < 12
? "上午"
: h < 14
? "正午"
: h < 18
? "下午"
: h < 21
? "傍晚"
: "深夜";
const shichenIdx = Math.floor(((h + 1) % 24) / 2);
const shichenNames = [
"zi",
"chou",
"yin",
"mao",
"chen",
"si",
"wu",
"wei",
"shen",
"you",
"xu",
"hai",
];
return ok({
snapshot: {
now_utc: now.toISOString(),
local_time: now.toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" }),
timezone: "Asia/Shanghai",
local_hour: h,
shichen: shichenNames[shichenIdx],
day_period: dayPeriod,
weekday: now.getDay(),
is_weekend: now.getDay() === 0 || now.getDay() === 6,
solar_date: now.toISOString().slice(0, 10),
lunar: {
year: now.getFullYear(),
month: now.getMonth() + 1,
day: now.getDate(),
leap: false,
text: "腊月十五",
},
season:
h >= 3 && h < 6
? "春"
: h >= 6 && h < 9
? "夏"
: h >= 9 && h < 12
? "秋"
: "冬",
solar_term: null,
festivals: [],
acquaintance: { total_days: 365, natural_language: "相识一载" },
offline: { seconds: 120, natural_language: "刚刚还在" },
next_milestone: {
days_until: 30,
name: "相识周年",
date: now.toISOString().slice(0, 10),
},
active_milestones: [],
home_tz_diff_minutes: 0,
location: { city: "上海", country: "中国", timezone: "Asia/Shanghai" },
},
factors: {
festival_joy: 0.3,
late_night_fragility:
dayPeriod === "深夜" || dayPeriod === "凌晨" ? 0.7 : 0.2,
reunion_intensity: 0.1,
weekend_relax: now.getDay() === 0 || now.getDay() === 6 ? 0.6 : 0.2,
season_resonance: 0.5,
same_period_resonance: 0.4,
care_suggest: dayPeriod === "深夜" ? 0.8 : 0.3,
care_kind: dayPeriod === "深夜" ? "jetlag" : null,
},
context_text:
dayPeriod === "深夜"
? "夜深了,万籁俱寂。这是思绪最容易沉降的时刻——适合温缓的承接,不宜急于追问。"
: "白日的喧嚣渐远,此刻宜静不宜动。让话语如湖面般展开,不催不赶。",
});
}
if (m === "GET" && path === "/time/anniversaries") {
return ok(_annivList);
}
if (m === "POST" && path === "/time/anniversaries") {
const body = (json ?? {}) as Record<string, unknown>;
const anniv = {
id: `anniv-${Date.now()}`,
...body,
recurs_yearly: true,
source: body.source ?? "manual",
};
_annivList.push(anniv);
return ok(anniv);
}
{
const mt = path.match(/^\/time\/anniversaries\/([^/]+)$/);
if (mt && m === "GET") {
const found = _annivList.find((a) => a.id === mt[1]);
return ok(
found ?? {
id: mt[1],
name: "纪念日",
calendar: "solar",
date: "2025-01-01",
recurs_yearly: true,
kind: "anniversary",
subject_type: "relationship",
source: "manual",
},
);
}
if (mt && m === "PATCH") {
const idx = _annivList.findIndex((a) => a.id === mt[1]);
const body = (json ?? {}) as Record<string, unknown>;
if (idx >= 0) {
_annivList[idx] = { ..._annivList[idx], ...body, id: mt[1] };
return ok(_annivList[idx]);
}
return ok({ id: mt[1], ...body, recurs_yearly: true });
}
if (mt && m === "DELETE") {
const idx = _annivList.findIndex((a) => a.id === mt[1]);
if (idx >= 0) _annivList.splice(idx, 1);
return ok(undefined, 204);
}
}
if (m === "GET" && path === "/time/acquaintance") {
return ok({ total_days: 365, natural_language: "相识一载,如镜映月" });
}
if (m === "GET" && path === "/time/config") {
return ok({
user_timezone: "Asia/Shanghai",
home_timezone: "Asia/Shanghai",
default_city: "上海",
offline_active_minutes: 5,
offline_short_absence_minutes: 30,
offline_long_absence_minutes: 180,
offline_reunion_impact_minutes: 15,
care_enabled: true,
jetlag_threshold_minutes: 120,
});
}
return undefined;
}