feat: 管理面板补齐(RAG导入/记忆添加/会话接history/诚实表述) + design/16 §4.6 IM多会话隔离+第三方身份+黑白名单
CI / Backend (build / vet / test / cross-compile) (push) Has been cancelled
CI / Frontend (tsc + vite build) (push) Has been cancelled

agent-98 面板批次:
- 会话管理面板改接 history.ListSessions(主web+次级) + 新建次级会话按钮
- 记忆面板手动添加入口 POST /admin/memories(owner)
- 知识库真正实现 IndexDocument(分块+落projectindex) + POST /admin/rag 导入端点
- 三面板空态诚实表述(去除'后端M10+提供')
- RAG嵌入降级到keyword/line(无直属embedder,不伪造语义,符合红线)

agent-99 design/16 §4.6:
- model.Message.SenderName + 迁移0006 sender_name 列
- 第三方身份边界: session.ResolveActor判定(非LLM猜) + [第三方·昵称]前缀(不改源content)
- 群聊/陌生人落独立sess-xxx(persistIsolated),不写主会话,不广播
- 黑/白名单(allowGroups/denyGroups/allowUsers/denyUsers)最前拦截,不进LLM
- 多账号 CHANNELS_JSON 配置 + 每平台多实例
- 管理只读 GET /admin/channels/{platform}/messages(该平台非主人消息流)

实机验证(JWT): 会话管理返回主会话web / 记忆添加成功 / RAG导入documents非空+Cyrene命中; 后端build/vet/test全绿 + 前端build绿
This commit is contained in:
Aska committed 2026-09-07 05:13:36 +08:00
1 parent 37e98ec584
commit 9c2a37c4ac
32 files changed
+1768 -136

No files matched your search

+26 -3
View File
@@ -21,12 +21,15 @@ import type {
LlmTestRequest,
LlmTestResult,
LoginResult,
MemoryAddRequest,
MemoryAddResult,
MemoryRecord,
MemorySearchRequest,
MemorySearchResult,
Mode,
PersonaRecord,
PersonaUpsertRequest,
RagIndexRequest,
Role,
SessionMeta,
SessionRecord,
@@ -205,6 +208,8 @@ export interface MessageDto {
platform?: string;
/** Concrete client/device that produced the message (design/16 §4.5). */
deviceId?: string;
/** Sender display name of a third-party message (design/16 §4.6). Blank for the owner. */
senderName?: string;
createdAt: number;
}
@@ -221,6 +226,7 @@ function toChatMessage(m: MessageDto): ChatMessage {
thinking: m.thinking,
platform: m.platform,
deviceId: m.deviceId,
senderName: m.senderName,
createdAt: m.createdAt,
};
}
@@ -295,9 +301,11 @@ export function isNotImplemented(err: unknown): boolean {
export function adminListPersonas(): Promise<PersonaRecord[]> {
return request<PersonaRecord[]>("/admin/personas");
}
/** GET /api/v1/admin/sessions */
export function adminListSessions(): Promise<SessionRecord[]> {
return request<SessionRecord[]>("/admin/sessions");
/** GET /api/v1/admin/sessions — the persistent main + secondary sessions (design/16).
* The main session (fixed id) is always present, so the panel is never empty in a
* history-wired deployment. */
export function adminListSessions(): Promise<SessionMeta[]> {
return request<SessionMeta[]>("/admin/sessions");
}
/** GET /api/v1/admin/memories */
export function adminListMemories(): Promise<MemoryRecord[]> {
@@ -364,6 +372,14 @@ export function adminDeleteRag(id: string): Promise<{ id: string }> {
body: JSON.stringify({ id }),
});
}
/** POST /api/v1/admin/rag — imports one knowledge document (chunk + index), so it
* becomes keyword/searchable in the project index. */
export function adminIndexRag(req: RagIndexRequest): Promise<{ name: string }> {
return request<{ name: string }>("/admin/rag", {
method: "POST",
body: JSON.stringify(req),
});
}
/* --- Admin write endpoints (owner-only, audited; real store or honest 501). --- */
@@ -491,6 +507,13 @@ export function adminPluginUninstall(req: { name: string }): Promise<{ name: str
export function adminMemorySearch(req: MemorySearchRequest): Promise<MemorySearchResult> {
return request<MemorySearchResult>("/admin/memories/search", { method: "POST", body: JSON.stringify(req) });
}
/** POST /api/v1/admin/memories — writes one memory item (manual "添加记忆"). */
export function adminAddMemory(req: MemoryAddRequest): Promise<MemoryAddResult> {
return request<MemoryAddResult>("/admin/memories", {
method: "POST",
body: JSON.stringify(req),
});
}
/** GET /api/v1/admin/sessions/{id}/export — exports a session as JSON. */
export function adminExportSession(id: string): Promise<{ session: SessionRecord }> {
return request<{ session: SessionRecord }>(`/admin/sessions/${encodeURIComponent(id)}/export`);
+26
View File
@@ -88,6 +88,10 @@ export interface ChatMessage {
* defaults to labeling the bubble source when this is present; absent on
* legacy/client-optimistic messages kept without it. */
deviceId?: string;
/** Human-readable sender display name of a third-party message (group/stranger,
* design/16 §4.6). Blank for the owner's private chat, so present only when a
* non-owner is speaking; the recipient renders it as the sender label. */
senderName?: string;
createdAt: number;
}
@@ -494,3 +498,25 @@ export interface MemorySearchResult {
semantic: boolean;
hits: MemorySearchHit[];
}
/** POST /api/v1/admin/memories body — the manual "添加记忆" write. Scope is
* optional and defaults to persona; it must be one of user|persona|project. */
export interface MemoryAddRequest {
personaId?: string;
content: string;
scope?: "user" | "persona" | "project";
}
/** POST /api/v1/admin/memories result (the stored item's identity + text). */
export interface MemoryAddResult {
id: string;
scope: string;
text: string;
}
/** POST /api/v1/admin/rag body — the admin "导入文档" write. */
export interface RagIndexRequest {
name: string;
desc?: string;
content: string;
}
+1
View File
@@ -176,6 +176,7 @@ function parsePersistedMessage(v: unknown): ChatMessage | null {
image: typeof r.image === "string" ? r.image : undefined,
platform: typeof r.platform === "string" && r.platform ? r.platform : undefined,
deviceId: typeof r.deviceId === "string" && r.deviceId ? r.deviceId : undefined,
senderName: typeof r.senderName === "string" && r.senderName ? r.senderName : undefined,
createdAt: typeof r.createdAt === "number" ? r.createdAt : now(),
};
}