@open-mercato/ui 0.6.6-develop.6184.1.b7e55f8d61 → 0.6.6-develop.6198.1.0822f97cc6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build.log +1 -1
- package/dist/ai/AiChat.js +1 -28
- package/dist/ai/AiChat.js.map +2 -2
- package/dist/ai/AiChatSessions.js +35 -32
- package/dist/ai/AiChatSessions.js.map +2 -2
- package/dist/ai/modelPickerStorage.js +29 -0
- package/dist/ai/modelPickerStorage.js.map +7 -0
- package/dist/ai/useAiChat.js.map +2 -2
- package/dist/backend/AppShell.js +24 -19
- package/dist/backend/AppShell.js.map +2 -2
- package/dist/backend/DataTable.js +26 -23
- package/dist/backend/DataTable.js.map +2 -2
- package/dist/backend/notifications/useNotificationActions.js +5 -5
- package/dist/backend/notifications/useNotificationActions.js.map +2 -2
- package/dist/backend/progress/ProgressTopBar.js.map +2 -2
- package/package.json +3 -3
- package/src/ai/AiChat.tsx +1 -37
- package/src/ai/AiChatSessions.tsx +54 -48
- package/src/ai/__tests__/AiChat.modelPickerStorage.test.ts +40 -0
- package/src/ai/__tests__/AiChatSessions.storage.test.ts +42 -0
- package/src/ai/__tests__/AiChatSessions.test.tsx +10 -6
- package/src/ai/modelPickerStorage.ts +40 -0
- package/src/ai/useAiChat.ts +7 -0
- package/src/backend/AppShell.tsx +41 -17
- package/src/backend/DataTable.tsx +34 -29
- package/src/backend/__tests__/DataTable.perspectiveStorage.test.ts +44 -0
- package/src/backend/notifications/__tests__/useNotificationActions.failedWrites.test.tsx +119 -0
- package/src/backend/notifications/useNotificationActions.ts +5 -5
- package/src/backend/progress/ProgressTopBar.tsx +4 -0
package/dist/ai/useAiChat.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../src/ai/useAiChat.ts"],
|
|
4
|
-
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createAiAgentTransport } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/agent-transport'\nimport { apiFetch } from '../backend/utils/api'\nimport type { LoopTracePanelTrace } from './LoopTracePanel'\nimport {\n createAiServerConversation,\n importAiLocalConversation,\n loadAiServerTranscript,\n serverMessageToChatMessage,\n} from './conversation-store'\n\n/**\n * Chat message shape used by {@link AiChat}. Kept intentionally minimal so the\n * component stays independent of the AI SDK's evolving `UIMessage` type. The\n * dispatcher route (`POST /api/ai_assistant/ai/chat`) accepts exactly this\n * shape for `messages`.\n */\nexport interface AiChatMessageFile {\n id?: string\n name: string\n type: string\n previewUrl?: string\n}\n\nexport interface AiChatToolCallSnapshot {\n id: string\n toolName: string\n caption?: string\n state: 'pending' | 'complete' | 'error'\n input?: unknown\n output?: unknown\n errorMessage?: string\n}\n\nexport interface AiChatMessageUiPart {\n componentId: string\n payload?: unknown\n pendingActionId?: string\n /** Stable id used as React key when rendering. */\n key: string\n}\n\n/**\n * Snapshot of a single task in the visible agent task plan\n * (spec `.ai/specs/implemented/2026-05-13-ai-chat-visible-task-plan.md`). Tasks are\n * streamed as additive `data-agent-task-plan` / `data-agent-task-update`\n * SSE chunks and rendered above raw tool-call rows in `<AiChat>`.\n *\n * `source: 'runtime'` tasks are derived from tool lifecycle events by the\n * agent runtime. `source: 'agent'` tasks are emitted through the reserved\n * `meta.update_task_plan` helper and sanitized before they reach the client.\n */\nexport interface AiAgentTaskSnapshot {\n id: string\n label: string\n state: 'pending' | 'running' | 'done' | 'failed' | 'skipped'\n detail?: string\n source: 'runtime' | 'agent'\n toolCallId?: string\n}\n\nexport interface AiChatMessage {\n id: string\n role: 'user' | 'assistant'\n content: string\n files?: AiChatMessageFile[]\n reasoning?: string\n reasoningStreaming?: boolean\n toolCalls?: AiChatToolCallSnapshot[]\n /** User ID of the message author. Present for server-loaded messages; null for AI messages and locally-composed messages. */\n senderUserId?: string | null\n /**\n * UI parts emitted by the agent during this message's lifecycle. Today\n * the only producer is `prepareMutation` (mutation approval flow):\n * the dispatcher's mutation tool returns an `awaiting-confirmation`\n * envelope, useAiChat parses it and attaches a `mutation-preview-card`\n * part here so AiChat can render the approval card inline. Phase 3\n * WS-C wiring \u2014 without this, the `MutationPreviewCard` registered in\n * the UI-part registry never surfaces.\n */\n uiParts?: AiChatMessageUiPart[]\n /**\n * Client-local visible task plan derived from streamed\n * `data-agent-task-plan` snapshots and `data-agent-task-update` deltas.\n * Not persisted in any chat storage payload.\n */\n taskPlan?: AiAgentTaskSnapshot[]\n}\n\nexport interface UseAiChatInput {\n agent: string\n apiPath?: string\n pageContext?: Record<string, unknown>\n attachmentIds?: string[]\n debug?: boolean\n initialMessages?: Array<Pick<AiChatMessage, 'role' | 'content'>>\n onError?: (err: { code?: string; message: string }) => void\n /**\n * Optional stable conversation id. When provided, the same id is forwarded\n * to the dispatcher on every turn so `prepareMutation`'s idempotency hash\n * (Step 5.6) stays stable across mutation preview / confirm / retry cycles.\n * When omitted, the hook mints a fresh random id once on mount and reuses\n * it for the lifetime of the component \u2014 callers can still override via\n * props at any time to reset the conversation.\n */\n conversationId?: string\n /**\n * Runtime provider override (4b.2). Forwarded as `?provider=` query param\n * on every POST to the dispatcher. Undefined/null means use the agent's\n * configured default (no override sent).\n */\n providerOverride?: string | null\n /**\n * Runtime model override (4b.2). Forwarded as `?model=` query param\n * on every POST to the dispatcher. Undefined/null means use the agent's\n * configured default (no override sent).\n */\n modelOverride?: string | null\n /**\n * Called when the server returns 404 for the active `conversationId`.\n * Used by hosts to remove a stale session entry (e.g. the AI dock\n * closes the session tab) when a conversation no longer exists for the\n * current tenant/org scope. A 404 here distinguishes a missing\n * conversation from a transient transport failure \u2014 the hook never\n * imports local messages onto the new scope when this fires.\n */\n onConversationNotFound?: () => void\n}\n\nexport interface AiChatErrorEnvelope {\n code?: string\n message: string\n}\n\nexport interface UseAiChatResult {\n messages: AiChatMessage[]\n status: 'idle' | 'submitting' | 'streaming'\n error: AiChatErrorEnvelope | null\n lastRequestDebug: { url: string; body: unknown } | null\n lastResponseDebug: { status: number; text: string } | null\n /**\n * The conversation id currently in use for this chat instance. Equal to\n * the caller-provided `conversationId` input when one is supplied;\n * otherwise the random id minted on mount. Stable across re-renders for a\n * given mount (Phase 3 WS-D contract with `prepareMutation`).\n */\n conversationId: string\n /**\n * Loop trace from the last completed turn. Populated when the dispatcher\n * emits a `loop-finish` SSE event at the end of the response stream.\n * `null` until the first turn completes or when the dispatcher does not\n * emit trace events (non-debug deployments may omit them).\n *\n * Phase 4 of spec `2026-04-28-ai-agents-agentic-loop-controls`.\n */\n lastLoopTrace: LoopTracePanelTrace | null\n /**\n * Whether the authenticated caller owns the loaded conversation. `true` for owners,\n * `false` for shared participants (read-only view), `null` while unresolved.\n */\n isOwner: boolean | null\n sendMessage: (input: string, files?: AiChatMessageFile[]) => Promise<void>\n cancel: () => void\n reset: () => void\n}\n\nfunction makeMessageId(): string {\n const random = Math.random().toString(36).slice(2, 10)\n const time = Date.now().toString(36)\n return `msg_${time}_${random}`\n}\n\nfunction makeConversationId(): string {\n // Use crypto.randomUUID() when the browser exposes it (all evergreen\n // runtimes do), otherwise fall back to a low-entropy token that is still\n // unique enough for the idempotency-hash use case.\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto && typeof g.crypto.randomUUID === 'function') {\n try {\n return g.crypto.randomUUID()\n } catch {\n // fall through to the random fallback\n }\n }\n const rand = () => Math.random().toString(16).slice(2, 10)\n return `conv_${Date.now().toString(16)}_${rand()}${rand()}`\n}\n\nconst SESSION_STORAGE_PREFIX = 'om-ai-chat:'\nconst SESSION_IMPORT_MARKER_PREFIX = 'om-ai-chat-imported:'\nconst SESSION_STORAGE_VERSION = 1\nconst SESSION_UPDATED_EVENT = 'om-ai-chat-session-updated'\nconst SESSION_STREAM_STATE_EVENT = 'om-ai-chat-session-stream-state'\nconst activeSessionStreams = new Set<string>()\n\ninterface PersistedAiChatSession {\n v: number\n conversationId: string\n messages: AiChatMessage[]\n}\n\nfunction getSessionStorageKey(agent: string, conversationId?: string | null): string {\n // When the caller pins a `conversationId` (e.g. via the AiChatSessions\n // provider's tabs), namespace the persisted slot per session so multiple\n // open conversations for the same agent don't overwrite each other. The\n // legacy single-session-per-agent layout (no externally-supplied id) is\n // kept for backward compatibility with code that still relies on it.\n if (typeof conversationId === 'string' && conversationId.length > 0) {\n return `${SESSION_STORAGE_PREFIX}${agent}:${conversationId}`\n }\n return `${SESSION_STORAGE_PREFIX}${agent}`\n}\n\nfunction writeSessionStreamState(storageKey: string, active: boolean): void {\n if (active) {\n activeSessionStreams.add(storageKey)\n } else {\n activeSessionStreams.delete(storageKey)\n }\n if (typeof window === 'undefined') return\n try {\n window.dispatchEvent(\n new CustomEvent(SESSION_STREAM_STATE_EVENT, {\n detail: { key: storageKey, active },\n }),\n )\n } catch {\n // ignore event dispatch failures in non-browser test environments\n }\n}\n\nfunction isSessionStreamActive(storageKey: string): boolean {\n return activeSessionStreams.has(storageKey)\n}\n\nfunction readPersistedSession(\n agent: string,\n conversationId?: string | null,\n): PersistedAiChatSession | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.localStorage.getItem(getSessionStorageKey(agent, conversationId))\n if (!raw) return null\n const parsed = JSON.parse(raw) as PersistedAiChatSession | null\n if (!parsed || parsed.v !== SESSION_STORAGE_VERSION) return null\n if (typeof parsed.conversationId !== 'string') return null\n if (!Array.isArray(parsed.messages)) return null\n const messages = parsed.messages.filter((entry): entry is AiChatMessage => {\n return (\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as AiChatMessage).id === 'string' &&\n typeof (entry as AiChatMessage).content === 'string' &&\n ((entry as AiChatMessage).role === 'user' || (entry as AiChatMessage).role === 'assistant')\n )\n })\n return { v: SESSION_STORAGE_VERSION, conversationId: parsed.conversationId, messages }\n } catch {\n return null\n }\n}\n\nfunction writePersistedSession(\n agent: string,\n session: PersistedAiChatSession,\n conversationId?: string | null,\n options?: { notify?: boolean },\n): void {\n if (typeof window === 'undefined') return\n try {\n // Strip transient blob/object preview URLs before persisting (they would\n // not survive a reload). Self-contained `data:` URLs and same-origin\n // attachment thumbnail URLs are durable enough for local fallback storage.\n const messages = session.messages.map((message) => {\n if (!message.files || message.files.length === 0) return message\n const safeFiles = message.files.map(({ id, name, type, previewUrl }) => {\n const durable =\n typeof previewUrl === 'string' &&\n (previewUrl.startsWith('data:') ||\n previewUrl.startsWith('/api/attachments/image/') ||\n previewUrl.startsWith('/api/attachments/file/'))\n ? previewUrl\n : undefined\n const base = id ? { id, name, type } : { name, type }\n return durable ? { ...base, previewUrl: durable } : base\n })\n return { ...message, files: safeFiles }\n })\n const storageKey = getSessionStorageKey(agent, conversationId)\n window.localStorage.setItem(storageKey, JSON.stringify({ ...session, messages }))\n if (options?.notify) {\n window.dispatchEvent(\n new CustomEvent(SESSION_UPDATED_EVENT, {\n detail: { key: storageKey, agent, conversationId: session.conversationId },\n }),\n )\n }\n } catch {\n // Quota exceeded / privacy mode \u2014 silently drop persistence.\n }\n}\n\nfunction clearPersistedSession(agent: string, conversationId?: string | null): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.removeItem(getSessionStorageKey(agent, conversationId))\n } catch {\n // ignore\n }\n}\n\nfunction getImportMarkerKey(agent: string, conversationId: string): string {\n return `${SESSION_IMPORT_MARKER_PREFIX}${agent}:${conversationId}`\n}\n\nfunction hasImportMarker(agent: string, conversationId: string): boolean {\n if (typeof window === 'undefined') return false\n try {\n return window.localStorage.getItem(getImportMarkerKey(agent, conversationId)) === '1'\n } catch {\n return false\n }\n}\n\nfunction writeImportMarker(agent: string, conversationId: string): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(getImportMarkerKey(agent, conversationId), '1')\n } catch {\n // ignore local marker failures; the server import remains authoritative\n }\n}\n\nfunction getTransportEndpoint(\n agent: string,\n apiPath?: string,\n providerOverride?: string | null,\n modelOverride?: string | null,\n): string {\n // Reuse the transport factory so UI consumers share the dispatcher URL\n // convention with server-side callers (e.g. runAiAgentText / Playwright\n // fixtures). The factory returns a ChatTransport<UI_MESSAGE> whose internal\n // endpoint we do not directly read \u2014 instead we reconstruct the same URL\n // shape here so downstream error handling stays deterministic.\n //\n // When the AI SDK exposes a public endpoint getter (or the stream format\n // switches from plain text to UIMessageChunk) we can call\n // transport.sendMessages(...) directly.\n const transport = createAiAgentTransport({ agentId: agent, endpoint: apiPath })\n void transport\n const base = apiPath && apiPath.length > 0 ? apiPath : '/api/ai_assistant/ai/chat'\n const separator = base.includes('?') ? '&' : '?'\n let url = `${base}${separator}agent=${encodeURIComponent(agent)}`\n if (providerOverride) {\n url += `&provider=${encodeURIComponent(providerOverride)}`\n }\n if (modelOverride) {\n url += `&model=${encodeURIComponent(modelOverride)}`\n }\n return url\n}\n\ninterface AssistantBuilderState {\n text: string\n reasoning: string\n reasoningStreaming: boolean\n toolCalls: AiChatToolCallSnapshot[]\n toolCallCaptions: Record<string, string>\n internalToolCallIds: string[]\n uiParts: AiChatMessageUiPart[]\n taskPlan: AiAgentTaskSnapshot[]\n}\n\nfunction createBuilder(): AssistantBuilderState {\n return {\n text: '',\n reasoning: '',\n reasoningStreaming: false,\n toolCalls: [],\n toolCallCaptions: {},\n internalToolCallIds: [],\n uiParts: [],\n taskPlan: [],\n }\n}\n\nconst VALID_TASK_STATES: ReadonlySet<AiAgentTaskSnapshot['state']> = new Set([\n 'pending',\n 'running',\n 'done',\n 'failed',\n 'skipped',\n])\nconst VALID_TASK_SOURCES: ReadonlySet<AiAgentTaskSnapshot['source']> = new Set(['runtime', 'agent'])\n\nfunction coerceTaskSnapshot(raw: unknown): AiAgentTaskSnapshot | null {\n if (!raw || typeof raw !== 'object') return null\n const value = raw as Record<string, unknown>\n const id = typeof value.id === 'string' ? value.id.trim() : ''\n if (!id) return null\n const label = typeof value.label === 'string' ? value.label : ''\n if (!label) return null\n const state = VALID_TASK_STATES.has(value.state as AiAgentTaskSnapshot['state'])\n ? (value.state as AiAgentTaskSnapshot['state'])\n : 'pending'\n const source = VALID_TASK_SOURCES.has(value.source as AiAgentTaskSnapshot['source'])\n ? (value.source as AiAgentTaskSnapshot['source'])\n : 'runtime'\n const detail = typeof value.detail === 'string' && value.detail.length > 0 ? value.detail : undefined\n const toolCallId =\n typeof value.toolCallId === 'string' && value.toolCallId.length > 0 ? value.toolCallId : undefined\n return { id, label, state, source, detail, toolCallId }\n}\n\nconst TERMINAL_TASK_STATES: ReadonlySet<AiAgentTaskSnapshot['state']> = new Set([\n 'done',\n 'failed',\n 'skipped',\n])\n\nfunction mergeTaskSnapshot(\n current: AiAgentTaskSnapshot[],\n incoming: AiAgentTaskSnapshot,\n): AiAgentTaskSnapshot[] {\n const idx = current.findIndex((task) => task.id === incoming.id)\n if (idx === -1) return [...current, incoming]\n const prior = current[idx]\n // Stream ordering safeguard: once a task reaches a terminal state we keep\n // it terminal so a late \"running\" event cannot revert it (spec \u00A7Risks \u2014\n // \"Stream ordering bugs cause stale statuses\").\n const nextState = TERMINAL_TASK_STATES.has(prior.state) ? prior.state : incoming.state\n const merged: AiAgentTaskSnapshot = {\n ...prior,\n ...incoming,\n state: nextState,\n }\n const next = current.slice()\n next[idx] = merged\n return next\n}\n\n/**\n * Generic extractor for UI parts emitted by tool outputs. A tool can\n * surface inline UI to the chat by returning JSON in any of these\n * shapes \u2014 each tool call produces zero or more UI parts:\n *\n * 1. The dispatcher's mutation envelope:\n * `{ status: 'awaiting-confirmation', pendingActionId, expiresAt,\n * agent, toolName, message }`\n * \u2192 synthesizes a `mutation-preview-card` part (the registered\n * card fetches the live diff via `useAiPendingActionPolling`).\n *\n * 2. A single explicit UI part:\n * `{ uiPart: { componentId, payload?, pendingActionId? } }`\n *\n * 3. Multiple explicit UI parts:\n * `{ uiParts: [{ componentId, payload? }, ...] }`\n *\n * Tool authors only need to JSON-encode an object whose `uiPart` /\n * `uiParts` reference component ids that the host has registered on\n * `defaultAiUiPartRegistry` (or a scoped registry passed through\n * `<AiChat registry={...}/>`). Unknown component ids fall back to the\n * `UnknownUiPartPlaceholder` so an unregistered id never blows up the\n * transcript.\n */\nfunction extractUiPartsFromOutput(\n output: unknown,\n toolCallId: string,\n): AiChatMessageUiPart[] {\n let parsed: unknown = output\n if (typeof output === 'string') {\n const trimmed = output.trim()\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) return []\n try {\n parsed = JSON.parse(trimmed)\n } catch {\n return []\n }\n }\n if (!parsed || typeof parsed !== 'object') return []\n const value = parsed as Record<string, unknown>\n const parts: AiChatMessageUiPart[] = []\n\n // (1) Mutation approval envelope. The dispatcher's `prepareMutation`\n // interceptor in `agent-tools.ts` formats the result via\n // `formatPendingActionToolResult` as\n // { status: 'pending-confirmation', agentId, toolName, pendingActionId,\n // expiresAt, message }\n // (NOTE: status is `pending-confirmation` and the field is `agentId`,\n // not `agent`). We also accept `awaiting-confirmation` / `agent` for\n // forward compat with older / alternative dispatchers.\n if (value.status === 'pending-confirmation' || value.status === 'awaiting-confirmation') {\n const pendingActionId =\n typeof value.pendingActionId === 'string' && value.pendingActionId.length > 0\n ? value.pendingActionId\n : null\n if (pendingActionId) {\n const agentId =\n typeof value.agentId === 'string'\n ? value.agentId\n : typeof value.agent === 'string'\n ? value.agent\n : undefined\n parts.push({\n componentId: 'mutation-preview-card',\n pendingActionId,\n payload: {\n pendingActionId,\n expiresAt: typeof value.expiresAt === 'string' ? value.expiresAt : undefined,\n agentId,\n toolName: typeof value.toolName === 'string' ? value.toolName : undefined,\n },\n key: `${toolCallId}:mutation-preview-card`,\n })\n }\n }\n\n // (2) Explicit single UI part.\n if (value.uiPart && typeof value.uiPart === 'object') {\n const part = value.uiPart as Record<string, unknown>\n if (typeof part.componentId === 'string' && part.componentId.length > 0) {\n parts.push({\n componentId: part.componentId,\n payload: part.payload,\n pendingActionId:\n typeof part.pendingActionId === 'string' ? part.pendingActionId : undefined,\n key: `${toolCallId}:${part.componentId}`,\n })\n }\n }\n\n // (3) Explicit list of UI parts.\n if (Array.isArray(value.uiParts)) {\n value.uiParts.forEach((entry, index) => {\n if (!entry || typeof entry !== 'object') return\n const part = entry as Record<string, unknown>\n if (typeof part.componentId !== 'string' || part.componentId.length === 0) return\n parts.push({\n componentId: part.componentId,\n payload: part.payload,\n pendingActionId:\n typeof part.pendingActionId === 'string' ? part.pendingActionId : undefined,\n key: `${toolCallId}:${index}:${part.componentId}`,\n })\n })\n }\n\n return parts\n}\n\nfunction updateToolCall(\n state: AssistantBuilderState,\n id: string,\n patch: Partial<AiChatToolCallSnapshot> & { toolName?: string },\n): AssistantBuilderState {\n if (!id) return state\n const idx = state.toolCalls.findIndex((entry) => entry.id === id)\n if (idx === -1) {\n const next: AiChatToolCallSnapshot = {\n id,\n toolName: patch.toolName ?? 'tool',\n caption: patch.caption ?? state.toolCallCaptions[id],\n state: patch.state ?? 'pending',\n input: patch.input,\n output: patch.output,\n errorMessage: patch.errorMessage,\n }\n return { ...state, toolCalls: [...state.toolCalls, next] }\n }\n const current = state.toolCalls[idx]\n const merged: AiChatToolCallSnapshot = {\n ...current,\n toolName: patch.toolName ?? current.toolName,\n caption: patch.caption ?? current.caption,\n state: patch.state ?? current.state,\n input: patch.input !== undefined ? patch.input : current.input,\n output: patch.output !== undefined ? patch.output : current.output,\n errorMessage: patch.errorMessage ?? current.errorMessage,\n }\n const nextCalls = state.toolCalls.slice()\n nextCalls[idx] = merged\n return { ...state, toolCalls: nextCalls }\n}\n\nfunction updateToolCallCaption(\n state: AssistantBuilderState,\n toolCallId: string | undefined,\n caption: string,\n): AssistantBuilderState {\n if (!toolCallId || !caption) return state\n const toolCallCaptions = { ...state.toolCallCaptions, [toolCallId]: caption }\n const idx = state.toolCalls.findIndex((entry) => entry.id === toolCallId)\n if (idx === -1) return { ...state, toolCallCaptions }\n const nextCalls = state.toolCalls.slice()\n nextCalls[idx] = { ...nextCalls[idx], caption }\n return { ...state, toolCallCaptions, toolCalls: nextCalls }\n}\n\nfunction displayToolName(toolName: unknown): string | undefined {\n if (typeof toolName !== 'string') return undefined\n return toolName.replace(/__/g, '.')\n}\n\nfunction isInternalTaskPlanTool(toolName: unknown): boolean {\n return displayToolName(toolName) === 'meta.update_task_plan'\n}\n\nfunction markInternalToolCall(state: AssistantBuilderState, toolCallId: unknown): AssistantBuilderState {\n const id = String(toolCallId ?? '')\n if (!id || state.internalToolCallIds.includes(id)) return state\n return { ...state, internalToolCallIds: [...state.internalToolCallIds, id] }\n}\n\nfunction isInternalToolCallId(state: AssistantBuilderState, toolCallId: unknown): boolean {\n const id = String(toolCallId ?? '')\n return id.length > 0 && state.internalToolCallIds.includes(id)\n}\n\nfunction applyChunk(\n state: AssistantBuilderState,\n chunk: { type: string; [key: string]: unknown },\n): AssistantBuilderState {\n switch (chunk.type) {\n case 'text-delta':\n return {\n ...state,\n text: state.text + (typeof chunk.delta === 'string' ? chunk.delta : ''),\n }\n case 'reasoning-start':\n return { ...state, reasoningStreaming: true }\n case 'reasoning-delta':\n return {\n ...state,\n reasoning:\n state.reasoning + (typeof chunk.delta === 'string' ? chunk.delta : ''),\n reasoningStreaming: true,\n }\n case 'reasoning-end':\n return { ...state, reasoningStreaming: false }\n case 'tool-input-start':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n state: 'pending',\n })\n case 'tool-input-available':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n input: chunk.input,\n state: 'pending',\n })\n case 'tool-output-available': {\n const toolCallId = String(chunk.toolCallId ?? '')\n if (isInternalToolCallId(state, toolCallId)) return state\n const next = updateToolCall(state, toolCallId, {\n output: chunk.output,\n state: 'complete',\n })\n // Phase 3 WS-C \u2014 surface ANY UI parts the tool output advertises:\n // the legacy `awaiting-confirmation` mutation envelope plus the\n // generic `{ uiPart }` / `{ uiParts: [...] }` shapes. This lets\n // module authors define their own dynamic cards (stats panels,\n // record summaries, charts\u2026) without touching the dispatcher or\n // the chat client.\n const newParts = extractUiPartsFromOutput(chunk.output, toolCallId)\n if (newParts.length === 0) return next\n const seen = new Set(next.uiParts.map((entry) => entry.key))\n const merged = [...next.uiParts]\n for (const part of newParts) {\n if (seen.has(part.key)) continue\n seen.add(part.key)\n merged.push(part)\n }\n if (merged.length === next.uiParts.length) return next\n return { ...next, uiParts: merged }\n }\n case 'tool-output-error':\n if (isInternalToolCallId(state, chunk.toolCallId)) return state\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n state: 'error',\n errorMessage:\n typeof chunk.errorText === 'string' ? chunk.errorText : 'Tool error',\n })\n case 'tool-input-error':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n input: chunk.input,\n state: 'error',\n errorMessage:\n typeof chunk.errorText === 'string' ? chunk.errorText : 'Tool error',\n })\n case 'data-agent-task-plan': {\n // Initial / replacement plan snapshot. The agent runtime emits this\n // when the first task in a turn becomes visible. Subsequent\n // `data-agent-task-update` events patch individual tasks.\n const rawTasks = Array.isArray(chunk.tasks) ? chunk.tasks : []\n const coerced = rawTasks\n .map(coerceTaskSnapshot)\n .filter((task): task is AiAgentTaskSnapshot => task !== null)\n if (coerced.length === 0) return state\n return coerced.reduce(\n (nextState, task) => updateToolCallCaption(nextState, task.toolCallId, task.label),\n { ...state, taskPlan: coerced },\n )\n }\n case 'data-agent-task-update': {\n const incoming = coerceTaskSnapshot(chunk.task)\n if (!incoming) return state\n return updateToolCallCaption(\n { ...state, taskPlan: mergeTaskSnapshot(state.taskPlan, incoming) },\n incoming.toolCallId,\n incoming.label,\n )\n }\n default:\n return state\n }\n}\n\nfunction mergeAssistantMessage(\n current: AiChatMessage,\n state: AssistantBuilderState,\n): AiChatMessage {\n return {\n ...current,\n content: state.text,\n reasoning: state.reasoning ? state.reasoning : undefined,\n reasoningStreaming: state.reasoning ? state.reasoningStreaming : undefined,\n toolCalls: state.toolCalls.length > 0 ? state.toolCalls : undefined,\n uiParts: state.uiParts.length > 0 ? state.uiParts : undefined,\n taskPlan: state.taskPlan.length > 0 ? state.taskPlan : undefined,\n }\n}\n\nfunction parseSseLines(buffer: string): { events: string[]; rest: string } {\n const events: string[] = []\n let rest = buffer\n for (;;) {\n const idx = rest.indexOf('\\n\\n')\n if (idx === -1) break\n events.push(rest.slice(0, idx))\n rest = rest.slice(idx + 2)\n }\n return { events, rest }\n}\n\nfunction extractDataPayload(eventBlock: string): string | null {\n const lines = eventBlock.split('\\n')\n const dataLines: string[] = []\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n dataLines.push(line.slice(6))\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5))\n }\n }\n if (dataLines.length === 0) return null\n return dataLines.join('\\n')\n}\n\nasync function readErrorEnvelope(response: Response): Promise<AiChatErrorEnvelope> {\n try {\n const data = (await response.clone().json()) as\n | { error?: unknown; code?: unknown; message?: unknown }\n | null\n if (data && typeof data === 'object') {\n const rawMessage =\n (typeof data.error === 'string' && data.error) ||\n (typeof data.message === 'string' && data.message) ||\n ''\n const rawCode = typeof data.code === 'string' ? data.code : undefined\n if (rawMessage || rawCode) {\n return {\n code: rawCode,\n message: rawMessage || 'Agent dispatch failed.',\n }\n }\n }\n } catch {\n // Fall through to text fallback\n }\n const text = await response.text().catch(() => '')\n return { message: text || `Agent dispatch failed (${response.status}).` }\n}\n\nexport function useAiChat(input: UseAiChatInput): UseAiChatResult {\n const { agent, apiPath, pageContext, attachmentIds, debug, initialMessages, onError, conversationId: conversationIdInput, providerOverride, modelOverride, onConversationNotFound } = input\n\n // Minted once on mount when the caller does not supply a conversationId.\n // The ref keeps the id stable across re-renders and is reused for every\n // turn so the Phase 3 WS-C `prepareMutation` idempotency hash stays\n // stable within the same chat. When the agent has a persisted session in\n // localStorage we re-hydrate the conversationId from it so re-opening the\n // chat continues the previous turn instead of starting fresh.\n const persistedRef = React.useRef<PersistedAiChatSession | null | 'unread'>('unread')\n if (persistedRef.current === 'unread') {\n // When the caller pins a `conversationId` (multi-tab session mode) we\n // read ONLY from that per-conversation slot. Falling back to the\n // legacy agent-only slot here would make every brand-new tab inherit\n // the previous tab's messages \u2014 the \"+ shows the same chat\" bug \u2014 so\n // unknown conversationIds always start clean. Without a pinned id we\n // keep the legacy single-session-per-agent layout for backward\n // compatibility.\n persistedRef.current =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? readPersistedSession(agent, conversationIdInput)\n : readPersistedSession(agent)\n }\n const persisted = persistedRef.current\n\n const mintedConversationIdRef = React.useRef<string | null>(null)\n if (mintedConversationIdRef.current === null) {\n mintedConversationIdRef.current = persisted?.conversationId ?? makeConversationId()\n }\n const effectiveConversationId =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : mintedConversationIdRef.current\n const persistKey =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : null\n const sessionStorageKey = getSessionStorageKey(agent, persistKey)\n\n const [messages, setMessages] = React.useState<AiChatMessage[]>(() => {\n if (persisted && persisted.messages.length > 0) {\n return persisted.messages\n }\n return (initialMessages ?? []).map((entry) => ({\n id: makeMessageId(),\n role: entry.role,\n content: entry.content,\n }))\n })\n\n // Persist messages + conversationId on every change. Skip during in-flight\n // streaming so we do not write the same growing string on every chunk \u2014\n // the next idle tick captures the final assistant content.\n const [status, setStatusInternal] = React.useState<'idle' | 'submitting' | 'streaming'>(() =>\n isSessionStreamActive(sessionStorageKey) ? 'streaming' : 'idle',\n )\n // Refs mirror the latest persist state so the unmount cleanup can flush\n // an in-flight assistant message to localStorage even though the streaming\n // skip above (and React's stale-closure semantics) would otherwise lose it.\n // Without this, closing the dock or switching agents while the assistant\n // is \"Thinking\" abandons the partial reply (issue #1816).\n const latestMessagesRef = React.useRef<AiChatMessage[]>(messages)\n const latestStatusRef = React.useRef<'idle' | 'submitting' | 'streaming'>(status)\n const persistKeyRef = React.useRef<string | null>(null)\n const agentRef = React.useRef<string>(agent)\n const pageContextRef = React.useRef<Record<string, unknown> | undefined>(pageContext)\n const effectiveConversationIdRef = React.useRef<string>(effectiveConversationId)\n const sessionStorageKeyRef = React.useRef<string>(sessionStorageKey)\n const mountedRef = React.useRef(true)\n React.useEffect(() => {\n latestMessagesRef.current = messages\n }, [messages])\n React.useEffect(() => {\n latestStatusRef.current = status\n }, [status])\n React.useEffect(() => {\n persistKeyRef.current =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : null\n }, [conversationIdInput])\n React.useEffect(() => {\n agentRef.current = agent\n }, [agent])\n React.useEffect(() => {\n pageContextRef.current = pageContext\n }, [pageContext])\n React.useEffect(() => {\n effectiveConversationIdRef.current = effectiveConversationId\n }, [effectiveConversationId])\n React.useEffect(() => {\n sessionStorageKeyRef.current = sessionStorageKey\n }, [sessionStorageKey])\n React.useEffect(() => {\n if (status !== 'idle') return\n if (messages.length === 0) {\n clearPersistedSession(agent, persistKey)\n return\n }\n writePersistedSession(\n agent,\n {\n v: SESSION_STORAGE_VERSION,\n conversationId: effectiveConversationId,\n messages,\n },\n persistKey,\n )\n }, [agent, effectiveConversationId, messages, persistKey, status])\n const persistSnapshot = React.useCallback((snapshot: AiChatMessage[], notify = false) => {\n if (snapshot.length === 0) return\n writePersistedSession(\n agentRef.current,\n {\n v: SESSION_STORAGE_VERSION,\n conversationId: effectiveConversationIdRef.current,\n messages: snapshot,\n },\n persistKeyRef.current,\n { notify },\n )\n }, [])\n\n const updateMessages = React.useCallback(\n (\n updater:\n | AiChatMessage[]\n | ((current: AiChatMessage[]) => AiChatMessage[]),\n options?: { persistWhenUnmounted?: boolean },\n ) => {\n const current = latestMessagesRef.current\n const next = typeof updater === 'function' ? updater(current) : updater\n latestMessagesRef.current = next\n if (mountedRef.current) {\n setMessages(next)\n } else if (options?.persistWhenUnmounted) {\n persistSnapshot(next, true)\n }\n },\n [persistSnapshot],\n )\n\n React.useEffect(() => {\n let cancelled = false\n const localCandidate = readPersistedSession(agent, persistKey)\n\n async function hydrateFromServer(): Promise<void> {\n const transcriptResult = await loadAiServerTranscript(effectiveConversationId, { limit: 100 })\n if (cancelled || latestStatusRef.current !== 'idle') return\n\n if (transcriptResult.ok) {\n const transcript = transcriptResult.data\n if (typeof transcript.conversation.isOwner === 'boolean') {\n setIsOwner(transcript.conversation.isOwner)\n }\n const serverMessages = transcript.messages\n .map(serverMessageToChatMessage)\n .filter((message): message is AiChatMessage => message !== null)\n if (serverMessages.length > 0 || !localCandidate || localCandidate.messages.length === 0) {\n updateMessages(serverMessages)\n if (serverMessages.length > 0) persistSnapshot(serverMessages, true)\n return\n }\n }\n\n // 404 \u2014 the conversation does not exist for the current tenant/org\n // scope (e.g. the user switched scope and the stale conversationId\n // came from the previous bucket's localStorage). Self-heal: drop\n // the local cache, signal the host so it can prune its session\n // registry, and NEVER fall through to importAiLocalConversation \u2014\n // that would silently write the previous scope's messages onto the\n // new scope's server.\n if (!transcriptResult.ok && transcriptResult.notFound) {\n updateMessages([])\n clearPersistedSession(agent, persistKey)\n onConversationNotFoundRef.current?.()\n return\n }\n\n if (!localCandidate || localCandidate.messages.length === 0) {\n if (!transcriptResult.ok) {\n // Transient transport failure (network down, 5xx, \u2026). Mint a\n // new server-side record optimistically; the next idle hydrate\n // will reconcile.\n void createAiServerConversation({\n agentId: agent,\n conversationId: effectiveConversationId,\n pageContext: pageContextRef.current ?? null,\n })\n }\n return\n }\n\n if (hasImportMarker(agent, effectiveConversationId)) return\n const imported = await importAiLocalConversation({\n agentId: agent,\n conversationId: effectiveConversationId,\n pageContext: pageContextRef.current ?? null,\n messages: localCandidate.messages,\n })\n if (cancelled || !imported) return\n writeImportMarker(agent, effectiveConversationId)\n }\n\n void hydrateFromServer()\n return () => {\n cancelled = true\n }\n }, [agent, effectiveConversationId, persistKey, persistSnapshot, updateMessages])\n\n const setStatus = React.useCallback((next: 'idle' | 'submitting' | 'streaming') => {\n if (mountedRef.current) {\n setStatusInternal(next)\n }\n }, [])\n const [error, setError] = React.useState<AiChatErrorEnvelope | null>(null)\n const [lastRequestDebug, setLastRequestDebug] = React.useState<\n { url: string; body: unknown } | null\n >(null)\n const [lastResponseDebug, setLastResponseDebug] = React.useState<\n { status: number; text: string } | null\n >(null)\n\n const [lastLoopTrace, setLastLoopTrace] = React.useState<LoopTracePanelTrace | null>(null)\n const [isOwner, setIsOwner] = React.useState<boolean | null>(null)\n\n const abortRef = React.useRef<AbortController | null>(null)\n const onErrorRef = React.useRef(onError)\n React.useEffect(() => {\n onErrorRef.current = onError\n }, [onError])\n const onConversationNotFoundRef = React.useRef(onConversationNotFound)\n React.useEffect(() => {\n onConversationNotFoundRef.current = onConversationNotFound\n }, [onConversationNotFound])\n\n const emitError = React.useCallback((envelope: AiChatErrorEnvelope) => {\n if (mountedRef.current) {\n setError(envelope)\n try {\n onErrorRef.current?.(envelope)\n } catch {\n // UI layer must never throw because a caller-supplied error handler\n // misbehaved.\n }\n }\n }, [])\n\n const cancel = React.useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort()\n abortRef.current = null\n }\n writeSessionStreamState(sessionStorageKeyRef.current, false)\n setStatus('idle')\n }, [setStatus])\n\n const reset = React.useCallback(() => {\n cancel()\n updateMessages([])\n setError(null)\n setLastRequestDebug(null)\n setLastResponseDebug(null)\n setLastLoopTrace(null)\n clearPersistedSession(agent, persistKey)\n mintedConversationIdRef.current = makeConversationId()\n }, [agent, cancel, persistKey, updateMessages])\n\n const sendMessage = React.useCallback(\n async (textInput: string, files?: AiChatMessageFile[]) => {\n const trimmed = textInput.trim()\n if (!trimmed) return\n if (abortRef.current) {\n abortRef.current.abort()\n }\n\n setError(null)\n const userMessage: AiChatMessage = {\n id: makeMessageId(),\n role: 'user',\n content: trimmed,\n files: files && files.length > 0 ? files : undefined,\n }\n const assistantMessage: AiChatMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: '',\n }\n const assistantId = assistantMessage.id\n // Snapshot prior messages for request payload so the dispatcher sees the\n // full turn history including the just-added user message.\n const outgoingHistory = [...latestMessagesRef.current, userMessage]\n updateMessages([...outgoingHistory, assistantMessage], { persistWhenUnmounted: true })\n setStatus('submitting')\n writeSessionStreamState(sessionStorageKey, true)\n\n const controller = new AbortController()\n abortRef.current = controller\n\n const url = getTransportEndpoint(agent, apiPath, providerOverride, modelOverride)\n const body = {\n messages: outgoingHistory.map((message) => ({\n id: message.id,\n role: message.role,\n content: message.content,\n files: message.files,\n uiParts: message.uiParts,\n })),\n pageContext,\n attachmentIds,\n debug,\n conversationId: effectiveConversationId,\n }\n if (mountedRef.current) {\n setLastRequestDebug({ url, body })\n }\n\n let response: Response\n try {\n response = await apiFetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream, text/plain, application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n } catch (requestError) {\n if ((requestError as { name?: string })?.name === 'AbortError') {\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n const message =\n requestError instanceof Error\n ? requestError.message\n : 'Network request failed.'\n emitError({ message })\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n\n if (!response.ok) {\n const envelope = await readErrorEnvelope(response)\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: envelope.message })\n }\n emitError(envelope)\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n abortRef.current = null\n return\n }\n\n const bodyStream = response.body\n if (!bodyStream) {\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: '' })\n }\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n\n // Per-turn loop trace collected from the `loop-finish` SSE event emitted\n // by the dispatcher at the end of the stream (Phase 4).\n let pendingLoopTrace: LoopTracePanelTrace | null = null\n\n const headerGet = (name: string): string | null => {\n const headers = (response as { headers?: { get?: (k: string) => string | null } })\n .headers\n if (!headers || typeof headers.get !== 'function') return null\n try {\n return headers.get(name)\n } catch {\n return null\n }\n }\n const isUiMessageStream =\n headerGet('x-vercel-ai-ui-message-stream') !== null ||\n (headerGet('content-type') ?? '').includes('event-stream')\n\n setStatus('streaming')\n const reader = bodyStream.getReader()\n const decoder = new TextDecoder()\n let streamedRaw = ''\n let builder = createBuilder()\n let sseBuffer = ''\n const flushUiMessageBuffer = (extra?: string) => {\n if (extra) sseBuffer += extra\n const { events, rest } = parseSseLines(sseBuffer)\n sseBuffer = rest\n for (const block of events) {\n const data = extractDataPayload(block)\n if (!data) continue\n if (data === '[DONE]') continue\n try {\n const parsed = JSON.parse(data) as { type?: string; trace?: unknown }\n if (!parsed || typeof parsed.type !== 'string') continue\n if (parsed.type === 'loop-finish') {\n // Capture the loop trace for post-stream state update.\n pendingLoopTrace = parsed.trace as LoopTracePanelTrace ?? null\n } else {\n builder = applyChunk(builder, parsed as { type: string })\n }\n } catch {\n // Tolerate malformed events / SSE comments.\n }\n }\n }\n try {\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (!value) continue\n const piece = decoder.decode(value, { stream: true })\n if (!piece) continue\n streamedRaw += piece\n\n if (isUiMessageStream) {\n flushUiMessageBuffer(piece)\n } else {\n // Plain text fallback (legacy `toTextStreamResponse`).\n builder = { ...builder, text: streamedRaw }\n }\n const snapshotBuilder = builder\n updateMessages(\n (current) =>\n current.map((entry) =>\n entry.id === assistantId\n ? mergeAssistantMessage(entry, snapshotBuilder)\n : entry,\n ),\n { persistWhenUnmounted: true },\n )\n }\n const tail = decoder.decode()\n if (tail) {\n streamedRaw += tail\n if (isUiMessageStream) {\n flushUiMessageBuffer(tail)\n } else {\n builder = { ...builder, text: streamedRaw }\n }\n }\n if (isUiMessageStream && sseBuffer.length > 0) {\n flushUiMessageBuffer('\\n\\n')\n }\n builder = { ...builder, reasoningStreaming: false }\n const finalSnapshot = builder\n updateMessages(\n (current) =>\n current.map((entry) =>\n entry.id === assistantId\n ? mergeAssistantMessage(entry, finalSnapshot)\n : entry,\n ),\n { persistWhenUnmounted: true },\n )\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: streamedRaw })\n if (pendingLoopTrace !== null) {\n setLastLoopTrace(pendingLoopTrace)\n }\n }\n const isEmpty =\n !builder.text.trim() && builder.toolCalls.length === 0 && !builder.reasoning\n if (isEmpty) {\n emitError({\n code: 'empty_response',\n message:\n 'The AI agent returned an empty response. This usually means the LLM provider rejected the request (invalid API key, rate limit, or model error). Check your server logs for details.',\n })\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n }\n } catch (streamError) {\n if ((streamError as { name?: string })?.name === 'AbortError') {\n // Cancelled by the user \u2014 keep whatever we have so far and exit\n // quietly.\n } else {\n const rawMessage =\n streamError instanceof Error\n ? streamError.message\n : 'Stream interrupted.'\n // LLM provider errors (auth failures, rate limits, invalid tool\n // schemas) surface as stream read errors. Include a hint so the\n // operator can check server logs for the full stack trace.\n const message = rawMessage.includes('API')\n ? rawMessage\n : `${rawMessage} \u2014 check server logs for LLM provider details.`\n emitError({ code: 'stream_error', message })\n // Remove the empty assistant placeholder so the error alert is\n // the only visible feedback.\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n }\n } finally {\n reader.releaseLock()\n if (abortRef.current === controller) {\n abortRef.current = null\n }\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n }\n },\n [agent, apiPath, attachmentIds, debug, effectiveConversationId, emitError, messages, modelOverride, pageContext, providerOverride],\n )\n\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const handleSessionUpdate = (event: Event) => {\n const detail = (event as CustomEvent<{ key?: string }>).detail\n if (!detail || detail.key !== sessionStorageKey) return\n const next = readPersistedSession(agent, persistKey)\n if (!next) return\n if (next.conversationId !== effectiveConversationId) return\n updateMessages(next.messages)\n }\n window.addEventListener(SESSION_UPDATED_EVENT, handleSessionUpdate)\n return () => {\n window.removeEventListener(SESSION_UPDATED_EVENT, handleSessionUpdate)\n }\n }, [agent, effectiveConversationId, persistKey, sessionStorageKey, updateMessages])\n\n React.useEffect(() => {\n if (isSessionStreamActive(sessionStorageKey)) {\n setStatusInternal('streaming')\n }\n if (typeof window === 'undefined') return\n const handleStreamState = (event: Event) => {\n const detail = (event as CustomEvent<{ key?: string; active?: boolean }>).detail\n if (!detail || detail.key !== sessionStorageKey) return\n setStatusInternal(detail.active ? 'streaming' : 'idle')\n }\n window.addEventListener(SESSION_STREAM_STATE_EVENT, handleStreamState)\n return () => {\n window.removeEventListener(SESSION_STREAM_STATE_EVENT, handleStreamState)\n }\n }, [sessionStorageKey])\n\n React.useEffect(() => {\n mountedRef.current = true\n return () => {\n // Flush the latest snapshot \u2014 including any in-flight assistant\n // content \u2014 and let the request keep running in the background.\n // Background stream updates continue writing to this same storage slot\n // and notify any reopened chat for the conversation (issue #1816).\n const finalMessages = latestMessagesRef.current\n if (finalMessages.length > 0) {\n persistSnapshot(finalMessages, true)\n }\n mountedRef.current = false\n }\n }, [persistSnapshot])\n\n return {\n messages,\n status,\n error,\n lastRequestDebug,\n lastResponseDebug,\n conversationId: effectiveConversationId,\n lastLoopTrace,\n isOwner,\n sendMessage,\n cancel,\n reset,\n }\n}\n"],
|
|
5
|
-
"mappings": "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",
|
|
4
|
+
"sourcesContent": ["\"use client\"\n\nimport * as React from 'react'\nimport { createAiAgentTransport } from '@open-mercato/ai-assistant/modules/ai_assistant/lib/agent-transport'\nimport { apiFetch } from '../backend/utils/api'\nimport type { LoopTracePanelTrace } from './LoopTracePanel'\nimport {\n createAiServerConversation,\n importAiLocalConversation,\n loadAiServerTranscript,\n serverMessageToChatMessage,\n} from './conversation-store'\n\n/**\n * Chat message shape used by {@link AiChat}. Kept intentionally minimal so the\n * component stays independent of the AI SDK's evolving `UIMessage` type. The\n * dispatcher route (`POST /api/ai_assistant/ai/chat`) accepts exactly this\n * shape for `messages`.\n */\nexport interface AiChatMessageFile {\n id?: string\n name: string\n type: string\n previewUrl?: string\n}\n\nexport interface AiChatToolCallSnapshot {\n id: string\n toolName: string\n caption?: string\n state: 'pending' | 'complete' | 'error'\n input?: unknown\n output?: unknown\n errorMessage?: string\n}\n\nexport interface AiChatMessageUiPart {\n componentId: string\n payload?: unknown\n pendingActionId?: string\n /** Stable id used as React key when rendering. */\n key: string\n}\n\n/**\n * Snapshot of a single task in the visible agent task plan\n * (spec `.ai/specs/implemented/2026-05-13-ai-chat-visible-task-plan.md`). Tasks are\n * streamed as additive `data-agent-task-plan` / `data-agent-task-update`\n * SSE chunks and rendered above raw tool-call rows in `<AiChat>`.\n *\n * `source: 'runtime'` tasks are derived from tool lifecycle events by the\n * agent runtime. `source: 'agent'` tasks are emitted through the reserved\n * `meta.update_task_plan` helper and sanitized before they reach the client.\n */\nexport interface AiAgentTaskSnapshot {\n id: string\n label: string\n state: 'pending' | 'running' | 'done' | 'failed' | 'skipped'\n detail?: string\n source: 'runtime' | 'agent'\n toolCallId?: string\n}\n\nexport interface AiChatMessage {\n id: string\n role: 'user' | 'assistant'\n content: string\n files?: AiChatMessageFile[]\n reasoning?: string\n reasoningStreaming?: boolean\n toolCalls?: AiChatToolCallSnapshot[]\n /** User ID of the message author. Present for server-loaded messages; null for AI messages and locally-composed messages. */\n senderUserId?: string | null\n /**\n * UI parts emitted by the agent during this message's lifecycle. Today\n * the only producer is `prepareMutation` (mutation approval flow):\n * the dispatcher's mutation tool returns an `awaiting-confirmation`\n * envelope, useAiChat parses it and attaches a `mutation-preview-card`\n * part here so AiChat can render the approval card inline. Phase 3\n * WS-C wiring \u2014 without this, the `MutationPreviewCard` registered in\n * the UI-part registry never surfaces.\n */\n uiParts?: AiChatMessageUiPart[]\n /**\n * Client-local visible task plan derived from streamed\n * `data-agent-task-plan` snapshots and `data-agent-task-update` deltas.\n * Not persisted in any chat storage payload.\n */\n taskPlan?: AiAgentTaskSnapshot[]\n}\n\nexport interface UseAiChatInput {\n agent: string\n apiPath?: string\n pageContext?: Record<string, unknown>\n attachmentIds?: string[]\n debug?: boolean\n initialMessages?: Array<Pick<AiChatMessage, 'role' | 'content'>>\n onError?: (err: { code?: string; message: string }) => void\n /**\n * Optional stable conversation id. When provided, the same id is forwarded\n * to the dispatcher on every turn so `prepareMutation`'s idempotency hash\n * (Step 5.6) stays stable across mutation preview / confirm / retry cycles.\n * When omitted, the hook mints a fresh random id once on mount and reuses\n * it for the lifetime of the component \u2014 callers can still override via\n * props at any time to reset the conversation.\n */\n conversationId?: string\n /**\n * Runtime provider override (4b.2). Forwarded as `?provider=` query param\n * on every POST to the dispatcher. Undefined/null means use the agent's\n * configured default (no override sent).\n */\n providerOverride?: string | null\n /**\n * Runtime model override (4b.2). Forwarded as `?model=` query param\n * on every POST to the dispatcher. Undefined/null means use the agent's\n * configured default (no override sent).\n */\n modelOverride?: string | null\n /**\n * Called when the server returns 404 for the active `conversationId`.\n * Used by hosts to remove a stale session entry (e.g. the AI dock\n * closes the session tab) when a conversation no longer exists for the\n * current tenant/org scope. A 404 here distinguishes a missing\n * conversation from a transient transport failure \u2014 the hook never\n * imports local messages onto the new scope when this fires.\n */\n onConversationNotFound?: () => void\n}\n\nexport interface AiChatErrorEnvelope {\n code?: string\n message: string\n}\n\nexport interface UseAiChatResult {\n messages: AiChatMessage[]\n status: 'idle' | 'submitting' | 'streaming'\n error: AiChatErrorEnvelope | null\n lastRequestDebug: { url: string; body: unknown } | null\n lastResponseDebug: { status: number; text: string } | null\n /**\n * The conversation id currently in use for this chat instance. Equal to\n * the caller-provided `conversationId` input when one is supplied;\n * otherwise the random id minted on mount. Stable across re-renders for a\n * given mount (Phase 3 WS-D contract with `prepareMutation`).\n */\n conversationId: string\n /**\n * Loop trace from the last completed turn. Populated when the dispatcher\n * emits a `loop-finish` SSE event at the end of the response stream.\n * `null` until the first turn completes or when the dispatcher does not\n * emit trace events (non-debug deployments may omit them).\n *\n * Phase 4 of spec `2026-04-28-ai-agents-agentic-loop-controls`.\n */\n lastLoopTrace: LoopTracePanelTrace | null\n /**\n * Whether the authenticated caller owns the loaded conversation. `true` for owners,\n * `false` for shared participants (read-only view), `null` while unresolved.\n */\n isOwner: boolean | null\n sendMessage: (input: string, files?: AiChatMessageFile[]) => Promise<void>\n cancel: () => void\n reset: () => void\n}\n\nfunction makeMessageId(): string {\n const random = Math.random().toString(36).slice(2, 10)\n const time = Date.now().toString(36)\n return `msg_${time}_${random}`\n}\n\nfunction makeConversationId(): string {\n // Use crypto.randomUUID() when the browser exposes it (all evergreen\n // runtimes do), otherwise fall back to a low-entropy token that is still\n // unique enough for the idempotency-hash use case.\n const g = globalThis as unknown as { crypto?: { randomUUID?: () => string } }\n if (g.crypto && typeof g.crypto.randomUUID === 'function') {\n try {\n return g.crypto.randomUUID()\n } catch {\n // fall through to the random fallback\n }\n }\n const rand = () => Math.random().toString(16).slice(2, 10)\n return `conv_${Date.now().toString(16)}_${rand()}${rand()}`\n}\n\nconst SESSION_STORAGE_PREFIX = 'om-ai-chat:'\nconst SESSION_IMPORT_MARKER_PREFIX = 'om-ai-chat-imported:'\nconst SESSION_STORAGE_VERSION = 1\nconst SESSION_UPDATED_EVENT = 'om-ai-chat-session-updated'\nconst SESSION_STREAM_STATE_EVENT = 'om-ai-chat-session-stream-state'\nconst activeSessionStreams = new Set<string>()\n\n// This slot already implements its own versioned, migratable shape: the inline\n// `v` discriminator is checked on read and the value is discarded on mismatch\n// (see `readPersistedSession`). It is intentionally NOT migrated to the generic\n// `{ v, data }` envelope from `@open-mercato/shared/lib/browser/versionedPreference`\n// \u2014 doing so would change the on-disk format and discard users' in-progress\n// sessions, and the write path needs bespoke transient-blob-URL stripping that\n// the generic helper does not express.\ninterface PersistedAiChatSession {\n v: number\n conversationId: string\n messages: AiChatMessage[]\n}\n\nfunction getSessionStorageKey(agent: string, conversationId?: string | null): string {\n // When the caller pins a `conversationId` (e.g. via the AiChatSessions\n // provider's tabs), namespace the persisted slot per session so multiple\n // open conversations for the same agent don't overwrite each other. The\n // legacy single-session-per-agent layout (no externally-supplied id) is\n // kept for backward compatibility with code that still relies on it.\n if (typeof conversationId === 'string' && conversationId.length > 0) {\n return `${SESSION_STORAGE_PREFIX}${agent}:${conversationId}`\n }\n return `${SESSION_STORAGE_PREFIX}${agent}`\n}\n\nfunction writeSessionStreamState(storageKey: string, active: boolean): void {\n if (active) {\n activeSessionStreams.add(storageKey)\n } else {\n activeSessionStreams.delete(storageKey)\n }\n if (typeof window === 'undefined') return\n try {\n window.dispatchEvent(\n new CustomEvent(SESSION_STREAM_STATE_EVENT, {\n detail: { key: storageKey, active },\n }),\n )\n } catch {\n // ignore event dispatch failures in non-browser test environments\n }\n}\n\nfunction isSessionStreamActive(storageKey: string): boolean {\n return activeSessionStreams.has(storageKey)\n}\n\nfunction readPersistedSession(\n agent: string,\n conversationId?: string | null,\n): PersistedAiChatSession | null {\n if (typeof window === 'undefined') return null\n try {\n const raw = window.localStorage.getItem(getSessionStorageKey(agent, conversationId))\n if (!raw) return null\n const parsed = JSON.parse(raw) as PersistedAiChatSession | null\n if (!parsed || parsed.v !== SESSION_STORAGE_VERSION) return null\n if (typeof parsed.conversationId !== 'string') return null\n if (!Array.isArray(parsed.messages)) return null\n const messages = parsed.messages.filter((entry): entry is AiChatMessage => {\n return (\n !!entry &&\n typeof entry === 'object' &&\n typeof (entry as AiChatMessage).id === 'string' &&\n typeof (entry as AiChatMessage).content === 'string' &&\n ((entry as AiChatMessage).role === 'user' || (entry as AiChatMessage).role === 'assistant')\n )\n })\n return { v: SESSION_STORAGE_VERSION, conversationId: parsed.conversationId, messages }\n } catch {\n return null\n }\n}\n\nfunction writePersistedSession(\n agent: string,\n session: PersistedAiChatSession,\n conversationId?: string | null,\n options?: { notify?: boolean },\n): void {\n if (typeof window === 'undefined') return\n try {\n // Strip transient blob/object preview URLs before persisting (they would\n // not survive a reload). Self-contained `data:` URLs and same-origin\n // attachment thumbnail URLs are durable enough for local fallback storage.\n const messages = session.messages.map((message) => {\n if (!message.files || message.files.length === 0) return message\n const safeFiles = message.files.map(({ id, name, type, previewUrl }) => {\n const durable =\n typeof previewUrl === 'string' &&\n (previewUrl.startsWith('data:') ||\n previewUrl.startsWith('/api/attachments/image/') ||\n previewUrl.startsWith('/api/attachments/file/'))\n ? previewUrl\n : undefined\n const base = id ? { id, name, type } : { name, type }\n return durable ? { ...base, previewUrl: durable } : base\n })\n return { ...message, files: safeFiles }\n })\n const storageKey = getSessionStorageKey(agent, conversationId)\n window.localStorage.setItem(storageKey, JSON.stringify({ ...session, messages }))\n if (options?.notify) {\n window.dispatchEvent(\n new CustomEvent(SESSION_UPDATED_EVENT, {\n detail: { key: storageKey, agent, conversationId: session.conversationId },\n }),\n )\n }\n } catch {\n // Quota exceeded / privacy mode \u2014 silently drop persistence.\n }\n}\n\nfunction clearPersistedSession(agent: string, conversationId?: string | null): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.removeItem(getSessionStorageKey(agent, conversationId))\n } catch {\n // ignore\n }\n}\n\nfunction getImportMarkerKey(agent: string, conversationId: string): string {\n return `${SESSION_IMPORT_MARKER_PREFIX}${agent}:${conversationId}`\n}\n\nfunction hasImportMarker(agent: string, conversationId: string): boolean {\n if (typeof window === 'undefined') return false\n try {\n return window.localStorage.getItem(getImportMarkerKey(agent, conversationId)) === '1'\n } catch {\n return false\n }\n}\n\nfunction writeImportMarker(agent: string, conversationId: string): void {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(getImportMarkerKey(agent, conversationId), '1')\n } catch {\n // ignore local marker failures; the server import remains authoritative\n }\n}\n\nfunction getTransportEndpoint(\n agent: string,\n apiPath?: string,\n providerOverride?: string | null,\n modelOverride?: string | null,\n): string {\n // Reuse the transport factory so UI consumers share the dispatcher URL\n // convention with server-side callers (e.g. runAiAgentText / Playwright\n // fixtures). The factory returns a ChatTransport<UI_MESSAGE> whose internal\n // endpoint we do not directly read \u2014 instead we reconstruct the same URL\n // shape here so downstream error handling stays deterministic.\n //\n // When the AI SDK exposes a public endpoint getter (or the stream format\n // switches from plain text to UIMessageChunk) we can call\n // transport.sendMessages(...) directly.\n const transport = createAiAgentTransport({ agentId: agent, endpoint: apiPath })\n void transport\n const base = apiPath && apiPath.length > 0 ? apiPath : '/api/ai_assistant/ai/chat'\n const separator = base.includes('?') ? '&' : '?'\n let url = `${base}${separator}agent=${encodeURIComponent(agent)}`\n if (providerOverride) {\n url += `&provider=${encodeURIComponent(providerOverride)}`\n }\n if (modelOverride) {\n url += `&model=${encodeURIComponent(modelOverride)}`\n }\n return url\n}\n\ninterface AssistantBuilderState {\n text: string\n reasoning: string\n reasoningStreaming: boolean\n toolCalls: AiChatToolCallSnapshot[]\n toolCallCaptions: Record<string, string>\n internalToolCallIds: string[]\n uiParts: AiChatMessageUiPart[]\n taskPlan: AiAgentTaskSnapshot[]\n}\n\nfunction createBuilder(): AssistantBuilderState {\n return {\n text: '',\n reasoning: '',\n reasoningStreaming: false,\n toolCalls: [],\n toolCallCaptions: {},\n internalToolCallIds: [],\n uiParts: [],\n taskPlan: [],\n }\n}\n\nconst VALID_TASK_STATES: ReadonlySet<AiAgentTaskSnapshot['state']> = new Set([\n 'pending',\n 'running',\n 'done',\n 'failed',\n 'skipped',\n])\nconst VALID_TASK_SOURCES: ReadonlySet<AiAgentTaskSnapshot['source']> = new Set(['runtime', 'agent'])\n\nfunction coerceTaskSnapshot(raw: unknown): AiAgentTaskSnapshot | null {\n if (!raw || typeof raw !== 'object') return null\n const value = raw as Record<string, unknown>\n const id = typeof value.id === 'string' ? value.id.trim() : ''\n if (!id) return null\n const label = typeof value.label === 'string' ? value.label : ''\n if (!label) return null\n const state = VALID_TASK_STATES.has(value.state as AiAgentTaskSnapshot['state'])\n ? (value.state as AiAgentTaskSnapshot['state'])\n : 'pending'\n const source = VALID_TASK_SOURCES.has(value.source as AiAgentTaskSnapshot['source'])\n ? (value.source as AiAgentTaskSnapshot['source'])\n : 'runtime'\n const detail = typeof value.detail === 'string' && value.detail.length > 0 ? value.detail : undefined\n const toolCallId =\n typeof value.toolCallId === 'string' && value.toolCallId.length > 0 ? value.toolCallId : undefined\n return { id, label, state, source, detail, toolCallId }\n}\n\nconst TERMINAL_TASK_STATES: ReadonlySet<AiAgentTaskSnapshot['state']> = new Set([\n 'done',\n 'failed',\n 'skipped',\n])\n\nfunction mergeTaskSnapshot(\n current: AiAgentTaskSnapshot[],\n incoming: AiAgentTaskSnapshot,\n): AiAgentTaskSnapshot[] {\n const idx = current.findIndex((task) => task.id === incoming.id)\n if (idx === -1) return [...current, incoming]\n const prior = current[idx]\n // Stream ordering safeguard: once a task reaches a terminal state we keep\n // it terminal so a late \"running\" event cannot revert it (spec \u00A7Risks \u2014\n // \"Stream ordering bugs cause stale statuses\").\n const nextState = TERMINAL_TASK_STATES.has(prior.state) ? prior.state : incoming.state\n const merged: AiAgentTaskSnapshot = {\n ...prior,\n ...incoming,\n state: nextState,\n }\n const next = current.slice()\n next[idx] = merged\n return next\n}\n\n/**\n * Generic extractor for UI parts emitted by tool outputs. A tool can\n * surface inline UI to the chat by returning JSON in any of these\n * shapes \u2014 each tool call produces zero or more UI parts:\n *\n * 1. The dispatcher's mutation envelope:\n * `{ status: 'awaiting-confirmation', pendingActionId, expiresAt,\n * agent, toolName, message }`\n * \u2192 synthesizes a `mutation-preview-card` part (the registered\n * card fetches the live diff via `useAiPendingActionPolling`).\n *\n * 2. A single explicit UI part:\n * `{ uiPart: { componentId, payload?, pendingActionId? } }`\n *\n * 3. Multiple explicit UI parts:\n * `{ uiParts: [{ componentId, payload? }, ...] }`\n *\n * Tool authors only need to JSON-encode an object whose `uiPart` /\n * `uiParts` reference component ids that the host has registered on\n * `defaultAiUiPartRegistry` (or a scoped registry passed through\n * `<AiChat registry={...}/>`). Unknown component ids fall back to the\n * `UnknownUiPartPlaceholder` so an unregistered id never blows up the\n * transcript.\n */\nfunction extractUiPartsFromOutput(\n output: unknown,\n toolCallId: string,\n): AiChatMessageUiPart[] {\n let parsed: unknown = output\n if (typeof output === 'string') {\n const trimmed = output.trim()\n if (!trimmed.startsWith('{') && !trimmed.startsWith('[')) return []\n try {\n parsed = JSON.parse(trimmed)\n } catch {\n return []\n }\n }\n if (!parsed || typeof parsed !== 'object') return []\n const value = parsed as Record<string, unknown>\n const parts: AiChatMessageUiPart[] = []\n\n // (1) Mutation approval envelope. The dispatcher's `prepareMutation`\n // interceptor in `agent-tools.ts` formats the result via\n // `formatPendingActionToolResult` as\n // { status: 'pending-confirmation', agentId, toolName, pendingActionId,\n // expiresAt, message }\n // (NOTE: status is `pending-confirmation` and the field is `agentId`,\n // not `agent`). We also accept `awaiting-confirmation` / `agent` for\n // forward compat with older / alternative dispatchers.\n if (value.status === 'pending-confirmation' || value.status === 'awaiting-confirmation') {\n const pendingActionId =\n typeof value.pendingActionId === 'string' && value.pendingActionId.length > 0\n ? value.pendingActionId\n : null\n if (pendingActionId) {\n const agentId =\n typeof value.agentId === 'string'\n ? value.agentId\n : typeof value.agent === 'string'\n ? value.agent\n : undefined\n parts.push({\n componentId: 'mutation-preview-card',\n pendingActionId,\n payload: {\n pendingActionId,\n expiresAt: typeof value.expiresAt === 'string' ? value.expiresAt : undefined,\n agentId,\n toolName: typeof value.toolName === 'string' ? value.toolName : undefined,\n },\n key: `${toolCallId}:mutation-preview-card`,\n })\n }\n }\n\n // (2) Explicit single UI part.\n if (value.uiPart && typeof value.uiPart === 'object') {\n const part = value.uiPart as Record<string, unknown>\n if (typeof part.componentId === 'string' && part.componentId.length > 0) {\n parts.push({\n componentId: part.componentId,\n payload: part.payload,\n pendingActionId:\n typeof part.pendingActionId === 'string' ? part.pendingActionId : undefined,\n key: `${toolCallId}:${part.componentId}`,\n })\n }\n }\n\n // (3) Explicit list of UI parts.\n if (Array.isArray(value.uiParts)) {\n value.uiParts.forEach((entry, index) => {\n if (!entry || typeof entry !== 'object') return\n const part = entry as Record<string, unknown>\n if (typeof part.componentId !== 'string' || part.componentId.length === 0) return\n parts.push({\n componentId: part.componentId,\n payload: part.payload,\n pendingActionId:\n typeof part.pendingActionId === 'string' ? part.pendingActionId : undefined,\n key: `${toolCallId}:${index}:${part.componentId}`,\n })\n })\n }\n\n return parts\n}\n\nfunction updateToolCall(\n state: AssistantBuilderState,\n id: string,\n patch: Partial<AiChatToolCallSnapshot> & { toolName?: string },\n): AssistantBuilderState {\n if (!id) return state\n const idx = state.toolCalls.findIndex((entry) => entry.id === id)\n if (idx === -1) {\n const next: AiChatToolCallSnapshot = {\n id,\n toolName: patch.toolName ?? 'tool',\n caption: patch.caption ?? state.toolCallCaptions[id],\n state: patch.state ?? 'pending',\n input: patch.input,\n output: patch.output,\n errorMessage: patch.errorMessage,\n }\n return { ...state, toolCalls: [...state.toolCalls, next] }\n }\n const current = state.toolCalls[idx]\n const merged: AiChatToolCallSnapshot = {\n ...current,\n toolName: patch.toolName ?? current.toolName,\n caption: patch.caption ?? current.caption,\n state: patch.state ?? current.state,\n input: patch.input !== undefined ? patch.input : current.input,\n output: patch.output !== undefined ? patch.output : current.output,\n errorMessage: patch.errorMessage ?? current.errorMessage,\n }\n const nextCalls = state.toolCalls.slice()\n nextCalls[idx] = merged\n return { ...state, toolCalls: nextCalls }\n}\n\nfunction updateToolCallCaption(\n state: AssistantBuilderState,\n toolCallId: string | undefined,\n caption: string,\n): AssistantBuilderState {\n if (!toolCallId || !caption) return state\n const toolCallCaptions = { ...state.toolCallCaptions, [toolCallId]: caption }\n const idx = state.toolCalls.findIndex((entry) => entry.id === toolCallId)\n if (idx === -1) return { ...state, toolCallCaptions }\n const nextCalls = state.toolCalls.slice()\n nextCalls[idx] = { ...nextCalls[idx], caption }\n return { ...state, toolCallCaptions, toolCalls: nextCalls }\n}\n\nfunction displayToolName(toolName: unknown): string | undefined {\n if (typeof toolName !== 'string') return undefined\n return toolName.replace(/__/g, '.')\n}\n\nfunction isInternalTaskPlanTool(toolName: unknown): boolean {\n return displayToolName(toolName) === 'meta.update_task_plan'\n}\n\nfunction markInternalToolCall(state: AssistantBuilderState, toolCallId: unknown): AssistantBuilderState {\n const id = String(toolCallId ?? '')\n if (!id || state.internalToolCallIds.includes(id)) return state\n return { ...state, internalToolCallIds: [...state.internalToolCallIds, id] }\n}\n\nfunction isInternalToolCallId(state: AssistantBuilderState, toolCallId: unknown): boolean {\n const id = String(toolCallId ?? '')\n return id.length > 0 && state.internalToolCallIds.includes(id)\n}\n\nfunction applyChunk(\n state: AssistantBuilderState,\n chunk: { type: string; [key: string]: unknown },\n): AssistantBuilderState {\n switch (chunk.type) {\n case 'text-delta':\n return {\n ...state,\n text: state.text + (typeof chunk.delta === 'string' ? chunk.delta : ''),\n }\n case 'reasoning-start':\n return { ...state, reasoningStreaming: true }\n case 'reasoning-delta':\n return {\n ...state,\n reasoning:\n state.reasoning + (typeof chunk.delta === 'string' ? chunk.delta : ''),\n reasoningStreaming: true,\n }\n case 'reasoning-end':\n return { ...state, reasoningStreaming: false }\n case 'tool-input-start':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n state: 'pending',\n })\n case 'tool-input-available':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n input: chunk.input,\n state: 'pending',\n })\n case 'tool-output-available': {\n const toolCallId = String(chunk.toolCallId ?? '')\n if (isInternalToolCallId(state, toolCallId)) return state\n const next = updateToolCall(state, toolCallId, {\n output: chunk.output,\n state: 'complete',\n })\n // Phase 3 WS-C \u2014 surface ANY UI parts the tool output advertises:\n // the legacy `awaiting-confirmation` mutation envelope plus the\n // generic `{ uiPart }` / `{ uiParts: [...] }` shapes. This lets\n // module authors define their own dynamic cards (stats panels,\n // record summaries, charts\u2026) without touching the dispatcher or\n // the chat client.\n const newParts = extractUiPartsFromOutput(chunk.output, toolCallId)\n if (newParts.length === 0) return next\n const seen = new Set(next.uiParts.map((entry) => entry.key))\n const merged = [...next.uiParts]\n for (const part of newParts) {\n if (seen.has(part.key)) continue\n seen.add(part.key)\n merged.push(part)\n }\n if (merged.length === next.uiParts.length) return next\n return { ...next, uiParts: merged }\n }\n case 'tool-output-error':\n if (isInternalToolCallId(state, chunk.toolCallId)) return state\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n state: 'error',\n errorMessage:\n typeof chunk.errorText === 'string' ? chunk.errorText : 'Tool error',\n })\n case 'tool-input-error':\n if (isInternalTaskPlanTool(chunk.toolName)) return markInternalToolCall(state, chunk.toolCallId)\n return updateToolCall(state, String(chunk.toolCallId ?? ''), {\n toolName: displayToolName(chunk.toolName),\n input: chunk.input,\n state: 'error',\n errorMessage:\n typeof chunk.errorText === 'string' ? chunk.errorText : 'Tool error',\n })\n case 'data-agent-task-plan': {\n // Initial / replacement plan snapshot. The agent runtime emits this\n // when the first task in a turn becomes visible. Subsequent\n // `data-agent-task-update` events patch individual tasks.\n const rawTasks = Array.isArray(chunk.tasks) ? chunk.tasks : []\n const coerced = rawTasks\n .map(coerceTaskSnapshot)\n .filter((task): task is AiAgentTaskSnapshot => task !== null)\n if (coerced.length === 0) return state\n return coerced.reduce(\n (nextState, task) => updateToolCallCaption(nextState, task.toolCallId, task.label),\n { ...state, taskPlan: coerced },\n )\n }\n case 'data-agent-task-update': {\n const incoming = coerceTaskSnapshot(chunk.task)\n if (!incoming) return state\n return updateToolCallCaption(\n { ...state, taskPlan: mergeTaskSnapshot(state.taskPlan, incoming) },\n incoming.toolCallId,\n incoming.label,\n )\n }\n default:\n return state\n }\n}\n\nfunction mergeAssistantMessage(\n current: AiChatMessage,\n state: AssistantBuilderState,\n): AiChatMessage {\n return {\n ...current,\n content: state.text,\n reasoning: state.reasoning ? state.reasoning : undefined,\n reasoningStreaming: state.reasoning ? state.reasoningStreaming : undefined,\n toolCalls: state.toolCalls.length > 0 ? state.toolCalls : undefined,\n uiParts: state.uiParts.length > 0 ? state.uiParts : undefined,\n taskPlan: state.taskPlan.length > 0 ? state.taskPlan : undefined,\n }\n}\n\nfunction parseSseLines(buffer: string): { events: string[]; rest: string } {\n const events: string[] = []\n let rest = buffer\n for (;;) {\n const idx = rest.indexOf('\\n\\n')\n if (idx === -1) break\n events.push(rest.slice(0, idx))\n rest = rest.slice(idx + 2)\n }\n return { events, rest }\n}\n\nfunction extractDataPayload(eventBlock: string): string | null {\n const lines = eventBlock.split('\\n')\n const dataLines: string[] = []\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n dataLines.push(line.slice(6))\n } else if (line.startsWith('data:')) {\n dataLines.push(line.slice(5))\n }\n }\n if (dataLines.length === 0) return null\n return dataLines.join('\\n')\n}\n\nasync function readErrorEnvelope(response: Response): Promise<AiChatErrorEnvelope> {\n try {\n const data = (await response.clone().json()) as\n | { error?: unknown; code?: unknown; message?: unknown }\n | null\n if (data && typeof data === 'object') {\n const rawMessage =\n (typeof data.error === 'string' && data.error) ||\n (typeof data.message === 'string' && data.message) ||\n ''\n const rawCode = typeof data.code === 'string' ? data.code : undefined\n if (rawMessage || rawCode) {\n return {\n code: rawCode,\n message: rawMessage || 'Agent dispatch failed.',\n }\n }\n }\n } catch {\n // Fall through to text fallback\n }\n const text = await response.text().catch(() => '')\n return { message: text || `Agent dispatch failed (${response.status}).` }\n}\n\nexport function useAiChat(input: UseAiChatInput): UseAiChatResult {\n const { agent, apiPath, pageContext, attachmentIds, debug, initialMessages, onError, conversationId: conversationIdInput, providerOverride, modelOverride, onConversationNotFound } = input\n\n // Minted once on mount when the caller does not supply a conversationId.\n // The ref keeps the id stable across re-renders and is reused for every\n // turn so the Phase 3 WS-C `prepareMutation` idempotency hash stays\n // stable within the same chat. When the agent has a persisted session in\n // localStorage we re-hydrate the conversationId from it so re-opening the\n // chat continues the previous turn instead of starting fresh.\n const persistedRef = React.useRef<PersistedAiChatSession | null | 'unread'>('unread')\n if (persistedRef.current === 'unread') {\n // When the caller pins a `conversationId` (multi-tab session mode) we\n // read ONLY from that per-conversation slot. Falling back to the\n // legacy agent-only slot here would make every brand-new tab inherit\n // the previous tab's messages \u2014 the \"+ shows the same chat\" bug \u2014 so\n // unknown conversationIds always start clean. Without a pinned id we\n // keep the legacy single-session-per-agent layout for backward\n // compatibility.\n persistedRef.current =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? readPersistedSession(agent, conversationIdInput)\n : readPersistedSession(agent)\n }\n const persisted = persistedRef.current\n\n const mintedConversationIdRef = React.useRef<string | null>(null)\n if (mintedConversationIdRef.current === null) {\n mintedConversationIdRef.current = persisted?.conversationId ?? makeConversationId()\n }\n const effectiveConversationId =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : mintedConversationIdRef.current\n const persistKey =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : null\n const sessionStorageKey = getSessionStorageKey(agent, persistKey)\n\n const [messages, setMessages] = React.useState<AiChatMessage[]>(() => {\n if (persisted && persisted.messages.length > 0) {\n return persisted.messages\n }\n return (initialMessages ?? []).map((entry) => ({\n id: makeMessageId(),\n role: entry.role,\n content: entry.content,\n }))\n })\n\n // Persist messages + conversationId on every change. Skip during in-flight\n // streaming so we do not write the same growing string on every chunk \u2014\n // the next idle tick captures the final assistant content.\n const [status, setStatusInternal] = React.useState<'idle' | 'submitting' | 'streaming'>(() =>\n isSessionStreamActive(sessionStorageKey) ? 'streaming' : 'idle',\n )\n // Refs mirror the latest persist state so the unmount cleanup can flush\n // an in-flight assistant message to localStorage even though the streaming\n // skip above (and React's stale-closure semantics) would otherwise lose it.\n // Without this, closing the dock or switching agents while the assistant\n // is \"Thinking\" abandons the partial reply (issue #1816).\n const latestMessagesRef = React.useRef<AiChatMessage[]>(messages)\n const latestStatusRef = React.useRef<'idle' | 'submitting' | 'streaming'>(status)\n const persistKeyRef = React.useRef<string | null>(null)\n const agentRef = React.useRef<string>(agent)\n const pageContextRef = React.useRef<Record<string, unknown> | undefined>(pageContext)\n const effectiveConversationIdRef = React.useRef<string>(effectiveConversationId)\n const sessionStorageKeyRef = React.useRef<string>(sessionStorageKey)\n const mountedRef = React.useRef(true)\n React.useEffect(() => {\n latestMessagesRef.current = messages\n }, [messages])\n React.useEffect(() => {\n latestStatusRef.current = status\n }, [status])\n React.useEffect(() => {\n persistKeyRef.current =\n typeof conversationIdInput === 'string' && conversationIdInput.length > 0\n ? conversationIdInput\n : null\n }, [conversationIdInput])\n React.useEffect(() => {\n agentRef.current = agent\n }, [agent])\n React.useEffect(() => {\n pageContextRef.current = pageContext\n }, [pageContext])\n React.useEffect(() => {\n effectiveConversationIdRef.current = effectiveConversationId\n }, [effectiveConversationId])\n React.useEffect(() => {\n sessionStorageKeyRef.current = sessionStorageKey\n }, [sessionStorageKey])\n React.useEffect(() => {\n if (status !== 'idle') return\n if (messages.length === 0) {\n clearPersistedSession(agent, persistKey)\n return\n }\n writePersistedSession(\n agent,\n {\n v: SESSION_STORAGE_VERSION,\n conversationId: effectiveConversationId,\n messages,\n },\n persistKey,\n )\n }, [agent, effectiveConversationId, messages, persistKey, status])\n const persistSnapshot = React.useCallback((snapshot: AiChatMessage[], notify = false) => {\n if (snapshot.length === 0) return\n writePersistedSession(\n agentRef.current,\n {\n v: SESSION_STORAGE_VERSION,\n conversationId: effectiveConversationIdRef.current,\n messages: snapshot,\n },\n persistKeyRef.current,\n { notify },\n )\n }, [])\n\n const updateMessages = React.useCallback(\n (\n updater:\n | AiChatMessage[]\n | ((current: AiChatMessage[]) => AiChatMessage[]),\n options?: { persistWhenUnmounted?: boolean },\n ) => {\n const current = latestMessagesRef.current\n const next = typeof updater === 'function' ? updater(current) : updater\n latestMessagesRef.current = next\n if (mountedRef.current) {\n setMessages(next)\n } else if (options?.persistWhenUnmounted) {\n persistSnapshot(next, true)\n }\n },\n [persistSnapshot],\n )\n\n React.useEffect(() => {\n let cancelled = false\n const localCandidate = readPersistedSession(agent, persistKey)\n\n async function hydrateFromServer(): Promise<void> {\n const transcriptResult = await loadAiServerTranscript(effectiveConversationId, { limit: 100 })\n if (cancelled || latestStatusRef.current !== 'idle') return\n\n if (transcriptResult.ok) {\n const transcript = transcriptResult.data\n if (typeof transcript.conversation.isOwner === 'boolean') {\n setIsOwner(transcript.conversation.isOwner)\n }\n const serverMessages = transcript.messages\n .map(serverMessageToChatMessage)\n .filter((message): message is AiChatMessage => message !== null)\n if (serverMessages.length > 0 || !localCandidate || localCandidate.messages.length === 0) {\n updateMessages(serverMessages)\n if (serverMessages.length > 0) persistSnapshot(serverMessages, true)\n return\n }\n }\n\n // 404 \u2014 the conversation does not exist for the current tenant/org\n // scope (e.g. the user switched scope and the stale conversationId\n // came from the previous bucket's localStorage). Self-heal: drop\n // the local cache, signal the host so it can prune its session\n // registry, and NEVER fall through to importAiLocalConversation \u2014\n // that would silently write the previous scope's messages onto the\n // new scope's server.\n if (!transcriptResult.ok && transcriptResult.notFound) {\n updateMessages([])\n clearPersistedSession(agent, persistKey)\n onConversationNotFoundRef.current?.()\n return\n }\n\n if (!localCandidate || localCandidate.messages.length === 0) {\n if (!transcriptResult.ok) {\n // Transient transport failure (network down, 5xx, \u2026). Mint a\n // new server-side record optimistically; the next idle hydrate\n // will reconcile.\n void createAiServerConversation({\n agentId: agent,\n conversationId: effectiveConversationId,\n pageContext: pageContextRef.current ?? null,\n })\n }\n return\n }\n\n if (hasImportMarker(agent, effectiveConversationId)) return\n const imported = await importAiLocalConversation({\n agentId: agent,\n conversationId: effectiveConversationId,\n pageContext: pageContextRef.current ?? null,\n messages: localCandidate.messages,\n })\n if (cancelled || !imported) return\n writeImportMarker(agent, effectiveConversationId)\n }\n\n void hydrateFromServer()\n return () => {\n cancelled = true\n }\n }, [agent, effectiveConversationId, persistKey, persistSnapshot, updateMessages])\n\n const setStatus = React.useCallback((next: 'idle' | 'submitting' | 'streaming') => {\n if (mountedRef.current) {\n setStatusInternal(next)\n }\n }, [])\n const [error, setError] = React.useState<AiChatErrorEnvelope | null>(null)\n const [lastRequestDebug, setLastRequestDebug] = React.useState<\n { url: string; body: unknown } | null\n >(null)\n const [lastResponseDebug, setLastResponseDebug] = React.useState<\n { status: number; text: string } | null\n >(null)\n\n const [lastLoopTrace, setLastLoopTrace] = React.useState<LoopTracePanelTrace | null>(null)\n const [isOwner, setIsOwner] = React.useState<boolean | null>(null)\n\n const abortRef = React.useRef<AbortController | null>(null)\n const onErrorRef = React.useRef(onError)\n React.useEffect(() => {\n onErrorRef.current = onError\n }, [onError])\n const onConversationNotFoundRef = React.useRef(onConversationNotFound)\n React.useEffect(() => {\n onConversationNotFoundRef.current = onConversationNotFound\n }, [onConversationNotFound])\n\n const emitError = React.useCallback((envelope: AiChatErrorEnvelope) => {\n if (mountedRef.current) {\n setError(envelope)\n try {\n onErrorRef.current?.(envelope)\n } catch {\n // UI layer must never throw because a caller-supplied error handler\n // misbehaved.\n }\n }\n }, [])\n\n const cancel = React.useCallback(() => {\n if (abortRef.current) {\n abortRef.current.abort()\n abortRef.current = null\n }\n writeSessionStreamState(sessionStorageKeyRef.current, false)\n setStatus('idle')\n }, [setStatus])\n\n const reset = React.useCallback(() => {\n cancel()\n updateMessages([])\n setError(null)\n setLastRequestDebug(null)\n setLastResponseDebug(null)\n setLastLoopTrace(null)\n clearPersistedSession(agent, persistKey)\n mintedConversationIdRef.current = makeConversationId()\n }, [agent, cancel, persistKey, updateMessages])\n\n const sendMessage = React.useCallback(\n async (textInput: string, files?: AiChatMessageFile[]) => {\n const trimmed = textInput.trim()\n if (!trimmed) return\n if (abortRef.current) {\n abortRef.current.abort()\n }\n\n setError(null)\n const userMessage: AiChatMessage = {\n id: makeMessageId(),\n role: 'user',\n content: trimmed,\n files: files && files.length > 0 ? files : undefined,\n }\n const assistantMessage: AiChatMessage = {\n id: makeMessageId(),\n role: 'assistant',\n content: '',\n }\n const assistantId = assistantMessage.id\n // Snapshot prior messages for request payload so the dispatcher sees the\n // full turn history including the just-added user message.\n const outgoingHistory = [...latestMessagesRef.current, userMessage]\n updateMessages([...outgoingHistory, assistantMessage], { persistWhenUnmounted: true })\n setStatus('submitting')\n writeSessionStreamState(sessionStorageKey, true)\n\n const controller = new AbortController()\n abortRef.current = controller\n\n const url = getTransportEndpoint(agent, apiPath, providerOverride, modelOverride)\n const body = {\n messages: outgoingHistory.map((message) => ({\n id: message.id,\n role: message.role,\n content: message.content,\n files: message.files,\n uiParts: message.uiParts,\n })),\n pageContext,\n attachmentIds,\n debug,\n conversationId: effectiveConversationId,\n }\n if (mountedRef.current) {\n setLastRequestDebug({ url, body })\n }\n\n let response: Response\n try {\n response = await apiFetch(url, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Accept: 'text/event-stream, text/plain, application/json',\n },\n body: JSON.stringify(body),\n signal: controller.signal,\n })\n } catch (requestError) {\n if ((requestError as { name?: string })?.name === 'AbortError') {\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n const message =\n requestError instanceof Error\n ? requestError.message\n : 'Network request failed.'\n emitError({ message })\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n\n if (!response.ok) {\n const envelope = await readErrorEnvelope(response)\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: envelope.message })\n }\n emitError(envelope)\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n abortRef.current = null\n return\n }\n\n const bodyStream = response.body\n if (!bodyStream) {\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: '' })\n }\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n abortRef.current = null\n return\n }\n\n // Per-turn loop trace collected from the `loop-finish` SSE event emitted\n // by the dispatcher at the end of the stream (Phase 4).\n let pendingLoopTrace: LoopTracePanelTrace | null = null\n\n const headerGet = (name: string): string | null => {\n const headers = (response as { headers?: { get?: (k: string) => string | null } })\n .headers\n if (!headers || typeof headers.get !== 'function') return null\n try {\n return headers.get(name)\n } catch {\n return null\n }\n }\n const isUiMessageStream =\n headerGet('x-vercel-ai-ui-message-stream') !== null ||\n (headerGet('content-type') ?? '').includes('event-stream')\n\n setStatus('streaming')\n const reader = bodyStream.getReader()\n const decoder = new TextDecoder()\n let streamedRaw = ''\n let builder = createBuilder()\n let sseBuffer = ''\n const flushUiMessageBuffer = (extra?: string) => {\n if (extra) sseBuffer += extra\n const { events, rest } = parseSseLines(sseBuffer)\n sseBuffer = rest\n for (const block of events) {\n const data = extractDataPayload(block)\n if (!data) continue\n if (data === '[DONE]') continue\n try {\n const parsed = JSON.parse(data) as { type?: string; trace?: unknown }\n if (!parsed || typeof parsed.type !== 'string') continue\n if (parsed.type === 'loop-finish') {\n // Capture the loop trace for post-stream state update.\n pendingLoopTrace = parsed.trace as LoopTracePanelTrace ?? null\n } else {\n builder = applyChunk(builder, parsed as { type: string })\n }\n } catch {\n // Tolerate malformed events / SSE comments.\n }\n }\n }\n try {\n while (true) {\n const { value, done } = await reader.read()\n if (done) break\n if (!value) continue\n const piece = decoder.decode(value, { stream: true })\n if (!piece) continue\n streamedRaw += piece\n\n if (isUiMessageStream) {\n flushUiMessageBuffer(piece)\n } else {\n // Plain text fallback (legacy `toTextStreamResponse`).\n builder = { ...builder, text: streamedRaw }\n }\n const snapshotBuilder = builder\n updateMessages(\n (current) =>\n current.map((entry) =>\n entry.id === assistantId\n ? mergeAssistantMessage(entry, snapshotBuilder)\n : entry,\n ),\n { persistWhenUnmounted: true },\n )\n }\n const tail = decoder.decode()\n if (tail) {\n streamedRaw += tail\n if (isUiMessageStream) {\n flushUiMessageBuffer(tail)\n } else {\n builder = { ...builder, text: streamedRaw }\n }\n }\n if (isUiMessageStream && sseBuffer.length > 0) {\n flushUiMessageBuffer('\\n\\n')\n }\n builder = { ...builder, reasoningStreaming: false }\n const finalSnapshot = builder\n updateMessages(\n (current) =>\n current.map((entry) =>\n entry.id === assistantId\n ? mergeAssistantMessage(entry, finalSnapshot)\n : entry,\n ),\n { persistWhenUnmounted: true },\n )\n if (mountedRef.current) {\n setLastResponseDebug({ status: response.status, text: streamedRaw })\n if (pendingLoopTrace !== null) {\n setLastLoopTrace(pendingLoopTrace)\n }\n }\n const isEmpty =\n !builder.text.trim() && builder.toolCalls.length === 0 && !builder.reasoning\n if (isEmpty) {\n emitError({\n code: 'empty_response',\n message:\n 'The AI agent returned an empty response. This usually means the LLM provider rejected the request (invalid API key, rate limit, or model error). Check your server logs for details.',\n })\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n }\n } catch (streamError) {\n if ((streamError as { name?: string })?.name === 'AbortError') {\n // Cancelled by the user \u2014 keep whatever we have so far and exit\n // quietly.\n } else {\n const rawMessage =\n streamError instanceof Error\n ? streamError.message\n : 'Stream interrupted.'\n // LLM provider errors (auth failures, rate limits, invalid tool\n // schemas) surface as stream read errors. Include a hint so the\n // operator can check server logs for the full stack trace.\n const message = rawMessage.includes('API')\n ? rawMessage\n : `${rawMessage} \u2014 check server logs for LLM provider details.`\n emitError({ code: 'stream_error', message })\n // Remove the empty assistant placeholder so the error alert is\n // the only visible feedback.\n updateMessages((current) => current.filter((entry) => entry.id !== assistantId), {\n persistWhenUnmounted: true,\n })\n }\n } finally {\n reader.releaseLock()\n if (abortRef.current === controller) {\n abortRef.current = null\n }\n writeSessionStreamState(sessionStorageKey, false)\n setStatus('idle')\n }\n },\n [agent, apiPath, attachmentIds, debug, effectiveConversationId, emitError, messages, modelOverride, pageContext, providerOverride],\n )\n\n React.useEffect(() => {\n if (typeof window === 'undefined') return\n const handleSessionUpdate = (event: Event) => {\n const detail = (event as CustomEvent<{ key?: string }>).detail\n if (!detail || detail.key !== sessionStorageKey) return\n const next = readPersistedSession(agent, persistKey)\n if (!next) return\n if (next.conversationId !== effectiveConversationId) return\n updateMessages(next.messages)\n }\n window.addEventListener(SESSION_UPDATED_EVENT, handleSessionUpdate)\n return () => {\n window.removeEventListener(SESSION_UPDATED_EVENT, handleSessionUpdate)\n }\n }, [agent, effectiveConversationId, persistKey, sessionStorageKey, updateMessages])\n\n React.useEffect(() => {\n if (isSessionStreamActive(sessionStorageKey)) {\n setStatusInternal('streaming')\n }\n if (typeof window === 'undefined') return\n const handleStreamState = (event: Event) => {\n const detail = (event as CustomEvent<{ key?: string; active?: boolean }>).detail\n if (!detail || detail.key !== sessionStorageKey) return\n setStatusInternal(detail.active ? 'streaming' : 'idle')\n }\n window.addEventListener(SESSION_STREAM_STATE_EVENT, handleStreamState)\n return () => {\n window.removeEventListener(SESSION_STREAM_STATE_EVENT, handleStreamState)\n }\n }, [sessionStorageKey])\n\n React.useEffect(() => {\n mountedRef.current = true\n return () => {\n // Flush the latest snapshot \u2014 including any in-flight assistant\n // content \u2014 and let the request keep running in the background.\n // Background stream updates continue writing to this same storage slot\n // and notify any reopened chat for the conversation (issue #1816).\n const finalMessages = latestMessagesRef.current\n if (finalMessages.length > 0) {\n persistSnapshot(finalMessages, true)\n }\n mountedRef.current = false\n }\n }, [persistSnapshot])\n\n return {\n messages,\n status,\n error,\n lastRequestDebug,\n lastResponseDebug,\n conversationId: effectiveConversationId,\n lastLoopTrace,\n isOwner,\n sendMessage,\n cancel,\n reset,\n }\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/dist/backend/AppShell.js
CHANGED
|
@@ -28,6 +28,7 @@ import { UpgradeActionBanner } from "./upgrades/UpgradeActionBanner.js";
|
|
|
28
28
|
import { PartialIndexBanner } from "./indexes/PartialIndexBanner.js";
|
|
29
29
|
import { useLocale, useT } from "@open-mercato/shared/lib/i18n/context";
|
|
30
30
|
import { slugifySidebarId } from "@open-mercato/shared/modules/navigation/sidebarPreferences";
|
|
31
|
+
import { readVersionedPreference, writeVersionedPreference } from "@open-mercato/shared/lib/browser/versionedPreference";
|
|
31
32
|
import { cloneSidebarGroups } from "./sidebar/customization-helpers.js";
|
|
32
33
|
import { InjectionSpot } from "./injection/InjectionSpot.js";
|
|
33
34
|
import { LEGACY_GLOBAL_MUTATION_INJECTION_SPOT_ID } from "./injection/mutationEvents.js";
|
|
@@ -53,6 +54,11 @@ import {
|
|
|
53
54
|
GLOBAL_HEADER_STATUS_INDICATORS_INJECTION_SPOT_ID,
|
|
54
55
|
GLOBAL_SIDEBAR_STATUS_BADGES_INJECTION_SPOT_ID
|
|
55
56
|
} from "./injection/spotIds.js";
|
|
57
|
+
const SIDEBAR_OPEN_GROUPS_KEY = "om:sidebarOpenGroups";
|
|
58
|
+
const SIDEBAR_OPEN_GROUPS_VERSION = 1;
|
|
59
|
+
function isBooleanRecord(value) {
|
|
60
|
+
return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((entry) => typeof entry === "boolean");
|
|
61
|
+
}
|
|
56
62
|
function convertInjectedMenuItemToSidebarItem(item, title) {
|
|
57
63
|
if (!item.href) return null;
|
|
58
64
|
return {
|
|
@@ -417,21 +423,23 @@ function AppShellBody({ productName, logo, email, canManageUpgradeActions = fals
|
|
|
417
423
|
};
|
|
418
424
|
}, [mobileOpen]);
|
|
419
425
|
React.useEffect(() => {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
426
|
+
const parsed = readVersionedPreference(
|
|
427
|
+
SIDEBAR_OPEN_GROUPS_KEY,
|
|
428
|
+
SIDEBAR_OPEN_GROUPS_VERSION,
|
|
429
|
+
isBooleanRecord,
|
|
430
|
+
{},
|
|
431
|
+
{ legacyIsValid: isBooleanRecord }
|
|
432
|
+
);
|
|
433
|
+
if (Object.keys(parsed).length === 0) return;
|
|
434
|
+
setOpenGroups((prev) => {
|
|
435
|
+
const next = { ...prev };
|
|
436
|
+
for (const group of resolvedGroups) {
|
|
437
|
+
const key = resolveGroupKey(group);
|
|
438
|
+
if (key in parsed) next[key] = !!parsed[key];
|
|
439
|
+
else if (group.name in parsed) next[key] = !!parsed[group.name];
|
|
440
|
+
}
|
|
441
|
+
return next;
|
|
442
|
+
});
|
|
435
443
|
}, [resolvedGroups]);
|
|
436
444
|
const toggleGroup = (groupId) => setOpenGroups((prev) => ({ ...prev, [groupId]: prev[groupId] === false }));
|
|
437
445
|
const asideWidth = effectiveCollapsed ? "80px" : expandedSidebarWidth;
|
|
@@ -461,10 +469,7 @@ function AppShellBody({ productName, logo, email, canManageUpgradeActions = fals
|
|
|
461
469
|
previousSidebarModeRef.current = sidebarMode;
|
|
462
470
|
}, [sidebarMode, collapsed]);
|
|
463
471
|
React.useEffect(() => {
|
|
464
|
-
|
|
465
|
-
localStorage.setItem("om:sidebarOpenGroups", JSON.stringify(openGroups));
|
|
466
|
-
} catch {
|
|
467
|
-
}
|
|
472
|
+
writeVersionedPreference(SIDEBAR_OPEN_GROUPS_KEY, SIDEBAR_OPEN_GROUPS_VERSION, openGroups);
|
|
468
473
|
}, [openGroups]);
|
|
469
474
|
React.useEffect(() => {
|
|
470
475
|
const activeGroup = navGroups.find((g) => g.items.some((i) => pathname?.startsWith(i.href)));
|