diff --git a/Dockerfile b/Dockerfile index 72e2e97..e94d57a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -15,12 +15,14 @@ WORKDIR /app COPY package.json bun.lock bunfig.toml tsconfig.json ./ COPY packages/agent-core/package.json packages/agent-core/ COPY packages/client/package.json packages/client/ +COPY packages/server/package.json packages/server/ COPY packages/session-sqlite/package.json packages/session-sqlite/ RUN bun install --frozen-lockfile COPY source source COPY packages/agent-core packages/agent-core +COPY packages/server packages/server COPY packages/session-sqlite packages/session-sqlite # 默认把挂载的用户目录作为工作区,会话和工具执行都落在里面 diff --git a/packages/agent-core/src/harness/core/llm.ts b/packages/agent-core/src/harness/core/llm.ts index d591095..42e3796 100644 --- a/packages/agent-core/src/harness/core/llm.ts +++ b/packages/agent-core/src/harness/core/llm.ts @@ -17,10 +17,17 @@ export interface StreamHandlers { onTrace?: (message: string) => void; } -const client = new OpenAI({ - apiKey: process.env.OPENAI_API_KEY, - baseURL: process.env.OPENAI_API_BASE_URL, -}); +let client: OpenAI | null = null; + +function getClient(): OpenAI { + if (!client) { + client = new OpenAI({ + apiKey: process.env.OPENAI_API_KEY, + baseURL: process.env.OPENAI_API_BASE_URL, + }); + } + return client; +} export const llmModel = process.env.OPENAI_MODEL ?? ''; @@ -32,7 +39,7 @@ export async function callLLM(messages: Message[]) { throw new Error(missingModelMessage); } - const res = await client.chat.completions.create({ + const res = await getClient().chat.completions.create({ model: llmModel, messages, temperature: 0, @@ -52,7 +59,7 @@ export async function streamLLM( throw new Error(missingModelMessage); } - const stream = await client.chat.completions.create({ + const stream = await getClient().chat.completions.create({ model: llmModel, messages, temperature: 0, diff --git a/packages/agent-core/src/harness/core/state.ts b/packages/agent-core/src/harness/core/state.ts index ef45996..61777b4 100644 --- a/packages/agent-core/src/harness/core/state.ts +++ b/packages/agent-core/src/harness/core/state.ts @@ -13,6 +13,8 @@ export interface TaskState { } export interface CreateTaskStateOptions { + /** 复用已有会话时传入会话 ID,未传时生成新会话 ID */ + id?: string; mode?: AgentMode; objective?: string; constraints?: string[]; @@ -36,7 +38,7 @@ export const createTaskState = ( const trimmedInput = input.trim(); return { - id: randomUUID(), + id: options.id?.trim() || randomUUID(), input, mode: options.mode ?? 'build', objective: options.objective?.trim() || trimmedInput || '完成用户请求', diff --git a/packages/agent-core/src/harness/runtime/run-loop.ts b/packages/agent-core/src/harness/runtime/run-loop.ts index 4721afd..cf5b700 100644 --- a/packages/agent-core/src/harness/runtime/run-loop.ts +++ b/packages/agent-core/src/harness/runtime/run-loop.ts @@ -127,10 +127,11 @@ export const runLoop = async ( if (!res) { return '无法获取 LLM 回复'; } + const parsed = parseAgentResponse(res); history.push({ role: 'assistant', content: res }); - session.appendAssistant(res); + // 会话里保存人类可读的 message,避免 WebUI 展示原始协议 JSON + session.appendAssistant(parsed ? parsed.message : extractFinalText(res)); - const parsed = parseAgentResponse(res); if (parsed && isToolCallAction(parsed)) { const execution = await runToolCall(task, parsed, session); history.push({ role: 'user', content: execution.content }); @@ -150,4 +151,4 @@ export const runLoop = async ( } return '已超出最大循环次数'; -}; \ No newline at end of file +}; diff --git a/packages/agent-core/src/harness/session/store-types.ts b/packages/agent-core/src/harness/session/store-types.ts index a3a419d..02fb4cc 100644 --- a/packages/agent-core/src/harness/session/store-types.ts +++ b/packages/agent-core/src/harness/session/store-types.ts @@ -48,6 +48,8 @@ export interface SessionStoreLike { options: { limit?: number }, ): EntryLike[]; listSessions(options: { limit?: number }): SessionLike[]; + deleteEntries(sessionId: string, entryIds: string[]): number; + deleteSession(sessionId: string): boolean; } /** 会话基本信息的最小形状 */ diff --git a/packages/agent-core/src/web/protocol.ts b/packages/agent-core/src/web/protocol.ts index d311d0e..1124373 100644 --- a/packages/agent-core/src/web/protocol.ts +++ b/packages/agent-core/src/web/protocol.ts @@ -4,6 +4,7 @@ import type { WebExport } from "./export.js"; export type ChatSendPayload = { type: "chat.send"; input: string; + sessionId?: string; mode?: AgentMode; objective?: string; constraints?: string[]; @@ -13,11 +14,14 @@ export type ChatSendPayload = { /** 客户端发给 WebSocket 服务的请求。 */ export type WebSocketClientMessage = | { type: "sessions.list" } + | { type: "sessions.create" } + | { type: "sessions.delete"; sessionId: string; entryIds?: string[] } | ChatSendPayload; /** 服务端发给客户端的响应。 */ export type WebSocketServerMessage = | { type: "sessions.snapshot"; data: WebExport } + | { type: "sessions.created"; sessionId: string } | { type: "chat.status"; status: "idle" | "running" | "success" | "error"; @@ -41,16 +45,50 @@ export const parseClientMessage = ( return { type: "sessions.list" }; } + if (message.type === "sessions.create") { + return { type: "sessions.create" }; + } + + if (message.type === "sessions.delete") { + const rawSessionId = (message as { sessionId?: unknown }).sessionId; + if (typeof rawSessionId !== "string" || !rawSessionId.trim()) { + return null; + } + const rawEntryIds = (message as { entryIds?: unknown }).entryIds; + if ( + rawEntryIds !== undefined && + (!Array.isArray(rawEntryIds) || + rawEntryIds.some( + (id) => typeof id !== "string" || !id.trim(), + )) + ) { + return null; + } + return { + type: "sessions.delete", + sessionId: rawSessionId.trim(), + entryIds: rawEntryIds?.map((id) => id.trim()), + }; + } + if (message.type === "chat.send") { const rawInput = (message as { input?: unknown }).input; if (typeof rawInput !== "string" || !rawInput.trim()) { return null; } + const rawSessionId = (message as { sessionId?: unknown }).sessionId; + if ( + rawSessionId !== undefined && + (typeof rawSessionId !== "string" || !rawSessionId.trim()) + ) { + return null; + } const rawMode = (message as { mode?: unknown }).mode; const mode: AgentMode = rawMode === "plan" ? "plan" : "build"; return { type: "chat.send", input: rawInput.trim(), + sessionId: rawSessionId?.trim(), mode, }; } diff --git a/packages/agent-core/src/web/server.ts b/packages/agent-core/src/web/server.ts index 356e972..d5de505 100644 --- a/packages/agent-core/src/web/server.ts +++ b/packages/agent-core/src/web/server.ts @@ -2,6 +2,7 @@ import { createServer, type IncomingMessage, type ServerResponse } from "node:ht import { readFile, stat } from "node:fs/promises"; import { extname, join, resolve, sep } from "node:path"; import { fileURLToPath } from "node:url"; +import { randomUUID } from "node:crypto"; import { WebSocket, WebSocketServer } from "ws"; import { runLoop } from "../harness/runtime/run-loop.js"; import { createTaskState } from "../harness/core/state.js"; @@ -170,6 +171,35 @@ export const startWebServer = async ( return; } + if (message.type === "sessions.create") { + const session = store.createSession({ + id: randomUUID(), + cwd: process.cwd(), + }); + sendJson(socket, { + type: "sessions.created", + sessionId: session.id, + }); + broadcast(wss, { + type: "sessions.snapshot", + data: buildWebExport(store), + }); + return; + } + + if (message.type === "sessions.delete") { + if (message.entryIds && message.entryIds.length > 0) { + store.deleteEntries(message.sessionId, message.entryIds); + } else { + store.deleteSession(message.sessionId); + } + broadcast(wss, { + type: "sessions.snapshot", + data: buildWebExport(store), + }); + return; + } + if (message.type === "chat.send") { if (isRunning) { sendJson(socket, { @@ -189,6 +219,7 @@ export const startWebServer = async ( try { const task = createTaskState(message.input, { + id: message.sessionId, mode: message.mode ?? "build", objective: message.objective, constraints: message.constraints, diff --git a/packages/client/dist/index.html b/packages/client/dist/index.html index c3ad76e..888ab4f 100644 --- a/packages/client/dist/index.html +++ b/packages/client/dist/index.html @@ -4,8 +4,8 @@ Call Code History - - + +
diff --git a/packages/client/src/App.tsx b/packages/client/src/App.tsx index 2ed643f..0f3e96d 100644 --- a/packages/client/src/App.tsx +++ b/packages/client/src/App.tsx @@ -6,6 +6,7 @@ import { connectLiveExport, type LiveExportConnection } from "./ws"; import type { AgentMode, ChatStatusMessage, Filter, Theme, WebExport } from "./types"; import { filterSessions } from "./utils"; import { ParticleField } from "./components/ParticleField"; +import { ConfirmDialog } from "./components/ConfirmDialog"; const THEME_KEY = "call-code-theme"; @@ -14,6 +15,10 @@ const initialTheme = (): Theme => { return stored === "light" ? "light" : "dark"; }; +type PendingDelete = + | { kind: "session"; sessionId: string } + | { kind: "entry"; sessionId: string; entryId: string }; + export default function App() { const [data, setData] = useState(null); const [loadState, setLoadState] = useState<"loading" | "ready" | "error">( @@ -26,6 +31,9 @@ export default function App() { const [activeId, setActiveId] = useState(null); const [query, setQuery] = useState(""); const [filter, setFilter] = useState("all"); + const [pendingDelete, setPendingDelete] = useState( + null, + ); const connectionRef = useRef(null); @@ -94,7 +102,42 @@ export default function App() { if (!connectionRef.current) { return false; } - return connectionRef.current.sendMessage(payload); + return connectionRef.current.sendMessage({ + ...payload, + sessionId: activeSession?.id, + }); + }; + + const handleNewTopic = async () => { + const sessionId = await connectionRef.current?.createSession(); + if (sessionId) { + setActiveId(sessionId); + setQuery(""); + setFilter("all"); + } + }; + + const requestDeleteEntry = (sessionId: string, entryId: string) => { + setPendingDelete({ kind: "entry", sessionId, entryId }); + }; + + const requestDeleteSession = (sessionId: string) => { + setPendingDelete({ kind: "session", sessionId }); + }; + + const confirmDelete = () => { + if (!pendingDelete) { + return; + } + + if (pendingDelete.kind === "entry") { + connectionRef.current?.deleteMessages(pendingDelete.sessionId, [ + pendingDelete.entryId, + ]); + } else { + connectionRef.current?.deleteMessages(pendingDelete.sessionId); + } + setPendingDelete(null); }; const sessions = useMemo(() => data?.sessions ?? [], [data]); @@ -104,10 +147,10 @@ export default function App() { ); const activeSession = useMemo( () => - filteredSessions.find((session) => session.id === activeId) ?? - filteredSessions[0] ?? + sessions.find((session) => session.id === activeId) ?? + sessions[0] ?? null, - [filteredSessions, activeId], + [sessions, activeId], ); return ( @@ -127,6 +170,8 @@ export default function App() { query={query} onSelect={setActiveId} onQueryChange={setQuery} + onDeleteSession={requestDeleteSession} + onNewTopic={handleNewTopic} /> + setPendingDelete(null)} + /> ); } diff --git a/packages/client/src/components/ConfirmDialog.tsx b/packages/client/src/components/ConfirmDialog.tsx new file mode 100644 index 0000000..be4ad42 --- /dev/null +++ b/packages/client/src/components/ConfirmDialog.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef } from 'react'; + +interface ConfirmDialogProps { + open: boolean; + title: string; + description: string; + confirmLabel?: string; + onConfirm: () => void; + onCancel: () => void; +} + +export function ConfirmDialog({ + open, + title, + description, + confirmLabel = '删除', + onConfirm, + onCancel, +}: ConfirmDialogProps) { + const cancelRef = useRef(null); + + useEffect(() => { + if (!open) { + return; + } + + cancelRef.current?.focus(); + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'Escape') { + onCancel(); + } + }; + window.addEventListener('keydown', handleKeyDown); + return () => window.removeEventListener('keydown', handleKeyDown); + }, [open, onCancel]); + + if (!open) { + return null; + } + + return ( +
+
event.stopPropagation()} + > +
+ +
+

+ {title} +

+

+ {description} +

