chore: commit workspace updates

This commit is contained in:
ddCat
2026-09-14 01:11:56 +08:00
parent 01a2d171f5
commit 9727656b2b
491 changed files with 12826 additions and 72249 deletions

View File

@@ -0,0 +1,131 @@
import { localForageStorageForScope } from "@/lib/localforage-storage";
import { markdownPlainText } from "@/lib/markdown-plain-text";
import { getActiveUserScope } from "@/lib/user-scope";
import type { AgentPermissionMode, AgentRun } from "@/services/api/agent";
export type CloudAgentConversationMessage = {
id: string;
role: "user" | "assistant" | "system" | "tool" | "error";
title?: string;
text: string;
meta?: string;
detail?: unknown;
attachments?: Array<{ id: string; name: string; url: string }>;
};
export type CloudAgentConversation = {
id: string;
title: string;
messages: CloudAgentConversationMessage[];
run: AgentRun | null;
model?: string;
permissionMode: AgentPermissionMode;
skillIds?: string[];
createdAt: string;
updatedAt: string;
};
type CloudAgentConversationDocument = {
version: 1;
activeId: string | null;
conversations: CloudAgentConversation[];
};
export type CloudAgentPendingSubmission = {
fingerprint: string;
key: string;
request?: import("@/services/api/agent").CreateAgentRunInput;
parentRunId?: string;
messageId?: string;
};
const CLOUD_AGENT_CONVERSATIONS_KEY = "cloud-agent-conversations-v1";
export async function loadCloudAgentConversations(canvasId: string): Promise<CloudAgentConversationDocument> {
if (!canvasId) throw new Error("缺少画布 ID,无法读取 Agent 对话");
const value = await localForageStorageForScope(getActiveUserScope()).getItem(storageKey(canvasId));
if (!value) return emptyDocument();
let parsed: unknown;
try {
parsed = JSON.parse(value);
} catch {
throw new Error("Agent 对话历史已损坏");
}
if (!isConversationDocument(parsed)) throw new Error("Agent 对话历史格式无效");
return parsed;
}
export async function saveCloudAgentConversations(canvasId: string, activeId: string | null, conversations: CloudAgentConversation[]) {
if (!canvasId) throw new Error("缺少画布 ID,无法保存 Agent 对话");
const document: CloudAgentConversationDocument = { version: 1, activeId, conversations };
await localForageStorageForScope(getActiveUserScope()).setItem(storageKey(canvasId), JSON.stringify(document));
}
// Keep the retry key outside the chat snapshot. If the response is lost, a
// page refresh can safely repeat the same request without creating another
// billed Agent turn.
export async function loadCloudAgentPendingSubmission(canvasId: string, conversationId: string) {
if (!canvasId || !conversationId) return null;
const value = await localForageStorageForScope(getActiveUserScope()).getItem(pendingStorageKey(canvasId, conversationId));
if (value === null || value === undefined) return null;
// A corrupt recovery record is not proof that the previous POST never ran.
// Fail closed rather than discarding its identity and risking another charge.
try {
const parsed = JSON.parse(value) as Partial<CloudAgentPendingSubmission> | null;
if (!parsed || typeof parsed.fingerprint !== "string" || typeof parsed.key !== "string" || parsed.key.length < 8) throw new Error("invalid identity");
if (parsed.request !== undefined && (!parsed.request || parsed.request.idempotencyKey !== parsed.key || parsed.request.canvasId !== canvasId || typeof parsed.request.prompt !== "string")) throw new Error("invalid request");
if (parsed.parentRunId !== undefined && typeof parsed.parentRunId !== "string") throw new Error("invalid parent");
if (parsed.messageId !== undefined && typeof parsed.messageId !== "string") throw new Error("invalid message");
return { fingerprint: parsed.fingerprint, key: parsed.key, request: parsed.request, parentRunId: parsed.parentRunId, messageId: parsed.messageId } satisfies CloudAgentPendingSubmission;
} catch {
throw new Error("待确认请求记录损坏,已暂停本对话发送;请先在任务中心核对原运行,不要直接重复生成");
}
}
export async function saveCloudAgentPendingSubmission(canvasId: string, conversationId: string, pending: CloudAgentPendingSubmission) {
if (!canvasId || !conversationId) throw new Error("缺少 Agent 对话范围,无法保存幂等提交");
await localForageStorageForScope(getActiveUserScope()).setItem(pendingStorageKey(canvasId, conversationId), JSON.stringify(pending));
}
export async function clearCloudAgentPendingSubmission(canvasId: string, conversationId: string) {
if (!canvasId || !conversationId) return;
await localForageStorageForScope(getActiveUserScope()).removeItem(pendingStorageKey(canvasId, conversationId));
}
export function cloudAgentConversationTitle(messages: CloudAgentConversationMessage[]) {
const firstPrompt = displayConversationTitle(messages.find((message) => message.role === "user")?.text || "");
return firstPrompt.length > 28 ? `${firstPrompt.slice(0, 28)}...` : firstPrompt;
}
function displayConversationTitle(value: string) {
return markdownPlainText(value.replace(/@\[skill:[^\]]+\]/gu, "技能包")) || "新对话";
}
function storageKey(canvasId: string) {
return `${CLOUD_AGENT_CONVERSATIONS_KEY}:${encodeURIComponent(canvasId)}`;
}
function pendingStorageKey(canvasId: string, conversationId: string) {
return `${CLOUD_AGENT_CONVERSATIONS_KEY}:pending:${encodeURIComponent(canvasId)}:${encodeURIComponent(conversationId)}`;
}
function emptyDocument(): CloudAgentConversationDocument {
return { version: 1, activeId: null, conversations: [] };
}
function isConversationDocument(value: unknown): value is CloudAgentConversationDocument {
if (!value || typeof value !== "object") return false;
const document = value as Partial<CloudAgentConversationDocument>;
if (document.version !== 1 || !Array.isArray(document.conversations)) return false;
return document.conversations.every((conversation) => {
if (!conversation || typeof conversation !== "object") return false;
const candidate = conversation as Partial<CloudAgentConversation>;
return typeof candidate.id === "string"
&& typeof candidate.title === "string"
&& Array.isArray(candidate.messages)
&& typeof candidate.createdAt === "string"
&& typeof candidate.updatedAt === "string"
&& ["read_only", "auto", "request_approval"].includes(candidate.permissionMode || "");
});
}