npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@athenaintel/react

v0.28.1

Published

Athena React SDK — Build AI-powered chat applications with the Athena platform

Readme

@athenaintel/react

React SDK for building AI-powered chat applications with the Athena platform.

Installation

npm install @athenaintel/react

If you use the PropelAuth-specific provider from @athenaintel/react/auth, also install:

npm install @propelauth/react

Upgrading

The SDK pins its assistant-ui runtime (@assistant-ui/tap, store, core, react, react-statewire, statewire) at exact versions, and those packages carry React contexts and module singletons: an app must resolve exactly one copy of each. A fresh install does. An existing app does not necessarily — bun keeps a bun.lock's existing resolutions on bun add, so after an SDK bump the transitive @assistant-ui/react-statewire / statewire dependencies can keep an older nested @assistant-ui/tap next to the SDK's new one. Two tap contexts mount and the page renders blank with:

Error: AssistantTapContext is not available

Make the app immune with a resolve.dedupe list in vite.config.ts (the Athena computer templates ship it; other bundlers have an equivalent single-instance setting):

export default defineConfig({
  resolve: {
    dedupe: [
      '@assistant-ui/tap',
      '@assistant-ui/store',
      '@assistant-ui/core',
      '@assistant-ui/react',
      '@assistant-ui/react-statewire',
      'react',
      'react-dom',
    ],
  },
});

Then bump to the release you want and re-resolve from scratch. Lockfile-preserving commands do not do it — bun update and bun install --force keep the nested lockfile entries, and bun remove + bun add (or deleting only bun.lock) rewrites the lockfile but bun leaves the nested directories in node_modules, which Node still resolves first:

bun add @athenaintel/react@<version>      # the release you are upgrading to
rm -rf node_modules bun.lock && bun install
find node_modules -path '*/@assistant-ui/tap/package.json' | wc -l   # expect 1

