@athenaintel/react 0.41.0 → 0.41.2

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 (39) hide show
  1. package/README.md +10 -0
  2. package/dist/agent-runtime/src/protocol.cjs +15 -0
  3. package/dist/agent-runtime/src/protocol.cjs.map +1 -1
  4. package/dist/agent-runtime/src/protocol.js +16 -1
  5. package/dist/agent-runtime/src/protocol.js.map +1 -1
  6. package/dist/agent-runtime/src/thread-snapshot-cache.cjs +2 -1
  7. package/dist/agent-runtime/src/thread-snapshot-cache.cjs.map +1 -1
  8. package/dist/agent-runtime/src/thread-snapshot-cache.js +2 -1
  9. package/dist/agent-runtime/src/thread-snapshot-cache.js.map +1 -1
  10. package/dist/chat-ui/lib/asset-urls.cjs +6 -1
  11. package/dist/chat-ui/lib/asset-urls.cjs.map +1 -1
  12. package/dist/chat-ui/lib/asset-urls.d.ts +6 -1
  13. package/dist/chat-ui/lib/asset-urls.js +7 -2
  14. package/dist/chat-ui/lib/asset-urls.js.map +1 -1
  15. package/dist/index.cjs +1 -1
  16. package/dist/index.d.ts +2 -2
  17. package/dist/index.js +2 -2
  18. package/dist/lib/athena-urls.cjs +17 -0
  19. package/dist/lib/athena-urls.cjs.map +1 -1
  20. package/dist/lib/athena-urls.d.ts +14 -0
  21. package/dist/lib/athena-urls.js +17 -0
  22. package/dist/lib/athena-urls.js.map +1 -1
  23. package/dist/react/package.json.cjs +1 -1
  24. package/dist/react/package.json.js +1 -1
  25. package/dist/runtime/useAthenaStatewireRuntime.cjs +4 -14
  26. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  27. package/dist/runtime/useAthenaStatewireRuntime.d.ts +5 -4
  28. package/dist/runtime/useAthenaStatewireRuntime.js +4 -14
  29. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  30. package/dist/workbench/session-workbench-model.cjs +0 -18
  31. package/dist/workbench/session-workbench-model.cjs.map +1 -1
  32. package/dist/workbench/session-workbench-model.d.ts +0 -13
  33. package/dist/workbench/session-workbench-model.js +0 -18
  34. package/dist/workbench/session-workbench-model.js.map +1 -1
  35. package/dist/workbench/session-workbench-panes.cjs +1 -1
  36. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  37. package/dist/workbench/session-workbench-panes.js +2 -2
  38. package/dist/workbench/session-workbench-panes.js.map +1 -1
  39. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"athena-urls.js","sources":["../../src/lib/athena-urls.ts"],"sourcesContent":["export type AthenaEnvironment = 'production' | 'staging';\n\nexport interface AthenaEnvironmentUrls {\n apiUrl: string;\n backendUrl: string;\n appUrl: string;\n}\n\n/**\n * Bare origins: `apiUrl` is the Iris host fronting the deployment (its origin\n * derives the `/v2/threads` statewire mount) and `backendUrl` is the Agora\n * REST base. Neither carries a path — `deriveStatewireSyncUrl` and the REST\n * helpers append their own.\n */\nconst ATHENA_ENVIRONMENT_URLS: Record<AthenaEnvironment, AthenaEnvironmentUrls> = {\n production: {\n apiUrl: 'https://iris.prd.athenaintel.com',\n backendUrl: 'https://api.athenaintel.com',\n appUrl: 'https://app.athenaintel.com',\n },\n staging: {\n apiUrl: 'https://iris.stg.athenaintel.com',\n backendUrl: 'https://staging-api.athenaintel.com',\n appUrl: 'https://staging-app.athenaintel.com',\n },\n};\n\nexport const DEFAULT_ATHENA_ENVIRONMENT: AthenaEnvironment = 'production';\nexport const DEFAULT_APP_URL = ATHENA_ENVIRONMENT_URLS.production.appUrl;\n/** Production statewire sync endpoint — the same host the mobile app and\n * Chrome extension ship as their production statewire base. Exported as a\n * constant for explicit configuration; it is deliberately NOT used as an\n * implicit fallback (see {@link resolveStatewireSyncUrl}). */\nexport const DEFAULT_STATEWIRE_SYNC_URL = 'https://iris.prd.athenaintel.com/v2/threads';\n\nconst STATEWIRE_SYNC_PATHNAME = '/v2/threads';\n\n/**\n * Derive the statewire sync endpoint (the `…/v2/threads` mount) from the Iris\n * host's origin: staging `https://iris.stg.athenaintel.com` maps to\n * `https://iris.stg.athenaintel.com/v2/threads`, and likewise for production\n * and workspace Iris hosts (a URL still carrying the retired `/api/chat` path\n * resolves the same way). Unparseable inputs return `null` so callers can\n * fall back explicitly.\n */\nexport function deriveStatewireSyncUrl({\n apiUrl,\n}: {\n apiUrl?: string | null;\n}): string | null {\n if (!apiUrl) return null;\n\n try {\n const parsedUrl = new URL(apiUrl);\n return `${parsedUrl.origin}${STATEWIRE_SYNC_PATHNAME}`;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve the statewire sync endpoint for a provider configuration: an\n * explicit `statewireSyncUrl` wins, otherwise it is derived from the\n * effective `apiUrl` host.\n *\n * When neither yields a URL this throws instead of falling back to an Athena\n * default — mirroring `deriveAthenaApiUrl`'s null-for-unknown-hosts rule, so\n * a misconfigured custom deployment can never silently send its bearer token\n * or API key to Athena production.\n */\nexport function resolveStatewireSyncUrl({\n statewireSyncUrl,\n apiUrl,\n}: {\n statewireSyncUrl?: string;\n apiUrl?: string | null;\n}): string {\n if (statewireSyncUrl) return statewireSyncUrl;\n\n const derived = deriveStatewireSyncUrl({ apiUrl });\n if (derived) return derived;\n\n throw new Error(\n `[AthenaSDK] transport 'statewire' could not derive a sync URL from apiUrl (${JSON.stringify(\n apiUrl ?? null,\n )}). Pass an explicit statewireSyncUrl (the …/v2/threads mount). Credentials are never sent to a default host.`,\n );\n}\n\n/**\n * Refuse to attach a host-authenticated app to a guessed host. A host that\n * answered the auth bridge (a `/_athena/auth` mint, an `athena-config`\n * message) but minted no statewire mount predates the statewire-native\n * mint; without an explicit `statewireSyncUrl` or `apiUrl` the provider\n * would otherwise fall back to the environment preset and send that host's\n * credential to managed-cloud Athena. No host answering at all is not this\n * case: the preset is the documented default for a standalone app.\n */\nexport function assertHostMintedStatewireMount({\n hostAnswered,\n hostStatewireSyncUrl,\n statewireSyncUrl,\n apiUrl,\n}: {\n hostAnswered: boolean;\n hostStatewireSyncUrl: string | null;\n statewireSyncUrl?: string;\n apiUrl?: string;\n}): void {\n if (!hostAnswered || hostStatewireSyncUrl || statewireSyncUrl || apiUrl) return;\n throw new Error(\n '[AthenaSDK] The host answered the auth bridge without a statewire sync mount (statewireSyncUrl) the SDK accepted: either the host predates the statewire-native mint (update it: Marathon >= 2026-08-29), or the parent page sent a URL the SDK refused (see the auth.bridge.config_rejected diagnostics event: it must be https, on an Athena host or under the parent page\\'s own domain). Or configure statewireSyncUrl / apiUrl explicitly. Credentials are never sent to a default host.',\n );\n}\n\nconst MARATHON_APP_PORT = '8082';\nconst SPACES_PATHNAME = 'dashboard/spaces/';\n\nconst normalizeBaseUrl = (url: string): string => url.replace(/\\/+$/, '');\n\nconst createUrlWithFallback = (url: string, fallbackUrl: string): URL => {\n try {\n return new URL(`${normalizeBaseUrl(url)}/`);\n } catch {\n return new URL(`${normalizeBaseUrl(fallbackUrl)}/`);\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) return null;\n\n try {\n return new URL(value).hostname;\n } catch {\n return null;\n }\n};\n\nconst deriveWorkspaceAppUrl = ({\n sourceUrl,\n sourcePort,\n}: {\n sourceUrl: string;\n sourcePort: '8000' | '8787';\n}): string | null => {\n try {\n const parsedUrl = new URL(sourceUrl);\n const match = parsedUrl.hostname.match(\n new RegExp(`^(?<prefix>.+)--${sourcePort}\\\\.(?<domain>app\\\\.athenaintel\\\\.com|staging-app\\\\.athenaintel\\\\.com)$`),\n );\n\n if (!match?.groups?.prefix || !match.groups.domain) {\n return null;\n }\n\n return `${parsedUrl.protocol}//${match.groups.prefix}--${MARATHON_APP_PORT}.${match.groups.domain}`;\n } catch {\n return null;\n }\n};\n\nconst deriveWorkspaceApiUrl = (backendUrl: string): string | null => {\n try {\n const parsedUrl = new URL(backendUrl);\n const match = parsedUrl.hostname.match(\n /^(?<prefix>.+)--8000\\.(?<domain>app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/,\n );\n\n if (!match?.groups?.prefix || !match.groups.domain) {\n return null;\n }\n\n return `${parsedUrl.protocol}//${match.groups.prefix}--8787.${match.groups.domain}`;\n } catch {\n return null;\n }\n};\n\nexport function getAthenaEnvironmentUrls({\n environment = DEFAULT_ATHENA_ENVIRONMENT,\n}: {\n environment?: AthenaEnvironment;\n} = {}): AthenaEnvironmentUrls {\n return ATHENA_ENVIRONMENT_URLS[environment];\n}\n\n/**\n * Resolve the Iris origin that fronts a known Agora backend.\n *\n * Unknown custom backends deliberately return `null`: callers can preserve a\n * direct-Agora fallback instead of accidentally sending a custom environment's\n * bearer token and traffic through production Iris.\n */\nexport function deriveAthenaApiUrl({\n backendUrl,\n}: {\n backendUrl?: string | null;\n}): string | null {\n if (!backendUrl) return null;\n\n const workspaceApiUrl = deriveWorkspaceApiUrl(backendUrl);\n if (workspaceApiUrl) return workspaceApiUrl;\n\n try {\n const parsedUrl = new URL(backendUrl);\n if (parsedUrl.hostname === 'api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.apiUrl;\n }\n if (parsedUrl.hostname === 'staging-api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.apiUrl;\n }\n if (parsedUrl.hostname === 'localhost' || parsedUrl.hostname === '127.0.0.1') {\n parsedUrl.port = '8787';\n return parsedUrl.origin;\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nexport function deriveAthenaAppUrl({\n apiUrl,\n backendUrl,\n}: {\n apiUrl?: string | null;\n backendUrl?: string | null;\n}): string | null {\n if (backendUrl) {\n const workspaceAppUrl = deriveWorkspaceAppUrl({\n sourceUrl: backendUrl,\n sourcePort: '8000',\n });\n if (workspaceAppUrl) return workspaceAppUrl;\n\n const backendHostname = getHostname(backendUrl);\n if (backendHostname === 'api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.appUrl;\n }\n if (backendHostname === 'staging-api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.appUrl;\n }\n }\n\n if (apiUrl) {\n const workspaceAppUrl = deriveWorkspaceAppUrl({\n sourceUrl: apiUrl,\n sourcePort: '8787',\n });\n if (workspaceAppUrl) return workspaceAppUrl;\n\n const apiHostname = getHostname(apiUrl);\n if (apiHostname === 'iris.prd.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.appUrl;\n }\n if (apiHostname === 'iris.stg.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.appUrl;\n }\n }\n\n return null;\n}\n\nexport function createAthenaSpacesUrl({\n appUrl = DEFAULT_APP_URL,\n assetIds,\n sessionId,\n}: {\n appUrl?: string;\n assetIds?: string | string[];\n sessionId?: string;\n}): string {\n const url = new URL(SPACES_PATHNAME, createUrlWithFallback(appUrl, DEFAULT_APP_URL));\n\n if (assetIds) {\n const normalizedAssetIds = (Array.isArray(assetIds) ? assetIds : [assetIds])\n .map((assetId) => assetId.trim())\n .filter((assetId) => assetId.length > 0);\n\n if (normalizedAssetIds.length > 0) {\n url.searchParams.set('asset_ids', normalizedAssetIds.join(','));\n }\n }\n\n if (sessionId) {\n url.searchParams.set('session_id', sessionId);\n }\n\n return url.toString();\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"athena-urls.js","sources":["../../src/lib/athena-urls.ts"],"sourcesContent":["export type AthenaEnvironment = 'production' | 'staging';\n\nexport interface AthenaEnvironmentUrls {\n apiUrl: string;\n backendUrl: string;\n appUrl: string;\n}\n\n/**\n * Bare origins: `apiUrl` is the Iris host fronting the deployment (its origin\n * derives the `/v2/threads` statewire mount) and `backendUrl` is the Agora\n * REST base. Neither carries a path — `deriveStatewireSyncUrl` and the REST\n * helpers append their own.\n */\nconst ATHENA_ENVIRONMENT_URLS: Record<AthenaEnvironment, AthenaEnvironmentUrls> = {\n production: {\n apiUrl: 'https://iris.prd.athenaintel.com',\n backendUrl: 'https://api.athenaintel.com',\n appUrl: 'https://app.athenaintel.com',\n },\n staging: {\n apiUrl: 'https://iris.stg.athenaintel.com',\n backendUrl: 'https://staging-api.athenaintel.com',\n appUrl: 'https://staging-app.athenaintel.com',\n },\n};\n\nexport const DEFAULT_ATHENA_ENVIRONMENT: AthenaEnvironment = 'production';\nexport const DEFAULT_APP_URL = ATHENA_ENVIRONMENT_URLS.production.appUrl;\n/** Production statewire sync endpoint — the same host the mobile app and\n * Chrome extension ship as their production statewire base. Exported as a\n * constant for explicit configuration; it is deliberately NOT used as an\n * implicit fallback (see {@link resolveStatewireSyncUrl}). */\nexport const DEFAULT_STATEWIRE_SYNC_URL = 'https://iris.prd.athenaintel.com/v2/threads';\n\nconst STATEWIRE_SYNC_PATHNAME = '/v2/threads';\n\n/**\n * Derive the statewire sync endpoint (the `…/v2/threads` mount) from the Iris\n * host's origin: staging `https://iris.stg.athenaintel.com` maps to\n * `https://iris.stg.athenaintel.com/v2/threads`, and likewise for production\n * and workspace Iris hosts (a URL still carrying the retired `/api/chat` path\n * resolves the same way). Unparseable inputs return `null` so callers can\n * fall back explicitly.\n */\nexport function deriveStatewireSyncUrl({\n apiUrl,\n}: {\n apiUrl?: string | null;\n}): string | null {\n if (!apiUrl) return null;\n\n try {\n const parsedUrl = new URL(apiUrl);\n return `${parsedUrl.origin}${STATEWIRE_SYNC_PATHNAME}`;\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve the statewire sync endpoint for a provider configuration: an\n * explicit `statewireSyncUrl` wins, otherwise it is derived from the\n * effective `apiUrl` host.\n *\n * When neither yields a URL this throws instead of falling back to an Athena\n * default — mirroring `deriveAthenaApiUrl`'s null-for-unknown-hosts rule, so\n * a misconfigured custom deployment can never silently send its bearer token\n * or API key to Athena production.\n */\nexport function resolveStatewireSyncUrl({\n statewireSyncUrl,\n apiUrl,\n}: {\n statewireSyncUrl?: string;\n apiUrl?: string | null;\n}): string {\n if (statewireSyncUrl) return statewireSyncUrl;\n\n const derived = deriveStatewireSyncUrl({ apiUrl });\n if (derived) return derived;\n\n throw new Error(\n `[AthenaSDK] transport 'statewire' could not derive a sync URL from apiUrl (${JSON.stringify(\n apiUrl ?? null,\n )}). Pass an explicit statewireSyncUrl (the …/v2/threads mount). Credentials are never sent to a default host.`,\n );\n}\n\n/**\n * Refuse to attach a host-authenticated app to a guessed host. A host that\n * answered the auth bridge (a `/_athena/auth` mint, an `athena-config`\n * message) but minted no statewire mount predates the statewire-native\n * mint; without an explicit `statewireSyncUrl` or `apiUrl` the provider\n * would otherwise fall back to the environment preset and send that host's\n * credential to managed-cloud Athena. No host answering at all is not this\n * case: the preset is the documented default for a standalone app.\n */\nexport function assertHostMintedStatewireMount({\n hostAnswered,\n hostStatewireSyncUrl,\n statewireSyncUrl,\n apiUrl,\n}: {\n hostAnswered: boolean;\n hostStatewireSyncUrl: string | null;\n statewireSyncUrl?: string;\n apiUrl?: string;\n}): void {\n if (!hostAnswered || hostStatewireSyncUrl || statewireSyncUrl || apiUrl) return;\n throw new Error(\n '[AthenaSDK] The host answered the auth bridge without a statewire sync mount (statewireSyncUrl) the SDK accepted: either the host predates the statewire-native mint (update it: Marathon >= 2026-08-29), or the parent page sent a URL the SDK refused (see the auth.bridge.config_rejected diagnostics event: it must be https, on an Athena host or under the parent page\\'s own domain). Or configure statewireSyncUrl / apiUrl explicitly. Credentials are never sent to a default host.',\n );\n}\n\nconst MARATHON_APP_PORT = '8082';\nconst SPACES_PATHNAME = 'dashboard/spaces/';\n\nconst normalizeBaseUrl = (url: string): string => url.replace(/\\/+$/, '');\n\nconst createUrlWithFallback = (url: string, fallbackUrl: string): URL => {\n try {\n return new URL(`${normalizeBaseUrl(url)}/`);\n } catch {\n return new URL(`${normalizeBaseUrl(fallbackUrl)}/`);\n }\n};\n\nconst getHostname = (value?: string | null): string | null => {\n if (!value) return null;\n\n try {\n return new URL(value).hostname;\n } catch {\n return null;\n }\n};\n\nconst deriveWorkspaceAppUrl = ({\n sourceUrl,\n sourcePort,\n}: {\n sourceUrl: string;\n sourcePort: '8000' | '8787';\n}): string | null => {\n try {\n const parsedUrl = new URL(sourceUrl);\n const match = parsedUrl.hostname.match(\n new RegExp(`^(?<prefix>.+)--${sourcePort}\\\\.(?<domain>app\\\\.athenaintel\\\\.com|staging-app\\\\.athenaintel\\\\.com)$`),\n );\n\n if (!match?.groups?.prefix || !match.groups.domain) {\n return null;\n }\n\n return `${parsedUrl.protocol}//${match.groups.prefix}--${MARATHON_APP_PORT}.${match.groups.domain}`;\n } catch {\n return null;\n }\n};\n\nconst deriveWorkspaceApiUrl = (backendUrl: string): string | null => {\n try {\n const parsedUrl = new URL(backendUrl);\n const match = parsedUrl.hostname.match(\n /^(?<prefix>.+)--8000\\.(?<domain>app\\.athenaintel\\.com|staging-app\\.athenaintel\\.com)$/,\n );\n\n if (!match?.groups?.prefix || !match.groups.domain) {\n return null;\n }\n\n return `${parsedUrl.protocol}//${match.groups.prefix}--8787.${match.groups.domain}`;\n } catch {\n return null;\n }\n};\n\nexport function getAthenaEnvironmentUrls({\n environment = DEFAULT_ATHENA_ENVIRONMENT,\n}: {\n environment?: AthenaEnvironment;\n} = {}): AthenaEnvironmentUrls {\n return ATHENA_ENVIRONMENT_URLS[environment];\n}\n\n/**\n * Resolve the Iris origin that fronts a known Agora backend.\n *\n * Unknown custom backends deliberately return `null`: callers can preserve a\n * direct-Agora fallback instead of accidentally sending a custom environment's\n * bearer token and traffic through production Iris.\n */\nexport function deriveAthenaApiUrl({\n backendUrl,\n}: {\n backendUrl?: string | null;\n}): string | null {\n if (!backendUrl) return null;\n\n const workspaceApiUrl = deriveWorkspaceApiUrl(backendUrl);\n if (workspaceApiUrl) return workspaceApiUrl;\n\n try {\n const parsedUrl = new URL(backendUrl);\n if (parsedUrl.hostname === 'api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.apiUrl;\n }\n if (parsedUrl.hostname === 'staging-api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.apiUrl;\n }\n if (parsedUrl.hostname === 'localhost' || parsedUrl.hostname === '127.0.0.1') {\n parsedUrl.port = '8787';\n return parsedUrl.origin;\n }\n } catch {\n return null;\n }\n\n return null;\n}\n\nexport function deriveAthenaAppUrl({\n apiUrl,\n backendUrl,\n}: {\n apiUrl?: string | null;\n backendUrl?: string | null;\n}): string | null {\n if (backendUrl) {\n const workspaceAppUrl = deriveWorkspaceAppUrl({\n sourceUrl: backendUrl,\n sourcePort: '8000',\n });\n if (workspaceAppUrl) return workspaceAppUrl;\n\n const backendHostname = getHostname(backendUrl);\n if (backendHostname === 'api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.appUrl;\n }\n if (backendHostname === 'staging-api.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.appUrl;\n }\n }\n\n if (apiUrl) {\n const workspaceAppUrl = deriveWorkspaceAppUrl({\n sourceUrl: apiUrl,\n sourcePort: '8787',\n });\n if (workspaceAppUrl) return workspaceAppUrl;\n\n const apiHostname = getHostname(apiUrl);\n if (apiHostname === 'iris.prd.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.production.appUrl;\n }\n if (apiHostname === 'iris.stg.athenaintel.com') {\n return ATHENA_ENVIRONMENT_URLS.staging.appUrl;\n }\n }\n\n return null;\n}\n\nexport function createAthenaSpacesUrl({\n appUrl = DEFAULT_APP_URL,\n assetIds,\n sessionId,\n}: {\n appUrl?: string;\n assetIds?: string | string[];\n sessionId?: string;\n}): string {\n const url = new URL(SPACES_PATHNAME, createUrlWithFallback(appUrl, DEFAULT_APP_URL));\n\n if (assetIds) {\n const normalizedAssetIds = (Array.isArray(assetIds) ? assetIds : [assetIds])\n .map((assetId) => assetId.trim())\n .filter((assetId) => assetId.length > 0);\n\n if (normalizedAssetIds.length > 0) {\n url.searchParams.set('asset_ids', normalizedAssetIds.join(','));\n }\n }\n\n if (sessionId) {\n url.searchParams.set('session_id', sessionId);\n }\n\n return url.toString();\n}\n\nfunction originOf(url: string): string | null {\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\n/**\n * The Athena frontend an \"Open in Athena\" link points at. The provider's\n * `appUrl` is that frontend, except in an app a computer serves: Marathon's\n * `/_athena/auth` mints `appUrl` as the app's OWN origin, so a link built on\n * it opens the app instead of Athena. When `appUrl` is the page's own origin\n * and the Agora base belongs to a known Athena deployment, the deployment's\n * frontend wins (on Olympus itself the two are the same origin anyway). The\n * workbench's \"Open in Athena\" links and the chat's asset chips both go by it.\n */\nexport function athenaLinkAppUrl({\n appUrl,\n backendUrl,\n pageOrigin,\n}: {\n appUrl: string;\n backendUrl: string;\n pageOrigin: string | null;\n}): string {\n const deployed = deriveAthenaAppUrl({ backendUrl });\n if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;\n return appUrl;\n}\n"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperties(exports, { __esModule: { value: true }, [Symbol.toStringTag]: { value: "Module" } });
3
- const version = "0.41.0";
3
+ const version = "0.41.2";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.41.0";
1
+ const version = "0.41.2";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -11,7 +11,7 @@ const statewireRunConfig = require("./statewire-run-config.cjs");
11
11
  const runconfigContract = require("../agent-runtime/src/generated/runconfig-contract.cjs");
12
12
  const useDeepAgentRuntime = require("../agent-runtime/src/use-deep-agent-runtime.cjs");
13
13
  const errors = require("../agent-runtime/src/errors.cjs");
14
- const statewireState = require("../agent-runtime/src/statewire-state.cjs");
14
+ const protocol = require("../agent-runtime/src/protocol.cjs");
15
15
  const composerMarkdown = require("../agent-runtime/src/composer-markdown.cjs");
16
16
  const ATHENA_SDK_MESSAGE_WINDOW = {
17
17
  size: runconfigContract.DEEP_AGENT_DEFAULT_MESSAGE_WINDOW
@@ -83,8 +83,6 @@ function useAthenaStatewireRuntime(config) {
83
83
  clientTools: statewireClientTools.statewireClientToolWireEntries(clientTools)
84
84
  });
85
85
  const runConfigKey = JSON.stringify(runConfigValue);
86
- const lastStateRef = react.useRef(null);
87
- const helloLandedRef = react.useRef(false);
88
86
  const auiTools = react.useMemo(() => react$1.Tools({ toolkit: frontendToolkit }), [frontendToolkit]);
89
87
  const useSdkClient = (thread) => react$1.useAui({ thread, tools: auiTools });
90
88
  return useDeepAgentRuntime.useDeepAgentRuntime({
@@ -123,14 +121,13 @@ function useAthenaStatewireRuntime(config) {
123
121
  getMessageMetadata: composerMarkdown.withStagedComposerMarkdown({ threadId }),
124
122
  useClient: useSdkClient,
125
123
  onStateChange: (state) => {
124
+ if (!protocol.isDeepAgentHostState(state)) return;
126
125
  const endSpan = attachSpanRef.current;
127
126
  if (endSpan) {
128
127
  attachSpanRef.current = null;
129
128
  endSpan("statewire.snapshot", { data: { thread_id: threadId } });
130
129
  }
131
- if (!statewireState.isDeepAgentStateHydrated(state)) return;
132
- lastStateRef.current = state;
133
- if (helloLandedRef.current) onServerState == null ? void 0 : onServerState(state);
130
+ onServerState == null ? void 0 : onServerState(state);
134
131
  },
135
132
  onConnectionChange: (connection) => {
136
133
  athenaDiagnostics.athenaDiagnostics.gauge("statewire.connection", (connection == null ? void 0 : connection.status) ?? null);
@@ -146,14 +143,7 @@ function useAthenaStatewireRuntime(config) {
146
143
  }
147
144
  onConnectionChange == null ? void 0 : onConnectionChange(connection);
148
145
  },
149
- onRawConnectionChange: (connection) => {
150
- if (connection.status === "connected" && !helloLandedRef.current) {
151
- helloLandedRef.current = true;
152
- const helloState = lastStateRef.current;
153
- if (helloState) onServerState == null ? void 0 : onServerState(helloState);
154
- }
155
- onRawConnectionChange == null ? void 0 : onRawConnectionChange(connection);
156
- },
146
+ onRawConnectionChange,
157
147
  onRunningChange,
158
148
  // Each model-context notification pulls the latest getter through the
159
149
  // core's per-render ref; the value-stable key re-registers (and so
@@ -1 +1 @@
1
- {"version":3,"file":"useAthenaStatewireRuntime.cjs","sources":["../../src/runtime/useAthenaStatewireRuntime.ts"],"sourcesContent":["import type { AssistantClient, Toolkit } from '@assistant-ui/react';\nimport { Tools, useAui } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n type DeepAgentMessageWindowDeclaration,\n type DeepAgentStreamValues,\n type DeepAgentThreadElement,\n type DeepAgentThreadSnapshotCache,\n type DeepAgentToolOutputStore,\n isDeepAgentStateHydrated,\n isStatewireChannelTeardown,\n logUnhandledStatewireError,\n type RegisteredClientTool,\n useDeepAgentRuntime,\n type UseDeepAgentThreadOptions,\n withStagedComposerMarkdown,\n} from '@athenaintel/agent-runtime';\nimport { useContext, useMemo, useRef } from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n collectStatewireClientTools,\n statewireClientToolWireEntries,\n} from '../chat/statewire-client-tools';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n} from './statewire-auth-headers';\nimport { statewireSessionKey } from './statewire-session-key';\n\ntype AthenaDiagnosticsSpanStarter = typeof athenaDiagnostics.span;\nimport {\n buildStatewireRunConfig,\n DEFAULT_STATEWIRE_MODEL,\n resolveAgentProp,\n} from './statewire-run-config';\n\nexport { DEFAULT_STATEWIRE_MODEL };\n\n/**\n * The head interest every SDK attach states (`athena/interest`): the host\n * mounts the transcript's tail as message documents instead of every message\n * — megabytes on long threads, over the 1 MiB frame limit of stock websocket\n * clients. Older pages load from the `StatewireHistoryEdge` row the chat UI\n * mounts above the messages, and cut tool outputs are fetched back for the\n * cards that show them.\n */\nexport const ATHENA_SDK_MESSAGE_WINDOW: DeepAgentMessageWindowDeclaration = {\n size: DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n};\n\nexport interface AthenaStatewireRuntimeConfig {\n /** Base URL of the statewire sync host (the `…/v2/threads` mount). */\n syncUrl: string;\n /** Thread ID the statewire session attaches to. */\n threadId: string;\n /** True only for a never-started local chat. Existing (or pre-assigned)\n * threads load until their server snapshot arrives. */\n isNewChat?: boolean;\n /** API key for authentication. Used when no auth token is provided. */\n apiKey?: string;\n /** Auth token. Takes priority over apiKey. */\n token?: string | null;\n /** Resolve the latest token immediately before a request (for expiring\n * clients). Called with `{ force: true }` right after the host refused the\n * previous credential (the transport's `staleAuth`), so a caching getter\n * mints a fresh token instead of returning the refused one. */\n getToken?: AthenaStatewireTokenGetter;\n /**\n * Per-provider LRU of each thread's last wire state. A switch back to a\n * remembered thread mounts with that snapshot and projects ready on the\n * first frame instead of a loading treatment; the attach snapshot then\n * replaces it. Owned above the per-thread mount so it survives switches.\n */\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /** The head interest stated on the attach (see {@link ATHENA_SDK_MESSAGE_WINDOW}, the default). */\n messageWindow?: DeepAgentMessageWindowDeclaration;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages;\n * the thread re-converts when one lands. `AthenaProvider` owns one per\n * provider and hands the same store to `StatewireHistoryEdge`.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /** LLM model identifier. Defaults to the deep-agent default model. */\n model?: string;\n /** Agent selector: only `collab_agent:<asset_id>` refs are accepted (the run\n * executes AS that published collab agent). Any other non-empty value\n * throws — see `resolveAgentProp`. */\n agent?: string;\n /** Collab-agent channel id selecting that channel's overrides. */\n channel?: string;\n /** The agent's mode picked for this thread (`AthenaProvider` `modes`). */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Frontend tools registered on the assistant client. Tools with a local\n * `execute` are also declared as `client_tools` and executed through the\n * statewire client-tool bridge. */\n frontendToolkit?: Toolkit;\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier for session filtering. Sent as app_id in runConfig.custom. */\n appId?: string;\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n /** Called on statewire transport errors (send rejections, stream failures). */\n onError?: (error: Error) => void;\n /** Projected connection status changes (deduped; null on unmount). */\n onConnectionChange?: UseDeepAgentThreadOptions['onConnectionChange'];\n /** Raw connection observer — carries the transport's reconnect handle. */\n onRawConnectionChange?: UseDeepAgentThreadOptions['onRawConnectionChange'];\n /** Notified when a run starts/stops. */\n onRunningChange?: UseDeepAgentThreadOptions['onRunningChange'];\n /**\n * Observes the host's own state: called once the attach's hello has landed\n * (with the snapshot it carried) and then on every later frame — never with\n * the placeholder or remembered snapshot a thread renders before its hello.\n * Called per streamed token, so keep it cheap.\n */\n onServerState?: (state: DeepAgentStreamValues) => void;\n}\n\nconst EMPTY_TOOLKIT: Toolkit = {};\nconst EMPTY_TOOL_IDS: string[] = [];\n\n/** The client tools derived from the runtime's frontend toolkit — exposed so\n * the provider can mount the client-tool bridge with the same set. */\nexport function useStatewireClientTools(\n frontendToolkit: Toolkit | undefined,\n): RegisteredClientTool[] {\n return useMemo(\n () => collectStatewireClientTools(frontendToolkit ?? EMPTY_TOOLKIT),\n [frontendToolkit],\n );\n}\n\n/**\n * Statewire runtime for the Athena SDK — a thin shim over the shared\n * `@athenaintel/agent-runtime` `useDeepAgentRuntime`: it attaches to the\n * agora-hosted deep-agent thread at `${syncUrl}/${threadId}` and returns the\n * `AssistantClient` to mount via `AuiProvider` — the same integration shape as\n * the Olympus, Chrome extension, Office, and mobile statewire hosts.\n *\n * Auth: a token (resolved live through `getToken` when provided) rides\n * `Authorization: Bearer`, and an API key falls back to `X-API-KEY` — both\n * accepted by the statewire host.\n */\nexport function useAthenaStatewireRuntime(\n config: AthenaStatewireRuntimeConfig,\n): AssistantClient {\n const {\n syncUrl,\n threadId,\n isNewChat = false,\n apiKey,\n token,\n getToken,\n model,\n agent,\n channel,\n mode,\n tools = EMPTY_TOOL_IDS,\n frontendToolIds = EMPTY_TOOL_IDS,\n frontendToolkit = EMPTY_TOOLKIT,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n snapshotCache,\n messageWindow = ATHENA_SDK_MESSAGE_WINDOW,\n toolOutputs,\n onError,\n onConnectionChange,\n onRawConnectionChange,\n onRunningChange,\n onServerState,\n } = config;\n\n // Fail loud at mount, not at first send: the statewire host always runs the\n // Athena deep agent, so an `agent` outside the collab_agent namespace can\n // never apply. Throwing here (render time) is what makes a misconfigured\n // integration visible instead of quietly answering as the wrong agent.\n resolveAgentProp({ agent });\n\n const clientTools = useStatewireClientTools(frontendToolkit);\n\n // The persisted command lane is keyed by WHO is signed in as well as by host\n // and thread (see `statewireSessionKey`).\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n\n // Attach → first server snapshot span, keyed per thread attach.\n const attachSpanRef = useRef<ReturnType<AthenaDiagnosticsSpanStarter> | null>(null);\n const attachThreadRef = useRef<string | null>(null);\n if (attachThreadRef.current !== threadId) {\n attachThreadRef.current = threadId;\n attachSpanRef.current = athenaDiagnostics.span(\n `statewire.attach:${threadId}`,\n 'statewire.attach.start',\n { thread_id: threadId, is_new_chat: isNewChat },\n );\n }\n\n // Built every render so its JSON signature can key the model-context\n // registration below: consumers commonly pass fresh array literals per\n // render, which must not thrash registration, while a changed value (a new\n // `model`, channel, tool list, system prompt, …) must re-sync the context\n // before the next send. The core pulls this value only on model-context\n // notifications, which a key change triggers.\n const runConfigValue = buildStatewireRunConfig({\n model,\n agent,\n channel,\n mode,\n tools,\n frontendToolIds,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n clientTools: statewireClientToolWireEntries(clientTools),\n });\n const runConfigKey = JSON.stringify(runConfigValue);\n\n // The host's state, told apart from what the thread renders before its\n // hello: statewire publishes the initial (placeholder or remembered) state\n // through `onStateChange` too, and reports `connected` in the same flush\n // right after publishing the state the hello carried. So the latest state\n // seen when the connection first turns `connected` is the hello's, and every\n // state after it is the host's. Refs are per attach: the runtime is mounted\n // per thread.\n const lastStateRef = useRef<DeepAgentStreamValues | null>(null);\n const helloLandedRef = useRef(false);\n\n const auiTools = useMemo(() => Tools({ toolkit: frontendToolkit }), [frontendToolkit]);\n // Web client for the shared runtime core (a `use*` name so the\n // rules-of-hooks lint sees the `useAui` call inside a custom hook; the core\n // invokes it unconditionally every render).\n const useSdkClient = (thread: DeepAgentThreadElement) => useAui({ thread, tools: auiTools });\n\n return useDeepAgentRuntime({\n surface: 'AthenaSDK',\n threadId,\n baseUrl: syncUrl,\n // Option closures are re-read per render by the core, so credential\n // rotation (new `token`/`getToken`/`apiKey` props) reaches the next\n // request without remounting the thread. The transport's `staleAuth`\n // (true right after a refused credential) is forwarded as `force`.\n headers: (ctx) => resolveStatewireAuthHeaders({ apiKey, token, getToken }, ctx),\n isNewChat,\n // Queued entries render in the SDK's queue panel, so only the steer lane\n // projects into the message tail.\n laneProjection: 'steer-only',\n // Durable session: a reload resumes the lane and resends whatever the\n // server has not marked durable. Scoped by host AND principal — a thread\n // id is only meaningful against the backend that minted it, and a stored\n // lane must never resend against another signed-in user. (Consumers\n // server-render this SDK; the core's availability probe keeps those\n // sessions process-local, as before the seam existed.)\n session: {\n kind: 'local-storage',\n key: (id) => statewireSessionKey({ syncUrl, principal, threadId: id }),\n },\n snapshotCache,\n // Head interest (see `ATHENA_SDK_MESSAGE_WINDOW`): the tail of the\n // transcript instead of every message; the chat UI's history edge pages\n // older ranges in and fetches cut tool outputs into `toolOutputs`.\n messageWindow,\n toolOutputs,\n capabilities: { edit: true, reload: true, continue: true },\n // The composer stages the draft each send came from right before it\n // sends; the message carries it as its composer markdown, so its bubble\n // shows the text exactly as typed (`UserMessageText`).\n getMessageMetadata: withStagedComposerMarkdown({ threadId }),\n useClient: useSdkClient,\n onStateChange: (state) => {\n // First snapshot for this attach ends the attach span (time-to-history).\n const endSpan = attachSpanRef.current;\n if (endSpan) {\n attachSpanRef.current = null;\n endSpan('statewire.snapshot', { data: { thread_id: threadId } });\n }\n // A replay boundary republishes the pre-stream default: not host state.\n if (!isDeepAgentStateHydrated(state)) return;\n lastStateRef.current = state;\n if (helloLandedRef.current) onServerState?.(state);\n },\n onConnectionChange: (connection) => {\n athenaDiagnostics.gauge('statewire.connection', connection?.status ?? null);\n if (connection?.status === 'stopped') {\n athenaDiagnostics.emit('statewire.connection', {\n level: 'warn',\n data: {\n status: connection.status,\n reason: connection.reason,\n message: connection.message,\n },\n });\n }\n onConnectionChange?.(connection);\n },\n onRawConnectionChange: (connection) => {\n if (connection.status === 'connected' && !helloLandedRef.current) {\n helloLandedRef.current = true;\n const helloState = lastStateRef.current;\n if (helloState) onServerState?.(helloState);\n }\n onRawConnectionChange?.(connection);\n },\n onRunningChange,\n // Each model-context notification pulls the latest getter through the\n // core's per-render ref; the value-stable key re-registers (and so\n // notifies) only when the built runConfig actually changed. On a thread\n // with a server config lane, agora keeps only the per-send keys of what\n // this carries; `StatewireThreadConfigLaneSync` writes a changed model,\n // system prompt, tool list, workbench or knowledge base to the lane itself.\n runConfig: {\n get: () => runConfigValue,\n key: runConfigKey,\n },\n onError: (error) => {\n if (!isStatewireChannelTeardown(error)) {\n athenaDiagnostics.error(\n 'statewire.error',\n error,\n {\n code: 'statewire_rejected',\n message: 'The statewire host rejected a command',\n hint: 'Inspect error.detail — agora rejections carry a structured {code, message}.',\n },\n { thread_id: threadId },\n );\n }\n if (onError) {\n onError(error);\n return;\n }\n logUnhandledStatewireError('AthenaSDK', error);\n },\n });\n}\n"],"names":["DEEP_AGENT_DEFAULT_MESSAGE_WINDOW","useMemo","collectStatewireClientTools","resolveAgentProp","useContext","AthenaAuthContext","useRef","athenaDiagnostics","buildStatewireRunConfig","statewireClientToolWireEntries","Tools","useAui","useDeepAgentRuntime","resolveStatewireAuthHeaders","statewireSessionKey","withStagedComposerMarkdown","isDeepAgentStateHydrated","isStatewireChannelTeardown","logUnhandledStatewireError"],"mappings":";;;;;;;;;;;;;;;AA+CO,MAAM,4BAA+D;AAAA,EAC1E,MAAMA,kBAAAA;AACR;AA+EA,MAAM,gBAAyB,CAAA;AAC/B,MAAM,iBAA2B,CAAA;AAI1B,SAAS,wBACd,iBACwB;AACxB,SAAOC,MAAAA;AAAAA,IACL,MAAMC,qBAAAA,4BAA4B,mBAAmB,aAAa;AAAA,IAClE,CAAC,eAAe;AAAA,EAAA;AAEpB;AAaO,SAAS,0BACd,QACiB;;AACjB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAMJC,qBAAAA,iBAAiB,EAAE,OAAO;AAE1B,QAAM,cAAc,wBAAwB,eAAe;AAI3D,QAAM,cAAcC,MAAAA,WAAWC,mCAAiB;AAChD,QAAM,cAAY,gDAAa,SAAb,mBAAmB,WAAU;AAG/C,QAAM,gBAAgBC,MAAAA,OAAwD,IAAI;AAClF,QAAM,kBAAkBA,MAAAA,OAAsB,IAAI;AAClD,MAAI,gBAAgB,YAAY,UAAU;AACxC,oBAAgB,UAAU;AAC1B,kBAAc,UAAUC,kBAAAA,kBAAkB;AAAA,MACxC,oBAAoB,QAAQ;AAAA,MAC5B;AAAA,MACA,EAAE,WAAW,UAAU,aAAa,UAAA;AAAA,IAAU;AAAA,EAElD;AAQA,QAAM,iBAAiBC,mBAAAA,wBAAwB;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAaC,qBAAAA,+BAA+B,WAAW;AAAA,EAAA,CACxD;AACD,QAAM,eAAe,KAAK,UAAU,cAAc;AASlD,QAAM,eAAeH,MAAAA,OAAqC,IAAI;AAC9D,QAAM,iBAAiBA,MAAAA,OAAO,KAAK;AAEnC,QAAM,WAAWL,cAAQ,MAAMS,cAAM,EAAE,SAAS,gBAAA,CAAiB,GAAG,CAAC,eAAe,CAAC;AAIrF,QAAM,eAAe,CAAC,WAAmCC,QAAAA,OAAO,EAAE,QAAQ,OAAO,UAAU;AAE3F,SAAOC,wCAAoB;AAAA,IACzB,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,SAAS,CAAC,QAAQC,iDAA4B,EAAE,QAAQ,OAAO,SAAA,GAAY,GAAG;AAAA,IAC9E;AAAA;AAAA;AAAA,IAGA,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhB,SAAS;AAAA,MACP,MAAM;AAAA,MACN,KAAK,CAAC,OAAOC,oBAAAA,oBAAoB,EAAE,SAAS,WAAW,UAAU,GAAA,CAAI;AAAA,IAAA;AAAA,IAEvE;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,IACA;AAAA,IACA,cAAc,EAAE,MAAM,MAAM,QAAQ,MAAM,UAAU,KAAA;AAAA;AAAA;AAAA;AAAA,IAIpD,oBAAoBC,iBAAAA,2BAA2B,EAAE,UAAU;AAAA,IAC3D,WAAW;AAAA,IACX,eAAe,CAAC,UAAU;AAExB,YAAM,UAAU,cAAc;AAC9B,UAAI,SAAS;AACX,sBAAc,UAAU;AACxB,gBAAQ,sBAAsB,EAAE,MAAM,EAAE,WAAW,SAAA,GAAY;AAAA,MACjE;AAEA,UAAI,CAACC,eAAAA,yBAAyB,KAAK,EAAG;AACtC,mBAAa,UAAU;AACvB,UAAI,eAAe,QAAS,gDAAgB;AAAA,IAC9C;AAAA,IACA,oBAAoB,CAAC,eAAe;AAClCT,wBAAAA,kBAAkB,MAAM,yBAAwB,yCAAY,WAAU,IAAI;AAC1E,WAAI,yCAAY,YAAW,WAAW;AACpCA,0BAAAA,kBAAkB,KAAK,wBAAwB;AAAA,UAC7C,OAAO;AAAA,UACP,MAAM;AAAA,YACJ,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,YACnB,SAAS,WAAW;AAAA,UAAA;AAAA,QACtB,CACD;AAAA,MACH;AACA,+DAAqB;AAAA,IACvB;AAAA,IACA,uBAAuB,CAAC,eAAe;AACrC,UAAI,WAAW,WAAW,eAAe,CAAC,eAAe,SAAS;AAChE,uBAAe,UAAU;AACzB,cAAM,aAAa,aAAa;AAChC,YAAI,2DAA4B;AAAA,MAClC;AACA,qEAAwB;AAAA,IAC1B;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,WAAW;AAAA,MACT,KAAK,MAAM;AAAA,MACX,KAAK;AAAA,IAAA;AAAA,IAEP,SAAS,CAAC,UAAU;AAClB,UAAI,CAACU,OAAAA,2BAA2B,KAAK,GAAG;AACtCV,0BAAAA,kBAAkB;AAAA,UAChB;AAAA,UACA;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,SAAS;AAAA,YACT,MAAM;AAAA,UAAA;AAAA,UAER,EAAE,WAAW,SAAA;AAAA,QAAS;AAAA,MAE1B;AACA,UAAI,SAAS;AACX,gBAAQ,KAAK;AACb;AAAA,MACF;AACAW,0BAAAA,2BAA2B,aAAa,KAAK;AAAA,IAC/C;AAAA,EAAA,CACD;AACH;;;;;"}
1
+ {"version":3,"file":"useAthenaStatewireRuntime.cjs","sources":["../../src/runtime/useAthenaStatewireRuntime.ts"],"sourcesContent":["import type { AssistantClient, Toolkit } from '@assistant-ui/react';\nimport { Tools, useAui } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n type DeepAgentMessageWindowDeclaration,\n type DeepAgentStreamValues,\n type DeepAgentThreadElement,\n type DeepAgentThreadSnapshotCache,\n type DeepAgentToolOutputStore,\n isDeepAgentHostState,\n isStatewireChannelTeardown,\n logUnhandledStatewireError,\n type RegisteredClientTool,\n useDeepAgentRuntime,\n type UseDeepAgentThreadOptions,\n withStagedComposerMarkdown,\n} from '@athenaintel/agent-runtime';\nimport { useContext, useMemo, useRef } from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n collectStatewireClientTools,\n statewireClientToolWireEntries,\n} from '../chat/statewire-client-tools';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n} from './statewire-auth-headers';\nimport { statewireSessionKey } from './statewire-session-key';\n\ntype AthenaDiagnosticsSpanStarter = typeof athenaDiagnostics.span;\nimport {\n buildStatewireRunConfig,\n DEFAULT_STATEWIRE_MODEL,\n resolveAgentProp,\n} from './statewire-run-config';\n\nexport { DEFAULT_STATEWIRE_MODEL };\n\n/**\n * The head interest every SDK attach states (`athena/interest`): the host\n * mounts the transcript's tail as message documents instead of every message\n * — megabytes on long threads, over the 1 MiB frame limit of stock websocket\n * clients. Older pages load from the `StatewireHistoryEdge` row the chat UI\n * mounts above the messages, and cut tool outputs are fetched back for the\n * cards that show them.\n */\nexport const ATHENA_SDK_MESSAGE_WINDOW: DeepAgentMessageWindowDeclaration = {\n size: DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n};\n\nexport interface AthenaStatewireRuntimeConfig {\n /** Base URL of the statewire sync host (the `…/v2/threads` mount). */\n syncUrl: string;\n /** Thread ID the statewire session attaches to. */\n threadId: string;\n /** True only for a never-started local chat. Existing (or pre-assigned)\n * threads load until their server snapshot arrives. */\n isNewChat?: boolean;\n /** API key for authentication. Used when no auth token is provided. */\n apiKey?: string;\n /** Auth token. Takes priority over apiKey. */\n token?: string | null;\n /** Resolve the latest token immediately before a request (for expiring\n * clients). Called with `{ force: true }` right after the host refused the\n * previous credential (the transport's `staleAuth`), so a caching getter\n * mints a fresh token instead of returning the refused one. */\n getToken?: AthenaStatewireTokenGetter;\n /**\n * Per-provider LRU of each thread's last wire state. A switch back to a\n * remembered thread mounts with that snapshot and projects ready on the\n * first frame instead of a loading treatment; the attach snapshot then\n * replaces it. Owned above the per-thread mount so it survives switches.\n */\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /** The head interest stated on the attach (see {@link ATHENA_SDK_MESSAGE_WINDOW}, the default). */\n messageWindow?: DeepAgentMessageWindowDeclaration;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages;\n * the thread re-converts when one lands. `AthenaProvider` owns one per\n * provider and hands the same store to `StatewireHistoryEdge`.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /** LLM model identifier. Defaults to the deep-agent default model. */\n model?: string;\n /** Agent selector: only `collab_agent:<asset_id>` refs are accepted (the run\n * executes AS that published collab agent). Any other non-empty value\n * throws — see `resolveAgentProp`. */\n agent?: string;\n /** Collab-agent channel id selecting that channel's overrides. */\n channel?: string;\n /** The agent's mode picked for this thread (`AthenaProvider` `modes`). */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Frontend tools registered on the assistant client. Tools with a local\n * `execute` are also declared as `client_tools` and executed through the\n * statewire client-tool bridge. */\n frontendToolkit?: Toolkit;\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier for session filtering. Sent as app_id in runConfig.custom. */\n appId?: string;\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n /** Called on statewire transport errors (send rejections, stream failures). */\n onError?: (error: Error) => void;\n /** Projected connection status changes (deduped; null on unmount). */\n onConnectionChange?: UseDeepAgentThreadOptions['onConnectionChange'];\n /** Raw connection observer — carries the transport's reconnect handle. */\n onRawConnectionChange?: UseDeepAgentThreadOptions['onRawConnectionChange'];\n /** Notified when a run starts/stops. */\n onRunningChange?: UseDeepAgentThreadOptions['onRunningChange'];\n /**\n * Observes the host's own state: called with the attach snapshot and then on\n * every later frame (`isDeepAgentHostState`) — never with the placeholder or\n * remembered snapshot a thread renders before its snapshot applies, which\n * can be after the connection reports `connected`. Called per streamed\n * token, so keep it cheap.\n */\n onServerState?: (state: DeepAgentStreamValues) => void;\n}\n\nconst EMPTY_TOOLKIT: Toolkit = {};\nconst EMPTY_TOOL_IDS: string[] = [];\n\n/** The client tools derived from the runtime's frontend toolkit — exposed so\n * the provider can mount the client-tool bridge with the same set. */\nexport function useStatewireClientTools(\n frontendToolkit: Toolkit | undefined,\n): RegisteredClientTool[] {\n return useMemo(\n () => collectStatewireClientTools(frontendToolkit ?? EMPTY_TOOLKIT),\n [frontendToolkit],\n );\n}\n\n/**\n * Statewire runtime for the Athena SDK — a thin shim over the shared\n * `@athenaintel/agent-runtime` `useDeepAgentRuntime`: it attaches to the\n * agora-hosted deep-agent thread at `${syncUrl}/${threadId}` and returns the\n * `AssistantClient` to mount via `AuiProvider` — the same integration shape as\n * the Olympus, Chrome extension, Office, and mobile statewire hosts.\n *\n * Auth: a token (resolved live through `getToken` when provided) rides\n * `Authorization: Bearer`, and an API key falls back to `X-API-KEY` — both\n * accepted by the statewire host.\n */\nexport function useAthenaStatewireRuntime(\n config: AthenaStatewireRuntimeConfig,\n): AssistantClient {\n const {\n syncUrl,\n threadId,\n isNewChat = false,\n apiKey,\n token,\n getToken,\n model,\n agent,\n channel,\n mode,\n tools = EMPTY_TOOL_IDS,\n frontendToolIds = EMPTY_TOOL_IDS,\n frontendToolkit = EMPTY_TOOLKIT,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n snapshotCache,\n messageWindow = ATHENA_SDK_MESSAGE_WINDOW,\n toolOutputs,\n onError,\n onConnectionChange,\n onRawConnectionChange,\n onRunningChange,\n onServerState,\n } = config;\n\n // Fail loud at mount, not at first send: the statewire host always runs the\n // Athena deep agent, so an `agent` outside the collab_agent namespace can\n // never apply. Throwing here (render time) is what makes a misconfigured\n // integration visible instead of quietly answering as the wrong agent.\n resolveAgentProp({ agent });\n\n const clientTools = useStatewireClientTools(frontendToolkit);\n\n // The persisted command lane is keyed by WHO is signed in as well as by host\n // and thread (see `statewireSessionKey`).\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n\n // Attach → first server snapshot span, keyed per thread attach.\n const attachSpanRef = useRef<ReturnType<AthenaDiagnosticsSpanStarter> | null>(null);\n const attachThreadRef = useRef<string | null>(null);\n if (attachThreadRef.current !== threadId) {\n attachThreadRef.current = threadId;\n attachSpanRef.current = athenaDiagnostics.span(\n `statewire.attach:${threadId}`,\n 'statewire.attach.start',\n { thread_id: threadId, is_new_chat: isNewChat },\n );\n }\n\n // Built every render so its JSON signature can key the model-context\n // registration below: consumers commonly pass fresh array literals per\n // render, which must not thrash registration, while a changed value (a new\n // `model`, channel, tool list, system prompt, …) must re-sync the context\n // before the next send. The core pulls this value only on model-context\n // notifications, which a key change triggers.\n const runConfigValue = buildStatewireRunConfig({\n model,\n agent,\n channel,\n mode,\n tools,\n frontendToolIds,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n clientTools: statewireClientToolWireEntries(clientTools),\n });\n const runConfigKey = JSON.stringify(runConfigValue);\n\n const auiTools = useMemo(() => Tools({ toolkit: frontendToolkit }), [frontendToolkit]);\n // Web client for the shared runtime core (a `use*` name so the\n // rules-of-hooks lint sees the `useAui` call inside a custom hook; the core\n // invokes it unconditionally every render).\n const useSdkClient = (thread: DeepAgentThreadElement) => useAui({ thread, tools: auiTools });\n\n return useDeepAgentRuntime({\n surface: 'AthenaSDK',\n threadId,\n baseUrl: syncUrl,\n // Option closures are re-read per render by the core, so credential\n // rotation (new `token`/`getToken`/`apiKey` props) reaches the next\n // request without remounting the thread. The transport's `staleAuth`\n // (true right after a refused credential) is forwarded as `force`.\n headers: (ctx) => resolveStatewireAuthHeaders({ apiKey, token, getToken }, ctx),\n isNewChat,\n // Queued entries render in the SDK's queue panel, so only the steer lane\n // projects into the message tail.\n laneProjection: 'steer-only',\n // Durable session: a reload resumes the lane and resends whatever the\n // server has not marked durable. Scoped by host AND principal — a thread\n // id is only meaningful against the backend that minted it, and a stored\n // lane must never resend against another signed-in user. (Consumers\n // server-render this SDK; the core's availability probe keeps those\n // sessions process-local, as before the seam existed.)\n session: {\n kind: 'local-storage',\n key: (id) => statewireSessionKey({ syncUrl, principal, threadId: id }),\n },\n snapshotCache,\n // Head interest (see `ATHENA_SDK_MESSAGE_WINDOW`): the tail of the\n // transcript instead of every message; the chat UI's history edge pages\n // older ranges in and fetches cut tool outputs into `toolOutputs`.\n messageWindow,\n toolOutputs,\n capabilities: { edit: true, reload: true, continue: true },\n // The composer stages the draft each send came from right before it\n // sends; the message carries it as its composer markdown, so its bubble\n // shows the text exactly as typed (`UserMessageText`).\n getMessageMetadata: withStagedComposerMarkdown({ threadId }),\n useClient: useSdkClient,\n onStateChange: (state) => {\n // Statewire publishes what the thread renders before its attach snapshot\n // through here too (the placeholder, a remembered snapshot, and a replay\n // boundary's pre-stream default), and on a thread mid-run it reports\n // `connected` before the snapshot applies: only a state the host sent\n // counts, whatever the connection says.\n if (!isDeepAgentHostState(state)) return;\n // The attach's first one ends the attach span (time-to-history).\n const endSpan = attachSpanRef.current;\n if (endSpan) {\n attachSpanRef.current = null;\n endSpan('statewire.snapshot', { data: { thread_id: threadId } });\n }\n onServerState?.(state);\n },\n onConnectionChange: (connection) => {\n athenaDiagnostics.gauge('statewire.connection', connection?.status ?? null);\n if (connection?.status === 'stopped') {\n athenaDiagnostics.emit('statewire.connection', {\n level: 'warn',\n data: {\n status: connection.status,\n reason: connection.reason,\n message: connection.message,\n },\n });\n }\n onConnectionChange?.(connection);\n },\n onRawConnectionChange,\n onRunningChange,\n // Each model-context notification pulls the latest getter through the\n // core's per-render ref; the value-stable key re-registers (and so\n // notifies) only when the built runConfig actually changed. On a thread\n // with a server config lane, agora keeps only the per-send keys of what\n // this carries; `StatewireThreadConfigLaneSync` writes a changed model,\n // system prompt, tool list, workbench or knowledge base to the lane itself.\n runConfig: {\n get: () => runConfigValue,\n key: runConfigKey,\n },\n onError: (error) => {\n if (!isStatewireChannelTeardown(error)) {\n athenaDiagnostics.error(\n 'statewire.error',\n error,\n {\n code: 'statewire_rejected',\n message: 'The statewire host rejected a command',\n hint: 'Inspect error.detail — agora rejections carry a structured {code, message}.',\n },\n { thread_id: threadId },\n );\n }\n if (onError) {\n onError(error);\n return;\n }\n logUnhandledStatewireError('AthenaSDK', error);\n },\n });\n}\n"],"names":["DEEP_AGENT_DEFAULT_MESSAGE_WINDOW","useMemo","collectStatewireClientTools","resolveAgentProp","useContext","AthenaAuthContext","useRef","athenaDiagnostics","buildStatewireRunConfig","statewireClientToolWireEntries","Tools","useAui","useDeepAgentRuntime","resolveStatewireAuthHeaders","statewireSessionKey","withStagedComposerMarkdown","isDeepAgentHostState","isStatewireChannelTeardown","logUnhandledStatewireError"],"mappings":"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"}
@@ -81,10 +81,11 @@ export interface AthenaStatewireRuntimeConfig {
81
81
  /** Notified when a run starts/stops. */
82
82
  onRunningChange?: UseDeepAgentThreadOptions['onRunningChange'];
83
83
  /**
84
- * Observes the host's own state: called once the attach's hello has landed
85
- * (with the snapshot it carried) and then on every later frame — never with
86
- * the placeholder or remembered snapshot a thread renders before its hello.
87
- * Called per streamed token, so keep it cheap.
84
+ * Observes the host's own state: called with the attach snapshot and then on
85
+ * every later frame (`isDeepAgentHostState`) — never with the placeholder or
86
+ * remembered snapshot a thread renders before its snapshot applies, which
87
+ * can be after the connection reports `connected`. Called per streamed
88
+ * token, so keep it cheap.
88
89
  */
89
90
  onServerState?: (state: DeepAgentStreamValues) => void;
90
91
  }
@@ -10,7 +10,7 @@ import { DEFAULT_STATEWIRE_MODEL } from "./statewire-run-config.js";
10
10
  import { DEEP_AGENT_DEFAULT_MESSAGE_WINDOW } from "../agent-runtime/src/generated/runconfig-contract.js";
11
11
  import { useDeepAgentRuntime, logUnhandledStatewireError } from "../agent-runtime/src/use-deep-agent-runtime.js";
12
12
  import { isStatewireChannelTeardown } from "../agent-runtime/src/errors.js";
13
- import { isDeepAgentStateHydrated } from "../agent-runtime/src/statewire-state.js";
13
+ import { isDeepAgentHostState } from "../agent-runtime/src/protocol.js";
14
14
  import { withStagedComposerMarkdown } from "../agent-runtime/src/composer-markdown.js";
15
15
  const ATHENA_SDK_MESSAGE_WINDOW = {
16
16
  size: DEEP_AGENT_DEFAULT_MESSAGE_WINDOW
@@ -82,8 +82,6 @@ function useAthenaStatewireRuntime(config) {
82
82
  clientTools: statewireClientToolWireEntries(clientTools)
83
83
  });
84
84
  const runConfigKey = JSON.stringify(runConfigValue);
85
- const lastStateRef = useRef(null);
86
- const helloLandedRef = useRef(false);
87
85
  const auiTools = useMemo(() => Tools({ toolkit: frontendToolkit }), [frontendToolkit]);
88
86
  const useSdkClient = (thread) => useAui({ thread, tools: auiTools });
89
87
  return useDeepAgentRuntime({
@@ -122,14 +120,13 @@ function useAthenaStatewireRuntime(config) {
122
120
  getMessageMetadata: withStagedComposerMarkdown({ threadId }),
123
121
  useClient: useSdkClient,
124
122
  onStateChange: (state) => {
123
+ if (!isDeepAgentHostState(state)) return;
125
124
  const endSpan = attachSpanRef.current;
126
125
  if (endSpan) {
127
126
  attachSpanRef.current = null;
128
127
  endSpan("statewire.snapshot", { data: { thread_id: threadId } });
129
128
  }
130
- if (!isDeepAgentStateHydrated(state)) return;
131
- lastStateRef.current = state;
132
- if (helloLandedRef.current) onServerState == null ? void 0 : onServerState(state);
129
+ onServerState == null ? void 0 : onServerState(state);
133
130
  },
134
131
  onConnectionChange: (connection) => {
135
132
  athenaDiagnostics.gauge("statewire.connection", (connection == null ? void 0 : connection.status) ?? null);
@@ -145,14 +142,7 @@ function useAthenaStatewireRuntime(config) {
145
142
  }
146
143
  onConnectionChange == null ? void 0 : onConnectionChange(connection);
147
144
  },
148
- onRawConnectionChange: (connection) => {
149
- if (connection.status === "connected" && !helloLandedRef.current) {
150
- helloLandedRef.current = true;
151
- const helloState = lastStateRef.current;
152
- if (helloState) onServerState == null ? void 0 : onServerState(helloState);
153
- }
154
- onRawConnectionChange == null ? void 0 : onRawConnectionChange(connection);
155
- },
145
+ onRawConnectionChange,
156
146
  onRunningChange,
157
147
  // Each model-context notification pulls the latest getter through the
158
148
  // core's per-render ref; the value-stable key re-registers (and so
@@ -1 +1 @@
1
- {"version":3,"file":"useAthenaStatewireRuntime.js","sources":["../../src/runtime/useAthenaStatewireRuntime.ts"],"sourcesContent":["import type { AssistantClient, Toolkit } from '@assistant-ui/react';\nimport { Tools, useAui } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n type DeepAgentMessageWindowDeclaration,\n type DeepAgentStreamValues,\n type DeepAgentThreadElement,\n type DeepAgentThreadSnapshotCache,\n type DeepAgentToolOutputStore,\n isDeepAgentStateHydrated,\n isStatewireChannelTeardown,\n logUnhandledStatewireError,\n type RegisteredClientTool,\n useDeepAgentRuntime,\n type UseDeepAgentThreadOptions,\n withStagedComposerMarkdown,\n} from '@athenaintel/agent-runtime';\nimport { useContext, useMemo, useRef } from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n collectStatewireClientTools,\n statewireClientToolWireEntries,\n} from '../chat/statewire-client-tools';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n} from './statewire-auth-headers';\nimport { statewireSessionKey } from './statewire-session-key';\n\ntype AthenaDiagnosticsSpanStarter = typeof athenaDiagnostics.span;\nimport {\n buildStatewireRunConfig,\n DEFAULT_STATEWIRE_MODEL,\n resolveAgentProp,\n} from './statewire-run-config';\n\nexport { DEFAULT_STATEWIRE_MODEL };\n\n/**\n * The head interest every SDK attach states (`athena/interest`): the host\n * mounts the transcript's tail as message documents instead of every message\n * — megabytes on long threads, over the 1 MiB frame limit of stock websocket\n * clients. Older pages load from the `StatewireHistoryEdge` row the chat UI\n * mounts above the messages, and cut tool outputs are fetched back for the\n * cards that show them.\n */\nexport const ATHENA_SDK_MESSAGE_WINDOW: DeepAgentMessageWindowDeclaration = {\n size: DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n};\n\nexport interface AthenaStatewireRuntimeConfig {\n /** Base URL of the statewire sync host (the `…/v2/threads` mount). */\n syncUrl: string;\n /** Thread ID the statewire session attaches to. */\n threadId: string;\n /** True only for a never-started local chat. Existing (or pre-assigned)\n * threads load until their server snapshot arrives. */\n isNewChat?: boolean;\n /** API key for authentication. Used when no auth token is provided. */\n apiKey?: string;\n /** Auth token. Takes priority over apiKey. */\n token?: string | null;\n /** Resolve the latest token immediately before a request (for expiring\n * clients). Called with `{ force: true }` right after the host refused the\n * previous credential (the transport's `staleAuth`), so a caching getter\n * mints a fresh token instead of returning the refused one. */\n getToken?: AthenaStatewireTokenGetter;\n /**\n * Per-provider LRU of each thread's last wire state. A switch back to a\n * remembered thread mounts with that snapshot and projects ready on the\n * first frame instead of a loading treatment; the attach snapshot then\n * replaces it. Owned above the per-thread mount so it survives switches.\n */\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /** The head interest stated on the attach (see {@link ATHENA_SDK_MESSAGE_WINDOW}, the default). */\n messageWindow?: DeepAgentMessageWindowDeclaration;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages;\n * the thread re-converts when one lands. `AthenaProvider` owns one per\n * provider and hands the same store to `StatewireHistoryEdge`.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /** LLM model identifier. Defaults to the deep-agent default model. */\n model?: string;\n /** Agent selector: only `collab_agent:<asset_id>` refs are accepted (the run\n * executes AS that published collab agent). Any other non-empty value\n * throws — see `resolveAgentProp`. */\n agent?: string;\n /** Collab-agent channel id selecting that channel's overrides. */\n channel?: string;\n /** The agent's mode picked for this thread (`AthenaProvider` `modes`). */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Frontend tools registered on the assistant client. Tools with a local\n * `execute` are also declared as `client_tools` and executed through the\n * statewire client-tool bridge. */\n frontendToolkit?: Toolkit;\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier for session filtering. Sent as app_id in runConfig.custom. */\n appId?: string;\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n /** Called on statewire transport errors (send rejections, stream failures). */\n onError?: (error: Error) => void;\n /** Projected connection status changes (deduped; null on unmount). */\n onConnectionChange?: UseDeepAgentThreadOptions['onConnectionChange'];\n /** Raw connection observer — carries the transport's reconnect handle. */\n onRawConnectionChange?: UseDeepAgentThreadOptions['onRawConnectionChange'];\n /** Notified when a run starts/stops. */\n onRunningChange?: UseDeepAgentThreadOptions['onRunningChange'];\n /**\n * Observes the host's own state: called once the attach's hello has landed\n * (with the snapshot it carried) and then on every later frame — never with\n * the placeholder or remembered snapshot a thread renders before its hello.\n * Called per streamed token, so keep it cheap.\n */\n onServerState?: (state: DeepAgentStreamValues) => void;\n}\n\nconst EMPTY_TOOLKIT: Toolkit = {};\nconst EMPTY_TOOL_IDS: string[] = [];\n\n/** The client tools derived from the runtime's frontend toolkit — exposed so\n * the provider can mount the client-tool bridge with the same set. */\nexport function useStatewireClientTools(\n frontendToolkit: Toolkit | undefined,\n): RegisteredClientTool[] {\n return useMemo(\n () => collectStatewireClientTools(frontendToolkit ?? EMPTY_TOOLKIT),\n [frontendToolkit],\n );\n}\n\n/**\n * Statewire runtime for the Athena SDK — a thin shim over the shared\n * `@athenaintel/agent-runtime` `useDeepAgentRuntime`: it attaches to the\n * agora-hosted deep-agent thread at `${syncUrl}/${threadId}` and returns the\n * `AssistantClient` to mount via `AuiProvider` — the same integration shape as\n * the Olympus, Chrome extension, Office, and mobile statewire hosts.\n *\n * Auth: a token (resolved live through `getToken` when provided) rides\n * `Authorization: Bearer`, and an API key falls back to `X-API-KEY` — both\n * accepted by the statewire host.\n */\nexport function useAthenaStatewireRuntime(\n config: AthenaStatewireRuntimeConfig,\n): AssistantClient {\n const {\n syncUrl,\n threadId,\n isNewChat = false,\n apiKey,\n token,\n getToken,\n model,\n agent,\n channel,\n mode,\n tools = EMPTY_TOOL_IDS,\n frontendToolIds = EMPTY_TOOL_IDS,\n frontendToolkit = EMPTY_TOOLKIT,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n snapshotCache,\n messageWindow = ATHENA_SDK_MESSAGE_WINDOW,\n toolOutputs,\n onError,\n onConnectionChange,\n onRawConnectionChange,\n onRunningChange,\n onServerState,\n } = config;\n\n // Fail loud at mount, not at first send: the statewire host always runs the\n // Athena deep agent, so an `agent` outside the collab_agent namespace can\n // never apply. Throwing here (render time) is what makes a misconfigured\n // integration visible instead of quietly answering as the wrong agent.\n resolveAgentProp({ agent });\n\n const clientTools = useStatewireClientTools(frontendToolkit);\n\n // The persisted command lane is keyed by WHO is signed in as well as by host\n // and thread (see `statewireSessionKey`).\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n\n // Attach → first server snapshot span, keyed per thread attach.\n const attachSpanRef = useRef<ReturnType<AthenaDiagnosticsSpanStarter> | null>(null);\n const attachThreadRef = useRef<string | null>(null);\n if (attachThreadRef.current !== threadId) {\n attachThreadRef.current = threadId;\n attachSpanRef.current = athenaDiagnostics.span(\n `statewire.attach:${threadId}`,\n 'statewire.attach.start',\n { thread_id: threadId, is_new_chat: isNewChat },\n );\n }\n\n // Built every render so its JSON signature can key the model-context\n // registration below: consumers commonly pass fresh array literals per\n // render, which must not thrash registration, while a changed value (a new\n // `model`, channel, tool list, system prompt, …) must re-sync the context\n // before the next send. The core pulls this value only on model-context\n // notifications, which a key change triggers.\n const runConfigValue = buildStatewireRunConfig({\n model,\n agent,\n channel,\n mode,\n tools,\n frontendToolIds,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n clientTools: statewireClientToolWireEntries(clientTools),\n });\n const runConfigKey = JSON.stringify(runConfigValue);\n\n // The host's state, told apart from what the thread renders before its\n // hello: statewire publishes the initial (placeholder or remembered) state\n // through `onStateChange` too, and reports `connected` in the same flush\n // right after publishing the state the hello carried. So the latest state\n // seen when the connection first turns `connected` is the hello's, and every\n // state after it is the host's. Refs are per attach: the runtime is mounted\n // per thread.\n const lastStateRef = useRef<DeepAgentStreamValues | null>(null);\n const helloLandedRef = useRef(false);\n\n const auiTools = useMemo(() => Tools({ toolkit: frontendToolkit }), [frontendToolkit]);\n // Web client for the shared runtime core (a `use*` name so the\n // rules-of-hooks lint sees the `useAui` call inside a custom hook; the core\n // invokes it unconditionally every render).\n const useSdkClient = (thread: DeepAgentThreadElement) => useAui({ thread, tools: auiTools });\n\n return useDeepAgentRuntime({\n surface: 'AthenaSDK',\n threadId,\n baseUrl: syncUrl,\n // Option closures are re-read per render by the core, so credential\n // rotation (new `token`/`getToken`/`apiKey` props) reaches the next\n // request without remounting the thread. The transport's `staleAuth`\n // (true right after a refused credential) is forwarded as `force`.\n headers: (ctx) => resolveStatewireAuthHeaders({ apiKey, token, getToken }, ctx),\n isNewChat,\n // Queued entries render in the SDK's queue panel, so only the steer lane\n // projects into the message tail.\n laneProjection: 'steer-only',\n // Durable session: a reload resumes the lane and resends whatever the\n // server has not marked durable. Scoped by host AND principal — a thread\n // id is only meaningful against the backend that minted it, and a stored\n // lane must never resend against another signed-in user. (Consumers\n // server-render this SDK; the core's availability probe keeps those\n // sessions process-local, as before the seam existed.)\n session: {\n kind: 'local-storage',\n key: (id) => statewireSessionKey({ syncUrl, principal, threadId: id }),\n },\n snapshotCache,\n // Head interest (see `ATHENA_SDK_MESSAGE_WINDOW`): the tail of the\n // transcript instead of every message; the chat UI's history edge pages\n // older ranges in and fetches cut tool outputs into `toolOutputs`.\n messageWindow,\n toolOutputs,\n capabilities: { edit: true, reload: true, continue: true },\n // The composer stages the draft each send came from right before it\n // sends; the message carries it as its composer markdown, so its bubble\n // shows the text exactly as typed (`UserMessageText`).\n getMessageMetadata: withStagedComposerMarkdown({ threadId }),\n useClient: useSdkClient,\n onStateChange: (state) => {\n // First snapshot for this attach ends the attach span (time-to-history).\n const endSpan = attachSpanRef.current;\n if (endSpan) {\n attachSpanRef.current = null;\n endSpan('statewire.snapshot', { data: { thread_id: threadId } });\n }\n // A replay boundary republishes the pre-stream default: not host state.\n if (!isDeepAgentStateHydrated(state)) return;\n lastStateRef.current = state;\n if (helloLandedRef.current) onServerState?.(state);\n },\n onConnectionChange: (connection) => {\n athenaDiagnostics.gauge('statewire.connection', connection?.status ?? null);\n if (connection?.status === 'stopped') {\n athenaDiagnostics.emit('statewire.connection', {\n level: 'warn',\n data: {\n status: connection.status,\n reason: connection.reason,\n message: connection.message,\n },\n });\n }\n onConnectionChange?.(connection);\n },\n onRawConnectionChange: (connection) => {\n if (connection.status === 'connected' && !helloLandedRef.current) {\n helloLandedRef.current = true;\n const helloState = lastStateRef.current;\n if (helloState) onServerState?.(helloState);\n }\n onRawConnectionChange?.(connection);\n },\n onRunningChange,\n // Each model-context notification pulls the latest getter through the\n // core's per-render ref; the value-stable key re-registers (and so\n // notifies) only when the built runConfig actually changed. On a thread\n // with a server config lane, agora keeps only the per-send keys of what\n // this carries; `StatewireThreadConfigLaneSync` writes a changed model,\n // system prompt, tool list, workbench or knowledge base to the lane itself.\n runConfig: {\n get: () => runConfigValue,\n key: runConfigKey,\n },\n onError: (error) => {\n if (!isStatewireChannelTeardown(error)) {\n athenaDiagnostics.error(\n 'statewire.error',\n error,\n {\n code: 'statewire_rejected',\n message: 'The statewire host rejected a command',\n hint: 'Inspect error.detail — agora rejections carry a structured {code, message}.',\n },\n { thread_id: threadId },\n );\n }\n if (onError) {\n onError(error);\n return;\n }\n logUnhandledStatewireError('AthenaSDK', error);\n },\n });\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useAthenaStatewireRuntime.js","sources":["../../src/runtime/useAthenaStatewireRuntime.ts"],"sourcesContent":["import type { AssistantClient, Toolkit } from '@assistant-ui/react';\nimport { Tools, useAui } from '@assistant-ui/react';\nimport {\n DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n type DeepAgentMessageWindowDeclaration,\n type DeepAgentStreamValues,\n type DeepAgentThreadElement,\n type DeepAgentThreadSnapshotCache,\n type DeepAgentToolOutputStore,\n isDeepAgentHostState,\n isStatewireChannelTeardown,\n logUnhandledStatewireError,\n type RegisteredClientTool,\n useDeepAgentRuntime,\n type UseDeepAgentThreadOptions,\n withStagedComposerMarkdown,\n} from '@athenaintel/agent-runtime';\nimport { useContext, useMemo, useRef } from 'react';\nimport { AthenaAuthContext } from '../auth/AthenaAuthContext';\nimport {\n collectStatewireClientTools,\n statewireClientToolWireEntries,\n} from '../chat/statewire-client-tools';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport {\n type AthenaStatewireTokenGetter,\n resolveStatewireAuthHeaders,\n} from './statewire-auth-headers';\nimport { statewireSessionKey } from './statewire-session-key';\n\ntype AthenaDiagnosticsSpanStarter = typeof athenaDiagnostics.span;\nimport {\n buildStatewireRunConfig,\n DEFAULT_STATEWIRE_MODEL,\n resolveAgentProp,\n} from './statewire-run-config';\n\nexport { DEFAULT_STATEWIRE_MODEL };\n\n/**\n * The head interest every SDK attach states (`athena/interest`): the host\n * mounts the transcript's tail as message documents instead of every message\n * — megabytes on long threads, over the 1 MiB frame limit of stock websocket\n * clients. Older pages load from the `StatewireHistoryEdge` row the chat UI\n * mounts above the messages, and cut tool outputs are fetched back for the\n * cards that show them.\n */\nexport const ATHENA_SDK_MESSAGE_WINDOW: DeepAgentMessageWindowDeclaration = {\n size: DEEP_AGENT_DEFAULT_MESSAGE_WINDOW,\n};\n\nexport interface AthenaStatewireRuntimeConfig {\n /** Base URL of the statewire sync host (the `…/v2/threads` mount). */\n syncUrl: string;\n /** Thread ID the statewire session attaches to. */\n threadId: string;\n /** True only for a never-started local chat. Existing (or pre-assigned)\n * threads load until their server snapshot arrives. */\n isNewChat?: boolean;\n /** API key for authentication. Used when no auth token is provided. */\n apiKey?: string;\n /** Auth token. Takes priority over apiKey. */\n token?: string | null;\n /** Resolve the latest token immediately before a request (for expiring\n * clients). Called with `{ force: true }` right after the host refused the\n * previous credential (the transport's `staleAuth`), so a caching getter\n * mints a fresh token instead of returning the refused one. */\n getToken?: AthenaStatewireTokenGetter;\n /**\n * Per-provider LRU of each thread's last wire state. A switch back to a\n * remembered thread mounts with that snapshot and projects ready on the\n * first frame instead of a loading treatment; the attach snapshot then\n * replaces it. Owned above the per-thread mount so it survives switches.\n */\n snapshotCache?: DeepAgentThreadSnapshotCache;\n /** The head interest stated on the attach (see {@link ATHENA_SDK_MESSAGE_WINDOW}, the default). */\n messageWindow?: DeepAgentMessageWindowDeclaration;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages;\n * the thread re-converts when one lands. `AthenaProvider` owns one per\n * provider and hands the same store to `StatewireHistoryEdge`.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /** LLM model identifier. Defaults to the deep-agent default model. */\n model?: string;\n /** Agent selector: only `collab_agent:<asset_id>` refs are accepted (the run\n * executes AS that published collab agent). Any other non-empty value\n * throws — see `resolveAgentProp`. */\n agent?: string;\n /** Collab-agent channel id selecting that channel's overrides. */\n channel?: string;\n /** The agent's mode picked for this thread (`AthenaProvider` `modes`). */\n mode?: string;\n /** Backend toolkit IDs to enable. */\n tools?: string[];\n /** Frontend tool IDs to include in enabled_tools. */\n frontendToolIds?: string[];\n /** Frontend tools registered on the assistant client. Tools with a local\n * `execute` are also declared as `client_tools` and executed through the\n * statewire client-tool bridge. */\n frontendToolkit?: Toolkit;\n /** Asset IDs to include in the workbench. */\n workbench?: string[];\n /** Knowledge base IDs for RAG context. */\n knowledgeBase?: string[];\n /** System prompt override. */\n systemPrompt?: string;\n /** Client app identifier for session filtering. Sent as app_id in runConfig.custom. */\n appId?: string;\n /** Additional fields merged into runConfig.custom. */\n extraRunConfig?: Record<string, unknown>;\n /** Called on statewire transport errors (send rejections, stream failures). */\n onError?: (error: Error) => void;\n /** Projected connection status changes (deduped; null on unmount). */\n onConnectionChange?: UseDeepAgentThreadOptions['onConnectionChange'];\n /** Raw connection observer — carries the transport's reconnect handle. */\n onRawConnectionChange?: UseDeepAgentThreadOptions['onRawConnectionChange'];\n /** Notified when a run starts/stops. */\n onRunningChange?: UseDeepAgentThreadOptions['onRunningChange'];\n /**\n * Observes the host's own state: called with the attach snapshot and then on\n * every later frame (`isDeepAgentHostState`) — never with the placeholder or\n * remembered snapshot a thread renders before its snapshot applies, which\n * can be after the connection reports `connected`. Called per streamed\n * token, so keep it cheap.\n */\n onServerState?: (state: DeepAgentStreamValues) => void;\n}\n\nconst EMPTY_TOOLKIT: Toolkit = {};\nconst EMPTY_TOOL_IDS: string[] = [];\n\n/** The client tools derived from the runtime's frontend toolkit — exposed so\n * the provider can mount the client-tool bridge with the same set. */\nexport function useStatewireClientTools(\n frontendToolkit: Toolkit | undefined,\n): RegisteredClientTool[] {\n return useMemo(\n () => collectStatewireClientTools(frontendToolkit ?? EMPTY_TOOLKIT),\n [frontendToolkit],\n );\n}\n\n/**\n * Statewire runtime for the Athena SDK — a thin shim over the shared\n * `@athenaintel/agent-runtime` `useDeepAgentRuntime`: it attaches to the\n * agora-hosted deep-agent thread at `${syncUrl}/${threadId}` and returns the\n * `AssistantClient` to mount via `AuiProvider` — the same integration shape as\n * the Olympus, Chrome extension, Office, and mobile statewire hosts.\n *\n * Auth: a token (resolved live through `getToken` when provided) rides\n * `Authorization: Bearer`, and an API key falls back to `X-API-KEY` — both\n * accepted by the statewire host.\n */\nexport function useAthenaStatewireRuntime(\n config: AthenaStatewireRuntimeConfig,\n): AssistantClient {\n const {\n syncUrl,\n threadId,\n isNewChat = false,\n apiKey,\n token,\n getToken,\n model,\n agent,\n channel,\n mode,\n tools = EMPTY_TOOL_IDS,\n frontendToolIds = EMPTY_TOOL_IDS,\n frontendToolkit = EMPTY_TOOLKIT,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n snapshotCache,\n messageWindow = ATHENA_SDK_MESSAGE_WINDOW,\n toolOutputs,\n onError,\n onConnectionChange,\n onRawConnectionChange,\n onRunningChange,\n onServerState,\n } = config;\n\n // Fail loud at mount, not at first send: the statewire host always runs the\n // Athena deep agent, so an `agent` outside the collab_agent namespace can\n // never apply. Throwing here (render time) is what makes a misconfigured\n // integration visible instead of quietly answering as the wrong agent.\n resolveAgentProp({ agent });\n\n const clientTools = useStatewireClientTools(frontendToolkit);\n\n // The persisted command lane is keyed by WHO is signed in as well as by host\n // and thread (see `statewireSessionKey`).\n const authContext = useContext(AthenaAuthContext);\n const principal = authContext?.user?.userId ?? '';\n\n // Attach → first server snapshot span, keyed per thread attach.\n const attachSpanRef = useRef<ReturnType<AthenaDiagnosticsSpanStarter> | null>(null);\n const attachThreadRef = useRef<string | null>(null);\n if (attachThreadRef.current !== threadId) {\n attachThreadRef.current = threadId;\n attachSpanRef.current = athenaDiagnostics.span(\n `statewire.attach:${threadId}`,\n 'statewire.attach.start',\n { thread_id: threadId, is_new_chat: isNewChat },\n );\n }\n\n // Built every render so its JSON signature can key the model-context\n // registration below: consumers commonly pass fresh array literals per\n // render, which must not thrash registration, while a changed value (a new\n // `model`, channel, tool list, system prompt, …) must re-sync the context\n // before the next send. The core pulls this value only on model-context\n // notifications, which a key change triggers.\n const runConfigValue = buildStatewireRunConfig({\n model,\n agent,\n channel,\n mode,\n tools,\n frontendToolIds,\n workbench,\n knowledgeBase,\n systemPrompt,\n appId,\n extraRunConfig,\n clientTools: statewireClientToolWireEntries(clientTools),\n });\n const runConfigKey = JSON.stringify(runConfigValue);\n\n const auiTools = useMemo(() => Tools({ toolkit: frontendToolkit }), [frontendToolkit]);\n // Web client for the shared runtime core (a `use*` name so the\n // rules-of-hooks lint sees the `useAui` call inside a custom hook; the core\n // invokes it unconditionally every render).\n const useSdkClient = (thread: DeepAgentThreadElement) => useAui({ thread, tools: auiTools });\n\n return useDeepAgentRuntime({\n surface: 'AthenaSDK',\n threadId,\n baseUrl: syncUrl,\n // Option closures are re-read per render by the core, so credential\n // rotation (new `token`/`getToken`/`apiKey` props) reaches the next\n // request without remounting the thread. The transport's `staleAuth`\n // (true right after a refused credential) is forwarded as `force`.\n headers: (ctx) => resolveStatewireAuthHeaders({ apiKey, token, getToken }, ctx),\n isNewChat,\n // Queued entries render in the SDK's queue panel, so only the steer lane\n // projects into the message tail.\n laneProjection: 'steer-only',\n // Durable session: a reload resumes the lane and resends whatever the\n // server has not marked durable. Scoped by host AND principal — a thread\n // id is only meaningful against the backend that minted it, and a stored\n // lane must never resend against another signed-in user. (Consumers\n // server-render this SDK; the core's availability probe keeps those\n // sessions process-local, as before the seam existed.)\n session: {\n kind: 'local-storage',\n key: (id) => statewireSessionKey({ syncUrl, principal, threadId: id }),\n },\n snapshotCache,\n // Head interest (see `ATHENA_SDK_MESSAGE_WINDOW`): the tail of the\n // transcript instead of every message; the chat UI's history edge pages\n // older ranges in and fetches cut tool outputs into `toolOutputs`.\n messageWindow,\n toolOutputs,\n capabilities: { edit: true, reload: true, continue: true },\n // The composer stages the draft each send came from right before it\n // sends; the message carries it as its composer markdown, so its bubble\n // shows the text exactly as typed (`UserMessageText`).\n getMessageMetadata: withStagedComposerMarkdown({ threadId }),\n useClient: useSdkClient,\n onStateChange: (state) => {\n // Statewire publishes what the thread renders before its attach snapshot\n // through here too (the placeholder, a remembered snapshot, and a replay\n // boundary's pre-stream default), and on a thread mid-run it reports\n // `connected` before the snapshot applies: only a state the host sent\n // counts, whatever the connection says.\n if (!isDeepAgentHostState(state)) return;\n // The attach's first one ends the attach span (time-to-history).\n const endSpan = attachSpanRef.current;\n if (endSpan) {\n attachSpanRef.current = null;\n endSpan('statewire.snapshot', { data: { thread_id: threadId } });\n }\n onServerState?.(state);\n },\n onConnectionChange: (connection) => {\n athenaDiagnostics.gauge('statewire.connection', connection?.status ?? null);\n if (connection?.status === 'stopped') {\n athenaDiagnostics.emit('statewire.connection', {\n level: 'warn',\n data: {\n status: connection.status,\n reason: connection.reason,\n message: connection.message,\n },\n });\n }\n onConnectionChange?.(connection);\n },\n onRawConnectionChange,\n onRunningChange,\n // Each model-context notification pulls the latest getter through the\n // core's per-render ref; the value-stable key re-registers (and so\n // notifies) only when the built runConfig actually changed. On a thread\n // with a server config lane, agora keeps only the per-send keys of what\n // this carries; `StatewireThreadConfigLaneSync` writes a changed model,\n // system prompt, tool list, workbench or knowledge base to the lane itself.\n runConfig: {\n get: () => runConfigValue,\n key: runConfigKey,\n },\n onError: (error) => {\n if (!isStatewireChannelTeardown(error)) {\n athenaDiagnostics.error(\n 'statewire.error',\n error,\n {\n code: 'statewire_rejected',\n message: 'The statewire host rejected a command',\n hint: 'Inspect error.detail — agora rejections carry a structured {code, message}.',\n },\n { thread_id: threadId },\n );\n }\n if (onError) {\n onError(error);\n return;\n }\n logUnhandledStatewireError('AthenaSDK', error);\n },\n });\n}\n"],"names":[],"mappings":";;;;;;;;;;;;;;AA+CO,MAAM,4BAA+D;AAAA,EAC1E,MAAM;AACR;AAgFA,MAAM,gBAAyB,CAAA;AAC/B,MAAM,iBAA2B,CAAA;AAI1B,SAAS,wBACd,iBACwB;AACxB,SAAO;AAAA,IACL,MAAM,4BAA4B,mBAAmB,aAAa;AAAA,IAClE,CAAC,eAAe;AAAA,EAAA;AAEpB;AAaO,SAAS,0BACd,QACiB;;AACjB,QAAM;AAAA,IACJ;AAAA,IACA;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ;AAAA,IACR,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EAAA,IACE;AAMJ,mBAAiB,EAAE,OAAO;AAE1B,QAAM,cAAc,wBAAwB,eAAe;AAI3D,QAAM,cAAc,WAAW,iBAAiB;AAChD,QAAM,cAAY,gDAAa,SAAb,mBAAmB,WAAU;AAG/C,QAAM,gBAAgB,OAAwD,IAAI;AAClF,QAAM,kBAAkB,OAAsB,IAAI;AAClD,MAAI,gBAAgB,YAAY,UAAU;AACxC,oBAAgB,UAAU;AAC1B,kBAAc,UAAU,kBAAkB;AAAA,MACxC,oBAAoB,QAAQ;AAAA,MAC5B;AAAA,MACA,EAAE,WAAW,UAAU,aAAa,UAAA;AAAA,IAAU;AAAA,EAElD;AAQA,QAAM,iBAAiB,wBAAwB;AAAA,IAC7C;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa,+BAA+B,WAAW;AAAA,EAAA,CACxD;AACD,QAAM,eAAe,KAAK,UAAU,cAAc;AAElD,QAAM,WAAW,QAAQ,MAAM,MAAM,EAAE,SAAS,gBAAA,CAAiB,GAAG,CAAC,eAAe,CAAC;AAIrF,QAAM,eAAe,CAAC,WAAmC,OAAO,EAAE,QAAQ,OAAO,UAAU;AAE3F,SAAO,oBAAoB;AAAA,IACzB,SAAS;AAAA,IACT;AAAA,IACA,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAKT,SAAS,CAAC,QAAQ,4BAA4B,EAAE,QAAQ,OAAO,SAAA,GAAY,GAAG;AAAA,IAC9E;AAAA;AAAA;AAAA,IAGA,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOhB,SAAS;AAAA,MACP,MAAM;AAAA,MACN,KAAK,CAAC,OAAO,oBAAoB,EAAE,SAAS,WAAW,UAAU,GAAA,CAAI;AAAA,IAAA;AAAA,IAEvE;AAAA;AAAA;AAAA;AAAA,IAIA;AAAA,IACA;AAAA,IACA,cAAc,EAAE,MAAM,MAAM,QAAQ,MAAM,UAAU,KAAA;AAAA;AAAA;AAAA;AAAA,IAIpD,oBAAoB,2BAA2B,EAAE,UAAU;AAAA,IAC3D,WAAW;AAAA,IACX,eAAe,CAAC,UAAU;AAMxB,UAAI,CAAC,qBAAqB,KAAK,EAAG;AAElC,YAAM,UAAU,cAAc;AAC9B,UAAI,SAAS;AACX,sBAAc,UAAU;AACxB,gBAAQ,sBAAsB,EAAE,MAAM,EAAE,WAAW,SAAA,GAAY;AAAA,MACjE;AACA,qDAAgB;AAAA,IAClB;AAAA,IACA,oBAAoB,CAAC,eAAe;AAClC,wBAAkB,MAAM,yBAAwB,yCAAY,WAAU,IAAI;AAC1E,WAAI,yCAAY,YAAW,WAAW;AACpC,0BAAkB,KAAK,wBAAwB;AAAA,UAC7C,OAAO;AAAA,UACP,MAAM;AAAA,YACJ,QAAQ,WAAW;AAAA,YACnB,QAAQ,WAAW;AAAA,YACnB,SAAS,WAAW;AAAA,UAAA;AAAA,QACtB,CACD;AAAA,MACH;AACA,+DAAqB;AAAA,IACvB;AAAA,IACA;AAAA,IACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOA,WAAW;AAAA,MACT,KAAK,MAAM;AAAA,MACX,KAAK;AAAA,IAAA;AAAA,IAEP,SAAS,CAAC,UAAU;AAClB,UAAI,CAAC,2BAA2B,KAAK,GAAG;AACtC,0BAAkB;AAAA,UAChB;AAAA,UACA;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,SAAS;AAAA,YACT,MAAM;AAAA,UAAA;AAAA,UAER,EAAE,WAAW,SAAA;AAAA,QAAS;AAAA,MAE1B;AACA,UAAI,SAAS;AACX,gBAAQ,KAAK;AACb;AAAA,MACF;AACA,iCAA2B,aAAa,KAAK;AAAA,IAC/C;AAAA,EAAA,CACD;AACH;"}
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const messageTime = require("../chat/message-time.cjs");
4
- const athenaUrls = require("../lib/athena-urls.cjs");
5
4
  const utils = require("../lib/utils.cjs");
6
5
  const writeTodosToolUi = require("../tools/write-todos-tool-ui.cjs");
7
6
  const toolNames = require("../agent-runtime/src/tool-names.cjs");
@@ -197,22 +196,6 @@ const ASSET_PANEL_TYPES = {
197
196
  function assetPanelTypeOf(assetType) {
198
197
  return ASSET_PANEL_TYPES[assetType] ?? null;
199
198
  }
200
- function originOf(url) {
201
- try {
202
- return new URL(url).origin;
203
- } catch {
204
- return null;
205
- }
206
- }
207
- function athenaLinkAppUrl({
208
- appUrl,
209
- backendUrl,
210
- pageOrigin
211
- }) {
212
- const deployed = athenaUrls.deriveAthenaAppUrl({ backendUrl });
213
- if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;
214
- return appUrl;
215
- }
216
199
  const REVIEW_DECISION_LABELS = {
217
200
  APPROVED: "approved",
218
201
  CHANGES_REQUESTED: "changes requested",
@@ -271,7 +254,6 @@ exports.SESSION_SANDBOX_LABEL = SESSION_SANDBOX_LABEL;
271
254
  exports.SHELL_OUTPUT_PREVIEW_CHARS = SHELL_OUTPUT_PREVIEW_CHARS;
272
255
  exports.assetPanelTypeOf = assetPanelTypeOf;
273
256
  exports.assetTypeLabel = assetTypeLabel;
274
- exports.athenaLinkAppUrl = athenaLinkAppUrl;
275
257
  exports.computerStateBadge = computerStateBadge;
276
258
  exports.deriveShellRows = deriveShellRows;
277
259
  exports.deriveTaskSteps = deriveTaskSteps;
@@ -1 +1 @@
1
- {"version":3,"file":"session-workbench-model.cjs","sources":["../../src/workbench/session-workbench-model.ts"],"sourcesContent":["/**\n * Pure derivations behind `SessionWorkbench`: the Shell pane's rows from the\n * transcript's `execute` calls, the Tasks pane's plan from the thread's\n * `write_todos` state, the Computer pane's choice of machine, and the tab\n * strip's per-thread open/active state. React-free so the panes stay thin and\n * hosts building their own panes reuse the same rules.\n */\nimport {\n EXECUTE_TOOL_NAME,\n parseExecuteOutput,\n readTruncatedToolOutputArtifact,\n summarizeShellCommand,\n} from '@athenaintel/agent-runtime';\nimport { readMessageCreatedAt } from '../chat/message-time';\nimport { deriveAthenaAppUrl } from '../lib/athena-urls';\nimport { isRecord } from '../lib/utils';\nimport { type PlanStep, parseTodoPlan } from '../tools/write-todos-tool-ui';\nimport type { SessionWorkspace, SessionWorkspaceAsset } from './session-workspace-api';\n\n// ─── Transcript shape ────────────────────────────────────────────────\n\n/**\n * The slice of an assistant-ui thread message the workbench reads — a\n * structural subset of `ThreadMessage`, so tests and hosts can hand plain\n * objects.\n */\nexport interface WorkbenchMessage {\n readonly id: string;\n readonly role: string;\n readonly content: readonly unknown[];\n readonly metadata?: { readonly custom?: Record<string, unknown> } | undefined;\n}\n\ninterface ToolCallPart {\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n argsText: string | undefined;\n result: unknown;\n isError: boolean;\n artifact: unknown;\n}\n\nfunction readToolCallPart(part: unknown): ToolCallPart | null {\n if (!isRecord(part) || part.type !== 'tool-call') return null;\n const { toolCallId, toolName, args, argsText, result, isError, artifact } = part;\n if (typeof toolName !== 'string' || typeof toolCallId !== 'string') return null;\n return {\n toolCallId,\n toolName,\n args: isRecord(args) ? args : {},\n argsText: typeof argsText === 'string' ? argsText : undefined,\n result,\n isError: isError === true,\n artifact,\n };\n}\n\n/** A tool result as text: a string as is, text parts joined, `{content}` unwrapped. */\nexport function toolResultText(result: unknown): string | null {\n if (result === undefined || result === null) return null;\n if (typeof result === 'string') return result;\n if (Array.isArray(result)) {\n const texts = result.flatMap((part) =>\n isRecord(part) && part.type === 'text' && typeof part.text === 'string' ? [part.text] : [],\n );\n return texts.length > 0 ? texts.join('\\n') : JSON.stringify(result);\n }\n if (isRecord(result) && typeof result.content === 'string') return result.content;\n return JSON.stringify(result);\n}\n\n// ─── Shell ───────────────────────────────────────────────────────────\n\n/** How a shell command ended, as far as the transcript says. */\nexport type ShellRowStatus = 'running' | 'succeeded' | 'failed' | 'unknown';\n\n/** Where a command ran: a computer (by asset id) or the session's own sandbox. */\nexport interface ShellMachine {\n /** `null` for the session sandbox (an `execute` without `sandbox_asset_id`). */\n assetId: string | null;\n label: string;\n}\n\nexport interface ShellRow {\n /** The tool call id — stable across renders. */\n id: string;\n command: string;\n /** The command's first non-blank line. */\n firstLine: string;\n /** Lines after the first. */\n extraLines: number;\n machine: ShellMachine;\n status: ShellRowStatus;\n exitCode: number | null;\n /** The output without its status notes, clipped to {@link SHELL_OUTPUT_PREVIEW_CHARS}. */\n output: string;\n /** The output shown is not all of it (clipped here, cut by the backend, or not loaded). */\n outputPartial: boolean;\n /** ISO time of the turn that ran it, when the runtime stamped one. */\n createdAt: string | null;\n}\n\n/** Characters of output a row keeps for its expanded view. */\nexport const SHELL_OUTPUT_PREVIEW_CHARS = 4000;\n\n/** The label for the session's own sandbox. */\nexport const SESSION_SANDBOX_LABEL = 'Session sandbox';\n\n/**\n * Name a machine: a computer the workspace lists by its title, a bare\n * `sandbox_asset_id` the caller cannot see by its id, the session sandbox by\n * {@link SESSION_SANDBOX_LABEL} (or by the session computer, when the session\n * is bound to one).\n */\nexport function shellMachine({\n sandboxAssetId,\n workspace,\n}: {\n sandboxAssetId: string | null;\n workspace: SessionWorkspace | null | undefined;\n}): ShellMachine {\n const targetId = sandboxAssetId ?? workspace?.sessionComputerAssetId ?? null;\n if (targetId === null) return { assetId: null, label: SESSION_SANDBOX_LABEL };\n const computer = workspace?.computers.find((entry) => entry.assetId === targetId);\n return { assetId: targetId, label: computer?.title ?? targetId };\n}\n\nfunction shellStatus({\n result,\n exitCode,\n isError,\n}: {\n result: unknown;\n exitCode: number | null;\n isError: boolean;\n}): ShellRowStatus {\n if (result === undefined) return 'running';\n if (exitCode !== null) return exitCode === 0 ? 'succeeded' : 'failed';\n return isError ? 'failed' : 'unknown';\n}\n\n/**\n * Every `execute` call in the transcript, newest first. Commands still\n * running read `running`; one whose output the live window cut reads its exit\n * note only when the loaded text still carries it (`unknown` otherwise).\n */\nexport function deriveShellRows({\n messages,\n workspace,\n}: {\n messages: readonly WorkbenchMessage[];\n workspace?: SessionWorkspace | null;\n}): ShellRow[] {\n const rows: ShellRow[] = [];\n for (const message of messages) {\n if (message.role !== 'assistant') continue;\n const createdAt = readMessageCreatedAt(message.metadata?.custom);\n for (const raw of message.content) {\n const part = readToolCallPart(raw);\n if (!part || part.toolName !== EXECUTE_TOOL_NAME) continue;\n const command = typeof part.args.command === 'string' ? part.args.command : '';\n const sandboxAssetId =\n typeof part.args.sandbox_asset_id === 'string' && part.args.sandbox_asset_id\n ? part.args.sandbox_asset_id\n : null;\n const text = toolResultText(part.result);\n const parsed = text === null ? null : parseExecuteOutput(text);\n const body = parsed?.body ?? '';\n const clipped = body.length > SHELL_OUTPUT_PREVIEW_CHARS;\n const { firstLine, extraLines } = summarizeShellCommand(command);\n rows.push({\n id: part.toolCallId,\n command,\n firstLine,\n extraLines,\n machine: shellMachine({ sandboxAssetId, workspace }),\n status: shellStatus({\n result: part.result,\n exitCode: parsed?.exitCode ?? null,\n isError: part.isError,\n }),\n exitCode: parsed?.exitCode ?? null,\n output: clipped ? body.slice(0, SHELL_OUTPUT_PREVIEW_CHARS) : body,\n outputPartial:\n clipped ||\n parsed?.truncated === true ||\n readTruncatedToolOutputArtifact(part.artifact) !== null,\n createdAt,\n });\n }\n }\n return rows.reverse();\n}\n\n/** Rows whose command, machine or output contains every word of `query` (case-insensitive). */\nexport function filterShellRows(rows: readonly ShellRow[], query: string): ShellRow[] {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (words.length === 0) return [...rows];\n return rows.filter((row) => {\n const haystack = `${row.command}\\n${row.machine.label}\\n${row.output}`.toLowerCase();\n return words.every((word) => haystack.includes(word));\n });\n}\n\n// ─── Tasks ───────────────────────────────────────────────────────────\n\nexport interface TaskProgress {\n steps: PlanStep[];\n /** Steps completed. */\n done: number;\n total: number;\n}\n\n/** The plan with its \"N/M done\" counts. */\nexport function taskProgress(steps: readonly PlanStep[]): TaskProgress {\n return {\n steps: [...steps],\n done: steps.filter((step) => step.status === 'completed').length,\n total: steps.length,\n };\n}\n\n/**\n * The session's current plan: the thread state's `todos` channel (the\n * deepagents plan the backend keeps — authoritative whenever it is an array,\n * an empty one included: a cleared plan stays cleared), else the newest\n * `write_todos` call in the loaded transcript, else empty. The transcript\n * fallback covers a host whose wire does not carry the channel.\n */\nexport function deriveTaskSteps({\n stateTodos,\n messages,\n}: {\n stateTodos: unknown;\n messages: readonly WorkbenchMessage[];\n}): PlanStep[] {\n if (Array.isArray(stateTodos)) {\n return parseTodoPlan({ args: { todos: stateTodos } }).steps;\n }\n for (let m = messages.length - 1; m >= 0; m -= 1) {\n const message = messages[m];\n if (!message || message.role !== 'assistant') continue;\n for (let p = message.content.length - 1; p >= 0; p -= 1) {\n const part = readToolCallPart(message.content[p]);\n if (!part || part.toolName !== 'write_todos') continue;\n const steps = parseTodoPlan({\n args: part.args,\n argsText: part.argsText,\n result: part.result,\n }).steps;\n if (steps.length > 0) return steps;\n }\n }\n return [];\n}\n\n// ─── Computers ───────────────────────────────────────────────────────\n\n/**\n * The computer the Computer pane leads with: the session's bound computer,\n * else the computer the session touched most recently. The rest follow.\n */\nexport function workspaceComputers(workspace: SessionWorkspace | null | undefined): {\n primary: SessionWorkspaceAsset | null;\n others: SessionWorkspaceAsset[];\n} {\n const computers = workspace?.computers ?? [];\n const primary =\n computers.find((computer) => computer.role === 'session_computer') ?? computers[0] ?? null;\n return {\n primary,\n others: computers.filter((computer) => computer !== primary),\n };\n}\n\nexport type ComputerStateTone = 'running' | 'transition' | 'stopped' | 'error';\n\nconst COMPUTER_STATE_BADGES: Record<string, { label: string; tone: ComputerStateTone }> = {\n started: { label: 'Running', tone: 'running' },\n running: { label: 'Running', tone: 'running' },\n ready: { label: 'Running', tone: 'running' },\n starting: { label: 'Starting', tone: 'transition' },\n pending: { label: 'Starting', tone: 'transition' },\n restoring: { label: 'Restoring', tone: 'transition' },\n resuming: { label: 'Waking', tone: 'transition' },\n stopping: { label: 'Stopping', tone: 'transition' },\n pausing: { label: 'Pausing', tone: 'transition' },\n stopped: { label: 'Stopped', tone: 'stopped' },\n paused: { label: 'Paused', tone: 'stopped' },\n suspended: { label: 'Sleeping', tone: 'stopped' },\n archived: { label: 'Archived', tone: 'stopped' },\n error: { label: 'Error', tone: 'error' },\n failed: { label: 'Error', tone: 'error' },\n errored: { label: 'Error', tone: 'error' },\n};\n\n/**\n * The badge for a computer's runtime state, or `null` when the state says\n * nothing a person can act on (`unknown`). A stop someone asked for wins over\n * a runtime that has not caught up yet.\n */\nexport function computerStateBadge(status: {\n state: string;\n explicitStop: string | null;\n}): { label: string; tone: ComputerStateTone } | null {\n if (status.explicitStop === 'stopping') return { label: 'Stopping', tone: 'transition' };\n const state = status.state.toLowerCase();\n if (state === '' || state === 'unknown') return null;\n return (\n COMPUTER_STATE_BADGES[state] ?? {\n label: state.charAt(0).toUpperCase() + state.slice(1).replace(/_/g, ' '),\n tone: 'stopped',\n }\n );\n}\n\n// ─── Assets ──────────────────────────────────────────────────────────\n\nconst ASSET_TYPE_LABELS: Record<string, string> = {\n presentation_pptx_studio: 'Presentation',\n pptx: 'PowerPoint file',\n pdf: 'PDF',\n txt: 'Text file',\n json: 'JSON',\n yaml: 'YAML',\n query_snippet: 'Query',\n web_page: 'Web page',\n browse_session: 'Browser session',\n agent_config: 'Agent',\n collab_agent: 'Agent',\n};\n\n/** A person's word for an Athena asset type (`spreadsheet` → \"Spreadsheet\"). */\nexport function assetTypeLabel(assetType: string): string {\n const known = ASSET_TYPE_LABELS[assetType];\n if (known) return known;\n if (!assetType) return 'Asset';\n const words = assetType.replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst ASSET_PANEL_TYPES: Record<string, 'presentation' | 'spreadsheet' | 'document' | 'notebook'> = {\n presentation: 'presentation',\n presentation_pptx_studio: 'presentation',\n spreadsheet: 'spreadsheet',\n document: 'document',\n notebook: 'notebook',\n};\n\n/** The asset panel's viewer for an asset type, or `null` when the panel has none. */\nexport function assetPanelTypeOf(\n assetType: string,\n): 'presentation' | 'spreadsheet' | 'document' | 'notebook' | null {\n return ASSET_PANEL_TYPES[assetType] ?? null;\n}\n\nfunction originOf(url: string): string | null {\n try {\n return new URL(url).origin;\n } catch {\n return null;\n }\n}\n\n/**\n * The Athena frontend an \"Open in Athena\" link points at. The provider's\n * `appUrl` is that frontend, except in an app a computer serves: Marathon's\n * `/_athena/auth` mints `appUrl` as the app's OWN origin, so a link built on\n * it opens the app instead of Athena. When `appUrl` is the page's own origin\n * and the Agora base belongs to a known Athena deployment, the deployment's\n * frontend wins (on Olympus itself the two are the same origin anyway).\n */\nexport function athenaLinkAppUrl({\n appUrl,\n backendUrl,\n pageOrigin,\n}: {\n appUrl: string;\n backendUrl: string;\n pageOrigin: string | null;\n}): string {\n const deployed = deriveAthenaAppUrl({ backendUrl });\n if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;\n return appUrl;\n}\n\n// ─── Pull requests ───────────────────────────────────────────────────\n\nconst REVIEW_DECISION_LABELS: Record<string, string> = {\n APPROVED: 'approved',\n CHANGES_REQUESTED: 'changes requested',\n REVIEW_REQUIRED: 'review required',\n};\n\n/** GitHub's review decision in words, or `''` when there is none. */\nexport function reviewDecisionLabel(reviewDecision: string | null): string {\n if (!reviewDecision) return '';\n return REVIEW_DECISION_LABELS[reviewDecision] ?? reviewDecision.toLowerCase().replace(/_/g, ' ');\n}\n\n// ─── Tabs ────────────────────────────────────────────────────────────\n\n/** One thread's open tabs (in strip order) and its active tab. */\nexport interface WorkbenchThreadTabs {\n open: string[];\n active: string | null;\n}\n\n/** Every thread's tabs this workbench has seen, in memory only. */\nexport type WorkbenchTabsState = Readonly<Record<string, WorkbenchThreadTabs>>;\n\nexport type WorkbenchTabsAction =\n | { type: 'open'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs }\n | { type: 'close'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs }\n | { type: 'activate'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs };\n\n/** A thread's tabs, or the defaults for a thread the workbench has not changed yet. */\nexport function threadTabs(\n state: WorkbenchTabsState,\n threadKey: string,\n defaults: WorkbenchThreadTabs,\n): WorkbenchThreadTabs {\n return state[threadKey] ?? defaults;\n}\n\n/**\n * The tab strip's transitions. Opening a tab that is already open activates\n * it; closing the active tab activates its right neighbour (else its left);\n * the first change to a thread starts from the defaults.\n */\nexport function workbenchTabsReducer(\n state: WorkbenchTabsState,\n action: WorkbenchTabsAction,\n): WorkbenchTabsState {\n const current = threadTabs(state, action.threadKey, action.defaults);\n let next: WorkbenchThreadTabs;\n switch (action.type) {\n case 'open':\n next = {\n open: current.open.includes(action.paneId)\n ? current.open\n : [...current.open, action.paneId],\n active: action.paneId,\n };\n break;\n case 'activate':\n if (!current.open.includes(action.paneId) || current.active === action.paneId) return state;\n next = { open: current.open, active: action.paneId };\n break;\n case 'close': {\n const index = current.open.indexOf(action.paneId);\n if (index < 0) return state;\n const open = current.open.filter((id) => id !== action.paneId);\n next = {\n open,\n active:\n current.active === action.paneId\n ? (open[index] ?? open[index - 1] ?? null)\n : current.active,\n };\n break;\n }\n }\n return { ...state, [action.threadKey]: next };\n}\n\n/**\n * The tabs as the strip renders them: only panes the registry still has, and\n * an active tab that is one of them (the first, when the stored one is gone).\n */\nexport function visibleThreadTabs(\n tabs: WorkbenchThreadTabs,\n paneIds: ReadonlySet<string>,\n): WorkbenchThreadTabs {\n const open = tabs.open.filter((id) => paneIds.has(id));\n const active =\n tabs.active !== null && open.includes(tabs.active) ? tabs.active : (open[0] ?? null);\n return { open, active };\n}\n\n/** The \"+\" menu's search: panes whose title or description holds the query, any case. */\nexport function filterWorkbenchPanes<P extends { title: string; description?: string }>(\n panes: readonly P[],\n query: string,\n): readonly P[] {\n const needle = query.trim().toLowerCase();\n if (!needle) return panes;\n return panes.filter(\n (pane) =>\n pane.title.toLowerCase().includes(needle) ||\n (pane.description?.toLowerCase().includes(needle) ?? false),\n );\n}\n"],"names":["isRecord","readMessageCreatedAt","EXECUTE_TOOL_NAME","parseExecuteOutput","summarizeShellCommand","readTruncatedToolOutputArtifact","parseTodoPlan","deriveAthenaAppUrl"],"mappings":"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"}
1
+ {"version":3,"file":"session-workbench-model.cjs","sources":["../../src/workbench/session-workbench-model.ts"],"sourcesContent":["/**\n * Pure derivations behind `SessionWorkbench`: the Shell pane's rows from the\n * transcript's `execute` calls, the Tasks pane's plan from the thread's\n * `write_todos` state, the Computer pane's choice of machine, and the tab\n * strip's per-thread open/active state. React-free so the panes stay thin and\n * hosts building their own panes reuse the same rules.\n */\nimport {\n EXECUTE_TOOL_NAME,\n parseExecuteOutput,\n readTruncatedToolOutputArtifact,\n summarizeShellCommand,\n} from '@athenaintel/agent-runtime';\nimport { readMessageCreatedAt } from '../chat/message-time';\nimport { isRecord } from '../lib/utils';\nimport { type PlanStep, parseTodoPlan } from '../tools/write-todos-tool-ui';\nimport type { SessionWorkspace, SessionWorkspaceAsset } from './session-workspace-api';\n\n// ─── Transcript shape ────────────────────────────────────────────────\n\n/**\n * The slice of an assistant-ui thread message the workbench reads — a\n * structural subset of `ThreadMessage`, so tests and hosts can hand plain\n * objects.\n */\nexport interface WorkbenchMessage {\n readonly id: string;\n readonly role: string;\n readonly content: readonly unknown[];\n readonly metadata?: { readonly custom?: Record<string, unknown> } | undefined;\n}\n\ninterface ToolCallPart {\n toolCallId: string;\n toolName: string;\n args: Record<string, unknown>;\n argsText: string | undefined;\n result: unknown;\n isError: boolean;\n artifact: unknown;\n}\n\nfunction readToolCallPart(part: unknown): ToolCallPart | null {\n if (!isRecord(part) || part.type !== 'tool-call') return null;\n const { toolCallId, toolName, args, argsText, result, isError, artifact } = part;\n if (typeof toolName !== 'string' || typeof toolCallId !== 'string') return null;\n return {\n toolCallId,\n toolName,\n args: isRecord(args) ? args : {},\n argsText: typeof argsText === 'string' ? argsText : undefined,\n result,\n isError: isError === true,\n artifact,\n };\n}\n\n/** A tool result as text: a string as is, text parts joined, `{content}` unwrapped. */\nexport function toolResultText(result: unknown): string | null {\n if (result === undefined || result === null) return null;\n if (typeof result === 'string') return result;\n if (Array.isArray(result)) {\n const texts = result.flatMap((part) =>\n isRecord(part) && part.type === 'text' && typeof part.text === 'string' ? [part.text] : [],\n );\n return texts.length > 0 ? texts.join('\\n') : JSON.stringify(result);\n }\n if (isRecord(result) && typeof result.content === 'string') return result.content;\n return JSON.stringify(result);\n}\n\n// ─── Shell ───────────────────────────────────────────────────────────\n\n/** How a shell command ended, as far as the transcript says. */\nexport type ShellRowStatus = 'running' | 'succeeded' | 'failed' | 'unknown';\n\n/** Where a command ran: a computer (by asset id) or the session's own sandbox. */\nexport interface ShellMachine {\n /** `null` for the session sandbox (an `execute` without `sandbox_asset_id`). */\n assetId: string | null;\n label: string;\n}\n\nexport interface ShellRow {\n /** The tool call id — stable across renders. */\n id: string;\n command: string;\n /** The command's first non-blank line. */\n firstLine: string;\n /** Lines after the first. */\n extraLines: number;\n machine: ShellMachine;\n status: ShellRowStatus;\n exitCode: number | null;\n /** The output without its status notes, clipped to {@link SHELL_OUTPUT_PREVIEW_CHARS}. */\n output: string;\n /** The output shown is not all of it (clipped here, cut by the backend, or not loaded). */\n outputPartial: boolean;\n /** ISO time of the turn that ran it, when the runtime stamped one. */\n createdAt: string | null;\n}\n\n/** Characters of output a row keeps for its expanded view. */\nexport const SHELL_OUTPUT_PREVIEW_CHARS = 4000;\n\n/** The label for the session's own sandbox. */\nexport const SESSION_SANDBOX_LABEL = 'Session sandbox';\n\n/**\n * Name a machine: a computer the workspace lists by its title, a bare\n * `sandbox_asset_id` the caller cannot see by its id, the session sandbox by\n * {@link SESSION_SANDBOX_LABEL} (or by the session computer, when the session\n * is bound to one).\n */\nexport function shellMachine({\n sandboxAssetId,\n workspace,\n}: {\n sandboxAssetId: string | null;\n workspace: SessionWorkspace | null | undefined;\n}): ShellMachine {\n const targetId = sandboxAssetId ?? workspace?.sessionComputerAssetId ?? null;\n if (targetId === null) return { assetId: null, label: SESSION_SANDBOX_LABEL };\n const computer = workspace?.computers.find((entry) => entry.assetId === targetId);\n return { assetId: targetId, label: computer?.title ?? targetId };\n}\n\nfunction shellStatus({\n result,\n exitCode,\n isError,\n}: {\n result: unknown;\n exitCode: number | null;\n isError: boolean;\n}): ShellRowStatus {\n if (result === undefined) return 'running';\n if (exitCode !== null) return exitCode === 0 ? 'succeeded' : 'failed';\n return isError ? 'failed' : 'unknown';\n}\n\n/**\n * Every `execute` call in the transcript, newest first. Commands still\n * running read `running`; one whose output the live window cut reads its exit\n * note only when the loaded text still carries it (`unknown` otherwise).\n */\nexport function deriveShellRows({\n messages,\n workspace,\n}: {\n messages: readonly WorkbenchMessage[];\n workspace?: SessionWorkspace | null;\n}): ShellRow[] {\n const rows: ShellRow[] = [];\n for (const message of messages) {\n if (message.role !== 'assistant') continue;\n const createdAt = readMessageCreatedAt(message.metadata?.custom);\n for (const raw of message.content) {\n const part = readToolCallPart(raw);\n if (!part || part.toolName !== EXECUTE_TOOL_NAME) continue;\n const command = typeof part.args.command === 'string' ? part.args.command : '';\n const sandboxAssetId =\n typeof part.args.sandbox_asset_id === 'string' && part.args.sandbox_asset_id\n ? part.args.sandbox_asset_id\n : null;\n const text = toolResultText(part.result);\n const parsed = text === null ? null : parseExecuteOutput(text);\n const body = parsed?.body ?? '';\n const clipped = body.length > SHELL_OUTPUT_PREVIEW_CHARS;\n const { firstLine, extraLines } = summarizeShellCommand(command);\n rows.push({\n id: part.toolCallId,\n command,\n firstLine,\n extraLines,\n machine: shellMachine({ sandboxAssetId, workspace }),\n status: shellStatus({\n result: part.result,\n exitCode: parsed?.exitCode ?? null,\n isError: part.isError,\n }),\n exitCode: parsed?.exitCode ?? null,\n output: clipped ? body.slice(0, SHELL_OUTPUT_PREVIEW_CHARS) : body,\n outputPartial:\n clipped ||\n parsed?.truncated === true ||\n readTruncatedToolOutputArtifact(part.artifact) !== null,\n createdAt,\n });\n }\n }\n return rows.reverse();\n}\n\n/** Rows whose command, machine or output contains every word of `query` (case-insensitive). */\nexport function filterShellRows(rows: readonly ShellRow[], query: string): ShellRow[] {\n const words = query.toLowerCase().split(/\\s+/).filter(Boolean);\n if (words.length === 0) return [...rows];\n return rows.filter((row) => {\n const haystack = `${row.command}\\n${row.machine.label}\\n${row.output}`.toLowerCase();\n return words.every((word) => haystack.includes(word));\n });\n}\n\n// ─── Tasks ───────────────────────────────────────────────────────────\n\nexport interface TaskProgress {\n steps: PlanStep[];\n /** Steps completed. */\n done: number;\n total: number;\n}\n\n/** The plan with its \"N/M done\" counts. */\nexport function taskProgress(steps: readonly PlanStep[]): TaskProgress {\n return {\n steps: [...steps],\n done: steps.filter((step) => step.status === 'completed').length,\n total: steps.length,\n };\n}\n\n/**\n * The session's current plan: the thread state's `todos` channel (the\n * deepagents plan the backend keeps — authoritative whenever it is an array,\n * an empty one included: a cleared plan stays cleared), else the newest\n * `write_todos` call in the loaded transcript, else empty. The transcript\n * fallback covers a host whose wire does not carry the channel.\n */\nexport function deriveTaskSteps({\n stateTodos,\n messages,\n}: {\n stateTodos: unknown;\n messages: readonly WorkbenchMessage[];\n}): PlanStep[] {\n if (Array.isArray(stateTodos)) {\n return parseTodoPlan({ args: { todos: stateTodos } }).steps;\n }\n for (let m = messages.length - 1; m >= 0; m -= 1) {\n const message = messages[m];\n if (!message || message.role !== 'assistant') continue;\n for (let p = message.content.length - 1; p >= 0; p -= 1) {\n const part = readToolCallPart(message.content[p]);\n if (!part || part.toolName !== 'write_todos') continue;\n const steps = parseTodoPlan({\n args: part.args,\n argsText: part.argsText,\n result: part.result,\n }).steps;\n if (steps.length > 0) return steps;\n }\n }\n return [];\n}\n\n// ─── Computers ───────────────────────────────────────────────────────\n\n/**\n * The computer the Computer pane leads with: the session's bound computer,\n * else the computer the session touched most recently. The rest follow.\n */\nexport function workspaceComputers(workspace: SessionWorkspace | null | undefined): {\n primary: SessionWorkspaceAsset | null;\n others: SessionWorkspaceAsset[];\n} {\n const computers = workspace?.computers ?? [];\n const primary =\n computers.find((computer) => computer.role === 'session_computer') ?? computers[0] ?? null;\n return {\n primary,\n others: computers.filter((computer) => computer !== primary),\n };\n}\n\nexport type ComputerStateTone = 'running' | 'transition' | 'stopped' | 'error';\n\nconst COMPUTER_STATE_BADGES: Record<string, { label: string; tone: ComputerStateTone }> = {\n started: { label: 'Running', tone: 'running' },\n running: { label: 'Running', tone: 'running' },\n ready: { label: 'Running', tone: 'running' },\n starting: { label: 'Starting', tone: 'transition' },\n pending: { label: 'Starting', tone: 'transition' },\n restoring: { label: 'Restoring', tone: 'transition' },\n resuming: { label: 'Waking', tone: 'transition' },\n stopping: { label: 'Stopping', tone: 'transition' },\n pausing: { label: 'Pausing', tone: 'transition' },\n stopped: { label: 'Stopped', tone: 'stopped' },\n paused: { label: 'Paused', tone: 'stopped' },\n suspended: { label: 'Sleeping', tone: 'stopped' },\n archived: { label: 'Archived', tone: 'stopped' },\n error: { label: 'Error', tone: 'error' },\n failed: { label: 'Error', tone: 'error' },\n errored: { label: 'Error', tone: 'error' },\n};\n\n/**\n * The badge for a computer's runtime state, or `null` when the state says\n * nothing a person can act on (`unknown`). A stop someone asked for wins over\n * a runtime that has not caught up yet.\n */\nexport function computerStateBadge(status: {\n state: string;\n explicitStop: string | null;\n}): { label: string; tone: ComputerStateTone } | null {\n if (status.explicitStop === 'stopping') return { label: 'Stopping', tone: 'transition' };\n const state = status.state.toLowerCase();\n if (state === '' || state === 'unknown') return null;\n return (\n COMPUTER_STATE_BADGES[state] ?? {\n label: state.charAt(0).toUpperCase() + state.slice(1).replace(/_/g, ' '),\n tone: 'stopped',\n }\n );\n}\n\n// ─── Assets ──────────────────────────────────────────────────────────\n\nconst ASSET_TYPE_LABELS: Record<string, string> = {\n presentation_pptx_studio: 'Presentation',\n pptx: 'PowerPoint file',\n pdf: 'PDF',\n txt: 'Text file',\n json: 'JSON',\n yaml: 'YAML',\n query_snippet: 'Query',\n web_page: 'Web page',\n browse_session: 'Browser session',\n agent_config: 'Agent',\n collab_agent: 'Agent',\n};\n\n/** A person's word for an Athena asset type (`spreadsheet` → \"Spreadsheet\"). */\nexport function assetTypeLabel(assetType: string): string {\n const known = ASSET_TYPE_LABELS[assetType];\n if (known) return known;\n if (!assetType) return 'Asset';\n const words = assetType.replace(/_/g, ' ');\n return words.charAt(0).toUpperCase() + words.slice(1);\n}\n\nconst ASSET_PANEL_TYPES: Record<string, 'presentation' | 'spreadsheet' | 'document' | 'notebook'> = {\n presentation: 'presentation',\n presentation_pptx_studio: 'presentation',\n spreadsheet: 'spreadsheet',\n document: 'document',\n notebook: 'notebook',\n};\n\n/** The asset panel's viewer for an asset type, or `null` when the panel has none. */\nexport function assetPanelTypeOf(\n assetType: string,\n): 'presentation' | 'spreadsheet' | 'document' | 'notebook' | null {\n return ASSET_PANEL_TYPES[assetType] ?? null;\n}\n\n\n// ─── Pull requests ───────────────────────────────────────────────────\n\nconst REVIEW_DECISION_LABELS: Record<string, string> = {\n APPROVED: 'approved',\n CHANGES_REQUESTED: 'changes requested',\n REVIEW_REQUIRED: 'review required',\n};\n\n/** GitHub's review decision in words, or `''` when there is none. */\nexport function reviewDecisionLabel(reviewDecision: string | null): string {\n if (!reviewDecision) return '';\n return REVIEW_DECISION_LABELS[reviewDecision] ?? reviewDecision.toLowerCase().replace(/_/g, ' ');\n}\n\n// ─── Tabs ────────────────────────────────────────────────────────────\n\n/** One thread's open tabs (in strip order) and its active tab. */\nexport interface WorkbenchThreadTabs {\n open: string[];\n active: string | null;\n}\n\n/** Every thread's tabs this workbench has seen, in memory only. */\nexport type WorkbenchTabsState = Readonly<Record<string, WorkbenchThreadTabs>>;\n\nexport type WorkbenchTabsAction =\n | { type: 'open'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs }\n | { type: 'close'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs }\n | { type: 'activate'; threadKey: string; paneId: string; defaults: WorkbenchThreadTabs };\n\n/** A thread's tabs, or the defaults for a thread the workbench has not changed yet. */\nexport function threadTabs(\n state: WorkbenchTabsState,\n threadKey: string,\n defaults: WorkbenchThreadTabs,\n): WorkbenchThreadTabs {\n return state[threadKey] ?? defaults;\n}\n\n/**\n * The tab strip's transitions. Opening a tab that is already open activates\n * it; closing the active tab activates its right neighbour (else its left);\n * the first change to a thread starts from the defaults.\n */\nexport function workbenchTabsReducer(\n state: WorkbenchTabsState,\n action: WorkbenchTabsAction,\n): WorkbenchTabsState {\n const current = threadTabs(state, action.threadKey, action.defaults);\n let next: WorkbenchThreadTabs;\n switch (action.type) {\n case 'open':\n next = {\n open: current.open.includes(action.paneId)\n ? current.open\n : [...current.open, action.paneId],\n active: action.paneId,\n };\n break;\n case 'activate':\n if (!current.open.includes(action.paneId) || current.active === action.paneId) return state;\n next = { open: current.open, active: action.paneId };\n break;\n case 'close': {\n const index = current.open.indexOf(action.paneId);\n if (index < 0) return state;\n const open = current.open.filter((id) => id !== action.paneId);\n next = {\n open,\n active:\n current.active === action.paneId\n ? (open[index] ?? open[index - 1] ?? null)\n : current.active,\n };\n break;\n }\n }\n return { ...state, [action.threadKey]: next };\n}\n\n/**\n * The tabs as the strip renders them: only panes the registry still has, and\n * an active tab that is one of them (the first, when the stored one is gone).\n */\nexport function visibleThreadTabs(\n tabs: WorkbenchThreadTabs,\n paneIds: ReadonlySet<string>,\n): WorkbenchThreadTabs {\n const open = tabs.open.filter((id) => paneIds.has(id));\n const active =\n tabs.active !== null && open.includes(tabs.active) ? tabs.active : (open[0] ?? null);\n return { open, active };\n}\n\n/** The \"+\" menu's search: panes whose title or description holds the query, any case. */\nexport function filterWorkbenchPanes<P extends { title: string; description?: string }>(\n panes: readonly P[],\n query: string,\n): readonly P[] {\n const needle = query.trim().toLowerCase();\n if (!needle) return panes;\n return panes.filter(\n (pane) =>\n pane.title.toLowerCase().includes(needle) ||\n (pane.description?.toLowerCase().includes(needle) ?? false),\n );\n}\n"],"names":["isRecord","readMessageCreatedAt","EXECUTE_TOOL_NAME","parseExecuteOutput","summarizeShellCommand","readTruncatedToolOutputArtifact","parseTodoPlan"],"mappings":"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"}
@@ -110,19 +110,6 @@ export declare function computerStateBadge(status: {
110
110
  export declare function assetTypeLabel(assetType: string): string;
111
111
  /** The asset panel's viewer for an asset type, or `null` when the panel has none. */
112
112
  export declare function assetPanelTypeOf(assetType: string): 'presentation' | 'spreadsheet' | 'document' | 'notebook' | null;
113
- /**
114
- * The Athena frontend an "Open in Athena" link points at. The provider's
115
- * `appUrl` is that frontend, except in an app a computer serves: Marathon's
116
- * `/_athena/auth` mints `appUrl` as the app's OWN origin, so a link built on
117
- * it opens the app instead of Athena. When `appUrl` is the page's own origin
118
- * and the Agora base belongs to a known Athena deployment, the deployment's
119
- * frontend wins (on Olympus itself the two are the same origin anyway).
120
- */
121
- export declare function athenaLinkAppUrl({ appUrl, backendUrl, pageOrigin, }: {
122
- appUrl: string;
123
- backendUrl: string;
124
- pageOrigin: string | null;
125
- }): string;
126
113
  /** GitHub's review decision in words, or `''` when there is none. */
127
114
  export declare function reviewDecisionLabel(reviewDecision: string | null): string;
128
115
  /** One thread's open tabs (in strip order) and its active tab. */
@@ -1,5 +1,4 @@
1
1
  import { readMessageCreatedAt } from "../chat/message-time.js";
2
- import { deriveAthenaAppUrl } from "../lib/athena-urls.js";
3
2
  import { isRecord } from "../lib/utils.js";
4
3
  import { parseTodoPlan } from "../tools/write-todos-tool-ui.js";
5
4
  import { EXECUTE_TOOL_NAME } from "../agent-runtime/src/tool-names.js";
@@ -195,22 +194,6 @@ const ASSET_PANEL_TYPES = {
195
194
  function assetPanelTypeOf(assetType) {
196
195
  return ASSET_PANEL_TYPES[assetType] ?? null;
197
196
  }
198
- function originOf(url) {
199
- try {
200
- return new URL(url).origin;
201
- } catch {
202
- return null;
203
- }
204
- }
205
- function athenaLinkAppUrl({
206
- appUrl,
207
- backendUrl,
208
- pageOrigin
209
- }) {
210
- const deployed = deriveAthenaAppUrl({ backendUrl });
211
- if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;
212
- return appUrl;
213
- }
214
197
  const REVIEW_DECISION_LABELS = {
215
198
  APPROVED: "approved",
216
199
  CHANGES_REQUESTED: "changes requested",
@@ -270,7 +253,6 @@ export {
270
253
  SHELL_OUTPUT_PREVIEW_CHARS,
271
254
  assetPanelTypeOf,
272
255
  assetTypeLabel,
273
- athenaLinkAppUrl,
274
256
  computerStateBadge,
275
257
  deriveShellRows,
276
258
  deriveTaskSteps,