@athenaintel/react 0.41.0 → 0.41.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -807,6 +807,10 @@ Both use the same bearer / API key as session creation. The full recipe
807
807
  template skill `voice-session-guide.md`. `createAthenaVoiceSession` is also
808
808
  exported if you hold the bridge state yourself.
809
809
 
810
+ ## What's new in 0.41.1
811
+
812
+ **A chip in a computer app links to Athena, not to the app.** In an app a computer serves, Marathon hands the provider the app's own origin as its `appUrl`, so every asset and citation chip linked to `https://<app>/dashboard/spaces/…`, the app itself. Chips now link to the deployment's Athena frontend there (`https://staging-app.athenaintel.com/…` on staging), as the workbench's "Open in Athena" links already did. This matters most for a repository file citation, which 0.41.0 opens as its link. `athenaLinkAppUrl` is now exported from the URL helpers (still from the package root). An `appUrl` you configure that is not the page's own origin is used as configured.
813
+
810
814
  ## What's new in 0.41.0
811
815
 
812
816
  **Citation chips say where they point, with an icon, in every app.** An asset chip in a chat
@@ -6,7 +6,12 @@ function createOlympusAssetUrl({
6
6
  backendUrl,
7
7
  assetIds
8
8
  }) {
9
- const resolvedAppUrl = (appUrl == null ? void 0 : appUrl.trim()) || (backendUrl ? athenaUrls.deriveAthenaAppUrl({ backendUrl }) : null);
9
+ const configuredAppUrl = appUrl == null ? void 0 : appUrl.trim();
10
+ const resolvedAppUrl = configuredAppUrl ? athenaUrls.athenaLinkAppUrl({
11
+ appUrl: configuredAppUrl,
12
+ backendUrl: backendUrl ?? "",
13
+ pageOrigin: typeof window === "undefined" ? null : window.location.origin
14
+ }) : backendUrl ? athenaUrls.deriveAthenaAppUrl({ backendUrl }) : null;
10
15
  if (!resolvedAppUrl) return null;
11
16
  const url = athenaUrls.createAthenaSpacesUrl({
12
17
  appUrl: resolvedAppUrl,
@@ -1 +1 @@
1
- {"version":3,"file":"asset-urls.cjs","sources":["../../../src/chat-ui/lib/asset-urls.ts"],"sourcesContent":["import {\n createAthenaSpacesUrl,\n deriveAthenaAppUrl,\n} from \"../../lib/athena-urls\";\n\n/** Deep link into Spaces for one or more assets referenced from chat. */\nexport function createOlympusAssetUrl({\n appUrl,\n backendUrl,\n assetIds,\n}: {\n appUrl?: string | null;\n backendUrl?: string | null;\n assetIds: string | string[];\n}): string | null {\n const resolvedAppUrl: string | null =\n appUrl?.trim() || (backendUrl ? deriveAthenaAppUrl({ backendUrl }) : null);\n if (!resolvedAppUrl) return null;\n\n const url = createAthenaSpacesUrl({\n appUrl: resolvedAppUrl,\n assetIds,\n });\n return new URL(url).toString();\n}\n"],"names":["deriveAthenaAppUrl","createAthenaSpacesUrl"],"mappings":";;;AAMO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;AAChB,QAAM,kBACJ,iCAAQ,YAAW,aAAaA,WAAAA,mBAAmB,EAAE,YAAY,IAAI;AACvE,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,MAAMC,WAAAA,sBAAsB;AAAA,IAChC,QAAQ;AAAA,IACR;AAAA,EAAA,CACD;AACD,SAAO,IAAI,IAAI,GAAG,EAAE,SAAA;AACtB;;"}
1
+ {"version":3,"file":"asset-urls.cjs","sources":["../../../src/chat-ui/lib/asset-urls.ts"],"sourcesContent":["import {\n athenaLinkAppUrl,\n createAthenaSpacesUrl,\n deriveAthenaAppUrl,\n} from \"../../lib/athena-urls\";\n\n/**\n * Deep link into Spaces for one or more assets referenced from chat. In an app\n * a computer serves, the provider's `appUrl` is the app's own origin (Marathon\n * mints it so), and the link goes to the deployment's Athena frontend instead,\n * as the workbench's \"Open in Athena\" links do (`athenaLinkAppUrl`).\n */\nexport function createOlympusAssetUrl({\n appUrl,\n backendUrl,\n assetIds,\n}: {\n appUrl?: string | null;\n backendUrl?: string | null;\n assetIds: string | string[];\n}): string | null {\n const configuredAppUrl = appUrl?.trim();\n const resolvedAppUrl: string | null = configuredAppUrl\n ? athenaLinkAppUrl({\n appUrl: configuredAppUrl,\n backendUrl: backendUrl ?? \"\",\n pageOrigin: typeof window === \"undefined\" ? null : window.location.origin,\n })\n : backendUrl\n ? deriveAthenaAppUrl({ backendUrl })\n : null;\n if (!resolvedAppUrl) return null;\n\n const url = createAthenaSpacesUrl({\n appUrl: resolvedAppUrl,\n assetIds,\n });\n return new URL(url).toString();\n}\n"],"names":["athenaLinkAppUrl","deriveAthenaAppUrl","createAthenaSpacesUrl"],"mappings":";;;AAYO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;AAChB,QAAM,mBAAmB,iCAAQ;AACjC,QAAM,iBAAgC,mBAClCA,4BAAiB;AAAA,IACf,QAAQ;AAAA,IACR,YAAY,cAAc;AAAA,IAC1B,YAAY,OAAO,WAAW,cAAc,OAAO,OAAO,SAAS;AAAA,EAAA,CACpE,IACD,aACEC,WAAAA,mBAAmB,EAAE,WAAA,CAAY,IACjC;AACN,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,MAAMC,WAAAA,sBAAsB;AAAA,IAChC,QAAQ;AAAA,IACR;AAAA,EAAA,CACD;AACD,SAAO,IAAI,IAAI,GAAG,EAAE,SAAA;AACtB;;"}
@@ -1,4 +1,9 @@
1
- /** Deep link into Spaces for one or more assets referenced from chat. */
1
+ /**
2
+ * Deep link into Spaces for one or more assets referenced from chat. In an app
3
+ * a computer serves, the provider's `appUrl` is the app's own origin (Marathon
4
+ * mints it so), and the link goes to the deployment's Athena frontend instead,
5
+ * as the workbench's "Open in Athena" links do (`athenaLinkAppUrl`).
6
+ */
2
7
  export declare function createOlympusAssetUrl({ appUrl, backendUrl, assetIds, }: {
3
8
  appUrl?: string | null;
4
9
  backendUrl?: string | null;
@@ -1,10 +1,15 @@
1
- import { deriveAthenaAppUrl, createAthenaSpacesUrl } from "../../lib/athena-urls.js";
1
+ import { athenaLinkAppUrl, deriveAthenaAppUrl, createAthenaSpacesUrl } from "../../lib/athena-urls.js";
2
2
  function createOlympusAssetUrl({
3
3
  appUrl,
4
4
  backendUrl,
5
5
  assetIds
6
6
  }) {
7
- const resolvedAppUrl = (appUrl == null ? void 0 : appUrl.trim()) || (backendUrl ? deriveAthenaAppUrl({ backendUrl }) : null);
7
+ const configuredAppUrl = appUrl == null ? void 0 : appUrl.trim();
8
+ const resolvedAppUrl = configuredAppUrl ? athenaLinkAppUrl({
9
+ appUrl: configuredAppUrl,
10
+ backendUrl: backendUrl ?? "",
11
+ pageOrigin: typeof window === "undefined" ? null : window.location.origin
12
+ }) : backendUrl ? deriveAthenaAppUrl({ backendUrl }) : null;
8
13
  if (!resolvedAppUrl) return null;
9
14
  const url = createAthenaSpacesUrl({
10
15
  appUrl: resolvedAppUrl,
@@ -1 +1 @@
1
- {"version":3,"file":"asset-urls.js","sources":["../../../src/chat-ui/lib/asset-urls.ts"],"sourcesContent":["import {\n createAthenaSpacesUrl,\n deriveAthenaAppUrl,\n} from \"../../lib/athena-urls\";\n\n/** Deep link into Spaces for one or more assets referenced from chat. */\nexport function createOlympusAssetUrl({\n appUrl,\n backendUrl,\n assetIds,\n}: {\n appUrl?: string | null;\n backendUrl?: string | null;\n assetIds: string | string[];\n}): string | null {\n const resolvedAppUrl: string | null =\n appUrl?.trim() || (backendUrl ? deriveAthenaAppUrl({ backendUrl }) : null);\n if (!resolvedAppUrl) return null;\n\n const url = createAthenaSpacesUrl({\n appUrl: resolvedAppUrl,\n assetIds,\n });\n return new URL(url).toString();\n}\n"],"names":[],"mappings":";AAMO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;AAChB,QAAM,kBACJ,iCAAQ,YAAW,aAAa,mBAAmB,EAAE,YAAY,IAAI;AACvE,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,MAAM,sBAAsB;AAAA,IAChC,QAAQ;AAAA,IACR;AAAA,EAAA,CACD;AACD,SAAO,IAAI,IAAI,GAAG,EAAE,SAAA;AACtB;"}
1
+ {"version":3,"file":"asset-urls.js","sources":["../../../src/chat-ui/lib/asset-urls.ts"],"sourcesContent":["import {\n athenaLinkAppUrl,\n createAthenaSpacesUrl,\n deriveAthenaAppUrl,\n} from \"../../lib/athena-urls\";\n\n/**\n * Deep link into Spaces for one or more assets referenced from chat. In an app\n * a computer serves, the provider's `appUrl` is the app's own origin (Marathon\n * mints it so), and the link goes to the deployment's Athena frontend instead,\n * as the workbench's \"Open in Athena\" links do (`athenaLinkAppUrl`).\n */\nexport function createOlympusAssetUrl({\n appUrl,\n backendUrl,\n assetIds,\n}: {\n appUrl?: string | null;\n backendUrl?: string | null;\n assetIds: string | string[];\n}): string | null {\n const configuredAppUrl = appUrl?.trim();\n const resolvedAppUrl: string | null = configuredAppUrl\n ? athenaLinkAppUrl({\n appUrl: configuredAppUrl,\n backendUrl: backendUrl ?? \"\",\n pageOrigin: typeof window === \"undefined\" ? null : window.location.origin,\n })\n : backendUrl\n ? deriveAthenaAppUrl({ backendUrl })\n : null;\n if (!resolvedAppUrl) return null;\n\n const url = createAthenaSpacesUrl({\n appUrl: resolvedAppUrl,\n assetIds,\n });\n return new URL(url).toString();\n}\n"],"names":[],"mappings":";AAYO,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;AAChB,QAAM,mBAAmB,iCAAQ;AACjC,QAAM,iBAAgC,mBAClC,iBAAiB;AAAA,IACf,QAAQ;AAAA,IACR,YAAY,cAAc;AAAA,IAC1B,YAAY,OAAO,WAAW,cAAc,OAAO,OAAO,SAAS;AAAA,EAAA,CACpE,IACD,aACE,mBAAmB,EAAE,WAAA,CAAY,IACjC;AACN,MAAI,CAAC,eAAgB,QAAO;AAE5B,QAAM,MAAM,sBAAsB;AAAA,IAChC,QAAQ;AAAA,IACR;AAAA,EAAA,CACD;AACD,SAAO,IAAI,IAAI,GAAG,EAAE,SAAA;AACtB;"}
package/dist/index.cjs CHANGED
@@ -330,7 +330,6 @@ exports.SESSION_SANDBOX_LABEL = sessionWorkbenchModel.SESSION_SANDBOX_LABEL;
330
330
  exports.SHELL_OUTPUT_PREVIEW_CHARS = sessionWorkbenchModel.SHELL_OUTPUT_PREVIEW_CHARS;
331
331
  exports.assetPanelTypeOf = sessionWorkbenchModel.assetPanelTypeOf;
332
332
  exports.assetTypeLabel = sessionWorkbenchModel.assetTypeLabel;
333
- exports.athenaLinkAppUrl = sessionWorkbenchModel.athenaLinkAppUrl;
334
333
  exports.computerStateBadge = sessionWorkbenchModel.computerStateBadge;
335
334
  exports.deriveShellRows = sessionWorkbenchModel.deriveShellRows;
336
335
  exports.deriveTaskSteps = sessionWorkbenchModel.deriveTaskSteps;
@@ -472,6 +471,7 @@ exports.useParentAuth = auth.useParentAuth;
472
471
  exports.useParentBridge = auth.useParentBridge;
473
472
  exports.DEFAULT_APP_URL = athenaUrls.DEFAULT_APP_URL;
474
473
  exports.DEFAULT_STATEWIRE_SYNC_URL = athenaUrls.DEFAULT_STATEWIRE_SYNC_URL;
474
+ exports.athenaLinkAppUrl = athenaUrls.athenaLinkAppUrl;
475
475
  exports.createAthenaSpacesUrl = athenaUrls.createAthenaSpacesUrl;
476
476
  exports.deriveAthenaApiUrl = athenaUrls.deriveAthenaApiUrl;
477
477
  exports.deriveAthenaAppUrl = athenaUrls.deriveAthenaAppUrl;
package/dist/index.d.ts CHANGED
@@ -105,7 +105,7 @@ export type { SessionWorkbenchProps } from './workbench/SessionWorkbench';
105
105
  export { SESSION_WORKSPACE_LIVE_REFETCH_MS, useSessionWorkbench, useSessionWorkbenchWorkspace, } from './workbench/session-workbench-context';
106
106
  export type { SessionWorkbenchPane, SessionWorkbenchPaneContext, } from './workbench/session-workbench-context';
107
107
  export { DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS, DEFAULT_SESSION_WORKBENCH_PANES, SESSION_WORKBENCH_ATTACHMENTS_PANE, SESSION_WORKBENCH_COMPUTER_PANE, SESSION_WORKBENCH_PULL_REQUESTS_PANE, SESSION_WORKBENCH_SHELL_PANE, SESSION_WORKBENCH_TASKS_PANE, SessionAttachmentsPane, SessionComputerPane, SessionPullRequestsPane, SessionShellPane, SessionTasksPane, } from './workbench/session-workbench-panes';
108
- export { assetPanelTypeOf, assetTypeLabel, athenaLinkAppUrl, computerStateBadge, deriveShellRows, deriveTaskSteps, filterShellRows, filterWorkbenchPanes, reviewDecisionLabel, SESSION_SANDBOX_LABEL, SHELL_OUTPUT_PREVIEW_CHARS, shellMachine, taskProgress, toolResultText, workspaceComputers, } from './workbench/session-workbench-model';
108
+ export { assetPanelTypeOf, assetTypeLabel, computerStateBadge, deriveShellRows, deriveTaskSteps, filterShellRows, filterWorkbenchPanes, reviewDecisionLabel, SESSION_SANDBOX_LABEL, SHELL_OUTPUT_PREVIEW_CHARS, shellMachine, taskProgress, toolResultText, workspaceComputers, } from './workbench/session-workbench-model';
109
109
  export type { ComputerStateTone, ShellMachine, ShellRow, ShellRowStatus, TaskProgress, WorkbenchMessage, } from './workbench/session-workbench-model';
110
110
  export { computerSshAccessPath, computerStatusPath, fetchComputerRuntimeStatus, fetchComputerSshAccess, fetchSessionWorkspace, readComputerRuntimeStatus, readComputerSshAccess, readSessionWorkspace, sessionWorkspacePath, } from './workbench/session-workspace-api';
111
111
  export type { ComputerRuntimeStatus, ComputerSshAccess, SessionWorkspace, SessionWorkspaceAsset, SessionWorkspaceRole, } from './workbench/session-workspace-api';
@@ -204,7 +204,7 @@ export { buildStatewireRunConfig } from './runtime/statewire-run-config';
204
204
  export type { StatewireRunConfigOptions } from './runtime/statewire-run-config';
205
205
  export { useParentAuth, useParentBridge } from './runtime/auth';
206
206
  export type { ParentBridge, ParentBridgeState } from './runtime/auth';
207
- export { createAthenaSpacesUrl, DEFAULT_APP_URL, DEFAULT_STATEWIRE_SYNC_URL, deriveAthenaApiUrl, deriveAthenaAppUrl, deriveStatewireSyncUrl, resolveStatewireSyncUrl, } from './lib/athena-urls';
207
+ export { athenaLinkAppUrl, createAthenaSpacesUrl, DEFAULT_APP_URL, DEFAULT_STATEWIRE_SYNC_URL, deriveAthenaApiUrl, deriveAthenaAppUrl, deriveStatewireSyncUrl, resolveStatewireSyncUrl, } from './lib/athena-urls';
208
208
  export type { AthenaEnvironment } from './lib/athena-urls';
209
209
  export { Button, buttonVariants } from './ui/button';
210
210
  export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } from './ui/tooltip';
package/dist/index.js CHANGED
@@ -66,7 +66,7 @@ import { useAssetPanelStore } from "./assets/asset-panel-store.js";
66
66
  import { SESSION_WORKBENCH_ASSETS_PANE_ID, SessionWorkbench } from "./workbench/SessionWorkbench.js";
67
67
  import { SESSION_WORKSPACE_LIVE_REFETCH_MS, useSessionWorkbench, useSessionWorkbenchWorkspace } from "./workbench/session-workbench-context.js";
68
68
  import { DEFAULT_SESSION_WORKBENCH_OPEN_PANE_IDS, DEFAULT_SESSION_WORKBENCH_PANES, SESSION_WORKBENCH_ATTACHMENTS_PANE, SESSION_WORKBENCH_COMPUTER_PANE, SESSION_WORKBENCH_PULL_REQUESTS_PANE, SESSION_WORKBENCH_SHELL_PANE, SESSION_WORKBENCH_TASKS_PANE, SessionAttachmentsPane, SessionComputerPane, SessionPullRequestsPane, SessionShellPane, SessionTasksPane } from "./workbench/session-workbench-panes.js";
69
- import { SESSION_SANDBOX_LABEL, SHELL_OUTPUT_PREVIEW_CHARS, assetPanelTypeOf, assetTypeLabel, athenaLinkAppUrl, computerStateBadge, deriveShellRows, deriveTaskSteps, filterShellRows, filterWorkbenchPanes, reviewDecisionLabel, shellMachine, taskProgress, toolResultText, workspaceComputers } from "./workbench/session-workbench-model.js";
69
+ import { SESSION_SANDBOX_LABEL, SHELL_OUTPUT_PREVIEW_CHARS, assetPanelTypeOf, assetTypeLabel, computerStateBadge, deriveShellRows, deriveTaskSteps, filterShellRows, filterWorkbenchPanes, reviewDecisionLabel, shellMachine, taskProgress, toolResultText, workspaceComputers } from "./workbench/session-workbench-model.js";
70
70
  import { computerSshAccessPath, computerStatusPath, fetchComputerRuntimeStatus, fetchComputerSshAccess, fetchSessionWorkspace, readComputerRuntimeStatus, readComputerSshAccess, readSessionWorkspace, sessionWorkspacePath } from "./workbench/session-workspace-api.js";
71
71
  import { useAthenaComputerSshAccess, useAthenaComputerStatus, useAthenaSessionWorkspace } from "./workbench/use-session-workspace.js";
72
72
  import { ComputerLiveDesktop, LIVE_DESKTOP_READY_TIMEOUT_MS } from "./workbench/ComputerLiveDesktop.js";
@@ -112,7 +112,7 @@ import { ComposerQuotePreview } from "./composer/ComposerQuotePreview.js";
112
112
  import { QuoteProvider, createQuoteStore, useQuote } from "./composer/QuoteContext.js";
113
113
  import { AttachmentProvider, createAttachmentStore, useAttachments } from "./composer/AttachmentContext.js";
114
114
  import { useParentAuth, useParentBridge } from "./runtime/auth.js";
115
- import { DEFAULT_APP_URL, DEFAULT_STATEWIRE_SYNC_URL, createAthenaSpacesUrl, deriveAthenaApiUrl, deriveAthenaAppUrl, deriveStatewireSyncUrl, resolveStatewireSyncUrl } from "./lib/athena-urls.js";
115
+ import { DEFAULT_APP_URL, DEFAULT_STATEWIRE_SYNC_URL, athenaLinkAppUrl, createAthenaSpacesUrl, deriveAthenaApiUrl, deriveAthenaAppUrl, deriveStatewireSyncUrl, resolveStatewireSyncUrl } from "./lib/athena-urls.js";
116
116
  import { Button, buttonVariants } from "./ui/button.js";
117
117
  import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "./ui/tooltip.js";
118
118
  import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "./ui/collapsible.js";
@@ -181,10 +181,27 @@ function createAthenaSpacesUrl({
181
181
  }
182
182
  return url.toString();
183
183
  }
184
+ function originOf(url) {
185
+ try {
186
+ return new URL(url).origin;
187
+ } catch {
188
+ return null;
189
+ }
190
+ }
191
+ function athenaLinkAppUrl({
192
+ appUrl,
193
+ backendUrl,
194
+ pageOrigin
195
+ }) {
196
+ const deployed = deriveAthenaAppUrl({ backendUrl });
197
+ if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;
198
+ return appUrl;
199
+ }
184
200
  exports.DEFAULT_APP_URL = DEFAULT_APP_URL;
185
201
  exports.DEFAULT_ATHENA_ENVIRONMENT = DEFAULT_ATHENA_ENVIRONMENT;
186
202
  exports.DEFAULT_STATEWIRE_SYNC_URL = DEFAULT_STATEWIRE_SYNC_URL;
187
203
  exports.assertHostMintedStatewireMount = assertHostMintedStatewireMount;
204
+ exports.athenaLinkAppUrl = athenaLinkAppUrl;
188
205
  exports.createAthenaSpacesUrl = createAthenaSpacesUrl;
189
206
  exports.deriveAthenaApiUrl = deriveAthenaApiUrl;
190
207
  exports.deriveAthenaAppUrl = deriveAthenaAppUrl;
@@ -1 +1 @@
1
- {"version":3,"file":"athena-urls.cjs","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.cjs","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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"}
@@ -73,3 +73,17 @@ export declare function createAthenaSpacesUrl({ appUrl, assetIds, sessionId, }:
73
73
  assetIds?: string | string[];
74
74
  sessionId?: string;
75
75
  }): string;
76
+ /**
77
+ * The Athena frontend an "Open in Athena" link points at. The provider's
78
+ * `appUrl` is that frontend, except in an app a computer serves: Marathon's
79
+ * `/_athena/auth` mints `appUrl` as the app's OWN origin, so a link built on
80
+ * it opens the app instead of Athena. When `appUrl` is the page's own origin
81
+ * and the Agora base belongs to a known Athena deployment, the deployment's
82
+ * frontend wins (on Olympus itself the two are the same origin anyway). The
83
+ * workbench's "Open in Athena" links and the chat's asset chips both go by it.
84
+ */
85
+ export declare function athenaLinkAppUrl({ appUrl, backendUrl, pageOrigin, }: {
86
+ appUrl: string;
87
+ backendUrl: string;
88
+ pageOrigin: string | null;
89
+ }): string;
@@ -179,11 +179,28 @@ function createAthenaSpacesUrl({
179
179
  }
180
180
  return url.toString();
181
181
  }
182
+ function originOf(url) {
183
+ try {
184
+ return new URL(url).origin;
185
+ } catch {
186
+ return null;
187
+ }
188
+ }
189
+ function athenaLinkAppUrl({
190
+ appUrl,
191
+ backendUrl,
192
+ pageOrigin
193
+ }) {
194
+ const deployed = deriveAthenaAppUrl({ backendUrl });
195
+ if (deployed && pageOrigin !== null && originOf(appUrl) === pageOrigin) return deployed;
196
+ return appUrl;
197
+ }
182
198
  export {
183
199
  DEFAULT_APP_URL,
184
200
  DEFAULT_ATHENA_ENVIRONMENT,
185
201
  DEFAULT_STATEWIRE_SYNC_URL,
186
202
  assertHostMintedStatewireMount,
203
+ athenaLinkAppUrl,
187
204
  createAthenaSpacesUrl,
188
205
  deriveAthenaApiUrl,
189
206
  deriveAthenaAppUrl,
@@ -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.1";
4
4
  const packageJson = {
5
5
  version
6
6
  };
@@ -1,4 +1,4 @@
1
- const version = "0.41.0";
1
+ const version = "0.41.1";
2
2
  const packageJson = {
3
3
  version
4
4
  };
@@ -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,