Do not add @assistant-ui/* packages to your own dependencies to pin them: bun update @assistant-ui/tap on a package that is not a direct dependency behaves like bun add at the latest version and produces more copies, not fewer.

Quick Start

import { AthenaProvider, AthenaChat, Toolkits } from '@athenaintel/react';
import '@athenaintel/react/styles.css';

function App() {
  return (
    <AthenaProvider
      config={{ apiKey: 'your-api-key' }}
      tools={[Toolkits.DOCUMENT, Toolkits.WEB_SEARCH]}
    >
      <AthenaChat />
    </AthenaProvider>
  );
}

Provider Config

<AthenaProvider config={{ environment: 'staging' }}>
  <AthenaChat />
</AthenaProvider>

<AthenaProvider
  config={{
    apiUrl: 'https://iris.example.com',
    backendUrl: 'https://api.example.com',
    appUrl: 'https://app.example.com',
  }}
>
  <AthenaChat />
</AthenaProvider>

config accepts apiKey, token, getToken, apiUrl, backendUrl, appUrl, statewireSyncUrl, trustedParentOrigins, environment, extraRunConfig, and posthog. Top-level apiKey, token, apiUrl, backendUrl, appUrl, and environment props remain supported as compatibility aliases, but config is now the preferred API.

If config.appUrl is omitted, the provider resolves it from the parent bridge or from the matching Athena environment defaults. Known Athena staging and production apiUrl and backendUrl values automatically fall back to the corresponding frontend origin.

Use trustedParentOrigins when the SDK runs inside an iframe on a private-cloud or custom parent domain and you want to explicitly allow postMessage auth/config from that parent. Only the window that frames the app is heard. By default athenaintel.com hosts, localhost and your own origin are trusted, except the hosts that serve customer-written code (*.app.athenaintel.com, *.code-sandpack.athenaintel.com and friends; see "What's new in 0.28.0").

Transport (statewire only, as of 0.18.0)

The SDK speaks one chat transport: statewire. The provider attaches to the deep-agent statewire sync host at <statewireSyncUrl>/<threadId>, so the thread is hosted server-side and replicated live — history, reconnect, run scheduling, mid-run queueing, HITL approvals, and client tools (for recognized SDK hosts, e.g. computer-asset app_ids) all come from the server snapshot.

The legacy Iris /api/chat assistant-transport leg was deleted in 0.18.0 along with the transport prop — there is nothing to opt back onto. See Migrating from 0.17.

The sync endpoint resolves in this order: an explicit statewireSyncUrl; the statewireSyncUrl the host environment supplies (a Marathon-served computer asset mints it from /_athena/auth alongside backendBaseUrl, the bare Agora base — a trusted parent iframe can post the same fields); then the effective apiUrl host + /v2/threads (e.g. staging resolves to https://iris.stg.athenaintel.com/v2/threads, the same statewire base the mobile app and Chrome extension ship). Pass statewireSyncUrl to pin a custom host whose sync mount lives on a different origin. If nothing yields a URL (e.g. an unparseable custom apiUrl), the provider throws instead of falling back to an Athena default — credentials are never sent to a host you didn't configure. Hosts that predate the minted statewire fields keep working through the apiUrl derivation. Auth reuses the provider's existing credential plumbing: tokens ride Authorization: Bearer and API keys ride X-API-KEY. A getToken resolver is called with { force: true } right after the sync host refuses a credential, so a caching getter must mint a fresh token then; the persisted command lane is scoped to the signed-in user as well as the host, so a shared browser never resends one user's pending sends under the next user's token.

What the statewire transport wires up in AthenaChat:

  • Approvals (HITL): when the backend parks a run on an approval-gated tool call, an approval card renders above the composer with Approve/Reject (or Continue for a generic pause) and resumes the run through the statewire run/input command. Hosts that build their own thread UI should mount the exported StatewireApprovalCard themselves — without it an approval-gated run stays parked with no way to answer it. Its renderDetails, isHandledElsewhere, and readOnly seams take host-specific review UIs, requests another surface answers, and viewers without edit rights; the headless useStatewireApprovalRequest behind it is exported for hosts with their own decision UI.
  • Frontend tools: tools in frontendTools with a local execute are declared to the backend as client_tools and executed by the mounted client-tool bridge, which resumes the run with each tool's result.
  • Mid-run sends and the queue: a message sent while a run is active queues on the server. The queue panel above the composer (StatewireQueuedMessages) lists queued entries with remove, "Send now" (steer), Prioritize (run/prioritize), inline edit, and failed sends with Retry/Discard, plus Continue for a stopped run's queue head; hosts can opt into autoContinue for stranded queues and slot their own row renderer and editor.
  • Lean entries for hosts with their own composer: @athenaintel/react/chat-ui/statewire exports just the approval card, the queue tray, and the continue/run-ended affordances (plus the runtime-message card and Continue pill they render), and @athenaintel/react/tools exports the generic ToolFallback card with its primitives (ToolCard, ExpandableSection, NestedPtcMessages) — so a host that already ships its own composer and mention stack (Olympus) never pulls the SDK's Tiptap graph into its build. Tool names, icons, categories, and running copy come from the shared registry in @athenaintel/agent-runtime (getToolDisplayMeta), and success/failure from its classifyToolResult; the fallback takes host seams (displayName, icon, extraAction, notice, renderResult, onOpenAsset, hideInternalIdArgs) instead of being copied. @athenaintel/react/super-grouping exports the grouped ("conversational") view of an assistant turn — SuperGroupingCard, SuperGroupingFinalText, CollapsibleGroup, the activity-timeline derivation, and the tool-based grouping function — with slots for the host's text/tool renderers and controls (renderHeaderActions, renderRunningMeta, summaryDetails, waitingTools, expansionKey), so Olympus and the Chrome extension mount one card instead of copies.
  • In-repo hosts consume the package, not the source. @athenaintel/react is a workspace dependency of Olympus, the Chrome extension, and the Office add-in, resolved through this package's exports to the built dist/ (the guarded prepare script builds it on a cold bun install; scripts/prepare-react-sdk.ts rebuilds a missing or stale dist in front of each host's typecheck, test, and build scripts, and scripts/typecheck-all.ts runs it once before the repo typecheck). While editing the SDK against a running host (next dev, the extension's bun run dev, the add-in's vite), run bun run dev here so the dist follows your edits. Because the dist inlines the private @athenaintel/agent-runtime, a host that also imports agent-runtime directly runs two copies of it; any agent-runtime state shared between the SDK's components and the host has to live on globalThis (see registerChatAssetMentionHandler).
  • Errors and connection state: transport errors (send rejections, paywall refusals) render as a dismissible banner; a degraded connection shows a reconnect strip; a send into a thread created by the retired legacy runtime is refused with a dismissible read-only notice (start a new chat). Custom hosts can read all of it via useAthenaStatewireLifecycle.
  • Thread list: enableThreadList provides the persisted Agora session list; switching threads (or starting a new chat) re-attaches the statewire session to the selected thread while the host's own children stay mounted — sidebars keep their scroll position and search text, custom hooks keep their state, and only the per-thread pieces reset: the runtime and its client, AthenaChat's surface (composer draft, viewport scroll, approval cards, banners), and the thread-id contexts. A host composing its own chat from the statewire pieces keys any per-thread UI on useAthenaStatewireThreadId() (the attached thread's id, defined for a never-started chat too) the way AthenaChat does. ThreadList, SessionList, useAthenaThreadManager, useAthenaSessionAttention, and useRefreshThreadList read that state (and render nothing / return null without it). See Session list, attention and wake markers.

The agent prop only accepts collab_agent:<asset_id> refs (see below), because the statewire host always runs the Athena deep agent. Any other non-blank value throws at first render — it is a misconfiguration, and a silently ignored selector would let a deployment answer as the wrong agent. model defaults to the deep-agent default model (DEFAULT_STATEWIRE_MODEL) unless a collab agent supplies one.

Session list, attention and wake markers

A Devin-style session list on top of enableThreadList: every row says what the session asks of the viewer, what to do next, and which pull requests it opened; the active thread gets a persistent "waiting on you" strip; and a GitHub wake renders as a runtime marker instead of a user bubble.

import { AthenaChat, AthenaProvider, SessionList } from '@athenaintel/react';

<AthenaProvider enableThreadList config={{ token }}>
  <aside><SessionList /></aside>
  <AthenaChat />
</AthenaProvider>

Field contract (v1)

Rows of POST /api/conversations/threads/list carry, all optional (an older server omits them and the SDK reads undefined):

| REST field | SDK field (ConversationSummary / AthenaStatewireThreadSummary) | Values | |---|---|---| | session_status / run_status | sessionStatus / runStatus | status_v2 planes | | attention | attention | needs_you · working · ready · watching · done · error | | next_action | nextAction | one line, ≤ 80 chars | | attention_source | attentionSource | interrupt · run · pull_request · termination · wake · handoff | | is_favorited | isFavorited | boolean — toggled through setFavorited / POST /api/conversations/threads/favorite | | last_wake | lastWake | { source, reason, at } | | pull_requests (opt-in: include_pull_requests) | pullRequests | [{ repoFullName, number, url, title, state OPEN/CLOSED/MERGED, isDraft, reviewDecision, checksState SUCCESS/FAILURE/PENDING/NONE, updatedAt }] — [] none, null unreadable right now |

The per-user conversation index pushes attention, next_action, attention_source and is_favorited (never pull-request state), and the thread list folds every push into its rows through the same summaryFromConversationIndexEntry that renames them — a session flips to "needs you" live. deriveConversationAttention (exported) is the one attention rule: the server value when reported, else needs_you for a human gate, working for a live run, error for a failed one.

SessionList

Same visual language as ThreadList (Tailwind via cn, lucide icons), plus:

  • a status indicator per row — needs_you an orange dot, working a spinner, unread (and not needs-you) a blue dot, watching a clock, otherwise none;
  • a second line — nextAction coloured by attention (needs-you orange, ready green, error red), else the muted relative time of the last update;
  • one pull-request chip with the state icon of the primary pull request (open / draft / merged / closed): #123 links to that pull request; 3 PRs opens a list of all of them (in flight first, then the most recently updated), each a link to GitHub with its number, state and checks (a repository shared by all of them is named once in the list's header) — a sibling of the row button, so a click on it never selects the row;
  • an origin badge from appId / startChannel / triggerType (Chrome, Word, Computer, GitHub, API, …);
  • a trailing action cluster per row, revealed on hover / focus (a pinned pin and any action whose write is in flight stay visible) — every action a labelled <button> that is a sibling of the row control, never nested:
    • Rename (prop showRename, default true): the title becomes an input in place; Enter commits, Escape cancels, blur commits; an unchanged or empty title is never sent; the row shows the new title at once and rolls back on failure (POST /api/conversations/threads/rename, { thread_id, title } → the stored, trimmed title; 1–200 characters). A viewer who can pin a shared session cannot retitle it (the route runs the same EDIT gate as updateAsset(title)); the row rolls back on the 403. On an agora that predates the route (404 / 405) the action is withdrawn (renameAvailable) after one console warning.
    • Pin / Unpin (prop showPinToggle, default true): a <button aria-pressed>, filled while the session is pinned.
    • Archive (prop showArchive, default true): the row leaves the list at once and stays hidden until both list sources stop reporting it; a failure brings it back (POST /api/conversations/threads/archive). Archiving the active session moves the chat to a new one once the server has confirmed. Withdrawn on a backend without the route (archiveAvailable).
  • groups Needs you → Working → Pinned → Recent (prop grouped, default true; a session appears once, in the first group that applies), labelled in 11 px uppercase muted text; the active row has a bg-muted background and a 2 px accent bar; titles are text-sm font-medium, the next-action line text-xs in its attention tone and never bolder than the title; the pull-request chip sits on the meta line; every row control and action has a focus-visible ring;
  • hover prefetch (prop prefetchOnHover, default true): after the pointer has rested on a row for 120 ms (or when the row control takes focus) the list warms the provider's thread snapshot cache with that thread's REST state — once per thread per identity, at most two fetches in flight, failures ignored — so selecting it paints the transcript on the first frame instead of the loading scrim while the statewire attach lands (useAthenaStatewireThreadList().prefetchThread(threadId) for hosts drawing their own rows);
  • skeleton rows (indicator, title line, meta line) while the first page loads — including while no credential has resolved — and for a refresh that has no rows to keep; "No sessions yet." only after a settled empty answer (hasResolvedFirstPage); a background refresh keeps the rows it has;
  • a search box wired to the server-side search (prop showSearch, default true), Load more over the shared pager, and New Chat / refresh as in ThreadList.

The pin writes POST /api/conversations/threads/favorite ({ thread_id, is_favorited } → the server's state after the write) with the same credential as the other thread endpoints — setThreadFavorite is exported for hosts calling it themselves. The toggle is optimistic: the row flips (and moves between Pinned and Recent) on the click, the server's answer is written back, and a failure restores the previous state. The optimistic value is held as an override over both list sources — the fetched page and the pushed conversation index — until they report the toggled value, so a push that predates the write cannot flip the row back. Writes for one session are serialized: the row's pin is disabled while its write is in flight (favoritePendingThreadIds), and a setFavorited call made meanwhile records the value and sends at most one follow-up write once the first has answered, so the server never ends on an earlier click. Overrides and in-flight writes are scoped to the (backendUrl, principal) pair — a credential swap reads an empty map and drops the previous identity's answers. On an agora that predates the route the first toggle gets a 404 / 405 (isThreadFavoriteUnavailableError): the row is restored, one console warning is logged, favoriteToggleAvailable on the thread-list state turns false, and the row shows a pinned session's pin read-only, as before. The conversation-history hooks (useStatewireConversationHistory, useRestConversationHistory) expose the same toggle as setConversationFavorited / favoritePending / favoriteToggleAvailable beside setConversationReadState.

The provider asks for pull-request state by default in thread-list mode (threadListIncludePullRequests, default true; turn it off in a host that never shows the chips to spare the server the lookup). The pure rules behind the rows — groupSessions, sessionIndicator, sessionSecondaryLine, pullRequestChip, orderedPullRequests, sessionOriginLabel — are exported for hosts drawing their own rows; useAthenaStatewireThreadList().setFavorited({ threadId, isFavorited }) is the toggle behind them.

useAthenaSessionAttention(threadId?)

{ threadId, attention, nextAction, attentionSource, pullRequests, isUnread } for the active (or given) thread, from the thread-list state; null outside thread-list mode or for a thread the list does not hold. Updates live with the index pushes.

WaitingOnYouBanner

Mounted by AthenaChat above the thread viewport: a persistent strip while the active thread's attention is needs_you or the approval hook (useStatewireApprovalRequest) reports a pending request. Its text is the server's nextAction, else a summary of the pending request; a click scrolls to the approval card (or to the composer when there is none). It never duplicates the card's Approve / Reject buttons. WaitingOnYouBannerView is the pure presenter for hosts that resolve the inputs themselves.

Wake markers

A user turn injected by a GitHub webhook wake carries a provenance envelope under metadata.custom.github_event_source. AthenaChat's user message (and the chat-ui UserMessage) render such a turn as a compact runtime marker — "Woke: review comment on #36337", linked, with the sender and the comment excerpt — in the same style as the runtime-ending card, instead of a bubble quoting the whole wake text. readGithubWakeEvent, isGithubWakeMessage, githubWakeMarkerText, WakeMarkerCard and WakeMarkerMessage are exported (also on @athenaintel/react/chat-ui and /chat-ui/statewire).

The envelope is message metadata, so the reader trusts only the shape the wake handler stamps: the repository must be owner/name, and the link is kept only when it is an https://github.com/<repo>/… URL for that repository (githubWakeEventUrl) — anything else renders the reference as plain text. The full message text always reaches the model; the marker is provenance for rendering only.

Voice Sessions (@athenaintel/react/voice)

useAthenaVoiceSession runs a real-time voice conversation with an Athena voice agent and streams the transcript into React state. It owns the whole browser side — session creation, room connect, mic, agent audio, live/interim transcript turns, reconnects, StrictMode-safe teardown — so an app is a start() button and a transcript list.

bun add @athenaintel/react livekit-client   # optional peer dep, only needed for voice
import { useAthenaVoiceSession } from '@athenaintel/react/voice';

const voice = useAthenaVoiceSession({
  session: {
    system_prompt: 'You are a customer at an auto-parts store...',
    provider: 'openai',      // "google" | "openai"
    model: 'gpt-realtime-2',
    voice: 'marin',
    enabled_tools: [],       // [] = no tools; omit for the server default set
  },
  onTranscript: (turn) => console.log(turn.speaker, turn.text),
});

// voice.status: 'idle' | 'connecting' | 'connected' | 'reconnecting' | 'ended' | 'error'
// voice.transcript: AthenaVoiceTranscriptTurn[]   voice.sessionId: asset id
<button onClick={voice.start}>Talk</button>
<button onClick={voice.stop}>Hang up</button>

Voice tools. enabled_tools takes voice tool names (AthenaVoiceToolId), not the chat Toolkits ids — 'web_search_browse_toolkit' is not a voice tool, 'web_search' is. Available: web_search (search_web is a legacy alias), browse (read a URL), search_knowledge_base and search_knowledge_base_lr (the workspace knowledge base; _lr is the thorough long-running retrieval), read_full_asset and search_assets (as the viewer), ask_agent (hand a question to the general agent), save_note, get_current_time_est. Tools that call back into the client over room RPC (write_to_clipboard, the desktop and mobile tools) are not part of the hook's contract. Omit the field for the server's default set; pass [] for pure conversation. The server refuses unknown names with a 422.

Silent start. By default the agent opens the call with a greeting. Pass silent_start: true and it stays quiet — no greeting audio, no greeting transcript turn — until a participant speaks (a deposition witness, a customer waiting to be addressed). Replies after that are unchanged. Silence is enforced by not asking the model to speak; with the full-duplex gpt-live-1 model the first reply still follows whatever the model hears, so verify on a real call if a pre-speech greeting would matter.

Authentication. Served from a Marathon computer-asset URL, the hook is zero-config: it takes the viewer bearer from useParentBridge() and creates the session as the person using the app, so it shows up under their Sessions in Athena and transcript/grading calls are authorized as them. This works for workspace members and for external SSO viewers alike: the viewer credential minted by /_athena/auth is authorized for session creation and grading the same way it is for /api/v0/sessions, scoped to the viewer's own workspace. Anywhere else — standalone hosting, custom auth — pass createSession and create the session from your own server (API key stays server-side):

useAthenaVoiceSession({
  createSession: (signal) =>
    fetch('/api/voice/session', { method: 'POST', signal }).then((r) => r.json()),
  // → { server_url, token, room_name, asset_id }
});

Off Marathon without createSession, start() fails with an explicit error; it never falls back to a shared key.

After the call. voice.sessionId is a regular Athena session asset:

  • Transcript: GET /api/v0/sessions/{asset_id}/download?format=messages
  • Grading against a rubric: POST /api/v0/agents/general/invoke with config.structured_output (a JSON Schema) → typed structured_output in the response.

Both use the same bearer / API key as session creation. The full recipe (persona → roleplay → transcript → rubric score) lives in the computer template skill voice-session-guide.md. createAthenaVoiceSession is also exported if you hold the bridge state yourself.

What's new in 0.28.0

The iframe auth bridge only listens to the page that frames your app, and that page can no longer point the SDK's token at another server. When the SDK runs inside an iframe, useParentBridge takes athena-auth (a token) and athena-config (URLs) from the parent window. Before this release it took them from any window on any *.athenaintel.com origin, including the hosts that serve customer computer and application apps, and an athena-config could replace backendBaseUrl / statewireSyncUrl at any time. A page on one of those hosts could frame or open another app and make its SDK send its token to a server of its choosing. Now:

  • A message counts only when its source is the window that frames the app (window.parent). A popup, an opener or a sibling frame on a trusted origin is ignored.
  • Hosts that serve customer-written code are no longer trusted parents by default: Marathon's computer and application apps ({id}.app.athenaintel.com, {id}.staging-app.athenaintel.com, {id}.canary-app.athenaintel.com, {id}.app.{env}.athenaintel.com, {id}.computer.stg.athenaintel.com, {id}.computer-canary.stg.athenaintel.com) and User Interface previews ({id}.code-sandpack.athenaintel.com). Every other athenaintel.com host (Olympus on app, staging-app and canary-app.athenaintel.com and its previews among them), localhost, your own origin and every origin you list in trustedParentOrigins are trusted as before.
  • A URL in athena-config (backendBaseUrl, statewireSyncUrl, appUrl) is taken only when it is https: (plain http: only between localhost, 127.0.0.1 and [::1]), carries no credentials, is not one of the customer-code hosts above, and is either an Athena host (athenaintel.com) or, for a parent outside athenaintel.com, the parent's own host or a subdomain of it (portal.example.com → portal.example.com, api.portal.example.com). Sibling hosts are refused: without a public-suffix list example.co.uk or tenant.github.io cannot be told apart from a private domain.
  • The first athena-config sets the URLs. A later one cannot change a URL or add one the first left out, so a running SDK cannot be repointed. Resending the same values is fine.
  • A refused URL emits the new diagnostics event auth.bridge.config_rejected (level warn, with field and reason: insecure, other_domain, customer_app_host, invalid or changed). When the refused URL is the statewire mount, the provider stops with the existing "without a statewire sync mount" error, which now names that cause.

useQuoteFromPostMessage (mounted by AthenaChat) now stages athena-add-to-composer text only from a frame embedded directly in the page. Any origin is still accepted, but a popup, an opener or another tab can no longer stage text.

The /_athena/auth mint (an SDK app served top-level by Marathon) is unchanged.

Host changes: none for Olympus or a Marathon wrapper. A wrapper whose page lives on a customer-code host passes its origin in trustedParentOrigins; one whose backend is neither an Athena host nor under its own domain (or that runs locally on a host other than the loopback names above) passes its URLs through config (backendUrl, statewireSyncUrl, appUrl). Explicit config always wins over the bridge.

What's new in 0.26.6

Queued messages read like the message you sent. In the StatewireQueuedMessages tray (mounted by AthenaChat), an [@name](…) mention renders as a mention chip instead of the raw markdown link, and a long message clamps to two lines behind a Show more / Show less toggle. A host that passes renderContent keeps its own renderer.

What's new in 0.26.5

  • useAthenaVoiceSession({ session: { silent_start: true } }) starts the voice agent without its opening greeting; it waits for the first participant to speak. Forwarded to agora's POST /api/orpheus/create-session, which needs an agora that accepts silent_start (staging from 2026-10-05).

What's new in 0.26.4

A | inside a table cell stays in its cell when a draft is sent. The composer wrote a pipe inside a table cell bare, so the sent message (and a draft restored into the composer) read it as a cell boundary: a | b became two cells, a code span holding x|y was split, and the cells after it moved over or were dropped. A pipe inside a cell, code spans and link targets included, is now written as \|, which reads back as a literal pipe. Tables without pipes, tables written as HTML and everything outside tables are written as before.

No host code changes are needed.

What's new in 0.27.0

A failing Marathon token mint stops retrying and fails closed. Before this release, when /_athena/auth answered an error other than 404 (for example a 502 {"error":"mint_failed"}), useParentBridge let the app continue with no token and retried in the background forever (every 1 to 5 minutes). Now each mint (the first one, the refresh before the token expires, or a forced renewal) is one bounded cycle:

  • A transient failure (5xx, 408, 429, a network error, or no answer within 2 s) is retried with capped exponential backoff and jitter: at most 4 requests in all, about 0.5 s, 1 s and 2 s apart.
  • A refusal (401, 403, or any other 4xx) and a 2xx without a JSON body are not retried.
  • A 404 still means "no Marathon bridge on this origin", and the other credential sources apply as before.
  • When a cycle gives up, the SDK reports one AthenaSdkError to onError. Its code is auth_bridge_failed, or auth_bridge_timeout when the last attempt timed out. It has the HTTP status, the host's error string in detail.code, and context.attempts. The same error is on the new useParentBridge().error field. The SDK starts no new cycle on its own after that, with one exception: when it still holds a token from an earlier mint, the single refresh already scheduled before that token expires still runs, as one bounded cycle. renewToken({ force: true }) starts one new bounded cycle, and a successful mint clears error. A 2xx answer without a token counts as a failed mint.
  • The bridge never puts an empty token in place of a real one. A renewal that gives up keeps the token that an earlier mint handed out.
  • AthenaProvider renders nothing when the mint gave up and it has no other credential (token, getToken, apiKey, or a sign-in provider's token). Before, it attached the chat with no credential to the default environment. To try again, remount the provider (for example, change its key).

New diagnostics event: auth.bridge.retry (level warn, one for each retry, with status, attempt, max_attempts and delay_ms). ParentBridgeState gains the optional error field.

No host code changes are needed. To show a clear state when the mint fails, handle auth_bridge_failed / auth_bridge_timeout in onError.

What's new in 0.26.3

A thread opens on its newest prompt, not halfway through a long agent run. When the newest turn is longer than the first page of history, the thread keeps loading until the message that started it shows, along with what came right before it.

What's new in 0.26.2

"Load earlier messages" and scrolling to the top of the thread show older history even when the oldest loaded turn is one long agent run. A page of history that lands entirely inside the first loaded turn (its tool steps) no longer uses up the request: the thread keeps paging until an earlier message appears.

What's new in 0.26.1

Source and mention chips in chat messages render their favicon/thumbnail at chip size again. The 64px prose image-thumbnail rule no longer applies to images inside not-prose chips.

What's new in 0.26.0

The composer and message bodies run on Tiptap 3. @athenaintel/react/chat-ui (the composer, the @ mention menu and the chat message body) moves from Tiptap 2 to Tiptap 3.31.3, with tiptap-markdown 0.9. Drafts, markdown and rendered messages are unchanged: the schema keeps its node and mark names, Enter after a typed 1. or - still starts a list instead of sending, and Shift+Enter still inserts a line break. An app that also imports Tiptap itself should move to Tiptap 3 as well, so its bundle carries one copy of @tiptap/core and ProseMirror.

No host code changes are needed.

What's new in 0.25.0

A refused token no longer leaves the app on "Unauthorized". Before this release, a deploy or a long idle could close the chat connection, and the reconnect sent the token the server had just refused. Nothing renewed it, so the app stayed on "Unauthorized" until a reload. Now every sign-in mode feeds one credential source, config.getToken, and each mode renews its own token:

  • AthenaAuthProvider (PropelAuth) and AthenaSSOProvider renew through the sign-in provider. AthenaAuthState gains an optional getToken, which a custom auth provider can set too.
  • A Marathon-served app re-mints /_athena/auth; an app inside a parent window asks the parent for a new token. useParentBridge() now also returns renewToken({ force }) (the ParentBridge type).
  • A host config.getToken is called as before, with { force: true } to renew. A static token and an API key never renew.

A token that expires within 30 seconds (read from the JWT exp) is renewed before it is sent, and requests that need a renewal at the same time share one. After a refusal, the chat connection and the session list restart as soon as a new token arrives, or when the tab becomes visible or comes back online. Each refusal gets at most one restart, so a credential the server keeps refusing never loops.

Every SDK request renews a refused token once. After a 401, these requests resend once with a renewed token: the session list and its actions (archive, pin, rename, read state), the account lookup, snapshot prefetch, dictation uploads, repository search, voice session creation, collab and presence token mints, mention search, asset embeds and attachment previews. A 403, a second 401, API-key requests and a static token that renewal cannot change are not retried.

  • CollabAuth gains getToken, and useWorkspacePresenceResolver reads the current token on every call.
  • MentionAuthContext gains renewAccessToken, and its getAccessToken may resolve nothing, which falls back to accessToken.
  • useAssetEmbed accepts getToken, which renews the held token.
  • useGenericDoc and useWorkspacePresence keep their connection when the token is renewed for the same user (the JWT sub), and rebuild it when someone else signs in, including under a host getToken.

No host code changes are needed.

What's new in 0.24.1

See every pull request a session links. In SessionList, a chip that counts several pull requests (3 PRs) is now a button. It opens a list of all of them, in flight first and then the most recently updated. Each row shows the title with the number, state and checks beneath it, and links to GitHub in a new tab. When every PR is in one repository, the header names it once; otherwise each row names its repository. A single pull request (#123) is still a direct link. New export: orderedPullRequests, the order the chip and the list share (its first entry is the one the chip stands for). A session now links a pull request only when it opens it, comments on it, or is asked to work on it; reading one no longer links it (#37792).

What's new in 0.24.0

Dictation is transcribed by Athena. The composer mic now records with MediaRecorder and, on Stop or Send, posts the clip to agora's POST /api/transcription/upload, which runs Deepgram Nova-3 and returns the text; the strip shows "Transcribing…" until the words land. Accuracy is Deepgram's, every language Deepgram supports works (the session lang is forwarded), and the mic is available in Firefox. There are no live interim captions. A recording stops on its own after 5 minutes and is transcribed as if Stop had been pressed (createRecordedDictationEngine({ maxDurationMs }) to change it); agora caps a clip at 10 MB and rate-limits uploads per user (20 a minute by default). Pass dictation={{ engine: createWebSpeechDictationEngine() }} to keep the browser's live Web Speech engine. Needs an agora that serves /api/transcription/upload (staging from 2026-09-24; production with the next release train).

Breaking: useDictation().stop() resolves to { text, error } (DictationStopResult) instead of a string, so a custom composer can tell a failed transcription from silence and keep the draft instead of sending it; AthenaChat does exactly that. DictationEngine gains optional flushTimeoutMs (how long stop() waits for the flush; 1.5 s live, 90 s recorded) and stoppingLabel; the controller exposes engine; DictationStrip takes stoppingLabel. New exports: createRecordedDictationEngine, useAthenaDictationEngine, DictationTranscriber, DictationTranscribeContext, RecordedDictationEngineOptions, DictationStopResult.

What's new in 0.23.3

Dictation in the composer. <AthenaChat> now has a microphone left of Send. Click it and the editor row becomes a live waveform; Stop puts the spoken words into the draft for review, Send sends them (queued while a run is live). The default engine is the browser's Web Speech API (Chrome, Edge, Safari) — no backend, no extra dependency; the mic hides itself where the engine is missing (Firefox). Pass dictation={false} to remove it, or dictation={{ lang, engine }} to set the language or plug in another speech engine (DictationEngine interface, useDictation() hook, DictationStrip component are exported for custom composers).

Composer slots. New composer prop on <AthenaChat>: toolbarStart renders after the attach button, toolbarEnd before the mic and Send, and onBeforeSend runs host bookkeeping before every submit (dictated sends included). Slot components render inside the chat's thread scope, so useAthenaRunLive() works in them; the template's model-picker portal is no longer needed.

Frontend tools bind reliably. Two fixes to the frontendTools run config: (1) tools with an execute() no longer also ride enabled_tools — doing so switched on any backend toolkit owning a tool of the same name, and the backend won the collision, so the client tool was silently dropped (a frontend notify_user ran the backend notification cascade instead); (2) declaring executable frontend tools without a recognised appId now emits an sdk.config.warning error through diagnostics and onError, because the deep-agent runtime drops every client tool for an unknown app_id without telling the client.

What's new in 0.23.2

  • Assistant turns no longer draw the grey vertical rule down their left edge. AthenaChat renders the lead column only when the host passes an AvatarComponent; without one the reply sits flush with the thread column and is told apart from the right-aligned user bubbles by alignment alone.

What's new in 0.23.1

  • Hotfix: 0.23.0 crashed AthenaChat on mount in published apps ("The chat hit an error", Cannot read properties of undefined (reading 'status')). The message-window hook shipped in 0.23.0 reads connection from the statewire extras, which exists in @assistant-ui/react-statewire 0.29 / statewire 0.19 — the versions the monorepo resolves and tests against — but the package still pinned 0.28.1 / 0.18.2 for consumers. The pins now match the tested versions, and a test fails the build if they drift again.

What's new in 0.23.0

0.23.0 is the Devin-style sessions release: a session list that says what each session asks of you, live pull-request state, a "waiting on you" strip, wake markers, hover actions (rename / pin / archive) with skeleton loading and hover prefetch on that list, a provider whose host UI survives thread switches, a paper composer with a staged welcome, a 46rem reading column with one tool-card anatomy and grouped turns by default, assistant text that keeps its source links and read_asset citations as chips, and the AI Chat computer template rebuilt around them. No breaking changes to the API; <AthenaChat groupToolCalls> now defaults to true (pass false for the previous inline tool cards) and the AI Chat template's VITE_CONVERSATIONAL_VIEW defaults on. Shipped in this release, by pull request:

  • fix(react): AthenaProvider keys only the per-thread runtime so host UI survives thread switches; new export useAthenaStatewireThreadId (#36402)
  • feat(react): SessionList with attention, next action and pull-request chips; waiting-on-you banner; wake markers (#36365)
  • feat(react): SessionList pin toggle via POST /api/conversations/threads/favorite (#36371)
  • fix(react): pin toggle serializes same-thread writes and scopes overrides per identity (#36373)
  • feat(agora,react): admin-only repository code search route + useAthenaRepoSearch (Devin-style Ask) (#36302)
  • feat(agora): live pull-request state on the session list from agent_pr_threads (Devin-style sessions) (#36306)
  • feat(deep-agent): the message window per viewer, rebuilt on Statewire documents and per-attach interests (#36322)
  • fix(athena-app): dedupe @assistant-ui runtime packages so an SDK upgrade cannot mount two tap contexts (#36395)
  • feat(react): paper composer with clear send states, Enter-to-send hint and a staged welcome screen (#36412)
  • feat(react): thread reading column, scannable turns, one tool-card anatomy, anchored scroll and a quiet thinking line (#36413) — see the thread's reading column under Styling
  • feat(react): one tool-card anatomy for the SDK thread — ToolFallback thread appearance, grouped turns by default (#36411) — see Tool cards in the thread
  • feat(react): SessionList hover actions with inline rename, skeleton loading, hover prefetch and tighter rhythm; props showRename / showArchive / prefetchOnHover, thread-list state renameThread / prefetchThread / renameAvailable / archiveAvailable / hasResolvedFirstPage (#36415) — see SessionList
  • feat(agora): REST rename for session threads (POST /api/conversations/threads/rename), the backend of the inline rename (#36409)
  • fix(react): assistant text keeps source links and read_asset citations as clickable chips; TiptapText renders through ChatMessageBody (#36414)
  • fix(react): message body re-parses only the trailing block and settles streamed words — block memoization, settled streamed words, deferred fence rendering while a reply streams (#36426)

Sources and citations (#36414)

  • Sources and citations render as chips in every SDK chat surface. The static message body (MarkdownText / ChatMessageBody, and AthenaChat's default TiptapText, which now renders through it instead of a live Tiptap editor per message) turns the deep agent's citation anchors into chips: a web source (<a class="mention" data-type="url" data-url="…" data-excerpt="…">, or any <a href data-excerpt>) becomes a favicon chip whose tooltip is the excerpt and which opens the page at the excerpt (#:~:text=); a read_asset citation (<a class="mention" data-type="pdf" data-id="asset_…" data-page-number=… data-excerpt="…">) becomes an asset chip deep-linked to the cited page (anchor=page / page_range) with the excerpt as its tooltip; a citation the model left unlabelled reads Page N instead of an empty chip. Markdown links, bare URLs and mailto: stay plain links; only http(s):, mailto:, athena-point: and app-relative Spaces hrefs pass (javascript:, ftp:, data:, //host are stripped to their label). Every anchor click goes through useAthenaLinkClickHandler (linkClicks, then the asset panel for an Athena citation); modified clicks stay with the browser. Favicons are requested only for public-looking hosts.

Composer and welcome (#36412)

  • Paper composer. <AthenaChat> renders the shared composer dock's appearance="paper" chrome: a card-toned surface (bg-card, hairline border, 22px radius — still bound to the composerBorder / composerRadius theme keys), a visible focus-within ring with a soft shadow, a data-drag-active="true" tint while files hover, and a bottom rail with attach + @ mention on the left and send / stop on the right. Send is an ink circle with the arrow that reads inert at 30% while the draft is empty; stop is the same circle with a filled square. The draft grows to about eight lines (12rem) before it scrolls. The first time the composer is focused in a browser it shows "Enter to send · Shift+Enter for a new line" underneath, until the first send (localStorage athena-sdk:composer-hint-seen). Enter sends and Shift+Enter breaks the line on every surface, as before.
  • Staged welcome. The greeting (h2) fades and rises in, the suggestion cards stagger in at 120 ms + 70 ms × index directly above the composer (equal-width in one row from a 40rem-wide thread, a two-column grid below), and the composer arrives last when the thread mounts empty — at 360 ms behind the default four cards, behind the last card when a host passes more. prefers-reduced-motion renders all of it in place with no motion.
  • @athenaintel/react/chat-ui: ChatComposerDock gains appearance ("default" | "paper", default unchanged), dragActive and belowSurface; TiptapComposer gains appearance, onSubmitted and the handle method openMentionMenu(); ComposerDropZone gains onDragActiveChange. The Chrome extension and Office add-in keep the default chrome and are unaffected.

The AI Chat computer template (docker/athena-app) pinned to this version mounts SessionList in its sidebar (hover rename / pin / archive, skeleton loading and hover prefetch at their defaults), shows the active session's title and attention state in its header, keeps ?session_id in step with the active thread (restored once per page load; one history entry per switch, so Back / Forward walk sessions), scopes the thread-loading overlay to the chat column, and adds a Cmd/Ctrl+K command palette with Cmd/Ctrl+Shift+O (new chat) and Cmd/Ctrl+] / Cmd/Ctrl+[ (next / previous session).

What's new in 0.22.4

Message actions catch up with Olympus Spaces: assistant replies gain a Regenerate button and a branch picker; user bubbles gain a hover-revealed Copy / Edit bar (Edit hides on read-only threads, while a run is live, or when the runtime lacks the capability) plus a branch picker; code blocks in replies get a copy button; the composer placeholder now hints at @ mentions. New optional readOnly prop on AthenaChat hides Edit and Regenerate for transcripts whose server rejects dispatch.

What's new in 0.22.3

The assistant message action bar (copy / more) no longer shifts the message on hover: it stays mounted and reveals with opacity instead of unmounting, so older replies keep their footer height. No API changes.

What's new in 0.22.2

Public voice docs, JSDoc/.d.ts and the session-creation error message now use product-neutral wording (no internal service or transport names). No API changes.

What's new in 0.22.1

Tailwind Typography colors now follow theme tokens inside SDK roots; userBubbleForeground / assistantForeground apply to message text, headings, bold, inline code and blockquotes.

What's new in 0.22.0

0.22.0 adds a read-only workspace presence feed to @athenaintel/react/collab: useWorkspacePresence(workspaceId) (and its vanilla core connectWorkspacePresence) subscribes to the Keryx workspace awareness room — the same per-recipient-filtered roster the Olympus People page renders — and resolveWorkspacePresence / useWorkspacePresenceResolver turn the ids it carries into titles. Admin-only. See Workspace presence. No breaking changes.

What's new in 0.21.0

0.21.0 adds the @athenaintel/react/voice entry: useAthenaVoiceSession (room lifecycle, mic, agent audio, live transcript turns, reconnect, StrictMode-safe) and createAthenaVoiceSession. On Marathon it is zero-config and attributes the session to the viewer (workspace members and external SSO viewers); elsewhere pass createSession. See Voice Sessions. No breaking changes.

What's new in 0.20.0

0.20.0 finishes the composer unification and retires the last chat-wire URL shapes. It is a minor release with breaking changes for consumers of the retired root-entry composer, the parent-bridge URL fields, and the URL defaults; <AthenaProvider> and <AthenaChat> accept the same props.

  • One composer, one mention stack. <AthenaChat> renders the shared chat-ui Tiptap composer and @ mention menu — the ones the Chrome extension and the Office add-in mount — so its menu offers the asset library (Favorites, Assets, Projects, Folders) beside the Tools / Toolkits list, with the same Enter-to-send / Shift-Enter behaviour on every surface. The mentionTools prop keeps its shape (MentionTool[], still exported from the root entry; it defaults to the provider's enabled toolkits). Quote (Cmd+L) and attachment pills compose into the send as before; a draft that is only a quote or attachments sends from Enter and the send button alike, and a submit during an attachment upload waits for the upload to land.
    • Removed root exports: TiptapComposer, TiptapComposerProps, useMentionSuggestions. Import the composer from @athenaintel/react/chat-ui (TiptapComposer, useMentionSuggestions, MentionTool, MentionRootCategory).
    • Outgoing text is Markdown on every surface. The shared composer serializes the draft with tiptap-markdown configured as the Olympus chat composer configures it, so <AthenaChat> keeps what 0.19 kept — a pasted hyperlink sends as [text](url), bold / italic / code marks and paragraph breaks survive, a Shift-Enter break is a backslash-newline — and the Chrome extension and Office add-in gain the same (they sent plain text before). A bare URL autolinks. Mentions serialize as the chat-ui stack does: tools are [@Name](mention://tool?name=<tool_id>&description=…) (was ?tool=), toolkits [@Name](mention://toolkit?name=<toolkit_id>&description=…) (was ?id=…&icon=…), and assets [@Title](/dashboard/spaces?asset_ids=<id>&assetType=<type>). A host that parses the outgoing text for the old query keys must read name. The draft mirrored into the composer runtime is that Markdown too, and a restore (a send refused during reconnect, useAppendToComposer) parses it back — mention links become chips again. serializeDraft(editor) is exported from @athenaintel/react/chat-ui; plainTextToTiptapContent is gone (the restore path parses Markdown now).
    • @athenaintel/react/chat-ui: TiptapComposer gains tools (a host's own Tools / Toolkits list in place of the server catalog), fileIntake ({ onPaste, onDrop? } — a host's own intake for pasted and dropped files; without onDrop the composer leaves drops to an enclosing drop zone) and waitForHostUploads (awaited before every submit, so a host-owned upload lands before the send); useMentionSuggestions gains tools. interceptSubmit now runs before the empty-draft check and may receive an empty string (a host composing extras of its own into the send) — an interceptor that only handles text returns false for it. A paste that carries meaningful text beside a bitmap (an Excel, Sheets or Word copy) now pastes the text on every surface instead of attaching the screenshot; a copied image file's own name (Finder, Explorer) is not such text. resolveComposerAction and ComposerSendOrStop drop the canQueue option — statewire always hosts a queue, so a mid-run draft always queues.
    • @athenaintel/react/styles.css styles the @ chips (.mention-chip) from the theme tokens, so they follow a dark theme and render in message text without a mounted composer.
  • Bare-origin URL defaults. The environment presets are origins: apiUrl is the Iris host (https://iris.prd.athenaintel.com, https://iris.stg.athenaintel.com; its origin derives the /v2/threads statewire mount) and backendUrl is the Agora REST base (https://api.athenaintel.com, https://staging-api.athenaintel.com). deriveAthenaApiUrl returns the Iris origin without /api/chat; deriveAthenaAppUrl no longer recognizes the retired sync.athenaintel.com / staging-sync.athenaintel.com hosts; and useAthenaConfig().backendUrl is the bare base — the provider trims a historical …/api/assistant-ui or …/api/v0 value you pass explicitly before publishing it.
  • Parent bridge. useParentBridge() / ParentBridgeState drop the chat-wire apiUrl and backendUrl; a host mints statewireSyncUrl, backendBaseUrl and appUrl (Marathon has since 0.17). Those two fields in an athena-config message or a /_athena/auth body are ignored. The new hostAnswered field reports that a host answered the bridge at all, and <AthenaProvider> throws when a host answered without minting statewireSyncUrl and neither statewireSyncUrl nor apiUrl is configured — a host's credential is never sent to the environment preset. Deploy order: a Marathon older than 2026-08-29 (#32053) mints no statewire mount; update it before an app on 0.20.0 is served from it.
  • Dependencies. @assistant-ui/react-langgraph and zod are no longer dependencies (nothing in the SDK imported them after the retired composer and transport went).

What's new in 0.19.0

0.19.0 is a minor release with no breaking changes: every export that shipped in 0.18.0 is still exported under the same name from the same entry, and <AthenaProvider> / <AthenaChat> accept the same props. Bump the pin and re-install; there is nothing to migrate.

  • Windowed history on every surface. Each attach now declares a message window (ATHENA_SDK_MESSAGE_WINDOW, 40 messages, sent as the x-athena-deep-agent-window hello), so a long thread's first snapshot carries the transcript's tail instead of every message. <AthenaChat> mounts the new StatewireHistoryEdge row above the messages — "N earlier messages", paged in as you scroll near the top (or on click) while the reading position is kept — and cut tool outputs are fetched back for the cards that show them. Hosts composing their own thread mount StatewireHistoryEdge in ChatThreadShell's beforeMessages slot (also exported from @athenaintel/react/chat-ui and /chat-ui/statewire) and read the tool-output store through useStatewireToolOutputs. Editing a message below the loaded window resubmits with an anchored run/edit.
  • One approval card, queue tray, and continue affordance. StatewireApprovalCard (renderDetails, isHandledElsewhere, readOnly), StatewireQueuedMessages (renderContent, renderEditor, autoContinue, onEvent), and the continue / run-ended affordances are the single implementations Olympus, the Chrome extension, and the Office add-in mount; the headless useStatewireApprovalRequest and HITL_PENDING_RESUME_GRACE_MS are exported for hosts with their own decision UI. New lean entry: @athenaintel/react/chat-ui/statewire.
  • One tool display registry, result classifier, and fallback. getToolDisplayMeta, classifyToolResult, isToolResultSuccess, describeToolArgs, resultMessage, toolActiveLabel, toolCompletedLabel (re-exported from @athenaintel/agent-runtime), TOOL_ICONS / toolIcon, the ToolFallback host seams (ToolFallbackNotice, ToolFallbackOptions, ToolFallbackContext, …), CopyToolSpecButton, InstallPackageToolUI, and NestedPtcComponentsProvider / useNestedPtcComponents. New lean entry: @athenaintel/react/tools.
  • Grouped activity card and timeline. SuperGroupingCard, SuperGroupingFinalText, SuperGroupingProvider, CollapsibleGroup, buildActivityTimeline, useActivityExpansion, createToolBasedGroupingFunction, TOOLKIT_THEMES, and their types are exported; <AthenaChat groupToolCalls> renders through them. New lean entry: @athenaintel/react/super-grouping.
  • rejected is a first-class tool-result state. A gated tool call the user refused classifies as { state: 'rejected' } (ToolResultClassification) instead of an error, and reads as rejected on the tool card and in the grouped timeline.
  • Diagnostics instead of console noise. Composer, tool-UI, and mention failures now emit composer.attachment.error, composer.cancel.error, tools.schema.warning, tools.debug, and mentions.fetch.error on the diagnostics bus (onDiagnostic / onError, __ATHENA_SDK__.diagnostics) rather than through console.*.
  • config.getToken may be called with { force: true } (AthenaStatewireTokenGetter) right after the statewire host refused the previous credential; a getter that caches should mint a fresh token then. Existing zero-argument getters keep working unchanged.
  • Dependencies. The assistant-ui family is pinned exactly on one release train (@assistant-ui/react 0.15.17, @assistant-ui/react-statewire 0.24.0, assistant-stream 0.3.41, statewire 0.14.0); the SDK's internal zod moved to v4 (it is not part of the public types).

Moved, not renamed: ToolCard, ExpandableSection, normalizeResult, truncate, formatToolName, and ToolStatus now live in the tool primitives module and are exported from both @athenaintel/react (as before) and @athenaintel/react/tools.

Migrating from 0.17

0.18.0 deletes the legacy assistant-transport leg. Every Athena chat client now speaks the native statewire wire, and the TAG:JSON transport over Iris /api/chat is being removed server-side, so the SDK no longer offers it.

Removed from <AthenaProvider>:

  • transport prop and config.transport — statewire is the only lane.
  • customToolConfigs — it only ever reached the legacy leg (the deep-agent runtime has no custom_tool_configs reader; MCP configuration is the parsed mcp_server_configs key).
  • agent no longer accepts named agents (athena_assist_agent, …). The only accepted shape is collab_agent:<asset_id>; anything else throws with a message naming the prop and the accepted shape. Omit the prop to run the Athena deep agent. Validate ahead of time with resolveAgentProp({ agent }).

Removed exports:

  • useAthenaRuntime, AthenaRuntimeConfig, DEFAULT_API_URL, DEFAULT_BACKEND_URL — use useAthenaStatewireRuntime / buildStatewireRunConfig for a custom chat surface.
  • ATHENA_TRANSPORTS, DEFAULT_ATHENA_TRANSPORT, resolveAthenaTransport, allowsMidRunSend, AthenaTransport, ResolvedAthenaTransport, and the transport field on AthenaConfig (useAthenaConfig()).
  • convertLangChainToThreadMessages, autoCloseInFlightSubgraphMessages, PTC_SUBCALL_DID_NOT_COMPLETE_MESSAGE — the LangChain message converter existed only for the legacy wire.
  • StatewireLegacyReadOnlyBanner (rendered nothing since 0.17) and the no-op onLegacyReadOnly option of useAthenaStatewireRuntime. The residual legacy_thread_read_only 409 still surfaces through StatewireErrorBanner / useAthenaStatewireLifecycle.
  • useAthenaThreadListAdapter (the assistant-ui remote-thread-list adapter). useRefreshThreadList, useAthenaThreadId, and AthenaThreadIdContext remain and read the statewire thread-list state.
  • Error codes stream_failed and config_ignored; diagnostics events stream.connected and stream.error. sdk.transport still fires (always statewire).

Unchanged: apiUrl/backendUrl (explicit, host-minted, or from environment) — apiUrl's origin still derives the statewire sync mount when statewireSyncUrl is not set, and backendUrl is still the Agora REST base for thread lists, uploads, and asset embeds.

Collab Agents

A collab agent is an agent configuration authored in Athena (prompt, model, toolkits, behavior) and addressed as collab_agent:<asset_id>. Point the provider at one and the chat runs as that agent:

<AthenaProvider
  agent="collab_agent:asset_432af46f-293d-480b-a518-30b1f42a9ef7"
  channel="askbob_web"
>
  <AthenaChat />
</AthenaProvider>

Copy the snippet with real ids from the agent's Channels tab in Athena.

channel is optional. It selects a channel override layer — a built-in channel (email, sms, …) or a custom channel defined on that agent — so one agent can present different prompts, models, and tools per surface. Omit it to run the agent's base configuration.

Requirements

  • The ref is the only accepted agent shape. collab_agent:<asset_id> selects the agent; omitting agent (or a blank string) runs the Athena deep agent; any other value throws at render (resolveAgentProp is the same check, exported for hosts that validate configuration up front).
  • Publish the agent. Resolution reads the published snapshot, not the live draft, so a channel that exists only in the editor is rejected with collab_agent_channel_unknown.
  • The acting user needs VIEW access to the agent asset (admins bypass). Running as an agent exposes its prompt and tool policy, so the same gate that governs opening the asset governs running it.

Do not also pass model, systemPrompt, or tools

Request keys override the agent definition, so anything you pass here silently replaces what the agent's author configured — the run succeeds and returns a plausible answer using your config instead of theirs.

// ❌ the agent's prompt, model, and tools are all discarded
<AthenaProvider
  agent="collab_agent:asset_1234"
  model="claude-opus-4-6"
  systemPrompt="You are a helpful assistant."
  tools={['web_search_browse_toolkit']}
/>

// ✅ the agent's own configuration wins
<AthenaProvider agent="collab_agent:asset_1234" />

Pass them only when you deliberately want to override the agent — an explicit model is honoured as a caller override, with the agent's model as the default beneath it.

Failure codes

A refused selection surfaces as an AthenaSdkError with code: 'collab_agent_rejected'; the specific reason below rides in the error's detail. Subscribe with onError (see Debugging) rather than guessing from messages:

| Code | Meaning | |---|---| | collab_agent_not_found | No such asset, or it is not a collab_agent | | collab_agent_forbidden | The acting user lacks VIEW on the asset | | collab_agent_channel_unknown | No such channel (the message lists the known ones) | | collab_agent_channel_disabled | The channel exists but is toggled off | | collab_agent_channel_kind_mismatch | A voice channel was selected for a text run | | collab_agent_runs_disabled | The deployment has the SDK seam turned off |

Authentication

Same-origin SSO defaults

import { AthenaSSOProvider } from '@athenaintel/react/auth';
import { AthenaChat, AthenaProvider } from '@athenaintel/react';

function App() {
  return (
    <AthenaSSOProvider>
      <AthenaProvider enableThreadList>
        <AthenaChat />
      </AthenaProvider>
    </AthenaSSOProvider>
  );
}

By default, AthenaSSOProvider uses same-origin SSO endpoints:

  • /api/sso/userinfo
  • /api/sso/initiate
  • /api/sso/logout

This makes the SSO URLs optional when your frontend is reverse-proxied with the backend.

Cross-origin SSO backend

import { AthenaSSOProvider } from '@athenaintel/react/auth';

<AthenaSSOProvider ssoBaseUrl="https://api.example.com">
  <AthenaProvider
    config={{
      backendUrl: 'https://api.example.com',
      apiUrl: 'https://iris.example.com',
      trustedParentOrigins: ['https://app.example.com'],
    }}
    enableThreadList
  >