agent-relay-server 0.57.2 → 0.59.0
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/docs/openapi.json +151 -1
- package/package.json +2 -2
- package/public/assets/{MessageBubble-CHKzD4RY.js → MessageBubble-BrEKymoV.js} +2 -2
- package/public/assets/{MessageBubble-CHKzD4RY.js.map → MessageBubble-BrEKymoV.js.map} +1 -1
- package/public/assets/{PlanGraphPanel-BFVTJa0q.js → PlanGraphPanel-CXix1C-H.js} +2 -2
- package/public/assets/{PlanGraphPanel-BFVTJa0q.js.map → PlanGraphPanel-CXix1C-H.js.map} +1 -1
- package/public/assets/{activity-M-dFLU_O.js → activity-CRafQ1Je.js} +2 -2
- package/public/assets/{activity-M-dFLU_O.js.map → activity-CRafQ1Je.js.map} +1 -1
- package/public/assets/{agent-profiles-CvHSoyCX.js → agent-profiles-DW4o5-ND.js} +2 -2
- package/public/assets/{agent-profiles-CvHSoyCX.js.map → agent-profiles-DW4o5-ND.js.map} +1 -1
- package/public/assets/agents-Cbegv0Hm.js +2 -0
- package/public/assets/{agents-DZBM5dp_.js.map → agents-Cbegv0Hm.js.map} +1 -1
- package/public/assets/analytics-DX6T7JX3.js +910 -0
- package/public/assets/{analytics-bvjb3Urs.js.map → analytics-DX6T7JX3.js.map} +1 -1
- package/public/assets/{automation-CDSn9Uq-.js → automation-CzSBv4gg.js} +2 -2
- package/public/assets/{automation-CDSn9Uq-.js.map → automation-CzSBv4gg.js.map} +1 -1
- package/public/assets/{badge-Dj6PREuI.js → badge-Cy6qO_Jz.js} +2 -2
- package/public/assets/{badge-Dj6PREuI.js.map → badge-Cy6qO_Jz.js.map} +1 -1
- package/public/assets/{branch-state-badge-CdYg2HYc.js → branch-state-badge-CRA50r81.js} +2 -2
- package/public/assets/{branch-state-badge-CdYg2HYc.js.map → branch-state-badge-CRA50r81.js.map} +1 -1
- package/public/assets/{button-Cqpufqam.js → button-73wpRw65.js} +2 -2
- package/public/assets/{button-Cqpufqam.js.map → button-73wpRw65.js.map} +1 -1
- package/public/assets/{card-OmDF73CE.js → card-BU6PvVHv.js} +2 -2
- package/public/assets/{card-OmDF73CE.js.map → card-BU6PvVHv.js.map} +1 -1
- package/public/assets/{channels-BkxrdOFZ.js → channels-B1MQCPCT.js} +2 -2
- package/public/assets/{channels-BkxrdOFZ.js.map → channels-B1MQCPCT.js.map} +1 -1
- package/public/assets/chat-BqIaFYGJ.js +2 -0
- package/public/assets/{chat-YzX1z76l.js.map → chat-BqIaFYGJ.js.map} +1 -1
- package/public/assets/{connectors-Bg3rHgl_.js → connectors-C9aFJIrh.js} +2 -2
- package/public/assets/{connectors-Bg3rHgl_.js.map → connectors-C9aFJIrh.js.map} +1 -1
- package/public/assets/{copy-button-DI7b8A7_.js → copy-button-HRopUwui.js} +2 -2
- package/public/assets/{copy-button-DI7b8A7_.js.map → copy-button-HRopUwui.js.map} +1 -1
- package/public/assets/display-CketawEF.js.map +1 -1
- package/public/assets/{dist-CNjnTZEI.js → dist-CC5f7oYw.js} +2 -2
- package/public/assets/{dist-CNjnTZEI.js.map → dist-CC5f7oYw.js.map} +1 -1
- package/public/assets/{dist-DkeTrbTA.js → dist-CLHMeXYi.js} +2 -2
- package/public/assets/{dist-DkeTrbTA.js.map → dist-CLHMeXYi.js.map} +1 -1
- package/public/assets/{dist-C5t_OxI0.js → dist-Ci1MM51P.js} +2 -2
- package/public/assets/{dist-C5t_OxI0.js.map → dist-Ci1MM51P.js.map} +1 -1
- package/public/assets/{dist-CV3sgFiv.js → dist-Cko2KPW8.js} +2 -2
- package/public/assets/{dist-CV3sgFiv.js.map → dist-Cko2KPW8.js.map} +1 -1
- package/public/assets/dist-D41tzJYg.js +2 -0
- package/public/assets/{dist-yYauqYKR.js.map → dist-D41tzJYg.js.map} +1 -1
- package/public/assets/{dist-CoUvwLTy.js → dist-DFLciw0h.js} +2 -2
- package/public/assets/{dist-CoUvwLTy.js.map → dist-DFLciw0h.js.map} +1 -1
- package/public/assets/dist-DUoZnlB2.js +2 -0
- package/public/assets/{dist-DmhyU0Na.js.map → dist-DUoZnlB2.js.map} +1 -1
- package/public/assets/dist-De2oguBm.js +2 -0
- package/public/assets/{dist-DxkMwkrr.js.map → dist-De2oguBm.js.map} +1 -1
- package/public/assets/{dist-1x-LE9Uk.js → dist-DmN94rc8.js} +2 -2
- package/public/assets/{dist-1x-LE9Uk.js.map → dist-DmN94rc8.js.map} +1 -1
- package/public/assets/{dist-DV2tZ2fq.js → dist-Kkpnjx_M.js} +2 -2
- package/public/assets/{dist-DV2tZ2fq.js.map → dist-Kkpnjx_M.js.map} +1 -1
- package/public/assets/{dist-Ca3ujqG_.js → dist-T7u7IZGJ.js} +2 -2
- package/public/assets/{dist-Ca3ujqG_.js.map → dist-T7u7IZGJ.js.map} +1 -1
- package/public/assets/{es2015-DcavLcQ0.js → es2015-Df08Opjx.js} +2 -2
- package/public/assets/{es2015-DcavLcQ0.js.map → es2015-Df08Opjx.js.map} +1 -1
- package/public/assets/{formatted-body-impl-D8i9qqL1.js → formatted-body-impl-DOXH7oDF.js} +2 -2
- package/public/assets/{formatted-body-impl-D8i9qqL1.js.map → formatted-body-impl-DOXH7oDF.js.map} +1 -1
- package/public/assets/index-DaNOQJci.css +2 -0
- package/public/assets/index-br_Y6Cvt.js +21 -0
- package/public/assets/{index-Ba2RDJaA.js.map → index-br_Y6Cvt.js.map} +1 -1
- package/public/assets/{input-C_dl_9fG.js → input-B2EDz-BE.js} +2 -2
- package/public/assets/{input-C_dl_9fG.js.map → input-B2EDz-BE.js.map} +1 -1
- package/public/assets/{insights-D1aANwZ2.js → insights-B1edq5lk.js} +2 -2
- package/public/assets/{insights-D1aANwZ2.js.map → insights-B1edq5lk.js.map} +1 -1
- package/public/assets/{integrations-BrAr9bap.js → integrations-Cm4fjp7_.js} +2 -2
- package/public/assets/{integrations-BrAr9bap.js.map → integrations-Cm4fjp7_.js.map} +1 -1
- package/public/assets/{lib-B3iMVsf-.js → lib-BZrjIp-o.js} +3 -3
- package/public/assets/{lib-B3iMVsf-.js.map → lib-BZrjIp-o.js.map} +1 -1
- package/public/assets/lucide-react-H_-8G79A.js +9 -0
- package/public/assets/{lucide-react-CMhBract.js.map → lucide-react-H_-8G79A.js.map} +1 -1
- package/public/assets/{maintenance-C-xspsMG.js → maintenance-D6zvw0fU.js} +2 -2
- package/public/assets/{maintenance-C-xspsMG.js.map → maintenance-D6zvw0fU.js.map} +1 -1
- package/public/assets/{managed-agents-Bf3ni0vo.js → managed-agents-CzfEznM1.js} +2 -2
- package/public/assets/{managed-agents-Bf3ni0vo.js.map → managed-agents-CzfEznM1.js.map} +1 -1
- package/public/assets/{markdown-preview-impl-DKbUBTf_.js → markdown-preview-impl-DZlCaTHQ.js} +2 -2
- package/public/assets/{markdown-preview-impl-DKbUBTf_.js.map → markdown-preview-impl-DZlCaTHQ.js.map} +1 -1
- package/public/assets/memory-CUUAzJDc.js +2 -0
- package/public/assets/{memory-DeLwJFwH.js.map → memory-CUUAzJDc.js.map} +1 -1
- package/public/assets/messages-6QZH46LG.js +2 -0
- package/public/assets/{messages-Cww6dZnM.js.map → messages-6QZH46LG.js.map} +1 -1
- package/public/assets/{orchestrators-DRjwROXw.js → orchestrators-CrcqkPuc.js} +2 -2
- package/public/assets/{orchestrators-DRjwROXw.js.map → orchestrators-CrcqkPuc.js.map} +1 -1
- package/public/assets/overview-418xXZO_.js +2 -0
- package/public/assets/{overview-DjcRomU_.js.map → overview-418xXZO_.js.map} +1 -1
- package/public/assets/pairs-CnedATgT.js +2 -0
- package/public/assets/{pairs-DT0u2fnW.js.map → pairs-CnedATgT.js.map} +1 -1
- package/public/assets/{react-dom-Dtl9-x_1.js → react-dom-WI6TjVe7.js} +2 -2
- package/public/assets/{react-dom-Dtl9-x_1.js.map → react-dom-WI6TjVe7.js.map} +1 -1
- package/public/assets/{security-Bwg3oFI8.js → security-DS13pbag.js} +2 -2
- package/public/assets/{security-Bwg3oFI8.js.map → security-DS13pbag.js.map} +1 -1
- package/public/assets/{select-Cariwv69.js → select-BOprGZDS.js} +2 -2
- package/public/assets/{select-Cariwv69.js.map → select-BOprGZDS.js.map} +1 -1
- package/public/assets/{settings-Bd6Ug3DO.js → settings-BgInn_f3.js} +4 -4
- package/public/assets/{settings-Bd6Ug3DO.js.map → settings-BgInn_f3.js.map} +1 -1
- package/public/assets/{store-BgJq_lN9.js → store-DHO_a8BD.js} +5 -5
- package/public/assets/{store-BgJq_lN9.js.map → store-DHO_a8BD.js.map} +1 -1
- package/public/assets/{tasks-DVz1zhOU.js → tasks-B-4oCPuv.js} +2 -2
- package/public/assets/{tasks-DVz1zhOU.js.map → tasks-B-4oCPuv.js.map} +1 -1
- package/public/assets/teams-sCQAs_QA.js +2 -0
- package/public/assets/{teams-TqFntFB_.js.map → teams-sCQAs_QA.js.map} +1 -1
- package/public/assets/{terminal-viewer-impl-OsJ5crlx.js → terminal-viewer-impl-BhqS2XpS.js} +3 -3
- package/public/assets/{terminal-viewer-impl-OsJ5crlx.js.map → terminal-viewer-impl-BhqS2XpS.js.map} +1 -1
- package/public/assets/{use-keyboard-viewport-BYHcL7sP.js → use-keyboard-viewport-fOL_3RHB.js} +2 -2
- package/public/assets/{use-keyboard-viewport-BYHcL7sP.js.map → use-keyboard-viewport-fOL_3RHB.js.map} +1 -1
- package/public/assets/{work-queue-B4b2jPLR.js → work-queue-BDhjlwUH.js} +2 -2
- package/public/assets/{work-queue-B4b2jPLR.js.map → work-queue-BDhjlwUH.js.map} +1 -1
- package/public/assets/workspaces-6I-C69nf.js +3 -0
- package/public/assets/{workspaces-m97LP4Eu.js.map → workspaces-6I-C69nf.js.map} +1 -1
- package/public/index.html +21 -21
- package/src/db/index.ts +2 -0
- package/src/db/project-entries.ts +270 -0
- package/src/db/projects.ts +304 -0
- package/src/db/schema.ts +50 -0
- package/src/db/stats.ts +209 -38
- package/src/db/teams.ts +13 -2
- package/src/maintenance.ts +9 -2
- package/src/mcp-project-tools.ts +352 -0
- package/src/mcp.ts +6 -0
- package/src/project-ingest.ts +186 -0
- package/src/project-instructions.ts +176 -0
- package/src/routes/agents-spawn.ts +2 -0
- package/src/routes/commands.ts +11 -5
- package/src/routes/index.ts +5 -0
- package/src/routes/projects.ts +55 -0
- package/src/runtime-tokens.ts +6 -0
- package/src/security.ts +4 -0
- package/src/services/spawn-agent.ts +33 -2
- package/src/services/team.ts +5 -1
- package/src/token-db.ts +3 -3
- package/src/workspace-merge.ts +11 -0
- package/src/workspace-pr-completion.ts +57 -1
- package/public/assets/agents-DZBM5dp_.js +0 -2
- package/public/assets/analytics-bvjb3Urs.js +0 -910
- package/public/assets/chat-YzX1z76l.js +0 -2
- package/public/assets/dist-DmhyU0Na.js +0 -2
- package/public/assets/dist-DxkMwkrr.js +0 -2
- package/public/assets/dist-yYauqYKR.js +0 -2
- package/public/assets/index-Ba2RDJaA.js +0 -21
- package/public/assets/index-CtFXWxBJ.css +0 -2
- package/public/assets/lucide-react-CMhBract.js +0 -9
- package/public/assets/memory-DeLwJFwH.js +0 -2
- package/public/assets/messages-Cww6dZnM.js +0 -2
- package/public/assets/overview-DjcRomU_.js +0 -2
- package/public/assets/pairs-DT0u2fnW.js +0 -2
- package/public/assets/teams-TqFntFB_.js +0 -2
- package/public/assets/workspaces-m97LP4Eu.js +0 -3
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MessageBubble-CHKzD4RY.js","names":[],"sources":["../../dashboard/src/components/chat/sessionTrace.ts","../../dashboard/src/components/chat/helpers.ts","../../dashboard/src/components/chat/TraceView.tsx","../../dashboard/src/components/chat/hooks/useTtsPlayingKey.ts","../../dashboard/src/components/chat/MessageBubble.tsx"],"sourcesContent":["\nimport { toTimestamp, responseBodyContainsBlock } from '@/lib/display'\nimport type { ChatStatusEvent as StatusEvent } from '@/store'\nimport type { ChatHistoryImport, ChatHistoryImportEntry, Message } from '@/types'\n\nexport interface ActivityStep { id: number; kind: 'narration' | 'reasoning' | 'tool'; label?: string; text: string; ts: number; turnId?: string }\n\nexport type TimelineEntry =\n | { type: 'message'; msg: Message }\n | { type: 'notice'; msg: Message }\n | { type: 'agent-action'; direction: 'spawned-child' | 'spawned-by'; agentId: string; provider?: string; status: 'ready' | 'exited' | 'failed'; reason?: string; timestamp: number }\n | { type: 'activity'; steps: ActivityStep[]; timestamp: number }\n | { type: 'import-boundary'; history: ChatHistoryImport; timestamp: number }\n | { type: 'imported-message'; history: ChatHistoryImport; entry: ChatHistoryImportEntry }\n | { type: 'status'; event: StatusEvent }\n | { type: 'date'; date: string; timestamp: number }\n | { type: 'created'; timestamp: number }\n\nexport const TIMELINE_STATUS_LABELS: Record<string, string> = {\n online: 'came online',\n offline: 'went offline',\n shutdown: 'shut down',\n restarted: 'restarted',\n 'compaction-requested': 'compaction requested',\n compacting: 'compaction started',\n compacted: 'context compacted',\n 'compaction-stalled': 'compaction stalled — may need manual intervention',\n 'clear-requested': 'clear requested',\n 'clearing-context': 'clear started',\n 'context-cleared': 'context cleared',\n 'clear-stalled': 'context clear stalled — may need manual intervention',\n 'shutting-down': 'shutting down',\n}\n\nconst TIMELINE_STATUSES = new Set(Object.keys(TIMELINE_STATUS_LABELS))\nconst STATUS_DEDUPE_WINDOW_MS = 3000\n\n// True event time of a message: occurredAt (set when a Runner backfilled a message it had\n// queued during an outage, #196) falling back to the server receive time. Used for chat\n// timeline ordering + display so a backfilled turn lands where it actually happened in the\n// conversation, not at the bottom. Display-only — DB poll cursors still use created_at.\nexport function messageEventTime(msg: Message): number {\n return new Date(msg.occurredAt ?? msg.createdAt).getTime()\n}\n\n// Narration/reasoning/tool session events are mirrored as `kind:\"session\"` messages\n// but must not become chat bubbles — they render inline in the turn's activity trace.\n// (`narration` = the agent's intermediate spoken text; `reasoning` = its thinking.)\nexport function sessionActivityStep(msg: Message): ActivityStep | null {\n if (msg.kind !== 'session') return null\n const s = msg.payload?.session as { type?: string; label?: string; turnId?: string } | undefined\n if (!s || (s.type !== 'narration' && s.type !== 'reasoning' && s.type !== 'tool')) return null\n return {\n id: msg.id,\n kind: s.type,\n label: typeof s.label === 'string' ? s.label : undefined,\n text: msg.body,\n ts: messageEventTime(msg),\n turnId: typeof s.turnId === 'string' ? s.turnId : undefined,\n }\n}\n\n// A `notice` session event (e.g. the runner's durable \"context compacted\" marker) renders\n// as a discreet timeline marker, reusing the StatusMarker path. Its `label` carries a\n// timeline-status key (e.g. \"compacted\"); we surface it as a StatusEvent so it dedupes with\n// the ephemeral live timeline-status decoration and survives a reload (the ephemeral one does\n// not). Returns null for non-notice messages or labels we don't render.\nexport function sessionNoticeStatusEvent(msg: Message): StatusEvent | null {\n if (msg.kind !== 'session') return null\n const s = msg.payload?.session as { type?: string; label?: string } | undefined\n if (s?.type !== 'notice') return null\n const status = typeof s.label === 'string' && s.label ? s.label : 'compacted'\n return { id: `notice-${msg.id}`, status, timestamp: messageEventTime(msg) }\n}\n\n// Bus-level system signals (kind:\"system\", from:\"system\") — e.g. branch.landed,\n// context.warning. Rendered as notice pills, NOT chat bubbles or session status markers.\nexport function isBusSignalMessage(msg: Message): boolean {\n return msg.kind === 'system' && msg.from === 'system'\n}\n\n// Spawn lifecycle signals routed to the parent: agent.ready / agent.exited / agent.spawn_failed.\n// These are richer than generic bus signals and get their own agent-action timeline entry.\nconst LIFECYCLE_KINDS = new Set(['agent.ready', 'agent.exited', 'agent.spawn_failed'])\n\nexport function dateKey(ts: number): string {\n const d = new Date(ts)\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`\n}\n\nexport function dedupeStatusEvents(statusEvents: StatusEvent[]): StatusEvent[] {\n const events = statusEvents\n .filter((event) => TIMELINE_STATUSES.has(event.status))\n .slice()\n .sort((a, b) => a.timestamp - b.timestamp)\n const result: StatusEvent[] = []\n for (const event of events) {\n if (result.some((existing) => existing.id === event.id)) continue\n if (result.some((existing) => existing.status === event.status && Math.abs(existing.timestamp - event.timestamp) <= STATUS_DEDUPE_WINDOW_MS)) continue\n result.push(event)\n }\n return result\n}\n\nexport function buildTimeline(\n messages: Message[],\n statusEvents: StatusEvent[],\n createdAt?: string | number | null,\n importedHistory: ChatHistoryImport[] = [],\n options?: { spawnedBy?: { agentId: string; provider?: string } },\n): TimelineEntry[] {\n const raw: Array<{ ts: number; entry: TimelineEntry }> = []\n\n for (const history of importedHistory) {\n const firstImportedAt = history.entries.reduce((min, entry) => Math.min(min, toTimestamp(entry.originalCreatedAt)), Number.POSITIVE_INFINITY)\n const boundaryAt = Number.isFinite(firstImportedAt) ? firstImportedAt - 1 : history.importedAt\n raw.push({ ts: boundaryAt, entry: { type: 'import-boundary', history, timestamp: boundaryAt } })\n for (const entry of history.entries) {\n raw.push({ ts: toTimestamp(entry.originalCreatedAt), entry: { type: 'imported-message', history, entry } })\n }\n }\n\n if (createdAt) {\n const ts = new Date(createdAt as string).getTime()\n if (Number.isFinite(ts)) raw.push({ ts, entry: { type: 'created', timestamp: ts } })\n }\n\n // Synthetic \"spawned by\" lead entry for child agents — derives from the agent's spawnedBy\n // metadata (no inbound system message exists for this direction). Placed at createdAt so it\n // sorts as the first real entry after the created/date markers.\n if (options?.spawnedBy) {\n const ts = createdAt ? new Date(createdAt as string).getTime() : 0\n const spawnedByTs = Number.isFinite(ts) ? ts : 0\n raw.push({ ts: spawnedByTs, entry: { type: 'agent-action', direction: 'spawned-by', agentId: options.spawnedBy.agentId, provider: options.spawnedBy.provider, status: 'ready', timestamp: spawnedByTs } })\n }\n\n // The closing response bubble (session type \"response\") repeats the turn's\n // narration. In `final` capture mode the body is just the final line; in `full`\n // mode it is every narration block joined with blank-line separators\n // (matching extractLastAssistantTurn).\n // Collect the response bodies per turn so a narration step is suppressed from the\n // trace whenever it appears as a blank-line-delimited block of the body — so no line\n // shows twice in either mode (final: only the matching final line; full: all of them).\n const responseBodiesByTurn = new Map<string, string[]>()\n for (const msg of messages) {\n if (msg.kind !== 'session') continue\n const s = msg.payload?.session as { type?: string; turnId?: string } | undefined\n if (s?.type === 'response' && typeof s.turnId === 'string') {\n const list = responseBodiesByTurn.get(s.turnId) ?? []\n list.push(msg.body.trim())\n responseBodiesByTurn.set(s.turnId, list)\n }\n }\n\n // Tracks in-progress agent-action entries by child id so ready→exited coalesces into one entry.\n type RawAgentAction = { ts: number; entry: Extract<TimelineEntry, { type: 'agent-action' }> }\n const agentActionByChild = new Map<string, RawAgentAction>()\n\n const noticeStatusEvents: StatusEvent[] = []\n for (const msg of messages) {\n if (isBusSignalMessage(msg)) {\n const kind = typeof msg.payload?.kind === 'string' ? msg.payload.kind : ''\n if (LIFECYCLE_KINDS.has(kind)) {\n const p = msg.payload as { kind: string; agentId?: string; provider?: string; reason?: string }\n const childId = typeof p.agentId === 'string' ? p.agentId : ''\n const ts = messageEventTime(msg)\n if (!childId) {\n // agent.spawn_failed without a child id (command-level failure): fall through to notice\n raw.push({ ts, entry: { type: 'notice', msg } })\n continue\n }\n const status: 'ready' | 'exited' | 'failed' = kind === 'agent.ready' ? 'ready' : kind === 'agent.exited' ? 'exited' : 'failed'\n const existing = agentActionByChild.get(childId)\n if (existing) {\n // Coalesce: evolve the same entry to the final status in place\n existing.entry.status = status\n if (typeof p.reason === 'string') existing.entry.reason = p.reason\n existing.entry.timestamp = ts\n existing.ts = ts\n } else {\n const item: RawAgentAction = {\n ts,\n entry: {\n type: 'agent-action',\n direction: 'spawned-child',\n agentId: childId,\n provider: typeof p.provider === 'string' ? p.provider : undefined,\n status,\n reason: typeof p.reason === 'string' ? p.reason : undefined,\n timestamp: ts,\n },\n }\n agentActionByChild.set(childId, item)\n raw.push(item)\n }\n continue\n }\n raw.push({ ts: messageEventTime(msg), entry: { type: 'notice', msg } })\n continue\n }\n const notice = sessionNoticeStatusEvent(msg)\n if (notice) {\n noticeStatusEvents.push(notice)\n continue\n }\n const step = sessionActivityStep(msg)\n if (step) {\n if (step.kind === 'narration' && step.turnId) {\n const bodies = responseBodiesByTurn.get(step.turnId)\n if (bodies?.some((body) => responseBodyContainsBlock(body, step.text))) continue\n }\n raw.push({ ts: step.ts, entry: { type: 'activity', steps: [step], timestamp: step.ts } })\n continue\n }\n raw.push({ ts: messageEventTime(msg), entry: { type: 'message', msg } })\n }\n for (const event of dedupeStatusEvents([...statusEvents, ...noticeStatusEvents])) {\n raw.push({ ts: event.timestamp, entry: { type: 'status', event } })\n }\n\n const typeOrder = { created: 0, date: 1, 'import-boundary': 2, 'imported-message': 3, message: 4, activity: 4, notice: 4, 'agent-action': 4, status: 5 } as const\n raw.sort((a, b) => {\n const dt = a.ts - b.ts\n if (Math.abs(dt) < 3000) return typeOrder[a.entry.type] - typeOrder[b.entry.type] || dt\n return dt\n })\n\n const entries: TimelineEntry[] = []\n let lastDate = ''\n for (const { ts, entry } of raw) {\n if (entry.type === 'created') {\n entries.push(entry)\n continue\n }\n const dk = dateKey(ts)\n if (dk !== lastDate) {\n entries.push({ type: 'date', date: dk, timestamp: ts })\n lastDate = dk\n }\n // Coalesce consecutive activity steps from the same turn into one trace.\n if (entry.type === 'activity') {\n const prev = entries[entries.length - 1]\n if (prev && prev.type === 'activity' && sameActivityTurn(prev, entry)) {\n prev.steps.push(...entry.steps)\n continue\n }\n }\n entries.push(entry)\n }\n\n return entries\n}\n\nexport function sameActivityTurn(a: Extract<TimelineEntry, { type: 'activity' }>, b: Extract<TimelineEntry, { type: 'activity' }>): boolean {\n const at = a.steps[a.steps.length - 1]?.turnId\n const bt = b.steps[0]?.turnId\n if (at && bt) return at === bt\n // No turn id on either side: group adjacent steps regardless.\n return !at && !bt\n}\n","\nimport { AlertCircle, Check, CheckCheck, Clock } from 'lucide-react'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport {\n isReactionEventMessage,\n normalizePathForCompare,\n normalizeReactionEmoji,\n pathWithinBase,\n toTimestamp,\n} from '@/lib/display'\nimport { planLookupKey } from '@/store/slices/plans'\nimport type { Agent, AttachmentRef, InboxThread, Message, Orchestrator, Plan } from '@/types'\nimport type { PendingAttachment } from './types'\n\nexport function formatBytes(size: number): string {\n if (size < 1024) return `${size} B`\n if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`\n return `${(size / 1024 / 1024).toFixed(1)} MB`\n}\n\nexport function resolveRelativePath(cwd: string, path: string): string {\n if (path.startsWith('/')) return path\n const parts = `${cwd.replace(/\\/+$/, '')}/${path}`.split('/')\n const stack: string[] = []\n for (const part of parts) {\n if (!part || part === '.') continue\n if (part === '..') stack.pop()\n else stack.push(part)\n }\n return '/' + stack.join('/')\n}\n\nexport function parentPath(path: string): string {\n const trimmed = path.replace(/\\/+$/, '')\n const index = trimmed.lastIndexOf('/')\n return index > 0 ? trimmed.slice(0, index) : '/'\n}\n\nexport function orchestratorForFilePreview(orchestrators: Orchestrator[], path: string): Orchestrator | undefined {\n return orchestrators\n .filter((orch) => orch.status === 'online' && pathWithinBase(path, orch.baseDir))\n .sort((a, b) => normalizePathForCompare(b.baseDir).length - normalizePathForCompare(a.baseDir).length)[0] ||\n orchestrators.find((orch) => orch.status === 'online')\n}\n\nexport function supportsSideFilePreview(): boolean {\n return typeof window !== 'undefined' && window.matchMedia('(min-width: 1280px)').matches\n}\n\nexport function hasActiveTextSelection(): boolean {\n return typeof window !== 'undefined' && Boolean(window.getSelection()?.toString())\n}\n\nexport function messageAttachments(msg: Message): AttachmentRef[] {\n const attachments = msg.payload?.attachments\n if (!Array.isArray(attachments)) return []\n return attachments.filter((item): item is AttachmentRef => Boolean(item && typeof item === 'object' && typeof (item as AttachmentRef).artifactId === 'string'))\n}\n\nexport const QUICK_REACTIONS = ['👍', '❤️', '✅', '👀'] as const\n\nexport function groupedReactions(msg: Message): Array<{ emoji: string; count: number; mine: boolean; actors: string[] }> {\n const groups = new Map<string, { emoji: string; count: number; mine: boolean; actors: string[] }>()\n for (const reaction of msg.reactions || []) {\n const emoji = normalizeReactionEmoji(reaction.emoji)\n const current = groups.get(emoji) ?? { emoji, count: 0, mine: false, actors: [] }\n current.count += 1\n current.mine = current.mine || reaction.actorId === HUMAN_AGENT_ID\n current.actors.push(reaction.actorId)\n groups.set(emoji, current)\n }\n return [...groups.values()].sort((a, b) => b.count - a.count || a.emoji.localeCompare(b.emoji))\n}\n\nexport function isReactionEvent(msg: Message): boolean {\n return isReactionEventMessage(msg)\n}\n\nexport function outboundReceipt(msg: Message, peer: string): { label: string; title: string; tone: 'muted' | 'ok' | 'warn' | 'danger'; icon: typeof Check } {\n const readBy = msg.readBy || []\n const expectedReader = msg.resolvedToAgent || peer || msg.to\n const readByExpected = expectedReader ? readBy.includes(expectedReader) : false\n const readBySomeone = readBy.some((id) => id !== HUMAN_AGENT_ID)\n if (readByExpected || readBySomeone) {\n const reader = readByExpected ? expectedReader : readBy.find((id) => id !== HUMAN_AGENT_ID)\n return { label: 'read', title: reader ? `Read by ${reader}` : 'Read by agent', tone: 'ok', icon: CheckCheck }\n }\n if (msg.deliveryStatus === 'delivered') {\n return { label: 'delivered', title: 'Delivered to agent inbox', tone: 'ok', icon: CheckCheck }\n }\n if (msg.deliveryStatus === 'queued') {\n return { label: 'queued', title: 'Queued until the target agent is available', tone: 'warn', icon: Clock }\n }\n if (msg.deliveryStatus === 'failed' || msg.deliveryStatus === 'dead') {\n const detail = msg.deliveryPoisonReason || msg.deliveryLastError || msg.deliveryStatus\n return { label: msg.deliveryStatus, title: detail, tone: 'danger', icon: AlertCircle }\n }\n return { label: 'sent', title: 'Accepted by Relay', tone: 'muted', icon: Check }\n}\n\nexport function messageActivityTimestamp(msg: Message): number {\n const messageAt = toTimestamp(msg.createdAt)\n return (msg.reactions || []).reduce((latest, reaction) => {\n return Math.max(latest, toTimestamp(reaction.updatedAt || reaction.createdAt))\n }, messageAt)\n}\n\nexport function threadActivityTimestamp(thread: InboxThread | undefined): number {\n if (!thread) return 0\n return thread.messages.reduce((latest, msg) => Math.max(latest, messageActivityTimestamp(msg)), 0)\n}\n\nexport function chatTimestamp(iso: string | number): string {\n const d = new Date(iso as string)\n return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n}\n\nexport function revokePendingAttachmentUrls(items: PendingAttachment[]): void {\n for (const item of items) if (item.previewUrl) URL.revokeObjectURL(item.previewUrl)\n}\n\ntype ChatPlanContext = { threadId: string | number; channel?: string } | { teamId: string; channel?: string }\n\nexport function resolveChatPlanContext(\n agent: Agent | null,\n messages: Message[],\n lastMessage: Message | null | undefined,\n plansByThread: Record<string, Plan | null>,\n): ChatPlanContext | null {\n if (agent?.teamId) return { teamId: agent.teamId }\n const ordered = [...messages]\n if (lastMessage && !ordered.some((message) => message.id === lastMessage.id)) ordered.push(lastMessage)\n const threaded = ordered.filter((message) => message.threadId).reverse()\n const knownPlanMessage = threaded.find((message) => Boolean(plansByThread[planLookupKey(message.threadId!, message.channel)]))\n const selected = knownPlanMessage ?? threaded[0]\n if (!selected?.threadId) return null\n return {\n threadId: selected.threadId,\n ...(selected.channel ? { channel: selected.channel } : {}),\n }\n}\n","\nimport { useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport type React from 'react'\nimport { AlertCircle, AlertTriangle, ArrowLeft, Brain, ChevronDown, ChevronRight, ChevronUp, Circle, CircleStop, GitFork, GitMerge, LogOut, Plus, Radio, Terminal } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { FormattedBody } from '@/components/shared/formatted-body'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { messageBody } from '@/lib/display'\nimport type { ChatHistoryImport, ChatHistoryImportEntry } from '@/types'\nimport { cn } from '@/lib/utils'\nimport { chatTimestamp } from './helpers'\nimport { dateKey, TIMELINE_STATUS_LABELS, type ActivityStep, type TimelineEntry } from './sessionTrace'\nimport type { Message } from '@/types'\nimport type { ChatStatusEvent as StatusEvent } from '@/store'\nimport { useRelayStore } from '@/store'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\n\nconst TIMELINE_STATUS_COLORS: Record<string, string> = {\n online: 'text-emerald-400',\n offline: 'text-zinc-400',\n shutdown: 'text-zinc-400',\n restarted: 'text-blue-400',\n 'compaction-requested': 'text-zinc-400',\n compacting: 'text-amber-400',\n compacted: 'text-amber-400',\n 'compaction-stalled': 'text-red-400',\n 'clear-requested': 'text-zinc-400',\n 'clearing-context': 'text-amber-400',\n 'context-cleared': 'text-amber-400',\n 'clear-stalled': 'text-red-400',\n 'shutting-down': 'text-zinc-400',\n}\n\nfunction dateSeparatorLabel(dateStr: string): string {\n const today = new Date()\n const [y, m, d] = dateStr.split('-').map(Number)\n const target = new Date(y!, m! - 1, d)\n const todayKey = dateKey(today.getTime())\n if (dateStr === todayKey) return 'Today'\n const yesterday = new Date(today)\n yesterday.setDate(yesterday.getDate() - 1)\n if (dateStr === dateKey(yesterday.getTime())) return 'Yesterday'\n return target.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric', year: target.getFullYear() !== today.getFullYear() ? 'numeric' : undefined })\n}\n\nexport function StatusMarker({ event }: { event: StatusEvent }) {\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <Circle className={cn('w-2 h-2 fill-current', TIMELINE_STATUS_COLORS[event.status] || 'text-zinc-400')} />\n <span>{TIMELINE_STATUS_LABELS[event.status] || event.status}</span>\n <span className=\"text-muted-foreground/60\">{chatTimestamp(event.timestamp)}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nconst BUS_SIGNAL_ICON: Record<string, LucideIcon> = {\n 'branch.landed': GitMerge,\n 'context.warning': AlertTriangle,\n}\n\nconst BUS_SIGNAL_LABEL: Record<string, string> = {\n 'branch.landed': 'branch landed',\n 'context.warning': 'context warning',\n}\n\nexport function NoticePill({ msg }: { msg: Message }) {\n const signalKind = typeof msg.payload?.kind === 'string' ? msg.payload.kind : ''\n const Icon: LucideIcon = BUS_SIGNAL_ICON[signalKind] ?? Radio\n const label = BUS_SIGNAL_LABEL[signalKind] ?? (signalKind || 'system signal')\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground\">\n <Icon className=\"h-3 w-3\" />\n <span>{label}</span>\n <span className=\"text-muted-foreground/60\">{chatTimestamp(msg.occurredAt ?? (msg.createdAt as string))}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nconst AGENT_ACTION_VERB: Record<string, Record<string, string>> = {\n 'spawned-child': { ready: 'spawned', exited: 'spawned · exited', failed: 'spawn failed' },\n 'spawned-by': { ready: 'spawned by', exited: 'spawned by', failed: 'spawned by' },\n}\n\nconst AGENT_ACTION_ICON: Record<string, LucideIcon> = {\n 'spawned-child-ready': Plus,\n 'spawned-child-exited': LogOut,\n 'spawned-child-failed': AlertCircle,\n 'spawned-by-ready': ArrowLeft,\n 'spawned-by-exited': ArrowLeft,\n 'spawned-by-failed': ArrowLeft,\n}\n\nconst AGENT_ACTION_COLOR: Record<string, string> = {\n 'spawned-child-ready': 'text-emerald-400',\n 'spawned-child-exited': 'text-zinc-400',\n 'spawned-child-failed': 'text-red-400',\n 'spawned-by-ready': 'text-blue-400',\n 'spawned-by-exited': 'text-blue-400',\n 'spawned-by-failed': 'text-blue-400',\n}\n\nexport function AgentActionEntry({ entry }: { entry: Extract<TimelineEntry, { type: 'agent-action' }> }) {\n const agentsById = useRelayStore((s) => s.agentsById)\n const openInboxThread = useRelayStore((s) => s.openInboxThread)\n const switchView = useRelayStore((s) => s.switchView)\n\n const agent = agentsById[entry.agentId] || null\n const label = agent?.label ?? entry.agentId\n\n const key = `${entry.direction}-${entry.status}`\n const verb = AGENT_ACTION_VERB[entry.direction]?.[entry.status] ?? entry.direction\n const Icon: LucideIcon = AGENT_ACTION_ICON[key] ?? Radio\n const color = AGENT_ACTION_COLOR[key] ?? 'text-muted-foreground'\n const isError = entry.status === 'failed'\n\n function navigate() {\n openInboxThread(entry.agentId)\n void switchView('chat')\n }\n\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className={cn('flex items-center gap-1.5 rounded-full border bg-muted/30 px-2.5 py-1 text-xs', isError ? 'border-red-500/30' : 'border-border')}>\n <Icon className={cn('h-3 w-3 shrink-0', color)} />\n <span className=\"text-muted-foreground\">{verb}</span>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 rounded hover:text-foreground text-muted-foreground transition-colors hover:bg-muted/50 px-1\"\n onClick={navigate}\n title={`Open chat for ${label}`}\n >\n <AgentTypeIcon agent={agent} className=\"w-4 h-4 text-[10px]\" />\n <span className=\"max-w-[160px] truncate\">{label}</span>\n </button>\n {entry.reason && isError && (\n <span className=\"text-red-400/80 max-w-[120px] truncate\" title={entry.reason}>— {entry.reason}</span>\n )}\n <span className=\"text-muted-foreground/60 shrink-0\">{chatTimestamp(entry.timestamp)}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function BusyIndicator({ blockedLabel, onInterrupt }: { blockedLabel?: string; onInterrupt?: () => void }) {\n if (blockedLabel) {\n return (\n <div className=\"flex justify-start mb-3\">\n <div className=\"flex items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-red-500/30 px-4 py-2.5\">\n <AlertCircle className=\"w-3.5 h-3.5 text-red-400\" />\n <span className=\"text-xs text-red-300\">blocked: {blockedLabel}</span>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex justify-start mb-3\">\n <div className=\"flex items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-foreground/10 px-4 py-2.5\">\n <div className=\"flex items-center gap-1\">\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_infinite]\" />\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]\" />\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]\" />\n </div>\n <span className=\"text-xs text-muted-foreground\">working</span>\n {onInterrupt && (\n <button\n onClick={onInterrupt}\n title=\"Interrupt\"\n className=\"ml-1 flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs text-muted-foreground hover:text-red-300 hover:bg-red-500/10 transition-colors\"\n >\n <CircleStop className=\"w-3.5 h-3.5\" />\n Stop\n </button>\n )}\n </div>\n </div>\n )\n}\n\nconst HIDE_TOOLS_KEY = 'agent-relay:chat-hide-tools'\nconst hideToolsStore = (() => {\n const listeners = new Set<() => void>()\n let value = (() => {\n try {\n return window.localStorage.getItem(HIDE_TOOLS_KEY) === 'on'\n } catch {\n return false\n }\n })()\n const emit = () => listeners.forEach((l) => l())\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', (e) => {\n if (e.key !== HIDE_TOOLS_KEY) return\n value = e.newValue === 'on'\n emit()\n })\n }\n return {\n subscribe(cb: () => void) {\n listeners.add(cb)\n return () => listeners.delete(cb)\n },\n get: () => value,\n toggle() {\n value = !value\n try {\n window.localStorage.setItem(HIDE_TOOLS_KEY, value ? 'on' : 'off')\n } catch {}\n emit()\n },\n }\n})()\n\nfunction useHideTools(): [boolean, () => void] {\n const hideTools = useSyncExternalStore(hideToolsStore.subscribe, hideToolsStore.get, () => false)\n return [hideTools, hideToolsStore.toggle]\n}\n\nexport function ActivityTrace({ steps, showReasoning }: { steps: ActivityStep[]; showReasoning: boolean }) {\n const [hideTools, toggleHideTools] = useHideTools()\n const visible = showReasoning ? steps : steps.filter((s) => s.kind !== 'reasoning')\n if (!visible.length) return null\n const toolCount = visible.filter((s) => s.kind === 'tool').length\n return (\n <div className=\"flex justify-start mb-2\">\n <div className=\"max-w-[85%] md:max-w-[75%] min-w-0 space-y-1.5\">\n {visible.map((step) => {\n if (step.kind === 'narration') {\n return (\n <FormattedBody\n key={step.id}\n text={step.text}\n className=\"text-sm leading-relaxed break-words text-foreground/90 min-w-0\"\n />\n )\n }\n if (step.kind === 'reasoning') {\n return (\n <div key={step.id} className=\"flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground/80\">\n <Brain className=\"w-3.5 h-3.5 mt-0.5 shrink-0 opacity-70\" />\n <FormattedBody text={step.text} className=\"italic break-words min-w-0\" />\n </div>\n )\n }\n if (hideTools) return null\n return (\n <div key={step.id} className=\"flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground\">\n <Terminal className=\"w-3 h-3 mt-0.5 shrink-0 opacity-70\" />\n <span className=\"min-w-0 break-words\">\n <span className=\"font-medium\">{step.label || 'tool'}</span>\n {step.text ? <span className=\"opacity-70\"> — {step.text}</span> : null}\n </span>\n </div>\n )\n })}\n {toolCount > 0 && (\n <button\n onClick={toggleHideTools}\n className=\"flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left\"\n >\n <ChevronRight className={cn('w-3 h-3 shrink-0 transition-transform', !hideTools && 'rotate-90')} />\n <span>{hideTools ? `show ${toolCount} tool step${toolCount === 1 ? '' : 's'}` : 'hide tool steps'}</span>\n </button>\n )}\n </div>\n </div>\n )\n}\n\nexport function DateSeparator({ date }: { date: string }) {\n return (\n <div className=\"flex items-center justify-center gap-3 py-3 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{dateSeparatorLabel(date)}</span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function CreatedMarker({ timestamp }: { timestamp: number }) {\n const d = new Date(timestamp)\n const label = d.toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs text-muted-foreground/60\">created {label}</span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function ImportedHistoryMarker({ history }: { history: ChatHistoryImport }) {\n const source = history.sourceAgentLabel || history.sourcePeerId\n const count = history.entries.length\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 rounded-full border border-dashed border-border bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground\">\n <GitFork className=\"h-3 w-3\" />\n <span>forked history from {source}</span>\n <span className=\"text-muted-foreground/60\">{count} {count === 1 ? 'entry' : 'entries'}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function ImportedMessageBubble({ entry, sourceLabel }: { entry: ChatHistoryImportEntry; sourceLabel?: string }) {\n const msg = entry.message\n const body = messageBody(msg)\n const isOriginalHuman = entry.originalFrom === HUMAN_AGENT_ID\n return (\n <div className={cn('flex mb-3 opacity-80', isOriginalHuman ? 'justify-end' : 'justify-start')}>\n <div className=\"max-w-[85%] md:max-w-[75%]\">\n <div\n className={cn(\n 'rounded-2xl border border-dashed px-3.5 py-2 text-sm select-text',\n isOriginalHuman\n ? 'border-primary/25 bg-primary/10 text-foreground rounded-br-sm'\n : 'border-border bg-card/70 rounded-bl-sm'\n )}\n >\n <div className={cn('mb-1 text-[10px] uppercase tracking-normal', isOriginalHuman ? 'text-primary/70' : 'text-muted-foreground')}>\n forked history · {isOriginalHuman ? 'you' : (sourceLabel || entry.originalFrom)}\n </div>\n <FormattedBody text={body} rawBody={msg.body} className=\"leading-relaxed\" />\n <p className={cn('mt-1 flex items-center justify-end gap-1.5 text-xs', isOriginalHuman ? 'text-primary/60' : 'text-muted-foreground')}>\n <span className=\"opacity-40\">#{entry.originalMessageId}</span>\n <span>{chatTimestamp(entry.originalCreatedAt)}</span>\n </p>\n </div>\n </div>\n </div>\n )\n}\n\nexport function StickyPromptBanner({ scrollRef, timeline }: { scrollRef: React.RefObject<HTMLElement | null>; timeline: TimelineEntry[] }) {\n const [hidden, setHidden] = useState(true)\n const [expandedByPrompt, setExpandedByPrompt] = useState<{ id: number; value: boolean } | null>(null)\n const [collapsedByPrompt, setCollapsedByPrompt] = useState<{ id: number; value: boolean } | null>(null)\n const [promptAbove, setPromptAbove] = useState(true)\n\n const lastOutbound = useMemo(() => {\n for (let i = timeline.length - 1; i >= 0; i--) {\n const entry = timeline[i]\n if (!entry || entry.type !== 'message') continue\n if (entry.msg.from === HUMAN_AGENT_ID) {\n return { id: entry.msg.id, body: messageBody(entry.msg) }\n }\n }\n return null\n }, [timeline])\n\n useEffect(() => {\n const scrollEl = scrollRef.current\n if (!lastOutbound || !scrollEl) { setHidden(true); return }\n const target = scrollEl.querySelector(`[data-msg-id=\"${lastOutbound.id}\"]`)\n if (!target) { setHidden(true); return }\n const observer = new IntersectionObserver(\n ([e]) => {\n if (!e) return\n setHidden(e.isIntersecting)\n if (!e.isIntersecting) {\n const rootTop = e.rootBounds?.top ?? scrollEl.getBoundingClientRect().top\n setPromptAbove(e.boundingClientRect.top < rootTop)\n }\n },\n { root: scrollEl, threshold: 0 }\n )\n observer.observe(target)\n const onScroll = () => {\n const r = target.getBoundingClientRect()\n const sr = scrollEl.getBoundingClientRect()\n setPromptAbove(r.top < sr.top)\n }\n scrollEl.addEventListener('scroll', onScroll, { passive: true })\n return () => { observer.disconnect(); scrollEl.removeEventListener('scroll', onScroll) }\n }, [lastOutbound?.id, scrollRef])\n\n if (hidden || !lastOutbound || !lastOutbound.body.trim()) return null\n\n const expanded = expandedByPrompt?.id === lastOutbound.id ? expandedByPrompt.value : false\n const collapsed = collapsedByPrompt?.id === lastOutbound.id ? collapsedByPrompt.value : false\n\n function scrollToPrompt() {\n const scrollEl = scrollRef.current\n if (!scrollEl || !lastOutbound) return\n const target = scrollEl.querySelector(`[data-msg-id=\"${lastOutbound.id}\"]`)\n target?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n }\n\n const arrow = promptAbove ? '↑' : '↓'\n\n if (collapsed) {\n return (\n <div className=\"sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 bg-background\">\n <button\n type=\"button\"\n className=\"w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary border-b border-primary/10 transition-colors\"\n onClick={() => setCollapsedByPrompt({ id: lastOutbound.id, value: false })}\n >\n <span>Your prompt</span>\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </div>\n )\n }\n\n return (\n <div className=\"sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 px-2 md:px-3 pt-2 pb-1.5 bg-background border-b border-primary/10\">\n <div className=\"flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5\">\n <span className=\"font-medium text-primary/70\">Your prompt</span>\n <button\n type=\"button\"\n className=\"ml-auto hover:text-foreground transition-colors\"\n onClick={scrollToPrompt}\n >\n {arrow} scroll to original\n </button>\n <button\n type=\"button\"\n className=\"hover:text-foreground transition-colors\"\n onClick={() => setCollapsedByPrompt({ id: lastOutbound.id, value: true })}\n title=\"Minimize\"\n >\n <ChevronUp className=\"w-3 h-3\" />\n </button>\n </div>\n <p\n className={cn('text-sm text-foreground leading-snug cursor-pointer', !expanded && 'line-clamp-2')}\n onClick={() => setExpandedByPrompt({ id: lastOutbound.id, value: !expanded })}\n >\n {lastOutbound.body}\n </p>\n </div>\n )\n}\n","\nimport { useSyncExternalStore } from 'react'\nimport { voiceTts } from '@/lib/voice'\n\nexport function useTtsPlayingKey(): string | null {\n return useSyncExternalStore(\n (cb) => voiceTts.subscribe(cb),\n () => voiceTts.getPlayingKey(),\n () => null,\n )\n}\n","\nimport { memo, useEffect, useRef, useState } from 'react'\nimport { AlertCircle, CircleStop, Download, FileText, Loader2, SmilePlus, Volume2, X, ZoomIn } from 'lucide-react'\nimport { voiceTts } from '@/lib/voice'\nimport { useRelayStore } from '@/store'\nimport { FormattedBody } from '@/components/shared/formatted-body'\nimport { Button } from '@/components/ui/button'\nimport { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'\nimport { apiBlob } from '@/lib/api'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { messageBody } from '@/lib/display'\nimport type { AttachmentRef, Message } from '@/types'\nimport { cn } from '@/lib/utils'\nimport type { ReferencedPathInfo } from './types'\nimport {\n chatTimestamp,\n groupedReactions,\n hasActiveTextSelection,\n isReactionEvent,\n messageAttachments,\n outboundReceipt,\n QUICK_REACTIONS,\n resolveRelativePath,\n} from './helpers'\nimport { useTtsPlayingKey } from './hooks/useTtsPlayingKey'\n\nfunction AttachmentPreview({ attachment, outbound }: { attachment: AttachmentRef; outbound: boolean }) {\n const isImage = attachment.kind === 'image'\n const title = attachment.title || attachment.artifactId\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [lightboxOpen, setLightboxOpen] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n let url: string | null = null\n\n async function load() {\n setLoading(true)\n setError(null)\n setObjectUrl(null)\n try {\n const blob = await apiBlob(`/artifacts/${encodeURIComponent(attachment.artifactId)}/content`)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch (e) {\n if (!cancelled) setError((e as Error).message)\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n\n void load()\n return () => {\n cancelled = true\n if (url) URL.revokeObjectURL(url)\n }\n }, [attachment.artifactId])\n\n function download() {\n if (!objectUrl) return\n const a = document.createElement('a')\n a.href = objectUrl\n a.download = title\n document.body.appendChild(a)\n a.click()\n a.remove()\n }\n\n if (isImage) {\n return (\n <>\n <button\n type=\"button\"\n onClick={() => objectUrl && setLightboxOpen(true)}\n disabled={!objectUrl}\n className=\"group/img mt-2 block overflow-hidden rounded-md border border-foreground/10 bg-black/10 text-left disabled:cursor-default relative\"\n >\n {objectUrl ? (\n <>\n <img src={objectUrl} alt={title} loading=\"lazy\" className=\"block max-h-72 max-w-full object-contain\" />\n <span className=\"absolute inset-0 flex items-center justify-center bg-black/0 group-hover/img:bg-black/30 transition-colors\">\n <ZoomIn className=\"w-6 h-6 text-white opacity-0 group-hover/img:opacity-100 transition-opacity drop-shadow-md\" />\n </span>\n </>\n ) : (\n <span className={cn('flex min-h-20 items-center gap-2 px-3 py-2 text-xs', error ? 'text-red-400' : 'text-muted-foreground')}>\n {loading ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <AlertCircle className=\"h-4 w-4\" />}\n {error || 'Loading image'}\n </span>\n )}\n </button>\n {objectUrl && (\n <Dialog open={lightboxOpen} onOpenChange={setLightboxOpen}>\n <DialogContent className=\"max-w-[95vw] max-h-[95vh] w-auto p-0 bg-black/95 border-zinc-800 gap-0 overflow-hidden\" showCloseButton={false}>\n <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n <div className=\"relative flex items-center justify-center min-h-[200px]\">\n <img src={objectUrl} alt={title} className=\"max-w-[93vw] max-h-[90vh] object-contain\" />\n <div className=\"absolute top-2 right-2 flex gap-1\">\n <button type=\"button\" onClick={download} className=\"rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors\" title=\"Download\">\n <Download className=\"w-4 h-4\" />\n </button>\n <button type=\"button\" onClick={() => setLightboxOpen(false)} className=\"rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors\" title=\"Close\">\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )}\n </>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={download}\n disabled={!objectUrl}\n className={cn(\n 'mt-2 flex w-full items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition-colors disabled:cursor-default disabled:opacity-70',\n outbound ? 'border-primary-foreground/20 bg-primary-foreground/10 hover:bg-primary-foreground/15' : 'border-border bg-background hover:bg-muted'\n )}\n >\n {loading ? <Loader2 className=\"h-4 w-4 shrink-0 animate-spin\" /> : error ? <AlertCircle className=\"h-4 w-4 shrink-0 text-red-400\" /> : <FileText className=\"h-4 w-4 shrink-0\" />}\n <span className={cn('min-w-0 flex-1 truncate', error && 'text-red-400')}>{error || title}</span>\n <Download className=\"h-3.5 w-3.5 shrink-0\" />\n </button>\n )\n}\n\nfunction AddReaction({ open, onToggle, onReact }: { open: boolean; onToggle: () => void; onReact: (emoji: string) => void }) {\n return (\n <div className=\"relative shrink-0\">\n <button\n type=\"button\"\n title=\"Add reaction\"\n onClick={onToggle}\n className={cn(\n 'inline-flex h-5 w-6 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground shadow-sm transition hover:bg-muted hover:text-foreground',\n open ? 'opacity-100' : 'opacity-0 pointer-events-none md:group-hover/msg:opacity-100 md:group-hover/msg:pointer-events-auto'\n )}\n >\n <SmilePlus className=\"h-3 w-3\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-30 mb-1 flex gap-0.5 rounded-full border border-border bg-popover p-0.5 shadow-md\">\n {QUICK_REACTIONS.map((emoji) => (\n <button\n key={emoji}\n type=\"button\"\n title={`React ${emoji}`}\n onClick={() => onReact(emoji)}\n className=\"inline-flex h-6 w-7 items-center justify-center rounded-full text-xs transition-colors hover:bg-muted\"\n >\n {emoji}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport const MessageBubble = memo(function MessageBubble({\n msg,\n peer,\n onOpenReferencedPath,\n onPreviewReferencedPath,\n onPreviewReferencedPathEnd,\n}: {\n msg: Message\n peer: string\n onOpenReferencedPath?: (path: string, line?: number, type?: ReferencedPathInfo['type']) => void\n onPreviewReferencedPath?: (path: string, line?: number, anchor?: DOMRect) => void\n onPreviewReferencedPathEnd?: () => void\n}) {\n const isPeerMessage = msg.from !== HUMAN_AGENT_ID && msg.to !== HUMAN_AGENT_ID\n // For agent↔agent messages, treat the selected agent (peer) as the local side:\n // messages it sent are outbound (right), messages it received are inbound (left).\n const isOutbound = isPeerMessage ? msg.from === peer : msg.from === HUMAN_AGENT_ID\n const peerLabel = isPeerMessage\n ? (isOutbound ? `→ ${msg.to}` : `from ${msg.from}`)\n : null\n const reactToMessage = useRelayStore((s) => s.reactToMessage)\n const voiceTtsEnabled = useRelayStore((s) => s.voiceTtsEnabled)\n const ttsPlayingKey = useTtsPlayingKey()\n const peerCwd = useRelayStore((s) => {\n const cwd = s.agentsById[peer]?.meta?.cwd\n return typeof cwd === 'string' ? cwd : ''\n })\n const [showQuickReact, setShowQuickReact] = useState(false)\n const bubbleRef = useRef<HTMLDivElement>(null)\n const pointerStartRef = useRef<{ x: number; y: number } | null>(null)\n const suppressBubbleClickRef = useRef(false)\n const body = messageBody(msg)\n const time = chatTimestamp(msg.occurredAt ?? (msg.createdAt as string))\n const attachments = messageAttachments(msg)\n const reactions = groupedReactions(msg)\n const receipt = isOutbound ? outboundReceipt(msg, peer) : null\n const ReceiptIcon = receipt?.icon\n // On-demand TTS: replay any agent response through the same Kokoro/browser\n // pipeline. Only when the speaker is on, and only for inbound non-reaction text.\n const isTtsPlaying = ttsPlayingKey === String(msg.id)\n const showPlayButton = voiceTtsEnabled && !isOutbound && !isPeerMessage && !isReactionEvent(msg) && body.trim().length > 0\n function togglePlay(e: React.MouseEvent) {\n e.stopPropagation()\n if (isTtsPlaying) voiceTts.bargeIn()\n else voiceTts.speak(msg.body, String(msg.id))\n }\n useEffect(() => {\n if (!showQuickReact) return\n function dismiss(e: MouseEvent | TouchEvent) {\n if (hasActiveTextSelection()) return\n if (bubbleRef.current && !bubbleRef.current.contains(e.target as Node)) setShowQuickReact(false)\n }\n document.addEventListener('mousedown', dismiss)\n document.addEventListener('touchstart', dismiss)\n return () => { document.removeEventListener('mousedown', dismiss); document.removeEventListener('touchstart', dismiss) }\n }, [showQuickReact])\n\n function resolvedReferencedPath(path: string): string {\n return resolveRelativePath(peerCwd || '/', path)\n }\n\n function openReferencedPath(path: string, line?: number) {\n const absolute = resolvedReferencedPath(path)\n onOpenReferencedPath?.(absolute, line)\n }\n\n function previewReferencedPath(path: string, line?: number, anchor?: DOMRect) {\n const absolute = resolvedReferencedPath(path)\n onPreviewReferencedPath?.(absolute, line, anchor)\n }\n\n function handleReact(emoji: string) {\n reactToMessage(msg, emoji)\n setShowQuickReact(false)\n }\n\n function handleBubblePointerDown(e: React.PointerEvent<HTMLDivElement>) {\n if (e.button !== 0) return\n pointerStartRef.current = { x: e.clientX, y: e.clientY }\n suppressBubbleClickRef.current = false\n }\n\n function handleBubblePointerMove(e: React.PointerEvent<HTMLDivElement>) {\n const start = pointerStartRef.current\n if (!start) return\n if (Math.abs(e.clientX - start.x) > 3 || Math.abs(e.clientY - start.y) > 3) {\n suppressBubbleClickRef.current = true\n }\n }\n\n function handleBubbleClick(e: React.MouseEvent<HTMLDivElement>) {\n if (isReactionEvent(msg) || isOutbound || isPeerMessage) return\n const target = e.target as HTMLElement\n if (target.closest('a,button,input,textarea,select,[role=\"button\"]')) return\n if (suppressBubbleClickRef.current || hasActiveTextSelection()) {\n suppressBubbleClickRef.current = false\n return\n }\n setShowQuickReact((v) => !v)\n }\n\n return (\n <div data-msg-id={msg.id} className={cn('group/msg flex mb-3', isOutbound ? 'justify-end' : 'justify-start')}>\n <div ref={bubbleRef} className=\"relative max-w-[85%] md:max-w-[75%]\">\n {showPlayButton && (\n <button\n type=\"button\"\n title={isTtsPlaying ? 'Stop playback' : 'Play aloud'}\n onClick={togglePlay}\n className={cn(\n 'absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition',\n isTtsPlaying\n ? 'border-primary/50 text-primary opacity-100'\n // Hide-until-hover only on hover-capable devices; touch screens (iPad)\n // have no hover, so keep the button visible there instead of opacity-0.\n : 'border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100'\n )}\n >\n {isTtsPlaying ? <CircleStop className=\"h-3.5 w-3.5 animate-pulse\" /> : <Volume2 className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n <div\n className={cn(\n 'rounded-2xl px-3.5 py-2 text-sm select-text',\n !isOutbound && !isReactionEvent(msg) && !isPeerMessage && 'cursor-pointer',\n isOutbound\n ? 'bg-primary text-primary-foreground rounded-br-sm'\n : 'bg-card ring-1 ring-foreground/10 rounded-bl-sm'\n )}\n onPointerDown={handleBubblePointerDown}\n onPointerMove={handleBubblePointerMove}\n onClick={handleBubbleClick}\n >\n {peerLabel && (\n <p className={cn('text-xs font-medium mb-1 opacity-60', isOutbound ? 'text-primary-foreground' : 'text-muted-foreground')}>\n {peerLabel}\n </p>\n )}\n {msg.subject && (\n <p className={cn('text-xs font-semibold mb-1', isOutbound ? 'text-primary-foreground/70' : 'text-muted-foreground')}>\n {msg.subject}\n </p>\n )}\n <FormattedBody\n text={body}\n rawBody={msg.body}\n className=\"leading-relaxed\"\n onOpenPath={openReferencedPath}\n onPreviewPath={previewReferencedPath}\n onPreviewPathEnd={onPreviewReferencedPathEnd}\n resolvePathTitle={(path, line) => `${resolvedReferencedPath(path)}${line ? `:${line}` : ''}`}\n />\n {attachments.map((attachment) => (\n <AttachmentPreview key={`${msg.id}-${attachment.artifactId}-${attachment.role || ''}`} attachment={attachment} outbound={isOutbound} />\n ))}\n <p className={cn('text-xs mt-1 flex items-center gap-1.5', isOutbound ? 'justify-end text-primary-foreground/60' : 'justify-end text-muted-foreground')}>\n <span className=\"opacity-40\">#{msg.id}</span>\n <span>{time}</span>\n {receipt && ReceiptIcon && (\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n receipt.tone === 'ok' && 'text-emerald-300',\n receipt.tone === 'warn' && 'text-amber-200',\n receipt.tone === 'danger' && 'text-red-200'\n )}\n title={receipt.title}\n >\n <ReceiptIcon className=\"h-3 w-3\" />\n {receipt.label}\n </span>\n )}\n </p>\n </div>\n {!isReactionEvent(msg) && reactions.length > 0 && (\n <div className={cn('flex flex-wrap items-center gap-0.5 -mt-1 relative z-10', isOutbound ? 'justify-end pr-2' : 'justify-start pl-2')}>\n {!isOutbound && <AddReaction open={showQuickReact} onToggle={() => setShowQuickReact((v) => !v)} onReact={handleReact} />}\n {reactions.map((reaction) => (\n <button\n key={reaction.emoji}\n type=\"button\"\n title={reaction.actors.join(', ')}\n onClick={() => handleReact(reaction.emoji)}\n className={cn(\n 'inline-flex h-5 items-center gap-0.5 rounded-full border px-1.5 text-[11px] shadow-sm transition-colors',\n reaction.mine\n ? 'border-primary/40 bg-primary/10 text-primary'\n : 'border-border bg-background hover:bg-muted text-foreground'\n )}\n >\n <span>{reaction.emoji}</span>\n {reaction.count > 1 && <span>{reaction.count}</span>}\n </button>\n ))}\n </div>\n )}\n {!isReactionEvent(msg) && !isOutbound && reactions.length === 0 && (\n <div className=\"absolute -bottom-2.5 left-1 z-20\">\n <AddReaction open={showQuickReact} onToggle={() => setShowQuickReact((v) => !v)} onReact={handleReact} />\n </div>\n )}\n </div>\n </div>\n )\n})\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"MessageBubble-BrEKymoV.js","names":[],"sources":["../../dashboard/src/components/chat/sessionTrace.ts","../../dashboard/src/components/chat/helpers.ts","../../dashboard/src/components/chat/TraceView.tsx","../../dashboard/src/components/chat/hooks/useTtsPlayingKey.ts","../../dashboard/src/components/chat/MessageBubble.tsx"],"sourcesContent":["\nimport { toTimestamp, responseBodyContainsBlock } from '@/lib/display'\nimport type { ChatStatusEvent as StatusEvent } from '@/store'\nimport type { ChatHistoryImport, ChatHistoryImportEntry, Message } from '@/types'\n\nexport interface ActivityStep { id: number; kind: 'narration' | 'reasoning' | 'tool'; label?: string; text: string; ts: number; turnId?: string }\n\nexport type TimelineEntry =\n | { type: 'message'; msg: Message }\n | { type: 'notice'; msg: Message }\n | { type: 'agent-action'; direction: 'spawned-child' | 'spawned-by'; agentId: string; provider?: string; status: 'ready' | 'exited' | 'failed'; reason?: string; timestamp: number }\n | { type: 'activity'; steps: ActivityStep[]; timestamp: number }\n | { type: 'import-boundary'; history: ChatHistoryImport; timestamp: number }\n | { type: 'imported-message'; history: ChatHistoryImport; entry: ChatHistoryImportEntry }\n | { type: 'status'; event: StatusEvent }\n | { type: 'date'; date: string; timestamp: number }\n | { type: 'created'; timestamp: number }\n\nexport const TIMELINE_STATUS_LABELS: Record<string, string> = {\n online: 'came online',\n offline: 'went offline',\n shutdown: 'shut down',\n restarted: 'restarted',\n 'compaction-requested': 'compaction requested',\n compacting: 'compaction started',\n compacted: 'context compacted',\n 'compaction-stalled': 'compaction stalled — may need manual intervention',\n 'clear-requested': 'clear requested',\n 'clearing-context': 'clear started',\n 'context-cleared': 'context cleared',\n 'clear-stalled': 'context clear stalled — may need manual intervention',\n 'shutting-down': 'shutting down',\n}\n\nconst TIMELINE_STATUSES = new Set(Object.keys(TIMELINE_STATUS_LABELS))\nconst STATUS_DEDUPE_WINDOW_MS = 3000\n\n// True event time of a message: occurredAt (set when a Runner backfilled a message it had\n// queued during an outage, #196) falling back to the server receive time. Used for chat\n// timeline ordering + display so a backfilled turn lands where it actually happened in the\n// conversation, not at the bottom. Display-only — DB poll cursors still use created_at.\nexport function messageEventTime(msg: Message): number {\n return new Date(msg.occurredAt ?? msg.createdAt).getTime()\n}\n\n// Narration/reasoning/tool session events are mirrored as `kind:\"session\"` messages\n// but must not become chat bubbles — they render inline in the turn's activity trace.\n// (`narration` = the agent's intermediate spoken text; `reasoning` = its thinking.)\nexport function sessionActivityStep(msg: Message): ActivityStep | null {\n if (msg.kind !== 'session') return null\n const s = msg.payload?.session as { type?: string; label?: string; turnId?: string } | undefined\n if (!s || (s.type !== 'narration' && s.type !== 'reasoning' && s.type !== 'tool')) return null\n return {\n id: msg.id,\n kind: s.type,\n label: typeof s.label === 'string' ? s.label : undefined,\n text: msg.body,\n ts: messageEventTime(msg),\n turnId: typeof s.turnId === 'string' ? s.turnId : undefined,\n }\n}\n\n// A `notice` session event (e.g. the runner's durable \"context compacted\" marker) renders\n// as a discreet timeline marker, reusing the StatusMarker path. Its `label` carries a\n// timeline-status key (e.g. \"compacted\"); we surface it as a StatusEvent so it dedupes with\n// the ephemeral live timeline-status decoration and survives a reload (the ephemeral one does\n// not). Returns null for non-notice messages or labels we don't render.\nexport function sessionNoticeStatusEvent(msg: Message): StatusEvent | null {\n if (msg.kind !== 'session') return null\n const s = msg.payload?.session as { type?: string; label?: string } | undefined\n if (s?.type !== 'notice') return null\n const status = typeof s.label === 'string' && s.label ? s.label : 'compacted'\n return { id: `notice-${msg.id}`, status, timestamp: messageEventTime(msg) }\n}\n\n// Bus-level system signals (kind:\"system\", from:\"system\") — e.g. branch.landed,\n// context.warning. Rendered as notice pills, NOT chat bubbles or session status markers.\nexport function isBusSignalMessage(msg: Message): boolean {\n return msg.kind === 'system' && msg.from === 'system'\n}\n\n// Spawn lifecycle signals routed to the parent: agent.ready / agent.exited / agent.spawn_failed.\n// These are richer than generic bus signals and get their own agent-action timeline entry.\nconst LIFECYCLE_KINDS = new Set(['agent.ready', 'agent.exited', 'agent.spawn_failed'])\n\nexport function dateKey(ts: number): string {\n const d = new Date(ts)\n return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`\n}\n\nexport function dedupeStatusEvents(statusEvents: StatusEvent[]): StatusEvent[] {\n const events = statusEvents\n .filter((event) => TIMELINE_STATUSES.has(event.status))\n .slice()\n .sort((a, b) => a.timestamp - b.timestamp)\n const result: StatusEvent[] = []\n for (const event of events) {\n if (result.some((existing) => existing.id === event.id)) continue\n if (result.some((existing) => existing.status === event.status && Math.abs(existing.timestamp - event.timestamp) <= STATUS_DEDUPE_WINDOW_MS)) continue\n result.push(event)\n }\n return result\n}\n\nexport function buildTimeline(\n messages: Message[],\n statusEvents: StatusEvent[],\n createdAt?: string | number | null,\n importedHistory: ChatHistoryImport[] = [],\n options?: { spawnedBy?: { agentId: string; provider?: string } },\n): TimelineEntry[] {\n const raw: Array<{ ts: number; entry: TimelineEntry }> = []\n\n for (const history of importedHistory) {\n const firstImportedAt = history.entries.reduce((min, entry) => Math.min(min, toTimestamp(entry.originalCreatedAt)), Number.POSITIVE_INFINITY)\n const boundaryAt = Number.isFinite(firstImportedAt) ? firstImportedAt - 1 : history.importedAt\n raw.push({ ts: boundaryAt, entry: { type: 'import-boundary', history, timestamp: boundaryAt } })\n for (const entry of history.entries) {\n raw.push({ ts: toTimestamp(entry.originalCreatedAt), entry: { type: 'imported-message', history, entry } })\n }\n }\n\n if (createdAt) {\n const ts = new Date(createdAt as string).getTime()\n if (Number.isFinite(ts)) raw.push({ ts, entry: { type: 'created', timestamp: ts } })\n }\n\n // Synthetic \"spawned by\" lead entry for child agents — derives from the agent's spawnedBy\n // metadata (no inbound system message exists for this direction). Placed at createdAt so it\n // sorts as the first real entry after the created/date markers.\n if (options?.spawnedBy) {\n const ts = createdAt ? new Date(createdAt as string).getTime() : 0\n const spawnedByTs = Number.isFinite(ts) ? ts : 0\n raw.push({ ts: spawnedByTs, entry: { type: 'agent-action', direction: 'spawned-by', agentId: options.spawnedBy.agentId, provider: options.spawnedBy.provider, status: 'ready', timestamp: spawnedByTs } })\n }\n\n // The closing response bubble (session type \"response\") repeats the turn's\n // narration. In `final` capture mode the body is just the final line; in `full`\n // mode it is every narration block joined with blank-line separators\n // (matching extractLastAssistantTurn).\n // Collect the response bodies per turn so a narration step is suppressed from the\n // trace whenever it appears as a blank-line-delimited block of the body — so no line\n // shows twice in either mode (final: only the matching final line; full: all of them).\n const responseBodiesByTurn = new Map<string, string[]>()\n for (const msg of messages) {\n if (msg.kind !== 'session') continue\n const s = msg.payload?.session as { type?: string; turnId?: string } | undefined\n if (s?.type === 'response' && typeof s.turnId === 'string') {\n const list = responseBodiesByTurn.get(s.turnId) ?? []\n list.push(msg.body.trim())\n responseBodiesByTurn.set(s.turnId, list)\n }\n }\n\n // Tracks in-progress agent-action entries by child id so ready→exited coalesces into one entry.\n type RawAgentAction = { ts: number; entry: Extract<TimelineEntry, { type: 'agent-action' }> }\n const agentActionByChild = new Map<string, RawAgentAction>()\n\n const noticeStatusEvents: StatusEvent[] = []\n for (const msg of messages) {\n if (isBusSignalMessage(msg)) {\n const kind = typeof msg.payload?.kind === 'string' ? msg.payload.kind : ''\n if (LIFECYCLE_KINDS.has(kind)) {\n const p = msg.payload as { kind: string; agentId?: string; provider?: string; reason?: string }\n const childId = typeof p.agentId === 'string' ? p.agentId : ''\n const ts = messageEventTime(msg)\n if (!childId) {\n // agent.spawn_failed without a child id (command-level failure): fall through to notice\n raw.push({ ts, entry: { type: 'notice', msg } })\n continue\n }\n const status: 'ready' | 'exited' | 'failed' = kind === 'agent.ready' ? 'ready' : kind === 'agent.exited' ? 'exited' : 'failed'\n const existing = agentActionByChild.get(childId)\n if (existing) {\n // Coalesce: evolve the same entry to the final status in place\n existing.entry.status = status\n if (typeof p.reason === 'string') existing.entry.reason = p.reason\n existing.entry.timestamp = ts\n existing.ts = ts\n } else {\n const item: RawAgentAction = {\n ts,\n entry: {\n type: 'agent-action',\n direction: 'spawned-child',\n agentId: childId,\n provider: typeof p.provider === 'string' ? p.provider : undefined,\n status,\n reason: typeof p.reason === 'string' ? p.reason : undefined,\n timestamp: ts,\n },\n }\n agentActionByChild.set(childId, item)\n raw.push(item)\n }\n continue\n }\n raw.push({ ts: messageEventTime(msg), entry: { type: 'notice', msg } })\n continue\n }\n const notice = sessionNoticeStatusEvent(msg)\n if (notice) {\n noticeStatusEvents.push(notice)\n continue\n }\n const step = sessionActivityStep(msg)\n if (step) {\n if (step.kind === 'narration' && step.turnId) {\n const bodies = responseBodiesByTurn.get(step.turnId)\n if (bodies?.some((body) => responseBodyContainsBlock(body, step.text))) continue\n }\n raw.push({ ts: step.ts, entry: { type: 'activity', steps: [step], timestamp: step.ts } })\n continue\n }\n raw.push({ ts: messageEventTime(msg), entry: { type: 'message', msg } })\n }\n for (const event of dedupeStatusEvents([...statusEvents, ...noticeStatusEvents])) {\n raw.push({ ts: event.timestamp, entry: { type: 'status', event } })\n }\n\n const typeOrder = { created: 0, date: 1, 'import-boundary': 2, 'imported-message': 3, message: 4, activity: 4, notice: 4, 'agent-action': 4, status: 5 } as const\n raw.sort((a, b) => {\n const dt = a.ts - b.ts\n if (Math.abs(dt) < 3000) return typeOrder[a.entry.type] - typeOrder[b.entry.type] || dt\n return dt\n })\n\n const entries: TimelineEntry[] = []\n let lastDate = ''\n for (const { ts, entry } of raw) {\n if (entry.type === 'created') {\n entries.push(entry)\n continue\n }\n const dk = dateKey(ts)\n if (dk !== lastDate) {\n entries.push({ type: 'date', date: dk, timestamp: ts })\n lastDate = dk\n }\n // Coalesce consecutive activity steps from the same turn into one trace.\n if (entry.type === 'activity') {\n const prev = entries[entries.length - 1]\n if (prev && prev.type === 'activity' && sameActivityTurn(prev, entry)) {\n prev.steps.push(...entry.steps)\n continue\n }\n }\n entries.push(entry)\n }\n\n return entries\n}\n\nexport function sameActivityTurn(a: Extract<TimelineEntry, { type: 'activity' }>, b: Extract<TimelineEntry, { type: 'activity' }>): boolean {\n const at = a.steps[a.steps.length - 1]?.turnId\n const bt = b.steps[0]?.turnId\n if (at && bt) return at === bt\n // No turn id on either side: group adjacent steps regardless.\n return !at && !bt\n}\n","\nimport { AlertCircle, Check, CheckCheck, Clock } from 'lucide-react'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport {\n isReactionEventMessage,\n normalizePathForCompare,\n normalizeReactionEmoji,\n pathWithinBase,\n toTimestamp,\n} from '@/lib/display'\nimport { planLookupKey } from '@/store/slices/plans'\nimport type { Agent, AttachmentRef, InboxThread, Message, Orchestrator, Plan } from '@/types'\nimport type { PendingAttachment } from './types'\n\nexport function formatBytes(size: number): string {\n if (size < 1024) return `${size} B`\n if (size < 1024 * 1024) return `${(size / 1024).toFixed(1)} KB`\n return `${(size / 1024 / 1024).toFixed(1)} MB`\n}\n\nexport function resolveRelativePath(cwd: string, path: string): string {\n if (path.startsWith('/')) return path\n const parts = `${cwd.replace(/\\/+$/, '')}/${path}`.split('/')\n const stack: string[] = []\n for (const part of parts) {\n if (!part || part === '.') continue\n if (part === '..') stack.pop()\n else stack.push(part)\n }\n return '/' + stack.join('/')\n}\n\nexport function parentPath(path: string): string {\n const trimmed = path.replace(/\\/+$/, '')\n const index = trimmed.lastIndexOf('/')\n return index > 0 ? trimmed.slice(0, index) : '/'\n}\n\nexport function orchestratorForFilePreview(orchestrators: Orchestrator[], path: string): Orchestrator | undefined {\n return orchestrators\n .filter((orch) => orch.status === 'online' && pathWithinBase(path, orch.baseDir))\n .sort((a, b) => normalizePathForCompare(b.baseDir).length - normalizePathForCompare(a.baseDir).length)[0] ||\n orchestrators.find((orch) => orch.status === 'online')\n}\n\nexport function supportsSideFilePreview(): boolean {\n return typeof window !== 'undefined' && window.matchMedia('(min-width: 1280px)').matches\n}\n\nexport function hasActiveTextSelection(): boolean {\n return typeof window !== 'undefined' && Boolean(window.getSelection()?.toString())\n}\n\nexport function messageAttachments(msg: Message): AttachmentRef[] {\n const attachments = msg.payload?.attachments\n if (!Array.isArray(attachments)) return []\n return attachments.filter((item): item is AttachmentRef => Boolean(item && typeof item === 'object' && typeof (item as AttachmentRef).artifactId === 'string'))\n}\n\nexport const QUICK_REACTIONS = ['👍', '❤️', '✅', '👀'] as const\n\nexport function groupedReactions(msg: Message): Array<{ emoji: string; count: number; mine: boolean; actors: string[] }> {\n const groups = new Map<string, { emoji: string; count: number; mine: boolean; actors: string[] }>()\n for (const reaction of msg.reactions || []) {\n const emoji = normalizeReactionEmoji(reaction.emoji)\n const current = groups.get(emoji) ?? { emoji, count: 0, mine: false, actors: [] }\n current.count += 1\n current.mine = current.mine || reaction.actorId === HUMAN_AGENT_ID\n current.actors.push(reaction.actorId)\n groups.set(emoji, current)\n }\n return [...groups.values()].sort((a, b) => b.count - a.count || a.emoji.localeCompare(b.emoji))\n}\n\nexport function isReactionEvent(msg: Message): boolean {\n return isReactionEventMessage(msg)\n}\n\nexport function outboundReceipt(msg: Message, peer: string): { label: string; title: string; tone: 'muted' | 'ok' | 'warn' | 'danger'; icon: typeof Check } {\n const readBy = msg.readBy || []\n const expectedReader = msg.resolvedToAgent || peer || msg.to\n const readByExpected = expectedReader ? readBy.includes(expectedReader) : false\n const readBySomeone = readBy.some((id) => id !== HUMAN_AGENT_ID)\n if (readByExpected || readBySomeone) {\n const reader = readByExpected ? expectedReader : readBy.find((id) => id !== HUMAN_AGENT_ID)\n return { label: 'read', title: reader ? `Read by ${reader}` : 'Read by agent', tone: 'ok', icon: CheckCheck }\n }\n if (msg.deliveryStatus === 'delivered') {\n return { label: 'delivered', title: 'Delivered to agent inbox', tone: 'ok', icon: CheckCheck }\n }\n if (msg.deliveryStatus === 'queued') {\n return { label: 'queued', title: 'Queued until the target agent is available', tone: 'warn', icon: Clock }\n }\n if (msg.deliveryStatus === 'failed' || msg.deliveryStatus === 'dead') {\n const detail = msg.deliveryPoisonReason || msg.deliveryLastError || msg.deliveryStatus\n return { label: msg.deliveryStatus, title: detail, tone: 'danger', icon: AlertCircle }\n }\n return { label: 'sent', title: 'Accepted by Relay', tone: 'muted', icon: Check }\n}\n\nexport function messageActivityTimestamp(msg: Message): number {\n const messageAt = toTimestamp(msg.createdAt)\n return (msg.reactions || []).reduce((latest, reaction) => {\n return Math.max(latest, toTimestamp(reaction.updatedAt || reaction.createdAt))\n }, messageAt)\n}\n\nexport function threadActivityTimestamp(thread: InboxThread | undefined): number {\n if (!thread) return 0\n return thread.messages.reduce((latest, msg) => Math.max(latest, messageActivityTimestamp(msg)), 0)\n}\n\nexport function chatTimestamp(iso: string | number): string {\n const d = new Date(iso as string)\n return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' })\n}\n\nexport function revokePendingAttachmentUrls(items: PendingAttachment[]): void {\n for (const item of items) if (item.previewUrl) URL.revokeObjectURL(item.previewUrl)\n}\n\ntype ChatPlanContext = { threadId: string | number; channel?: string } | { teamId: string; channel?: string }\n\nexport function resolveChatPlanContext(\n agent: Agent | null,\n messages: Message[],\n lastMessage: Message | null | undefined,\n plansByThread: Record<string, Plan | null>,\n): ChatPlanContext | null {\n if (agent?.teamId) return { teamId: agent.teamId }\n const ordered = [...messages]\n if (lastMessage && !ordered.some((message) => message.id === lastMessage.id)) ordered.push(lastMessage)\n const threaded = ordered.filter((message) => message.threadId).reverse()\n const knownPlanMessage = threaded.find((message) => Boolean(plansByThread[planLookupKey(message.threadId!, message.channel)]))\n const selected = knownPlanMessage ?? threaded[0]\n if (!selected?.threadId) return null\n return {\n threadId: selected.threadId,\n ...(selected.channel ? { channel: selected.channel } : {}),\n }\n}\n","\nimport { useEffect, useMemo, useState, useSyncExternalStore } from 'react'\nimport type React from 'react'\nimport { AlertCircle, AlertTriangle, ArrowLeft, Brain, ChevronDown, ChevronRight, ChevronUp, Circle, CircleStop, GitFork, GitMerge, LogOut, Plus, Radio, Terminal } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\nimport { FormattedBody } from '@/components/shared/formatted-body'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { messageBody } from '@/lib/display'\nimport type { ChatHistoryImport, ChatHistoryImportEntry } from '@/types'\nimport { cn } from '@/lib/utils'\nimport { chatTimestamp } from './helpers'\nimport { dateKey, TIMELINE_STATUS_LABELS, type ActivityStep, type TimelineEntry } from './sessionTrace'\nimport type { Message } from '@/types'\nimport type { ChatStatusEvent as StatusEvent } from '@/store'\nimport { useRelayStore } from '@/store'\nimport { AgentTypeIcon } from '@/components/shared/agent-type-icon'\n\nconst TIMELINE_STATUS_COLORS: Record<string, string> = {\n online: 'text-emerald-400',\n offline: 'text-zinc-400',\n shutdown: 'text-zinc-400',\n restarted: 'text-blue-400',\n 'compaction-requested': 'text-zinc-400',\n compacting: 'text-amber-400',\n compacted: 'text-amber-400',\n 'compaction-stalled': 'text-red-400',\n 'clear-requested': 'text-zinc-400',\n 'clearing-context': 'text-amber-400',\n 'context-cleared': 'text-amber-400',\n 'clear-stalled': 'text-red-400',\n 'shutting-down': 'text-zinc-400',\n}\n\nfunction dateSeparatorLabel(dateStr: string): string {\n const today = new Date()\n const [y, m, d] = dateStr.split('-').map(Number)\n const target = new Date(y!, m! - 1, d)\n const todayKey = dateKey(today.getTime())\n if (dateStr === todayKey) return 'Today'\n const yesterday = new Date(today)\n yesterday.setDate(yesterday.getDate() - 1)\n if (dateStr === dateKey(yesterday.getTime())) return 'Yesterday'\n return target.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric', year: target.getFullYear() !== today.getFullYear() ? 'numeric' : undefined })\n}\n\nexport function StatusMarker({ event }: { event: StatusEvent }) {\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <Circle className={cn('w-2 h-2 fill-current', TIMELINE_STATUS_COLORS[event.status] || 'text-zinc-400')} />\n <span>{TIMELINE_STATUS_LABELS[event.status] || event.status}</span>\n <span className=\"text-muted-foreground/60\">{chatTimestamp(event.timestamp)}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nconst BUS_SIGNAL_ICON: Record<string, LucideIcon> = {\n 'branch.landed': GitMerge,\n 'context.warning': AlertTriangle,\n}\n\nconst BUS_SIGNAL_LABEL: Record<string, string> = {\n 'branch.landed': 'branch landed',\n 'context.warning': 'context warning',\n}\n\nexport function NoticePill({ msg }: { msg: Message }) {\n const signalKind = typeof msg.payload?.kind === 'string' ? msg.payload.kind : ''\n const Icon: LucideIcon = BUS_SIGNAL_ICON[signalKind] ?? Radio\n const label = BUS_SIGNAL_LABEL[signalKind] ?? (signalKind || 'system signal')\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 rounded-full border border-border bg-muted/30 px-2.5 py-1 text-xs text-muted-foreground\">\n <Icon className=\"h-3 w-3\" />\n <span>{label}</span>\n <span className=\"text-muted-foreground/60\">{chatTimestamp(msg.occurredAt ?? (msg.createdAt as string))}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nconst AGENT_ACTION_VERB: Record<string, Record<string, string>> = {\n 'spawned-child': { ready: 'spawned', exited: 'spawned · exited', failed: 'spawn failed' },\n 'spawned-by': { ready: 'spawned by', exited: 'spawned by', failed: 'spawned by' },\n}\n\nconst AGENT_ACTION_ICON: Record<string, LucideIcon> = {\n 'spawned-child-ready': Plus,\n 'spawned-child-exited': LogOut,\n 'spawned-child-failed': AlertCircle,\n 'spawned-by-ready': ArrowLeft,\n 'spawned-by-exited': ArrowLeft,\n 'spawned-by-failed': ArrowLeft,\n}\n\nconst AGENT_ACTION_COLOR: Record<string, string> = {\n 'spawned-child-ready': 'text-emerald-400',\n 'spawned-child-exited': 'text-zinc-400',\n 'spawned-child-failed': 'text-red-400',\n 'spawned-by-ready': 'text-blue-400',\n 'spawned-by-exited': 'text-blue-400',\n 'spawned-by-failed': 'text-blue-400',\n}\n\nexport function AgentActionEntry({ entry }: { entry: Extract<TimelineEntry, { type: 'agent-action' }> }) {\n const agentsById = useRelayStore((s) => s.agentsById)\n const openInboxThread = useRelayStore((s) => s.openInboxThread)\n const switchView = useRelayStore((s) => s.switchView)\n\n const agent = agentsById[entry.agentId] || null\n const label = agent?.label ?? entry.agentId\n\n const key = `${entry.direction}-${entry.status}`\n const verb = AGENT_ACTION_VERB[entry.direction]?.[entry.status] ?? entry.direction\n const Icon: LucideIcon = AGENT_ACTION_ICON[key] ?? Radio\n const color = AGENT_ACTION_COLOR[key] ?? 'text-muted-foreground'\n const isError = entry.status === 'failed'\n\n function navigate() {\n openInboxThread(entry.agentId)\n void switchView('chat')\n }\n\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className={cn('flex items-center gap-1.5 rounded-full border bg-muted/30 px-2.5 py-1 text-xs', isError ? 'border-red-500/30' : 'border-border')}>\n <Icon className={cn('h-3 w-3 shrink-0', color)} />\n <span className=\"text-muted-foreground\">{verb}</span>\n <button\n type=\"button\"\n className=\"inline-flex items-center gap-1 rounded hover:text-foreground text-muted-foreground transition-colors hover:bg-muted/50 px-1\"\n onClick={navigate}\n title={`Open chat for ${label}`}\n >\n <AgentTypeIcon agent={agent} className=\"w-4 h-4 text-[10px]\" />\n <span className=\"max-w-[160px] truncate\">{label}</span>\n </button>\n {entry.reason && isError && (\n <span className=\"text-red-400/80 max-w-[120px] truncate\" title={entry.reason}>— {entry.reason}</span>\n )}\n <span className=\"text-muted-foreground/60 shrink-0\">{chatTimestamp(entry.timestamp)}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function BusyIndicator({ blockedLabel, onInterrupt }: { blockedLabel?: string; onInterrupt?: () => void }) {\n if (blockedLabel) {\n return (\n <div className=\"flex justify-start mb-3\">\n <div className=\"flex items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-red-500/30 px-4 py-2.5\">\n <AlertCircle className=\"w-3.5 h-3.5 text-red-400\" />\n <span className=\"text-xs text-red-300\">blocked: {blockedLabel}</span>\n </div>\n </div>\n )\n }\n\n return (\n <div className=\"flex justify-start mb-3\">\n <div className=\"flex items-center gap-2 rounded-2xl rounded-bl-sm bg-card ring-1 ring-foreground/10 px-4 py-2.5\">\n <div className=\"flex items-center gap-1\">\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_infinite]\" />\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.2s_infinite]\" />\n <span className=\"w-1.5 h-1.5 rounded-full bg-yellow-400 animate-[pulse_1.4s_ease-in-out_0.4s_infinite]\" />\n </div>\n <span className=\"text-xs text-muted-foreground\">working</span>\n {onInterrupt && (\n <button\n onClick={onInterrupt}\n title=\"Interrupt\"\n className=\"ml-1 flex items-center gap-1 rounded-full px-1.5 py-0.5 text-xs text-muted-foreground hover:text-red-300 hover:bg-red-500/10 transition-colors\"\n >\n <CircleStop className=\"w-3.5 h-3.5\" />\n Stop\n </button>\n )}\n </div>\n </div>\n )\n}\n\nconst HIDE_TOOLS_KEY = 'agent-relay:chat-hide-tools'\nconst hideToolsStore = (() => {\n const listeners = new Set<() => void>()\n let value = (() => {\n try {\n return window.localStorage.getItem(HIDE_TOOLS_KEY) === 'on'\n } catch {\n return false\n }\n })()\n const emit = () => listeners.forEach((l) => l())\n if (typeof window !== 'undefined') {\n window.addEventListener('storage', (e) => {\n if (e.key !== HIDE_TOOLS_KEY) return\n value = e.newValue === 'on'\n emit()\n })\n }\n return {\n subscribe(cb: () => void) {\n listeners.add(cb)\n return () => listeners.delete(cb)\n },\n get: () => value,\n toggle() {\n value = !value\n try {\n window.localStorage.setItem(HIDE_TOOLS_KEY, value ? 'on' : 'off')\n } catch {}\n emit()\n },\n }\n})()\n\nfunction useHideTools(): [boolean, () => void] {\n const hideTools = useSyncExternalStore(hideToolsStore.subscribe, hideToolsStore.get, () => false)\n return [hideTools, hideToolsStore.toggle]\n}\n\nexport function ActivityTrace({ steps, showReasoning }: { steps: ActivityStep[]; showReasoning: boolean }) {\n const [hideTools, toggleHideTools] = useHideTools()\n const visible = showReasoning ? steps : steps.filter((s) => s.kind !== 'reasoning')\n if (!visible.length) return null\n const toolCount = visible.filter((s) => s.kind === 'tool').length\n return (\n <div className=\"flex justify-start mb-2\">\n <div className=\"max-w-[85%] md:max-w-[75%] min-w-0 space-y-1.5\">\n {visible.map((step) => {\n if (step.kind === 'narration') {\n return (\n <FormattedBody\n key={step.id}\n text={step.text}\n className=\"text-sm leading-relaxed break-words text-foreground/90 min-w-0\"\n />\n )\n }\n if (step.kind === 'reasoning') {\n return (\n <div key={step.id} className=\"flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground/80\">\n <Brain className=\"w-3.5 h-3.5 mt-0.5 shrink-0 opacity-70\" />\n <FormattedBody text={step.text} className=\"italic break-words min-w-0\" />\n </div>\n )\n }\n if (hideTools) return null\n return (\n <div key={step.id} className=\"flex items-start gap-1.5 text-xs leading-relaxed text-muted-foreground\">\n <Terminal className=\"w-3 h-3 mt-0.5 shrink-0 opacity-70\" />\n <span className=\"min-w-0 break-words\">\n <span className=\"font-medium\">{step.label || 'tool'}</span>\n {step.text ? <span className=\"opacity-70\"> — {step.text}</span> : null}\n </span>\n </div>\n )\n })}\n {toolCount > 0 && (\n <button\n onClick={toggleHideTools}\n className=\"flex items-center gap-1 text-[11px] text-muted-foreground/50 hover:text-muted-foreground transition-colors text-left\"\n >\n <ChevronRight className={cn('w-3 h-3 shrink-0 transition-transform', !hideTools && 'rotate-90')} />\n <span>{hideTools ? `show ${toolCount} tool step${toolCount === 1 ? '' : 's'}` : 'hide tool steps'}</span>\n </button>\n )}\n </div>\n </div>\n )\n}\n\nexport function DateSeparator({ date }: { date: string }) {\n return (\n <div className=\"flex items-center justify-center gap-3 py-3 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs font-medium text-muted-foreground\">{dateSeparatorLabel(date)}</span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function CreatedMarker({ timestamp }: { timestamp: number }) {\n const d = new Date(timestamp)\n const label = d.toLocaleString(undefined, { month: 'short', day: 'numeric', year: 'numeric', hour: '2-digit', minute: '2-digit' })\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <span className=\"text-xs text-muted-foreground/60\">created {label}</span>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function ImportedHistoryMarker({ history }: { history: ChatHistoryImport }) {\n const source = history.sourceAgentLabel || history.sourcePeerId\n const count = history.entries.length\n return (\n <div className=\"flex items-center justify-center gap-2 py-2 my-1\">\n <div className=\"h-px flex-1 bg-border\" />\n <div className=\"flex items-center gap-1.5 rounded-full border border-dashed border-border bg-muted/30 px-2 py-1 text-[11px] text-muted-foreground\">\n <GitFork className=\"h-3 w-3\" />\n <span>forked history from {source}</span>\n <span className=\"text-muted-foreground/60\">{count} {count === 1 ? 'entry' : 'entries'}</span>\n </div>\n <div className=\"h-px flex-1 bg-border\" />\n </div>\n )\n}\n\nexport function ImportedMessageBubble({ entry, sourceLabel }: { entry: ChatHistoryImportEntry; sourceLabel?: string }) {\n const msg = entry.message\n const body = messageBody(msg)\n const isOriginalHuman = entry.originalFrom === HUMAN_AGENT_ID\n return (\n <div className={cn('flex mb-3 opacity-80', isOriginalHuman ? 'justify-end' : 'justify-start')}>\n <div className=\"max-w-[85%] md:max-w-[75%]\">\n <div\n className={cn(\n 'rounded-2xl border border-dashed px-3.5 py-2 text-sm select-text',\n isOriginalHuman\n ? 'border-primary/25 bg-primary/10 text-foreground rounded-br-sm'\n : 'border-border bg-card/70 rounded-bl-sm'\n )}\n >\n <div className={cn('mb-1 text-[10px] uppercase tracking-normal', isOriginalHuman ? 'text-primary/70' : 'text-muted-foreground')}>\n forked history · {isOriginalHuman ? 'you' : (sourceLabel || entry.originalFrom)}\n </div>\n <FormattedBody text={body} rawBody={msg.body} className=\"leading-relaxed\" />\n <p className={cn('mt-1 flex items-center justify-end gap-1.5 text-xs', isOriginalHuman ? 'text-primary/60' : 'text-muted-foreground')}>\n <span className=\"opacity-40\">#{entry.originalMessageId}</span>\n <span>{chatTimestamp(entry.originalCreatedAt)}</span>\n </p>\n </div>\n </div>\n </div>\n )\n}\n\nexport function StickyPromptBanner({ scrollRef, timeline }: { scrollRef: React.RefObject<HTMLElement | null>; timeline: TimelineEntry[] }) {\n const [hidden, setHidden] = useState(true)\n const [expandedByPrompt, setExpandedByPrompt] = useState<{ id: number; value: boolean } | null>(null)\n const [collapsedByPrompt, setCollapsedByPrompt] = useState<{ id: number; value: boolean } | null>(null)\n const [promptAbove, setPromptAbove] = useState(true)\n\n const lastOutbound = useMemo(() => {\n for (let i = timeline.length - 1; i >= 0; i--) {\n const entry = timeline[i]\n if (!entry || entry.type !== 'message') continue\n if (entry.msg.from === HUMAN_AGENT_ID) {\n return { id: entry.msg.id, body: messageBody(entry.msg) }\n }\n }\n return null\n }, [timeline])\n\n useEffect(() => {\n const scrollEl = scrollRef.current\n if (!lastOutbound || !scrollEl) { setHidden(true); return }\n const target = scrollEl.querySelector(`[data-msg-id=\"${lastOutbound.id}\"]`)\n if (!target) { setHidden(true); return }\n const observer = new IntersectionObserver(\n ([e]) => {\n if (!e) return\n setHidden(e.isIntersecting)\n if (!e.isIntersecting) {\n const rootTop = e.rootBounds?.top ?? scrollEl.getBoundingClientRect().top\n setPromptAbove(e.boundingClientRect.top < rootTop)\n }\n },\n { root: scrollEl, threshold: 0 }\n )\n observer.observe(target)\n const onScroll = () => {\n const r = target.getBoundingClientRect()\n const sr = scrollEl.getBoundingClientRect()\n setPromptAbove(r.top < sr.top)\n }\n scrollEl.addEventListener('scroll', onScroll, { passive: true })\n return () => { observer.disconnect(); scrollEl.removeEventListener('scroll', onScroll) }\n }, [lastOutbound?.id, scrollRef])\n\n if (hidden || !lastOutbound || !lastOutbound.body.trim()) return null\n\n const expanded = expandedByPrompt?.id === lastOutbound.id ? expandedByPrompt.value : false\n const collapsed = collapsedByPrompt?.id === lastOutbound.id ? collapsedByPrompt.value : false\n\n function scrollToPrompt() {\n const scrollEl = scrollRef.current\n if (!scrollEl || !lastOutbound) return\n const target = scrollEl.querySelector(`[data-msg-id=\"${lastOutbound.id}\"]`)\n target?.scrollIntoView({ behavior: 'smooth', block: 'center' })\n }\n\n const arrow = promptAbove ? '↑' : '↓'\n\n if (collapsed) {\n return (\n <div className=\"sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 bg-background\">\n <button\n type=\"button\"\n className=\"w-full flex items-center justify-center gap-1.5 py-1 text-[11px] text-primary/60 hover:text-primary border-b border-primary/10 transition-colors\"\n onClick={() => setCollapsedByPrompt({ id: lastOutbound.id, value: false })}\n >\n <span>Your prompt</span>\n <ChevronDown className=\"w-3 h-3\" />\n </button>\n </div>\n )\n }\n\n return (\n <div className=\"sticky top-0 z-10 -mx-3 md:-mx-4 -mt-3 md:-mt-4 px-2 md:px-3 pt-2 pb-1.5 bg-background border-b border-primary/10\">\n <div className=\"flex items-center gap-2 text-[11px] text-muted-foreground mb-0.5\">\n <span className=\"font-medium text-primary/70\">Your prompt</span>\n <button\n type=\"button\"\n className=\"ml-auto hover:text-foreground transition-colors\"\n onClick={scrollToPrompt}\n >\n {arrow} scroll to original\n </button>\n <button\n type=\"button\"\n className=\"hover:text-foreground transition-colors\"\n onClick={() => setCollapsedByPrompt({ id: lastOutbound.id, value: true })}\n title=\"Minimize\"\n >\n <ChevronUp className=\"w-3 h-3\" />\n </button>\n </div>\n <p\n className={cn('text-sm text-foreground leading-snug cursor-pointer', !expanded && 'line-clamp-2')}\n onClick={() => setExpandedByPrompt({ id: lastOutbound.id, value: !expanded })}\n >\n {lastOutbound.body}\n </p>\n </div>\n )\n}\n","\nimport { useSyncExternalStore } from 'react'\nimport { voiceTts } from '@/lib/voice'\n\nexport function useTtsPlayingKey(): string | null {\n return useSyncExternalStore(\n (cb) => voiceTts.subscribe(cb),\n () => voiceTts.getPlayingKey(),\n () => null,\n )\n}\n","\nimport { memo, useEffect, useRef, useState } from 'react'\nimport { AlertCircle, CircleStop, Download, FileText, Loader2, SmilePlus, Volume2, X, ZoomIn } from 'lucide-react'\nimport { voiceTts } from '@/lib/voice'\nimport { useRelayStore } from '@/store'\nimport { FormattedBody } from '@/components/shared/formatted-body'\nimport { Button } from '@/components/ui/button'\nimport { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog'\nimport { apiBlob } from '@/lib/api'\nimport { HUMAN_AGENT_ID } from '@/lib/constants'\nimport { messageBody } from '@/lib/display'\nimport type { AttachmentRef, Message } from '@/types'\nimport { cn } from '@/lib/utils'\nimport type { ReferencedPathInfo } from './types'\nimport {\n chatTimestamp,\n groupedReactions,\n hasActiveTextSelection,\n isReactionEvent,\n messageAttachments,\n outboundReceipt,\n QUICK_REACTIONS,\n resolveRelativePath,\n} from './helpers'\nimport { useTtsPlayingKey } from './hooks/useTtsPlayingKey'\n\nfunction AttachmentPreview({ attachment, outbound }: { attachment: AttachmentRef; outbound: boolean }) {\n const isImage = attachment.kind === 'image'\n const title = attachment.title || attachment.artifactId\n const [objectUrl, setObjectUrl] = useState<string | null>(null)\n const [error, setError] = useState<string | null>(null)\n const [loading, setLoading] = useState(false)\n const [lightboxOpen, setLightboxOpen] = useState(false)\n\n useEffect(() => {\n let cancelled = false\n let url: string | null = null\n\n async function load() {\n setLoading(true)\n setError(null)\n setObjectUrl(null)\n try {\n const blob = await apiBlob(`/artifacts/${encodeURIComponent(attachment.artifactId)}/content`)\n if (cancelled) return\n url = URL.createObjectURL(blob)\n setObjectUrl(url)\n } catch (e) {\n if (!cancelled) setError((e as Error).message)\n } finally {\n if (!cancelled) setLoading(false)\n }\n }\n\n void load()\n return () => {\n cancelled = true\n if (url) URL.revokeObjectURL(url)\n }\n }, [attachment.artifactId])\n\n function download() {\n if (!objectUrl) return\n const a = document.createElement('a')\n a.href = objectUrl\n a.download = title\n document.body.appendChild(a)\n a.click()\n a.remove()\n }\n\n if (isImage) {\n return (\n <>\n <button\n type=\"button\"\n onClick={() => objectUrl && setLightboxOpen(true)}\n disabled={!objectUrl}\n className=\"group/img mt-2 block overflow-hidden rounded-md border border-foreground/10 bg-black/10 text-left disabled:cursor-default relative\"\n >\n {objectUrl ? (\n <>\n <img src={objectUrl} alt={title} loading=\"lazy\" className=\"block max-h-72 max-w-full object-contain\" />\n <span className=\"absolute inset-0 flex items-center justify-center bg-black/0 group-hover/img:bg-black/30 transition-colors\">\n <ZoomIn className=\"w-6 h-6 text-white opacity-0 group-hover/img:opacity-100 transition-opacity drop-shadow-md\" />\n </span>\n </>\n ) : (\n <span className={cn('flex min-h-20 items-center gap-2 px-3 py-2 text-xs', error ? 'text-red-400' : 'text-muted-foreground')}>\n {loading ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <AlertCircle className=\"h-4 w-4\" />}\n {error || 'Loading image'}\n </span>\n )}\n </button>\n {objectUrl && (\n <Dialog open={lightboxOpen} onOpenChange={setLightboxOpen}>\n <DialogContent className=\"max-w-[95vw] max-h-[95vh] w-auto p-0 bg-black/95 border-zinc-800 gap-0 overflow-hidden\" showCloseButton={false}>\n <DialogTitle className=\"sr-only\">{title}</DialogTitle>\n <div className=\"relative flex items-center justify-center min-h-[200px]\">\n <img src={objectUrl} alt={title} className=\"max-w-[93vw] max-h-[90vh] object-contain\" />\n <div className=\"absolute top-2 right-2 flex gap-1\">\n <button type=\"button\" onClick={download} className=\"rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors\" title=\"Download\">\n <Download className=\"w-4 h-4\" />\n </button>\n <button type=\"button\" onClick={() => setLightboxOpen(false)} className=\"rounded-md bg-black/60 hover:bg-black/80 p-2 text-white transition-colors\" title=\"Close\">\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n </div>\n </DialogContent>\n </Dialog>\n )}\n </>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={download}\n disabled={!objectUrl}\n className={cn(\n 'mt-2 flex w-full items-center gap-2 rounded-md border px-2.5 py-2 text-left text-xs transition-colors disabled:cursor-default disabled:opacity-70',\n outbound ? 'border-primary-foreground/20 bg-primary-foreground/10 hover:bg-primary-foreground/15' : 'border-border bg-background hover:bg-muted'\n )}\n >\n {loading ? <Loader2 className=\"h-4 w-4 shrink-0 animate-spin\" /> : error ? <AlertCircle className=\"h-4 w-4 shrink-0 text-red-400\" /> : <FileText className=\"h-4 w-4 shrink-0\" />}\n <span className={cn('min-w-0 flex-1 truncate', error && 'text-red-400')}>{error || title}</span>\n <Download className=\"h-3.5 w-3.5 shrink-0\" />\n </button>\n )\n}\n\nfunction AddReaction({ open, onToggle, onReact }: { open: boolean; onToggle: () => void; onReact: (emoji: string) => void }) {\n return (\n <div className=\"relative shrink-0\">\n <button\n type=\"button\"\n title=\"Add reaction\"\n onClick={onToggle}\n className={cn(\n 'inline-flex h-5 w-6 items-center justify-center rounded-full border border-border bg-popover text-muted-foreground shadow-sm transition hover:bg-muted hover:text-foreground',\n open ? 'opacity-100' : 'opacity-0 pointer-events-none md:group-hover/msg:opacity-100 md:group-hover/msg:pointer-events-auto'\n )}\n >\n <SmilePlus className=\"h-3 w-3\" />\n </button>\n {open && (\n <div className=\"absolute bottom-full left-0 z-30 mb-1 flex gap-0.5 rounded-full border border-border bg-popover p-0.5 shadow-md\">\n {QUICK_REACTIONS.map((emoji) => (\n <button\n key={emoji}\n type=\"button\"\n title={`React ${emoji}`}\n onClick={() => onReact(emoji)}\n className=\"inline-flex h-6 w-7 items-center justify-center rounded-full text-xs transition-colors hover:bg-muted\"\n >\n {emoji}\n </button>\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport const MessageBubble = memo(function MessageBubble({\n msg,\n peer,\n onOpenReferencedPath,\n onPreviewReferencedPath,\n onPreviewReferencedPathEnd,\n}: {\n msg: Message\n peer: string\n onOpenReferencedPath?: (path: string, line?: number, type?: ReferencedPathInfo['type']) => void\n onPreviewReferencedPath?: (path: string, line?: number, anchor?: DOMRect) => void\n onPreviewReferencedPathEnd?: () => void\n}) {\n const isPeerMessage = msg.from !== HUMAN_AGENT_ID && msg.to !== HUMAN_AGENT_ID\n // For agent↔agent messages, treat the selected agent (peer) as the local side:\n // messages it sent are outbound (right), messages it received are inbound (left).\n const isOutbound = isPeerMessage ? msg.from === peer : msg.from === HUMAN_AGENT_ID\n const peerLabel = isPeerMessage\n ? (isOutbound ? `→ ${msg.to}` : `from ${msg.from}`)\n : null\n const reactToMessage = useRelayStore((s) => s.reactToMessage)\n const voiceTtsEnabled = useRelayStore((s) => s.voiceTtsEnabled)\n const ttsPlayingKey = useTtsPlayingKey()\n const peerCwd = useRelayStore((s) => {\n const cwd = s.agentsById[peer]?.meta?.cwd\n return typeof cwd === 'string' ? cwd : ''\n })\n const [showQuickReact, setShowQuickReact] = useState(false)\n const bubbleRef = useRef<HTMLDivElement>(null)\n const pointerStartRef = useRef<{ x: number; y: number } | null>(null)\n const suppressBubbleClickRef = useRef(false)\n const body = messageBody(msg)\n const time = chatTimestamp(msg.occurredAt ?? (msg.createdAt as string))\n const attachments = messageAttachments(msg)\n const reactions = groupedReactions(msg)\n const receipt = isOutbound ? outboundReceipt(msg, peer) : null\n const ReceiptIcon = receipt?.icon\n // On-demand TTS: replay any agent response through the same Kokoro/browser\n // pipeline. Only when the speaker is on, and only for inbound non-reaction text.\n const isTtsPlaying = ttsPlayingKey === String(msg.id)\n const showPlayButton = voiceTtsEnabled && !isOutbound && !isPeerMessage && !isReactionEvent(msg) && body.trim().length > 0\n function togglePlay(e: React.MouseEvent) {\n e.stopPropagation()\n if (isTtsPlaying) voiceTts.bargeIn()\n else voiceTts.speak(msg.body, String(msg.id))\n }\n useEffect(() => {\n if (!showQuickReact) return\n function dismiss(e: MouseEvent | TouchEvent) {\n if (hasActiveTextSelection()) return\n if (bubbleRef.current && !bubbleRef.current.contains(e.target as Node)) setShowQuickReact(false)\n }\n document.addEventListener('mousedown', dismiss)\n document.addEventListener('touchstart', dismiss)\n return () => { document.removeEventListener('mousedown', dismiss); document.removeEventListener('touchstart', dismiss) }\n }, [showQuickReact])\n\n function resolvedReferencedPath(path: string): string {\n return resolveRelativePath(peerCwd || '/', path)\n }\n\n function openReferencedPath(path: string, line?: number) {\n const absolute = resolvedReferencedPath(path)\n onOpenReferencedPath?.(absolute, line)\n }\n\n function previewReferencedPath(path: string, line?: number, anchor?: DOMRect) {\n const absolute = resolvedReferencedPath(path)\n onPreviewReferencedPath?.(absolute, line, anchor)\n }\n\n function handleReact(emoji: string) {\n reactToMessage(msg, emoji)\n setShowQuickReact(false)\n }\n\n function handleBubblePointerDown(e: React.PointerEvent<HTMLDivElement>) {\n if (e.button !== 0) return\n pointerStartRef.current = { x: e.clientX, y: e.clientY }\n suppressBubbleClickRef.current = false\n }\n\n function handleBubblePointerMove(e: React.PointerEvent<HTMLDivElement>) {\n const start = pointerStartRef.current\n if (!start) return\n if (Math.abs(e.clientX - start.x) > 3 || Math.abs(e.clientY - start.y) > 3) {\n suppressBubbleClickRef.current = true\n }\n }\n\n function handleBubbleClick(e: React.MouseEvent<HTMLDivElement>) {\n if (isReactionEvent(msg) || isOutbound || isPeerMessage) return\n const target = e.target as HTMLElement\n if (target.closest('a,button,input,textarea,select,[role=\"button\"]')) return\n if (suppressBubbleClickRef.current || hasActiveTextSelection()) {\n suppressBubbleClickRef.current = false\n return\n }\n setShowQuickReact((v) => !v)\n }\n\n return (\n <div data-msg-id={msg.id} className={cn('group/msg flex mb-3', isOutbound ? 'justify-end' : 'justify-start')}>\n <div ref={bubbleRef} className=\"relative max-w-[85%] md:max-w-[75%]\">\n {showPlayButton && (\n <button\n type=\"button\"\n title={isTtsPlaying ? 'Stop playback' : 'Play aloud'}\n onClick={togglePlay}\n className={cn(\n 'absolute -top-2 -right-2 z-20 inline-flex h-6 w-6 items-center justify-center rounded-full border bg-popover shadow-sm transition',\n isTtsPlaying\n ? 'border-primary/50 text-primary opacity-100'\n // Hide-until-hover only on hover-capable devices; touch screens (iPad)\n // have no hover, so keep the button visible there instead of opacity-0.\n : 'border-border text-muted-foreground opacity-70 hover:bg-muted hover:text-foreground hover:opacity-100 [@media(hover:hover)]:md:opacity-0 [@media(hover:hover)]:md:group-hover/msg:opacity-100'\n )}\n >\n {isTtsPlaying ? <CircleStop className=\"h-3.5 w-3.5 animate-pulse\" /> : <Volume2 className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n <div\n className={cn(\n 'rounded-2xl px-3.5 py-2 text-sm select-text',\n !isOutbound && !isReactionEvent(msg) && !isPeerMessage && 'cursor-pointer',\n isOutbound\n ? 'bg-primary text-primary-foreground rounded-br-sm'\n : 'bg-card ring-1 ring-foreground/10 rounded-bl-sm'\n )}\n onPointerDown={handleBubblePointerDown}\n onPointerMove={handleBubblePointerMove}\n onClick={handleBubbleClick}\n >\n {peerLabel && (\n <p className={cn('text-xs font-medium mb-1 opacity-60', isOutbound ? 'text-primary-foreground' : 'text-muted-foreground')}>\n {peerLabel}\n </p>\n )}\n {msg.subject && (\n <p className={cn('text-xs font-semibold mb-1', isOutbound ? 'text-primary-foreground/70' : 'text-muted-foreground')}>\n {msg.subject}\n </p>\n )}\n <FormattedBody\n text={body}\n rawBody={msg.body}\n className=\"leading-relaxed\"\n onOpenPath={openReferencedPath}\n onPreviewPath={previewReferencedPath}\n onPreviewPathEnd={onPreviewReferencedPathEnd}\n resolvePathTitle={(path, line) => `${resolvedReferencedPath(path)}${line ? `:${line}` : ''}`}\n />\n {attachments.map((attachment) => (\n <AttachmentPreview key={`${msg.id}-${attachment.artifactId}-${attachment.role || ''}`} attachment={attachment} outbound={isOutbound} />\n ))}\n <p className={cn('text-xs mt-1 flex items-center gap-1.5', isOutbound ? 'justify-end text-primary-foreground/60' : 'justify-end text-muted-foreground')}>\n <span className=\"opacity-40\">#{msg.id}</span>\n <span>{time}</span>\n {receipt && ReceiptIcon && (\n <span\n className={cn(\n 'inline-flex items-center gap-1',\n receipt.tone === 'ok' && 'text-emerald-300',\n receipt.tone === 'warn' && 'text-amber-200',\n receipt.tone === 'danger' && 'text-red-200'\n )}\n title={receipt.title}\n >\n <ReceiptIcon className=\"h-3 w-3\" />\n {receipt.label}\n </span>\n )}\n </p>\n </div>\n {!isReactionEvent(msg) && reactions.length > 0 && (\n <div className={cn('flex flex-wrap items-center gap-0.5 -mt-1 relative z-10', isOutbound ? 'justify-end pr-2' : 'justify-start pl-2')}>\n {!isOutbound && <AddReaction open={showQuickReact} onToggle={() => setShowQuickReact((v) => !v)} onReact={handleReact} />}\n {reactions.map((reaction) => (\n <button\n key={reaction.emoji}\n type=\"button\"\n title={reaction.actors.join(', ')}\n onClick={() => handleReact(reaction.emoji)}\n className={cn(\n 'inline-flex h-5 items-center gap-0.5 rounded-full border px-1.5 text-[11px] shadow-sm transition-colors',\n reaction.mine\n ? 'border-primary/40 bg-primary/10 text-primary'\n : 'border-border bg-background hover:bg-muted text-foreground'\n )}\n >\n <span>{reaction.emoji}</span>\n {reaction.count > 1 && <span>{reaction.count}</span>}\n </button>\n ))}\n </div>\n )}\n {!isReactionEvent(msg) && !isOutbound && reactions.length === 0 && (\n <div className=\"absolute -bottom-2.5 left-1 z-20\">\n <AddReaction open={showQuickReact} onToggle={() => setShowQuickReact((v) => !v)} onReact={handleReact} />\n </div>\n )}\n </div>\n </div>\n )\n})\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{
|
|
2
|
-
//# sourceMappingURL=PlanGraphPanel-
|
|
1
|
+
import{r as e}from"./chunk-CilyBKbf.js";import{Fn as t,Nn as n,an as r,cn as i,qn as a,wt as o}from"./lucide-react-H_-8G79A.js";import{n as s,r as c,t as l}from"./store-DHO_a8BD.js";var u=e(a(),1),d=`(max-width: 767px)`;function f(e){if(typeof window>`u`)return()=>{};let t=window.matchMedia(d);return t.addEventListener(`change`,e),()=>t.removeEventListener(`change`,e)}function p(){return(0,u.useSyncExternalStore)(f,()=>typeof window<`u`?window.matchMedia(d).matches:!1,()=>!1)}var m=t(),h=196,g=68,_=52,v=16,y=14,b=[`landed`,`in-progress`,`pending`,`blocked`],x={landed:{label:`landed`,dot:`bg-emerald-400`,accent:`border-l-emerald-400`,text:`text-emerald-300`,wire:`#34d399`},"in-progress":{label:`in progress`,dot:`bg-sky-400`,accent:`border-l-sky-400`,text:`text-sky-300`,wire:`#38bdf8`},pending:{label:`pending`,dot:`bg-amber-400`,accent:`border-l-amber-400`,text:`text-amber-300`,wire:`#fbbf24`},blocked:{label:`blocked`,dot:`bg-red-400`,accent:`border-l-red-400`,text:`text-red-300`,wire:`#f87171`}};function S(e){return e?e.label||(e.policyName?`policy:${e.policyName}`:e.agentId||``):``}function C(e){let t=new Map(e.nodes.map(e=>[e.id,e])),n=new Map(e.nodes.map((e,t)=>[e.id,t])),r=new Map,i=new Map(e.nodes.map(e=>[e.id,0]));for(let n of e.edges)!t.has(n.from)||!t.has(n.to)||(r.set(n.from,[...r.get(n.from)??[],n.to]),i.set(n.to,(i.get(n.to)??0)+1));let a=new Map(e.nodes.map(e=>[e.id,0])),o=e.nodes.filter(e=>(i.get(e.id)??0)===0).map(e=>e.id),s=new Set;for(;o.length;){let e=o.shift();s.add(e);let t=a.get(e)??0;for(let n of r.get(e)??[])a.set(n,Math.max(a.get(n)??0,t+1)),i.set(n,(i.get(n)??0)-1),(i.get(n)??0)===0&&o.push(n)}let c=Math.max(0,...Array.from(a.values()))+1;for(let t of e.nodes)s.has(t.id)||a.set(t.id,c++);let l=new Map;for(let t of e.nodes){let e=a.get(t.id)??0;l.set(e,[...l.get(e)??[],t])}let u=[...l.entries()].sort(([e],[t])=>e-t),d=Math.max(1,...u.map(([,e])=>e.length)),f=g+v,p=[];u.forEach(([e,t],r)=>{let i=[...t].sort((e,t)=>(e.lane||``).localeCompare(t.lane||``)||(n.get(e.id)??0)-(n.get(t.id)??0)),a=(d-i.length)*f/2;i.forEach((t,n)=>{p.push({node:t,layer:e,x:y+r*(h+_),y:y+a+n*f})})});let m=u.length;return{nodes:p,width:Math.max(320,y*2+m*h+Math.max(0,m-1)*_),height:Math.max(g+y*2,y*2+d*g+Math.max(0,d-1)*v)}}function w(e,t){let n=e.x+h,r=e.y+g/2,i=t.x,a=t.y+g/2,o=n+Math.max(20,(i-n)/2);return`M ${n} ${r} C ${o} ${r}, ${o} ${a}, ${i} ${a}`}function T({threadId:e,teamId:t,channel:a}){let d=t?c(t,a):e?s(e,a):``,f=l(e=>t?e.plansByTeam[d]:d?e.plansByThread[d]:null),_=l(e=>e.fetchPlan),v=l(e=>e.planCollapsed),y=l(e=>e.setPlanCollapsed),T=p(),E=v??T;(0,u.useEffect)(()=>{if(t){_({teamId:t,channel:a});return}e&&_({threadId:e,channel:a})},[a,_,t,e]);let D=(0,u.useMemo)(()=>f?C(f):null,[f]),O=(0,u.useMemo)(()=>new Map(D?.nodes.map(e=>[e.node.id,e])??[]),[D]),k=(0,u.useMemo)(()=>{if(!f)return[];let e=new Map;for(let t of f.nodes)e.set(t.status,(e.get(t.status)??0)+1);return b.filter(t=>e.has(t)).map(t=>({status:t,count:e.get(t)??0}))},[f]);if(!f||!D)return null;let A=f.nodes.length,j=k.find(e=>e.status===`landed`)?.count??0,M=`${A} node${A===1?``:`s`}${j>0?` · ${j} landed`:``}`;return(0,m.jsxs)(`section`,{className:`shrink-0 border-b border-border bg-card/45 px-3 py-2`,children:[(0,m.jsxs)(`button`,{type:`button`,onClick:()=>y(!E),"aria-expanded":!E,"aria-label":E?`Expand plan graph`:`Collapse plan graph`,className:n(`flex w-full min-w-0 items-center gap-2 text-left`,!E&&`mb-2`),children:[(0,m.jsx)(`span`,{className:`flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted/60`,children:(0,m.jsx)(o,{className:`h-3 w-3 text-muted-foreground`})}),(0,m.jsxs)(`div`,{className:`min-w-0 flex-1`,children:[(0,m.jsx)(`div`,{className:`truncate text-[13px] font-semibold leading-tight text-foreground`,children:f.title||`Plan`}),(0,m.jsx)(`div`,{className:`truncate text-[11px] leading-tight text-muted-foreground`,children:E?M:f.objective||M})]}),k.length>0&&(0,m.jsx)(`div`,{className:`hidden shrink-0 items-center gap-2.5 sm:flex`,children:k.map(({status:e,count:t})=>(0,m.jsxs)(`span`,{className:`flex items-center gap-1 text-[10px] text-muted-foreground`,children:[(0,m.jsx)(`span`,{className:n(`h-1.5 w-1.5 rounded-full`,x[e].dot)}),(0,m.jsx)(`span`,{className:n(`font-medium tabular-nums`,x[e].text),children:t}),(0,m.jsx)(`span`,{children:x[e].label})]},e))}),(0,m.jsxs)(`span`,{className:`shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground`,children:[`v`,f.version]}),(0,m.jsx)(`span`,{className:`flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted/60 hover:text-foreground`,children:E?(0,m.jsx)(i,{className:`h-3.5 w-3.5`}):(0,m.jsx)(r,{className:`h-3.5 w-3.5`})})]}),E?null:f.nodes.length===0?(0,m.jsx)(`div`,{className:`rounded-md border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground`,children:`No nodes yet — the plan graph fills in as the team is composed.`}):(0,m.jsx)(`div`,{className:`max-h-[min(46vh,360px)] overflow-auto`,children:(0,m.jsxs)(`div`,{className:`relative`,style:{width:D.width,height:D.height},children:[(0,m.jsxs)(`svg`,{className:`pointer-events-none absolute inset-0`,width:D.width,height:D.height,"aria-hidden":`true`,children:[(0,m.jsx)(`defs`,{children:b.map(e=>(0,m.jsx)(`marker`,{id:`plan-arrow-${e}`,viewBox:`0 0 8 8`,refX:`6.5`,refY:`4`,markerWidth:`6`,markerHeight:`6`,orient:`auto-start-reverse`,children:(0,m.jsx)(`path`,{d:`M0 0 L8 4 L0 8 z`,fill:x[e].wire})},e))}),f.edges.map(e=>{let t=O.get(e.from),n=O.get(e.to);if(!t||!n)return null;let r=x[t.node.status];return(0,m.jsx)(`path`,{d:w(t,n),stroke:r.wire,strokeOpacity:.7,strokeWidth:2,fill:`none`,strokeLinecap:`round`,markerEnd:`url(#plan-arrow-${t.node.status})`},`${e.from}->${e.to}`)})]}),D.nodes.map(({node:e,x:t,y:r})=>{let i=x[e.status],a=S(e.ownerRef);return(0,m.jsxs)(`article`,{className:n(`group absolute flex flex-col justify-between rounded-md border border-l-[3px] bg-background/95 p-2`,`shadow-sm transition-all hover:-translate-y-px hover:border-border hover:shadow-md hover:bg-background`,i.accent),style:{left:t,top:r,width:h,height:g},title:e.scopeBlurb||e.label,children:[(0,m.jsx)(`div`,{className:`truncate text-xs font-semibold leading-tight text-foreground`,children:e.label}),(e.scopeBlurb||e.id)&&(0,m.jsx)(`div`,{className:`truncate text-[10px] leading-tight text-muted-foreground`,children:e.scopeBlurb||e.id}),(0,m.jsxs)(`div`,{className:`flex min-w-0 items-center justify-between gap-1.5`,children:[(0,m.jsxs)(`div`,{className:`flex shrink-0 items-center gap-1.5`,children:[(0,m.jsx)(`span`,{className:n(`h-1.5 w-1.5 shrink-0 rounded-full`,i.dot)}),(0,m.jsx)(`span`,{className:n(`text-[10px] font-medium`,i.text),children:i.label})]}),(0,m.jsxs)(`div`,{className:`flex min-w-0 items-center gap-1.5`,children:[a&&(0,m.jsx)(`span`,{className:`truncate text-[10px] text-muted-foreground`,children:a}),e.lane&&(0,m.jsx)(`span`,{className:`shrink-0 rounded-sm bg-muted/70 px-1 py-px text-[9px] font-medium uppercase tracking-wide text-muted-foreground`,children:e.lane})]})]})]},e.id)})]})})]})}export{T as t};
|
|
2
|
+
//# sourceMappingURL=PlanGraphPanel-CXix1C-H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlanGraphPanel-BFVTJa0q.js","names":[],"sources":["../../dashboard/src/components/chat/hooks/useIsMobile.ts","../../dashboard/src/components/chat/PlanGraphPanel.tsx"],"sourcesContent":["import { useSyncExternalStore } from 'react'\n\n// Reactive narrow-viewport flag. Subscribes to the same `max-width: 767px`\n// breakpoint the chat single-pane layout uses, via useSyncExternalStore — an\n// external-store subscription, not effect-driven derived state — so it stays\n// correct across resize/rotate without a useEffect.\nconst QUERY = '(max-width: 767px)'\n\nfunction subscribe(cb: () => void): () => void {\n if (typeof window === 'undefined') return () => {}\n const mql = window.matchMedia(QUERY)\n mql.addEventListener('change', cb)\n return () => mql.removeEventListener('change', cb)\n}\n\nexport function useIsMobile(): boolean {\n return useSyncExternalStore(\n subscribe,\n () => (typeof window !== 'undefined' ? window.matchMedia(QUERY).matches : false),\n () => false,\n )\n}\n","import { useEffect, useMemo } from 'react'\nimport { ChevronDown, ChevronUp, GitBranch } from 'lucide-react'\nimport { useRelayStore } from '@/store'\nimport type { Plan, PlanNode, PlanOwnerRef, PlanStatus } from '@/types'\nimport { cn } from '@/lib/utils'\nimport { planLookupKey, planTeamLookupKey } from '@/store/slices/plans'\nimport { useIsMobile } from './hooks/useIsMobile'\n\n// Left-to-right DAG: dependency depth flows along x (sequential gates), nodes\n// that share a depth stack along y (parallel lanes). This keeps the panel SHORT\n// — it's an operator overlay above the chat, not a full view — and lets the\n// gate-vs-lane structure read at a glance. Height is capped + scrollable below.\nconst NODE_W = 196\nconst NODE_H = 68\nconst COL_GAP = 52\nconst ROW_GAP = 16\nconst PAD = 14\n\nconst STATUS_ORDER: PlanStatus[] = ['landed', 'in-progress', 'pending', 'blocked']\n\nconst STATUS_STYLE: Record<\n PlanStatus,\n { label: string; dot: string; accent: string; text: string; wire: string }\n> = {\n landed: { label: 'landed', dot: 'bg-emerald-400', accent: 'border-l-emerald-400', text: 'text-emerald-300', wire: '#34d399' },\n 'in-progress': { label: 'in progress', dot: 'bg-sky-400', accent: 'border-l-sky-400', text: 'text-sky-300', wire: '#38bdf8' },\n pending: { label: 'pending', dot: 'bg-amber-400', accent: 'border-l-amber-400', text: 'text-amber-300', wire: '#fbbf24' },\n blocked: { label: 'blocked', dot: 'bg-red-400', accent: 'border-l-red-400', text: 'text-red-300', wire: '#f87171' },\n}\n\ninterface LayoutNode {\n node: PlanNode\n x: number\n y: number\n layer: number\n}\n\nfunction ownerLabel(owner?: PlanOwnerRef): string {\n if (!owner) return ''\n return owner.label || (owner.policyName ? `policy:${owner.policyName}` : owner.agentId || '')\n}\n\nfunction layerPlan(plan: Plan): { nodes: LayoutNode[]; width: number; height: number } {\n const nodeById = new Map(plan.nodes.map((node) => [node.id, node]))\n const indexById = new Map(plan.nodes.map((node, index) => [node.id, index]))\n const outgoing = new Map<string, string[]>()\n const indegree = new Map(plan.nodes.map((node) => [node.id, 0]))\n for (const edge of plan.edges) {\n if (!nodeById.has(edge.from) || !nodeById.has(edge.to)) continue\n outgoing.set(edge.from, [...(outgoing.get(edge.from) ?? []), edge.to])\n indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1)\n }\n\n const layerById = new Map(plan.nodes.map((node) => [node.id, 0]))\n const queue = plan.nodes\n .filter((node) => (indegree.get(node.id) ?? 0) === 0)\n .map((node) => node.id)\n const visited = new Set<string>()\n\n while (queue.length) {\n const id = queue.shift()!\n visited.add(id)\n const fromLayer = layerById.get(id) ?? 0\n for (const to of outgoing.get(id) ?? []) {\n layerById.set(to, Math.max(layerById.get(to) ?? 0, fromLayer + 1))\n indegree.set(to, (indegree.get(to) ?? 0) - 1)\n if ((indegree.get(to) ?? 0) === 0) queue.push(to)\n }\n }\n\n let fallbackLayer = Math.max(0, ...Array.from(layerById.values())) + 1\n for (const node of plan.nodes) {\n if (visited.has(node.id)) continue\n layerById.set(node.id, fallbackLayer++)\n }\n\n const layers = new Map<number, PlanNode[]>()\n for (const node of plan.nodes) {\n const layer = layerById.get(node.id) ?? 0\n layers.set(layer, [...(layers.get(layer) ?? []), node])\n }\n\n const orderedLayers = [...layers.entries()].sort(([a], [b]) => a - b)\n const maxRows = Math.max(1, ...orderedLayers.map(([, nodes]) => nodes.length))\n const rowPitch = NODE_H + ROW_GAP\n\n const layoutNodes: LayoutNode[] = []\n orderedLayers.forEach(([layer, nodes], col) => {\n const ordered = [...nodes].sort((a, b) => {\n const lane = (a.lane || '').localeCompare(b.lane || '')\n return lane || ((indexById.get(a.id) ?? 0) - (indexById.get(b.id) ?? 0))\n })\n // Center each column vertically against the tallest column so the spine\n // runs through the middle and the parallel fan reads as a fan.\n const offset = ((maxRows - ordered.length) * rowPitch) / 2\n ordered.forEach((node, row) => {\n layoutNodes.push({\n node,\n layer,\n x: PAD + col * (NODE_W + COL_GAP),\n y: PAD + offset + row * rowPitch,\n })\n })\n })\n\n const cols = orderedLayers.length\n return {\n nodes: layoutNodes,\n width: Math.max(320, PAD * 2 + cols * NODE_W + Math.max(0, cols - 1) * COL_GAP),\n height: Math.max(NODE_H + PAD * 2, PAD * 2 + maxRows * NODE_H + Math.max(0, maxRows - 1) * ROW_GAP),\n }\n}\n\nfunction edgePath(from: LayoutNode, to: LayoutNode): string {\n const x1 = from.x + NODE_W\n const y1 = from.y + NODE_H / 2\n const x2 = to.x\n const y2 = to.y + NODE_H / 2\n const midX = x1 + Math.max(20, (x2 - x1) / 2)\n return `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`\n}\n\nexport function PlanGraphPanel({ threadId, teamId, channel }: { threadId?: string | number | null; teamId?: string | null; channel?: string }) {\n const key = teamId ? planTeamLookupKey(teamId, channel) : threadId ? planLookupKey(threadId, channel) : ''\n const plan = useRelayStore((s) => teamId ? s.plansByTeam[key] : key ? s.plansByThread[key] : null)\n const fetchPlan = useRelayStore((s) => s.fetchPlan)\n const planCollapsed = useRelayStore((s) => s.planCollapsed)\n const setPlanCollapsed = useRelayStore((s) => s.setPlanCollapsed)\n const isMobile = useIsMobile()\n // null = no manual toggle yet → follow the viewport default. The graph\n // dominates a phone screen, so it starts collapsed there and expanded on\n // desktop; an explicit user toggle (persisted) overrides the default.\n const collapsed = planCollapsed ?? isMobile\n\n useEffect(() => {\n if (teamId) {\n void fetchPlan({ teamId, channel })\n return\n }\n if (threadId) void fetchPlan({ threadId, channel })\n }, [channel, fetchPlan, teamId, threadId])\n\n const layout = useMemo(() => plan ? layerPlan(plan) : null, [plan])\n const nodeById = useMemo(() => new Map(layout?.nodes.map((node) => [node.node.id, node]) ?? []), [layout])\n\n const legend = useMemo(() => {\n if (!plan) return []\n const counts = new Map<PlanStatus, number>()\n for (const node of plan.nodes) counts.set(node.status, (counts.get(node.status) ?? 0) + 1)\n return STATUS_ORDER.filter((status) => counts.has(status)).map((status) => ({\n status,\n count: counts.get(status) ?? 0,\n }))\n }, [plan])\n\n if (!plan || !layout) return null\n\n const nodeCount = plan.nodes.length\n const landedCount = legend.find((entry) => entry.status === 'landed')?.count ?? 0\n // Thin summary for the collapsed bar (the legend dots are hidden on mobile),\n // e.g. \"5 nodes · 4 landed\".\n const summary = `${nodeCount} node${nodeCount === 1 ? '' : 's'}${landedCount > 0 ? ` · ${landedCount} landed` : ''}`\n\n return (\n <section className=\"shrink-0 border-b border-border bg-card/45 px-3 py-2\">\n <button\n type=\"button\"\n onClick={() => setPlanCollapsed(!collapsed)}\n aria-expanded={!collapsed}\n aria-label={collapsed ? 'Expand plan graph' : 'Collapse plan graph'}\n className={cn(\n 'flex w-full min-w-0 items-center gap-2 text-left',\n !collapsed && 'mb-2',\n )}\n >\n <span className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted/60\">\n <GitBranch className=\"h-3 w-3 text-muted-foreground\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-[13px] font-semibold leading-tight text-foreground\">{plan.title || 'Plan'}</div>\n <div className=\"truncate text-[11px] leading-tight text-muted-foreground\">\n {collapsed ? summary : (plan.objective || summary)}\n </div>\n </div>\n {legend.length > 0 && (\n <div className=\"hidden shrink-0 items-center gap-2.5 sm:flex\">\n {legend.map(({ status, count }) => (\n <span key={status} className=\"flex items-center gap-1 text-[10px] text-muted-foreground\">\n <span className={cn('h-1.5 w-1.5 rounded-full', STATUS_STYLE[status].dot)} />\n <span className={cn('font-medium tabular-nums', STATUS_STYLE[status].text)}>{count}</span>\n <span>{STATUS_STYLE[status].label}</span>\n </span>\n ))}\n </div>\n )}\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground\">\n v{plan.version}\n </span>\n <span className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted/60 hover:text-foreground\">\n {collapsed ? <ChevronDown className=\"h-3.5 w-3.5\" /> : <ChevronUp className=\"h-3.5 w-3.5\" />}\n </span>\n </button>\n {collapsed ? null : plan.nodes.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground\">\n No nodes yet — the plan graph fills in as the team is composed.\n </div>\n ) : (\n <div className=\"max-h-[min(46vh,360px)] overflow-auto\">\n <div className=\"relative\" style={{ width: layout.width, height: layout.height }}>\n <svg className=\"pointer-events-none absolute inset-0\" width={layout.width} height={layout.height} aria-hidden=\"true\">\n <defs>\n {STATUS_ORDER.map((status) => (\n <marker\n key={status}\n id={`plan-arrow-${status}`}\n viewBox=\"0 0 8 8\"\n refX=\"6.5\"\n refY=\"4\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M0 0 L8 4 L0 8 z\" fill={STATUS_STYLE[status].wire} />\n </marker>\n ))}\n </defs>\n {plan.edges.map((edge) => {\n const from = nodeById.get(edge.from)\n const to = nodeById.get(edge.to)\n if (!from || !to) return null\n const wire = STATUS_STYLE[from.node.status]\n return (\n <path\n key={`${edge.from}->${edge.to}`}\n d={edgePath(from, to)}\n stroke={wire.wire}\n strokeOpacity={0.7}\n strokeWidth={2}\n fill=\"none\"\n strokeLinecap=\"round\"\n markerEnd={`url(#plan-arrow-${from.node.status})`}\n />\n )\n })}\n </svg>\n {layout.nodes.map(({ node, x, y }) => {\n const status = STATUS_STYLE[node.status]\n const owner = ownerLabel(node.ownerRef)\n return (\n <article\n key={node.id}\n className={cn(\n 'group absolute flex flex-col justify-between rounded-md border border-l-[3px] bg-background/95 p-2',\n 'shadow-sm transition-all hover:-translate-y-px hover:border-border hover:shadow-md hover:bg-background',\n status.accent,\n )}\n style={{ left: x, top: y, width: NODE_W, height: NODE_H }}\n title={node.scopeBlurb || node.label}\n >\n <div className=\"truncate text-xs font-semibold leading-tight text-foreground\">{node.label}</div>\n {(node.scopeBlurb || node.id) && (\n <div className=\"truncate text-[10px] leading-tight text-muted-foreground\">{node.scopeBlurb || node.id}</div>\n )}\n <div className=\"flex min-w-0 items-center justify-between gap-1.5\">\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <span className={cn('h-1.5 w-1.5 shrink-0 rounded-full', status.dot)} />\n <span className={cn('text-[10px] font-medium', status.text)}>{status.label}</span>\n </div>\n <div className=\"flex min-w-0 items-center gap-1.5\">\n {owner && <span className=\"truncate text-[10px] text-muted-foreground\">{owner}</span>}\n {node.lane && (\n <span className=\"shrink-0 rounded-sm bg-muted/70 px-1 py-px text-[9px] font-medium uppercase tracking-wide text-muted-foreground\">\n {node.lane}\n </span>\n )}\n </div>\n </div>\n </article>\n )\n })}\n </div>\n </div>\n )}\n </section>\n )\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PlanGraphPanel-CXix1C-H.js","names":[],"sources":["../../dashboard/src/components/chat/hooks/useIsMobile.ts","../../dashboard/src/components/chat/PlanGraphPanel.tsx"],"sourcesContent":["import { useSyncExternalStore } from 'react'\n\n// Reactive narrow-viewport flag. Subscribes to the same `max-width: 767px`\n// breakpoint the chat single-pane layout uses, via useSyncExternalStore — an\n// external-store subscription, not effect-driven derived state — so it stays\n// correct across resize/rotate without a useEffect.\nconst QUERY = '(max-width: 767px)'\n\nfunction subscribe(cb: () => void): () => void {\n if (typeof window === 'undefined') return () => {}\n const mql = window.matchMedia(QUERY)\n mql.addEventListener('change', cb)\n return () => mql.removeEventListener('change', cb)\n}\n\nexport function useIsMobile(): boolean {\n return useSyncExternalStore(\n subscribe,\n () => (typeof window !== 'undefined' ? window.matchMedia(QUERY).matches : false),\n () => false,\n )\n}\n","import { useEffect, useMemo } from 'react'\nimport { ChevronDown, ChevronUp, GitBranch } from 'lucide-react'\nimport { useRelayStore } from '@/store'\nimport type { Plan, PlanNode, PlanOwnerRef, PlanStatus } from '@/types'\nimport { cn } from '@/lib/utils'\nimport { planLookupKey, planTeamLookupKey } from '@/store/slices/plans'\nimport { useIsMobile } from './hooks/useIsMobile'\n\n// Left-to-right DAG: dependency depth flows along x (sequential gates), nodes\n// that share a depth stack along y (parallel lanes). This keeps the panel SHORT\n// — it's an operator overlay above the chat, not a full view — and lets the\n// gate-vs-lane structure read at a glance. Height is capped + scrollable below.\nconst NODE_W = 196\nconst NODE_H = 68\nconst COL_GAP = 52\nconst ROW_GAP = 16\nconst PAD = 14\n\nconst STATUS_ORDER: PlanStatus[] = ['landed', 'in-progress', 'pending', 'blocked']\n\nconst STATUS_STYLE: Record<\n PlanStatus,\n { label: string; dot: string; accent: string; text: string; wire: string }\n> = {\n landed: { label: 'landed', dot: 'bg-emerald-400', accent: 'border-l-emerald-400', text: 'text-emerald-300', wire: '#34d399' },\n 'in-progress': { label: 'in progress', dot: 'bg-sky-400', accent: 'border-l-sky-400', text: 'text-sky-300', wire: '#38bdf8' },\n pending: { label: 'pending', dot: 'bg-amber-400', accent: 'border-l-amber-400', text: 'text-amber-300', wire: '#fbbf24' },\n blocked: { label: 'blocked', dot: 'bg-red-400', accent: 'border-l-red-400', text: 'text-red-300', wire: '#f87171' },\n}\n\ninterface LayoutNode {\n node: PlanNode\n x: number\n y: number\n layer: number\n}\n\nfunction ownerLabel(owner?: PlanOwnerRef): string {\n if (!owner) return ''\n return owner.label || (owner.policyName ? `policy:${owner.policyName}` : owner.agentId || '')\n}\n\nfunction layerPlan(plan: Plan): { nodes: LayoutNode[]; width: number; height: number } {\n const nodeById = new Map(plan.nodes.map((node) => [node.id, node]))\n const indexById = new Map(plan.nodes.map((node, index) => [node.id, index]))\n const outgoing = new Map<string, string[]>()\n const indegree = new Map(plan.nodes.map((node) => [node.id, 0]))\n for (const edge of plan.edges) {\n if (!nodeById.has(edge.from) || !nodeById.has(edge.to)) continue\n outgoing.set(edge.from, [...(outgoing.get(edge.from) ?? []), edge.to])\n indegree.set(edge.to, (indegree.get(edge.to) ?? 0) + 1)\n }\n\n const layerById = new Map(plan.nodes.map((node) => [node.id, 0]))\n const queue = plan.nodes\n .filter((node) => (indegree.get(node.id) ?? 0) === 0)\n .map((node) => node.id)\n const visited = new Set<string>()\n\n while (queue.length) {\n const id = queue.shift()!\n visited.add(id)\n const fromLayer = layerById.get(id) ?? 0\n for (const to of outgoing.get(id) ?? []) {\n layerById.set(to, Math.max(layerById.get(to) ?? 0, fromLayer + 1))\n indegree.set(to, (indegree.get(to) ?? 0) - 1)\n if ((indegree.get(to) ?? 0) === 0) queue.push(to)\n }\n }\n\n let fallbackLayer = Math.max(0, ...Array.from(layerById.values())) + 1\n for (const node of plan.nodes) {\n if (visited.has(node.id)) continue\n layerById.set(node.id, fallbackLayer++)\n }\n\n const layers = new Map<number, PlanNode[]>()\n for (const node of plan.nodes) {\n const layer = layerById.get(node.id) ?? 0\n layers.set(layer, [...(layers.get(layer) ?? []), node])\n }\n\n const orderedLayers = [...layers.entries()].sort(([a], [b]) => a - b)\n const maxRows = Math.max(1, ...orderedLayers.map(([, nodes]) => nodes.length))\n const rowPitch = NODE_H + ROW_GAP\n\n const layoutNodes: LayoutNode[] = []\n orderedLayers.forEach(([layer, nodes], col) => {\n const ordered = [...nodes].sort((a, b) => {\n const lane = (a.lane || '').localeCompare(b.lane || '')\n return lane || ((indexById.get(a.id) ?? 0) - (indexById.get(b.id) ?? 0))\n })\n // Center each column vertically against the tallest column so the spine\n // runs through the middle and the parallel fan reads as a fan.\n const offset = ((maxRows - ordered.length) * rowPitch) / 2\n ordered.forEach((node, row) => {\n layoutNodes.push({\n node,\n layer,\n x: PAD + col * (NODE_W + COL_GAP),\n y: PAD + offset + row * rowPitch,\n })\n })\n })\n\n const cols = orderedLayers.length\n return {\n nodes: layoutNodes,\n width: Math.max(320, PAD * 2 + cols * NODE_W + Math.max(0, cols - 1) * COL_GAP),\n height: Math.max(NODE_H + PAD * 2, PAD * 2 + maxRows * NODE_H + Math.max(0, maxRows - 1) * ROW_GAP),\n }\n}\n\nfunction edgePath(from: LayoutNode, to: LayoutNode): string {\n const x1 = from.x + NODE_W\n const y1 = from.y + NODE_H / 2\n const x2 = to.x\n const y2 = to.y + NODE_H / 2\n const midX = x1 + Math.max(20, (x2 - x1) / 2)\n return `M ${x1} ${y1} C ${midX} ${y1}, ${midX} ${y2}, ${x2} ${y2}`\n}\n\nexport function PlanGraphPanel({ threadId, teamId, channel }: { threadId?: string | number | null; teamId?: string | null; channel?: string }) {\n const key = teamId ? planTeamLookupKey(teamId, channel) : threadId ? planLookupKey(threadId, channel) : ''\n const plan = useRelayStore((s) => teamId ? s.plansByTeam[key] : key ? s.plansByThread[key] : null)\n const fetchPlan = useRelayStore((s) => s.fetchPlan)\n const planCollapsed = useRelayStore((s) => s.planCollapsed)\n const setPlanCollapsed = useRelayStore((s) => s.setPlanCollapsed)\n const isMobile = useIsMobile()\n // null = no manual toggle yet → follow the viewport default. The graph\n // dominates a phone screen, so it starts collapsed there and expanded on\n // desktop; an explicit user toggle (persisted) overrides the default.\n const collapsed = planCollapsed ?? isMobile\n\n useEffect(() => {\n if (teamId) {\n void fetchPlan({ teamId, channel })\n return\n }\n if (threadId) void fetchPlan({ threadId, channel })\n }, [channel, fetchPlan, teamId, threadId])\n\n const layout = useMemo(() => plan ? layerPlan(plan) : null, [plan])\n const nodeById = useMemo(() => new Map(layout?.nodes.map((node) => [node.node.id, node]) ?? []), [layout])\n\n const legend = useMemo(() => {\n if (!plan) return []\n const counts = new Map<PlanStatus, number>()\n for (const node of plan.nodes) counts.set(node.status, (counts.get(node.status) ?? 0) + 1)\n return STATUS_ORDER.filter((status) => counts.has(status)).map((status) => ({\n status,\n count: counts.get(status) ?? 0,\n }))\n }, [plan])\n\n if (!plan || !layout) return null\n\n const nodeCount = plan.nodes.length\n const landedCount = legend.find((entry) => entry.status === 'landed')?.count ?? 0\n // Thin summary for the collapsed bar (the legend dots are hidden on mobile),\n // e.g. \"5 nodes · 4 landed\".\n const summary = `${nodeCount} node${nodeCount === 1 ? '' : 's'}${landedCount > 0 ? ` · ${landedCount} landed` : ''}`\n\n return (\n <section className=\"shrink-0 border-b border-border bg-card/45 px-3 py-2\">\n <button\n type=\"button\"\n onClick={() => setPlanCollapsed(!collapsed)}\n aria-expanded={!collapsed}\n aria-label={collapsed ? 'Expand plan graph' : 'Collapse plan graph'}\n className={cn(\n 'flex w-full min-w-0 items-center gap-2 text-left',\n !collapsed && 'mb-2',\n )}\n >\n <span className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded bg-muted/60\">\n <GitBranch className=\"h-3 w-3 text-muted-foreground\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className=\"truncate text-[13px] font-semibold leading-tight text-foreground\">{plan.title || 'Plan'}</div>\n <div className=\"truncate text-[11px] leading-tight text-muted-foreground\">\n {collapsed ? summary : (plan.objective || summary)}\n </div>\n </div>\n {legend.length > 0 && (\n <div className=\"hidden shrink-0 items-center gap-2.5 sm:flex\">\n {legend.map(({ status, count }) => (\n <span key={status} className=\"flex items-center gap-1 text-[10px] text-muted-foreground\">\n <span className={cn('h-1.5 w-1.5 rounded-full', STATUS_STYLE[status].dot)} />\n <span className={cn('font-medium tabular-nums', STATUS_STYLE[status].text)}>{count}</span>\n <span>{STATUS_STYLE[status].label}</span>\n </span>\n ))}\n </div>\n )}\n <span className=\"shrink-0 rounded bg-muted/60 px-1.5 py-0.5 text-[10px] font-medium tabular-nums text-muted-foreground\">\n v{plan.version}\n </span>\n <span className=\"flex h-5 w-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-muted/60 hover:text-foreground\">\n {collapsed ? <ChevronDown className=\"h-3.5 w-3.5\" /> : <ChevronUp className=\"h-3.5 w-3.5\" />}\n </span>\n </button>\n {collapsed ? null : plan.nodes.length === 0 ? (\n <div className=\"rounded-md border border-dashed border-border px-3 py-3 text-center text-xs text-muted-foreground\">\n No nodes yet — the plan graph fills in as the team is composed.\n </div>\n ) : (\n <div className=\"max-h-[min(46vh,360px)] overflow-auto\">\n <div className=\"relative\" style={{ width: layout.width, height: layout.height }}>\n <svg className=\"pointer-events-none absolute inset-0\" width={layout.width} height={layout.height} aria-hidden=\"true\">\n <defs>\n {STATUS_ORDER.map((status) => (\n <marker\n key={status}\n id={`plan-arrow-${status}`}\n viewBox=\"0 0 8 8\"\n refX=\"6.5\"\n refY=\"4\"\n markerWidth=\"6\"\n markerHeight=\"6\"\n orient=\"auto-start-reverse\"\n >\n <path d=\"M0 0 L8 4 L0 8 z\" fill={STATUS_STYLE[status].wire} />\n </marker>\n ))}\n </defs>\n {plan.edges.map((edge) => {\n const from = nodeById.get(edge.from)\n const to = nodeById.get(edge.to)\n if (!from || !to) return null\n const wire = STATUS_STYLE[from.node.status]\n return (\n <path\n key={`${edge.from}->${edge.to}`}\n d={edgePath(from, to)}\n stroke={wire.wire}\n strokeOpacity={0.7}\n strokeWidth={2}\n fill=\"none\"\n strokeLinecap=\"round\"\n markerEnd={`url(#plan-arrow-${from.node.status})`}\n />\n )\n })}\n </svg>\n {layout.nodes.map(({ node, x, y }) => {\n const status = STATUS_STYLE[node.status]\n const owner = ownerLabel(node.ownerRef)\n return (\n <article\n key={node.id}\n className={cn(\n 'group absolute flex flex-col justify-between rounded-md border border-l-[3px] bg-background/95 p-2',\n 'shadow-sm transition-all hover:-translate-y-px hover:border-border hover:shadow-md hover:bg-background',\n status.accent,\n )}\n style={{ left: x, top: y, width: NODE_W, height: NODE_H }}\n title={node.scopeBlurb || node.label}\n >\n <div className=\"truncate text-xs font-semibold leading-tight text-foreground\">{node.label}</div>\n {(node.scopeBlurb || node.id) && (\n <div className=\"truncate text-[10px] leading-tight text-muted-foreground\">{node.scopeBlurb || node.id}</div>\n )}\n <div className=\"flex min-w-0 items-center justify-between gap-1.5\">\n <div className=\"flex shrink-0 items-center gap-1.5\">\n <span className={cn('h-1.5 w-1.5 shrink-0 rounded-full', status.dot)} />\n <span className={cn('text-[10px] font-medium', status.text)}>{status.label}</span>\n </div>\n <div className=\"flex min-w-0 items-center gap-1.5\">\n {owner && <span className=\"truncate text-[10px] text-muted-foreground\">{owner}</span>}\n {node.lane && (\n <span className=\"shrink-0 rounded-sm bg-muted/70 px-1 py-px text-[9px] font-medium uppercase tracking-wide text-muted-foreground\">\n {node.lane}\n </span>\n )}\n </div>\n </div>\n </article>\n )\n })}\n </div>\n </div>\n )}\n </section>\n )\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{r as e}from"./chunk-CilyBKbf.js";import{
|
|
2
|
-
//# sourceMappingURL=activity-
|
|
1
|
+
import{r as e}from"./chunk-CilyBKbf.js";import{At as t,Fn as n,Ft as r,Mn as i,Nn as a,P as o,X as s,Z as c,Zt as l,ct as u,en as d,qn as f,u as p,vn as m}from"./lucide-react-H_-8G79A.js";import{o as h,t as g}from"./store-DHO_a8BD.js";import{U as _,f as v,h as y,it as b}from"./display-CketawEF.js";import{t as x}from"./badge-Cy6qO_Jz.js";import{t as S}from"./button-73wpRw65.js";import{s as C}from"./index-br_Y6Cvt.js";import{n as w,t as T}from"./card-BU6PvVHv.js";import{l as E,n as D,o as O,r as k,t as A,u as j,y as M}from"./MessageBubble-BrEKymoV.js";var N=e(f(),1),P=n(),F={question:d,reply:s,message:c,operator:p,pair:u,task:l,state:o},I={question:`bg-blue-500/10 text-blue-400 border-blue-500/20`,reply:`bg-emerald-500/10 text-emerald-400 border-emerald-500/20`,message:`bg-zinc-500/10 text-zinc-400 border-zinc-500/20`,operator:`bg-purple-500/10 text-purple-400 border-purple-500/20`,pair:`bg-yellow-500/10 text-yellow-400 border-yellow-500/20`,task:`bg-orange-500/10 text-orange-400 border-orange-500/20`,state:`bg-cyan-500/10 text-cyan-400 border-cyan-500/20`},L=[`question`,`reply`,`message`,`operator`,`pair`,`task`,`state`],R=[`chat`,`session`,`system`,`task`];function z(e){let t=e?.surface;if(!t||typeof t!=`object`)return null;let n=t,r=[];return typeof n.component==`string`&&r.push(n.component),typeof n.view==`string`&&n.view!==n.component&&r.push(n.view),typeof n.session==`string`&&r.push(`tab ${n.session.slice(0,6)}`),typeof n.client==`string`&&r.push(`client ${n.client.slice(0,6)}`),r.length?r.join(` · `):null}function B({entry:e,agentsById:t}){if(e.type===`notice`)return(0,P.jsx)(`div`,{className:`px-1`,children:(0,P.jsx)(E,{msg:e.msg})});if(e.type===`agent-action`)return(0,P.jsx)(`div`,{className:`px-1`,children:(0,P.jsx)(k,{entry:e})});if(e.type===`activity`)return(0,P.jsx)(`div`,{className:`px-1 opacity-60`,children:(0,P.jsx)(D,{steps:e.steps,showReasoning:!1})});if(e.type===`status`)return(0,P.jsx)(`div`,{className:`px-1`,children:(0,P.jsx)(j,{event:e.event})});if(e.type===`date`)return(0,P.jsx)(`div`,{className:`px-1`,children:(0,P.jsx)(O,{date:e.date})});if(e.type===`created`||e.type===`import-boundary`||e.type===`imported-message`||e.type!==`message`)return null;let{msg:n}=e,r=v(n.from,t),i=v(n.to,t);return(0,P.jsxs)(`div`,{children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-1.5 px-3 mb-0.5`,children:[(0,P.jsx)(`span`,{className:`text-xs font-mono text-muted-foreground truncate max-w-[140px]`,title:n.from,children:r}),(0,P.jsx)(`span`,{className:`text-xs text-muted-foreground shrink-0`,children:`→`}),(0,P.jsx)(`span`,{className:`text-xs font-mono text-muted-foreground truncate max-w-[140px]`,title:n.to,children:i}),(0,P.jsx)(x,{variant:`outline`,className:`text-[10px] px-1 py-0 shrink-0 ml-1`,children:n.kind})]}),(0,P.jsx)(A,{msg:n,peer:b})]})}function V(){let e=g(e=>e.messages),t=g(e=>e.agentsById),[n,r]=(0,N.useState)(``),[i,a]=(0,N.useState)(``),o=(0,N.useMemo)(()=>{let t=e;if(n){let e=n.toLowerCase();t=t.filter(t=>t.from.toLowerCase().includes(e)||t.to.toLowerCase().includes(e))}return i&&(t=t.filter(e=>e.kind===i)),t},[e,n,i]),s=(0,N.useMemo)(()=>M(o,[]),[o]);return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsx)(`input`,{type:`text`,className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm w-44`,placeholder:`Filter by agent…`,value:n,onChange:e=>r(e.target.value)}),(0,P.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:i,onChange:e=>a(e.target.value),children:[(0,P.jsx)(`option`,{value:``,children:`All kinds`}),R.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))]}),(0,P.jsx)(x,{variant:`secondary`,className:`ml-auto`,children:o.length})]}),(0,P.jsx)(C,{className:`h-[calc(100dvh-13rem)]`,children:(0,P.jsxs)(`div`,{className:`space-y-1 pr-2`,children:[s.length===0&&(0,P.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`No messages yet`}),s.map((e,n)=>(0,P.jsx)(B,{entry:e,agentsById:t},n))]})})]})}function H(){let e=h(),t=g(e=>e.activityFilter),n=g(e=>e.activityEvents),a=g(e=>e.operatorActivity),o=g(e=>e.set),s=g(e=>e.openActivityItem),c=g(e=>e.exportActivity),l=(0,N.useMemo)(()=>{let e=[...n,...a],t=new Set,r=[];for(let n of e){let e=n.clientId||String(n.id);t.has(e)||(t.add(e),r.push(n))}return r.map(e=>({...e,ts:e.ts??new Date(e.createdAt).getTime()})).sort((e,t)=>(t.ts??0)-(e.ts??0))},[n,a]),u=(0,N.useMemo)(()=>t?l.filter(e=>e.kind===t):l,[l,t]);return(0,P.jsxs)(P.Fragment,{children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsxs)(`select`,{className:`rounded-md border border-input bg-background px-3 py-1.5 text-sm`,value:t,onChange:e=>o({activityFilter:e.target.value}),children:[(0,P.jsx)(`option`,{value:``,children:`All types`}),L.map(e=>(0,P.jsx)(`option`,{value:e,children:e},e))]}),(0,P.jsxs)(S,{size:`sm`,variant:`outline`,onClick:()=>c(`markdown`),children:[(0,P.jsx)(r,{className:`w-4 h-4 mr-1`}),` Markdown`]}),(0,P.jsxs)(S,{size:`sm`,variant:`outline`,onClick:()=>c(`json`),children:[(0,P.jsx)(m,{className:`w-4 h-4 mr-1`}),` JSON`]}),(0,P.jsx)(x,{variant:`secondary`,className:`ml-auto`,children:u.length})]}),(0,P.jsx)(C,{className:`h-[calc(100dvh-13rem)]`,children:(0,P.jsxs)(`div`,{className:`space-y-2 pr-2`,children:[u.length===0&&(0,P.jsx)(`div`,{className:`text-center text-muted-foreground py-16 text-sm`,children:`No activity yet`}),u.map(t=>{let n=F[t.kind]||i,r=I[t.kind]||I.message,a=z(t.metadata);return(0,P.jsx)(T,{className:`cursor-pointer hover:bg-accent/40 transition-colors`,onClick:()=>s(t),children:(0,P.jsxs)(w,{className:`p-3 flex gap-3`,children:[(0,P.jsx)(`span`,{className:`inline-flex items-center justify-center w-7 h-7 rounded-md border shrink-0 mt-0.5 ${r}`,children:(0,P.jsx)(n,{className:`w-3.5 h-3.5`})}),(0,P.jsxs)(`div`,{className:`flex-1 min-w-0`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2 flex-wrap`,children:[(0,P.jsx)(`span`,{className:`font-medium text-sm truncate`,children:t.title}),(0,P.jsx)(x,{variant:`outline`,className:`text-xs px-1.5 py-0 border ${r}`,children:t.kind}),(0,P.jsx)(`span`,{className:`text-xs text-muted-foreground ml-auto shrink-0`,title:y(t.ts??t.createdAt),children:_(e,t.ts??t.createdAt)})]}),t.body&&(0,P.jsx)(`p`,{className:`text-xs text-muted-foreground mt-0.5 line-clamp-2`,children:t.body}),t.meta&&(0,P.jsx)(`p`,{className:`text-xs text-muted-foreground/60 mt-0.5 font-mono truncate`,children:t.meta}),a&&(0,P.jsxs)(`p`,{className:`text-xs text-muted-foreground/60 mt-0.5 truncate`,title:a,children:[(0,P.jsx)(`span`,{className:`opacity-70`,children:`via`}),` `,a]})]})]})},t.clientId||t.id)})]})})]})}function U(){let[e,n]=(0,N.useState)(`firehose`);return(0,P.jsxs)(`div`,{className:`space-y-4`,children:[(0,P.jsxs)(`div`,{className:`flex items-center justify-between flex-wrap gap-2`,children:[(0,P.jsxs)(`div`,{className:`flex items-center gap-2`,children:[e===`firehose`?(0,P.jsx)(t,{className:`w-5 h-5 text-muted-foreground`}):(0,P.jsx)(i,{className:`w-5 h-5 text-muted-foreground`}),(0,P.jsx)(`h2`,{className:`text-lg font-semibold`,children:e===`firehose`?`Firehose`:`Events`})]}),(0,P.jsxs)(`div`,{className:`flex items-center gap-1 rounded-md border border-input overflow-hidden`,children:[(0,P.jsx)(`button`,{className:a(`px-3 py-1.5 text-sm transition-colors`,e===`firehose`?`bg-primary text-primary-foreground`:`hover:bg-accent`),onClick:()=>n(`firehose`),children:`Firehose`}),(0,P.jsx)(`button`,{className:a(`px-3 py-1.5 text-sm transition-colors`,e===`events`?`bg-primary text-primary-foreground`:`hover:bg-accent`),onClick:()=>n(`events`),children:`Events`})]})]}),e===`firehose`?(0,P.jsx)(V,{}):(0,P.jsx)(H,{})]})}export{U as ActivityView};
|
|
2
|
+
//# sourceMappingURL=activity-CRafQ1Je.js.map
|