+
+
+ +
+ + +
+
+
+ ); +} diff --git a/packages/client/src/components/MainPanel.tsx b/packages/client/src/components/MainPanel.tsx index bcc55ad..35f75b2 100644 --- a/packages/client/src/components/MainPanel.tsx +++ b/packages/client/src/components/MainPanel.tsx @@ -9,6 +9,7 @@ interface MainPanelProps { onFilterChange: (filter: Filter) => void; chatStatus: ChatStatusMessage; onSendMessage: (payload: { input: string; mode: AgentMode }) => boolean; + onDeleteEntry: (sessionId: string, entryId: string) => void; } const filters: Array<{ value: Filter; label: string }> = [ @@ -131,6 +132,7 @@ export function MainPanel({ onFilterChange, chatStatus, onSendMessage, + onDeleteEntry, }: MainPanelProps) { const [input, setInput] = useState(""); const [mode, setMode] = useState("build"); @@ -227,7 +229,11 @@ export function MainPanel({ ) : (
{entries.map((entry) => ( - + onDeleteEntry(session.id, entry.id)} + /> ))} {session.facts ? : null} {session.records ? ( diff --git a/packages/client/src/components/MessageItem.tsx b/packages/client/src/components/MessageItem.tsx index f1c2cf6..d9bfe7e 100644 --- a/packages/client/src/components/MessageItem.tsx +++ b/packages/client/src/components/MessageItem.tsx @@ -4,6 +4,7 @@ import type { EntryRole } from '../types'; interface MessageItemProps { entry: WebEntry; + onDelete?: () => void; } const roleMeta: Record = { @@ -13,14 +14,14 @@ const roleMeta: Record 320; return ( -
+
{/* 角色指示点 */}
{/* 内容 */} diff --git a/packages/client/src/components/Sidebar.tsx b/packages/client/src/components/Sidebar.tsx index 0ddcb19..355273f 100644 --- a/packages/client/src/components/Sidebar.tsx +++ b/packages/client/src/components/Sidebar.tsx @@ -7,6 +7,8 @@ interface SidebarProps { query: string; onSelect: (id: string) => void; onQueryChange: (query: string) => void; + onDeleteSession: (id: string) => void; + onNewTopic: () => void; } export function Sidebar({ @@ -15,6 +17,8 @@ export function Sidebar({ query, onSelect, onQueryChange, + onDeleteSession, + onNewTopic, }: SidebarProps) { return (
- + + + + + + + + ); })} diff --git a/packages/client/src/global.css b/packages/client/src/global.css index 8276e2d..93b1a3b 100644 --- a/packages/client/src/global.css +++ b/packages/client/src/global.css @@ -27,6 +27,10 @@ --segment-active: #ffffff; --segment-active-text: #111827; --segment-active-shadow: 0 1px 3px rgb(0 0 0 / 0.1); + + --danger: #dc2626; + --danger-hover: #ef4444; + --danger-soft: rgb(220 38 38 / 0.12); } :root.dark, @@ -287,3 +291,60 @@ body { .search-box:focus-within { border-color: var(--text-primary) !important; } + +.sidebar-new-topic:hover { + border-color: var(--text-primary); + color: var(--text-primary); +} + +.sidebar-new-topic:focus-visible { + outline: 2px solid var(--text-primary); + outline-offset: 2px; +} + +.confirm-overlay { + animation: confirm-overlay-in 0.16s ease-out; +} + +.confirm-dialog { + animation: confirm-dialog-in 0.18s ease-out; +} + +.dialog-cancel { + border: 1px solid var(--chip-border); + background: var(--chip-bg); + color: var(--text-secondary); +} + +.dialog-cancel:hover { + color: var(--text-primary); +} + +.dialog-cancel:focus-visible { + outline: 2px solid var(--text-primary); + outline-offset: 2px; +} + +.btn-danger { + background: var(--danger); + color: #fff; +} + +.btn-danger:hover { + background: var(--danger-hover); +} + +.btn-danger:focus-visible { + outline: 2px solid var(--danger); + outline-offset: 2px; +} + +@keyframes confirm-overlay-in { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes confirm-dialog-in { + from { opacity: 0; transform: translateY(8px) scale(0.98); } + to { opacity: 1; transform: translateY(0) scale(1); } +} diff --git a/packages/client/src/utils.ts b/packages/client/src/utils.ts index 044825b..2cf552b 100644 --- a/packages/client/src/utils.ts +++ b/packages/client/src/utils.ts @@ -23,21 +23,31 @@ export const entryText = (entry: WebEntry): string => { return ''; }; +const SESSION_TITLE_MAX_CHARS = 20; + +const compactTitle = (value: string): string => { + const normalized = value.replace(/\s+/g, ' ').trim(); + if (normalized.length <= SESSION_TITLE_MAX_CHARS) { + return normalized; + } + return normalized.slice(0, SESSION_TITLE_MAX_CHARS); +}; + export const getSessionTitle = (session: WebSession): string => { const firstUser = session.entries.find((entry) => entryRole(entry) === 'user'); if (firstUser) { const text = entryText(firstUser).trim(); if (text) { - return text; + return compactTitle(text); } } const objective = session.metadata?.objective; if (typeof objective === 'string' && objective.trim()) { - return objective; + return compactTitle(objective); } - return session.id; + return '新会话'; }; export const getSessionMode = (session: WebSession): string => { diff --git a/packages/client/src/ws.ts b/packages/client/src/ws.ts index 0892f6c..a40926d 100644 --- a/packages/client/src/ws.ts +++ b/packages/client/src/ws.ts @@ -2,6 +2,7 @@ export { connectLiveExport, isWebExport, parseChatStatusMessage, + parseCreatedSessionMessage, parseSnapshotMessage, } from '@call-code/server/client'; export type { diff --git a/packages/server/src/client.ts b/packages/server/src/client.ts index c8dee28..13a985b 100644 --- a/packages/server/src/client.ts +++ b/packages/server/src/client.ts @@ -145,6 +145,25 @@ export const parseChatStatusMessage = ( }; }; +/** 从服务端消息中提取新建会话 ID,结构非法时返回 null。 */ +export const parseCreatedSessionMessage = ( + value: unknown, +): string | null => { + if (!value || typeof value !== 'object') { + return null; + } + + const message = value as { type?: unknown; sessionId?: unknown }; + if ( + message.type !== 'sessions.created' || + typeof message.sessionId !== 'string' || + !message.sessionId.trim() + ) { + return null; + } + return message.sessionId.trim(); +}; + const resolveWsUrl = (override?: string): string => { if (override) { return override; @@ -170,7 +189,13 @@ export interface LiveExportOptions { export interface LiveExportConnection { ready: Promise; - sendMessage: (payload: { input: string; mode?: AgentMode }) => boolean; + sendMessage: (payload: { + input: string; + mode?: AgentMode; + sessionId?: string; + }) => boolean; + createSession: () => Promise; + deleteMessages: (sessionId: string, entryIds?: string[]) => boolean; refresh: () => void; close(): void; } @@ -197,6 +222,7 @@ export const connectLiveExport = ( let retryTimer: ReturnType | null = null; let attempt = 0; let readySettled = false; + let createResolver: ((sessionId: string | null) => void) | null = null; let resolveReady: (value: WebExport | null) => void = () => undefined; const ready = new Promise((resolve) => { @@ -228,6 +254,10 @@ export const connectLiveExport = ( const teardownSocket = () => { clearTimers(); + if (createResolver) { + createResolver(null); + createResolver = null; + } if (!socket) { return; } @@ -312,6 +342,13 @@ export const connectLiveExport = ( return; } + const createdSessionId = parseCreatedSessionMessage(parsed); + if (createdSessionId) { + createResolver?.(createdSessionId); + createResolver = null; + return; + } + const data = parseSnapshotMessage(parsed); if (!data) { return; @@ -351,6 +388,34 @@ export const connectLiveExport = ( type: 'chat.send', input: payload.input, mode: payload.mode ?? 'build', + ...(payload.sessionId ? { sessionId: payload.sessionId } : {}), + }), + ); + return true; + }, + createSession() { + if ( + createResolver || + !socket || + socket.readyState !== WebSocket.OPEN + ) { + return Promise.resolve(null); + } + const current = socket; + return new Promise((resolve) => { + createResolver = resolve; + current.send(JSON.stringify({ type: 'sessions.create' })); + }); + }, + deleteMessages(sessionId, entryIds) { + if (!socket || socket.readyState !== WebSocket.OPEN) { + return false; + } + socket.send( + JSON.stringify({ + type: 'sessions.delete', + sessionId, + ...(entryIds && entryIds.length > 0 ? { entryIds } : {}), }), ); return true; diff --git a/packages/session-sqlite/src/store.ts b/packages/session-sqlite/src/store.ts index dffb15b..9f7b2b5 100644 --- a/packages/session-sqlite/src/store.ts +++ b/packages/session-sqlite/src/store.ts @@ -126,6 +126,21 @@ export class SessionStore { takeLease: ReturnType; getLease: ReturnType; deleteLease: ReturnType; + deleteEntryById: ReturnType; + deleteBranchEntryById: ReturnType; + deleteLaneMovesByLeaf: ReturnType; + deleteBranchTipByTip: ReturnType; + deleteSessionEntries: ReturnType; + deleteSessionBranchEntries: ReturnType; + deleteSessionLaneMoves: ReturnType; + deleteSessionBranchTips: ReturnType; + deleteSessionFacts: ReturnType; + deleteSessionLanes: ReturnType; + deleteSessionRecords: ReturnType; + deleteSessionStats: ReturnType; + deleteSessionSequences: ReturnType; + deleteSessionLeases: ReturnType; + deleteSessionRow: ReturnType; }; constructor(config: SessionStoreConfig = {}) { @@ -300,6 +315,51 @@ export class SessionStore { deleteLease: db.prepare(` DELETE FROM leases WHERE session_id = :sessionId AND owner_id = :ownerId `), + deleteEntryById: db.prepare(` + DELETE FROM entries WHERE session_id = :sessionId AND id = :id + `), + deleteBranchEntryById: db.prepare(` + DELETE FROM branch_entries WHERE session_id = :sessionId AND entry_id = :id + `), + deleteLaneMovesByLeaf: db.prepare(` + DELETE FROM lane_moves WHERE session_id = :sessionId AND leaf_id = :id + `), + deleteBranchTipByTip: db.prepare(` + DELETE FROM branch_tips WHERE session_id = :sessionId AND tip_id = :tipId + `), + deleteSessionEntries: db.prepare(` + DELETE FROM entries WHERE session_id = :sessionId + `), + deleteSessionBranchEntries: db.prepare(` + DELETE FROM branch_entries WHERE session_id = :sessionId + `), + deleteSessionLaneMoves: db.prepare(` + DELETE FROM lane_moves WHERE session_id = :sessionId + `), + deleteSessionBranchTips: db.prepare(` + DELETE FROM branch_tips WHERE session_id = :sessionId + `), + deleteSessionFacts: db.prepare(` + DELETE FROM facts WHERE session_id = :sessionId + `), + deleteSessionLanes: db.prepare(` + DELETE FROM lanes WHERE session_id = :sessionId + `), + deleteSessionRecords: db.prepare(` + DELETE FROM records WHERE session_id = :sessionId + `), + deleteSessionStats: db.prepare(` + DELETE FROM session_stats WHERE session_id = :sessionId + `), + deleteSessionSequences: db.prepare(` + DELETE FROM session_sequences WHERE session_id = :sessionId + `), + deleteSessionLeases: db.prepare(` + DELETE FROM leases WHERE session_id = :sessionId + `), + deleteSessionRow: db.prepare(` + DELETE FROM sessions WHERE id = :sessionId + `), }; } @@ -552,6 +612,120 @@ export class SessionStore { return result!; } + /** + * 删除会话内指定的消息及所有后代条目,并同步清理泳道、分支指针和统计。 + */ + deleteEntries(sessionId: string, entryIds: string[]): number { + const entries = this.getEntries(sessionId, { limit: 100000 }); + if (entries.length === 0 || entryIds.length === 0) { + return 0; + } + + const byId = new Map(entries.map((entry) => [entry.id, entry])); + const toDelete = new Set(entryIds.filter((id) => byId.has(id))); + if (toDelete.size === 0) { + return 0; + } + + // 同分支下的后代随父条目一起删除;其他分支保持独立,避免误删分叉消息 + const entryBranch = new Map(); + for (const tip of this.listBranchTips(sessionId)) { + for (const item of this.getBranchEntries(sessionId, tip.branchId)) { + entryBranch.set(item.entryId, tip.branchId); + } + } + let expanded = true; + while (expanded) { + expanded = false; + for (const entry of entries) { + if ( + entry.parentId && + toDelete.has(entry.parentId) && + !toDelete.has(entry.id) && + entryBranch.get(entry.id) === entryBranch.get(entry.parentId) + ) { + toDelete.add(entry.id); + expanded = true; + } + } + } + + this.transaction(() => { + for (const tip of this.listBranchTips(sessionId)) { + if (!toDelete.has(tip.tipId)) { + continue; + } + const replacement = this.findRemainingAncestor(byId, toDelete, tip.tipId); + if (replacement) { + this.statements.updateBranchTip.run({ + ':sessionId': sessionId, + ':branchId': tip.branchId, + ':tipId': replacement, + }); + } else { + this.statements.deleteBranchTipByTip.run({ + ':sessionId': sessionId, + ':tipId': tip.tipId, + }); + } + } + + for (const lane of this.listLanes(sessionId)) { + if (!lane.leafId || !toDelete.has(lane.leafId)) { + continue; + } + this.statements.updateLane.run({ + ':sessionId': sessionId, + ':lane': lane.lane, + ':leafId': this.findRemainingAncestor(byId, toDelete, lane.leafId), + }); + } + + for (const id of toDelete) { + this.statements.deleteBranchEntryById.run({ ':sessionId': sessionId, ':id': id }); + this.statements.deleteLaneMovesByLeaf.run({ ':sessionId': sessionId, ':id': id }); + this.statements.deleteEntryById.run({ ':sessionId': sessionId, ':id': id }); + } + + const stats = this.getStats(sessionId); + this.statements.upsertStats.run({ + ':sessionId': sessionId, + ':messageCount': Math.max(0, stats.messageCount - toDelete.size), + ':cachedTokens': stats.cachedTokens, + ':uncachedTokens': stats.uncachedTokens, + ':totalTokens': stats.totalTokens, + ':costTotal': stats.costTotal, + }); + }); + + return toDelete.size; + } + + /** + * 删除整个会话及其关联的条目、记录、泳道、事实、统计和租约。 + */ + deleteSession(sessionId: string): boolean { + if (!this.getSession(sessionId)) { + return false; + } + + this.transaction(() => { + this.statements.deleteSessionBranchTips.run({ ':sessionId': sessionId }); + this.statements.deleteSessionLaneMoves.run({ ':sessionId': sessionId }); + this.statements.deleteSessionFacts.run({ ':sessionId': sessionId }); + this.statements.deleteSessionBranchEntries.run({ ':sessionId': sessionId }); + this.statements.deleteSessionRecords.run({ ':sessionId': sessionId }); + this.statements.deleteSessionLanes.run({ ':sessionId': sessionId }); + this.statements.deleteSessionEntries.run({ ':sessionId': sessionId }); + this.statements.deleteSessionStats.run({ ':sessionId': sessionId }); + this.statements.deleteSessionSequences.run({ ':sessionId': sessionId }); + this.statements.deleteSessionLeases.run({ ':sessionId': sessionId }); + this.statements.deleteSessionRow.run({ ':sessionId': sessionId }); + }); + + return true; + } + /** * 追加一条运行记录,记录会与条目共享同一个会话序号。 */ @@ -808,6 +982,22 @@ export class SessionStore { return seq; } + /** 查找删除集合外最近的祖先条目,找不到时返回 null */ + private findRemainingAncestor( + byId: Map, + toDelete: Set, + id: string, + ): string | null { + let parentId = byId.get(id)?.parentId ?? null; + while (parentId) { + if (!toDelete.has(parentId)) { + return parentId; + } + parentId = byId.get(parentId)?.parentId ?? null; + } + return null; + } + /** 根据父条目推导它所属的分支,找不到时返回 null */ private findEntryBranch(sessionId: string, entryId: string): string | null { const row = this.db diff --git a/packages/session-sqlite/tsconfig.tsbuildinfo b/packages/session-sqlite/tsconfig.tsbuildinfo index e0cd08e..497bc3b 100644 --- a/packages/session-sqlite/tsconfig.tsbuildinfo +++ b/packages/session-sqlite/tsconfig.tsbuildinfo @@ -1 +1 @@ -{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2025.float16.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/typescript/lib/lib.es2020.full.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react/jsx-runtime.d.ts","./src/schema.ts","./src/types.ts","./src/store.ts","./src/index.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/blob.d.ts","../../node_modules/@types/node/web-globals/console.d.ts","../../node_modules/@types/node/web-globals/crypto.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/encoding.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/undici-types/utility.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client-stats.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/round-robin-pool.d.ts","../../node_modules/undici-types/h2c-client.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-call-history.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/snapshot-agent.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/socks5-proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/cache-interceptor.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/importmeta.d.ts","../../node_modules/@types/node/web-globals/messaging.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/performance.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/web-globals/streams.d.ts","../../node_modules/@types/node/web-globals/timers.d.ts","../../node_modules/@types/node/web-globals/url.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/inspector/promises.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/path/posix.d.ts","../../node_modules/@types/node/path/win32.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/quic.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/iter.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/test/reporters.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/util/types.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/zlib/iter.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/vite/types/hmrpayload.d.ts","../../node_modules/vite/types/customevent.d.ts","../../node_modules/vite/types/hot.d.ts","../../node_modules/vite/types/importglob.d.ts","../../node_modules/vite/types/importmeta.d.ts","../../node_modules/vite/client.d.ts"],"fileIdsList":[[64,125,126,128,136,140,143,145,146,147,159],[64,127,128,136,140,143,145,146,147,159],[128,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,168],[64,128,129,134,136,139,140,143,145,146,147,149,159,164,177],[64,128,129,130,136,139,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159],[64,128,131,136,140,143,145,146,147,159,178],[64,128,132,133,136,140,143,145,146,147,150,159],[64,128,133,136,140,143,145,146,147,159,164,174],[64,128,134,136,139,140,143,145,146,147,149,159],[64,127,128,135,136,140,143,145,146,147,159],[64,128,136,137,140,143,145,146,147,159],[64,128,136,138,139,140,143,145,146,147,159],[64,127,128,136,139,140,143,145,146,147,159],[64,128,136,139,140,141,143,145,146,147,159,164,177],[64,128,136,139,140,141,143,145,146,147,159,164,166,168],[64,115,128,136,139,140,142,143,145,146,147,149,159,164,177],[64,128,136,139,140,142,143,145,146,147,149,159,164,174,177],[64,128,136,140,142,143,144,145,146,147,159,164,174,177],[62,63,64,65,66,67,68,69,70,71,72,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185],[64,128,136,139,140,143,145,146,147,159],[64,128,136,140,143,145,147,159],[64,128,136,140,143,145,146,147,148,159,177],[64,128,136,139,140,143,145,146,147,149,159,164],[64,128,136,140,143,145,146,147,150,159],[64,128,136,140,143,145,146,147,151,159],[64,128,136,139,140,143,145,146,147,154,159],[64,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184],[64,128,136,140,143,145,146,147,156,159],[64,128,136,140,143,145,146,147,157,159],[64,128,133,136,140,143,145,146,147,149,159,168],[64,128,136,139,140,143,145,146,147,159,160],[64,128,136,140,143,145,146,147,159,161,178,181],[64,128,136,139,140,143,145,146,147,159,164,167,168],[64,128,136,140,143,145,146,147,159,165,168],[64,128,136,140,143,145,146,147,159,166],[64,128,136,140,143,145,146,147,159,168,178],[64,128,136,140,143,145,146,147,159,169],[64,125,128,136,140,143,145,146,147,159,164,171,177],[64,128,136,140,143,145,146,147,159,164,170],[64,128,136,139,140,143,145,146,147,159,172,173],[64,128,136,140,143,145,146,147,159,172,173],[64,128,133,136,140,143,145,146,147,149,159,164,174],[64,128,136,140,143,145,146,147,159,175],[64,128,136,140,143,145,146,147,149,159,176],[64,128,136,140,142,143,145,146,147,157,159,177],[64,128,136,140,143,145,146,147,159,178,179],[64,128,133,136,140,143,145,146,147,159,179],[64,128,136,140,143,145,146,147,159,164,180],[64,128,136,140,143,145,146,147,148,159,181],[64,128,136,140,143,145,146,147,159,182],[64,128,131,136,140,143,145,146,147,159],[64,128,133,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,178],[64,115,128,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,159,183],[64,128,136,140,143,145,146,147,154,159],[64,128,136,140,143,145,146,147,159,173],[64,115,128,136,139,140,141,143,145,146,147,154,159,164,168,177,180,181,183],[64,128,136,140,143,145,146,147,159,164,184],[64,128,136,140,143,145,146,147,159,166,185],[54,55,64,128,136,140,143,145,146,147,159],[56,64,128,136,140,143,145,146,147,159],[64,79,82,85,86,128,136,140,143,145,146,147,159,177],[64,82,128,136,140,143,145,146,147,159,164,177],[64,82,86,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,159,164],[64,76,128,136,140,143,145,146,147,159],[64,80,128,136,140,143,145,146,147,159],[64,78,79,82,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,149,159,174],[64,128,136,140,143,145,146,147,159,186],[64,76,128,136,140,143,145,146,147,159,186],[64,78,82,128,136,140,143,145,146,147,149,159,177],[64,73,74,75,77,81,128,136,139,140,143,145,146,147,159,164,177],[64,82,91,99,128,136,140,143,145,146,147,159],[64,74,80,128,136,140,143,145,146,147,159],[64,82,109,110,128,136,140,143,145,146,147,159],[64,74,77,82,128,136,140,143,145,146,147,159,168,177,186],[64,82,128,136,140,143,145,146,147,159],[64,78,82,128,136,140,143,145,146,147,159,177],[64,73,128,136,140,143,145,146,147,159],[64,76,77,78,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,128,136,140,143,145,146,147,159],[64,82,102,105,128,136,140,143,145,146,147,159],[64,82,91,92,93,128,136,140,143,145,146,147,159],[64,80,82,92,94,128,136,140,143,145,146,147,159],[64,81,128,136,140,143,145,146,147,159],[64,74,76,82,128,136,140,143,145,146,147,159],[64,82,86,92,94,128,136,140,143,145,146,147,159],[64,86,128,136,140,143,145,146,147,159],[64,80,82,85,128,136,140,143,145,146,147,159,177],[64,74,78,82,91,128,136,140,143,145,146,147,159],[64,82,102,128,136,140,143,145,146,147,159],[64,94,128,136,140,143,145,146,147,159],[64,74,78,82,86,128,136,140,143,145,146,147,159],[64,76,82,109,128,136,140,143,145,146,147,159,168,183,186],[64,128,136,140,143,145,146,147,159,191],[64,128,136,140,143,145,146,147,159,187],[64,128,136,140,143,145,146,147,159,188],[64,128,136,140,143,145,146,147,159,189,190],[57,58,59,60,64,128,136,140,143,145,146,147,159],[57,64,128,136,140,143,145,146,147,159],[57,58,59,64,128,133,136,140,143,145,146,147,151,159,163]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"577cc49be1979782a5a695b46049566d123e2a75382f10bb82f2e5c87e29059a","impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","impliedFormat":1},{"version":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","impliedFormat":1},"8fbe06618f17d801507119ff16c1fbc37977a0d0b06595b197ed518e948c7edc","5ed797667a4ab2c2027605ad16143d9fcc8d1b29e6e0223f12f6d78a579f5ce2","56650ba1a26027a8fe602d150581eb6def6aedebafa29826c57380e8055702fb","e47e63ee8d77c3b982c7e09219e8748e2ebe7b3a5545cc7fad32cb20d00c9123",{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc2110f7decca6bfb9392e30421cfa1436479e4a6756e8fec6cbc22625d4f881","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"14e9acf826baba0ef4b5665704084896e7bcc06f65a9ab13af7e93d27d6b7069","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"21adf13435b9b748529c8cedf80f884e5130b9684188120a686cd2b26a2059c7","impliedFormat":1},{"version":"eec76bf6b9346f3f95fa402621b889489e96930e72295b0369022f332e9b4a6a","impliedFormat":1},{"version":"0ecd58f413f9bc3b7d4383eae31b0c8fc576985cd7404d6f99f8c643543ade74","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"d33ce35e3f9cfcc1d94eca415bdd3bde94d5b153ffdd33e6c4455c029986c630","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"98498b101803bb3dde9f76a56e65c14b75db1cc8bec5f4db72be541570f74fc5","impliedFormat":1},{"version":"4dc59f6e1dbf3d5f66660fceabe6c174d3261b37b696ae1854f0dbaf255fc753","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"436d7b4543b340b0f3eef4310d524242e41369b9652aa9c70428767c4dcac455","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"114f493b30f364255290472111b5a4791d5902c308645670cd0401429cbc6930","impliedFormat":1},{"version":"c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"d2ae155afe8a01cc0ae612d99117cf8ef16692ba7c4366590156fdec1bcf2d8c","impliedFormat":1},{"version":"3f5e5d9be35913db9fea42a63f3df0b7e3c8703b97670a2125587b4dbbd56d7c","impliedFormat":1},{"version":"c8b8968311ec4e5e97b7b5fb8a65efaba455db9bdcfd7fff7fb15f6e317bfba0","impliedFormat":1},{"version":"57c23df0b5f7a8e26363a3849b0bc7763f6b241207157c8e40089d1df4116f35","affectsGlobalScope":true,"impliedFormat":1},{"version":"3b8bc0c17b54081b0878673989216229e575d67a10874e84566a21025a2461ee","impliedFormat":1},{"version":"5b0db5a58b73498792a29bfebc333438e61906fef75da898b410e24e52229e6f","impliedFormat":1},{"version":"dbe055b2b29a7bab2c1ca8f259436306adb43f469dca7e639a02cd3695d3f621","impliedFormat":1},{"version":"1678b04557dca52feab73cc67610918a7f5e25bfdba3e7fa081acd625d93106d","impliedFormat":1},{"version":"aecbf1d9e6a18dab7d92ef8a89a1444b47e1eb6134cb2bb776a26d55ff58c29a","impliedFormat":1},{"version":"2ea729503db9793f2691162fec3dd1118cab62e96d025f8eeb376d43ec293395","impliedFormat":1},{"version":"9ec87fea42b92894b0f209931a880789d43c3397d09dd99c631ae40a2f7071d1","impliedFormat":1},{"version":"c68e88cdfadfb6c8ba5fc38e58a3a166b0beae77b1f05b7d921150a32a5ffb8d","impliedFormat":1},{"version":"2bc7aa4fba46df0bd495425a7c8201437a7d465f83854fac859df2d67f664df3","impliedFormat":1},{"version":"41d17e1ad9a002feb11c8cdd2777e5bbc0cdb1e3f595d237e4dded0b6949983b","impliedFormat":1},{"version":"1fede9296beac11ce8e6b425396a1791f64341f2be85deebb6286faf6e16306e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce697b6a251d9cad53998c7fd3098072df883b525ec45d83530e434dc6d80dc6","impliedFormat":1},{"version":"53ff1acab080a8318445df2c7da35ec47489dc83be40569e39c7c750046e217f","impliedFormat":1},{"version":"0eb5d0cbf09de5d34542b977fd6a933bb2e0817bffe8e1a541b2f1ad1b9af1ff","impliedFormat":1},{"version":"3db996ecdee7aabecc5385976cc07eb66216034a273c07b17d1a85292e9bab0c","impliedFormat":1},{"version":"2c2bdaa1d8ead9f68628d6d9d250e46ee8e81aa4898b4769a36956ae15e060fe","impliedFormat":1},{"version":"c32c840c62d8bd7aeb3147aa6754cd2d922b990a6b6634530cb2ebdce5adc8e9","impliedFormat":1},{"version":"5ff4433a2deae4f85ab1377e90a7554ce6b47ae51c69a84ca30a6e22fae85834","impliedFormat":1},{"version":"82b91e4e42e6c41bc7fc1b6c2dc5eba6a2ba98375eb1f210e6ff6bba2d54177e","impliedFormat":1},{"version":"c1fa52b3d014001e8662fa2669d90ea15373958a288e3b83a3b621733d25292a","affectsGlobalScope":true,"impliedFormat":1},{"version":"cbed824fec91efefc7bbdcb8b43d1a531fdbebd0e2ef19481501ff365a93cb70","impliedFormat":1},{"version":"d0716593b3f2b0451bcf0c24cfa86dec2235c325c89f201934248b7c742715fc","impliedFormat":1},{"version":"ec501101c2a96133a6c695f934c8f6642149cc728571b29cbb7b770984c1088e","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"2991bca2cc0f0628a278df2a2ccdb8d6cbcb700f3761abbed62bba137d5b1790","impliedFormat":1},{"version":"47d480d0c84b0f6215f1f817e0939b247ebf5217b6784846b96fa6ab710ce99d","affectsGlobalScope":true,"impliedFormat":1},{"version":"230763250f20449fa7b3c9273e1967adb0023dc890d4be1553faca658ee65971","impliedFormat":1},{"version":"c3e9078b60cb329d1221f5878e88cecfa3e74460550e605a58fcfb41a66029ff","impliedFormat":1},{"version":"8413d0641f293aed551c7464615b770d34a02dedede889b9591172287d68e773","impliedFormat":1},{"version":"0ea59f7d3e51440baa64f429253759b106cfcbaf51e474cae606e02265b37cf8","impliedFormat":1},{"version":"bc18a1991ba681f03e13285fa1d7b99b03b67ee671b7bc936254467177543890","impliedFormat":1},{"version":"1b241e24f3227d078c06aeda6e050187ad59a4e591f4467abed44d92b084e08d","impliedFormat":1},{"version":"fa94bbf532b7af8f394b95fa310980d6e20bd2d4c871c6a6cb9f70f03750a44b","impliedFormat":1},{"version":"7fde0e1be5c8be204ffbf428abfcf01da2eb0f130e1bc3f539eb7275f4fd1f58","impliedFormat":1},{"version":"e284328553df5f425a5d33d36a0c3fa66b46af9d097cad6f4d2e8696dfdeb0f1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7fa2214bb0d64701bc6f9ce8cde2fd2ff8c571e0b23065fa04a8a5a6beb91511","impliedFormat":1},{"version":"f36b3fbe2be150a9ca140da48593f21e6a8172004f92ddc549b43efec39f3e54","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"016b29bf4926b80255a108c53a1451717350059da04fcae64d1075f5e93bbb39","impliedFormat":1},{"version":"841983e39bd4cbb463be385e92fda11057cab368bf27100a801c492f1d86cbaa","impliedFormat":1},{"version":"1c4f139ade4f6ebf45463505f8155173e5d7a5305e50e0aae0a5e712d6ff3b48","impliedFormat":1},{"version":"e16b319e5aca1031168de823c4946ff8e29629c4c8cc0ec0fcfe2a8ab2155043","impliedFormat":1},{"version":"e4156ddb25aa0e3b5303d372f26957b36778f0f6bbd4326359269873295e3058","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc1b433a84cae05ddc5672d4823170af78606ad21ecef60dbc4570190cbf1357","impliedFormat":1},{"version":"9d3821bc75c59577e52643324cec92fc2145642e8d17cf7ee07a3181f21d985d","impliedFormat":1},{"version":"7f78cfb2b343838612c192cb251746e3a7c62ac7675726a47e130d9b213f6580","impliedFormat":1},{"version":"201db9cf1687fab1adf5282fcba861f382b32303dc4f67c89d59655e78a25461","impliedFormat":1},{"version":"2c3c5c0f54055e87640f5d233716fd889f3034fc7911d603b642369b0dbeb2a7","impliedFormat":1},{"version":"0a20eaf2e4b1e3c1e1f87f7bccb0c936375b23b022baeea750519b7c9bc6ce83","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"a16b91b27bd6b706c687c88cbc8a7d4ee98e5ed6043026d6b84bda923c0aed67","impliedFormat":1},{"version":"1c9e5b1a17b1fc9b3711fb36e0690421261ab2880f15b145155b5b2ba2ab6c2d","impliedFormat":1},{"version":"99ab6d0d660ce4d21efb52288a39fd35bb3f556980ec5463b1ae8f304a3bbc85","impliedFormat":1},{"version":"6eeded8c7e352be6e0efb83f4935ec752513c4d22043b52522b90849a49a3a11","impliedFormat":1},{"version":"6c1ad90050ffbb151cacc68e2d06ea1a26a945659391e32651f5d42b86fd7f2c","impliedFormat":1},{"version":"afa1c49f8e559e413d57343339db857d2a8159435cf9cf7d4deb41718fff1b88","impliedFormat":1},{"version":"6953d7597831d0860c7034cf4f0419687d263b6b98a4b32e37ce6d49615c36e2","impliedFormat":1},{"version":"64ccfe7dcbcad9b8a661a042ecb2309e41b9ab957f7b8cd070b05583a7821a13","impliedFormat":99},{"version":"98580d6ed68c9cee7f956685abbb6502d70bd92f48175ac807ae6c5e254fadc6","impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","affectsGlobalScope":true,"impliedFormat":99},{"version":"72429cda84a47f092a3bf777bbc4ece9dda492fe0f02324aa7f197f3800089c6","affectsGlobalScope":true,"impliedFormat":99},{"version":"74b98be6ab7723114273526706070ff9885fdf990d5c4e722801addca4d264e1","affectsGlobalScope":true,"impliedFormat":99}],"root":[[58,61]],"options":{"allowImportingTsExtensions":true,"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"jsx":4,"module":99,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"strict":true,"target":7},"referencedMap":[[125,1],[126,1],[127,2],[64,3],[128,4],[129,5],[130,6],[62,7],[131,8],[132,9],[133,10],[134,11],[135,12],[136,13],[137,13],[138,14],[139,15],[140,16],[141,17],[65,7],[63,7],[142,18],[143,19],[144,20],[186,21],[145,22],[146,23],[147,22],[148,24],[149,25],[150,26],[151,27],[152,27],[153,27],[154,28],[155,29],[156,30],[157,31],[158,32],[159,33],[160,33],[161,34],[162,7],[163,7],[164,35],[165,36],[166,37],[167,35],[168,38],[169,39],[170,40],[171,41],[172,42],[173,43],[174,44],[175,45],[176,46],[177,47],[178,48],[179,49],[180,50],[181,51],[182,52],[66,22],[67,7],[68,53],[69,54],[70,7],[71,55],[72,7],[116,56],[117,57],[118,58],[119,58],[120,59],[121,7],[122,4],[123,60],[124,57],[183,61],[184,62],[185,63],[54,7],[56,64],[57,65],[55,7],[51,7],[52,7],[10,7],[8,7],[9,7],[14,7],[13,7],[2,7],[15,7],[16,7],[17,7],[18,7],[19,7],[20,7],[21,7],[22,7],[3,7],[23,7],[24,7],[4,7],[25,7],[29,7],[26,7],[27,7],[28,7],[30,7],[31,7],[32,7],[5,7],[33,7],[34,7],[35,7],[36,7],[6,7],[40,7],[37,7],[38,7],[39,7],[41,7],[7,7],[42,7],[53,7],[47,7],[48,7],[43,7],[44,7],[45,7],[46,7],[49,7],[1,7],[50,7],[12,7],[11,7],[91,66],[104,67],[88,68],[105,69],[114,70],[79,71],[80,72],[78,73],[113,74],[108,75],[112,76],[82,77],[101,78],[81,79],[111,80],[76,81],[77,75],[83,82],[84,7],[90,83],[87,82],[74,84],[115,85],[106,86],[94,87],[93,82],[95,88],[98,89],[92,90],[96,91],[109,74],[85,92],[86,93],[99,94],[75,69],[103,95],[102,82],[89,93],[97,96],[100,97],[107,7],[73,7],[110,98],[192,99],[188,100],[187,7],[189,101],[190,7],[191,102],[61,103],[58,104],[60,105],[59,104]],"affectedFilesPendingEmit":[[61,17],[58,17],[60,17],[59,17]],"emitSignatures":[58,59,60,61],"version":"6.0.3"} \ No newline at end of file +{"fileNames":["../../node_modules/typescript/lib/lib.es5.d.ts","../../node_modules/typescript/lib/lib.es2015.d.ts","../../node_modules/typescript/lib/lib.es2016.d.ts","../../node_modules/typescript/lib/lib.es2017.d.ts","../../node_modules/typescript/lib/lib.es2018.d.ts","../../node_modules/typescript/lib/lib.es2019.d.ts","../../node_modules/typescript/lib/lib.es2020.d.ts","../../node_modules/typescript/lib/lib.dom.d.ts","../../node_modules/typescript/lib/lib.dom.iterable.d.ts","../../node_modules/typescript/lib/lib.dom.asynciterable.d.ts","../../node_modules/typescript/lib/lib.webworker.importscripts.d.ts","../../node_modules/typescript/lib/lib.scripthost.d.ts","../../node_modules/typescript/lib/lib.es2015.core.d.ts","../../node_modules/typescript/lib/lib.es2015.collection.d.ts","../../node_modules/typescript/lib/lib.es2015.generator.d.ts","../../node_modules/typescript/lib/lib.es2015.iterable.d.ts","../../node_modules/typescript/lib/lib.es2015.promise.d.ts","../../node_modules/typescript/lib/lib.es2015.proxy.d.ts","../../node_modules/typescript/lib/lib.es2015.reflect.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.d.ts","../../node_modules/typescript/lib/lib.es2015.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2016.array.include.d.ts","../../node_modules/typescript/lib/lib.es2016.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.arraybuffer.d.ts","../../node_modules/typescript/lib/lib.es2017.date.d.ts","../../node_modules/typescript/lib/lib.es2017.object.d.ts","../../node_modules/typescript/lib/lib.es2017.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2017.string.d.ts","../../node_modules/typescript/lib/lib.es2017.intl.d.ts","../../node_modules/typescript/lib/lib.es2017.typedarrays.d.ts","../../node_modules/typescript/lib/lib.es2018.asyncgenerator.d.ts","../../node_modules/typescript/lib/lib.es2018.asynciterable.d.ts","../../node_modules/typescript/lib/lib.es2018.intl.d.ts","../../node_modules/typescript/lib/lib.es2018.promise.d.ts","../../node_modules/typescript/lib/lib.es2018.regexp.d.ts","../../node_modules/typescript/lib/lib.es2019.array.d.ts","../../node_modules/typescript/lib/lib.es2019.object.d.ts","../../node_modules/typescript/lib/lib.es2019.string.d.ts","../../node_modules/typescript/lib/lib.es2019.symbol.d.ts","../../node_modules/typescript/lib/lib.es2019.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.bigint.d.ts","../../node_modules/typescript/lib/lib.es2020.date.d.ts","../../node_modules/typescript/lib/lib.es2020.promise.d.ts","../../node_modules/typescript/lib/lib.es2020.sharedmemory.d.ts","../../node_modules/typescript/lib/lib.es2020.string.d.ts","../../node_modules/typescript/lib/lib.es2020.symbol.wellknown.d.ts","../../node_modules/typescript/lib/lib.es2020.intl.d.ts","../../node_modules/typescript/lib/lib.es2020.number.d.ts","../../node_modules/typescript/lib/lib.es2025.float16.d.ts","../../node_modules/typescript/lib/lib.esnext.disposable.d.ts","../../node_modules/typescript/lib/lib.decorators.d.ts","../../node_modules/typescript/lib/lib.decorators.legacy.d.ts","../../node_modules/typescript/lib/lib.es2020.full.d.ts","../../node_modules/@types/react/global.d.ts","../../node_modules/csstype/index.d.ts","../../node_modules/@types/react/index.d.ts","../../node_modules/@types/react/jsx-runtime.d.ts","./src/schema.ts","./src/types.ts","./src/store.ts","./src/index.ts","../../node_modules/@types/node/compatibility/iterators.d.ts","../../node_modules/@types/node/globals.typedarray.d.ts","../../node_modules/@types/node/buffer.buffer.d.ts","../../node_modules/@types/node/globals.d.ts","../../node_modules/@types/node/web-globals/abortcontroller.d.ts","../../node_modules/@types/node/web-globals/blob.d.ts","../../node_modules/@types/node/web-globals/console.d.ts","../../node_modules/@types/node/web-globals/crypto.d.ts","../../node_modules/@types/node/web-globals/domexception.d.ts","../../node_modules/@types/node/web-globals/encoding.d.ts","../../node_modules/@types/node/web-globals/events.d.ts","../../node_modules/undici-types/utility.d.ts","../../node_modules/undici-types/header.d.ts","../../node_modules/undici-types/readable.d.ts","../../node_modules/undici-types/fetch.d.ts","../../node_modules/undici-types/formdata.d.ts","../../node_modules/undici-types/connector.d.ts","../../node_modules/undici-types/client-stats.d.ts","../../node_modules/undici-types/client.d.ts","../../node_modules/undici-types/errors.d.ts","../../node_modules/undici-types/dispatcher.d.ts","../../node_modules/undici-types/global-dispatcher.d.ts","../../node_modules/undici-types/global-origin.d.ts","../../node_modules/undici-types/pool-stats.d.ts","../../node_modules/undici-types/pool.d.ts","../../node_modules/undici-types/handlers.d.ts","../../node_modules/undici-types/balanced-pool.d.ts","../../node_modules/undici-types/round-robin-pool.d.ts","../../node_modules/undici-types/h2c-client.d.ts","../../node_modules/undici-types/agent.d.ts","../../node_modules/undici-types/mock-interceptor.d.ts","../../node_modules/undici-types/mock-call-history.d.ts","../../node_modules/undici-types/mock-agent.d.ts","../../node_modules/undici-types/mock-client.d.ts","../../node_modules/undici-types/mock-pool.d.ts","../../node_modules/undici-types/snapshot-agent.d.ts","../../node_modules/undici-types/mock-errors.d.ts","../../node_modules/undici-types/proxy-agent.d.ts","../../node_modules/undici-types/socks5-proxy-agent.d.ts","../../node_modules/undici-types/env-http-proxy-agent.d.ts","../../node_modules/undici-types/retry-handler.d.ts","../../node_modules/undici-types/retry-agent.d.ts","../../node_modules/undici-types/api.d.ts","../../node_modules/undici-types/cache-interceptor.d.ts","../../node_modules/undici-types/interceptors.d.ts","../../node_modules/undici-types/util.d.ts","../../node_modules/undici-types/cookies.d.ts","../../node_modules/undici-types/patch.d.ts","../../node_modules/undici-types/websocket.d.ts","../../node_modules/undici-types/eventsource.d.ts","../../node_modules/undici-types/diagnostics-channel.d.ts","../../node_modules/undici-types/content-type.d.ts","../../node_modules/undici-types/cache.d.ts","../../node_modules/undici-types/index.d.ts","../../node_modules/@types/node/web-globals/fetch.d.ts","../../node_modules/@types/node/web-globals/importmeta.d.ts","../../node_modules/@types/node/web-globals/messaging.d.ts","../../node_modules/@types/node/web-globals/navigator.d.ts","../../node_modules/@types/node/web-globals/performance.d.ts","../../node_modules/@types/node/web-globals/storage.d.ts","../../node_modules/@types/node/web-globals/streams.d.ts","../../node_modules/@types/node/web-globals/timers.d.ts","../../node_modules/@types/node/web-globals/url.d.ts","../../node_modules/@types/node/assert.d.ts","../../node_modules/@types/node/assert/strict.d.ts","../../node_modules/@types/node/async_hooks.d.ts","../../node_modules/@types/node/buffer.d.ts","../../node_modules/@types/node/child_process.d.ts","../../node_modules/@types/node/cluster.d.ts","../../node_modules/@types/node/console.d.ts","../../node_modules/@types/node/constants.d.ts","../../node_modules/@types/node/crypto.d.ts","../../node_modules/@types/node/dgram.d.ts","../../node_modules/@types/node/diagnostics_channel.d.ts","../../node_modules/@types/node/dns.d.ts","../../node_modules/@types/node/dns/promises.d.ts","../../node_modules/@types/node/domain.d.ts","../../node_modules/@types/node/events.d.ts","../../node_modules/@types/node/fs.d.ts","../../node_modules/@types/node/fs/promises.d.ts","../../node_modules/@types/node/http.d.ts","../../node_modules/@types/node/http2.d.ts","../../node_modules/@types/node/https.d.ts","../../node_modules/@types/node/inspector.d.ts","../../node_modules/@types/node/inspector.generated.d.ts","../../node_modules/@types/node/inspector/promises.d.ts","../../node_modules/@types/node/module.d.ts","../../node_modules/@types/node/net.d.ts","../../node_modules/@types/node/os.d.ts","../../node_modules/@types/node/path.d.ts","../../node_modules/@types/node/path/posix.d.ts","../../node_modules/@types/node/path/win32.d.ts","../../node_modules/@types/node/perf_hooks.d.ts","../../node_modules/@types/node/process.d.ts","../../node_modules/@types/node/punycode.d.ts","../../node_modules/@types/node/querystring.d.ts","../../node_modules/@types/node/quic.d.ts","../../node_modules/@types/node/readline.d.ts","../../node_modules/@types/node/readline/promises.d.ts","../../node_modules/@types/node/repl.d.ts","../../node_modules/@types/node/sea.d.ts","../../node_modules/@types/node/sqlite.d.ts","../../node_modules/@types/node/stream.d.ts","../../node_modules/@types/node/stream/consumers.d.ts","../../node_modules/@types/node/stream/iter.d.ts","../../node_modules/@types/node/stream/promises.d.ts","../../node_modules/@types/node/stream/web.d.ts","../../node_modules/@types/node/string_decoder.d.ts","../../node_modules/@types/node/test.d.ts","../../node_modules/@types/node/test/reporters.d.ts","../../node_modules/@types/node/timers.d.ts","../../node_modules/@types/node/timers/promises.d.ts","../../node_modules/@types/node/tls.d.ts","../../node_modules/@types/node/trace_events.d.ts","../../node_modules/@types/node/tty.d.ts","../../node_modules/@types/node/url.d.ts","../../node_modules/@types/node/util.d.ts","../../node_modules/@types/node/util/types.d.ts","../../node_modules/@types/node/v8.d.ts","../../node_modules/@types/node/vm.d.ts","../../node_modules/@types/node/wasi.d.ts","../../node_modules/@types/node/worker_threads.d.ts","../../node_modules/@types/node/zlib.d.ts","../../node_modules/@types/node/zlib/iter.d.ts","../../node_modules/@types/node/index.d.ts","../../node_modules/vite/types/hmrpayload.d.ts","../../node_modules/vite/types/customevent.d.ts","../../node_modules/vite/types/hot.d.ts","../../node_modules/vite/types/importglob.d.ts","../../node_modules/vite/types/importmeta.d.ts","../../node_modules/vite/client.d.ts"],"fileIdsList":[[64,125,126,128,136,140,143,145,146,147,159],[64,127,128,136,140,143,145,146,147,159],[128,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,168],[64,128,129,134,136,139,140,143,145,146,147,149,159,164,177],[64,128,129,130,136,139,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159],[64,128,131,136,140,143,145,146,147,159,178],[64,128,132,133,136,140,143,145,146,147,150,159],[64,128,133,136,140,143,145,146,147,159,164,174],[64,128,134,136,139,140,143,145,146,147,149,159],[64,127,128,135,136,140,143,145,146,147,159],[64,128,136,137,140,143,145,146,147,159],[64,128,136,138,139,140,143,145,146,147,159],[64,127,128,136,139,140,143,145,146,147,159],[64,128,136,139,140,141,143,145,146,147,159,164,177],[64,128,136,139,140,141,143,145,146,147,159,164,166,168],[64,115,128,136,139,140,142,143,145,146,147,149,159,164,177],[64,128,136,139,140,142,143,145,146,147,149,159,164,174,177],[64,128,136,140,142,143,144,145,146,147,159,164,174,177],[62,63,64,65,66,67,68,69,70,71,72,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185],[64,128,136,139,140,143,145,146,147,159],[64,128,136,140,143,145,147,159],[64,128,136,140,143,145,146,147,148,159,177],[64,128,136,139,140,143,145,146,147,149,159,164],[64,128,136,140,143,145,146,147,150,159],[64,128,136,140,143,145,146,147,151,159],[64,128,136,139,140,143,145,146,147,154,159],[64,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184],[64,128,136,140,143,145,146,147,156,159],[64,128,136,140,143,145,146,147,157,159],[64,128,133,136,140,143,145,146,147,149,159,168],[64,128,136,139,140,143,145,146,147,159,160],[64,128,136,140,143,145,146,147,159,161,178,181],[64,128,136,139,140,143,145,146,147,159,164,167,168],[64,128,136,140,143,145,146,147,159,165,168],[64,128,136,140,143,145,146,147,159,166],[64,128,136,140,143,145,146,147,159,168,178],[64,128,136,140,143,145,146,147,159,169],[64,125,128,136,140,143,145,146,147,159,164,171,177],[64,128,136,140,143,145,146,147,159,164,170],[64,128,136,139,140,143,145,146,147,159,172,173],[64,128,136,140,143,145,146,147,159,172,173],[64,128,133,136,140,143,145,146,147,149,159,164,174],[64,128,136,140,143,145,146,147,159,175],[64,128,136,140,143,145,146,147,149,159,176],[64,128,136,140,142,143,145,146,147,157,159,177],[64,128,136,140,143,145,146,147,159,178,179],[64,128,133,136,140,143,145,146,147,159,179],[64,128,136,140,143,145,146,147,159,164,180],[64,128,136,140,143,145,146,147,148,159,181],[64,128,136,140,143,145,146,147,159,182],[64,128,131,136,140,143,145,146,147,159],[64,128,133,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,178],[64,115,128,136,140,143,145,146,147,159],[64,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,159,183],[64,128,136,140,143,145,146,147,154,159],[64,128,136,140,143,145,146,147,159,173],[64,115,128,136,139,140,141,143,145,146,147,154,159,164,168,177,180,181,183],[64,128,136,140,143,145,146,147,159,164,184],[64,128,136,140,143,145,146,147,159,166,185],[54,55,64,128,136,140,143,145,146,147,159],[56,64,128,136,140,143,145,146,147,159],[64,79,82,85,86,128,136,140,143,145,146,147,159,177],[64,82,128,136,140,143,145,146,147,159,164,177],[64,82,86,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,159,164],[64,76,128,136,140,143,145,146,147,159],[64,80,128,136,140,143,145,146,147,159],[64,78,79,82,128,136,140,143,145,146,147,159,177],[64,128,136,140,143,145,146,147,149,159,174],[64,128,136,140,143,145,146,147,159,186],[64,76,128,136,140,143,145,146,147,159,186],[64,78,82,128,136,140,143,145,146,147,149,159,177],[64,73,74,75,77,81,128,136,139,140,143,145,146,147,159,164,177],[64,82,91,99,128,136,140,143,145,146,147,159],[64,74,80,128,136,140,143,145,146,147,159],[64,82,109,110,128,136,140,143,145,146,147,159],[64,74,77,82,128,136,140,143,145,146,147,159,168,177,186],[64,82,128,136,140,143,145,146,147,159],[64,78,82,128,136,140,143,145,146,147,159,177],[64,73,128,136,140,143,145,146,147,159],[64,76,77,78,80,81,82,83,84,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,110,111,112,113,114,128,136,140,143,145,146,147,159],[64,82,102,105,128,136,140,143,145,146,147,159],[64,82,91,92,93,128,136,140,143,145,146,147,159],[64,80,82,92,94,128,136,140,143,145,146,147,159],[64,81,128,136,140,143,145,146,147,159],[64,74,76,82,128,136,140,143,145,146,147,159],[64,82,86,92,94,128,136,140,143,145,146,147,159],[64,86,128,136,140,143,145,146,147,159],[64,80,82,85,128,136,140,143,145,146,147,159,177],[64,74,78,82,91,128,136,140,143,145,146,147,159],[64,82,102,128,136,140,143,145,146,147,159],[64,94,128,136,140,143,145,146,147,159],[64,74,78,82,86,128,136,140,143,145,146,147,159],[64,76,82,109,128,136,140,143,145,146,147,159,168,183,186],[64,128,136,140,143,145,146,147,159,191],[64,128,136,140,143,145,146,147,159,187],[64,128,136,140,143,145,146,147,159,188],[64,128,136,140,143,145,146,147,159,189,190],[57,58,59,60,64,128,136,140,143,145,146,147,159],[57,64,128,136,140,143,145,146,147,159],[57,58,59,64,128,133,136,140,143,145,146,147,151,159,163]],"fileInfos":[{"version":"bcd24271a113971ba9eb71ff8cb01bc6b0f872a85c23fdbe5d93065b375933cd","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f88bedbeb09c6f5a6645cb24c7c55f1aa22d19ae96c8e6959cbd8b85a707bc6","impliedFormat":1},{"version":"7fe93b39b810eadd916be8db880dd7f0f7012a5cc6ffb62de8f62a2117fa6f1f","impliedFormat":1},{"version":"bb0074cc08b84a2374af33d8bf044b80851ccc9e719a5e202eacf40db2c31600","impliedFormat":1},{"version":"1a7daebe4f45fb03d9ec53d60008fbf9ac45a697fdc89e4ce218bc94b94f94d6","impliedFormat":1},{"version":"f94b133a3cb14a288803be545ac2683e0d0ff6661bcd37e31aaaec54fc382aed","impliedFormat":1},{"version":"f59d0650799f8782fd74cf73c19223730c6d1b9198671b1c5b3a38e1188b5953","impliedFormat":1},{"version":"d6b1eba8496bdd0eed6fc8a685768fe01b2da4a0388b5fe7df558290bffcf32f","affectsGlobalScope":true,"impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"7f57fc4404ff020bc45b9c620aff2b40f700b95fe31164024c453a5e3c163c54","impliedFormat":1},{"version":"2a2de5b9459b3fc44decd9ce6100b72f1b002ef523126c1d3d8b2a4a63d74d78","affectsGlobalScope":true,"impliedFormat":1},{"version":"f13f4b465c99041e912db5c44129a94588e1aafee35a50eab51044833f50b4ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"eadcffda2aa84802c73938e589b9e58248d74c59cb7fcbca6474e3435ac15504","affectsGlobalScope":true,"impliedFormat":1},{"version":"105ba8ff7ba746404fe1a2e189d1d3d2e0eb29a08c18dded791af02f29fb4711","affectsGlobalScope":true,"impliedFormat":1},{"version":"00343ca5b2e3d48fa5df1db6e32ea2a59afab09590274a6cccb1dbae82e60c7c","affectsGlobalScope":true,"impliedFormat":1},{"version":"ebd9f816d4002697cb2864bea1f0b70a103124e18a8cd9645eeccc09bdf80ab4","affectsGlobalScope":true,"impliedFormat":1},{"version":"2c1afac30a01772cd2a9a298a7ce7706b5892e447bb46bdbeef720f7b5da77ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"7b0225f483e4fa685625ebe43dd584bb7973bbd84e66a6ba7bbe175ee1048b4f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c0a4b8ac6ce74679c1da2b3795296f5896e31c38e888469a8e0f99dc3305de60","affectsGlobalScope":true,"impliedFormat":1},{"version":"3084a7b5f569088e0146533a00830e206565de65cae2239509168b11434cd84f","affectsGlobalScope":true,"impliedFormat":1},{"version":"c5079c53f0f141a0698faa903e76cb41cd664e3efb01cc17a5c46ec2eb0bef42","affectsGlobalScope":true,"impliedFormat":1},{"version":"32cafbc484dea6b0ab62cf8473182bbcb23020d70845b406f80b7526f38ae862","affectsGlobalScope":true,"impliedFormat":1},{"version":"fca4cdcb6d6c5ef18a869003d02c9f0fd95df8cfaf6eb431cd3376bc034cad36","affectsGlobalScope":true,"impliedFormat":1},{"version":"b93ec88115de9a9dc1b602291b85baf825c85666bf25985cc5f698073892b467","affectsGlobalScope":true,"impliedFormat":1},{"version":"f5c06dcc3fe849fcb297c247865a161f995cc29de7aa823afdd75aaaddc1419b","affectsGlobalScope":true,"impliedFormat":1},{"version":"b77e16112127a4b169ef0b8c3a4d730edf459c5f25fe52d5e436a6919206c4d7","affectsGlobalScope":true,"impliedFormat":1},{"version":"fbffd9337146eff822c7c00acbb78b01ea7ea23987f6c961eba689349e744f8c","affectsGlobalScope":true,"impliedFormat":1},{"version":"a995c0e49b721312f74fdfb89e4ba29bd9824c770bbb4021d74d2bf560e4c6bd","affectsGlobalScope":true,"impliedFormat":1},{"version":"c7b3542146734342e440a84b213384bfa188835537ddbda50d30766f0593aff9","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce6180fa19b1cccd07ee7f7dbb9a367ac19c0ed160573e4686425060b6df7f57","affectsGlobalScope":true,"impliedFormat":1},{"version":"3f02e2476bccb9dbe21280d6090f0df17d2f66b74711489415a8aa4df73c9675","affectsGlobalScope":true,"impliedFormat":1},{"version":"45e3ab34c1c013c8ab2dc1ba4c80c780744b13b5676800ae2e3be27ae862c40c","affectsGlobalScope":true,"impliedFormat":1},{"version":"805c86f6cca8d7702a62a844856dbaa2a3fd2abef0536e65d48732441dde5b5b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e42e397f1a5a77994f0185fd1466520691456c772d06bf843e5084ceb879a0ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"f4c2b41f90c95b1c532ecc874bd3c111865793b23aebcc1c3cbbabcd5d76ffb0","affectsGlobalScope":true,"impliedFormat":1},{"version":"ab26191cfad5b66afa11b8bf935ef1cd88fabfcb28d30b2dfa6fad877d050332","affectsGlobalScope":true,"impliedFormat":1},{"version":"2088bc26531e38fb05eedac2951480db5309f6be3fa4a08d2221abb0f5b4200d","affectsGlobalScope":true,"impliedFormat":1},{"version":"cb9d366c425fea79716a8fb3af0d78e6b22ebbab3bd64d25063b42dc9f531c1e","affectsGlobalScope":true,"impliedFormat":1},{"version":"500934a8089c26d57ebdb688fc9757389bb6207a3c8f0674d68efa900d2abb34","affectsGlobalScope":true,"impliedFormat":1},{"version":"689da16f46e647cef0d64b0def88910e818a5877ca5379ede156ca3afb780ac3","affectsGlobalScope":true,"impliedFormat":1},{"version":"bc21cc8b6fee4f4c2440d08035b7ea3c06b3511314c8bab6bef7a92de58a2593","affectsGlobalScope":true,"impliedFormat":1},{"version":"7ca53d13d2957003abb47922a71866ba7cb2068f8d154877c596d63c359fed25","affectsGlobalScope":true,"impliedFormat":1},{"version":"54725f8c4df3d900cb4dac84b64689ce29548da0b4e9b7c2de61d41c79293611","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5594bc3076ac29e6c1ebda77939bc4c8833de72f654b6e376862c0473199323","affectsGlobalScope":true,"impliedFormat":1},{"version":"2f3eb332c2d73e729f3364fcc0c2b375e72a121e8157d25a82d67a138c83a95c","affectsGlobalScope":true,"impliedFormat":1},{"version":"6f4427f9642ce8d500970e4e69d1397f64072ab73b97e476b4002a646ac743b1","affectsGlobalScope":true,"impliedFormat":1},{"version":"48915f327cd1dea4d7bd358d9dc7732f58f9e1626a29cc0c05c8c692419d9bb7","affectsGlobalScope":true,"impliedFormat":1},{"version":"b7bf9377723203b5a6a4b920164df22d56a43f593269ba6ae1fdc97774b68855","affectsGlobalScope":true,"impliedFormat":1},{"version":"f128dae7c44d8f35ee42e0a437000a57c9f06cc04f8b4fb42eebf44954d53dc8","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ecb8e347cb6b2a8927c09b86263663289418df375f5e68e11a0ae683776978f","affectsGlobalScope":true,"impliedFormat":1},{"version":"1ce14b81c5cc821994aa8ec1d42b220dd41b27fcc06373bce3958af7421b77d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3a048b3e9302ef9a34ef4ebb9aecfb28b66abb3bce577206a79fee559c230da","affectsGlobalScope":true,"impliedFormat":1},{"version":"577cc49be1979782a5a695b46049566d123e2a75382f10bb82f2e5c87e29059a","impliedFormat":1},{"version":"7e29f41b158de217f94cb9676bf9cbd0cd9b5a46e1985141ed36e075c52bf6ad","affectsGlobalScope":true,"impliedFormat":1},{"version":"ac51dd7d31333793807a6abaa5ae168512b6131bd41d9c5b98477fc3b7800f9f","impliedFormat":1},{"version":"814d5c7384f3ca276e9dc4bcfde5545801a3ea0bfae09916b3336774e662fd1b","impliedFormat":1},{"version":"b838d4c72740eb0afd284bf7575b74c624b105eff2e8c7b4aeead57e7ac320ff","impliedFormat":1},"8fbe06618f17d801507119ff16c1fbc37977a0d0b06595b197ed518e948c7edc","5ed797667a4ab2c2027605ad16143d9fcc8d1b29e6e0223f12f6d78a579f5ce2",{"version":"677c43c7e5baa576b755f18397cc4922295355d0c71ef767d3f682b9a7763493","signature":"00e0694130337b13fa0288b43736c823e478134b750fffd95d68dc861fd5ac44"},{"version":"0e6b3e7d3a46a2f024c755d96744bcbd6d0403c3c1a7a3862614be06d51f7e6a","signature":"996f9aa2ef693aa04adb24e5f13ccfc6675bba9f3f8d98c9e57693bb25d1dde1"},{"version":"d153a11543fd884b596587ccd97aebbeed950b26933ee000f94009f1ab142848","affectsGlobalScope":true,"impliedFormat":1},{"version":"0ccdaa19852d25ecd84eec365c3bfa16e7859cadecf6e9ca6d0dbbbee439743f","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc2110f7decca6bfb9392e30421cfa1436479e4a6756e8fec6cbc22625d4f881","affectsGlobalScope":true,"impliedFormat":1},{"version":"096116f8fedc1765d5bd6ef360c257b4a9048e5415054b3bf3c41b07f8951b0b","affectsGlobalScope":true,"impliedFormat":1},{"version":"e5e01375c9e124a83b52ee4b3244ed1a4d214a6cfb54ac73e164a823a4a7860a","affectsGlobalScope":true,"impliedFormat":1},{"version":"f90ae2bbce1505e67f2f6502392e318f5714bae82d2d969185c4a6cecc8af2fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"4b58e207b93a8f1c88bbf2a95ddc686ac83962b13830fe8ad3f404ffc7051fb4","affectsGlobalScope":true,"impliedFormat":1},{"version":"1fefabcb2b06736a66d2904074d56268753654805e829989a46a0161cd8412c5","affectsGlobalScope":true,"impliedFormat":1},{"version":"9798340ffb0d067d69b1ae5b32faa17ab31b82466a3fc00d8f2f2df0c8554aaa","affectsGlobalScope":true,"impliedFormat":1},{"version":"c18a99f01eb788d849ad032b31cafd49de0b19e083fe775370834c5675d7df8e","affectsGlobalScope":true,"impliedFormat":1},{"version":"5247874c2a23b9a62d178ae84f2db6a1d54e6c9a2e7e057e178cc5eea13757fc","affectsGlobalScope":true,"impliedFormat":1},{"version":"cdcf9ea426ad970f96ac930cd176d5c69c6c24eebd9fc580e1572d6c6a88f62c","impliedFormat":1},{"version":"23cd712e2ce083d68afe69224587438e5914b457b8acf87073c22494d706a3d0","impliedFormat":1},{"version":"156a859e21ef3244d13afeeba4e49760a6afa035c149dda52f0c45ea8903b338","impliedFormat":1},{"version":"10ec5e82144dfac6f04fa5d1d6c11763b3e4dbbac6d99101427219ab3e2ae887","impliedFormat":1},{"version":"615754924717c0b1e293e083b83503c0a872717ad5aa60ed7f1a699eb1b4ea5c","impliedFormat":1},{"version":"14e9acf826baba0ef4b5665704084896e7bcc06f65a9ab13af7e93d27d6b7069","impliedFormat":1},{"version":"68834d631c8838c715f225509cfc3927913b9cc7a4870460b5b60c8dbdb99baf","impliedFormat":1},{"version":"21adf13435b9b748529c8cedf80f884e5130b9684188120a686cd2b26a2059c7","impliedFormat":1},{"version":"eec76bf6b9346f3f95fa402621b889489e96930e72295b0369022f332e9b4a6a","impliedFormat":1},{"version":"0ecd58f413f9bc3b7d4383eae31b0c8fc576985cd7404d6f99f8c643543ade74","impliedFormat":1},{"version":"ea6bc8de8b59f90a7a3960005fd01988f98fd0784e14bc6922dde2e93305ec7d","impliedFormat":1},{"version":"36107995674b29284a115e21a0618c4c2751b32a8766dd4cb3ba740308b16d59","impliedFormat":1},{"version":"914a0ae30d96d71915fc519ccb4efbf2b62c0ddfb3a3fc6129151076bc01dc60","impliedFormat":1},{"version":"9c32412007b5662fd34a8eb04292fb5314ec370d7016d1c2fb8aa193c807fe22","impliedFormat":1},{"version":"7fd1b31fd35876b0aa650811c25ec2c97a3c6387e5473eb18004bed86cdd76b6","impliedFormat":1},{"version":"4d327f7d72ad0918275cea3eee49a6a8dc8114ae1d5b7f3f5d0774de75f7439a","impliedFormat":1},{"version":"6ebe8ebb8659aaa9d1acbf3710d7dae3e923e97610238b9511c25dc39023a166","impliedFormat":1},{"version":"e85d7f8068f6a26710bff0cc8c0fc5e47f71089c3780fbede05857331d2ddec9","impliedFormat":1},{"version":"7befaf0e76b5671be1d47b77fcc65f2b0aad91cc26529df1904f4a7c46d216e9","impliedFormat":1},{"version":"0a60a292b89ca7218b8616f78e5bbd1c96b87e048849469cccb4355e98af959a","impliedFormat":1},{"version":"0b6e25234b4eec6ed96ab138d96eb70b135690d7dd01f3dd8a8ab291c35a683a","impliedFormat":1},{"version":"9666f2f84b985b62400d2e5ab0adae9ff44de9b2a34803c2c5bd3c8325b17dc0","impliedFormat":1},{"version":"40cd35c95e9cf22cfa5bd84e96408b6fcbca55295f4ff822390abb11afbc3dca","impliedFormat":1},{"version":"b1616b8959bf557feb16369c6124a97a0e74ed6f49d1df73bb4b9ddf68acf3f3","impliedFormat":1},{"version":"5b03a034c72146b61573aab280f295b015b9168470f2df05f6080a2122f9b4df","impliedFormat":1},{"version":"40b463c6766ca1b689bfcc46d26b5e295954f32ad43e37ee6953c0a677e4ae2b","impliedFormat":1},{"version":"249b9cab7f5d628b71308c7d9bb0a808b50b091e640ba3ed6e2d0516f4a8d91d","impliedFormat":1},{"version":"d33ce35e3f9cfcc1d94eca415bdd3bde94d5b153ffdd33e6c4455c029986c630","impliedFormat":1},{"version":"80aae6afc67faa5ac0b32b5b8bc8cc9f7fa299cff15cf09cc2e11fd28c6ae29e","impliedFormat":1},{"version":"f473cd2288991ff3221165dcf73cd5d24da30391f87e85b3dd4d0450c787a391","impliedFormat":1},{"version":"499e5b055a5aba1e1998f7311a6c441a369831c70905cc565ceac93c28083d53","impliedFormat":1},{"version":"8aee8b6d4f9f62cf3776cda1305fb18763e2aade7e13cea5bbe699112df85214","impliedFormat":1},{"version":"98498b101803bb3dde9f76a56e65c14b75db1cc8bec5f4db72be541570f74fc5","impliedFormat":1},{"version":"4dc59f6e1dbf3d5f66660fceabe6c174d3261b37b696ae1854f0dbaf255fc753","impliedFormat":1},{"version":"5d0375ca7310efb77e3ef18d068d53784faf62705e0ad04569597ae0e755c401","impliedFormat":1},{"version":"59af37caec41ecf7b2e76059c9672a49e682c1a2aa6f9d7dc78878f53aa284d6","impliedFormat":1},{"version":"addf417b9eb3f938fddf8d81e96393a165e4be0d4a8b6402292f9c634b1cb00d","impliedFormat":1},{"version":"436d7b4543b340b0f3eef4310d524242e41369b9652aa9c70428767c4dcac455","impliedFormat":1},{"version":"adf27937dba6af9f08a68c5b1d3fce0ca7d4b960c57e6d6c844e7d1a8e53adae","impliedFormat":1},{"version":"12950411eeab8563b349cb7959543d92d8d02c289ed893d78499a19becb5a8cc","impliedFormat":1},{"version":"2e85db9e6fd73cfa3d7f28e0ab6b55417ea18931423bd47b409a96e4a169e8e6","impliedFormat":1},{"version":"c46e079fe54c76f95c67fb89081b3e399da2c7d109e7dca8e4b58d83e332e605","impliedFormat":1},{"version":"114f493b30f364255290472111b5a4791d5902c308645670cd0401429cbc6930","impliedFormat":1},{"version":"c3f5289820990ab66b70c7fb5b63cb674001009ff84b13de40619619a9c8175f","affectsGlobalScope":true,"impliedFormat":1},{"version":"b3275d55fac10b799c9546804126239baf020d220136163f763b55a74e50e750","affectsGlobalScope":true,"impliedFormat":1},{"version":"fa68a0a3b7cb32c00e39ee3cd31f8f15b80cac97dce51b6ee7fc14a1e8deb30b","affectsGlobalScope":true,"impliedFormat":1},{"version":"1cf059eaf468efcc649f8cf6075d3cb98e9a35a0fe9c44419ec3d2f5428d7123","affectsGlobalScope":true,"impliedFormat":1},{"version":"6c36e755bced82df7fb6ce8169265d0a7bb046ab4e2cb6d0da0cb72b22033e89","affectsGlobalScope":true,"impliedFormat":1},{"version":"e7721c4f69f93c91360c26a0a84ee885997d748237ef78ef665b153e622b36c1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7a93de4ff8a63bafe62ba86b89af1df0ccb5e40bb85b0c67d6bbcfdcf96bf3d4","affectsGlobalScope":true,"impliedFormat":1},{"version":"90e85f9bc549dfe2b5749b45fe734144e96cd5d04b38eae244028794e142a77e","affectsGlobalScope":true,"impliedFormat":1},{"version":"e0a5deeb610b2a50a6350bd23df6490036a1773a8a71d70f2f9549ab009e67ee","affectsGlobalScope":true,"impliedFormat":1},{"version":"d2ae155afe8a01cc0ae612d99117cf8ef16692ba7c4366590156fdec1bcf2d8c","impliedFormat":1},{"version":"3f5e5d9be35913db9fea42a63f3df0b7e3c8703b97670a2125587b4dbbd56d7c","impliedFormat":1},{"version":"c8b8968311ec4e5e97b7b5fb8a65efaba455db9bdcfd7fff7fb15f6e317bfba0","impliedFormat":1},{"version":"57c23df0b5f7a8e26363a3849b0bc7763f6b241207157c8e40089d1df4116f35","affectsGlobalScope":true,"impliedFormat":1},{"version":"3b8bc0c17b54081b0878673989216229e575d67a10874e84566a21025a2461ee","impliedFormat":1},{"version":"5b0db5a58b73498792a29bfebc333438e61906fef75da898b410e24e52229e6f","impliedFormat":1},{"version":"dbe055b2b29a7bab2c1ca8f259436306adb43f469dca7e639a02cd3695d3f621","impliedFormat":1},{"version":"1678b04557dca52feab73cc67610918a7f5e25bfdba3e7fa081acd625d93106d","impliedFormat":1},{"version":"aecbf1d9e6a18dab7d92ef8a89a1444b47e1eb6134cb2bb776a26d55ff58c29a","impliedFormat":1},{"version":"2ea729503db9793f2691162fec3dd1118cab62e96d025f8eeb376d43ec293395","impliedFormat":1},{"version":"9ec87fea42b92894b0f209931a880789d43c3397d09dd99c631ae40a2f7071d1","impliedFormat":1},{"version":"c68e88cdfadfb6c8ba5fc38e58a3a166b0beae77b1f05b7d921150a32a5ffb8d","impliedFormat":1},{"version":"2bc7aa4fba46df0bd495425a7c8201437a7d465f83854fac859df2d67f664df3","impliedFormat":1},{"version":"41d17e1ad9a002feb11c8cdd2777e5bbc0cdb1e3f595d237e4dded0b6949983b","impliedFormat":1},{"version":"1fede9296beac11ce8e6b425396a1791f64341f2be85deebb6286faf6e16306e","affectsGlobalScope":true,"impliedFormat":1},{"version":"ce697b6a251d9cad53998c7fd3098072df883b525ec45d83530e434dc6d80dc6","impliedFormat":1},{"version":"53ff1acab080a8318445df2c7da35ec47489dc83be40569e39c7c750046e217f","impliedFormat":1},{"version":"0eb5d0cbf09de5d34542b977fd6a933bb2e0817bffe8e1a541b2f1ad1b9af1ff","impliedFormat":1},{"version":"3db996ecdee7aabecc5385976cc07eb66216034a273c07b17d1a85292e9bab0c","impliedFormat":1},{"version":"2c2bdaa1d8ead9f68628d6d9d250e46ee8e81aa4898b4769a36956ae15e060fe","impliedFormat":1},{"version":"c32c840c62d8bd7aeb3147aa6754cd2d922b990a6b6634530cb2ebdce5adc8e9","impliedFormat":1},{"version":"5ff4433a2deae4f85ab1377e90a7554ce6b47ae51c69a84ca30a6e22fae85834","impliedFormat":1},{"version":"82b91e4e42e6c41bc7fc1b6c2dc5eba6a2ba98375eb1f210e6ff6bba2d54177e","impliedFormat":1},{"version":"c1fa52b3d014001e8662fa2669d90ea15373958a288e3b83a3b621733d25292a","affectsGlobalScope":true,"impliedFormat":1},{"version":"cbed824fec91efefc7bbdcb8b43d1a531fdbebd0e2ef19481501ff365a93cb70","impliedFormat":1},{"version":"d0716593b3f2b0451bcf0c24cfa86dec2235c325c89f201934248b7c742715fc","impliedFormat":1},{"version":"ec501101c2a96133a6c695f934c8f6642149cc728571b29cbb7b770984c1088e","impliedFormat":1},{"version":"b214ebcf76c51b115453f69729ee8aa7b7f8eccdae2a922b568a45c2d7ff52f7","impliedFormat":1},{"version":"429c9cdfa7d126255779efd7e6d9057ced2d69c81859bbab32073bad52e9ba76","impliedFormat":1},{"version":"2991bca2cc0f0628a278df2a2ccdb8d6cbcb700f3761abbed62bba137d5b1790","impliedFormat":1},{"version":"47d480d0c84b0f6215f1f817e0939b247ebf5217b6784846b96fa6ab710ce99d","affectsGlobalScope":true,"impliedFormat":1},{"version":"230763250f20449fa7b3c9273e1967adb0023dc890d4be1553faca658ee65971","impliedFormat":1},{"version":"c3e9078b60cb329d1221f5878e88cecfa3e74460550e605a58fcfb41a66029ff","impliedFormat":1},{"version":"8413d0641f293aed551c7464615b770d34a02dedede889b9591172287d68e773","impliedFormat":1},{"version":"0ea59f7d3e51440baa64f429253759b106cfcbaf51e474cae606e02265b37cf8","impliedFormat":1},{"version":"bc18a1991ba681f03e13285fa1d7b99b03b67ee671b7bc936254467177543890","impliedFormat":1},{"version":"1b241e24f3227d078c06aeda6e050187ad59a4e591f4467abed44d92b084e08d","impliedFormat":1},{"version":"fa94bbf532b7af8f394b95fa310980d6e20bd2d4c871c6a6cb9f70f03750a44b","impliedFormat":1},{"version":"7fde0e1be5c8be204ffbf428abfcf01da2eb0f130e1bc3f539eb7275f4fd1f58","impliedFormat":1},{"version":"e284328553df5f425a5d33d36a0c3fa66b46af9d097cad6f4d2e8696dfdeb0f1","affectsGlobalScope":true,"impliedFormat":1},{"version":"7fa2214bb0d64701bc6f9ce8cde2fd2ff8c571e0b23065fa04a8a5a6beb91511","impliedFormat":1},{"version":"f36b3fbe2be150a9ca140da48593f21e6a8172004f92ddc549b43efec39f3e54","impliedFormat":1},{"version":"f1c93e046fb3d9b7f8249629f4b63dc068dd839b824dd0aa39a5e68476dc9420","impliedFormat":1},{"version":"016b29bf4926b80255a108c53a1451717350059da04fcae64d1075f5e93bbb39","impliedFormat":1},{"version":"841983e39bd4cbb463be385e92fda11057cab368bf27100a801c492f1d86cbaa","impliedFormat":1},{"version":"1c4f139ade4f6ebf45463505f8155173e5d7a5305e50e0aae0a5e712d6ff3b48","impliedFormat":1},{"version":"e16b319e5aca1031168de823c4946ff8e29629c4c8cc0ec0fcfe2a8ab2155043","impliedFormat":1},{"version":"e4156ddb25aa0e3b5303d372f26957b36778f0f6bbd4326359269873295e3058","affectsGlobalScope":true,"impliedFormat":1},{"version":"cc1b433a84cae05ddc5672d4823170af78606ad21ecef60dbc4570190cbf1357","impliedFormat":1},{"version":"9d3821bc75c59577e52643324cec92fc2145642e8d17cf7ee07a3181f21d985d","impliedFormat":1},{"version":"7f78cfb2b343838612c192cb251746e3a7c62ac7675726a47e130d9b213f6580","impliedFormat":1},{"version":"201db9cf1687fab1adf5282fcba861f382b32303dc4f67c89d59655e78a25461","impliedFormat":1},{"version":"2c3c5c0f54055e87640f5d233716fd889f3034fc7911d603b642369b0dbeb2a7","impliedFormat":1},{"version":"0a20eaf2e4b1e3c1e1f87f7bccb0c936375b23b022baeea750519b7c9bc6ce83","impliedFormat":1},{"version":"b484ec11ba00e3a2235562a41898d55372ccabe607986c6fa4f4aba72093749f","impliedFormat":1},{"version":"a16b91b27bd6b706c687c88cbc8a7d4ee98e5ed6043026d6b84bda923c0aed67","impliedFormat":1},{"version":"1c9e5b1a17b1fc9b3711fb36e0690421261ab2880f15b145155b5b2ba2ab6c2d","impliedFormat":1},{"version":"99ab6d0d660ce4d21efb52288a39fd35bb3f556980ec5463b1ae8f304a3bbc85","impliedFormat":1},{"version":"6eeded8c7e352be6e0efb83f4935ec752513c4d22043b52522b90849a49a3a11","impliedFormat":1},{"version":"6c1ad90050ffbb151cacc68e2d06ea1a26a945659391e32651f5d42b86fd7f2c","impliedFormat":1},{"version":"afa1c49f8e559e413d57343339db857d2a8159435cf9cf7d4deb41718fff1b88","impliedFormat":1},{"version":"6953d7597831d0860c7034cf4f0419687d263b6b98a4b32e37ce6d49615c36e2","impliedFormat":1},{"version":"64ccfe7dcbcad9b8a661a042ecb2309e41b9ab957f7b8cd070b05583a7821a13","impliedFormat":99},{"version":"98580d6ed68c9cee7f956685abbb6502d70bd92f48175ac807ae6c5e254fadc6","impliedFormat":99},{"version":"4e003c868b0d8f8ad200b96cbc653e18e513fa23e1c19c4fe3cc25d4394efc47","impliedFormat":99},{"version":"e50d04d261120f8f9d07e85698735d5b88f3a66bc395c08ce26c3d817d141a73","affectsGlobalScope":true,"impliedFormat":99},{"version":"72429cda84a47f092a3bf777bbc4ece9dda492fe0f02324aa7f197f3800089c6","affectsGlobalScope":true,"impliedFormat":99},{"version":"74b98be6ab7723114273526706070ff9885fdf990d5c4e722801addca4d264e1","affectsGlobalScope":true,"impliedFormat":99}],"root":[[58,61]],"options":{"allowImportingTsExtensions":false,"allowSyntheticDefaultImports":true,"composite":true,"declaration":true,"jsx":4,"module":99,"outDir":"./dist","rootDir":"./src","skipLibCheck":true,"strict":true,"target":7},"referencedMap":[[125,1],[126,1],[127,2],[64,3],[128,4],[129,5],[130,6],[62,7],[131,8],[132,9],[133,10],[134,11],[135,12],[136,13],[137,13],[138,14],[139,15],[140,16],[141,17],[65,7],[63,7],[142,18],[143,19],[144,20],[186,21],[145,22],[146,23],[147,22],[148,24],[149,25],[150,26],[151,27],[152,27],[153,27],[154,28],[155,29],[156,30],[157,31],[158,32],[159,33],[160,33],[161,34],[162,7],[163,7],[164,35],[165,36],[166,37],[167,35],[168,38],[169,39],[170,40],[171,41],[172,42],[173,43],[174,44],[175,45],[176,46],[177,47],[178,48],[179,49],[180,50],[181,51],[182,52],[66,22],[67,7],[68,53],[69,54],[70,7],[71,55],[72,7],[116,56],[117,57],[118,58],[119,58],[120,59],[121,7],[122,4],[123,60],[124,57],[183,61],[184,62],[185,63],[54,7],[56,64],[57,65],[55,7],[51,7],[52,7],[10,7],[8,7],[9,7],[14,7],[13,7],[2,7],[15,7],[16,7],[17,7],[18,7],[19,7],[20,7],[21,7],[22,7],[3,7],[23,7],[24,7],[4,7],[25,7],[29,7],[26,7],[27,7],[28,7],[30,7],[31,7],[32,7],[5,7],[33,7],[34,7],[35,7],[36,7],[6,7],[40,7],[37,7],[38,7],[39,7],[41,7],[7,7],[42,7],[53,7],[47,7],[48,7],[43,7],[44,7],[45,7],[46,7],[49,7],[1,7],[50,7],[12,7],[11,7],[91,66],[104,67],[88,68],[105,69],[114,70],[79,71],[80,72],[78,73],[113,74],[108,75],[112,76],[82,77],[101,78],[81,79],[111,80],[76,81],[77,75],[83,82],[84,7],[90,83],[87,82],[74,84],[115,85],[106,86],[94,87],[93,82],[95,88],[98,89],[92,90],[96,91],[109,74],[85,92],[86,93],[99,94],[75,69],[103,95],[102,82],[89,93],[97,96],[100,97],[107,7],[73,7],[110,98],[192,99],[188,100],[187,7],[189,101],[190,7],[191,102],[61,103],[58,104],[60,105],[59,104]],"affectedFilesPendingEmit":[[61,17],[58,17],[60,17],[59,17]],"emitSignatures":[58,59,60,61],"version":"6.0.3"} \ No newline at end of file diff --git a/source/app.tsx b/source/app.tsx index d9c500e..802932f 100644 --- a/source/app.tsx +++ b/source/app.tsx @@ -18,6 +18,13 @@ import { import { writeWebExport } from '@web/export'; import { SessionStore } from '../packages/session-sqlite/src/index'; import { resolveUserPath } from '@agent-core/harness/tools/pathUtils'; +import { + deleteMessagesCommand, + deleteSessionCommand, + formatSessionEntries, + formatSessionList, + sessionCommandUsage, +} from './session-commands'; const encoding = get_encoding('cl100k_base'); @@ -174,6 +181,10 @@ const commandHelp = [ '/memory - 查看 memory 概览', '/status - 查看当前 CLI 状态', '/export - 导出会话数据到 JSON 文件', + '/sessions - 列出最近会话', + '/session - 查看会话消息', + '/delmsg - 删除一条消息及其后续回复', + '/delsession - 删除整个会话', '/mode - 查看当前模式和阶段', '/plan - 切换到 PLAN 模式', '/build - 切换到 BUILD 模式', @@ -188,7 +199,7 @@ const commandHelp = [ 'Ctrl+C 退出程序', ].join('\n'); -const readOnlyCommands = new Set(['/help', '/status']); +const readOnlyCommands = new Set(['/help', '/status', '/sessions', '/session']); const App = () => { const initialActivity = loadActivityPanel(); @@ -484,6 +495,54 @@ const App = () => { return true; } + case '/sessions': { + showCommandMessage(formatSessionList(getSharedSessionStore())); + return true; + } + + case '/session': { + const parts = rawCommand.trim().split(/\s+/); + if (parts.length < 2) { + showCommandMessage(sessionCommandUsage('session')); + return true; + } + showCommandMessage( + formatSessionEntries(getSharedSessionStore(), parts[1]), + ); + return true; + } + + case '/delmsg': { + const parts = rawCommand.trim().split(/\s+/); + if (parts.length < 3) { + showCommandMessage(sessionCommandUsage('delmsg')); + return true; + } + const deletedMessage = deleteMessagesCommand( + getSharedSessionStore(), + parts[1], + parts[2], + ); + setState((prev) => ({ ...prev, ...refreshActivity() })); + showCommandMessage(deletedMessage); + return true; + } + + case '/delsession': { + const parts = rawCommand.trim().split(/\s+/); + if (parts.length < 2) { + showCommandMessage(sessionCommandUsage('delsession')); + return true; + } + const deletedMessage = deleteSessionCommand( + getSharedSessionStore(), + parts[1], + ); + setState((prev) => ({ ...prev, ...refreshActivity() })); + showCommandMessage(deletedMessage); + return true; + } + case '/mode': showCommandMessage( `当前模式: ${state.mode.toUpperCase()}\n当前阶段: ${state.phase}`, diff --git a/source/session-commands.ts b/source/session-commands.ts new file mode 100644 index 0000000..27077b8 --- /dev/null +++ b/source/session-commands.ts @@ -0,0 +1,89 @@ +import type { SessionStoreLike } from '@agent-core/harness/session/store-types'; + +const compactText = (text: string, maxLength = 96): string => { + const normalized = text.replace(/\s+/g, ' ').trim(); + if (normalized.length <= maxLength) { + return normalized; + } + return `${normalized.slice(0, maxLength - 1)}...`; +}; + +const entryContent = (entry: { payload: unknown }): string => { + if (!entry.payload || typeof entry.payload !== 'object') { + return ''; + } + const content = (entry.payload as { content?: unknown }).content; + return typeof content === 'string' ? content : ''; +}; + +/** 生成 /sessions 的展示文本 */ +export const formatSessionList = ( + store: SessionStoreLike, + limit = 20, +): string => { + const sessions = store.listSessions({ limit }); + if (sessions.length === 0) { + return '暂无会话。'; + } + + const lines = sessions.map((session) => { + const objective = session.metadata?.objective; + const title = + typeof objective === 'string' && objective.trim() + ? compactText(objective, 44) + : session.id; + return `${session.id} ${store.getStats(session.id).messageCount} 条 ${title}`; + }); + return ['会话列表:', ...lines].join('\n'); +}; + +/** 生成 /session 的展示文本 */ +export const formatSessionEntries = ( + store: SessionStoreLike, + sessionId: string, + limit = 100, +): string => { + const entries = store.getEntries(sessionId, { limit }); + if (entries.length === 0) { + return '未找到会话或会话没有消息。'; + } + + return entries + .map( + (entry) => + `${entry.seq} ${entry.type} ${entry.id}\n${compactText(entryContent(entry), 88)}`, + ) + .join('\n\n'); +}; + +/** 删除单条消息并返回给用户的反馈文本 */ +export const deleteMessagesCommand = ( + store: SessionStoreLike, + sessionId: string, + entryId: string, +): string => { + const deleted = store.deleteEntries(sessionId, [entryId]); + return deleted > 0 + ? `已删除 ${deleted} 条消息(含后续回复)。` + : '未找到该消息。'; +}; + +/** 删除整个会话并返回给用户的反馈文本 */ +export const deleteSessionCommand = ( + store: SessionStoreLike, + sessionId: string, +): string => + store.deleteSession(sessionId) ? '已删除整个会话。' : '未找到该会话。'; + +/** 带参数命令的用法提示 */ +export const sessionCommandUsage = ( + command: 'session' | 'delmsg' | 'delsession', +): string => { + if (command === 'session') { + return '用法: /session '; + } + if (command === 'delmsg') { + return '用法: /delmsg '; + } + return '用法: /delsession '; +}; diff --git a/tests/agent-session-runloop.spec.ts b/tests/agent-session-runloop.spec.ts index 2a4ee6c..78e5d98 100644 --- a/tests/agent-session-runloop.spec.ts +++ b/tests/agent-session-runloop.spec.ts @@ -49,12 +49,15 @@ describe('runLoop SQLite 持久化', () => { const res = await runLoop(task, {}, { persist: true }); expect(res).toBe('done'); - expect(store.getEntries(task.id).map((entry) => entry.type)).toEqual([ + const entries = store.getEntries(task.id); + expect(entries.map((entry) => entry.type)).toEqual([ 'user', 'assistant', 'tool', 'assistant', ]); + expect(entries[1].payload).toMatchObject({ content: 'inspect environment' }); + expect(entries[3].payload).toMatchObject({ content: 'done' }); expect(store.getRecords(task.id).map((record) => record.type)).toEqual([ 'tool_call', 'tool_result', diff --git a/tests/cli-session-commands.spec.ts b/tests/cli-session-commands.spec.ts new file mode 100644 index 0000000..bf0f7d4 --- /dev/null +++ b/tests/cli-session-commands.spec.ts @@ -0,0 +1,126 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { SessionStore } from '../packages/session-sqlite/src/index'; +import { + deleteMessagesCommand, + deleteSessionCommand, + formatSessionEntries, + formatSessionList, + sessionCommandUsage, +} from '../source/session-commands'; + +describe('CLI 会话删除命令', () => { + const stores: SessionStore[] = []; + + const createStore = () => { + const store = new SessionStore({ dbPath: ':memory:' }); + stores.push(store); + return store; + }; + + afterEach(() => { + for (const store of stores.splice(0)) { + store.close(); + } + }); + + it('列出会话和会话消息', () => { + const store = createStore(); + store.createSession({ + cwd: '/tmp/project', + id: 's-cli', + metadata: { objective: '修复登录问题' }, + }); + store.appendEntry('s-cli', { + id: 'e1', + type: 'user', + payload: { role: 'user', content: '修复登录问题' }, + }); + store.appendEntry('s-cli', { + id: 'e2', + parentId: 'e1', + type: 'assistant', + payload: { role: 'assistant', content: '开始排查' }, + }); + store.updateStats('s-cli', { + messageCount: 2, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + + const listText = formatSessionList(store); + expect(listText).toContain('s-cli'); + expect(listText).toContain('2 条'); + expect(listText).toContain('修复登录问题'); + + const entriesText = formatSessionEntries(store, 's-cli'); + expect(entriesText).toContain('e1'); + expect(entriesText).toContain('修复登录问题'); + expect(entriesText).toContain('开始排查'); + }); + + it('删除单条消息时连同后续回复一起删除', () => { + const store = createStore(); + store.createSession({ cwd: '/tmp/project', id: 's-cli-del' }); + store.appendEntry('s-cli-del', { + id: 'e1', + type: 'user', + payload: { role: 'user', content: 'q' }, + }); + store.appendEntry('s-cli-del', { + id: 'e2', + parentId: 'e1', + type: 'assistant', + payload: { role: 'assistant', content: 'a' }, + }); + store.appendEntry('s-cli-del', { + id: 'e3', + parentId: 'e2', + type: 'tool', + payload: { role: 'tool', content: 't' }, + }); + store.updateStats('s-cli-del', { + messageCount: 3, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + + expect(deleteMessagesCommand(store, 's-cli-del', 'e2')).toContain( + '已删除 2 条消息', + ); + expect(store.getEntries('s-cli-del').map((entry) => entry.id)).toEqual([ + 'e1', + ]); + expect(store.getStats('s-cli-del').messageCount).toBe(1); + expect(deleteMessagesCommand(store, 's-cli-del', 'missing')).toContain( + '未找到', + ); + }); + + it('删除整个会话', () => { + const store = createStore(); + store.createSession({ cwd: '/tmp/project', id: 's-cli-session' }); + store.appendEntry('s-cli-session', { + id: 'e1', + type: 'user', + payload: { role: 'user', content: 'q' }, + }); + + expect(deleteSessionCommand(store, 's-cli-session')).toContain('已删除'); + expect(store.listSessions()).toHaveLength(0); + expect(deleteSessionCommand(store, 'missing')).toContain('未找到'); + }); + + it('提供带参数命令的用法提示', () => { + expect(sessionCommandUsage('session')).toContain('/session '); + expect(sessionCommandUsage('delmsg')).toContain( + '/delmsg ', + ); + expect(sessionCommandUsage('delsession')).toContain( + '/delsession ', + ); + }); +}); diff --git a/tests/client-ui.spec.ts b/tests/client-ui.spec.ts index 8b428ff..6222e7a 100644 --- a/tests/client-ui.spec.ts +++ b/tests/client-ui.spec.ts @@ -3,6 +3,8 @@ import React from "react"; import { renderToStaticMarkup } from "react-dom/server"; import { HeaderBar } from "../packages/client/src/components/HeaderBar"; import { MainPanel } from "../packages/client/src/components/MainPanel"; +import { Sidebar } from "../packages/client/src/components/Sidebar"; +import { ConfirmDialog } from "../packages/client/src/components/ConfirmDialog"; import type { WebSession } from "../packages/client/src/types"; vi.mock("../packages/client/assets/call-code.png", () => ({ @@ -106,6 +108,7 @@ describe("client MainPanel", () => { session: makeSession(), filter: "all", onFilterChange: () => undefined, + onDeleteEntry: () => undefined, chatStatus: { status: "idle" }, onSendMessage: () => true, }), @@ -123,6 +126,7 @@ describe("client MainPanel", () => { session: makeSession(), filter: "all", onFilterChange: () => undefined, + onDeleteEntry: () => undefined, chatStatus: { status: "running", trace: "正在执行工具调用..." }, onSendMessage: () => true, }), @@ -131,3 +135,77 @@ describe("client MainPanel", () => { expect(html).toContain("正在执行工具调用..."); }); }); + +describe("client Sidebar", () => { + it("每个会话都渲染删除入口", () => { + const sessions = [makeSession(), makeSession({ id: "s2", entries: [] })]; + const html = renderToStaticMarkup( + React.createElement(Sidebar, { + sessions, + activeId: "s1", + query: "", + onSelect: () => undefined, + onQueryChange: () => undefined, + onDeleteSession: () => undefined, + onNewTopic: () => undefined, + }), + ); + + expect(html).toContain("开启新话题"); + expect(html.match(/aria-label="删除会话/g)).toHaveLength(2); + expect(html).toContain('title="删除会话"'); + expect(html).toContain('aria-label="删除会话 hello world"'); + expect(html).toContain('aria-label="删除会话 新会话"'); + }); + + it("顶部提供开启新话题入口", () => { + const html = renderToStaticMarkup( + React.createElement(Sidebar, { + sessions: [makeSession()], + activeId: "s1", + query: "", + onSelect: () => undefined, + onQueryChange: () => undefined, + onDeleteSession: () => undefined, + onNewTopic: () => undefined, + }), + ); + + expect(html).toContain("开启新话题"); + expect(html).toContain('type="button"'); + }); +}); + +describe("client ConfirmDialog", () => { + it("渲染删除确认标题、说明和操作按钮", () => { + const html = renderToStaticMarkup( + React.createElement(ConfirmDialog, { + open: true, + title: "删除会话", + description: "删除整个会话?此操作无法撤销。", + onConfirm: () => undefined, + onCancel: () => undefined, + }), + ); + + expect(html).toContain('role="alertdialog"'); + expect(html).toContain("删除会话"); + expect(html).toContain("删除整个会话?此操作无法撤销。"); + expect(html).toContain("取消"); + expect(html).toContain("删除"); + }); + + it("未打开时不渲染对话框", () => { + const html = renderToStaticMarkup( + React.createElement(ConfirmDialog, { + open: false, + title: "删除会话", + description: "删除整个会话?此操作无法撤销。", + onConfirm: () => undefined, + onCancel: () => undefined, + }), + ); + + expect(html).toBe(""); + }); +}); diff --git a/tests/client-utils.spec.ts b/tests/client-utils.spec.ts index e3c7be2..cc18525 100644 --- a/tests/client-utils.spec.ts +++ b/tests/client-utils.spec.ts @@ -69,7 +69,31 @@ describe('client utils', () => { expect(getSessionTitle(userSession)).toBe('用户任务'); expect(getSessionTitle(objectiveSession)).toBe('目标文本'); - expect(getSessionTitle(idSession)).toBe('s3'); + expect(getSessionTitle(idSession)).toBe('新会话'); + }); + + it('会话标题取用户输入前 20 个字并折叠空白', () => { + const longUserSession = makeSession({ + entries: [ + makeEntry({ + type: 'user', + role: 'user', + payload: { content: '请帮我重构这个项目的权限模块并且补充完整测试' }, + }), + ], + }); + const multilineSession = makeSession({ + entries: [ + makeEntry({ + type: 'user', + role: 'user', + payload: { content: '第一行\n 第二行' }, + }), + ], + }); + + expect(getSessionTitle(longUserSession)).toBe('请帮我重构这个项目的权限模块并且补充完整'); + expect(getSessionTitle(multilineSession)).toBe('第一行 第二行'); }); it('会话搜索覆盖 id、目录、消息内容和工具名', () => { diff --git a/tests/client-ws.spec.ts b/tests/client-ws.spec.ts index 8777a1c..9fde2d9 100644 --- a/tests/client-ws.spec.ts +++ b/tests/client-ws.spec.ts @@ -3,6 +3,7 @@ import { SessionStore } from '../packages/session-sqlite/src/index'; import type { WebExport } from '@call-code/server/client'; import { connectLiveExport, + parseCreatedSessionMessage, parseSnapshotMessage, } from '@call-code/server/client'; import { @@ -50,6 +51,19 @@ describe('客户端 WebSocket 适配', () => { ).toBeNull(); }); + it('解析 sessions.created 消息', () => { + expect( + parseCreatedSessionMessage({ + type: 'sessions.created', + sessionId: 's-new-topic', + }), + ).toBe('s-new-topic'); + expect(parseCreatedSessionMessage({ type: 'other' })).toBeNull(); + expect( + parseCreatedSessionMessage({ type: 'sessions.created', sessionId: 1 }), + ).toBeNull(); + }); + it('连接服务后收到快照并周期性刷新', async () => { const { store, handle } = await createServer(); store.createSession({ cwd: '/tmp/project', id: 's-client' }); @@ -80,4 +94,107 @@ describe('客户端 WebSocket 适配', () => { connection.close(); }); + + it('deleteMessages 删除消息和会话后同步快照', async () => { + const { store, handle } = await createServer(); + store.createSession({ cwd: '/tmp/project', id: 's-client-del' }); + store.appendEntry('s-client-del', { + id: 'e1', + type: 'user', + payload: { role: 'user', content: 'ping' }, + }); + store.appendEntry('s-client-del', { + id: 'e2', + parentId: 'e1', + type: 'assistant', + payload: { role: 'assistant', content: 'pong' }, + }); + store.appendEntry('s-client-del', { + id: 'e3', + parentId: 'e2', + type: 'tool', + payload: { role: 'tool', content: 'ok' }, + }); + store.updateStats('s-client-del', { + messageCount: 3, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + + const snapshots: WebExport[] = []; + const connection = connectLiveExport({ + url: `ws://127.0.0.1:${handle.port}/ws`, + timeoutMs: 3000, + refreshMs: 3000, + onSnapshot: (data) => snapshots.push(data), + }); + const first = await connection.ready; + expect(first?.sessions[0].entries).toHaveLength(3); + + connection.deleteMessages('s-client-del', ['e2']); + + const deadline = Date.now() + 2000; + while (Date.now() < deadline) { + const latest = snapshots[snapshots.length - 1]; + if ( + latest?.sessions[0].entries.every((entry) => entry.id !== 'e2') + ) { + break; + } + await new Promise((resolve) => setTimeout(resolve, 30)); + } + expect(snapshots[snapshots.length - 1].sessions[0].entries.map((entry) => entry.id)).toEqual([ + 'e1', + ]); + expect(snapshots[snapshots.length - 1].sessions[0].stats.messageCount).toBe(1); + + connection.deleteMessages('s-client-del'); + + const sessionDeadline = Date.now() + 2000; + while (Date.now() < sessionDeadline) { + const latest = snapshots[snapshots.length - 1]; + if (latest?.sessions.length === 0) { + break; + } + await new Promise((resolve) => setTimeout(resolve, 30)); + } + expect(snapshots[snapshots.length - 1].sessions).toHaveLength(0); + + connection.close(); + }); + + it('createSession 创建空会话并返回新会话 ID', async () => { + const { handle } = await createServer(); + const snapshots: WebExport[] = []; + const connection = connectLiveExport({ + url: `ws://127.0.0.1:${handle.port}/ws`, + timeoutMs: 3000, + refreshMs: 3000, + onSnapshot: (data) => snapshots.push(data), + }); + + const first = await connection.ready; + expect(first?.sessions).toHaveLength(0); + + const sessionId = await connection.createSession(); + expect(sessionId).toBeTruthy(); + + const deadline = Date.now() + 2000; + while (Date.now() < deadline) { + const latest = snapshots[snapshots.length - 1]; + if (latest?.sessions.some((session) => session.id === sessionId)) { + break; + } + await new Promise((resolve) => setTimeout(resolve, 30)); + } + + const latest = snapshots[snapshots.length - 1]; + expect(latest?.sessions).toHaveLength(1); + expect(latest?.sessions[0].id).toBe(sessionId); + expect(latest?.sessions[0].entries).toEqual([]); + + connection.close(); + }); }); diff --git a/tests/session-sqlite.spec.ts b/tests/session-sqlite.spec.ts index 330c2ff..88622fd 100644 --- a/tests/session-sqlite.spec.ts +++ b/tests/session-sqlite.spec.ts @@ -134,4 +134,88 @@ describe('SessionStore', () => { expect(store.releaseLease('s5', 'owner-a')).toBe(true); expect(store.getLease('s5')).toBeNull(); }); + + it('删除消息时同步清理后代、泳道、分支指针和统计', () => { + const store = createStore(); + store.createSession({ cwd: '/tmp/project', id: 's-del' }); + store.appendEntry('s-del', { id: 'e1', type: 'user', payload: { content: 'q' } }); + store.appendEntry('s-del', { + id: 'e2', + parentId: 'e1', + type: 'assistant', + payload: { content: 'a' }, + }); + store.appendEntry('s-del', { + id: 'e3', + parentId: 'e2', + type: 'tool', + payload: { content: 't' }, + }); + store.appendEntry('s-del', { + id: 'e4', + parentId: 'e1', + type: 'assistant', + payload: { content: 'b' }, + branchId: 'feature', + }); + store.updateStats('s-del', { + messageCount: 4, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + + const deleted = store.deleteEntries('s-del', ['e1']); + + expect(deleted).toBe(3); + expect(store.getEntries('s-del').map((entry) => entry.id)).toEqual(['e4']); + expect(store.getStats('s-del').messageCount).toBe(1); + expect(store.getLane('s-del', DEFAULT_LANE)?.leafId).toBe('e4'); + expect(store.listBranchTips('s-del').map((tip) => tip.branchId)).toEqual([ + 'feature', + ]); + expect(store.getBranchEntries('s-del', 'feature').map((item) => item.entryId)).toEqual([ + 'e4', + ]); + expect(store.deleteEntries('s-del', ['missing'])).toBe(0); + }); + + it('删除整个会话时清理全部关联数据', () => { + const store = createStore(); + store.createSession({ cwd: '/tmp/project', id: 's-del-session' }); + store.appendEntry('s-del-session', { + id: 'e1', + type: 'user', + payload: { content: 'q' }, + }); + store.appendRecord('s-del-session', { + id: 'r1', + lane: 'run', + runId: 'run-1', + type: 'tool_call', + payload: { tool: 'read_file' }, + }); + store.addFact('s-del-session', { kind: 'task-objective', value: 'demo' }); + store.updateStats('s-del-session', { + messageCount: 1, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + store.acquireLease('s-del-session', 'owner-a', 60_000); + + expect(store.deleteSession('s-del-session')).toBe(true); + expect(store.deleteSession('missing-session')).toBe(false); + expect(store.getSession('s-del-session')).toBeNull(); + expect(store.listSessions()).toHaveLength(0); + expect(store.getEntries('s-del-session')).toEqual([]); + expect(store.getRecords('s-del-session')).toEqual([]); + expect(store.listFacts('s-del-session')).toEqual([]); + expect(store.getLane('s-del-session', DEFAULT_LANE)).toBeNull(); + expect(store.listBranchTips('s-del-session')).toEqual([]); + expect(store.getStats('s-del-session').messageCount).toBe(0); + expect(store.getLease('s-del-session')).toBeNull(); + }); }); diff --git a/tests/web-server.spec.ts b/tests/web-server.spec.ts index ad24084..dce6483 100644 --- a/tests/web-server.spec.ts +++ b/tests/web-server.spec.ts @@ -11,7 +11,7 @@ vi.mock("../packages/agent-core/src/harness/runtime/run-loop", () => ({ handlers?.onTrace?.("正在检索本地上下文"); if (options?.persist && options?.sessionStore) { const store = options.sessionStore; - store.createSession({ id: task.id, cwd: task.workspace || "/tmp" }); + store.replaceSession({ id: task.id, cwd: task.workspace || "/tmp" }); store.appendEntry(task.id, { type: "assistant", payload: { role: "assistant", content: "已处理完毕" }, @@ -74,6 +74,124 @@ const requestSnapshot = (url: string): Promise => }); }); +const requestDelete = ( + url: string, + sessionId: string, + entryIds?: string[], +): Promise => + new Promise((resolve, reject) => { + const socket = new WebSocket(url); + const timeout = setTimeout(() => { + socket.terminate(); + reject(new Error("等待删除快照超时")); + }, 3000); + + socket.on("open", () => { + socket.send( + JSON.stringify({ + type: "sessions.delete", + sessionId, + ...(entryIds && entryIds.length > 0 ? { entryIds } : {}), + }), + ); + }); + socket.on("message", (raw) => { + const message = JSON.parse(String(raw)) as { + type?: string; + data?: WebExport; + }; + if (message.type === "sessions.snapshot" && message.data) { + clearTimeout(timeout); + socket.close(); + resolve(message.data); + } + }); + socket.on("error", (error) => { + clearTimeout(timeout); + reject(error); + }); + }); + +const requestCreate = ( + url: string, +): Promise<{ sessionId: string; snapshot: WebExport }> => + new Promise((resolve, reject) => { + const socket = new WebSocket(url); + const timeout = setTimeout(() => { + socket.terminate(); + reject(new Error("等待新建会话超时")); + }, 3000); + let createdId = ""; + + socket.on("open", () => { + socket.send(JSON.stringify({ type: "sessions.create" })); + }); + socket.on("message", (raw) => { + const message = JSON.parse(String(raw)) as { + type?: string; + sessionId?: string; + data?: WebExport; + }; + if ( + message.type === "sessions.created" && + typeof message.sessionId === "string" + ) { + createdId = message.sessionId; + } + if (message.type === "sessions.snapshot" && message.data && createdId) { + clearTimeout(timeout); + socket.close(); + resolve({ sessionId: createdId, snapshot: message.data }); + } + }); + socket.on("error", (error) => { + clearTimeout(timeout); + reject(error); + }); + }); + +const requestChat = ( + url: string, + payload: { input: string; sessionId?: string }, +): Promise<{ statuses: unknown[]; snapshot: WebExport | null }> => + new Promise((resolve, reject) => { + const socket = new WebSocket(url); + const timeout = setTimeout(() => { + socket.terminate(); + reject(new Error("等待任务执行超时")); + }, 4000); + const statuses: unknown[] = []; + let snapshot: WebExport | null = null; + + socket.on("open", () => { + socket.send(JSON.stringify({ type: "chat.send", ...payload })); + }); + socket.on("message", (raw) => { + const message = JSON.parse(String(raw)) as { + type?: string; + status?: string; + data?: WebExport; + }; + if (message.type === "chat.status") { + statuses.push(message); + } else if (message.type === "sessions.snapshot" && message.data) { + snapshot = message.data; + } + if ( + snapshot && + statuses.some((item: any) => item.status === "success") + ) { + clearTimeout(timeout); + socket.close(); + resolve({ statuses, snapshot }); + } + }); + socket.on("error", (error) => { + clearTimeout(timeout); + reject(error); + }); + }); + describe("WebSocket 会话服务", () => { it("响应 sessions.list 并返回完整会话快照", async () => { const { store, handle } = await createServer(); @@ -154,6 +272,90 @@ describe("WebSocket 会话服务", () => { expect((snapshotReceived as unknown as WebExport).sessions.length).toBeGreaterThan(0); }); + it("响应 sessions.create 创建空会话并回传快照", async () => { + const { handle } = await createServer(); + + const result = await requestCreate(`ws://127.0.0.1:${handle.port}/ws`); + + expect(result.sessionId).toBeTruthy(); + expect(result.snapshot.sessions).toHaveLength(1); + expect(result.snapshot.sessions[0].id).toBe(result.sessionId); + expect(result.snapshot.sessions[0].entries).toEqual([]); + }); + + it("chat.send 携带 sessionId 时续写同一会话", async () => { + const { handle } = await createServer(); + const url = `ws://127.0.0.1:${handle.port}/ws`; + + const first = await requestChat(url, { + input: "第一轮", + sessionId: "s-continue", + }); + const second = await requestChat(url, { + input: "第二轮", + sessionId: "s-continue", + }); + + expect(first.snapshot?.sessions[0].id).toBe("s-continue"); + expect( + second.snapshot?.sessions.filter((session) => session.id === "s-continue"), + ).toHaveLength(1); + expect( + second.snapshot?.sessions[0].entries.filter( + (entry) => entry.text === "已处理完毕", + ), + ).toHaveLength(2); + }); + + it("响应 sessions.delete 删除单条消息并回传快照", async () => { + const { store, handle } = await createServer(); + store.createSession({ cwd: "/tmp/project", id: "s-del-ws" }); + store.appendEntry("s-del-ws", { + id: "e1", + type: "user", + payload: { role: "user", content: "hello" }, + }); + store.appendEntry("s-del-ws", { + id: "e2", + parentId: "e1", + type: "assistant", + payload: { role: "assistant", content: "hi" }, + }); + store.updateStats("s-del-ws", { + messageCount: 2, + cachedTokens: 0, + uncachedTokens: 0, + totalTokens: 0, + costTotal: 0, + }); + + const data = await requestDelete( + `ws://127.0.0.1:${handle.port}/ws`, + "s-del-ws", + ["e2"], + ); + + expect(data.sessions[0].entries.map((entry) => entry.id)).toEqual(["e1"]); + expect(data.sessions[0].stats.messageCount).toBe(1); + }); + + it("响应 sessions.delete 删除整个会话", async () => { + const { store, handle } = await createServer(); + store.createSession({ cwd: "/tmp/project", id: "s-del-session-ws" }); + store.appendEntry("s-del-session-ws", { + id: "e1", + type: "user", + payload: { role: "user", content: "hello" }, + }); + + const data = await requestDelete( + `ws://127.0.0.1:${handle.port}/ws`, + "s-del-session-ws", + ); + + expect(data.sessions).toHaveLength(0); + }); + it("无法解析的消息返回 error 响应", async () => { const { handle } = await createServer(); const message = await new Promise((resolve, reject) => {