@athenaintel/react 0.50.0 → 0.52.1

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.
Files changed (104) hide show
  1. package/README.md +20 -0
  2. package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
  3. package/dist/agent-runtime/src/edit-command.cjs +21 -0
  4. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  5. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  6. package/dist/agent-runtime/src/edit-command.js +21 -0
  7. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  8. package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
  9. package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
  10. package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
  11. package/dist/agent-runtime/src/handoff-arrival.js +20 -1
  12. package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
  13. package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
  14. package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
  15. package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
  16. package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
  17. package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
  18. package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
  19. package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
  20. package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
  21. package/dist/agent-runtime/src/handoff-cursor.js +88 -0
  22. package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
  23. package/dist/agent-runtime/src/index.d.ts +4 -3
  24. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +6 -0
  25. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +114 -4
  26. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  27. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
  28. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  29. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
  30. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
  31. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  32. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +5 -0
  33. package/dist/chat/AthenaChat.cjs +27 -12
  34. package/dist/chat/AthenaChat.cjs.map +1 -1
  35. package/dist/chat/AthenaChat.d.ts +13 -0
  36. package/dist/chat/AthenaChat.js +27 -12
  37. package/dist/chat/AthenaChat.js.map +1 -1
  38. package/dist/chat/AthenaPlanStrip.cjs +97 -0
  39. package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
  40. package/dist/chat/AthenaPlanStrip.d.ts +15 -0
  41. package/dist/chat/AthenaPlanStrip.js +97 -0
  42. package/dist/chat/AthenaPlanStrip.js.map +1 -0
  43. package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
  44. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  45. package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
  46. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  47. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
  48. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  49. package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
  50. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  51. package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
  52. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  53. package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
  54. package/dist/chat/super-grouping/activity-timeline.js +6 -4
  55. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  56. package/dist/chat/super-grouping/index.d.ts +1 -1
  57. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
  58. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
  59. package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
  60. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
  61. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
  62. package/dist/chat-ui/index.cjs +8 -0
  63. package/dist/chat-ui/index.cjs.map +1 -1
  64. package/dist/chat-ui/index.d.ts +2 -1
  65. package/dist/chat-ui/index.js +9 -1
  66. package/dist/chat-ui/index.js.map +1 -1
  67. package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
  68. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  69. package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
  70. package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
  71. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  72. package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
  73. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
  74. package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
  75. package/dist/chat-ui/thread/handoff-cursor.js +216 -0
  76. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
  77. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
  78. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  79. package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
  80. package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
  81. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  82. package/dist/index.cjs +2 -0
  83. package/dist/index.cjs.map +1 -1
  84. package/dist/index.d.ts +2 -1
  85. package/dist/index.js +2 -0
  86. package/dist/index.js.map +1 -1
  87. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +20 -3
  88. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -1
  89. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +11 -2
  90. package/dist/keryx-http/src/unconfirmed-update-attributions.js +21 -4
  91. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -1
  92. package/dist/react/package.json.cjs +1 -1
  93. package/dist/react/package.json.js +1 -1
  94. package/dist/styles.css +1 -1
  95. package/dist/tools/write-todos-tool-ui.cjs +8 -0
  96. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  97. package/dist/tools/write-todos-tool-ui.d.ts +2 -0
  98. package/dist/tools/write-todos-tool-ui.js +8 -0
  99. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  100. package/dist/workbench/session-workbench-panes.cjs +1 -8
  101. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  102. package/dist/workbench/session-workbench-panes.js +2 -9
  103. package/dist/workbench/session-workbench-panes.js.map +1 -1
  104. package/package.json +2 -2
