@athenaintel/react 0.37.0 → 0.38.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 (40) hide show
  1. package/README.md +41 -0
  2. package/dist/agent-runtime/src/messages.cjs +27 -1
  3. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  4. package/dist/agent-runtime/src/messages.js +27 -1
  5. package/dist/agent-runtime/src/messages.js.map +1 -1
  6. package/dist/agent-runtime/src/queue.cjs +4 -0
  7. package/dist/agent-runtime/src/queue.cjs.map +1 -1
  8. package/dist/agent-runtime/src/queue.js +4 -0
  9. package/dist/agent-runtime/src/queue.js.map +1 -1
  10. package/dist/agent-runtime/src/use-deep-agent-config.cjs +2 -1
  11. package/dist/agent-runtime/src/use-deep-agent-config.cjs.map +1 -1
  12. package/dist/agent-runtime/src/use-deep-agent-config.js +2 -1
  13. package/dist/agent-runtime/src/use-deep-agent-config.js.map +1 -1
  14. package/dist/auth/AthenaSSOProvider.cjs +202 -103
  15. package/dist/auth/AthenaSSOProvider.cjs.map +1 -1
  16. package/dist/auth/AthenaSSOProvider.d.ts +35 -4
  17. package/dist/auth/AthenaSSOProvider.js +202 -103
  18. package/dist/auth/AthenaSSOProvider.js.map +1 -1
  19. package/dist/auth/index.d.ts +1 -1
  20. package/dist/chat/AthenaChat.cjs +1 -1
  21. package/dist/chat/AthenaChat.cjs.map +1 -1
  22. package/dist/chat/AthenaChat.js +1 -1
  23. package/dist/chat/AthenaChat.js.map +1 -1
  24. package/dist/chat/StatewireQueuedMessages.cjs +18 -0
  25. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  26. package/dist/chat/StatewireQueuedMessages.d.ts +19 -1
  27. package/dist/chat/StatewireQueuedMessages.js +20 -2
  28. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  29. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  30. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  31. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  32. package/dist/diagnostics/errors.cjs +7 -0
  33. package/dist/diagnostics/errors.cjs.map +1 -1
  34. package/dist/diagnostics/errors.d.ts +7 -0
  35. package/dist/diagnostics/errors.js +7 -0
  36. package/dist/diagnostics/errors.js.map +1 -1
  37. package/dist/react/package.json.cjs +1 -1
  38. package/dist/react/package.json.js +1 -1
  39. package/dist/styles.css +1 -1
  40. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-agent-config.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-config.ts"],"sourcesContent":["import { useStatewireSendCommand, useStatewireState } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n applyConfigPatch,\n applyToolsDisable,\n applyToolsEnable,\n type ThreadConfigCustom,\n type ThreadConfigPatch,\n} from './config-mutations';\nimport { statewireRejectionPayload } from './errors';\nimport { DEEP_AGENT_CONFIG_COMMANDS } from './generated/runconfig-contract';\nimport { readDeepAgentStateThreadId } from './statewire-state';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** Set / unset top-level keys of the thread's server config (`null` unsets). */\n configPatch: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch;\n patch: Record<string, unknown>;\n };\n /** Append ids to the thread's `enabled_tools`. */\n configToolsEnable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable;\n toolIds: string[];\n };\n /** Remove ids from the thread's `enabled_tools`. */\n configToolsDisable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable;\n toolIds: string[];\n };\n }\n }\n}\n\n/**\n * The replicated `state.config` lane of a thread with server-authoritative\n * run config. Absent — and `readServerConfig` returns `null` — while the\n * thread has never been configured (version 0: a brand-new thread, or one\n * created by a client predating the lane).\n */\nexport interface DeepAgentServerConfig {\n /** Monotonic server-incremented version; always >= 1 when the lane exists. */\n version: number;\n /** The thread-config keys every run on this thread uses as its base. */\n custom: ThreadConfigCustom;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Read the server-config lane off a raw statewire state.\n *\n * Identity-preserving (returns the raw `state.config` reference), so it is\n * safe as a `useStatewireState` selector: the reference only changes when the\n * server writes the lane.\n */\nexport function readServerConfig(state: unknown): DeepAgentServerConfig | null {\n const config = (state as { config?: unknown } | null | undefined)?.config;\n if (!isRecord(config)) return null;\n const version = config.version;\n if (typeof version !== 'number' || !Number.isInteger(version) || version <= 0) return null;\n if (!isRecord(config.custom)) return null;\n return config as unknown as DeepAgentServerConfig;\n}\n\n/** The thread's server config version: 0 while the thread has never been configured. */\nexport function readConfigVersion(state: unknown): number {\n return readServerConfig(state)?.version ?? 0;\n}\n\n/**\n * {@link readServerConfig}, but only from a state `threadId`'s own attach\n * produced (`readDeepAgentStateThreadId`); any other state reads as\n * unconfigured. With no `threadId` it is {@link readServerConfig} unchanged.\n *\n * Identity-preserving like {@link readServerConfig}, so it is safe as a\n * `useStatewireState` selector.\n */\nexport function readThreadServerConfig({\n state,\n threadId,\n}: {\n state: unknown;\n threadId: string | undefined;\n}): DeepAgentServerConfig | null {\n if (threadId !== undefined && readDeepAgentStateThreadId(state) !== threadId) return null;\n return readServerConfig(state);\n}\n\n/**\n * The 403 rejection codes a view-only collaborator's `config/*` write can\n * come back with: `config_write_forbidden` from the config host's own gate,\n * or `thread_write_forbidden` from the host-wide mutation gate that runs\n * ahead of it and refuses every thread-mutating command on a view grant.\n */\nconst CONFIG_WRITE_FORBIDDEN_CODES: ReadonlySet<string> = new Set([\n 'config_write_forbidden',\n 'thread_write_forbidden',\n]);\n\n/**\n * Whether a rejected `config/*` command was refused because the submitter\n * only has view access to the session. The write is not retriable by that\n * user; hosts disable their config controls.\n */\nexport function isConfigWriteForbiddenRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 403) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return typeof detail?.code === 'string' && CONFIG_WRITE_FORBIDDEN_CODES.has(detail.code);\n}\n\n/**\n * Whether a rejected `config/*` command was refused because the thread's\n * workspace cannot hold server config at all (`config_sync_unavailable`, a\n * regionally-pinned workspace). No write will ever succeed on the thread;\n * hosts stop issuing them and send their full config per run instead.\n */\nexport function isConfigSyncUnavailableRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 400) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return detail?.code === 'config_sync_unavailable';\n}\n\n/** The settled result of a `config/*` command: the new server config. */\nexport interface DeepAgentConfigWriteResult {\n version: number;\n custom: ThreadConfigCustom;\n}\n\n/** An observed server-config version, delivered to the host callback. */\nexport interface DeepAgentServerConfigEvent {\n version: number;\n /** The server config with this client's still-pending writes folded over. */\n custom: ThreadConfigCustom;\n}\n\ninterface PendingOp {\n id: number;\n apply: (custom: ThreadConfigCustom) => ThreadConfigCustom;\n /** The version the server assigned this op; retired once the lane reaches it. */\n settledVersion: number | null;\n}\n\n/**\n * Fold the pending optimistic ops over the server config — the effective\n * config a client shows and sends against. Pure, for the tests.\n */\nexport function foldPendingConfigOps(\n serverConfig: DeepAgentServerConfig | null,\n ops: readonly Pick<PendingOp, 'apply' | 'settledVersion'>[]\n): ThreadConfigCustom {\n const version = serverConfig?.version ?? 0;\n let custom: ThreadConfigCustom = serverConfig?.custom ?? {};\n for (const op of ops) {\n if (op.settledVersion !== null && op.settledVersion <= version) continue;\n custom = op.apply(custom);\n }\n return custom;\n}\n\nexport interface UseDeepAgentConfigOptions {\n /**\n * Fired whenever the server writes a new version of the lane (another\n * device, the seed, or the echo of our own command), with this client's\n * still-pending writes folded over — hosts mirror it into their local\n * store; nothing is ever clobbered because pending edits stay on top.\n */\n onServerConfig?: (event: DeepAgentServerConfigEvent) => void;\n /**\n * The thread this binding serves. When set, the lane is read only from a\n * state that thread's own attach produced ({@link readThreadServerConfig}):\n * for the first render after a thread switch the client still projects the\n * previous thread's state, and adopting its lane would report the previous\n * thread's config and version as this thread's.\n */\n threadId?: string;\n}\n\nexport interface UseDeepAgentConfigResult {\n /** Reactive server config version (0 while the thread has never been configured). */\n configVersion: number;\n /** Reactive server-config lane, or `null` at version 0. */\n serverConfig: DeepAgentServerConfig | null;\n /**\n * The effective thread config: the server lane with this client's pending\n * writes folded over. `{}` at version 0 with nothing pending.\n */\n custom: ThreadConfigCustom;\n /** `true` while at least one `config/*` command is unacknowledged by the server. */\n hasPendingWrites: boolean;\n /** `config/patch` — set / unset top-level keys; `null` unsets. */\n patchConfig: (patch: ThreadConfigPatch) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/enable`. */\n enableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/disable`. */\n disableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n}\n\ntype ConfigCommand =\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch; patch: Record<string, unknown> }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable; toolIds: string[] }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable; toolIds: string[] };\n\n/**\n * The server-authoritative thread config of a deep-agent thread.\n *\n * Every capability a run depends on — model, agent binding, enabled tools,\n * sandbox, environment, … — lives on the thread's server config lane and is\n * changed only through the `config/*` commands. Sends carry per-send keys\n * only (`pickPerSendCustom`). The commands are applied optimistically: each\n * one folds its transform over the server config the moment it is issued\n * (`custom`), and retires once the lane reaches the version the server\n * assigned it; a rejection drops it, so the UI snaps back to the truth.\n *\n * Must be mounted under the thread's statewire context (an `AuiProvider`\n * holding `useDeepAgentThread`'s element), keyed by the thread identity so a\n * thread switch remounts it with an empty pending set. A binding that persists\n * what it reads per thread passes `threadId`, so the render that still sees\n * the previous thread's state after a switch reads no lane at all.\n */\nexport function useDeepAgentConfig({\n threadId,\n ...options\n}: UseDeepAgentConfigOptions = {}): UseDeepAgentConfigResult {\n const sendCommand = useStatewireSendCommand();\n const selectServerConfig = useCallback(\n (state: unknown) => readThreadServerConfig({ state, threadId }),\n [threadId]\n );\n const serverConfig = useStatewireState(selectServerConfig);\n return useDeepAgentConfigController({\n serverConfig,\n sendCommand: (command) => Promise.resolve(sendCommand(command as never)),\n ...options,\n });\n}\n\nexport interface DeepAgentConfigControllerInputs extends UseDeepAgentConfigOptions {\n serverConfig: DeepAgentServerConfig | null;\n sendCommand: (command: ConfigCommand) => Promise<unknown>;\n}\n\n/**\n * The transport-agnostic body of {@link useDeepAgentConfig}, with the reactive\n * inputs injected — so the optimistic-overlay behavior is testable without a\n * statewire session.\n */\nexport function useDeepAgentConfigController({\n serverConfig,\n sendCommand,\n onServerConfig,\n}: DeepAgentConfigControllerInputs): UseDeepAgentConfigResult {\n const [ops, setOps] = useState<readonly PendingOp[]>([]);\n const nextOpIdRef = useRef(1);\n const sendCommandRef = useRef(sendCommand);\n sendCommandRef.current = sendCommand;\n const onServerConfigRef = useRef(onServerConfig);\n onServerConfigRef.current = onServerConfig;\n\n const custom = useMemo(() => foldPendingConfigOps(serverConfig, ops), [serverConfig, ops]);\n\n // Retire ops the lane has caught up with (their transform is now part of\n // the server config itself).\n const serverVersion = serverConfig?.version ?? 0;\n const serverVersionRef = useRef(serverVersion);\n serverVersionRef.current = serverVersion;\n useEffect(() => {\n setOps((current) => {\n const kept = current.filter(\n (op) => op.settledVersion === null || op.settledVersion > serverVersion\n );\n return kept.length === current.length ? current : kept;\n });\n }, [serverVersion]);\n\n const lastNotifiedRef = useRef<DeepAgentServerConfig | null>(null);\n useEffect(() => {\n if (serverConfig === null || serverConfig === lastNotifiedRef.current) return;\n lastNotifiedRef.current = serverConfig;\n onServerConfigRef.current?.({ version: serverConfig.version, custom });\n }, [serverConfig, custom]);\n\n const run = useCallback(\n async (\n command: ConfigCommand,\n apply: PendingOp['apply']\n ): Promise<DeepAgentConfigWriteResult> => {\n const id = nextOpIdRef.current++;\n setOps((current) => [...current, { id, apply, settledVersion: null }]);\n try {\n const raw = (await sendCommandRef.current(command)) as\n | { version?: unknown; custom?: unknown }\n | null\n | undefined;\n const version = typeof raw?.version === 'number' ? raw.version : Number.POSITIVE_INFINITY;\n // The lane echo may have landed before the acknowledgement: retire\n // now if so, otherwise stamp the version for the retire effect.\n setOps((current) =>\n version <= serverVersionRef.current\n ? current.filter((op) => op.id !== id)\n : current.map((op) => (op.id === id ? { ...op, settledVersion: version } : op))\n );\n return {\n version: Number.isFinite(version) ? version : 0,\n custom: isRecord(raw?.custom) ? raw.custom : apply({}),\n };\n } catch (error) {\n setOps((current) => current.filter((op) => op.id !== id));\n throw error;\n }\n },\n []\n );\n\n const patchConfig = useCallback(\n (patch: ThreadConfigPatch) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.patch, patch }, (base) =>\n applyConfigPatch(base, patch)\n ),\n [run]\n );\n const enableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsEnable, toolIds: [...toolIds] }, (base) =>\n applyToolsEnable(base, toolIds)\n ),\n [run]\n );\n const disableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsDisable, toolIds: [...toolIds] }, (base) =>\n applyToolsDisable(base, toolIds)\n ),\n [run]\n );\n\n return {\n configVersion: serverVersion,\n serverConfig,\n custom,\n hasPendingWrites: ops.some((op) => op.settledVersion === null),\n patchConfig,\n enableTools,\n disableTools,\n };\n}\n"],"names":["statewireRejectionPayload","useState","useRef","useMemo","useEffect","useCallback","DEEP_AGENT_CONFIG_COMMANDS","applyConfigPatch","applyToolsEnable","applyToolsDisable"],"mappings":"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"}
1
+ {"version":3,"file":"use-deep-agent-config.cjs","sources":["../../../../agent-runtime/src/use-deep-agent-config.ts"],"sourcesContent":["import { useStatewireSendCommand, useStatewireState } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n applyConfigPatch,\n applyToolsDisable,\n applyToolsEnable,\n type ThreadConfigCustom,\n type ThreadConfigPatch,\n} from './config-mutations';\nimport { statewireRejectionPayload } from './errors';\nimport { DEEP_AGENT_CONFIG_COMMANDS } from './generated/runconfig-contract';\nimport { readDeepAgentStateThreadId } from './statewire-state';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** Set / unset top-level keys of the thread's server config (`null` unsets). */\n configPatch: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch;\n patch: Record<string, unknown>;\n };\n /** Append ids to the thread's `enabled_tools`. */\n configToolsEnable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable;\n toolIds: string[];\n };\n /** Remove ids from the thread's `enabled_tools`. */\n configToolsDisable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable;\n toolIds: string[];\n };\n }\n }\n}\n\n/**\n * The replicated `state.config` lane of a thread with server-authoritative\n * run config. Absent — and `readServerConfig` returns `null` — while the\n * thread has never been configured (version 0: a brand-new thread, or one\n * created by a client predating the lane).\n */\nexport interface DeepAgentServerConfig {\n /** Monotonic server-incremented version; always >= 1 when the lane exists. */\n version: number;\n /** The thread-config keys every run on this thread uses as its base. */\n custom: ThreadConfigCustom;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Read the server-config lane off a raw statewire state.\n *\n * Identity-preserving (returns the raw `state.config` reference), so it is\n * safe as a `useStatewireState` selector: the reference only changes when the\n * server writes the lane.\n */\nexport function readServerConfig(state: unknown): DeepAgentServerConfig | null {\n const config = (state as { config?: unknown } | null | undefined)?.config;\n if (!isRecord(config)) return null;\n const version = config.version;\n if (typeof version !== 'number' || !Number.isInteger(version) || version <= 0) return null;\n if (!isRecord(config.custom)) return null;\n return config as unknown as DeepAgentServerConfig;\n}\n\n/** The thread's server config version: 0 while the thread has never been configured. */\nexport function readConfigVersion(state: unknown): number {\n return readServerConfig(state)?.version ?? 0;\n}\n\n/**\n * {@link readServerConfig}, but only from a state `threadId`'s own attach\n * produced (`readDeepAgentStateThreadId`); any other state reads as\n * unconfigured. With no `threadId` it is {@link readServerConfig} unchanged.\n *\n * Identity-preserving like {@link readServerConfig}, so it is safe as a\n * `useStatewireState` selector.\n */\nexport function readThreadServerConfig({\n state,\n threadId,\n}: {\n state: unknown;\n threadId: string | undefined;\n}): DeepAgentServerConfig | null {\n if (threadId !== undefined && readDeepAgentStateThreadId(state) !== threadId) return null;\n return readServerConfig(state);\n}\n\n/**\n * The 403 rejection codes a view-only collaborator's `config/*` write can\n * come back with: `config_write_forbidden` from the config host's own gate,\n * or `thread_write_forbidden` from the host-wide mutation gate that runs\n * ahead of it and refuses every thread-mutating command on a view grant.\n */\nconst CONFIG_WRITE_FORBIDDEN_CODES: ReadonlySet<string> = new Set([\n 'config_write_forbidden',\n 'thread_write_forbidden',\n]);\n\n/**\n * Whether a rejected `config/*` command was refused because the submitter\n * only has view access to the session, or because the conversation is\n * archived (`conversation_archived`, a 409: read-only until it is restored).\n * The write is not retriable by that user; hosts disable their config\n * controls.\n */\nexport function isConfigWriteForbiddenRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n const detail = payload?.detail as { code?: unknown } | undefined;\n if (payload?.status === 409) return detail?.code === 'conversation_archived';\n if (payload?.status !== 403) return false;\n return typeof detail?.code === 'string' && CONFIG_WRITE_FORBIDDEN_CODES.has(detail.code);\n}\n\n/**\n * Whether a rejected `config/*` command was refused because the thread's\n * workspace cannot hold server config at all (`config_sync_unavailable`, a\n * regionally-pinned workspace). No write will ever succeed on the thread;\n * hosts stop issuing them and send their full config per run instead.\n */\nexport function isConfigSyncUnavailableRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 400) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return detail?.code === 'config_sync_unavailable';\n}\n\n/** The settled result of a `config/*` command: the new server config. */\nexport interface DeepAgentConfigWriteResult {\n version: number;\n custom: ThreadConfigCustom;\n}\n\n/** An observed server-config version, delivered to the host callback. */\nexport interface DeepAgentServerConfigEvent {\n version: number;\n /** The server config with this client's still-pending writes folded over. */\n custom: ThreadConfigCustom;\n}\n\ninterface PendingOp {\n id: number;\n apply: (custom: ThreadConfigCustom) => ThreadConfigCustom;\n /** The version the server assigned this op; retired once the lane reaches it. */\n settledVersion: number | null;\n}\n\n/**\n * Fold the pending optimistic ops over the server config — the effective\n * config a client shows and sends against. Pure, for the tests.\n */\nexport function foldPendingConfigOps(\n serverConfig: DeepAgentServerConfig | null,\n ops: readonly Pick<PendingOp, 'apply' | 'settledVersion'>[]\n): ThreadConfigCustom {\n const version = serverConfig?.version ?? 0;\n let custom: ThreadConfigCustom = serverConfig?.custom ?? {};\n for (const op of ops) {\n if (op.settledVersion !== null && op.settledVersion <= version) continue;\n custom = op.apply(custom);\n }\n return custom;\n}\n\nexport interface UseDeepAgentConfigOptions {\n /**\n * Fired whenever the server writes a new version of the lane (another\n * device, the seed, or the echo of our own command), with this client's\n * still-pending writes folded over — hosts mirror it into their local\n * store; nothing is ever clobbered because pending edits stay on top.\n */\n onServerConfig?: (event: DeepAgentServerConfigEvent) => void;\n /**\n * The thread this binding serves. When set, the lane is read only from a\n * state that thread's own attach produced ({@link readThreadServerConfig}):\n * for the first render after a thread switch the client still projects the\n * previous thread's state, and adopting its lane would report the previous\n * thread's config and version as this thread's.\n */\n threadId?: string;\n}\n\nexport interface UseDeepAgentConfigResult {\n /** Reactive server config version (0 while the thread has never been configured). */\n configVersion: number;\n /** Reactive server-config lane, or `null` at version 0. */\n serverConfig: DeepAgentServerConfig | null;\n /**\n * The effective thread config: the server lane with this client's pending\n * writes folded over. `{}` at version 0 with nothing pending.\n */\n custom: ThreadConfigCustom;\n /** `true` while at least one `config/*` command is unacknowledged by the server. */\n hasPendingWrites: boolean;\n /** `config/patch` — set / unset top-level keys; `null` unsets. */\n patchConfig: (patch: ThreadConfigPatch) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/enable`. */\n enableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/disable`. */\n disableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n}\n\ntype ConfigCommand =\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch; patch: Record<string, unknown> }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable; toolIds: string[] }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable; toolIds: string[] };\n\n/**\n * The server-authoritative thread config of a deep-agent thread.\n *\n * Every capability a run depends on — model, agent binding, enabled tools,\n * sandbox, environment, … — lives on the thread's server config lane and is\n * changed only through the `config/*` commands. Sends carry per-send keys\n * only (`pickPerSendCustom`). The commands are applied optimistically: each\n * one folds its transform over the server config the moment it is issued\n * (`custom`), and retires once the lane reaches the version the server\n * assigned it; a rejection drops it, so the UI snaps back to the truth.\n *\n * Must be mounted under the thread's statewire context (an `AuiProvider`\n * holding `useDeepAgentThread`'s element), keyed by the thread identity so a\n * thread switch remounts it with an empty pending set. A binding that persists\n * what it reads per thread passes `threadId`, so the render that still sees\n * the previous thread's state after a switch reads no lane at all.\n */\nexport function useDeepAgentConfig({\n threadId,\n ...options\n}: UseDeepAgentConfigOptions = {}): UseDeepAgentConfigResult {\n const sendCommand = useStatewireSendCommand();\n const selectServerConfig = useCallback(\n (state: unknown) => readThreadServerConfig({ state, threadId }),\n [threadId]\n );\n const serverConfig = useStatewireState(selectServerConfig);\n return useDeepAgentConfigController({\n serverConfig,\n sendCommand: (command) => Promise.resolve(sendCommand(command as never)),\n ...options,\n });\n}\n\nexport interface DeepAgentConfigControllerInputs extends UseDeepAgentConfigOptions {\n serverConfig: DeepAgentServerConfig | null;\n sendCommand: (command: ConfigCommand) => Promise<unknown>;\n}\n\n/**\n * The transport-agnostic body of {@link useDeepAgentConfig}, with the reactive\n * inputs injected — so the optimistic-overlay behavior is testable without a\n * statewire session.\n */\nexport function useDeepAgentConfigController({\n serverConfig,\n sendCommand,\n onServerConfig,\n}: DeepAgentConfigControllerInputs): UseDeepAgentConfigResult {\n const [ops, setOps] = useState<readonly PendingOp[]>([]);\n const nextOpIdRef = useRef(1);\n const sendCommandRef = useRef(sendCommand);\n sendCommandRef.current = sendCommand;\n const onServerConfigRef = useRef(onServerConfig);\n onServerConfigRef.current = onServerConfig;\n\n const custom = useMemo(() => foldPendingConfigOps(serverConfig, ops), [serverConfig, ops]);\n\n // Retire ops the lane has caught up with (their transform is now part of\n // the server config itself).\n const serverVersion = serverConfig?.version ?? 0;\n const serverVersionRef = useRef(serverVersion);\n serverVersionRef.current = serverVersion;\n useEffect(() => {\n setOps((current) => {\n const kept = current.filter(\n (op) => op.settledVersion === null || op.settledVersion > serverVersion\n );\n return kept.length === current.length ? current : kept;\n });\n }, [serverVersion]);\n\n const lastNotifiedRef = useRef<DeepAgentServerConfig | null>(null);\n useEffect(() => {\n if (serverConfig === null || serverConfig === lastNotifiedRef.current) return;\n lastNotifiedRef.current = serverConfig;\n onServerConfigRef.current?.({ version: serverConfig.version, custom });\n }, [serverConfig, custom]);\n\n const run = useCallback(\n async (\n command: ConfigCommand,\n apply: PendingOp['apply']\n ): Promise<DeepAgentConfigWriteResult> => {\n const id = nextOpIdRef.current++;\n setOps((current) => [...current, { id, apply, settledVersion: null }]);\n try {\n const raw = (await sendCommandRef.current(command)) as\n | { version?: unknown; custom?: unknown }\n | null\n | undefined;\n const version = typeof raw?.version === 'number' ? raw.version : Number.POSITIVE_INFINITY;\n // The lane echo may have landed before the acknowledgement: retire\n // now if so, otherwise stamp the version for the retire effect.\n setOps((current) =>\n version <= serverVersionRef.current\n ? current.filter((op) => op.id !== id)\n : current.map((op) => (op.id === id ? { ...op, settledVersion: version } : op))\n );\n return {\n version: Number.isFinite(version) ? version : 0,\n custom: isRecord(raw?.custom) ? raw.custom : apply({}),\n };\n } catch (error) {\n setOps((current) => current.filter((op) => op.id !== id));\n throw error;\n }\n },\n []\n );\n\n const patchConfig = useCallback(\n (patch: ThreadConfigPatch) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.patch, patch }, (base) =>\n applyConfigPatch(base, patch)\n ),\n [run]\n );\n const enableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsEnable, toolIds: [...toolIds] }, (base) =>\n applyToolsEnable(base, toolIds)\n ),\n [run]\n );\n const disableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsDisable, toolIds: [...toolIds] }, (base) =>\n applyToolsDisable(base, toolIds)\n ),\n [run]\n );\n\n return {\n configVersion: serverVersion,\n serverConfig,\n custom,\n hasPendingWrites: ops.some((op) => op.settledVersion === null),\n patchConfig,\n enableTools,\n disableTools,\n };\n}\n"],"names":["statewireRejectionPayload","useState","useRef","useMemo","useEffect","useCallback","DEEP_AGENT_CONFIG_COMMANDS","applyConfigPatch","applyToolsEnable","applyToolsDisable"],"mappings":"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"}
@@ -18,8 +18,9 @@ const CONFIG_WRITE_FORBIDDEN_CODES = /* @__PURE__ */ new Set([
18
18
  ]);
19
19
  function isConfigWriteForbiddenRejection(error) {
20
20
  const payload = statewireRejectionPayload(error);
21
+ const detail = payload == null ? void 0 : payload.detail;
22
+ if ((payload == null ? void 0 : payload.status) === 409) return (detail == null ? void 0 : detail.code) === "conversation_archived";
21
23
  if ((payload == null ? void 0 : payload.status) !== 403) return false;
22
- const detail = payload.detail;
23
24
  return typeof (detail == null ? void 0 : detail.code) === "string" && CONFIG_WRITE_FORBIDDEN_CODES.has(detail.code);
24
25
  }
25
26
  function isConfigSyncUnavailableRejection(error) {
@@ -1 +1 @@
1
- {"version":3,"file":"use-deep-agent-config.js","sources":["../../../../agent-runtime/src/use-deep-agent-config.ts"],"sourcesContent":["import { useStatewireSendCommand, useStatewireState } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n applyConfigPatch,\n applyToolsDisable,\n applyToolsEnable,\n type ThreadConfigCustom,\n type ThreadConfigPatch,\n} from './config-mutations';\nimport { statewireRejectionPayload } from './errors';\nimport { DEEP_AGENT_CONFIG_COMMANDS } from './generated/runconfig-contract';\nimport { readDeepAgentStateThreadId } from './statewire-state';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** Set / unset top-level keys of the thread's server config (`null` unsets). */\n configPatch: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch;\n patch: Record<string, unknown>;\n };\n /** Append ids to the thread's `enabled_tools`. */\n configToolsEnable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable;\n toolIds: string[];\n };\n /** Remove ids from the thread's `enabled_tools`. */\n configToolsDisable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable;\n toolIds: string[];\n };\n }\n }\n}\n\n/**\n * The replicated `state.config` lane of a thread with server-authoritative\n * run config. Absent — and `readServerConfig` returns `null` — while the\n * thread has never been configured (version 0: a brand-new thread, or one\n * created by a client predating the lane).\n */\nexport interface DeepAgentServerConfig {\n /** Monotonic server-incremented version; always >= 1 when the lane exists. */\n version: number;\n /** The thread-config keys every run on this thread uses as its base. */\n custom: ThreadConfigCustom;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Read the server-config lane off a raw statewire state.\n *\n * Identity-preserving (returns the raw `state.config` reference), so it is\n * safe as a `useStatewireState` selector: the reference only changes when the\n * server writes the lane.\n */\nexport function readServerConfig(state: unknown): DeepAgentServerConfig | null {\n const config = (state as { config?: unknown } | null | undefined)?.config;\n if (!isRecord(config)) return null;\n const version = config.version;\n if (typeof version !== 'number' || !Number.isInteger(version) || version <= 0) return null;\n if (!isRecord(config.custom)) return null;\n return config as unknown as DeepAgentServerConfig;\n}\n\n/** The thread's server config version: 0 while the thread has never been configured. */\nexport function readConfigVersion(state: unknown): number {\n return readServerConfig(state)?.version ?? 0;\n}\n\n/**\n * {@link readServerConfig}, but only from a state `threadId`'s own attach\n * produced (`readDeepAgentStateThreadId`); any other state reads as\n * unconfigured. With no `threadId` it is {@link readServerConfig} unchanged.\n *\n * Identity-preserving like {@link readServerConfig}, so it is safe as a\n * `useStatewireState` selector.\n */\nexport function readThreadServerConfig({\n state,\n threadId,\n}: {\n state: unknown;\n threadId: string | undefined;\n}): DeepAgentServerConfig | null {\n if (threadId !== undefined && readDeepAgentStateThreadId(state) !== threadId) return null;\n return readServerConfig(state);\n}\n\n/**\n * The 403 rejection codes a view-only collaborator's `config/*` write can\n * come back with: `config_write_forbidden` from the config host's own gate,\n * or `thread_write_forbidden` from the host-wide mutation gate that runs\n * ahead of it and refuses every thread-mutating command on a view grant.\n */\nconst CONFIG_WRITE_FORBIDDEN_CODES: ReadonlySet<string> = new Set([\n 'config_write_forbidden',\n 'thread_write_forbidden',\n]);\n\n/**\n * Whether a rejected `config/*` command was refused because the submitter\n * only has view access to the session. The write is not retriable by that\n * user; hosts disable their config controls.\n */\nexport function isConfigWriteForbiddenRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 403) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return typeof detail?.code === 'string' && CONFIG_WRITE_FORBIDDEN_CODES.has(detail.code);\n}\n\n/**\n * Whether a rejected `config/*` command was refused because the thread's\n * workspace cannot hold server config at all (`config_sync_unavailable`, a\n * regionally-pinned workspace). No write will ever succeed on the thread;\n * hosts stop issuing them and send their full config per run instead.\n */\nexport function isConfigSyncUnavailableRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 400) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return detail?.code === 'config_sync_unavailable';\n}\n\n/** The settled result of a `config/*` command: the new server config. */\nexport interface DeepAgentConfigWriteResult {\n version: number;\n custom: ThreadConfigCustom;\n}\n\n/** An observed server-config version, delivered to the host callback. */\nexport interface DeepAgentServerConfigEvent {\n version: number;\n /** The server config with this client's still-pending writes folded over. */\n custom: ThreadConfigCustom;\n}\n\ninterface PendingOp {\n id: number;\n apply: (custom: ThreadConfigCustom) => ThreadConfigCustom;\n /** The version the server assigned this op; retired once the lane reaches it. */\n settledVersion: number | null;\n}\n\n/**\n * Fold the pending optimistic ops over the server config — the effective\n * config a client shows and sends against. Pure, for the tests.\n */\nexport function foldPendingConfigOps(\n serverConfig: DeepAgentServerConfig | null,\n ops: readonly Pick<PendingOp, 'apply' | 'settledVersion'>[]\n): ThreadConfigCustom {\n const version = serverConfig?.version ?? 0;\n let custom: ThreadConfigCustom = serverConfig?.custom ?? {};\n for (const op of ops) {\n if (op.settledVersion !== null && op.settledVersion <= version) continue;\n custom = op.apply(custom);\n }\n return custom;\n}\n\nexport interface UseDeepAgentConfigOptions {\n /**\n * Fired whenever the server writes a new version of the lane (another\n * device, the seed, or the echo of our own command), with this client's\n * still-pending writes folded over — hosts mirror it into their local\n * store; nothing is ever clobbered because pending edits stay on top.\n */\n onServerConfig?: (event: DeepAgentServerConfigEvent) => void;\n /**\n * The thread this binding serves. When set, the lane is read only from a\n * state that thread's own attach produced ({@link readThreadServerConfig}):\n * for the first render after a thread switch the client still projects the\n * previous thread's state, and adopting its lane would report the previous\n * thread's config and version as this thread's.\n */\n threadId?: string;\n}\n\nexport interface UseDeepAgentConfigResult {\n /** Reactive server config version (0 while the thread has never been configured). */\n configVersion: number;\n /** Reactive server-config lane, or `null` at version 0. */\n serverConfig: DeepAgentServerConfig | null;\n /**\n * The effective thread config: the server lane with this client's pending\n * writes folded over. `{}` at version 0 with nothing pending.\n */\n custom: ThreadConfigCustom;\n /** `true` while at least one `config/*` command is unacknowledged by the server. */\n hasPendingWrites: boolean;\n /** `config/patch` — set / unset top-level keys; `null` unsets. */\n patchConfig: (patch: ThreadConfigPatch) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/enable`. */\n enableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/disable`. */\n disableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n}\n\ntype ConfigCommand =\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch; patch: Record<string, unknown> }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable; toolIds: string[] }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable; toolIds: string[] };\n\n/**\n * The server-authoritative thread config of a deep-agent thread.\n *\n * Every capability a run depends on — model, agent binding, enabled tools,\n * sandbox, environment, … — lives on the thread's server config lane and is\n * changed only through the `config/*` commands. Sends carry per-send keys\n * only (`pickPerSendCustom`). The commands are applied optimistically: each\n * one folds its transform over the server config the moment it is issued\n * (`custom`), and retires once the lane reaches the version the server\n * assigned it; a rejection drops it, so the UI snaps back to the truth.\n *\n * Must be mounted under the thread's statewire context (an `AuiProvider`\n * holding `useDeepAgentThread`'s element), keyed by the thread identity so a\n * thread switch remounts it with an empty pending set. A binding that persists\n * what it reads per thread passes `threadId`, so the render that still sees\n * the previous thread's state after a switch reads no lane at all.\n */\nexport function useDeepAgentConfig({\n threadId,\n ...options\n}: UseDeepAgentConfigOptions = {}): UseDeepAgentConfigResult {\n const sendCommand = useStatewireSendCommand();\n const selectServerConfig = useCallback(\n (state: unknown) => readThreadServerConfig({ state, threadId }),\n [threadId]\n );\n const serverConfig = useStatewireState(selectServerConfig);\n return useDeepAgentConfigController({\n serverConfig,\n sendCommand: (command) => Promise.resolve(sendCommand(command as never)),\n ...options,\n });\n}\n\nexport interface DeepAgentConfigControllerInputs extends UseDeepAgentConfigOptions {\n serverConfig: DeepAgentServerConfig | null;\n sendCommand: (command: ConfigCommand) => Promise<unknown>;\n}\n\n/**\n * The transport-agnostic body of {@link useDeepAgentConfig}, with the reactive\n * inputs injected — so the optimistic-overlay behavior is testable without a\n * statewire session.\n */\nexport function useDeepAgentConfigController({\n serverConfig,\n sendCommand,\n onServerConfig,\n}: DeepAgentConfigControllerInputs): UseDeepAgentConfigResult {\n const [ops, setOps] = useState<readonly PendingOp[]>([]);\n const nextOpIdRef = useRef(1);\n const sendCommandRef = useRef(sendCommand);\n sendCommandRef.current = sendCommand;\n const onServerConfigRef = useRef(onServerConfig);\n onServerConfigRef.current = onServerConfig;\n\n const custom = useMemo(() => foldPendingConfigOps(serverConfig, ops), [serverConfig, ops]);\n\n // Retire ops the lane has caught up with (their transform is now part of\n // the server config itself).\n const serverVersion = serverConfig?.version ?? 0;\n const serverVersionRef = useRef(serverVersion);\n serverVersionRef.current = serverVersion;\n useEffect(() => {\n setOps((current) => {\n const kept = current.filter(\n (op) => op.settledVersion === null || op.settledVersion > serverVersion\n );\n return kept.length === current.length ? current : kept;\n });\n }, [serverVersion]);\n\n const lastNotifiedRef = useRef<DeepAgentServerConfig | null>(null);\n useEffect(() => {\n if (serverConfig === null || serverConfig === lastNotifiedRef.current) return;\n lastNotifiedRef.current = serverConfig;\n onServerConfigRef.current?.({ version: serverConfig.version, custom });\n }, [serverConfig, custom]);\n\n const run = useCallback(\n async (\n command: ConfigCommand,\n apply: PendingOp['apply']\n ): Promise<DeepAgentConfigWriteResult> => {\n const id = nextOpIdRef.current++;\n setOps((current) => [...current, { id, apply, settledVersion: null }]);\n try {\n const raw = (await sendCommandRef.current(command)) as\n | { version?: unknown; custom?: unknown }\n | null\n | undefined;\n const version = typeof raw?.version === 'number' ? raw.version : Number.POSITIVE_INFINITY;\n // The lane echo may have landed before the acknowledgement: retire\n // now if so, otherwise stamp the version for the retire effect.\n setOps((current) =>\n version <= serverVersionRef.current\n ? current.filter((op) => op.id !== id)\n : current.map((op) => (op.id === id ? { ...op, settledVersion: version } : op))\n );\n return {\n version: Number.isFinite(version) ? version : 0,\n custom: isRecord(raw?.custom) ? raw.custom : apply({}),\n };\n } catch (error) {\n setOps((current) => current.filter((op) => op.id !== id));\n throw error;\n }\n },\n []\n );\n\n const patchConfig = useCallback(\n (patch: ThreadConfigPatch) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.patch, patch }, (base) =>\n applyConfigPatch(base, patch)\n ),\n [run]\n );\n const enableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsEnable, toolIds: [...toolIds] }, (base) =>\n applyToolsEnable(base, toolIds)\n ),\n [run]\n );\n const disableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsDisable, toolIds: [...toolIds] }, (base) =>\n applyToolsDisable(base, toolIds)\n ),\n [run]\n );\n\n return {\n configVersion: serverVersion,\n serverConfig,\n custom,\n hasPendingWrites: ops.some((op) => op.settledVersion === null),\n patchConfig,\n enableTools,\n disableTools,\n };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"use-deep-agent-config.js","sources":["../../../../agent-runtime/src/use-deep-agent-config.ts"],"sourcesContent":["import { useStatewireSendCommand, useStatewireState } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport {\n applyConfigPatch,\n applyToolsDisable,\n applyToolsEnable,\n type ThreadConfigCustom,\n type ThreadConfigPatch,\n} from './config-mutations';\nimport { statewireRejectionPayload } from './errors';\nimport { DEEP_AGENT_CONFIG_COMMANDS } from './generated/runconfig-contract';\nimport { readDeepAgentStateThreadId } from './statewire-state';\n\ndeclare module '@assistant-ui/core' {\n namespace Assistant {\n interface Commands {\n /** Set / unset top-level keys of the thread's server config (`null` unsets). */\n configPatch: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch;\n patch: Record<string, unknown>;\n };\n /** Append ids to the thread's `enabled_tools`. */\n configToolsEnable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable;\n toolIds: string[];\n };\n /** Remove ids from the thread's `enabled_tools`. */\n configToolsDisable: {\n type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable;\n toolIds: string[];\n };\n }\n }\n}\n\n/**\n * The replicated `state.config` lane of a thread with server-authoritative\n * run config. Absent — and `readServerConfig` returns `null` — while the\n * thread has never been configured (version 0: a brand-new thread, or one\n * created by a client predating the lane).\n */\nexport interface DeepAgentServerConfig {\n /** Monotonic server-incremented version; always >= 1 when the lane exists. */\n version: number;\n /** The thread-config keys every run on this thread uses as its base. */\n custom: ThreadConfigCustom;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Read the server-config lane off a raw statewire state.\n *\n * Identity-preserving (returns the raw `state.config` reference), so it is\n * safe as a `useStatewireState` selector: the reference only changes when the\n * server writes the lane.\n */\nexport function readServerConfig(state: unknown): DeepAgentServerConfig | null {\n const config = (state as { config?: unknown } | null | undefined)?.config;\n if (!isRecord(config)) return null;\n const version = config.version;\n if (typeof version !== 'number' || !Number.isInteger(version) || version <= 0) return null;\n if (!isRecord(config.custom)) return null;\n return config as unknown as DeepAgentServerConfig;\n}\n\n/** The thread's server config version: 0 while the thread has never been configured. */\nexport function readConfigVersion(state: unknown): number {\n return readServerConfig(state)?.version ?? 0;\n}\n\n/**\n * {@link readServerConfig}, but only from a state `threadId`'s own attach\n * produced (`readDeepAgentStateThreadId`); any other state reads as\n * unconfigured. With no `threadId` it is {@link readServerConfig} unchanged.\n *\n * Identity-preserving like {@link readServerConfig}, so it is safe as a\n * `useStatewireState` selector.\n */\nexport function readThreadServerConfig({\n state,\n threadId,\n}: {\n state: unknown;\n threadId: string | undefined;\n}): DeepAgentServerConfig | null {\n if (threadId !== undefined && readDeepAgentStateThreadId(state) !== threadId) return null;\n return readServerConfig(state);\n}\n\n/**\n * The 403 rejection codes a view-only collaborator's `config/*` write can\n * come back with: `config_write_forbidden` from the config host's own gate,\n * or `thread_write_forbidden` from the host-wide mutation gate that runs\n * ahead of it and refuses every thread-mutating command on a view grant.\n */\nconst CONFIG_WRITE_FORBIDDEN_CODES: ReadonlySet<string> = new Set([\n 'config_write_forbidden',\n 'thread_write_forbidden',\n]);\n\n/**\n * Whether a rejected `config/*` command was refused because the submitter\n * only has view access to the session, or because the conversation is\n * archived (`conversation_archived`, a 409: read-only until it is restored).\n * The write is not retriable by that user; hosts disable their config\n * controls.\n */\nexport function isConfigWriteForbiddenRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n const detail = payload?.detail as { code?: unknown } | undefined;\n if (payload?.status === 409) return detail?.code === 'conversation_archived';\n if (payload?.status !== 403) return false;\n return typeof detail?.code === 'string' && CONFIG_WRITE_FORBIDDEN_CODES.has(detail.code);\n}\n\n/**\n * Whether a rejected `config/*` command was refused because the thread's\n * workspace cannot hold server config at all (`config_sync_unavailable`, a\n * regionally-pinned workspace). No write will ever succeed on the thread;\n * hosts stop issuing them and send their full config per run instead.\n */\nexport function isConfigSyncUnavailableRejection(error: unknown): boolean {\n const payload = statewireRejectionPayload(error);\n if (payload?.status !== 400) return false;\n const detail = payload.detail as { code?: unknown } | undefined;\n return detail?.code === 'config_sync_unavailable';\n}\n\n/** The settled result of a `config/*` command: the new server config. */\nexport interface DeepAgentConfigWriteResult {\n version: number;\n custom: ThreadConfigCustom;\n}\n\n/** An observed server-config version, delivered to the host callback. */\nexport interface DeepAgentServerConfigEvent {\n version: number;\n /** The server config with this client's still-pending writes folded over. */\n custom: ThreadConfigCustom;\n}\n\ninterface PendingOp {\n id: number;\n apply: (custom: ThreadConfigCustom) => ThreadConfigCustom;\n /** The version the server assigned this op; retired once the lane reaches it. */\n settledVersion: number | null;\n}\n\n/**\n * Fold the pending optimistic ops over the server config — the effective\n * config a client shows and sends against. Pure, for the tests.\n */\nexport function foldPendingConfigOps(\n serverConfig: DeepAgentServerConfig | null,\n ops: readonly Pick<PendingOp, 'apply' | 'settledVersion'>[]\n): ThreadConfigCustom {\n const version = serverConfig?.version ?? 0;\n let custom: ThreadConfigCustom = serverConfig?.custom ?? {};\n for (const op of ops) {\n if (op.settledVersion !== null && op.settledVersion <= version) continue;\n custom = op.apply(custom);\n }\n return custom;\n}\n\nexport interface UseDeepAgentConfigOptions {\n /**\n * Fired whenever the server writes a new version of the lane (another\n * device, the seed, or the echo of our own command), with this client's\n * still-pending writes folded over — hosts mirror it into their local\n * store; nothing is ever clobbered because pending edits stay on top.\n */\n onServerConfig?: (event: DeepAgentServerConfigEvent) => void;\n /**\n * The thread this binding serves. When set, the lane is read only from a\n * state that thread's own attach produced ({@link readThreadServerConfig}):\n * for the first render after a thread switch the client still projects the\n * previous thread's state, and adopting its lane would report the previous\n * thread's config and version as this thread's.\n */\n threadId?: string;\n}\n\nexport interface UseDeepAgentConfigResult {\n /** Reactive server config version (0 while the thread has never been configured). */\n configVersion: number;\n /** Reactive server-config lane, or `null` at version 0. */\n serverConfig: DeepAgentServerConfig | null;\n /**\n * The effective thread config: the server lane with this client's pending\n * writes folded over. `{}` at version 0 with nothing pending.\n */\n custom: ThreadConfigCustom;\n /** `true` while at least one `config/*` command is unacknowledged by the server. */\n hasPendingWrites: boolean;\n /** `config/patch` — set / unset top-level keys; `null` unsets. */\n patchConfig: (patch: ThreadConfigPatch) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/enable`. */\n enableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n /** `config/tools/disable`. */\n disableTools: (toolIds: readonly string[]) => Promise<DeepAgentConfigWriteResult>;\n}\n\ntype ConfigCommand =\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.patch; patch: Record<string, unknown> }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsEnable; toolIds: string[] }\n | { type: typeof DEEP_AGENT_CONFIG_COMMANDS.toolsDisable; toolIds: string[] };\n\n/**\n * The server-authoritative thread config of a deep-agent thread.\n *\n * Every capability a run depends on — model, agent binding, enabled tools,\n * sandbox, environment, … — lives on the thread's server config lane and is\n * changed only through the `config/*` commands. Sends carry per-send keys\n * only (`pickPerSendCustom`). The commands are applied optimistically: each\n * one folds its transform over the server config the moment it is issued\n * (`custom`), and retires once the lane reaches the version the server\n * assigned it; a rejection drops it, so the UI snaps back to the truth.\n *\n * Must be mounted under the thread's statewire context (an `AuiProvider`\n * holding `useDeepAgentThread`'s element), keyed by the thread identity so a\n * thread switch remounts it with an empty pending set. A binding that persists\n * what it reads per thread passes `threadId`, so the render that still sees\n * the previous thread's state after a switch reads no lane at all.\n */\nexport function useDeepAgentConfig({\n threadId,\n ...options\n}: UseDeepAgentConfigOptions = {}): UseDeepAgentConfigResult {\n const sendCommand = useStatewireSendCommand();\n const selectServerConfig = useCallback(\n (state: unknown) => readThreadServerConfig({ state, threadId }),\n [threadId]\n );\n const serverConfig = useStatewireState(selectServerConfig);\n return useDeepAgentConfigController({\n serverConfig,\n sendCommand: (command) => Promise.resolve(sendCommand(command as never)),\n ...options,\n });\n}\n\nexport interface DeepAgentConfigControllerInputs extends UseDeepAgentConfigOptions {\n serverConfig: DeepAgentServerConfig | null;\n sendCommand: (command: ConfigCommand) => Promise<unknown>;\n}\n\n/**\n * The transport-agnostic body of {@link useDeepAgentConfig}, with the reactive\n * inputs injected — so the optimistic-overlay behavior is testable without a\n * statewire session.\n */\nexport function useDeepAgentConfigController({\n serverConfig,\n sendCommand,\n onServerConfig,\n}: DeepAgentConfigControllerInputs): UseDeepAgentConfigResult {\n const [ops, setOps] = useState<readonly PendingOp[]>([]);\n const nextOpIdRef = useRef(1);\n const sendCommandRef = useRef(sendCommand);\n sendCommandRef.current = sendCommand;\n const onServerConfigRef = useRef(onServerConfig);\n onServerConfigRef.current = onServerConfig;\n\n const custom = useMemo(() => foldPendingConfigOps(serverConfig, ops), [serverConfig, ops]);\n\n // Retire ops the lane has caught up with (their transform is now part of\n // the server config itself).\n const serverVersion = serverConfig?.version ?? 0;\n const serverVersionRef = useRef(serverVersion);\n serverVersionRef.current = serverVersion;\n useEffect(() => {\n setOps((current) => {\n const kept = current.filter(\n (op) => op.settledVersion === null || op.settledVersion > serverVersion\n );\n return kept.length === current.length ? current : kept;\n });\n }, [serverVersion]);\n\n const lastNotifiedRef = useRef<DeepAgentServerConfig | null>(null);\n useEffect(() => {\n if (serverConfig === null || serverConfig === lastNotifiedRef.current) return;\n lastNotifiedRef.current = serverConfig;\n onServerConfigRef.current?.({ version: serverConfig.version, custom });\n }, [serverConfig, custom]);\n\n const run = useCallback(\n async (\n command: ConfigCommand,\n apply: PendingOp['apply']\n ): Promise<DeepAgentConfigWriteResult> => {\n const id = nextOpIdRef.current++;\n setOps((current) => [...current, { id, apply, settledVersion: null }]);\n try {\n const raw = (await sendCommandRef.current(command)) as\n | { version?: unknown; custom?: unknown }\n | null\n | undefined;\n const version = typeof raw?.version === 'number' ? raw.version : Number.POSITIVE_INFINITY;\n // The lane echo may have landed before the acknowledgement: retire\n // now if so, otherwise stamp the version for the retire effect.\n setOps((current) =>\n version <= serverVersionRef.current\n ? current.filter((op) => op.id !== id)\n : current.map((op) => (op.id === id ? { ...op, settledVersion: version } : op))\n );\n return {\n version: Number.isFinite(version) ? version : 0,\n custom: isRecord(raw?.custom) ? raw.custom : apply({}),\n };\n } catch (error) {\n setOps((current) => current.filter((op) => op.id !== id));\n throw error;\n }\n },\n []\n );\n\n const patchConfig = useCallback(\n (patch: ThreadConfigPatch) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.patch, patch }, (base) =>\n applyConfigPatch(base, patch)\n ),\n [run]\n );\n const enableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsEnable, toolIds: [...toolIds] }, (base) =>\n applyToolsEnable(base, toolIds)\n ),\n [run]\n );\n const disableTools = useCallback(\n (toolIds: readonly string[]) =>\n run({ type: DEEP_AGENT_CONFIG_COMMANDS.toolsDisable, toolIds: [...toolIds] }, (base) =>\n applyToolsDisable(base, toolIds)\n ),\n [run]\n );\n\n return {\n configVersion: serverVersion,\n serverConfig,\n custom,\n hasPendingWrites: ops.some((op) => op.settledVersion === null),\n patchConfig,\n enableTools,\n disableTools,\n };\n}\n"],"names":[],"mappings":"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"}
@@ -1,8 +1,129 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
+ const reactQuery = require("@tanstack/react-query");
4
5
  const react = require("react");
6
+ const athenaDiagnostics = require("../diagnostics/athena-diagnostics.cjs");
7
+ const errors = require("../diagnostics/errors.cjs");
8
+ const auth = require("../runtime/auth.cjs");
5
9
  const AthenaAuthContext = require("./AthenaAuthContext.cjs");
10
+ const SSO_USERINFO_RETRY_POLICY = {
11
+ ...auth.DEFAULT_MINT_RETRY_POLICY,
12
+ timeoutMs: 1e4
13
+ };
14
+ const abortError = () => new DOMException("The session read was cancelled.", "AbortError");
15
+ async function readUserInfoOnce({
16
+ url,
17
+ signal,
18
+ timeoutMs
19
+ }) {
20
+ if (signal.aborted) throw abortError();
21
+ const request = new AbortController();
22
+ const cancel = () => request.abort();
23
+ signal.addEventListener("abort", cancel, { once: true });
24
+ let timedOut = false;
25
+ const timer = setTimeout(() => {
26
+ timedOut = true;
27
+ request.abort();
28
+ }, timeoutMs);
29
+ try {
30
+ const response = await fetch(url, { credentials: "include", signal: request.signal });
31
+ if (response.status === 401 || response.status === 403) return { kind: "unauthenticated" };
32
+ if (!response.ok) {
33
+ return auth.isRetryableMintStatus(response.status) ? { kind: "transient", status: response.status, reason: "status" } : { kind: "failed", status: response.status };
34
+ }
35
+ let data;
36
+ try {
37
+ data = await response.json();
38
+ } catch (error) {
39
+ if (signal.aborted) throw abortError();
40
+ return timedOut ? { kind: "transient", status: null, reason: "timeout", cause: error } : { kind: "failed", status: response.status };
41
+ }
42
+ return isValidSSOUserInfo(data) ? { kind: "session", data } : { kind: "invalid_response" };
43
+ } catch (error) {
44
+ if (signal.aborted) throw abortError();
45
+ return { kind: "transient", status: null, reason: timedOut ? "timeout" : "network", cause: error };
46
+ } finally {
47
+ clearTimeout(timer);
48
+ signal.removeEventListener("abort", cancel);
49
+ }
50
+ }
51
+ async function readSession({
52
+ url,
53
+ signal,
54
+ policy
55
+ }) {
56
+ const maxAttempts = Math.max(1, Math.floor(policy.maxAttempts));
57
+ for (let attempts = 1; ; attempts += 1) {
58
+ const attempt = await readUserInfoOnce({ url, signal, timeoutMs: policy.timeoutMs });
59
+ if (attempt.kind === "session") return { kind: "session", data: attempt.data };
60
+ if (attempt.kind === "unauthenticated") return { kind: "no_session", reason: "unauthenticated" };
61
+ if (attempt.kind === "invalid_response") {
62
+ console.error(
63
+ '[AthenaSDK] Invalid SSO userinfo response: expected "user.user_id", "user.email", "orgMemberInfos", and "accessToken"'
64
+ );
65
+ return { kind: "no_session", reason: "invalid_response" };
66
+ }
67
+ if (attempt.kind === "transient" && attempts < maxAttempts) {
68
+ const delayMs = auth.getMintRetryDelay({ retry: attempts, policy });
69
+ athenaDiagnostics.athenaDiagnostics.emit("auth.sso.userinfo.retry", {
70
+ level: "warn",
71
+ data: {
72
+ status: attempt.status,
73
+ reason: attempt.reason,
74
+ attempt: attempts,
75
+ max_attempts: maxAttempts,
76
+ delay_ms: delayMs
77
+ }
78
+ });
79
+ await waitBeforeRetry({ delayMs, signal });
80
+ continue;
81
+ }
82
+ throw sessionReadError({ url, attempt, attempts });
83
+ }
84
+ }
85
+ function waitBeforeRetry({ delayMs, signal }) {
86
+ return new Promise((resolve, reject) => {
87
+ if (signal.aborted) {
88
+ reject(abortError());
89
+ return;
90
+ }
91
+ const onAbort = () => {
92
+ clearTimeout(timer);
93
+ reject(abortError());
94
+ };
95
+ const timer = setTimeout(() => {
96
+ signal.removeEventListener("abort", onAbort);
97
+ resolve();
98
+ }, delayMs);
99
+ signal.addEventListener("abort", onAbort, { once: true });
100
+ });
101
+ }
102
+ function sessionReadError({
103
+ url,
104
+ attempt,
105
+ attempts
106
+ }) {
107
+ if (attempt.kind === "failed") {
108
+ const unreadable = attempt.status >= 200 && attempt.status < 300;
109
+ return new errors.AthenaSdkError({
110
+ code: "auth_sso_failed",
111
+ message: unreadable ? `The SSO session read (${url}) answered HTTP ${attempt.status} without a JSON body.` : `The SSO session read (${url}) answered HTTP ${attempt.status}.`,
112
+ hint: "Check that ssoBaseUrl / ssoUserInfoUrl point at the SSO userinfo endpoint.",
113
+ status: attempt.status,
114
+ context: { attempts, reason: unreadable ? "invalid_body" : "status" }
115
+ });
116
+ }
117
+ const answer = attempt.status !== null ? `HTTP ${attempt.status}` : attempt.reason === "timeout" ? "a timeout" : "a network error";
118
+ return new errors.AthenaSdkError({
119
+ code: "auth_sso_failed",
120
+ message: `The SSO session read (${url}) failed ${attempts} time${attempts === 1 ? "" : "s"} (last: ${answer}).`,
121
+ hint: "The sign-in service did not answer; the person may still be signed in. Offer a retry (displayOnError receives retry).",
122
+ ...attempt.status !== null && { status: attempt.status },
123
+ ...attempt.cause !== void 0 && { cause: attempt.cause },
124
+ context: { attempts, reason: attempt.reason }
125
+ });
126
+ }
6
127
  const DEFAULT_SSO_PATHS = {
7
128
  userInfo: "/api/sso/userinfo",
8
129
  login: "/api/sso/initiate",
@@ -54,13 +175,15 @@ function AthenaSSOProvider({
54
175
  ssoLogoutUrl,
55
176
  displayWhileLoading,
56
177
  displayOnError,
57
- revalidateOnWindowFocus = false
178
+ onError,
179
+ revalidateOnWindowFocus = false,
180
+ retryPolicy = SSO_USERINFO_RETRY_POLICY
58
181
  }) {
59
- const [ssoData, setSSOData] = react.useState(null);
60
- const [loading, setLoading] = react.useState(true);
61
- const [error, setError] = react.useState(null);
62
- const activeRequestRef = react.useRef(null);
63
- const activeReadRef = react.useRef(null);
182
+ const [queryClient] = react.useState(() => new reactQuery.QueryClient());
183
+ const retryPolicyRef = react.useRef(retryPolicy);
184
+ retryPolicyRef.current = retryPolicy;
185
+ const onErrorRef = react.useRef(onError);
186
+ onErrorRef.current = onError;
64
187
  const ssoUrls = react.useMemo(
65
188
  () => ({
66
189
  userInfo: resolveSSOUrl({
@@ -81,101 +204,75 @@ function AthenaSSOProvider({
81
204
  }),
82
205
  [ssoBaseUrl, ssoUserInfoUrl, ssoLoginUrl, ssoLogoutUrl]
83
206
  );
84
- const refreshSession = react.useCallback(
85
- ({ showLoading = false } = {}) => {
207
+ const queryKey = react.useMemo(() => ["athena-sdk", "sso-session", ssoUrls.userInfo], [ssoUrls.userInfo]);
208
+ const queryFn = react.useCallback(
209
+ async ({ signal }) => {
86
210
  var _a;
87
- (_a = activeRequestRef.current) == null ? void 0 : _a.abort();
88
- const abortController = new AbortController();
89
- activeRequestRef.current = abortController;
90
- const read = (async () => {
91
- if (showLoading) {
92
- setLoading(true);
93
- setError(null);
94
- setSSOData(null);
95
- }
96
- try {
97
- const response = await fetch(ssoUrls.userInfo, {
98
- credentials: "include",
99
- signal: abortController.signal
100
- });
101
- if (!response.ok) {
102
- if (response.status === 401 || response.status === 403) {
103
- setSSOData(null);
104
- setError("unauthenticated");
105
- } else {
106
- setError("request_failed");
107
- }
108
- return null;
109
- }
110
- const data = await response.json();
111
- if (!isValidSSOUserInfo(data)) {
112
- console.error(
113
- '[AthenaSDK] Invalid SSO userinfo response: expected "user.user_id", "user.email", "orgMemberInfos", and "accessToken"'
114
- );
115
- setSSOData(null);
116
- setError("invalid_response");
117
- return null;
118
- }
119
- setSSOData((prev) => {
120
- if (prev && prev.accessToken === data.accessToken && prev.user.user_id === data.user.user_id) {
121
- return prev;
122
- }
123
- return data;
124
- });
125
- setError(null);
126
- return data;
127
- } catch (fetchError) {
128
- if (fetchError instanceof DOMException && fetchError.name === "AbortError") {
129
- return activeRequestRef.current !== abortController ? activeReadRef.current : null;
130
- }
131
- setError("request_failed");
132
- return null;
133
- } finally {
134
- if (activeRequestRef.current === abortController) {
135
- activeRequestRef.current = null;
136
- setLoading(false);
211
+ try {
212
+ return await readSession({ url: ssoUrls.userInfo, signal, policy: retryPolicyRef.current });
213
+ } catch (readError) {
214
+ if (readError instanceof errors.AthenaSdkError) {
215
+ athenaDiagnostics.athenaDiagnostics.emit("auth.sso.userinfo.error", { error: readError });
216
+ try {
217
+ (_a = onErrorRef.current) == null ? void 0 : _a.call(onErrorRef, readError);
218
+ } catch (callbackError) {
219
+ console.error("[AthenaSDK] AthenaSSOProvider onError threw", callbackError);
137
220
  }
138
221
  }
139
- })();
140
- activeReadRef.current = read;
141
- return read;
222
+ throw readError;
223
+ }
142
224
  },
143
225
  [ssoUrls.userInfo]
144
226
  );
145
- react.useEffect(() => {
146
- void refreshSession({ showLoading: true });
147
- return () => {
148
- var _a;
149
- (_a = activeRequestRef.current) == null ? void 0 : _a.abort();
150
- };
151
- }, [refreshSession]);
227
+ const query = reactQuery.useQuery(
228
+ {
229
+ queryKey,
230
+ queryFn,
231
+ // `readSession` retries a transient failure itself, with the bridge's
232
+ // policy; the query never retries on top of it, and reads while the
233
+ // browser reports itself offline too, so that policy decides.
234
+ retry: false,
235
+ networkMode: "always",
236
+ staleTime: Number.POSITIVE_INFINITY,
237
+ refetchOnWindowFocus: false,
238
+ refetchOnReconnect: false
239
+ },
240
+ queryClient
241
+ );
242
+ const answer = query.data;
243
+ const session = (answer == null ? void 0 : answer.kind) === "session" ? answer.data : null;
244
+ const loading = query.isPending;
245
+ const reason = session ? null : (answer == null ? void 0 : answer.kind) === "no_session" ? answer.reason : query.isError ? "request_failed" : null;
246
+ const signedOut = (answer == null ? void 0 : answer.kind) === "no_session" && answer.signedOut === true;
152
247
  react.useEffect(() => {
153
248
  if (!revalidateOnWindowFocus || typeof window === "undefined") {
154
249
  return;
155
250
  }
156
- const handleFocus = () => {
157
- void refreshSession();
251
+ const revalidate = () => {
252
+ void queryClient.refetchQueries({ queryKey }, { cancelRefetch: false });
158
253
  };
159
254
  const handleVisibilityChange = () => {
160
255
  if (document.visibilityState === "visible") {
161
- void refreshSession();
256
+ revalidate();
162
257
  }
163
258
  };
164
- window.addEventListener("focus", handleFocus);
259
+ window.addEventListener("focus", revalidate);
165
260
  document.addEventListener("visibilitychange", handleVisibilityChange);
166
261
  return () => {
167
- window.removeEventListener("focus", handleFocus);
262
+ window.removeEventListener("focus", revalidate);
168
263
  document.removeEventListener("visibilitychange", handleVisibilityChange);
169
264
  };
170
- }, [revalidateOnWindowFocus, refreshSession]);
265
+ }, [revalidateOnWindowFocus, queryClient, queryKey]);
171
266
  react.useEffect(() => {
172
- if (!loading && error === "unauthenticated" && !displayOnError && typeof window !== "undefined") {
267
+ if (!loading && reason === "unauthenticated" && !signedOut && !displayOnError && typeof window !== "undefined") {
173
268
  window.location.assign(ssoUrls.login);
174
269
  }
175
- }, [loading, error, displayOnError, ssoUrls.login]);
270
+ }, [loading, reason, signedOut, displayOnError, ssoUrls.login]);
271
+ const retry = react.useCallback(() => {
272
+ void queryClient.resetQueries({ queryKey });
273
+ }, [queryClient, queryKey]);
176
274
  const logout = react.useCallback(async (redirectOnLogout = true) => {
177
- var _a;
178
- (_a = activeRequestRef.current) == null ? void 0 : _a.abort();
275
+ await queryClient.cancelQueries({ queryKey });
179
276
  try {
180
277
  await fetch(ssoUrls.logout, {
181
278
  method: "POST",
@@ -184,25 +281,36 @@ function AthenaSSOProvider({
184
281
  } catch (logoutError) {
185
282
  console.error("[AthenaSDK] Failed to invalidate SSO session during logout", logoutError);
186
283
  }
187
- setSSOData(null);
188
- setError(null);
189
- setLoading(false);
284
+ queryClient.setQueryData(queryKey, {
285
+ kind: "no_session",
286
+ reason: "unauthenticated",
287
+ signedOut: true
288
+ });
190
289
  if (redirectOnLogout && typeof window !== "undefined") {
191
290
  window.location.assign(ssoUrls.login);
192
291
  }
193
- }, [ssoUrls.login, ssoUrls.logout]);
194
- const ssoDataRef = react.useRef(ssoData);
195
- ssoDataRef.current = ssoData;
292
+ }, [queryClient, queryKey, ssoUrls.login, ssoUrls.logout]);
293
+ const sessionRef = react.useRef(session);
294
+ sessionRef.current = session;
196
295
  const getToken = react.useCallback(
197
296
  async ({ force = false } = {}) => {
198
297
  var _a;
199
298
  if (force) {
200
- const renewed = await refreshSession();
201
- if (renewed) return renewed.accessToken;
299
+ try {
300
+ const renewed = await queryClient.fetchQuery({
301
+ queryKey,
302
+ queryFn,
303
+ staleTime: 0,
304
+ retry: false,
305
+ networkMode: "always"
306
+ });
307
+ if (renewed.kind === "session") return renewed.data.accessToken;
308
+ } catch {
309
+ }
202
310
  }
203
- return ((_a = ssoDataRef.current) == null ? void 0 : _a.accessToken) ?? null;
311
+ return ((_a = sessionRef.current) == null ? void 0 : _a.accessToken) ?? null;
204
312
  },
205
- [refreshSession]
313
+ [queryClient, queryKey, queryFn]
206
314
  );
207
315
  const authState = react.useMemo(() => {
208
316
  if (loading) {
@@ -217,19 +325,7 @@ function AthenaSSOProvider({
217
325
  getToken
218
326
  };
219
327
  }
220
- if (error === "request_failed" && ssoData) {
221
- const { user: user2, orgs: orgs2, accessToken: accessToken2 } = mapSSOResponse(ssoData);
222
- return {
223
- isLoggedIn: true,
224
- isLoading: false,
225
- user: user2,
226
- accessToken: accessToken2,
227
- orgs: orgs2,
228
- logout,
229
- getToken
230
- };
231
- }
232
- if (error || !ssoData) {
328
+ if (!session) {
233
329
  return {
234
330
  isLoggedIn: false,
235
331
  isLoading: false,
@@ -240,7 +336,7 @@ function AthenaSSOProvider({
240
336
  getToken
241
337
  };
242
338
  }
243
- const { user, orgs, accessToken } = mapSSOResponse(ssoData);
339
+ const { user, orgs, accessToken } = mapSSOResponse(session);
244
340
  return {
245
341
  isLoggedIn: true,
246
342
  isLoading: false,
@@ -250,11 +346,14 @@ function AthenaSSOProvider({
250
346
  logout,
251
347
  getToken
252
348
  };
253
- }, [loading, error, ssoData, logout, getToken]);
349
+ }, [loading, session, logout, getToken]);
254
350
  if (authState.isLoading) {
255
351
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayWhileLoading ?? null });
256
352
  }
257
353
  if (!authState.isLoggedIn) {
354
+ if (typeof displayOnError === "function") {
355
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayOnError({ reason: reason ?? "unauthenticated", retry }) });
356
+ }
258
357
  return displayOnError ? /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: displayOnError }) : null;
259
358
  }
260
359
  return /* @__PURE__ */ jsxRuntime.jsx(AthenaAuthContext.AthenaAuthContext.Provider, { value: authState, children });