Files
hunjing/frontend/src/lib/mock/index.ts
T
chenxin b419e0afde M7:形仪与语音——真形锚点、候选定相、声音档案与克隆授权、ASR 接入
- 形仪域 domains/xingyi:form_anchors(稳定辨识特征/年龄表现/许可禁忌/可变范围——生成内容不可静默覆盖锚点);forms(主仪/化仪/文字形仪,candidate→adopt 定相治理:未定相不进呈现位、主仪唯一、新主仪自动退役旧主仪);voice_profiles(三来源,授权克隆必填用途与传播范围,revoke-grant 撤回立即生效,assert-usable 合成前置校验拒绝撤回/过期)
- ASR:元炁 SenseVoice /v1/audio/transcriptions 透传(Whisper 兼容 multipart);YUANQI_BASE_URL 未配置时 503 不降级不伪造——多模态关闭纪律
- 前端形仪工坊(人物详情页):真形锚点编辑(特征逐行解析)/候选登记定相废弃/声音档案建立与授权撤回;头像呈现位改走定相主仪(forms/primary);xingyi api 库 + mock + 组件 4 用例
- 退役:旧 avatar 模块(services/endpoints/avatar、avatar_dir 配置、avatar_url 字段、前端直传组件与测试)
- 验收(test_m7_xingyi 三用例):未定相不成为正式形仪(含主仪唯一与旧主仪 retired 断言);克隆授权可撤回(缺用途拒/撤回即拒/过期同理/预设不受约束);关闭全部多模态后文字相伴完整可用(ASR 503 时镜树缘契百工照常——回归)
- 四套件:后端 376、前端 203、BFF 4、神识 5 包全绿(2 个预存环境失败不变);迁移 008 净库+±1 干净
2026-08-27 20:26:52 +08:00

1340 lines
40 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);
// —— 形仪:真形锚点 / 形仪候选 / 声音档案 ——
{
const m1 = path.match(/^\/characters\/([^/]+)\/form-anchor$/);
if (m1 && (m === "GET" || m === "PATCH" || m === "PUT")) {
return ok({
character_id: m1[1],
features: { 发色: "月白", 瞳色: "琥珀" },
age_presentation: "adult",
permissions: { 画风: "水墨" },
mutable_scope: { 服装: "可变", 表情: "可变" },
});
}
}
{
const m2 = path.match(/^\/characters\/([^/]+)\/forms\/primary$/);
if (m2 && m === "GET") {
return ok({
id: "form_mock_001",
character_id: m2[1],
kind: "primary",
status: "adopted",
source: "image_gen",
description: "月白长衫立绘",
media_ref: null,
live2d: {},
created_at: "2026-08-27T10:00:00Z",
adopted_at: "2026-08-27T10:05:00Z",
});
}
}
{
const m3 = path.match(/^\/characters\/([^/]+)\/forms$/);
if (m3 && m === "GET") {
return ok([
{
id: "form_mock_001",
character_id: m3[1],
kind: "primary",
status: "adopted",
source: "image_gen",
description: "月白长衫立绘",
media_ref: null,
live2d: {},
created_at: "2026-08-27T10:00:00Z",
adopted_at: "2026-08-27T10:05:00Z",
},
{
id: "form_mock_002",
character_id: m3[1],
kind: "costume",
status: "candidate",
source: "upload",
description: "雪夜披氅(候选)",
media_ref: null,
live2d: {},
created_at: "2026-08-27T11:00:00Z",
adopted_at: null,
},
]);
}
if (m3 && m === "POST") {
return ok({ id: "form_mock_003", status: "candidate" }, 201);
}
}
{
const m4 = path.match(
/^\/characters\/([^/]+)\/forms\/([^/]+)\/(adopt|discard)$/,
);
if (m4 && m === "POST") {
return ok({
id: m4[2],
status: m4[3] === "adopt" ? "adopted" : "discarded",
});
}
}
{
const m5 = path.match(/^\/characters\/([^/]+)\/voices$/);
if (m5 && m === "GET") {
return ok([
{
id: "voice_mock_001",
character_id: m5[1],
source: "synthesis_preset",
name: "预设音色 A",
params: { timbre: "warm" },
grant_purpose: null,
grant_expires_at: null,
grant_distribution: null,
grant_revoked_at: null,
is_active: true,
},
]);
}
if (m5 && m === "POST") {
return ok({ id: "voice_mock_002", status: "created" }, 201);
}
}
{
const m6 = path.match(
/^\/characters\/([^/]+)\/voices\/([^/]+)\/revoke-grant$/,
);
if (m6 && m === "POST") {
return ok({
voice_id: m6[2],
grant_revoked: true,
effective: "immediate",
});
}
}
// —— 百工:能力目录 / 授契 / 请命 / 行动账 ——
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;
}