@@ -1 +1 @@
1
- {"version":3,"file":"activity-timeline.cjs","sources":["../../../src/chat/super-grouping/activity-timeline.ts"],"sourcesContent":["/**\n * The assistant turn's activity timeline — the one derivation of \"what did\n * this turn do, and how did each step end\" behind the collapsed view\n * (previously called the conversational view) on every surface: the SDK's\n * `AthenaChat`, Olympus's Spaces chat, and the Chrome extension. Pure\n * functions over assistant-ui message parts; `SuperGroupingCard` renders them.\n *\n * Step states come from the shared classifier (`classifyToolResult`) plus the\n * approval outcomes a part carries (`approval`, `interrupt`, a rejection\n * result), so a call that reads as failed, paused, or rejected on one surface\n * cannot read as a green check on another. Copy comes from the shared tool\n * summary registry (`summarizeToolCall`), itself built on the display\n * registry.\n */\nimport type {\n MessageStatus,\n ThreadAssistantMessagePart,\n ToolCallMessagePartStatus,\n} from '@assistant-ui/react';\nimport {\n classifyToolResult,\n countInEvalDispatches,\n EVAL_TOOL_NAME,\n isRunningToolPlaceholderArtifact,\n isSpokenTurnTextPart,\n readRejectedToolResult,\n TASK_TOOL_NAME,\n type ToolStatusLabelState,\n} from '@athenaintel/agent-runtime';\nimport { summarizeToolCall, type ToolCallSummary } from '../../tools/tool-summary';\nimport type { GroupablePart, GroupingFunction } from './grouping-function';\n\nexport type ActivityToolState = ToolStatusLabelState;\n\nexport type ActivityItem =\n | {\n kind: 'reasoning';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n text: string;\n state: 'running' | 'complete';\n }\n | {\n kind: 'tool';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n toolName: string;\n args?: ToolCallPart['args'];\n /** The tool's real result; absent while it runs or holds only the streaming placeholder. */\n result?: unknown;\n state: ActivityToolState;\n /**\n * Subagents the call delegated to: for a `task` call one, or none when it\n * was refused, withdrawn or expired at its approval and never ran; for an\n * `eval` call its in-interpreter `task()` dispatches\n * (`countInEvalDispatches`), absent when it made none.\n */\n delegations?: number;\n };\n\n/** The turn as a whole: still working, parked on the person, ended in error, or done. */\nexport type ActivityPhase = 'running' | 'waiting' | 'complete' | 'error';\n\ntype ToolCallPart = Extract<ThreadAssistantMessagePart, { type: 'tool-call' }>;\n\nexport interface ActivityTimelineOptions {\n /**\n * Tools whose unresolved call means the turn is waiting on the person even\n * without an interrupt or approval on the part (the Chrome extension's\n * device plan approval, which the tool UI itself collects).\n */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id, as the host reads them off\n * its pending approval: a gated call, or (in Olympus) a `task` call whose\n * subagent waits on an approval. Any of them still without its real result\n * reads as waiting.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /**\n * Tools whose result is an application payload relayed verbatim (the Chrome\n * extension's page JavaScript), so a `success: false` flag counts only with\n * an error detail — see `classifyToolResult`'s `passThroughPayload`.\n */\n passThroughTools?: ReadonlySet<string>;\n}\n\nconst NO_TOOLS: ReadonlySet<string> = new Set();\nconst NO_TOOL_CALLS: ReadonlySet<string> = new Set();\n\n/**\n * Whether a tool-call part still holds the backend's running-tool placeholder\n * result rather than the tool's real output. Long-running tools (task\n * subagents, studio and Python execution, deep research) append a ToolMessage\n * the moment they start so their card can fill in live; its marker reaches the\n * part's artifact (`isRunningToolPlaceholderArtifact`). The real ToolMessage\n * that replaces it has no such marker, so a defined `result` alone does not\n * mean the tool is done.\n */\nexport function isStreamingToolPlaceholder(part: Pick<ToolCallPart, 'artifact'>): boolean {\n return isRunningToolPlaceholderArtifact(part.artifact);\n}\n\n/** The tool's real result: undefined while it has none or only the streaming placeholder. */\nfunction finalResult(part: ToolCallPart): unknown {\n return isStreamingToolPlaceholder(part) ? undefined : part.result;\n}\n\nfunction toolCallStatus(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n): ToolCallMessagePartStatus | undefined {\n if (finalResult(part) !== undefined) return { type: 'complete' };\n if (messageStatus?.type === 'incomplete') {\n return {\n type: 'incomplete',\n reason: messageStatus.reason === 'tool-calls' ? 'other' : messageStatus.reason,\n error: messageStatus.error,\n };\n }\n if (messageStatus?.type === 'complete') {\n // The placeholder is the backend's word that the call is still in flight.\n // A turn reads `complete` whenever nothing streams (a run parked on an\n // approval inside a subagent, the gap before a drained run resumes), and\n // assistant-ui counts the placeholder as the call's result when it settles\n // that status, so the turn's status cannot finish the call.\n return isStreamingToolPlaceholder(part) ? { type: 'running' } : { type: 'complete' };\n }\n if (messageStatus?.type === 'requires-action') {\n return { type: 'requires-action', reason: messageStatus.reason };\n }\n if (messageStatus?.type === 'running') return { type: 'running' };\n return undefined;\n}\n\n/** How one tool call ended (or is going), reading the approval fields before the result. */\nexport function getActivityToolState(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n {\n waitingTools = NO_TOOLS,\n waitingToolCallIds = NO_TOOL_CALLS,\n passThroughTools = NO_TOOLS,\n }: ActivityTimelineOptions = {},\n): ActivityToolState {\n if (part.approval?.approved === false || readRejectedToolResult(part.result) !== null) {\n return 'rejected';\n }\n if (part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired') {\n return 'cancelled';\n }\n const result = finalResult(part);\n const unresolved = result === undefined;\n const messageIsActive =\n messageStatus === undefined ||\n messageStatus.type === 'running' ||\n messageStatus.type === 'requires-action';\n if (\n unresolved &&\n (part.interrupt?.type === 'human' ||\n (part.approval !== undefined &&\n part.approval.approved === undefined &&\n part.approval.resolution === undefined) ||\n waitingToolCallIds.has(part.toolCallId) ||\n (waitingTools.has(part.toolName) && messageIsActive))\n ) {\n return 'waiting';\n }\n\n const classification = classifyToolResult({\n status: toolCallStatus(part, messageStatus),\n result,\n isError: part.isError,\n passThroughPayload: passThroughTools.has(part.toolName),\n });\n if (classification.state === 'requires-action') return 'waiting';\n return classification.state;\n}\n\n/**\n * The compact reasoning/tool timeline in the order the parts streamed.\n *\n * A reasoning part is still streaming only while it is the final part of a\n * running message — once any later part appears, the thought has settled.\n * Settled reasoning parts with no text are dropped so the list never shows an\n * empty thinking row.\n */\nexport function buildActivityTimeline(\n content: readonly ThreadAssistantMessagePart[] | undefined,\n messageStatus: MessageStatus | undefined,\n options: ActivityTimelineOptions = {},\n): ActivityItem[] {\n if (!content?.length) return [];\n\n const lastPartIndex = content.length - 1;\n const timeline: ActivityItem[] = [];\n for (let index = 0; index < content.length; index += 1) {\n const part = content[index];\n if (part.type === 'tool-call') {\n const result = finalResult(part);\n const state = getActivityToolState(part, messageStatus, options);\n const delegations = toolCallDelegations({ part, state });\n timeline.push({\n kind: 'tool',\n id: part.toolCallId,\n partIndex: index,\n args: part.args,\n ...(result !== undefined ? { result } : {}),\n toolName: part.toolName,\n state,\n ...(delegations !== null ? { delegations } : {}),\n });\n continue;\n }\n if (part.type !== 'reasoning') continue;\n\n const text = typeof part.text === 'string' ? part.text : '';\n const stillStreaming = messageStatus?.type === 'running' && index === lastPartIndex;\n if (!stillStreaming && text.trim() === '') continue;\n timeline.push({\n kind: 'reasoning',\n id: `reasoning-${index}`,\n partIndex: index,\n text,\n state: stillStreaming ? 'running' : 'complete',\n });\n }\n return timeline;\n}\n\n/**\n * Subagents one tool call delegated to (see `ActivityItem.delegations`), or\n * `null` for a call that delegates by nature of neither: a `task` call one,\n * unless the person refused it or its approval was withdrawn or expired (it\n * never ran); an `eval` call its in-interpreter dispatches, when it made any.\n */\nfunction toolCallDelegations({\n part,\n state,\n}: {\n part: ToolCallPart;\n state: ActivityToolState;\n}): number | null {\n if (part.toolName === TASK_TOOL_NAME) {\n const withdrawn =\n part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired';\n return state === 'rejected' || withdrawn ? 0 : 1;\n }\n if (part.toolName === EVAL_TOOL_NAME) {\n const dispatches = countInEvalDispatches(part.artifact);\n return dispatches > 0 ? dispatches : null;\n }\n return null;\n}\n\n/** Number of tool calls in the timeline. */\nexport function countActivityTools(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => (item.kind === 'tool' ? count + 1 : count), 0);\n}\n\n/**\n * Subagents the turn delegated to: its `task` calls that ran, plus the\n * `task()` dispatches its `eval` calls made from inside the interpreter. A\n * `task` step built without its count (a host's own timeline) counts unless it\n * reads refused or cancelled.\n */\nexport function countActivityDelegations(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => {\n if (item.kind !== 'tool') return count;\n if (item.delegations !== undefined) return count + item.delegations;\n const ranTask =\n item.toolName === TASK_TOOL_NAME && item.state !== 'rejected' && item.state !== 'cancelled';\n return ranTask ? count + 1 : count;\n }, 0);\n}\n\n/**\n * The turn's phase. Waiting on the person wins over everything — a\n * `requires-action` status, a waiting tool, or a host's own `waitingForInput`\n * assertion (no in-repo host passes one) — then the message's own status. A\n * complete turn with a step still running is running: only a step holding the\n * backend's running placeholder reads so once nothing streams (a run parked on\n * an approval inside a subagent, the gap before a drained run resumes).\n */\nexport function resolveActivityPhase(\n status: MessageStatus | undefined,\n timeline: readonly ActivityItem[],\n { waitingForInput = false }: { waitingForInput?: boolean } = {},\n): ActivityPhase {\n if (\n waitingForInput ||\n status?.type === 'requires-action' ||\n timeline.some((item) => item.kind === 'tool' && item.state === 'waiting')\n ) {\n return 'waiting';\n }\n if (status?.type === 'running') return 'running';\n if (status?.type === 'incomplete') return 'error';\n if (\n status?.type === 'complete' &&\n timeline.some((item) => item.kind === 'tool' && item.state === 'running')\n ) {\n return 'running';\n }\n return 'complete';\n}\n\n/**\n * A settled thought's row: the heading a reasoning summary opens with\n * (\"**Checking the workstation**\" → \"Checking the workstation\"), else the\n * generic wording. The thought itself stays in the run's details.\n */\nexport function getReasoningLabel(text: string, state: 'running' | 'complete'): string {\n if (state === 'running') return 'Thinking';\n const heading = /^\\s*\\*\\*([^*\\n]{3,80}?)\\*\\*/.exec(text)?.[1]?.trim();\n return heading ? heading.replace(/[.:]+$/, '') : 'Thought through the next step';\n}\n\n/**\n * The status-row copy for one step: the call's own summary from the shared\n * summary registry (`summarizeToolCall`: \"Ran git status\", \"Read config.ts\").\n * `resolveToolDisplayName` lets a host with a richer tool catalog name tools\n * neither registry has an entry for; `assetTitles` names the machine or\n * document a call targeted by id when an earlier result in the turn did.\n */\nexport function getActivityItemLabel(\n item: ActivityItem,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): string {\n if (item.kind === 'reasoning') return getReasoningLabel(item.text, item.state);\n return summarizeActivityTool(item, resolveToolDisplayName, assetTitles).label;\n}\n\n/** The summary of one tool step (see {@link getActivityItemLabel}). */\nexport function summarizeActivityTool(\n item: Extract<ActivityItem, { kind: 'tool' }>,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): ToolCallSummary {\n return summarizeToolCall({\n toolName: item.toolName,\n args: item.args,\n result: item.result,\n status: item.state,\n ...(assetTitles ? { assetTitles } : {}),\n ...(resolveToolDisplayName ? { displayName: resolveToolDisplayName(item.toolName) } : {}),\n });\n}\n\n/**\n * The collapsed-card summary of a finished turn. \"Completed with issues\" means\n * the turn ended on a problem: a call the person refused, or a failed call\n * that no later call completed after. A failed command the agent then got past\n * (a search that found nothing, a retry that worked) is ordinary work, so a\n * long turn that recovered from it reads as the tools it used.\n *\n * A turn that delegated work leads with it — \"Delegated to 10 agents · used 6\n * tools\" — counting its `task` calls and the `task()` dispatches its `eval`\n * calls made from inside the interpreter (`countActivityDelegations`). A\n * `task` call is the delegation itself, so the tools it is followed by are the\n * turn's other calls; an outcome above replaces them (\"Delegated to 3 agents ·\n * completed with issues\").\n */\nexport function getActivitySummary(timeline: readonly ActivityItem[]): string {\n const tools = timeline.filter((item): item is ToolActivityItem => item.kind === 'tool');\n const outcome = activityOutcome(tools);\n const delegations = countActivityDelegations(timeline);\n if (delegations > 0) {\n const lead = `Delegated to ${delegations} ${delegations === 1 ? 'agent' : 'agents'}`;\n const otherTools = tools.filter((item) => item.toolName !== TASK_TOOL_NAME).length;\n const rest = outcome\n ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}`\n : otherTools > 0\n ? `used ${otherTools} ${otherTools === 1 ? 'tool' : 'tools'}`\n : null;\n return rest ? `${lead} · ${rest}` : lead;\n }\n if (outcome) return outcome;\n if (tools.length > 0) return `Used ${tools.length} ${tools.length === 1 ? 'tool' : 'tools'}`;\n return 'Reasoning complete';\n}\n\ntype ToolActivityItem = Extract<ActivityItem, { kind: 'tool' }>;\n\n/** How a finished turn's tool calls ended, when that is not simply the tools it used. */\nfunction activityOutcome(tools: readonly ToolActivityItem[]): string | null {\n const lastFailure = tools.findLastIndex((item) => item.state === 'error');\n const endedOnFailure =\n lastFailure >= 0 && !tools.slice(lastFailure + 1).some((item) => item.state === 'complete');\n if (endedOnFailure || tools.some((item) => item.state === 'rejected')) return 'Completed with issues';\n if (tools.some((item) => item.state === 'cancelled')) return 'Action cancelled';\n if (tools.some((item) => item.state === 'paused')) return 'Paused during tool use';\n return null;\n}\n\n/** The part shape the final-text read needs; any message role's parts satisfy it. */\nexport type FinalTextPart = { readonly type: string; readonly text?: string };\n\n/**\n * Whether a text part shows below the card instead of in the details panel:\n * it comes after the final tool call, or it holds a spoken voice turn's words\n * (a live turn heard during a delegated run lands between the run's tool\n * calls, see `isSpokenTurnTextPart`; it is the conversation, not narration).\n */\nfunction isAnswerTextPart(\n part: { readonly type: string } | undefined,\n index: number,\n lastToolIndex: number,\n): boolean {\n return part?.type === 'text' && (index > lastToolIndex || isSpokenTurnTextPart(part));\n}\n\n/**\n * The visible answer of a grouped turn: the text after the final tool call\n * (all of it when the turn never called a tool), plus the words of every\n * spoken voice turn in the message, in order. Other intermediate text (before\n * or between tool calls) stays available in the details panel.\n */\nexport function getFinalText(content: readonly FinalTextPart[] | undefined): string | null {\n const parts = getFinalTextParts(content);\n return parts.length > 0 ? parts.map((part) => part.text).join('\\n\\n') : null;\n}\n\n/** One text part of the visible answer, with its index in the message content. */\nexport interface FinalTextSegment {\n index: number;\n text: string;\n}\n\n/**\n * The parts `getFinalText` joins, each kept separate with its content index.\n * A spoken turn and the run's own reply can stream at the same time, so the\n * earlier part may grow while a later one exists: rendered as one joined\n * string, that text changes in the middle rather than at its end, and the\n * streaming reveal restarts from an empty bubble on every word.\n */\nexport function getFinalTextParts(content: readonly FinalTextPart[] | undefined): FinalTextSegment[] {\n if (!content?.length) return [];\n\n const lastToolIndex = content.findLastIndex((part) => part?.type === 'tool-call');\n const parts: FinalTextSegment[] = [];\n content.forEach((part, index) => {\n if (part.text && isAnswerTextPart(part, index, lastToolIndex)) parts.push({ index, text: part.text });\n });\n return parts;\n}\n\n/**\n * Keep the answer out of the details panel: it renders below the card\n * (`SuperGroupingFinalText`), so the details grouping drops every text part\n * `getFinalText` shows and any group that becomes empty.\n */\nexport function excludeFinalTextFromGroups(grouping: GroupingFunction): GroupingFunction {\n return (parts) => {\n const lastToolIndex = parts.findLastIndex((part) => part?.type === 'tool-call');\n return grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => !isAnswerTextPart(parts[index], index, lastToolIndex)),\n }))\n .filter((group) => group.indices.length > 0);\n };\n}\n\n/**\n * Drop the parts `exclude` matches from every group, and any group left\n * empty: `SuperGroupingCard` renders inline tool calls outside the details,\n * so they must not render a second time inside it.\n */\nexport function excludePartsFromGroups(\n grouping: GroupingFunction,\n exclude: (part: GroupablePart) => boolean,\n): GroupingFunction {\n return (parts) =>\n grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => {\n const part = parts[index];\n return part === undefined || !exclude(part);\n }),\n }))\n .filter((group) => group.indices.length > 0);\n}\n"],"names":["isRunningToolPlaceholderArtifact","readRejectedToolResult","classifyToolResult","TASK_TOOL_NAME","EVAL_TOOL_NAME","countInEvalDispatches","summarizeToolCall","isSpokenTurnTextPart"],"mappings":"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"}
1
+ {"version":3,"file":"activity-timeline.cjs","sources":["../../../src/chat/super-grouping/activity-timeline.ts"],"sourcesContent":["/**\n * The assistant turn's activity timeline — the one derivation of \"what did\n * this turn do, and how did each step end\" behind the collapsed view\n * (previously called the conversational view) on every surface: the SDK's\n * `AthenaChat`, Olympus's Spaces chat, and the Chrome extension. Pure\n * functions over assistant-ui message parts; `SuperGroupingCard` renders them.\n *\n * Step states come from the shared classifier (`classifyToolResult`) plus the\n * approval outcomes a part carries (`approval`, `interrupt`, a rejection\n * result), so a call that reads as failed, paused, or rejected on one surface\n * cannot read as a green check on another. Copy comes from the shared tool\n * summary registry (`summarizeToolCall`), itself built on the display\n * registry.\n */\nimport type {\n MessageStatus,\n ThreadAssistantMessagePart,\n ToolCallMessagePartStatus,\n} from '@assistant-ui/react';\nimport {\n classifyToolResult,\n countInEvalDispatches,\n EVAL_TOOL_NAME,\n isRunningToolPlaceholderArtifact,\n isSpokenTurnTextPart,\n readRejectedToolResult,\n TASK_TOOL_NAME,\n type ToolStatusLabelState,\n} from '@athenaintel/agent-runtime';\nimport { summarizeToolCall, type ToolCallSummary } from '../../tools/tool-summary';\nimport type { GroupablePart, GroupingFunction } from './grouping-function';\n\nexport type ActivityToolState = ToolStatusLabelState;\n\nexport type ActivityItem =\n | {\n kind: 'reasoning';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n text: string;\n state: 'running' | 'complete';\n }\n | {\n kind: 'tool';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n toolName: string;\n args?: ToolCallPart['args'];\n /** The tool's real result; absent while it runs or holds only the streaming placeholder. */\n result?: unknown;\n state: ActivityToolState;\n /**\n * Subagents the call delegated to: for a `task` call one, or none when it\n * was refused, withdrawn or expired at its approval and never ran; for an\n * `eval` call its in-interpreter `task()` dispatches\n * (`countInEvalDispatches`), absent when it made none.\n */\n delegations?: number;\n };\n\n/** The turn as a whole: still working, parked on the person, ended in error, or done. */\nexport type ActivityPhase = 'running' | 'waiting' | 'complete' | 'error';\n\ntype ToolCallPart = Extract<ThreadAssistantMessagePart, { type: 'tool-call' }>;\n\nexport interface ActivityTimelineOptions {\n /**\n * Tools whose unresolved call means the turn is waiting on the person even\n * without an interrupt or approval on the part (the Chrome extension's\n * device plan approval, which the tool UI itself collects).\n */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id, as the host reads them off\n * its pending approval: a gated call, or (in Olympus) a `task` call whose\n * subagent waits on an approval. Any of them still without its real result\n * reads as waiting.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /**\n * Tools whose result is an application payload relayed verbatim (the Chrome\n * extension's page JavaScript), so a `success: false` flag counts only with\n * an error detail — see `classifyToolResult`'s `passThroughPayload`.\n */\n passThroughTools?: ReadonlySet<string>;\n}\n\nconst NO_TOOLS: ReadonlySet<string> = new Set();\nconst NO_TOOL_CALLS: ReadonlySet<string> = new Set();\n\n/**\n * Whether a tool-call part still holds the backend's running-tool placeholder\n * result rather than the tool's real output. Long-running tools (task\n * subagents, studio and Python execution, deep research) append a ToolMessage\n * the moment they start so their card can fill in live; its marker reaches the\n * part's artifact (`isRunningToolPlaceholderArtifact`). The real ToolMessage\n * that replaces it has no such marker, so a defined `result` alone does not\n * mean the tool is done.\n */\nexport function isStreamingToolPlaceholder(part: Pick<ToolCallPart, 'artifact'>): boolean {\n return isRunningToolPlaceholderArtifact(part.artifact);\n}\n\n/** The tool's real result: undefined while it has none or only the streaming placeholder. */\nfunction finalResult(part: ToolCallPart): unknown {\n return isStreamingToolPlaceholder(part) ? undefined : part.result;\n}\n\nfunction toolCallStatus(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n): ToolCallMessagePartStatus | undefined {\n if (finalResult(part) !== undefined) return { type: 'complete' };\n if (messageStatus?.type === 'incomplete') {\n return {\n type: 'incomplete',\n reason: messageStatus.reason === 'tool-calls' ? 'other' : messageStatus.reason,\n error: messageStatus.error,\n };\n }\n if (messageStatus?.type === 'complete') {\n // The placeholder is the backend's word that the call is still in flight.\n // A turn reads `complete` whenever nothing streams (a run parked on an\n // approval inside a subagent, the gap before a drained run resumes), and\n // assistant-ui counts the placeholder as the call's result when it settles\n // that status, so the turn's status cannot finish the call.\n return isStreamingToolPlaceholder(part) ? { type: 'running' } : { type: 'complete' };\n }\n if (messageStatus?.type === 'requires-action') {\n return { type: 'requires-action', reason: messageStatus.reason };\n }\n if (messageStatus?.type === 'running') return { type: 'running' };\n return undefined;\n}\n\n/** How one tool call ended (or is going), reading the approval fields before the result. */\nexport function getActivityToolState(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n {\n waitingTools = NO_TOOLS,\n waitingToolCallIds = NO_TOOL_CALLS,\n passThroughTools = NO_TOOLS,\n }: ActivityTimelineOptions = {},\n): ActivityToolState {\n if (part.approval?.approved === false || readRejectedToolResult(part.result) !== null) {\n return 'rejected';\n }\n if (part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired') {\n return 'cancelled';\n }\n const result = finalResult(part);\n const unresolved = result === undefined;\n const messageIsActive =\n messageStatus === undefined ||\n messageStatus.type === 'running' ||\n messageStatus.type === 'requires-action';\n if (\n unresolved &&\n (part.interrupt?.type === 'human' ||\n (part.approval !== undefined &&\n part.approval.approved === undefined &&\n part.approval.resolution === undefined) ||\n waitingToolCallIds.has(part.toolCallId) ||\n (waitingTools.has(part.toolName) && messageIsActive))\n ) {\n return 'waiting';\n }\n\n const classification = classifyToolResult({\n status: toolCallStatus(part, messageStatus),\n result,\n isError: part.isError,\n passThroughPayload: passThroughTools.has(part.toolName),\n });\n if (classification.state === 'requires-action') return 'waiting';\n return classification.state;\n}\n\n/**\n * The compact reasoning/tool timeline in the order the parts streamed.\n *\n * A reasoning part is still streaming only while it is the final part of a\n * running message — once any later part appears, the thought has settled.\n * Settled reasoning parts with no text are dropped so the list never shows an\n * empty thinking row.\n */\nexport function buildActivityTimeline(\n content: readonly ThreadAssistantMessagePart[] | undefined,\n messageStatus: MessageStatus | undefined,\n options: ActivityTimelineOptions = {},\n): ActivityItem[] {\n if (!content?.length) return [];\n\n const lastPartIndex = content.length - 1;\n const timeline: ActivityItem[] = [];\n for (let index = 0; index < content.length; index += 1) {\n const part = content[index];\n if (part.type === 'tool-call') {\n const result = finalResult(part);\n const state = getActivityToolState(part, messageStatus, options);\n const delegations = toolCallDelegations({ part, state });\n timeline.push({\n kind: 'tool',\n id: part.toolCallId,\n partIndex: index,\n args: part.args,\n ...(result !== undefined ? { result } : {}),\n toolName: part.toolName,\n state,\n ...(delegations !== null ? { delegations } : {}),\n });\n continue;\n }\n if (part.type !== 'reasoning') continue;\n\n const text = typeof part.text === 'string' ? part.text : '';\n const stillStreaming = messageStatus?.type === 'running' && index === lastPartIndex;\n if (!stillStreaming && text.trim() === '') continue;\n timeline.push({\n kind: 'reasoning',\n id: `reasoning-${index}`,\n partIndex: index,\n text,\n state: stillStreaming ? 'running' : 'complete',\n });\n }\n return timeline;\n}\n\n/**\n * Subagents one tool call delegated to (see `ActivityItem.delegations`), or\n * `null` for a call that delegates by nature of neither: a `task` call one,\n * unless the person refused it or its approval was withdrawn or expired (it\n * never ran); an `eval` call its in-interpreter dispatches, when it made any.\n */\nfunction toolCallDelegations({\n part,\n state,\n}: {\n part: ToolCallPart;\n state: ActivityToolState;\n}): number | null {\n if (part.toolName === TASK_TOOL_NAME) {\n const withdrawn =\n part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired';\n return state === 'rejected' || withdrawn ? 0 : 1;\n }\n if (part.toolName === EVAL_TOOL_NAME) {\n const dispatches = countInEvalDispatches(part.artifact);\n return dispatches > 0 ? dispatches : null;\n }\n return null;\n}\n\n/** Number of tool calls in the timeline. */\nexport function countActivityTools(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => (item.kind === 'tool' ? count + 1 : count), 0);\n}\n\n/**\n * Subagents the turn delegated to: its `task` calls that ran, plus the\n * `task()` dispatches its `eval` calls made from inside the interpreter. A\n * `task` step built without its count (a host's own timeline) counts unless it\n * reads refused or cancelled.\n */\nexport function countActivityDelegations(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => {\n if (item.kind !== 'tool') return count;\n if (item.delegations !== undefined) return count + item.delegations;\n const ranTask =\n item.toolName === TASK_TOOL_NAME && item.state !== 'rejected' && item.state !== 'cancelled';\n return ranTask ? count + 1 : count;\n }, 0);\n}\n\n/**\n * The turn's phase. Waiting on the person wins over everything — a\n * `requires-action` status, a waiting tool, or a host's own `waitingForInput`\n * assertion (no in-repo host passes one) — then the message's own status. A\n * complete turn with a step still running is running: only a step holding the\n * backend's running placeholder reads so once nothing streams (a run parked on\n * an approval inside a subagent, the gap before a drained run resumes).\n */\nexport function resolveActivityPhase(\n status: MessageStatus | undefined,\n timeline: readonly ActivityItem[],\n { waitingForInput = false }: { waitingForInput?: boolean } = {},\n): ActivityPhase {\n if (\n waitingForInput ||\n status?.type === 'requires-action' ||\n timeline.some((item) => item.kind === 'tool' && item.state === 'waiting')\n ) {\n return 'waiting';\n }\n if (status?.type === 'running') return 'running';\n if (status?.type === 'incomplete') return 'error';\n if (\n status?.type === 'complete' &&\n timeline.some((item) => item.kind === 'tool' && item.state === 'running')\n ) {\n return 'running';\n }\n return 'complete';\n}\n\n/**\n * A settled thought's row: the heading a reasoning summary opens with\n * (\"**Checking the workstation**\" → \"Checking the workstation\"), else the\n * generic wording. The thought itself stays in the run's details.\n */\nexport function getReasoningLabel(text: string, state: 'running' | 'complete'): string {\n if (state === 'running') return 'Thinking';\n const heading = /^\\s*\\*\\*([^*\\n]{3,80}?)\\*\\*/.exec(text)?.[1]?.trim();\n return heading ? heading.replace(/[.:]+$/, '') : 'Thought through the next step';\n}\n\n/**\n * The status-row copy for one step: the call's own summary from the shared\n * summary registry (`summarizeToolCall`: \"Ran git status\", \"Read config.ts\").\n * `resolveToolDisplayName` lets a host with a richer tool catalog name tools\n * neither registry has an entry for; `assetTitles` names the machine or\n * document a call targeted by id when an earlier result in the turn did.\n */\nexport function getActivityItemLabel(\n item: ActivityItem,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): string {\n if (item.kind === 'reasoning') return getReasoningLabel(item.text, item.state);\n return summarizeActivityTool(item, resolveToolDisplayName, assetTitles).label;\n}\n\n/** The summary of one tool step (see {@link getActivityItemLabel}). */\nexport function summarizeActivityTool(\n item: Extract<ActivityItem, { kind: 'tool' }>,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): ToolCallSummary {\n return summarizeToolCall({\n toolName: item.toolName,\n args: item.args,\n result: item.result,\n status: item.state,\n ...(assetTitles ? { assetTitles } : {}),\n ...(resolveToolDisplayName ? { displayName: resolveToolDisplayName(item.toolName) } : {}),\n });\n}\n\n/**\n * The collapsed-card summary of a finished turn. \"Completed with issues\" means\n * the turn ended on a problem: a call the person refused, or a failed call\n * that no later call completed after. A failed command the agent then got past\n * (a search that found nothing, a retry that worked) is ordinary work, so a\n * long turn that recovered from it reads as the tools it used.\n *\n * A turn that delegated work leads with it — \"Delegated to 10 agents · used 6\n * tools\" — counting its `task` calls and the `task()` dispatches its `eval`\n * calls made from inside the interpreter (`countActivityDelegations`). A\n * `task` call is the delegation itself, so the tools it is followed by are the\n * turn's other calls; an outcome above replaces them (\"Delegated to 3 agents ·\n * completed with issues\").\n */\nexport function getActivitySummary(\n timeline: readonly ActivityItem[],\n { earlierStepsUnloaded = false }: ActivitySummaryOptions = {},\n): string {\n const tools = timeline.filter((item): item is ToolActivityItem => item.kind === 'tool');\n const outcome = activityOutcome(tools);\n const delegations = countActivityDelegations(timeline);\n // A turn whose earlier steps have not loaded counts what has: a floor.\n const more = earlierStepsUnloaded ? '+' : '';\n if (delegations > 0) {\n const lead = `Delegated to ${delegations}${more} ${delegations === 1 && !more ? 'agent' : 'agents'}`;\n const otherTools = tools.filter((item) => item.toolName !== TASK_TOOL_NAME).length;\n const rest = outcome\n ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}`\n : otherTools > 0\n ? `used ${otherTools}${more} ${otherTools === 1 && !more ? 'tool' : 'tools'}`\n : null;\n return rest ? `${lead} · ${rest}` : lead;\n }\n if (outcome) return outcome;\n if (tools.length > 0) return `Used ${tools.length}${more} ${tools.length === 1 && !more ? 'tool' : 'tools'}`;\n if (earlierStepsUnloaded) return 'Earlier steps not loaded';\n return 'Reasoning complete';\n}\n\n/** How {@link getActivitySummary} reads a turn that may not be loaded whole. */\nexport interface ActivitySummaryOptions {\n /**\n * The turn's earlier steps have not loaded yet (older history above it, see\n * `useEarlierStepsUnloaded`): its counts read as floors, \"Used 43+ tools\".\n */\n earlierStepsUnloaded?: boolean;\n}\n\ntype ToolActivityItem = Extract<ActivityItem, { kind: 'tool' }>;\n\n/** How a finished turn's tool calls ended, when that is not simply the tools it used. */\nfunction activityOutcome(tools: readonly ToolActivityItem[]): string | null {\n const lastFailure = tools.findLastIndex((item) => item.state === 'error');\n const endedOnFailure =\n lastFailure >= 0 && !tools.slice(lastFailure + 1).some((item) => item.state === 'complete');\n if (endedOnFailure || tools.some((item) => item.state === 'rejected')) return 'Completed with issues';\n if (tools.some((item) => item.state === 'cancelled')) return 'Action cancelled';\n if (tools.some((item) => item.state === 'paused')) return 'Paused during tool use';\n return null;\n}\n\n/** The part shape the final-text read needs; any message role's parts satisfy it. */\nexport type FinalTextPart = { readonly type: string; readonly text?: string };\n\n/**\n * Whether a text part shows below the card instead of in the details panel:\n * it comes after the final tool call, or it holds a spoken voice turn's words\n * (a live turn heard during a delegated run lands between the run's tool\n * calls, see `isSpokenTurnTextPart`; it is the conversation, not narration).\n */\nfunction isAnswerTextPart(\n part: { readonly type: string } | undefined,\n index: number,\n lastToolIndex: number,\n): boolean {\n return part?.type === 'text' && (index > lastToolIndex || isSpokenTurnTextPart(part));\n}\n\n/**\n * The visible answer of a grouped turn: the text after the final tool call\n * (all of it when the turn never called a tool), plus the words of every\n * spoken voice turn in the message, in order. Other intermediate text (before\n * or between tool calls) stays available in the details panel.\n */\nexport function getFinalText(content: readonly FinalTextPart[] | undefined): string | null {\n const parts = getFinalTextParts(content);\n return parts.length > 0 ? parts.map((part) => part.text).join('\\n\\n') : null;\n}\n\n/** One text part of the visible answer, with its index in the message content. */\nexport interface FinalTextSegment {\n index: number;\n text: string;\n}\n\n/**\n * The parts `getFinalText` joins, each kept separate with its content index.\n * A spoken turn and the run's own reply can stream at the same time, so the\n * earlier part may grow while a later one exists: rendered as one joined\n * string, that text changes in the middle rather than at its end, and the\n * streaming reveal restarts from an empty bubble on every word.\n */\nexport function getFinalTextParts(content: readonly FinalTextPart[] | undefined): FinalTextSegment[] {\n if (!content?.length) return [];\n\n const lastToolIndex = content.findLastIndex((part) => part?.type === 'tool-call');\n const parts: FinalTextSegment[] = [];\n content.forEach((part, index) => {\n if (part.text && isAnswerTextPart(part, index, lastToolIndex)) parts.push({ index, text: part.text });\n });\n return parts;\n}\n\n/**\n * Keep the answer out of the details panel: it renders below the card\n * (`SuperGroupingFinalText`), so the details grouping drops every text part\n * `getFinalText` shows and any group that becomes empty.\n */\nexport function excludeFinalTextFromGroups(grouping: GroupingFunction): GroupingFunction {\n return (parts) => {\n const lastToolIndex = parts.findLastIndex((part) => part?.type === 'tool-call');\n return grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => !isAnswerTextPart(parts[index], index, lastToolIndex)),\n }))\n .filter((group) => group.indices.length > 0);\n };\n}\n\n/**\n * Drop the parts `exclude` matches from every group, and any group left\n * empty: `SuperGroupingCard` renders inline tool calls outside the details,\n * so they must not render a second time inside it.\n */\nexport function excludePartsFromGroups(\n grouping: GroupingFunction,\n exclude: (part: GroupablePart) => boolean,\n): GroupingFunction {\n return (parts) =>\n grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => {\n const part = parts[index];\n return part === undefined || !exclude(part);\n }),\n }))\n .filter((group) => group.indices.length > 0);\n}\n"],"names":["isRunningToolPlaceholderArtifact","readRejectedToolResult","classifyToolResult","TASK_TOOL_NAME","EVAL_TOOL_NAME","countInEvalDispatches","summarizeToolCall","isSpokenTurnTextPart"],"mappings":"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"}
@@ -141,7 +141,15 @@ export declare function summarizeActivityTool(item: Extract<ActivityItem, {
141
141
  * turn's other calls; an outcome above replaces them ("Delegated to 3 agents ·
142
142
  * completed with issues").
143
143
  */
144
- export declare function getActivitySummary(timeline: readonly ActivityItem[]): string;
144
+ export declare function getActivitySummary(timeline: readonly ActivityItem[], { earlierStepsUnloaded }?: ActivitySummaryOptions): string;
145
+ /** How {@link getActivitySummary} reads a turn that may not be loaded whole. */
146
+ export interface ActivitySummaryOptions {
147
+ /**
148
+ * The turn's earlier steps have not loaded yet (older history above it, see
149
+ * `useEarlierStepsUnloaded`): its counts read as floors, "Used 43+ tools".
150
+ */
151
+ earlierStepsUnloaded?: boolean;
152
+ }
145
153
  /** The part shape the final-text read needs; any message role's parts satisfy it. */
146
154
  export type FinalTextPart = {
147
155
  readonly type: string;
@@ -150,18 +150,20 @@ function summarizeActivityTool(item, resolveToolDisplayName, assetTitles) {
150
150
  ...resolveToolDisplayName ? { displayName: resolveToolDisplayName(item.toolName) } : {}
151
151
  });
152
152
  }
153
- function getActivitySummary(timeline) {
153
+ function getActivitySummary(timeline, { earlierStepsUnloaded = false } = {}) {
154
154
  const tools = timeline.filter((item) => item.kind === "tool");
155
155
  const outcome = activityOutcome(tools);
156
156
  const delegations = countActivityDelegations(timeline);
157
+ const more = earlierStepsUnloaded ? "+" : "";
157
158
  if (delegations > 0) {
158
- const lead = `Delegated to ${delegations} ${delegations === 1 ? "agent" : "agents"}`;
159
+ const lead = `Delegated to ${delegations}${more} ${delegations === 1 && !more ? "agent" : "agents"}`;
159
160
  const otherTools = tools.filter((item) => item.toolName !== TASK_TOOL_NAME).length;
160
- const rest = outcome ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}` : otherTools > 0 ? `used ${otherTools} ${otherTools === 1 ? "tool" : "tools"}` : null;
161
+ const rest = outcome ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}` : otherTools > 0 ? `used ${otherTools}${more} ${otherTools === 1 && !more ? "tool" : "tools"}` : null;
161
162
  return rest ? `${lead} · ${rest}` : lead;
162
163
  }
163
164
  if (outcome) return outcome;
164
- if (tools.length > 0) return `Used ${tools.length} ${tools.length === 1 ? "tool" : "tools"}`;
165
+ if (tools.length > 0) return `Used ${tools.length}${more} ${tools.length === 1 && !more ? "tool" : "tools"}`;
166
+ if (earlierStepsUnloaded) return "Earlier steps not loaded";
165
167
  return "Reasoning complete";
166
168
  }
167
169
  function activityOutcome(tools) {
@@ -1 +1 @@
1
- {"version":3,"file":"activity-timeline.js","sources":["../../../src/chat/super-grouping/activity-timeline.ts"],"sourcesContent":["/**\n * The assistant turn's activity timeline — the one derivation of \"what did\n * this turn do, and how did each step end\" behind the collapsed view\n * (previously called the conversational view) on every surface: the SDK's\n * `AthenaChat`, Olympus's Spaces chat, and the Chrome extension. Pure\n * functions over assistant-ui message parts; `SuperGroupingCard` renders them.\n *\n * Step states come from the shared classifier (`classifyToolResult`) plus the\n * approval outcomes a part carries (`approval`, `interrupt`, a rejection\n * result), so a call that reads as failed, paused, or rejected on one surface\n * cannot read as a green check on another. Copy comes from the shared tool\n * summary registry (`summarizeToolCall`), itself built on the display\n * registry.\n */\nimport type {\n MessageStatus,\n ThreadAssistantMessagePart,\n ToolCallMessagePartStatus,\n} from '@assistant-ui/react';\nimport {\n classifyToolResult,\n countInEvalDispatches,\n EVAL_TOOL_NAME,\n isRunningToolPlaceholderArtifact,\n isSpokenTurnTextPart,\n readRejectedToolResult,\n TASK_TOOL_NAME,\n type ToolStatusLabelState,\n} from '@athenaintel/agent-runtime';\nimport { summarizeToolCall, type ToolCallSummary } from '../../tools/tool-summary';\nimport type { GroupablePart, GroupingFunction } from './grouping-function';\n\nexport type ActivityToolState = ToolStatusLabelState;\n\nexport type ActivityItem =\n | {\n kind: 'reasoning';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n text: string;\n state: 'running' | 'complete';\n }\n | {\n kind: 'tool';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n toolName: string;\n args?: ToolCallPart['args'];\n /** The tool's real result; absent while it runs or holds only the streaming placeholder. */\n result?: unknown;\n state: ActivityToolState;\n /**\n * Subagents the call delegated to: for a `task` call one, or none when it\n * was refused, withdrawn or expired at its approval and never ran; for an\n * `eval` call its in-interpreter `task()` dispatches\n * (`countInEvalDispatches`), absent when it made none.\n */\n delegations?: number;\n };\n\n/** The turn as a whole: still working, parked on the person, ended in error, or done. */\nexport type ActivityPhase = 'running' | 'waiting' | 'complete' | 'error';\n\ntype ToolCallPart = Extract<ThreadAssistantMessagePart, { type: 'tool-call' }>;\n\nexport interface ActivityTimelineOptions {\n /**\n * Tools whose unresolved call means the turn is waiting on the person even\n * without an interrupt or approval on the part (the Chrome extension's\n * device plan approval, which the tool UI itself collects).\n */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id, as the host reads them off\n * its pending approval: a gated call, or (in Olympus) a `task` call whose\n * subagent waits on an approval. Any of them still without its real result\n * reads as waiting.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /**\n * Tools whose result is an application payload relayed verbatim (the Chrome\n * extension's page JavaScript), so a `success: false` flag counts only with\n * an error detail — see `classifyToolResult`'s `passThroughPayload`.\n */\n passThroughTools?: ReadonlySet<string>;\n}\n\nconst NO_TOOLS: ReadonlySet<string> = new Set();\nconst NO_TOOL_CALLS: ReadonlySet<string> = new Set();\n\n/**\n * Whether a tool-call part still holds the backend's running-tool placeholder\n * result rather than the tool's real output. Long-running tools (task\n * subagents, studio and Python execution, deep research) append a ToolMessage\n * the moment they start so their card can fill in live; its marker reaches the\n * part's artifact (`isRunningToolPlaceholderArtifact`). The real ToolMessage\n * that replaces it has no such marker, so a defined `result` alone does not\n * mean the tool is done.\n */\nexport function isStreamingToolPlaceholder(part: Pick<ToolCallPart, 'artifact'>): boolean {\n return isRunningToolPlaceholderArtifact(part.artifact);\n}\n\n/** The tool's real result: undefined while it has none or only the streaming placeholder. */\nfunction finalResult(part: ToolCallPart): unknown {\n return isStreamingToolPlaceholder(part) ? undefined : part.result;\n}\n\nfunction toolCallStatus(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n): ToolCallMessagePartStatus | undefined {\n if (finalResult(part) !== undefined) return { type: 'complete' };\n if (messageStatus?.type === 'incomplete') {\n return {\n type: 'incomplete',\n reason: messageStatus.reason === 'tool-calls' ? 'other' : messageStatus.reason,\n error: messageStatus.error,\n };\n }\n if (messageStatus?.type === 'complete') {\n // The placeholder is the backend's word that the call is still in flight.\n // A turn reads `complete` whenever nothing streams (a run parked on an\n // approval inside a subagent, the gap before a drained run resumes), and\n // assistant-ui counts the placeholder as the call's result when it settles\n // that status, so the turn's status cannot finish the call.\n return isStreamingToolPlaceholder(part) ? { type: 'running' } : { type: 'complete' };\n }\n if (messageStatus?.type === 'requires-action') {\n return { type: 'requires-action', reason: messageStatus.reason };\n }\n if (messageStatus?.type === 'running') return { type: 'running' };\n return undefined;\n}\n\n/** How one tool call ended (or is going), reading the approval fields before the result. */\nexport function getActivityToolState(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n {\n waitingTools = NO_TOOLS,\n waitingToolCallIds = NO_TOOL_CALLS,\n passThroughTools = NO_TOOLS,\n }: ActivityTimelineOptions = {},\n): ActivityToolState {\n if (part.approval?.approved === false || readRejectedToolResult(part.result) !== null) {\n return 'rejected';\n }\n if (part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired') {\n return 'cancelled';\n }\n const result = finalResult(part);\n const unresolved = result === undefined;\n const messageIsActive =\n messageStatus === undefined ||\n messageStatus.type === 'running' ||\n messageStatus.type === 'requires-action';\n if (\n unresolved &&\n (part.interrupt?.type === 'human' ||\n (part.approval !== undefined &&\n part.approval.approved === undefined &&\n part.approval.resolution === undefined) ||\n waitingToolCallIds.has(part.toolCallId) ||\n (waitingTools.has(part.toolName) && messageIsActive))\n ) {\n return 'waiting';\n }\n\n const classification = classifyToolResult({\n status: toolCallStatus(part, messageStatus),\n result,\n isError: part.isError,\n passThroughPayload: passThroughTools.has(part.toolName),\n });\n if (classification.state === 'requires-action') return 'waiting';\n return classification.state;\n}\n\n/**\n * The compact reasoning/tool timeline in the order the parts streamed.\n *\n * A reasoning part is still streaming only while it is the final part of a\n * running message — once any later part appears, the thought has settled.\n * Settled reasoning parts with no text are dropped so the list never shows an\n * empty thinking row.\n */\nexport function buildActivityTimeline(\n content: readonly ThreadAssistantMessagePart[] | undefined,\n messageStatus: MessageStatus | undefined,\n options: ActivityTimelineOptions = {},\n): ActivityItem[] {\n if (!content?.length) return [];\n\n const lastPartIndex = content.length - 1;\n const timeline: ActivityItem[] = [];\n for (let index = 0; index < content.length; index += 1) {\n const part = content[index];\n if (part.type === 'tool-call') {\n const result = finalResult(part);\n const state = getActivityToolState(part, messageStatus, options);\n const delegations = toolCallDelegations({ part, state });\n timeline.push({\n kind: 'tool',\n id: part.toolCallId,\n partIndex: index,\n args: part.args,\n ...(result !== undefined ? { result } : {}),\n toolName: part.toolName,\n state,\n ...(delegations !== null ? { delegations } : {}),\n });\n continue;\n }\n if (part.type !== 'reasoning') continue;\n\n const text = typeof part.text === 'string' ? part.text : '';\n const stillStreaming = messageStatus?.type === 'running' && index === lastPartIndex;\n if (!stillStreaming && text.trim() === '') continue;\n timeline.push({\n kind: 'reasoning',\n id: `reasoning-${index}`,\n partIndex: index,\n text,\n state: stillStreaming ? 'running' : 'complete',\n });\n }\n return timeline;\n}\n\n/**\n * Subagents one tool call delegated to (see `ActivityItem.delegations`), or\n * `null` for a call that delegates by nature of neither: a `task` call one,\n * unless the person refused it or its approval was withdrawn or expired (it\n * never ran); an `eval` call its in-interpreter dispatches, when it made any.\n */\nfunction toolCallDelegations({\n part,\n state,\n}: {\n part: ToolCallPart;\n state: ActivityToolState;\n}): number | null {\n if (part.toolName === TASK_TOOL_NAME) {\n const withdrawn =\n part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired';\n return state === 'rejected' || withdrawn ? 0 : 1;\n }\n if (part.toolName === EVAL_TOOL_NAME) {\n const dispatches = countInEvalDispatches(part.artifact);\n return dispatches > 0 ? dispatches : null;\n }\n return null;\n}\n\n/** Number of tool calls in the timeline. */\nexport function countActivityTools(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => (item.kind === 'tool' ? count + 1 : count), 0);\n}\n\n/**\n * Subagents the turn delegated to: its `task` calls that ran, plus the\n * `task()` dispatches its `eval` calls made from inside the interpreter. A\n * `task` step built without its count (a host's own timeline) counts unless it\n * reads refused or cancelled.\n */\nexport function countActivityDelegations(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => {\n if (item.kind !== 'tool') return count;\n if (item.delegations !== undefined) return count + item.delegations;\n const ranTask =\n item.toolName === TASK_TOOL_NAME && item.state !== 'rejected' && item.state !== 'cancelled';\n return ranTask ? count + 1 : count;\n }, 0);\n}\n\n/**\n * The turn's phase. Waiting on the person wins over everything — a\n * `requires-action` status, a waiting tool, or a host's own `waitingForInput`\n * assertion (no in-repo host passes one) — then the message's own status. A\n * complete turn with a step still running is running: only a step holding the\n * backend's running placeholder reads so once nothing streams (a run parked on\n * an approval inside a subagent, the gap before a drained run resumes).\n */\nexport function resolveActivityPhase(\n status: MessageStatus | undefined,\n timeline: readonly ActivityItem[],\n { waitingForInput = false }: { waitingForInput?: boolean } = {},\n): ActivityPhase {\n if (\n waitingForInput ||\n status?.type === 'requires-action' ||\n timeline.some((item) => item.kind === 'tool' && item.state === 'waiting')\n ) {\n return 'waiting';\n }\n if (status?.type === 'running') return 'running';\n if (status?.type === 'incomplete') return 'error';\n if (\n status?.type === 'complete' &&\n timeline.some((item) => item.kind === 'tool' && item.state === 'running')\n ) {\n return 'running';\n }\n return 'complete';\n}\n\n/**\n * A settled thought's row: the heading a reasoning summary opens with\n * (\"**Checking the workstation**\" → \"Checking the workstation\"), else the\n * generic wording. The thought itself stays in the run's details.\n */\nexport function getReasoningLabel(text: string, state: 'running' | 'complete'): string {\n if (state === 'running') return 'Thinking';\n const heading = /^\\s*\\*\\*([^*\\n]{3,80}?)\\*\\*/.exec(text)?.[1]?.trim();\n return heading ? heading.replace(/[.:]+$/, '') : 'Thought through the next step';\n}\n\n/**\n * The status-row copy for one step: the call's own summary from the shared\n * summary registry (`summarizeToolCall`: \"Ran git status\", \"Read config.ts\").\n * `resolveToolDisplayName` lets a host with a richer tool catalog name tools\n * neither registry has an entry for; `assetTitles` names the machine or\n * document a call targeted by id when an earlier result in the turn did.\n */\nexport function getActivityItemLabel(\n item: ActivityItem,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): string {\n if (item.kind === 'reasoning') return getReasoningLabel(item.text, item.state);\n return summarizeActivityTool(item, resolveToolDisplayName, assetTitles).label;\n}\n\n/** The summary of one tool step (see {@link getActivityItemLabel}). */\nexport function summarizeActivityTool(\n item: Extract<ActivityItem, { kind: 'tool' }>,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): ToolCallSummary {\n return summarizeToolCall({\n toolName: item.toolName,\n args: item.args,\n result: item.result,\n status: item.state,\n ...(assetTitles ? { assetTitles } : {}),\n ...(resolveToolDisplayName ? { displayName: resolveToolDisplayName(item.toolName) } : {}),\n });\n}\n\n/**\n * The collapsed-card summary of a finished turn. \"Completed with issues\" means\n * the turn ended on a problem: a call the person refused, or a failed call\n * that no later call completed after. A failed command the agent then got past\n * (a search that found nothing, a retry that worked) is ordinary work, so a\n * long turn that recovered from it reads as the tools it used.\n *\n * A turn that delegated work leads with it — \"Delegated to 10 agents · used 6\n * tools\" — counting its `task` calls and the `task()` dispatches its `eval`\n * calls made from inside the interpreter (`countActivityDelegations`). A\n * `task` call is the delegation itself, so the tools it is followed by are the\n * turn's other calls; an outcome above replaces them (\"Delegated to 3 agents ·\n * completed with issues\").\n */\nexport function getActivitySummary(timeline: readonly ActivityItem[]): string {\n const tools = timeline.filter((item): item is ToolActivityItem => item.kind === 'tool');\n const outcome = activityOutcome(tools);\n const delegations = countActivityDelegations(timeline);\n if (delegations > 0) {\n const lead = `Delegated to ${delegations} ${delegations === 1 ? 'agent' : 'agents'}`;\n const otherTools = tools.filter((item) => item.toolName !== TASK_TOOL_NAME).length;\n const rest = outcome\n ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}`\n : otherTools > 0\n ? `used ${otherTools} ${otherTools === 1 ? 'tool' : 'tools'}`\n : null;\n return rest ? `${lead} · ${rest}` : lead;\n }\n if (outcome) return outcome;\n if (tools.length > 0) return `Used ${tools.length} ${tools.length === 1 ? 'tool' : 'tools'}`;\n return 'Reasoning complete';\n}\n\ntype ToolActivityItem = Extract<ActivityItem, { kind: 'tool' }>;\n\n/** How a finished turn's tool calls ended, when that is not simply the tools it used. */\nfunction activityOutcome(tools: readonly ToolActivityItem[]): string | null {\n const lastFailure = tools.findLastIndex((item) => item.state === 'error');\n const endedOnFailure =\n lastFailure >= 0 && !tools.slice(lastFailure + 1).some((item) => item.state === 'complete');\n if (endedOnFailure || tools.some((item) => item.state === 'rejected')) return 'Completed with issues';\n if (tools.some((item) => item.state === 'cancelled')) return 'Action cancelled';\n if (tools.some((item) => item.state === 'paused')) return 'Paused during tool use';\n return null;\n}\n\n/** The part shape the final-text read needs; any message role's parts satisfy it. */\nexport type FinalTextPart = { readonly type: string; readonly text?: string };\n\n/**\n * Whether a text part shows below the card instead of in the details panel:\n * it comes after the final tool call, or it holds a spoken voice turn's words\n * (a live turn heard during a delegated run lands between the run's tool\n * calls, see `isSpokenTurnTextPart`; it is the conversation, not narration).\n */\nfunction isAnswerTextPart(\n part: { readonly type: string } | undefined,\n index: number,\n lastToolIndex: number,\n): boolean {\n return part?.type === 'text' && (index > lastToolIndex || isSpokenTurnTextPart(part));\n}\n\n/**\n * The visible answer of a grouped turn: the text after the final tool call\n * (all of it when the turn never called a tool), plus the words of every\n * spoken voice turn in the message, in order. Other intermediate text (before\n * or between tool calls) stays available in the details panel.\n */\nexport function getFinalText(content: readonly FinalTextPart[] | undefined): string | null {\n const parts = getFinalTextParts(content);\n return parts.length > 0 ? parts.map((part) => part.text).join('\\n\\n') : null;\n}\n\n/** One text part of the visible answer, with its index in the message content. */\nexport interface FinalTextSegment {\n index: number;\n text: string;\n}\n\n/**\n * The parts `getFinalText` joins, each kept separate with its content index.\n * A spoken turn and the run's own reply can stream at the same time, so the\n * earlier part may grow while a later one exists: rendered as one joined\n * string, that text changes in the middle rather than at its end, and the\n * streaming reveal restarts from an empty bubble on every word.\n */\nexport function getFinalTextParts(content: readonly FinalTextPart[] | undefined): FinalTextSegment[] {\n if (!content?.length) return [];\n\n const lastToolIndex = content.findLastIndex((part) => part?.type === 'tool-call');\n const parts: FinalTextSegment[] = [];\n content.forEach((part, index) => {\n if (part.text && isAnswerTextPart(part, index, lastToolIndex)) parts.push({ index, text: part.text });\n });\n return parts;\n}\n\n/**\n * Keep the answer out of the details panel: it renders below the card\n * (`SuperGroupingFinalText`), so the details grouping drops every text part\n * `getFinalText` shows and any group that becomes empty.\n */\nexport function excludeFinalTextFromGroups(grouping: GroupingFunction): GroupingFunction {\n return (parts) => {\n const lastToolIndex = parts.findLastIndex((part) => part?.type === 'tool-call');\n return grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => !isAnswerTextPart(parts[index], index, lastToolIndex)),\n }))\n .filter((group) => group.indices.length > 0);\n };\n}\n\n/**\n * Drop the parts `exclude` matches from every group, and any group left\n * empty: `SuperGroupingCard` renders inline tool calls outside the details,\n * so they must not render a second time inside it.\n */\nexport function excludePartsFromGroups(\n grouping: GroupingFunction,\n exclude: (part: GroupablePart) => boolean,\n): GroupingFunction {\n return (parts) =>\n grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => {\n const part = parts[index];\n return part === undefined || !exclude(part);\n }),\n }))\n .filter((group) => group.indices.length > 0);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"activity-timeline.js","sources":["../../../src/chat/super-grouping/activity-timeline.ts"],"sourcesContent":["/**\n * The assistant turn's activity timeline — the one derivation of \"what did\n * this turn do, and how did each step end\" behind the collapsed view\n * (previously called the conversational view) on every surface: the SDK's\n * `AthenaChat`, Olympus's Spaces chat, and the Chrome extension. Pure\n * functions over assistant-ui message parts; `SuperGroupingCard` renders them.\n *\n * Step states come from the shared classifier (`classifyToolResult`) plus the\n * approval outcomes a part carries (`approval`, `interrupt`, a rejection\n * result), so a call that reads as failed, paused, or rejected on one surface\n * cannot read as a green check on another. Copy comes from the shared tool\n * summary registry (`summarizeToolCall`), itself built on the display\n * registry.\n */\nimport type {\n MessageStatus,\n ThreadAssistantMessagePart,\n ToolCallMessagePartStatus,\n} from '@assistant-ui/react';\nimport {\n classifyToolResult,\n countInEvalDispatches,\n EVAL_TOOL_NAME,\n isRunningToolPlaceholderArtifact,\n isSpokenTurnTextPart,\n readRejectedToolResult,\n TASK_TOOL_NAME,\n type ToolStatusLabelState,\n} from '@athenaintel/agent-runtime';\nimport { summarizeToolCall, type ToolCallSummary } from '../../tools/tool-summary';\nimport type { GroupablePart, GroupingFunction } from './grouping-function';\n\nexport type ActivityToolState = ToolStatusLabelState;\n\nexport type ActivityItem =\n | {\n kind: 'reasoning';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n text: string;\n state: 'running' | 'complete';\n }\n | {\n kind: 'tool';\n id?: string;\n /** Position of the part in the message's content. */\n partIndex?: number;\n toolName: string;\n args?: ToolCallPart['args'];\n /** The tool's real result; absent while it runs or holds only the streaming placeholder. */\n result?: unknown;\n state: ActivityToolState;\n /**\n * Subagents the call delegated to: for a `task` call one, or none when it\n * was refused, withdrawn or expired at its approval and never ran; for an\n * `eval` call its in-interpreter `task()` dispatches\n * (`countInEvalDispatches`), absent when it made none.\n */\n delegations?: number;\n };\n\n/** The turn as a whole: still working, parked on the person, ended in error, or done. */\nexport type ActivityPhase = 'running' | 'waiting' | 'complete' | 'error';\n\ntype ToolCallPart = Extract<ThreadAssistantMessagePart, { type: 'tool-call' }>;\n\nexport interface ActivityTimelineOptions {\n /**\n * Tools whose unresolved call means the turn is waiting on the person even\n * without an interrupt or approval on the part (the Chrome extension's\n * device plan approval, which the tool UI itself collects).\n */\n waitingTools?: ReadonlySet<string>;\n /**\n * Calls the run is parked on, by tool-call id, as the host reads them off\n * its pending approval: a gated call, or (in Olympus) a `task` call whose\n * subagent waits on an approval. Any of them still without its real result\n * reads as waiting.\n */\n waitingToolCallIds?: ReadonlySet<string>;\n /**\n * Tools whose result is an application payload relayed verbatim (the Chrome\n * extension's page JavaScript), so a `success: false` flag counts only with\n * an error detail — see `classifyToolResult`'s `passThroughPayload`.\n */\n passThroughTools?: ReadonlySet<string>;\n}\n\nconst NO_TOOLS: ReadonlySet<string> = new Set();\nconst NO_TOOL_CALLS: ReadonlySet<string> = new Set();\n\n/**\n * Whether a tool-call part still holds the backend's running-tool placeholder\n * result rather than the tool's real output. Long-running tools (task\n * subagents, studio and Python execution, deep research) append a ToolMessage\n * the moment they start so their card can fill in live; its marker reaches the\n * part's artifact (`isRunningToolPlaceholderArtifact`). The real ToolMessage\n * that replaces it has no such marker, so a defined `result` alone does not\n * mean the tool is done.\n */\nexport function isStreamingToolPlaceholder(part: Pick<ToolCallPart, 'artifact'>): boolean {\n return isRunningToolPlaceholderArtifact(part.artifact);\n}\n\n/** The tool's real result: undefined while it has none or only the streaming placeholder. */\nfunction finalResult(part: ToolCallPart): unknown {\n return isStreamingToolPlaceholder(part) ? undefined : part.result;\n}\n\nfunction toolCallStatus(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n): ToolCallMessagePartStatus | undefined {\n if (finalResult(part) !== undefined) return { type: 'complete' };\n if (messageStatus?.type === 'incomplete') {\n return {\n type: 'incomplete',\n reason: messageStatus.reason === 'tool-calls' ? 'other' : messageStatus.reason,\n error: messageStatus.error,\n };\n }\n if (messageStatus?.type === 'complete') {\n // The placeholder is the backend's word that the call is still in flight.\n // A turn reads `complete` whenever nothing streams (a run parked on an\n // approval inside a subagent, the gap before a drained run resumes), and\n // assistant-ui counts the placeholder as the call's result when it settles\n // that status, so the turn's status cannot finish the call.\n return isStreamingToolPlaceholder(part) ? { type: 'running' } : { type: 'complete' };\n }\n if (messageStatus?.type === 'requires-action') {\n return { type: 'requires-action', reason: messageStatus.reason };\n }\n if (messageStatus?.type === 'running') return { type: 'running' };\n return undefined;\n}\n\n/** How one tool call ended (or is going), reading the approval fields before the result. */\nexport function getActivityToolState(\n part: ToolCallPart,\n messageStatus: MessageStatus | undefined,\n {\n waitingTools = NO_TOOLS,\n waitingToolCallIds = NO_TOOL_CALLS,\n passThroughTools = NO_TOOLS,\n }: ActivityTimelineOptions = {},\n): ActivityToolState {\n if (part.approval?.approved === false || readRejectedToolResult(part.result) !== null) {\n return 'rejected';\n }\n if (part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired') {\n return 'cancelled';\n }\n const result = finalResult(part);\n const unresolved = result === undefined;\n const messageIsActive =\n messageStatus === undefined ||\n messageStatus.type === 'running' ||\n messageStatus.type === 'requires-action';\n if (\n unresolved &&\n (part.interrupt?.type === 'human' ||\n (part.approval !== undefined &&\n part.approval.approved === undefined &&\n part.approval.resolution === undefined) ||\n waitingToolCallIds.has(part.toolCallId) ||\n (waitingTools.has(part.toolName) && messageIsActive))\n ) {\n return 'waiting';\n }\n\n const classification = classifyToolResult({\n status: toolCallStatus(part, messageStatus),\n result,\n isError: part.isError,\n passThroughPayload: passThroughTools.has(part.toolName),\n });\n if (classification.state === 'requires-action') return 'waiting';\n return classification.state;\n}\n\n/**\n * The compact reasoning/tool timeline in the order the parts streamed.\n *\n * A reasoning part is still streaming only while it is the final part of a\n * running message — once any later part appears, the thought has settled.\n * Settled reasoning parts with no text are dropped so the list never shows an\n * empty thinking row.\n */\nexport function buildActivityTimeline(\n content: readonly ThreadAssistantMessagePart[] | undefined,\n messageStatus: MessageStatus | undefined,\n options: ActivityTimelineOptions = {},\n): ActivityItem[] {\n if (!content?.length) return [];\n\n const lastPartIndex = content.length - 1;\n const timeline: ActivityItem[] = [];\n for (let index = 0; index < content.length; index += 1) {\n const part = content[index];\n if (part.type === 'tool-call') {\n const result = finalResult(part);\n const state = getActivityToolState(part, messageStatus, options);\n const delegations = toolCallDelegations({ part, state });\n timeline.push({\n kind: 'tool',\n id: part.toolCallId,\n partIndex: index,\n args: part.args,\n ...(result !== undefined ? { result } : {}),\n toolName: part.toolName,\n state,\n ...(delegations !== null ? { delegations } : {}),\n });\n continue;\n }\n if (part.type !== 'reasoning') continue;\n\n const text = typeof part.text === 'string' ? part.text : '';\n const stillStreaming = messageStatus?.type === 'running' && index === lastPartIndex;\n if (!stillStreaming && text.trim() === '') continue;\n timeline.push({\n kind: 'reasoning',\n id: `reasoning-${index}`,\n partIndex: index,\n text,\n state: stillStreaming ? 'running' : 'complete',\n });\n }\n return timeline;\n}\n\n/**\n * Subagents one tool call delegated to (see `ActivityItem.delegations`), or\n * `null` for a call that delegates by nature of neither: a `task` call one,\n * unless the person refused it or its approval was withdrawn or expired (it\n * never ran); an `eval` call its in-interpreter dispatches, when it made any.\n */\nfunction toolCallDelegations({\n part,\n state,\n}: {\n part: ToolCallPart;\n state: ActivityToolState;\n}): number | null {\n if (part.toolName === TASK_TOOL_NAME) {\n const withdrawn =\n part.approval?.resolution === 'cancelled' || part.approval?.resolution === 'expired';\n return state === 'rejected' || withdrawn ? 0 : 1;\n }\n if (part.toolName === EVAL_TOOL_NAME) {\n const dispatches = countInEvalDispatches(part.artifact);\n return dispatches > 0 ? dispatches : null;\n }\n return null;\n}\n\n/** Number of tool calls in the timeline. */\nexport function countActivityTools(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => (item.kind === 'tool' ? count + 1 : count), 0);\n}\n\n/**\n * Subagents the turn delegated to: its `task` calls that ran, plus the\n * `task()` dispatches its `eval` calls made from inside the interpreter. A\n * `task` step built without its count (a host's own timeline) counts unless it\n * reads refused or cancelled.\n */\nexport function countActivityDelegations(timeline: readonly ActivityItem[]): number {\n return timeline.reduce((count, item) => {\n if (item.kind !== 'tool') return count;\n if (item.delegations !== undefined) return count + item.delegations;\n const ranTask =\n item.toolName === TASK_TOOL_NAME && item.state !== 'rejected' && item.state !== 'cancelled';\n return ranTask ? count + 1 : count;\n }, 0);\n}\n\n/**\n * The turn's phase. Waiting on the person wins over everything — a\n * `requires-action` status, a waiting tool, or a host's own `waitingForInput`\n * assertion (no in-repo host passes one) — then the message's own status. A\n * complete turn with a step still running is running: only a step holding the\n * backend's running placeholder reads so once nothing streams (a run parked on\n * an approval inside a subagent, the gap before a drained run resumes).\n */\nexport function resolveActivityPhase(\n status: MessageStatus | undefined,\n timeline: readonly ActivityItem[],\n { waitingForInput = false }: { waitingForInput?: boolean } = {},\n): ActivityPhase {\n if (\n waitingForInput ||\n status?.type === 'requires-action' ||\n timeline.some((item) => item.kind === 'tool' && item.state === 'waiting')\n ) {\n return 'waiting';\n }\n if (status?.type === 'running') return 'running';\n if (status?.type === 'incomplete') return 'error';\n if (\n status?.type === 'complete' &&\n timeline.some((item) => item.kind === 'tool' && item.state === 'running')\n ) {\n return 'running';\n }\n return 'complete';\n}\n\n/**\n * A settled thought's row: the heading a reasoning summary opens with\n * (\"**Checking the workstation**\" → \"Checking the workstation\"), else the\n * generic wording. The thought itself stays in the run's details.\n */\nexport function getReasoningLabel(text: string, state: 'running' | 'complete'): string {\n if (state === 'running') return 'Thinking';\n const heading = /^\\s*\\*\\*([^*\\n]{3,80}?)\\*\\*/.exec(text)?.[1]?.trim();\n return heading ? heading.replace(/[.:]+$/, '') : 'Thought through the next step';\n}\n\n/**\n * The status-row copy for one step: the call's own summary from the shared\n * summary registry (`summarizeToolCall`: \"Ran git status\", \"Read config.ts\").\n * `resolveToolDisplayName` lets a host with a richer tool catalog name tools\n * neither registry has an entry for; `assetTitles` names the machine or\n * document a call targeted by id when an earlier result in the turn did.\n */\nexport function getActivityItemLabel(\n item: ActivityItem,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): string {\n if (item.kind === 'reasoning') return getReasoningLabel(item.text, item.state);\n return summarizeActivityTool(item, resolveToolDisplayName, assetTitles).label;\n}\n\n/** The summary of one tool step (see {@link getActivityItemLabel}). */\nexport function summarizeActivityTool(\n item: Extract<ActivityItem, { kind: 'tool' }>,\n resolveToolDisplayName?: (toolName: string) => string | undefined,\n assetTitles?: ReadonlyMap<string, string>,\n): ToolCallSummary {\n return summarizeToolCall({\n toolName: item.toolName,\n args: item.args,\n result: item.result,\n status: item.state,\n ...(assetTitles ? { assetTitles } : {}),\n ...(resolveToolDisplayName ? { displayName: resolveToolDisplayName(item.toolName) } : {}),\n });\n}\n\n/**\n * The collapsed-card summary of a finished turn. \"Completed with issues\" means\n * the turn ended on a problem: a call the person refused, or a failed call\n * that no later call completed after. A failed command the agent then got past\n * (a search that found nothing, a retry that worked) is ordinary work, so a\n * long turn that recovered from it reads as the tools it used.\n *\n * A turn that delegated work leads with it — \"Delegated to 10 agents · used 6\n * tools\" — counting its `task` calls and the `task()` dispatches its `eval`\n * calls made from inside the interpreter (`countActivityDelegations`). A\n * `task` call is the delegation itself, so the tools it is followed by are the\n * turn's other calls; an outcome above replaces them (\"Delegated to 3 agents ·\n * completed with issues\").\n */\nexport function getActivitySummary(\n timeline: readonly ActivityItem[],\n { earlierStepsUnloaded = false }: ActivitySummaryOptions = {},\n): string {\n const tools = timeline.filter((item): item is ToolActivityItem => item.kind === 'tool');\n const outcome = activityOutcome(tools);\n const delegations = countActivityDelegations(timeline);\n // A turn whose earlier steps have not loaded counts what has: a floor.\n const more = earlierStepsUnloaded ? '+' : '';\n if (delegations > 0) {\n const lead = `Delegated to ${delegations}${more} ${delegations === 1 && !more ? 'agent' : 'agents'}`;\n const otherTools = tools.filter((item) => item.toolName !== TASK_TOOL_NAME).length;\n const rest = outcome\n ? `${outcome.charAt(0).toLowerCase()}${outcome.slice(1)}`\n : otherTools > 0\n ? `used ${otherTools}${more} ${otherTools === 1 && !more ? 'tool' : 'tools'}`\n : null;\n return rest ? `${lead} · ${rest}` : lead;\n }\n if (outcome) return outcome;\n if (tools.length > 0) return `Used ${tools.length}${more} ${tools.length === 1 && !more ? 'tool' : 'tools'}`;\n if (earlierStepsUnloaded) return 'Earlier steps not loaded';\n return 'Reasoning complete';\n}\n\n/** How {@link getActivitySummary} reads a turn that may not be loaded whole. */\nexport interface ActivitySummaryOptions {\n /**\n * The turn's earlier steps have not loaded yet (older history above it, see\n * `useEarlierStepsUnloaded`): its counts read as floors, \"Used 43+ tools\".\n */\n earlierStepsUnloaded?: boolean;\n}\n\ntype ToolActivityItem = Extract<ActivityItem, { kind: 'tool' }>;\n\n/** How a finished turn's tool calls ended, when that is not simply the tools it used. */\nfunction activityOutcome(tools: readonly ToolActivityItem[]): string | null {\n const lastFailure = tools.findLastIndex((item) => item.state === 'error');\n const endedOnFailure =\n lastFailure >= 0 && !tools.slice(lastFailure + 1).some((item) => item.state === 'complete');\n if (endedOnFailure || tools.some((item) => item.state === 'rejected')) return 'Completed with issues';\n if (tools.some((item) => item.state === 'cancelled')) return 'Action cancelled';\n if (tools.some((item) => item.state === 'paused')) return 'Paused during tool use';\n return null;\n}\n\n/** The part shape the final-text read needs; any message role's parts satisfy it. */\nexport type FinalTextPart = { readonly type: string; readonly text?: string };\n\n/**\n * Whether a text part shows below the card instead of in the details panel:\n * it comes after the final tool call, or it holds a spoken voice turn's words\n * (a live turn heard during a delegated run lands between the run's tool\n * calls, see `isSpokenTurnTextPart`; it is the conversation, not narration).\n */\nfunction isAnswerTextPart(\n part: { readonly type: string } | undefined,\n index: number,\n lastToolIndex: number,\n): boolean {\n return part?.type === 'text' && (index > lastToolIndex || isSpokenTurnTextPart(part));\n}\n\n/**\n * The visible answer of a grouped turn: the text after the final tool call\n * (all of it when the turn never called a tool), plus the words of every\n * spoken voice turn in the message, in order. Other intermediate text (before\n * or between tool calls) stays available in the details panel.\n */\nexport function getFinalText(content: readonly FinalTextPart[] | undefined): string | null {\n const parts = getFinalTextParts(content);\n return parts.length > 0 ? parts.map((part) => part.text).join('\\n\\n') : null;\n}\n\n/** One text part of the visible answer, with its index in the message content. */\nexport interface FinalTextSegment {\n index: number;\n text: string;\n}\n\n/**\n * The parts `getFinalText` joins, each kept separate with its content index.\n * A spoken turn and the run's own reply can stream at the same time, so the\n * earlier part may grow while a later one exists: rendered as one joined\n * string, that text changes in the middle rather than at its end, and the\n * streaming reveal restarts from an empty bubble on every word.\n */\nexport function getFinalTextParts(content: readonly FinalTextPart[] | undefined): FinalTextSegment[] {\n if (!content?.length) return [];\n\n const lastToolIndex = content.findLastIndex((part) => part?.type === 'tool-call');\n const parts: FinalTextSegment[] = [];\n content.forEach((part, index) => {\n if (part.text && isAnswerTextPart(part, index, lastToolIndex)) parts.push({ index, text: part.text });\n });\n return parts;\n}\n\n/**\n * Keep the answer out of the details panel: it renders below the card\n * (`SuperGroupingFinalText`), so the details grouping drops every text part\n * `getFinalText` shows and any group that becomes empty.\n */\nexport function excludeFinalTextFromGroups(grouping: GroupingFunction): GroupingFunction {\n return (parts) => {\n const lastToolIndex = parts.findLastIndex((part) => part?.type === 'tool-call');\n return grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => !isAnswerTextPart(parts[index], index, lastToolIndex)),\n }))\n .filter((group) => group.indices.length > 0);\n };\n}\n\n/**\n * Drop the parts `exclude` matches from every group, and any group left\n * empty: `SuperGroupingCard` renders inline tool calls outside the details,\n * so they must not render a second time inside it.\n */\nexport function excludePartsFromGroups(\n grouping: GroupingFunction,\n exclude: (part: GroupablePart) => boolean,\n): GroupingFunction {\n return (parts) =>\n grouping(parts)\n .map((group) => ({\n ...group,\n indices: group.indices.filter((index) => {\n const part = parts[index];\n return part === undefined || !exclude(part);\n }),\n }))\n .filter((group) => group.indices.length > 0);\n}\n"],"names":[],"mappings":"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"}
@@ -10,7 +10,7 @@
10
10
  * A lean entry so hosts with their own thread (Olympus, the Chrome extension)
11
11
  * mount it from source without the SDK's composer stack.
12
12
  */
13
- export { type ActivityItem, type ActivityPhase, type ActivityTimelineOptions, type ActivityToolState, buildActivityTimeline, countActivityDelegations, countActivityTools, excludeFinalTextFromGroups, excludePartsFromGroups, type FinalTextPart, type FinalTextSegment, getActivityItemLabel, getActivitySummary, getReasoningLabel, getActivityToolState, isStreamingToolPlaceholder, getFinalText, getFinalTextParts, resolveActivityPhase, } from './activity-timeline';
13
+ export { type ActivityItem, type ActivityPhase, type ActivityTimelineOptions, type ActivityToolState, buildActivityTimeline, countActivityDelegations, countActivityTools, excludeFinalTextFromGroups, excludePartsFromGroups, type FinalTextPart, type FinalTextSegment, type ActivitySummaryOptions, getActivityItemLabel, getActivitySummary, getReasoningLabel, getActivityToolState, isStreamingToolPlaceholder, getFinalText, getFinalTextParts, resolveActivityPhase, } from './activity-timeline';
14
14
  export { CollapsibleGroup, type CollapsibleGroupProps, DEFAULT_TOOLKIT_THEME, SuperGroupingProvider, TOOLKIT_THEMES, type ToolkitTheme, } from './CollapsibleGroup';
15
15
  export { GroupedMessageParts, type GroupedMessagePartsProps } from './GroupedMessageParts';
16
16
  export { createToolBasedGroupingFunction, type GroupablePart, type GroupingFunction, type MessagePartGroup, } from './grouping-function';
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const react = require("@assistant-ui/react");
4
+ const require$$0 = require("react");
5
+ const statewireHistoryContext = require("../../provider/statewire-history-context.cjs");
6
+ const threadIdContext = require("../../threads/thread-id-context.cjs");
7
+ function useEarlierStepsUnloaded() {
8
+ const isOldestLoaded = react.useAuiState((s) => {
9
+ var _a;
10
+ return ((_a = s.thread.messages[0]) == null ? void 0 : _a.id) === s.message.id;
11
+ });
12
+ const threadId = require$$0.useContext(threadIdContext.AthenaStatewireThreadIdContext);
13
+ const history = statewireHistoryContext.useStatewireHistory(threadId);
14
+ return isOldestLoaded && (history == null ? void 0 : history.hasOlder) === true;
15
+ }
16
+ exports.useEarlierStepsUnloaded = useEarlierStepsUnloaded;
17
+ //# sourceMappingURL=use-earlier-steps-unloaded.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-earlier-steps-unloaded.cjs","sources":["../../../src/chat/super-grouping/use-earlier-steps-unloaded.ts"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { useContext } from 'react';\nimport { useStatewireHistory } from '../../provider/statewire-history-context';\nimport { AthenaStatewireThreadIdContext } from '../../threads/thread-id-context';\n\n/**\n * Whether the calling message is the oldest one loaded while the host still\n * holds older history: on a windowed (statewire) thread that is a turn whose\n * earlier steps, and prompt, have not loaded yet, so what the row counts is a\n * floor. Pages of older history fold into it as they load, and it counts\n * whole once its prompt shows. `false` on a thread with no history edge.\n */\nexport function useEarlierStepsUnloaded(): boolean {\n const isOldestLoaded = useAuiState((s) => s.thread.messages[0]?.id === s.message.id);\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const history = useStatewireHistory(threadId);\n return isOldestLoaded && history?.hasOlder === true;\n}\n"],"names":["useAuiState","useContext","AthenaStatewireThreadIdContext","useStatewireHistory"],"mappings":";;;;;;AAYO,SAAS,0BAAmC;AACjD,QAAM,iBAAiBA,MAAAA,YAAY,CAAC,MAAA;;AAAM,oBAAE,OAAO,SAAS,CAAC,MAAnB,mBAAsB,QAAO,EAAE,QAAQ;AAAA,GAAE;AACnF,QAAM,WAAWC,WAAAA,WAAWC,8CAA8B;AAC1D,QAAM,UAAUC,wBAAAA,oBAAoB,QAAQ;AAC5C,SAAO,mBAAkB,mCAAS,cAAa;AACjD;;"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Whether the calling message is the oldest one loaded while the host still
3
+ * holds older history: on a windowed (statewire) thread that is a turn whose
4
+ * earlier steps, and prompt, have not loaded yet, so what the row counts is a
5
+ * floor. Pages of older history fold into it as they load, and it counts
6
+ * whole once its prompt shows. `false` on a thread with no history edge.
7
+ */
8
+ export declare function useEarlierStepsUnloaded(): boolean;
@@ -0,0 +1,17 @@
1
+ import { useAuiState } from "@assistant-ui/react";
2
+ import { useContext } from "react";
3
+ import { useStatewireHistory } from "../../provider/statewire-history-context.js";
4
+ import { AthenaStatewireThreadIdContext } from "../../threads/thread-id-context.js";
5
+ function useEarlierStepsUnloaded() {
6
+ const isOldestLoaded = useAuiState((s) => {
7
+ var _a;
8
+ return ((_a = s.thread.messages[0]) == null ? void 0 : _a.id) === s.message.id;
9
+ });
10
+ const threadId = useContext(AthenaStatewireThreadIdContext);
11
+ const history = useStatewireHistory(threadId);
12
+ return isOldestLoaded && (history == null ? void 0 : history.hasOlder) === true;
13
+ }
14
+ export {
15
+ useEarlierStepsUnloaded
16
+ };
17
+ //# sourceMappingURL=use-earlier-steps-unloaded.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-earlier-steps-unloaded.js","sources":["../../../src/chat/super-grouping/use-earlier-steps-unloaded.ts"],"sourcesContent":["import { useAuiState } from '@assistant-ui/react';\nimport { useContext } from 'react';\nimport { useStatewireHistory } from '../../provider/statewire-history-context';\nimport { AthenaStatewireThreadIdContext } from '../../threads/thread-id-context';\n\n/**\n * Whether the calling message is the oldest one loaded while the host still\n * holds older history: on a windowed (statewire) thread that is a turn whose\n * earlier steps, and prompt, have not loaded yet, so what the row counts is a\n * floor. Pages of older history fold into it as they load, and it counts\n * whole once its prompt shows. `false` on a thread with no history edge.\n */\nexport function useEarlierStepsUnloaded(): boolean {\n const isOldestLoaded = useAuiState((s) => s.thread.messages[0]?.id === s.message.id);\n const threadId = useContext(AthenaStatewireThreadIdContext);\n const history = useStatewireHistory(threadId);\n return isOldestLoaded && history?.hasOlder === true;\n}\n"],"names":[],"mappings":";;;;AAYO,SAAS,0BAAmC;AACjD,QAAM,iBAAiB,YAAY,CAAC,MAAA;;AAAM,oBAAE,OAAO,SAAS,CAAC,MAAnB,mBAAsB,QAAO,EAAE,QAAQ;AAAA,GAAE;AACnF,QAAM,WAAW,WAAW,8BAA8B;AAC1D,QAAM,UAAU,oBAAoB,QAAQ;AAC5C,SAAO,mBAAkB,mCAAS,cAAa;AACjD;"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const chatThreadShell = require("./thread/chat-thread-shell.cjs");
4
4
  const chatComposerDock = require("./thread/chat-composer-dock.cjs");
5
5
  const statewireHistoryEdge = require("./thread/statewire-history-edge.cjs");
6
+ const handoffCursor = require("./thread/handoff-cursor.cjs");
6
7
  const threadLoadingOverlay = require("./thread/thread-loading-overlay.cjs");
7
8
  const statewireWindowContext = require("../provider/statewire-window-context.cjs");
8
9
  const composerSendOrStop = require("./thread/composer-send-or-stop.cjs");
@@ -54,6 +55,13 @@ exports.HISTORY_EDGE_NEAR_MARGIN_PX = statewireHistoryEdge.HISTORY_EDGE_NEAR_MAR
54
55
  exports.HistoryEdgeRow = statewireHistoryEdge.HistoryEdgeRow;
55
56
  exports.StatewireHistoryEdge = statewireHistoryEdge.StatewireHistoryEdge;
56
57
  exports.THREAD_VIEWPORT_ATTR = statewireHistoryEdge.THREAD_VIEWPORT_ATTR;
58
+ exports.THREAD_VIEWPORT_FOOTER_ATTR = statewireHistoryEdge.THREAD_VIEWPORT_FOOTER_ATTR;
59
+ exports.HANDOFF_CURSOR_REVEALED_ATTR = handoffCursor.HANDOFF_CURSOR_REVEALED_ATTR;
60
+ exports.HandoffCursorReveal = handoffCursor.HandoffCursorReveal;
61
+ exports.findThreadViewport = handoffCursor.findThreadViewport;
62
+ exports.readThreadReadingCursor = handoffCursor.readThreadReadingCursor;
63
+ exports.startHandoffCursorReveal = handoffCursor.startHandoffCursorReveal;
64
+ exports.useReadThreadReadingCursor = handoffCursor.useReadThreadReadingCursor;
57
65
  exports.THREAD_LOADING_OVERLAY_TIMEOUT_MS = threadLoadingOverlay.THREAD_LOADING_OVERLAY_TIMEOUT_MS;
58
66
  exports.ThreadLoadingOverlay = threadLoadingOverlay.ThreadLoadingOverlay;
59
67
  exports.StatewireToolOutputsContext = statewireWindowContext.StatewireToolOutputsContext;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -11,7 +11,8 @@ export { ChatThreadShell } from "./thread/chat-thread-shell";
11
11
  export type { ChatThreadMessageComponents, ChatThreadShellProps, } from "./thread/chat-thread-shell";
12
12
  export { ChatComposerDock } from "./thread/chat-composer-dock";
13
13
  export type { ChatComposerAppearance, ChatComposerDockProps, } from "./thread/chat-composer-dock";
14
- export { HISTORY_EDGE_NEAR_MARGIN_PX, HistoryEdgeRow, type HistoryEdgeRowProps, StatewireHistoryEdge, type StatewireHistoryEdgeProps, THREAD_VIEWPORT_ATTR, } from "./thread/statewire-history-edge";
14
+ export { HISTORY_EDGE_NEAR_MARGIN_PX, HistoryEdgeRow, type HistoryEdgeRowProps, StatewireHistoryEdge, type StatewireHistoryEdgeProps, THREAD_VIEWPORT_ATTR, THREAD_VIEWPORT_FOOTER_ATTR, } from "./thread/statewire-history-edge";
15
+ export { findThreadViewport, HANDOFF_CURSOR_REVEALED_ATTR, HandoffCursorReveal, readThreadReadingCursor, startHandoffCursorReveal, useReadThreadReadingCursor, } from "./thread/handoff-cursor";
15
16
  export { THREAD_LOADING_OVERLAY_TIMEOUT_MS, ThreadLoadingOverlay, } from "./thread/thread-loading-overlay";
16
17
  export { StatewireToolOutputsContext, useStatewireToolOutputs, } from "../provider/statewire-window-context";
17
18
  export { ComposerSendOrStop } from "./thread/composer-send-or-stop";
@@ -1,6 +1,7 @@
1
1
  import { ChatThreadShell } from "./thread/chat-thread-shell.js";
2
2
  import { ChatComposerDock } from "./thread/chat-composer-dock.js";
3
- import { HISTORY_EDGE_NEAR_MARGIN_PX, HistoryEdgeRow, StatewireHistoryEdge, THREAD_VIEWPORT_ATTR } from "./thread/statewire-history-edge.js";
3
+ import { HISTORY_EDGE_NEAR_MARGIN_PX, HistoryEdgeRow, StatewireHistoryEdge, THREAD_VIEWPORT_ATTR, THREAD_VIEWPORT_FOOTER_ATTR } from "./thread/statewire-history-edge.js";
4
+ import { HANDOFF_CURSOR_REVEALED_ATTR, HandoffCursorReveal, findThreadViewport, readThreadReadingCursor, startHandoffCursorReveal, useReadThreadReadingCursor } from "./thread/handoff-cursor.js";
4
5
  import { THREAD_LOADING_OVERLAY_TIMEOUT_MS, ThreadLoadingOverlay } from "./thread/thread-loading-overlay.js";
5
6
  import { StatewireToolOutputsContext, useStatewireToolOutputs } from "../provider/statewire-window-context.js";
6
7
  import { ComposerSendOrStop } from "./thread/composer-send-or-stop.js";
@@ -73,11 +74,13 @@ export {
73
74
  DrivingOnChip,
74
75
  FromTheCallAfterMessage,
75
76
  FromTheCallCard,
77
+ HANDOFF_CURSOR_REVEALED_ATTR,
76
78
  HISTORY_EDGE_NEAR_MARGIN_PX,
77
79
  HITL_PENDING_RESUME_GRACE_MS,
78
80
  HandoffArrivalAfterMessage,
79
81
  HandoffArrivalDivider,
80
82
  HandoffArrivalScope,
83
+ HandoffCursorReveal,
81
84
  HistoryEdgeRow,
82
85
  IconButton,
83
86
  MESSAGE_ARRIVAL_ANIMATION_CLASS,
@@ -121,6 +124,7 @@ export {
121
124
  THREAD_PANEL_CLASS,
122
125
  THREAD_SCROLL_TO_BOTTOM_CLASS,
123
126
  THREAD_VIEWPORT_ATTR,
127
+ THREAD_VIEWPORT_FOOTER_ATTR,
124
128
  TextShimmerLoader,
125
129
  ThreadLoadingOverlay,
126
130
  ThreadRunningIndicator,
@@ -141,6 +145,7 @@ export {
141
145
  createAssistantMessage,
142
146
  createOlympusAssetUrl,
143
147
  fetchGraphQL,
148
+ findThreadViewport,
144
149
  makeMentionUrl,
145
150
  normalizeAthenaMentionHtml,
146
151
  parseAssetAnchorAttributes,
@@ -148,15 +153,18 @@ export {
148
153
  parseMentionUrl,
149
154
  parseMentionsInText,
150
155
  prepareComposerSend,
156
+ readThreadReadingCursor,
151
157
  resolveComposerAction,
152
158
  serializeDraft,
153
159
  serializeMention,
160
+ startHandoffCursorReveal,
154
161
  useAthenaBrowserDecisionConfirmer,
155
162
  useBrowserDecisionConfirmer,
156
163
  useMentionAuth,
157
164
  useMentionSuggestions,
158
165
  useMessageActionsReadOnly,
159
166
  useMessageArrivalAnimation,
167
+ useReadThreadReadingCursor,
160
168
  useStatewireApprovalRequest,
161
169
  useStatewireToolOutputs,
162
170
  webpageMentionFingerprint
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -11,6 +11,7 @@ const userEditComposer = require("../messages/user-edit-composer.cjs");
11
11
  const runningIndicator = require("../messages/running-indicator.cjs");
12
12
  const handoffArrivalDivider = require("../messages/handoff-arrival-divider.cjs");
13
13
  const fromTheCallCard = require("../messages/from-the-call-card.cjs");
14
+ const statewireHistoryEdge = require("./statewire-history-edge.cjs");
14
15
  const threadChrome = require("../messages/thread-chrome.cjs");
15
16
  const messages = require("../../agent-runtime/src/messages.cjs");
16
17
  const withArrivalDivider = /* @__PURE__ */ (() => {
@@ -115,6 +116,7 @@ function ChatThreadShell({
115
116
  react.ThreadPrimitive.ViewportFooter,
116
117
  {
117
118
  className: viewportFooterClassName ?? DEFAULT_VIEWPORT_FOOTER_CLASS,
119
+ ...{ [statewireHistoryEdge.THREAD_VIEWPORT_FOOTER_ATTR]: "" },
118
120
  children: [
119
121
  scrollToBottom,
120
122
  viewportFooter
@@ -1 +1 @@
1
- {"version":3,"file":"chat-thread-shell.cjs","sources":["../../../src/chat-ui/thread/chat-thread-shell.tsx"],"sourcesContent":["import { AuiIf, ThreadPrimitive, useAuiState } from \"@assistant-ui/react\";\nimport { deepAgentRowKey } from \"@athenaintel/agent-runtime\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, CSSProperties, FC, ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport {\n THREAD_PANEL_CLASS,\n THREAD_SCROLL_TO_BOTTOM_CLASS,\n} from \"../messages/message-components\";\nimport { AnswerLabelScope } from \"../messages/answer-label\";\nimport { MessageActionsReadOnlyProvider } from \"../messages/message-actions-config\";\nimport { UserEditComposer } from \"../messages/user-edit-composer\";\nimport { ThreadRunningIndicator } from \"../messages/running-indicator\";\nimport {\n HandoffArrivalAfterMessage,\n HandoffArrivalScope,\n} from \"../messages/handoff-arrival-divider\";\nimport { FromTheCallAfterMessage } from \"../messages/from-the-call-card\";\n\nexport interface ChatThreadMessageComponents {\n UserMessage: FC;\n AssistantMessage: FC;\n /**\n * Rendered in place of a user message while it is being edited (the action\n * bar's Edit control). Defaults to the shared UserEditComposer.\n */\n UserEditComposer?: FC;\n}\n\n// One wrapper per message renderer, so a renderer keeps its identity across\n// shells and renders: a row renders its message through the component it is\n// handed, and a new component identity would remount every row.\nconst withArrivalDivider = (() => {\n const wrapped = new WeakMap<FC, FC>();\n return (Message: FC): FC => {\n const existing = wrapped.get(Message);\n if (existing) return existing;\n // After the message: the \"From the call\" card where a call's stretch\n // ends, then the arrival divider (the arrival came after the call).\n const WithArrivalDivider: FC = () => (\n <>\n <Message />\n <FromTheCallAfterMessage />\n <HandoffArrivalAfterMessage />\n </>\n );\n wrapped.set(Message, WithArrivalDivider);\n return WithArrivalDivider;\n };\n})();\n\ntype ThreadRowComponents = ComponentProps<typeof ThreadPrimitive.MessageByIndex>[\"components\"];\n\nconst ROW_KEY_SEPARATOR = \"\\u0000\";\n\n/**\n * The thread's messages, one row each under its row key (`deepAgentRowKey`).\n *\n * `ThreadPrimitive.Messages` keys a row by its message id. A page of older\n * history that folds into the first loaded turn (a long run's earlier steps)\n * gives that turn a new id on every page, so keyed by id the turn's row\n * remounted once per page: it flashed through its entrance animation, closed\n * what the person had opened in it and took the browser's scroll anchor with\n * it. The row key stays with the row the turn continues, so the page lands in\n * the mounted row. A message from another runtime keys by its id, as before.\n */\nconst ThreadRows: FC<{ components: ThreadRowComponents }> = ({ components }) => {\n const joinedKeys = useAuiState((s) =>\n s.thread.messages.map((message) => deepAgentRowKey(message)).join(ROW_KEY_SEPARATOR),\n );\n const rowKeys = useMemo(\n () => (joinedKeys === \"\" ? [] : joinedKeys.split(ROW_KEY_SEPARATOR)),\n [joinedKeys],\n );\n return (\n <>\n {rowKeys.map((key, index) => (\n <ThreadPrimitive.MessageByIndex key={key} index={index} components={components} />\n ))}\n </>\n );\n};\n\nconst DEFAULT_VIEWPORT_CLASS =\n \"relative flex flex-1 flex-col overflow-y-auto scroll-smooth\";\nconst DEFAULT_CONTENT_CLASS = \"w-full flex-1 px-3 pt-3\";\nconst DEFAULT_VIEWPORT_FOOTER_CLASS = \"sticky bottom-0\";\n\nconst DefaultScrollToBottom: FC = () => (\n <AuiIf condition={(state) => !state.thread.isEmpty}>\n <div className=\"flex justify-center pb-0.5\">\n <ThreadPrimitive.ScrollToBottom\n aria-label=\"Scroll to bottom\"\n title=\"Scroll to bottom\"\n className={THREAD_SCROLL_TO_BOTTOM_CLASS}\n >\n <ChevronDownIcon className=\"size-3.5\" />\n </ThreadPrimitive.ScrollToBottom>\n </div>\n </AuiIf>\n);\n\nexport interface ChatThreadShellProps {\n /**\n * Rendered as the first child of the panel — for absolutely-positioned\n * scrims that must cover the whole panel (AthenaChat's thread-loading\n * overlay). The shell does not force a positioning context; hosts include\n * `relative` in `panelClassName` when the scrim anchors to the panel.\n */\n overlay?: ReactNode;\n /**\n * Rendered above the viewport, inside the panel: header rows, slide-over\n * panels, and anything else the host docks at the top.\n */\n header?: ReactNode;\n /** Status/approval banners rendered between the header and the viewport. */\n banners?: ReactNode;\n /**\n * Nodes rendered inside the scroll viewport before the message column —\n * absolutely/sticky-positioned overlays (thread-switch skeletons,\n * scroll-to-top controls) and invisible observers (read receipts).\n */\n viewportOverlay?: ReactNode;\n /** Empty-thread state, rendered via ThreadPrimitive.Empty when provided. */\n welcome?: ReactNode;\n /**\n * Rendered inside the message column right above the messages — the\n * windowed wire's history edge (`StatewireHistoryEdge`): the \"N earlier\n * messages\" row that pages older history in and fetches cut tool outputs.\n */\n beforeMessages?: ReactNode;\n /** Message renderers for the thread's rows. */\n components: ChatThreadMessageComponents;\n /**\n * Wraps the messages element — for hosts that provide message-scoped\n * context (e.g. activity-grouping providers). Must render its argument.\n */\n renderMessages?: (messages: ReactNode) => ReactNode;\n /**\n * Rendered between the viewport and the composer: connection banners,\n * queued-message trays.\n */\n belowViewport?: ReactNode;\n /** The composer dock (or a read-only notice replacing it). */\n composer?: ReactNode;\n /**\n * Replaces the docked-panel card chrome — for hosts that render a\n * full-page column (AthenaChat's `aui-*` surface) instead of the\n * side-panel/task-pane card.\n */\n panelClassName?: string;\n /**\n * Value of the `--thread-max-width` variable read by width-capped\n * message/welcome/footer columns. Panels fill their width (the default,\n * \"100%\"); full-page hosts cap the column (AthenaChat defaults to 44rem).\n */\n maxWidth?: string;\n /**\n * Anchors the latest turn to the top of the viewport instead of pinning\n * scroll to the bottom (AthenaChat). Omitted = the primitive's default.\n */\n turnAnchor?: \"top\" | \"bottom\";\n /** Replaces the scroll-viewport chrome (padding, overflow tuning). */\n viewportClassName?: string;\n /**\n * Replaces the message-column chrome. Hosts whose welcome screen centers\n * vertically make this a flex column so the welcome node can grow.\n */\n contentClassName?: string;\n /**\n * Thread-level \"Thinking...\" indicator rendered after the messages. Hosts\n * whose message components render their own per-message indicators pass\n * null so the two never double up.\n */\n runningIndicator?: ReactNode;\n /**\n * Replaces the default centered scroll-to-bottom pill inside the sticky\n * viewport footer (AthenaChat floats a circular button above its composer).\n */\n scrollToBottom?: ReactNode;\n /**\n * Content docked inside the sticky viewport footer, after the\n * scroll-to-bottom control — for hosts whose banners + composer float over\n * the scrolling messages (AthenaChat) instead of sitting below the\n * viewport via `belowViewport`/`composer`.\n */\n viewportFooter?: ReactNode;\n /** Replaces the sticky viewport-footer chrome. */\n viewportFooterClassName?: string;\n /**\n * Marks the transcript read-only: mutating message actions (Edit,\n * Regenerate) hide while Copy stays available. For threads whose server\n * rejects dispatch (e.g. legacy pre-cutover conversations) — the runtime's\n * static capability flags cannot express a per-thread fence.\n */\n readOnly?: boolean;\n /**\n * The backend id of the thread on screen. When this surface picked the\n * conversation up from another one (`recordHandoffArrival`), the transcript\n * shows \"Continued from …\" after the last message that was already there.\n */\n arrivalThreadId?: string | null;\n /**\n * The signed-in viewer's Athena user id (`user_…`), for the answer labels\n * (`AnswerLabel`): their own answers read \"for you\", and an answer for\n * another person is labelled even in a 1:1 chat. Omitted, answers name the\n * person by name and are labelled only once the thread holds several agents\n * or several people.\n */\n viewerUserId?: string | null;\n}\n\n/**\n * The shared thread layout: panel chrome, scroll viewport with welcome +\n * messages + running indicator, the sticky scroll-to-bottom control, and the\n * host-supplied composer dock.\n *\n * Hosts express their genuine divergences through the slots above; the\n * layout, scroll behaviour, and empty/running states stay identical across\n * surfaces so fixes land once.\n */\nexport function ChatThreadShell({\n overlay,\n header,\n banners,\n viewportOverlay,\n welcome,\n beforeMessages,\n components,\n renderMessages,\n belowViewport,\n composer,\n panelClassName,\n maxWidth = \"100%\",\n turnAnchor,\n viewportClassName,\n contentClassName,\n runningIndicator = <ThreadRunningIndicator />,\n scrollToBottom = <DefaultScrollToBottom />,\n viewportFooter,\n viewportFooterClassName,\n readOnly = false,\n arrivalThreadId = null,\n viewerUserId = null,\n}: ChatThreadShellProps) {\n const {\n UserMessage,\n AssistantMessage,\n UserEditComposer: UserEditComposerComponent = UserEditComposer,\n } = components;\n // A row resolves its renderer by component identity, so keep the merged map\n // referentially stable across renders.\n const messageComponents = useMemo(\n () => ({\n UserMessage: withArrivalDivider(UserMessage),\n AssistantMessage: withArrivalDivider(AssistantMessage),\n UserEditComposer: UserEditComposerComponent,\n }),\n [UserMessage, AssistantMessage, UserEditComposerComponent],\n );\n const messages = (\n <HandoffArrivalScope threadId={arrivalThreadId}>\n <AnswerLabelScope viewerUserId={viewerUserId}>\n <ThreadRows components={messageComponents} />\n </AnswerLabelScope>\n </HandoffArrivalScope>\n );\n return (\n <MessageActionsReadOnlyProvider readOnly={readOnly}>\n <ThreadPrimitive.Root\n className={panelClassName ?? THREAD_PANEL_CLASS}\n style={{ \"--thread-max-width\": maxWidth } as CSSProperties}\n >\n {overlay}\n {header}\n {banners}\n <ThreadPrimitive.Viewport\n turnAnchor={turnAnchor}\n className={viewportClassName ?? DEFAULT_VIEWPORT_CLASS}\n data-aui-thread-viewport=\"\"\n >\n {viewportOverlay}\n {/* No min-h-0 here: the column's automatic minimum height is what\n lets it extend past one screen, keeping the sticky footer's flow\n position after the full message list. Capping it detaches the\n footer (scroll pill, docked composers) as soon as the thread\n scrolls — proven in a live-browser probe of both structures. */}\n <div className=\"flex flex-1 flex-col items-center\">\n <div className={contentClassName ?? DEFAULT_CONTENT_CLASS}>\n {welcome != null ? (\n <ThreadPrimitive.Empty>{welcome}</ThreadPrimitive.Empty>\n ) : null}\n {beforeMessages}\n {renderMessages ? renderMessages(messages) : messages}\n {runningIndicator}\n </div>\n </div>\n <ThreadPrimitive.ViewportFooter\n className={viewportFooterClassName ?? DEFAULT_VIEWPORT_FOOTER_CLASS}\n >\n {scrollToBottom}\n {viewportFooter}\n </ThreadPrimitive.ViewportFooter>\n </ThreadPrimitive.Viewport>\n {belowViewport}\n {composer}\n </ThreadPrimitive.Root>\n </MessageActionsReadOnlyProvider>\n );\n}\n"],"names":["jsxs","Fragment","jsx","FromTheCallAfterMessage","HandoffArrivalAfterMessage","useAuiState","deepAgentRowKey","useMemo","ThreadPrimitive","AuiIf","THREAD_SCROLL_TO_BOTTOM_CLASS","ChevronDownIcon","runningIndicator","ThreadRunningIndicator","UserEditComposer","messageComponents","messages","HandoffArrivalScope","AnswerLabelScope","MessageActionsReadOnlyProvider","THREAD_PANEL_CLASS"],"mappings":"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"}
1
+ {"version":3,"file":"chat-thread-shell.cjs","sources":["../../../src/chat-ui/thread/chat-thread-shell.tsx"],"sourcesContent":["import { AuiIf, ThreadPrimitive, useAuiState } from \"@assistant-ui/react\";\nimport { deepAgentRowKey } from \"@athenaintel/agent-runtime\";\nimport { ChevronDownIcon } from \"lucide-react\";\nimport type { ComponentProps, CSSProperties, FC, ReactNode } from \"react\";\nimport { useMemo } from \"react\";\nimport {\n THREAD_PANEL_CLASS,\n THREAD_SCROLL_TO_BOTTOM_CLASS,\n} from \"../messages/message-components\";\nimport { AnswerLabelScope } from \"../messages/answer-label\";\nimport { MessageActionsReadOnlyProvider } from \"../messages/message-actions-config\";\nimport { UserEditComposer } from \"../messages/user-edit-composer\";\nimport { ThreadRunningIndicator } from \"../messages/running-indicator\";\nimport {\n HandoffArrivalAfterMessage,\n HandoffArrivalScope,\n} from \"../messages/handoff-arrival-divider\";\nimport { FromTheCallAfterMessage } from \"../messages/from-the-call-card\";\nimport { THREAD_VIEWPORT_FOOTER_ATTR } from \"./statewire-history-edge\";\n\nexport interface ChatThreadMessageComponents {\n UserMessage: FC;\n AssistantMessage: FC;\n /**\n * Rendered in place of a user message while it is being edited (the action\n * bar's Edit control). Defaults to the shared UserEditComposer.\n */\n UserEditComposer?: FC;\n}\n\n// One wrapper per message renderer, so a renderer keeps its identity across\n// shells and renders: a row renders its message through the component it is\n// handed, and a new component identity would remount every row.\nconst withArrivalDivider = (() => {\n const wrapped = new WeakMap<FC, FC>();\n return (Message: FC): FC => {\n const existing = wrapped.get(Message);\n if (existing) return existing;\n // After the message: the \"From the call\" card where a call's stretch\n // ends, then the arrival divider (the arrival came after the call).\n const WithArrivalDivider: FC = () => (\n <>\n <Message />\n <FromTheCallAfterMessage />\n <HandoffArrivalAfterMessage />\n </>\n );\n wrapped.set(Message, WithArrivalDivider);\n return WithArrivalDivider;\n };\n})();\n\ntype ThreadRowComponents = ComponentProps<typeof ThreadPrimitive.MessageByIndex>[\"components\"];\n\nconst ROW_KEY_SEPARATOR = \"\\u0000\";\n\n/**\n * The thread's messages, one row each under its row key (`deepAgentRowKey`).\n *\n * `ThreadPrimitive.Messages` keys a row by its message id. A page of older\n * history that folds into the first loaded turn (a long run's earlier steps)\n * gives that turn a new id on every page, so keyed by id the turn's row\n * remounted once per page: it flashed through its entrance animation, closed\n * what the person had opened in it and took the browser's scroll anchor with\n * it. The row key stays with the row the turn continues, so the page lands in\n * the mounted row. A message from another runtime keys by its id, as before.\n */\nconst ThreadRows: FC<{ components: ThreadRowComponents }> = ({ components }) => {\n const joinedKeys = useAuiState((s) =>\n s.thread.messages.map((message) => deepAgentRowKey(message)).join(ROW_KEY_SEPARATOR),\n );\n const rowKeys = useMemo(\n () => (joinedKeys === \"\" ? [] : joinedKeys.split(ROW_KEY_SEPARATOR)),\n [joinedKeys],\n );\n return (\n <>\n {rowKeys.map((key, index) => (\n <ThreadPrimitive.MessageByIndex key={key} index={index} components={components} />\n ))}\n </>\n );\n};\n\nconst DEFAULT_VIEWPORT_CLASS =\n \"relative flex flex-1 flex-col overflow-y-auto scroll-smooth\";\nconst DEFAULT_CONTENT_CLASS = \"w-full flex-1 px-3 pt-3\";\nconst DEFAULT_VIEWPORT_FOOTER_CLASS = \"sticky bottom-0\";\n\nconst DefaultScrollToBottom: FC = () => (\n <AuiIf condition={(state) => !state.thread.isEmpty}>\n <div className=\"flex justify-center pb-0.5\">\n <ThreadPrimitive.ScrollToBottom\n aria-label=\"Scroll to bottom\"\n title=\"Scroll to bottom\"\n className={THREAD_SCROLL_TO_BOTTOM_CLASS}\n >\n <ChevronDownIcon className=\"size-3.5\" />\n </ThreadPrimitive.ScrollToBottom>\n </div>\n </AuiIf>\n);\n\nexport interface ChatThreadShellProps {\n /**\n * Rendered as the first child of the panel — for absolutely-positioned\n * scrims that must cover the whole panel (AthenaChat's thread-loading\n * overlay). The shell does not force a positioning context; hosts include\n * `relative` in `panelClassName` when the scrim anchors to the panel.\n */\n overlay?: ReactNode;\n /**\n * Rendered above the viewport, inside the panel: header rows, slide-over\n * panels, and anything else the host docks at the top.\n */\n header?: ReactNode;\n /** Status/approval banners rendered between the header and the viewport. */\n banners?: ReactNode;\n /**\n * Nodes rendered inside the scroll viewport before the message column —\n * absolutely/sticky-positioned overlays (thread-switch skeletons,\n * scroll-to-top controls) and invisible observers (read receipts).\n */\n viewportOverlay?: ReactNode;\n /** Empty-thread state, rendered via ThreadPrimitive.Empty when provided. */\n welcome?: ReactNode;\n /**\n * Rendered inside the message column right above the messages — the\n * windowed wire's history edge (`StatewireHistoryEdge`): the \"N earlier\n * messages\" row that pages older history in and fetches cut tool outputs.\n */\n beforeMessages?: ReactNode;\n /** Message renderers for the thread's rows. */\n components: ChatThreadMessageComponents;\n /**\n * Wraps the messages element — for hosts that provide message-scoped\n * context (e.g. activity-grouping providers). Must render its argument.\n */\n renderMessages?: (messages: ReactNode) => ReactNode;\n /**\n * Rendered between the viewport and the composer: connection banners,\n * queued-message trays.\n */\n belowViewport?: ReactNode;\n /** The composer dock (or a read-only notice replacing it). */\n composer?: ReactNode;\n /**\n * Replaces the docked-panel card chrome — for hosts that render a\n * full-page column (AthenaChat's `aui-*` surface) instead of the\n * side-panel/task-pane card.\n */\n panelClassName?: string;\n /**\n * Value of the `--thread-max-width` variable read by width-capped\n * message/welcome/footer columns. Panels fill their width (the default,\n * \"100%\"); full-page hosts cap the column (AthenaChat defaults to 44rem).\n */\n maxWidth?: string;\n /**\n * Anchors the latest turn to the top of the viewport instead of pinning\n * scroll to the bottom (AthenaChat). Omitted = the primitive's default.\n */\n turnAnchor?: \"top\" | \"bottom\";\n /** Replaces the scroll-viewport chrome (padding, overflow tuning). */\n viewportClassName?: string;\n /**\n * Replaces the message-column chrome. Hosts whose welcome screen centers\n * vertically make this a flex column so the welcome node can grow.\n */\n contentClassName?: string;\n /**\n * Thread-level \"Thinking...\" indicator rendered after the messages. Hosts\n * whose message components render their own per-message indicators pass\n * null so the two never double up.\n */\n runningIndicator?: ReactNode;\n /**\n * Replaces the default centered scroll-to-bottom pill inside the sticky\n * viewport footer (AthenaChat floats a circular button above its composer).\n */\n scrollToBottom?: ReactNode;\n /**\n * Content docked inside the sticky viewport footer, after the\n * scroll-to-bottom control — for hosts whose banners + composer float over\n * the scrolling messages (AthenaChat) instead of sitting below the\n * viewport via `belowViewport`/`composer`.\n */\n viewportFooter?: ReactNode;\n /** Replaces the sticky viewport-footer chrome. */\n viewportFooterClassName?: string;\n /**\n * Marks the transcript read-only: mutating message actions (Edit,\n * Regenerate) hide while Copy stays available. For threads whose server\n * rejects dispatch (e.g. legacy pre-cutover conversations) — the runtime's\n * static capability flags cannot express a per-thread fence.\n */\n readOnly?: boolean;\n /**\n * The backend id of the thread on screen. When this surface picked the\n * conversation up from another one (`recordHandoffArrival`), the transcript\n * shows \"Continued from …\" after the last message that was already there.\n * A host that is a handover destination also mounts `HandoffCursorReveal`\n * for it in `viewportOverlay`, so the transcript opens at the message the\n * other surface was reading (hosts that never receive one, the Office pane,\n * don't carry it).\n */\n arrivalThreadId?: string | null;\n /**\n * The signed-in viewer's Athena user id (`user_…`), for the answer labels\n * (`AnswerLabel`): their own answers read \"for you\", and an answer for\n * another person is labelled even in a 1:1 chat. Omitted, answers name the\n * person by name and are labelled only once the thread holds several agents\n * or several people.\n */\n viewerUserId?: string | null;\n}\n\n/**\n * The shared thread layout: panel chrome, scroll viewport with welcome +\n * messages + running indicator, the sticky scroll-to-bottom control, and the\n * host-supplied composer dock.\n *\n * Hosts express their genuine divergences through the slots above; the\n * layout, scroll behaviour, and empty/running states stay identical across\n * surfaces so fixes land once.\n */\nexport function ChatThreadShell({\n overlay,\n header,\n banners,\n viewportOverlay,\n welcome,\n beforeMessages,\n components,\n renderMessages,\n belowViewport,\n composer,\n panelClassName,\n maxWidth = \"100%\",\n turnAnchor,\n viewportClassName,\n contentClassName,\n runningIndicator = <ThreadRunningIndicator />,\n scrollToBottom = <DefaultScrollToBottom />,\n viewportFooter,\n viewportFooterClassName,\n readOnly = false,\n arrivalThreadId = null,\n viewerUserId = null,\n}: ChatThreadShellProps) {\n const {\n UserMessage,\n AssistantMessage,\n UserEditComposer: UserEditComposerComponent = UserEditComposer,\n } = components;\n // A row resolves its renderer by component identity, so keep the merged map\n // referentially stable across renders.\n const messageComponents = useMemo(\n () => ({\n UserMessage: withArrivalDivider(UserMessage),\n AssistantMessage: withArrivalDivider(AssistantMessage),\n UserEditComposer: UserEditComposerComponent,\n }),\n [UserMessage, AssistantMessage, UserEditComposerComponent],\n );\n const messages = (\n <HandoffArrivalScope threadId={arrivalThreadId}>\n <AnswerLabelScope viewerUserId={viewerUserId}>\n <ThreadRows components={messageComponents} />\n </AnswerLabelScope>\n </HandoffArrivalScope>\n );\n return (\n <MessageActionsReadOnlyProvider readOnly={readOnly}>\n <ThreadPrimitive.Root\n className={panelClassName ?? THREAD_PANEL_CLASS}\n style={{ \"--thread-max-width\": maxWidth } as CSSProperties}\n >\n {overlay}\n {header}\n {banners}\n <ThreadPrimitive.Viewport\n turnAnchor={turnAnchor}\n className={viewportClassName ?? DEFAULT_VIEWPORT_CLASS}\n data-aui-thread-viewport=\"\"\n >\n {viewportOverlay}\n {/* No min-h-0 here: the column's automatic minimum height is what\n lets it extend past one screen, keeping the sticky footer's flow\n position after the full message list. Capping it detaches the\n footer (scroll pill, docked composers) as soon as the thread\n scrolls — proven in a live-browser probe of both structures. */}\n <div className=\"flex flex-1 flex-col items-center\">\n <div className={contentClassName ?? DEFAULT_CONTENT_CLASS}>\n {welcome != null ? (\n <ThreadPrimitive.Empty>{welcome}</ThreadPrimitive.Empty>\n ) : null}\n {beforeMessages}\n {renderMessages ? renderMessages(messages) : messages}\n {runningIndicator}\n </div>\n </div>\n <ThreadPrimitive.ViewportFooter\n className={viewportFooterClassName ?? DEFAULT_VIEWPORT_FOOTER_CLASS}\n {...{ [THREAD_VIEWPORT_FOOTER_ATTR]: \"\" }}\n >\n {scrollToBottom}\n {viewportFooter}\n </ThreadPrimitive.ViewportFooter>\n </ThreadPrimitive.Viewport>\n {belowViewport}\n {composer}\n </ThreadPrimitive.Root>\n </MessageActionsReadOnlyProvider>\n );\n}\n"],"names":["jsxs","Fragment","jsx","FromTheCallAfterMessage","HandoffArrivalAfterMessage","useAuiState","deepAgentRowKey","useMemo","ThreadPrimitive","AuiIf","THREAD_SCROLL_TO_BOTTOM_CLASS","ChevronDownIcon","runningIndicator","ThreadRunningIndicator","UserEditComposer","messageComponents","messages","HandoffArrivalScope","AnswerLabelScope","MessageActionsReadOnlyProvider","THREAD_PANEL_CLASS","THREAD_VIEWPORT_FOOTER_ATTR"],"mappings":"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"}
@@ -106,6 +106,10 @@ export interface ChatThreadShellProps {
106
106
  * The backend id of the thread on screen. When this surface picked the
107
107
  * conversation up from another one (`recordHandoffArrival`), the transcript
108
108
  * shows "Continued from …" after the last message that was already there.
109
+ * A host that is a handover destination also mounts `HandoffCursorReveal`
110
+ * for it in `viewportOverlay`, so the transcript opens at the message the
111
+ * other surface was reading (hosts that never receive one, the Office pane,
112
+ * don't carry it).
109
113
  */
110
114
  arrivalThreadId?: string | null;
111
115
  /**