@athenaintel/react 0.59.0 → 0.60.0
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 +52 -19
- package/dist/agent-runtime/src/composer-markdown.cjs +0 -16
- package/dist/agent-runtime/src/composer-markdown.cjs.map +1 -1
- package/dist/agent-runtime/src/composer-markdown.js +1 -17
- package/dist/agent-runtime/src/composer-markdown.js.map +1 -1
- package/dist/agent-runtime/src/named-agents.js +1 -1
- package/dist/agent-runtime/src/offline-status.cjs.map +1 -1
- package/dist/agent-runtime/src/offline-status.d.ts +27 -0
- package/dist/agent-runtime/src/offline-status.js.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.cjs +7 -0
- package/dist/agent-runtime/src/tool-metadata.cjs.map +1 -1
- package/dist/agent-runtime/src/tool-metadata.js +7 -0
- package/dist/agent-runtime/src/tool-metadata.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +15 -5
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.js +16 -6
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat-ui/index.cjs +2 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +1 -0
- package/dist/chat-ui/index.js +2 -0
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.cjs +9 -3
- package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
- package/dist/chat-ui/mentions/mention-chip.js +9 -3
- package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.cjs +18 -2
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.d.ts +10 -1
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.js +19 -3
- package/dist/chat-ui/mentions/suggestions/components/mention-icon.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +9 -0
- package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +6 -0
- package/dist/chat-ui/mentions/suggestions/data/api-client.js +9 -0
- package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.cjs +89 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.cjs.map +1 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.d.ts +8 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.js +89 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icon-names.generated.js.map +1 -0
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs +140 -116
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.d.ts +46 -12
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js +141 -117
- package/dist/chat-ui/mentions/suggestions/data/sources/asset-type-icons.js.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs +37 -6
- package/dist/chat-ui/mentions/suggestions/data/sources/index.cjs.map +1 -1
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js +39 -8
- package/dist/chat-ui/mentions/suggestions/data/sources/index.js.map +1 -1
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs +12 -0
- package/dist/chat-ui/mentions/use-asset-icon-app-url.cjs.map +1 -0
- package/dist/chat-ui/mentions/use-asset-icon-app-url.d.ts +8 -0
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js +12 -0
- package/dist/chat-ui/mentions/use-asset-icon-app-url.js.map +1 -0
- package/dist/collab/client.cjs +24 -1
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +16 -0
- package/dist/collab/client.js +24 -1
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/attached-references.js +1 -1
- package/dist/composer/staged-message-metadata.cjs +12 -0
- package/dist/composer/staged-message-metadata.cjs.map +1 -0
- package/dist/composer/staged-message-metadata.d.ts +15 -0
- package/dist/composer/staged-message-metadata.js +12 -0
- package/dist/composer/staged-message-metadata.js.map +1 -0
- package/dist/data/athena-request.cjs +2 -0
- package/dist/data/athena-request.cjs.map +1 -1
- package/dist/data/athena-request.js +2 -0
- package/dist/data/athena-request.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -3
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +4 -3
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/references/src/anchors/parse.cjs +11 -1
- package/dist/references/src/anchors/parse.cjs.map +1 -1
- package/dist/references/src/anchors/parse.js +11 -1
- package/dist/references/src/anchors/parse.js.map +1 -1
- package/dist/references/src/anchors/schemas.d.ts +1 -0
- package/dist/references/src/anchors/serialize.cjs +1 -0
- package/dist/references/src/anchors/serialize.cjs.map +1 -1
- package/dist/references/src/anchors/serialize.js +1 -0
- package/dist/references/src/anchors/serialize.js.map +1 -1
- package/dist/references/src/anchors/types.cjs.map +1 -1
- package/dist/references/src/anchors/types.d.ts +9 -0
- package/dist/references/src/anchors/types.js.map +1 -1
- package/dist/references/src/schemas.d.ts +2 -0
- package/dist/runtime/useAthenaStatewireRuntime.cjs +6 -8
- package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
- package/dist/runtime/useAthenaStatewireRuntime.js +6 -8
- package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +14 -2
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +11 -2
- package/dist/threads/statewire-thread-list.js +14 -2
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/tools/drive-tool-uis.cjs +8 -4
- package/dist/tools/drive-tool-uis.cjs.map +1 -1
- package/dist/tools/drive-tool-uis.d.ts +5 -4
- package/dist/tools/drive-tool-uis.js +9 -5
- package/dist/tools/drive-tool-uis.js.map +1 -1
- package/dist/version.cjs +4 -0
- package/dist/version.cjs.map +1 -1
- package/dist/version.d.ts +9 -0
- package/dist/version.js +4 -0
- package/dist/version.js.map +1 -1
- package/dist/workbench/live-desktop-api.cjs +2 -1
- package/dist/workbench/live-desktop-api.cjs.map +1 -1
- package/dist/workbench/live-desktop-api.js +2 -1
- package/dist/workbench/live-desktop-api.js.map +1 -1
- package/dist/workbench/session-workspace-api.cjs +7 -2
- package/dist/workbench/session-workspace-api.cjs.map +1 -1
- package/dist/workbench/session-workspace-api.d.ts +10 -0
- package/dist/workbench/session-workspace-api.js +7 -2
- package/dist/workbench/session-workspace-api.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"live-desktop-api.js","sources":["../../src/workbench/live-desktop-api.ts"],"sourcesContent":["/**\n * A computer's live desktop, as an app the computer serves on Athena embeds it.\n *\n * `POST /api/sandbox/computer/{asset_id}/live-desktop/session` (agora\n * `web/api/computer/live_desktop.py`) is the gate: VIEW with the workspace\n * fence, the computer in the caller's current workspace, EDIT and the\n * computer role policy, for watching as for control. It answers the viewer\n * page's path (carrying a 60 s single-use ticket), or why there is nothing to\n * show. The session names the page that will frame the viewer\n * (`embed_origin`, this page's own origin): agora accepts only an Athena app\n * the caller may open in the computer's workspace, and binds the viewer's\n * `frame-ancestors` and messages to it.\n *\n * The viewer page is Selkies' own web client, served by agora from the guest.\n * Whoever controls the computer controls that page, so its messages are hints\n * for the UI (connected, failed, closed) and never trusted beyond that.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\nexport const LiveDesktopMode = { WATCH: 'watch', CONTROL: 'control' } as const;\nexport type LiveDesktopMode = (typeof LiveDesktopMode)[keyof typeof LiveDesktopMode];\n\n/** What the session route answered. */\nexport type LiveDesktopSession =\n | {\n status: 'ready';\n /** A path on agora's own origin, carrying the single-use ticket. */\n viewerPath: string;\n mode: LiveDesktopMode;\n /** The remote screen, when known: what the frame keeps the aspect of. */\n display: { width: number; height: number } | null;\n }\n | { status: 'display_not_running'; detail: string }\n | { status: 'unsupported'; detail: string };\n\nexport function computerLiveDesktopSessionPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/live-desktop/session`;\n}\n\nfunction readDisplay(value: unknown): { width: number; height: number } | null {\n if (!isRecord(value)) return null;\n const { width, height } = value;\n return typeof width === 'number' && width > 0 && typeof height === 'number' && height > 0\n ? { width, height }\n : null;\n}\n\n/** The route's body as a session, or `null` for a shape the pane cannot use. */\nexport function readLiveDesktopSession(body: unknown): LiveDesktopSession | null {\n if (!isRecord(body)) return null;\n const detail = typeof body.detail === 'string' ? body.detail : '';\n if (body.status === 'display_not_running') return { status: 'display_not_running', detail };\n if (body.status === 'unsupported') return { status: 'unsupported', detail };\n if (body.status !== 'ready') return null;\n const { viewer_path: viewerPath, mode } = body;\n // Only a path on agora's own origin, never a protocol-relative URL.\n if (typeof viewerPath !== 'string' || !/^\\/(?!\\/)/.test(viewerPath)) return null;\n if (mode !== LiveDesktopMode.WATCH && mode !== LiveDesktopMode.CONTROL) return null;\n return { status: 'ready', viewerPath, mode, display: readDisplay(body.display) };\n}\n\n/**\n * The viewer page's URL on agora's origin. Throws when *viewerPath* would\n * leave that origin: the frame is given agora's page or nothing.\n */\nexport function liveDesktopViewerUrl({\n backendUrl,\n viewerPath,\n}: {\n backendUrl: string;\n viewerPath: string;\n}): string {\n const base = new URL(`${getAthenaApiBaseUrl(backendUrl)}/`);\n const url = new URL(viewerPath, base);\n if (url.origin !== base.origin) {\n throw new Error('The live desktop viewer must be served by the Athena API');\n }\n return url.toString();\n}\n\nfunction refusalDetail(body: unknown): string | null {\n if (!isRecord(body)) return null;\n return typeof body.detail === 'string' && body.detail ? body.detail : null;\n}\n\n/** Ask for a viewer of the computer's desktop. Every failure is an `AthenaSdkError`. */\nexport async function createComputerLiveDesktopSession({\n backendUrl,\n auth,\n assetId,\n mode,\n start,\n embedOrigin,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n mode: LiveDesktopMode;\n /** Start the computer's display when it is down (this also opens its browser). */\n start: boolean;\n /** The origin of the page that will frame the viewer (this page's). */\n embedOrigin: string | null;\n signal?: AbortSignal;\n}): Promise<LiveDesktopSession> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${computerLiveDesktopSessionPath(assetId)}`;\n const body = JSON.stringify({\n start,\n mode,\n ...(embedOrigin ? { embed_origin: embedOrigin } : {}),\n });\n const send = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body,\n signal: signal ?? null,\n });\n } catch (error) {\n if ((error instanceof DOMException || error instanceof Error) && error.name === 'AbortError') {\n throw error;\n }\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop could not be reached.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, send);\n const parsed: unknown = await response.json().catch(() => undefined);\n if (!response.ok) {\n throw new AthenaSdkError({\n code: response.status === 401 ? 'auth_rejected' : 'live_desktop_failed',\n message:\n refusalDetail(parsed) ?? `Opening the computer’s desktop failed (${response.status}).`,\n status: response.status,\n detail: parsed,\n hint:\n response.status === 403\n ? 'Watching a computer’s desktop needs edit access to it, in its own workspace.'\n : undefined,\n });\n }\n const session = readLiveDesktopSession(parsed);\n if (!session) {\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop answered in a shape this app does not understand.',\n status: response.status,\n detail: parsed,\n });\n }\n return session;\n}\n\n/** Names the viewer page's messages (`LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE` in agora). */\nexport const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = 'athena-live-desktop';\n\n/** The viewer page's messages the pane acts on; the rest are ignored. */\nexport type LiveDesktopViewerMessage =\n | { status: 'ready' }\n | { status: 'error'; detail: string }\n | { status: 'open' }\n | { status: 'closed'; code: number; reason: string };\n\nexport function readLiveDesktopViewerMessage(data: unknown): LiveDesktopViewerMessage | null {\n if (!isRecord(data) || data.source !== LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE) return null;\n switch (data.status) {\n case 'ready':\n return { status: 'ready' };\n case 'open':\n return { status: 'open' };\n case 'error':\n return typeof data.detail === 'string'\n ? { status: 'error', detail: data.detail.slice(0, 300) }\n : null;\n case 'closed':\n return typeof data.code === 'number'\n ? {\n status: 'closed',\n code: data.code,\n reason: typeof data.reason === 'string' ? data.reason.slice(0, 120) : '',\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Close codes agora's relay ends a view with for good\n * (`LIVE_DESKTOP_CLOSE_*` in agora `live_desktop.py`): the view is over and\n * the pane says why. Any other close is a drop Selkies re-dials by itself.\n */\nexport function finalLiveDesktopCloseNotice(code: number): string | null {\n switch (code) {\n case 4001:\n return 'The desktop session expired. Reconnect to start a new one.';\n case 4003:\n return 'You no longer have access to this computer’s desktop.';\n case 4008:\n return 'The desktop session reached its time limit, so the computer can go idle. Reconnect to keep watching.';\n default:\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"live-desktop-api.js","sources":["../../src/workbench/live-desktop-api.ts"],"sourcesContent":["/**\n * A computer's live desktop, as an app the computer serves on Athena embeds it.\n *\n * `POST /api/sandbox/computer/{asset_id}/live-desktop/session` (agora\n * `web/api/computer/live_desktop.py`) is the gate: VIEW with the workspace\n * fence, the computer in the caller's current workspace, EDIT and the\n * computer role policy, for watching as for control. It answers the viewer\n * page's path (carrying a 60 s single-use ticket), or why there is nothing to\n * show. The session names the page that will frame the viewer\n * (`embed_origin`, this page's own origin): agora accepts only an Athena app\n * the caller may open in the computer's workspace, and binds the viewer's\n * `frame-ancestors` and messages to it.\n *\n * The viewer page is Selkies' own web client, served by agora from the guest.\n * Whoever controls the computer controls that page, so its messages are hints\n * for the UI (connected, failed, closed) and never trusted beyond that.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\nimport { isAssetMissingResponse } from './session-workspace-api';\n\nexport const LiveDesktopMode = { WATCH: 'watch', CONTROL: 'control' } as const;\nexport type LiveDesktopMode = (typeof LiveDesktopMode)[keyof typeof LiveDesktopMode];\n\n/** What the session route answered. */\nexport type LiveDesktopSession =\n | {\n status: 'ready';\n /** A path on agora's own origin, carrying the single-use ticket. */\n viewerPath: string;\n mode: LiveDesktopMode;\n /** The remote screen, when known: what the frame keeps the aspect of. */\n display: { width: number; height: number } | null;\n }\n | { status: 'display_not_running'; detail: string }\n | { status: 'unsupported'; detail: string };\n\nexport function computerLiveDesktopSessionPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/live-desktop/session`;\n}\n\nfunction readDisplay(value: unknown): { width: number; height: number } | null {\n if (!isRecord(value)) return null;\n const { width, height } = value;\n return typeof width === 'number' && width > 0 && typeof height === 'number' && height > 0\n ? { width, height }\n : null;\n}\n\n/** The route's body as a session, or `null` for a shape the pane cannot use. */\nexport function readLiveDesktopSession(body: unknown): LiveDesktopSession | null {\n if (!isRecord(body)) return null;\n const detail = typeof body.detail === 'string' ? body.detail : '';\n if (body.status === 'display_not_running') return { status: 'display_not_running', detail };\n if (body.status === 'unsupported') return { status: 'unsupported', detail };\n if (body.status !== 'ready') return null;\n const { viewer_path: viewerPath, mode } = body;\n // Only a path on agora's own origin, never a protocol-relative URL.\n if (typeof viewerPath !== 'string' || !/^\\/(?!\\/)/.test(viewerPath)) return null;\n if (mode !== LiveDesktopMode.WATCH && mode !== LiveDesktopMode.CONTROL) return null;\n return { status: 'ready', viewerPath, mode, display: readDisplay(body.display) };\n}\n\n/**\n * The viewer page's URL on agora's origin. Throws when *viewerPath* would\n * leave that origin: the frame is given agora's page or nothing.\n */\nexport function liveDesktopViewerUrl({\n backendUrl,\n viewerPath,\n}: {\n backendUrl: string;\n viewerPath: string;\n}): string {\n const base = new URL(`${getAthenaApiBaseUrl(backendUrl)}/`);\n const url = new URL(viewerPath, base);\n if (url.origin !== base.origin) {\n throw new Error('The live desktop viewer must be served by the Athena API');\n }\n return url.toString();\n}\n\nfunction refusalDetail(body: unknown): string | null {\n if (!isRecord(body)) return null;\n return typeof body.detail === 'string' && body.detail ? body.detail : null;\n}\n\n/** Ask for a viewer of the computer's desktop. Every failure is an `AthenaSdkError`. */\nexport async function createComputerLiveDesktopSession({\n backendUrl,\n auth,\n assetId,\n mode,\n start,\n embedOrigin,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n mode: LiveDesktopMode;\n /** Start the computer's display when it is down (this also opens its browser). */\n start: boolean;\n /** The origin of the page that will frame the viewer (this page's). */\n embedOrigin: string | null;\n signal?: AbortSignal;\n}): Promise<LiveDesktopSession> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${computerLiveDesktopSessionPath(assetId)}`;\n const body = JSON.stringify({\n start,\n mode,\n ...(embedOrigin ? { embed_origin: embedOrigin } : {}),\n });\n const send = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, {\n method: 'POST',\n headers: { ...headers, 'Content-Type': 'application/json' },\n body,\n signal: signal ?? null,\n });\n } catch (error) {\n if ((error instanceof DOMException || error instanceof Error) && error.name === 'AbortError') {\n throw error;\n }\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop could not be reached.',\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, send);\n const parsed: unknown = await response.json().catch(() => undefined);\n if (!response.ok) {\n throw new AthenaSdkError({\n code: response.status === 401 ? 'auth_rejected' : 'live_desktop_failed',\n message:\n refusalDetail(parsed) ?? `Opening the computer’s desktop failed (${response.status}).`,\n status: response.status,\n detail: parsed,\n hint:\n response.status === 403\n ? 'Watching a computer’s desktop needs edit access to it, in its own workspace.'\n : isAssetMissingResponse({ status: response.status, body: parsed })\n ? 'The computer does not exist, or the signed-in user may not open it.'\n : undefined,\n });\n }\n const session = readLiveDesktopSession(parsed);\n if (!session) {\n throw new AthenaSdkError({\n code: 'live_desktop_failed',\n message: 'The computer’s desktop answered in a shape this app does not understand.',\n status: response.status,\n detail: parsed,\n });\n }\n return session;\n}\n\n/** Names the viewer page's messages (`LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE` in agora). */\nexport const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = 'athena-live-desktop';\n\n/** The viewer page's messages the pane acts on; the rest are ignored. */\nexport type LiveDesktopViewerMessage =\n | { status: 'ready' }\n | { status: 'error'; detail: string }\n | { status: 'open' }\n | { status: 'closed'; code: number; reason: string };\n\nexport function readLiveDesktopViewerMessage(data: unknown): LiveDesktopViewerMessage | null {\n if (!isRecord(data) || data.source !== LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE) return null;\n switch (data.status) {\n case 'ready':\n return { status: 'ready' };\n case 'open':\n return { status: 'open' };\n case 'error':\n return typeof data.detail === 'string'\n ? { status: 'error', detail: data.detail.slice(0, 300) }\n : null;\n case 'closed':\n return typeof data.code === 'number'\n ? {\n status: 'closed',\n code: data.code,\n reason: typeof data.reason === 'string' ? data.reason.slice(0, 120) : '',\n }\n : null;\n default:\n return null;\n }\n}\n\n/**\n * Close codes agora's relay ends a view with for good\n * (`LIVE_DESKTOP_CLOSE_*` in agora `live_desktop.py`): the view is over and\n * the pane says why. Any other close is a drop Selkies re-dials by itself.\n */\nexport function finalLiveDesktopCloseNotice(code: number): string | null {\n switch (code) {\n case 4001:\n return 'The desktop session expired. Reconnect to start a new one.';\n case 4003:\n return 'You no longer have access to this computer’s desktop.';\n case 4008:\n return 'The desktop session reached its time limit, so the computer can go idle. Reconnect to keep watching.';\n default:\n return null;\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -13,6 +13,9 @@ function computerStatusPath(assetId) {
|
|
|
13
13
|
function computerSshAccessPath(assetId) {
|
|
14
14
|
return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;
|
|
15
15
|
}
|
|
16
|
+
function isAssetMissingResponse({ status, body }) {
|
|
17
|
+
return status === 404 && utils.isRecord(body) && typeof body.detail === "string" && /^Asset (\S+ )?not found$/.test(body.detail);
|
|
18
|
+
}
|
|
16
19
|
const WORKSPACE_ROLES = /* @__PURE__ */ new Set(["session_computer", "computer", "asset"]);
|
|
17
20
|
function readWorkspaceAsset(value) {
|
|
18
21
|
if (!utils.isRecord(value)) return null;
|
|
@@ -90,12 +93,13 @@ async function getAgoraJson({
|
|
|
90
93
|
const response = await credentialRenewal.sendWithCredentialRenewal(auth, get);
|
|
91
94
|
if (!response.ok) {
|
|
92
95
|
const refused = response.status === 401 || response.status === 403;
|
|
96
|
+
const detail = await response.json().catch(() => void 0);
|
|
93
97
|
throw new errors.AthenaSdkError({
|
|
94
98
|
code: refused ? "auth_rejected" : "session_workspace_failed",
|
|
95
99
|
message: `Reading ${what} failed: ${response.status}`,
|
|
96
100
|
status: response.status,
|
|
97
|
-
detail
|
|
98
|
-
hint: refused ? "The signed-in user cannot read this session or asset." : "Check that backendUrl points at an Agora that serves this route."
|
|
101
|
+
detail,
|
|
102
|
+
hint: refused ? "The signed-in user cannot read this session or asset." : isAssetMissingResponse({ status: response.status, body: detail }) ? "The asset does not exist, or the signed-in user may not read it." : "Check that backendUrl points at an Agora that serves this route."
|
|
99
103
|
});
|
|
100
104
|
}
|
|
101
105
|
return response.json();
|
|
@@ -150,6 +154,7 @@ exports.computerStatusPath = computerStatusPath;
|
|
|
150
154
|
exports.fetchComputerRuntimeStatus = fetchComputerRuntimeStatus;
|
|
151
155
|
exports.fetchComputerSshAccess = fetchComputerSshAccess;
|
|
152
156
|
exports.fetchSessionWorkspace = fetchSessionWorkspace;
|
|
157
|
+
exports.isAssetMissingResponse = isAssetMissingResponse;
|
|
153
158
|
exports.readComputerRuntimeStatus = readComputerRuntimeStatus;
|
|
154
159
|
exports.readComputerSshAccess = readComputerSshAccess;
|
|
155
160
|
exports.readSessionWorkspace = readSessionWorkspace;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-workspace-api.cjs","sources":["../../src/workbench/session-workspace-api.ts"],"sourcesContent":["/**\n * The Agora reads behind the session workbench, each one an existing,\n * permission-gated route:\n *\n * - `GET /api/conversations/threads/{thread_id}/workspace` — the computers and\n * assets a session works with (agora `web/api/session/workspace.py`; thread\n * access, then a narrow per-asset permission check, so it names only assets\n * the caller may open).\n * - `GET /api/sandbox/computer/{asset_id}/status` — a computer's runtime state\n * (asset VIEW; reads the runtime, never wakes it).\n * - `GET /api/v0/computer/{asset_id}/ssh-access` — the ready-made `ssh`\n * command (asset EDIT; mints nothing, wakes nothing).\n *\n * The credential rides like every SDK REST call: `Authorization: Bearer`\n * (resolved live through `getToken`, with one forced re-mint after a 401) or\n * `X-API-KEY`, else a same-origin host's cookie. Every failure is an\n * `AthenaSdkError` (`session_workspace_failed`, or `auth_rejected` for a\n * 401/403); an abort keeps its own identity.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** Why an asset is in a session's workspace. */\nexport type SessionWorkspaceRole = 'session_computer' | 'computer' | 'asset';\n\n/** One computer or asset a session works with. */\nexport interface SessionWorkspaceAsset {\n assetId: string;\n title: string | null;\n /** The asset's type (`computer`, `spreadsheet`, `document`, …). */\n assetType: string;\n /** ISO 8601 time of the asset's last update. */\n updatedAt: string;\n role: SessionWorkspaceRole;\n}\n\n/** What a session works with, as the caller may see it. */\nexport interface SessionWorkspace {\n threadId: string;\n /** The computer bound to the session, when it has one the caller may open. */\n sessionComputerAssetId: string | null;\n /** Session computer first, then the computers the session touched, most recent first. */\n computers: SessionWorkspaceAsset[];\n /** The assets the session created or edited, most recently touched first. */\n assets: SessionWorkspaceAsset[];\n}\n\n/** A computer's runtime state as `/status` reports it. */\nexport interface ComputerRuntimeStatus {\n /** `started`, `stopped`, `restoring`, `unknown`, … — the provider's word. */\n state: string;\n /** `stopping` / `stopped` while someone asked it to stop. */\n explicitStop: string | null;\n provider: string | null;\n}\n\n/** The SSH target of a computer (identity mode: a registered key logs in). */\nexport interface ComputerSshAccess {\n /** Ready to run: `ssh [-p <port>] <asset_id>@<host>`. */\n command: string;\n host: string;\n port: number;\n username: string;\n /** False where the gateway accepts temporary tokens only (the command is refused there). */\n keyAuthEnabled: boolean;\n}\n\nexport function sessionWorkspacePath(threadId: string): string {\n return `/api/conversations/threads/${encodeURIComponent(threadId)}/workspace`;\n}\n\nexport function computerStatusPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/status`;\n}\n\nexport function computerSshAccessPath(assetId: string): string {\n return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;\n}\n\nconst WORKSPACE_ROLES: ReadonlySet<string> = new Set(['session_computer', 'computer', 'asset']);\n\nfunction readWorkspaceAsset(value: unknown): SessionWorkspaceAsset | null {\n if (!isRecord(value)) return null;\n const { asset_id, title, asset_type, updated_at, role } = value;\n if (typeof asset_id !== 'string' || asset_id.length === 0) return null;\n return {\n assetId: asset_id,\n title: typeof title === 'string' && title.trim().length > 0 ? title : null,\n assetType: typeof asset_type === 'string' ? asset_type : '',\n updatedAt: typeof updated_at === 'string' ? updated_at : '',\n role:\n typeof role === 'string' && WORKSPACE_ROLES.has(role)\n ? (role as SessionWorkspaceRole)\n : 'asset',\n };\n}\n\nfunction readWorkspaceAssets(value: unknown): SessionWorkspaceAsset[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n const asset = readWorkspaceAsset(entry);\n return asset ? [asset] : [];\n });\n}\n\n/**\n * The route's snake_case body as the SDK's camelCase shape. Tolerant: a\n * malformed entry is dropped rather than failing the whole pane, and a body\n * that is not an object reads as an empty workspace for `threadId`.\n */\nexport function readSessionWorkspace(body: unknown, threadId: string): SessionWorkspace {\n if (!isRecord(body)) {\n return { threadId, sessionComputerAssetId: null, computers: [], assets: [] };\n }\n const sessionComputer = body.session_computer_asset_id;\n return {\n threadId: typeof body.thread_id === 'string' && body.thread_id ? body.thread_id : threadId,\n sessionComputerAssetId:\n typeof sessionComputer === 'string' && sessionComputer ? sessionComputer : null,\n computers: readWorkspaceAssets(body.computers),\n assets: readWorkspaceAssets(body.assets),\n };\n}\n\nexport function readComputerRuntimeStatus(body: unknown): ComputerRuntimeStatus | null {\n if (!isRecord(body) || typeof body.state !== 'string' || !body.state) return null;\n return {\n state: body.state,\n explicitStop: typeof body.explicit_stop === 'string' ? body.explicit_stop : null,\n provider: typeof body.provider === 'string' ? body.provider : null,\n };\n}\n\nexport function readComputerSshAccess(body: unknown): ComputerSshAccess | null {\n if (!isRecord(body) || typeof body.command !== 'string' || !body.command) return null;\n return {\n command: body.command,\n host: typeof body.host === 'string' ? body.host : '',\n port: typeof body.port === 'number' ? body.port : 22,\n username: typeof body.username === 'string' ? body.username : '',\n keyAuthEnabled: body.key_auth_enabled === true,\n };\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\nasync function getAgoraJson({\n backendUrl,\n auth,\n path,\n what,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n path: string;\n /** Names the read in the error message (`the session workspace`). */\n what: string;\n signal?: AbortSignal;\n}): Promise<unknown> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${path}`;\n const get = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, { method: 'GET', headers, signal: signal ?? null });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'session_workspace_failed',\n message: `Reading ${what} could not reach the Agora API.`,\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, get);\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'session_workspace_failed',\n message: `Reading ${what} failed: ${response.status}`,\n status: response.status,\n detail: await response.json().catch(() => undefined),\n hint: refused\n ? 'The signed-in user cannot read this session or asset.'\n : 'Check that backendUrl points at an Agora that serves this route.',\n });\n }\n return response.json();\n}\n\nexport async function fetchSessionWorkspace({\n backendUrl,\n auth,\n threadId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n threadId: string;\n signal?: AbortSignal;\n}): Promise<SessionWorkspace> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: sessionWorkspacePath(threadId),\n what: 'the session workspace',\n signal,\n });\n return readSessionWorkspace(body, threadId);\n}\n\nexport async function fetchComputerRuntimeStatus({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerRuntimeStatus | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerStatusPath(assetId),\n what: 'the computer status',\n signal,\n });\n return readComputerRuntimeStatus(body);\n}\n\nexport async function fetchComputerSshAccess({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerSshAccess | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerSshAccessPath(assetId),\n what: 'the computer SSH target',\n signal,\n });\n return readComputerSshAccess(body);\n}\n"],"names":["isRecord","getAthenaApiBaseUrl","AthenaSdkError","sendWithCredentialRenewal"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-workspace-api.cjs","sources":["../../src/workbench/session-workspace-api.ts"],"sourcesContent":["/**\n * The Agora reads behind the session workbench, each one an existing,\n * permission-gated route:\n *\n * - `GET /api/conversations/threads/{thread_id}/workspace` — the computers and\n * assets a session works with (agora `web/api/session/workspace.py`; thread\n * access, then a narrow per-asset permission check, so it names only assets\n * the caller may open).\n * - `GET /api/sandbox/computer/{asset_id}/status` — a computer's runtime state\n * (asset VIEW; reads the runtime, never wakes it).\n * - `GET /api/v0/computer/{asset_id}/ssh-access` — the ready-made `ssh`\n * command (asset EDIT; mints nothing, wakes nothing).\n *\n * The credential rides like every SDK REST call: `Authorization: Bearer`\n * (resolved live through `getToken`, with one forced re-mint after a 401) or\n * `X-API-KEY`, else a same-origin host's cookie. Every failure is an\n * `AthenaSdkError` (`session_workspace_failed`, or `auth_rejected` for a\n * 401/403); an abort keeps its own identity. A computer or asset the caller may\n * not learn of answers like one that does not exist (404 \"Asset <id> not\n * found\", gate finding F19): `session_workspace_failed`, with a hint that says so.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** Why an asset is in a session's workspace. */\nexport type SessionWorkspaceRole = 'session_computer' | 'computer' | 'asset';\n\n/** One computer or asset a session works with. */\nexport interface SessionWorkspaceAsset {\n assetId: string;\n title: string | null;\n /** The asset's type (`computer`, `spreadsheet`, `document`, …). */\n assetType: string;\n /** ISO 8601 time of the asset's last update. */\n updatedAt: string;\n role: SessionWorkspaceRole;\n}\n\n/** What a session works with, as the caller may see it. */\nexport interface SessionWorkspace {\n threadId: string;\n /** The computer bound to the session, when it has one the caller may open. */\n sessionComputerAssetId: string | null;\n /** Session computer first, then the computers the session touched, most recent first. */\n computers: SessionWorkspaceAsset[];\n /** The assets the session created or edited, most recently touched first. */\n assets: SessionWorkspaceAsset[];\n}\n\n/** A computer's runtime state as `/status` reports it. */\nexport interface ComputerRuntimeStatus {\n /** `started`, `stopped`, `restoring`, `unknown`, … — the provider's word. */\n state: string;\n /** `stopping` / `stopped` while someone asked it to stop. */\n explicitStop: string | null;\n provider: string | null;\n}\n\n/** The SSH target of a computer (identity mode: a registered key logs in). */\nexport interface ComputerSshAccess {\n /** Ready to run: `ssh [-p <port>] <asset_id>@<host>`. */\n command: string;\n host: string;\n port: number;\n username: string;\n /** False where the gateway accepts temporary tokens only (the command is refused there). */\n keyAuthEnabled: boolean;\n}\n\nexport function sessionWorkspacePath(threadId: string): string {\n return `/api/conversations/threads/${encodeURIComponent(threadId)}/workspace`;\n}\n\nexport function computerStatusPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/status`;\n}\n\nexport function computerSshAccessPath(assetId: string): string {\n return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;\n}\n\n/**\n * agora's 404 for an asset that does not exist or that the caller may not learn\n * of: `detail` \"Asset <id> not found\". Since gate finding F19 every refusal of a\n * caller outside the asset's workspace reads exactly like this; a refused member\n * of that workspace gets 403.\n */\nexport function isAssetMissingResponse({ status, body }: { status: number; body: unknown }): boolean {\n return (\n status === 404 &&\n isRecord(body) &&\n typeof body.detail === 'string' &&\n /^Asset (\\S+ )?not found$/.test(body.detail)\n );\n}\n\nconst WORKSPACE_ROLES: ReadonlySet<string> = new Set(['session_computer', 'computer', 'asset']);\n\nfunction readWorkspaceAsset(value: unknown): SessionWorkspaceAsset | null {\n if (!isRecord(value)) return null;\n const { asset_id, title, asset_type, updated_at, role } = value;\n if (typeof asset_id !== 'string' || asset_id.length === 0) return null;\n return {\n assetId: asset_id,\n title: typeof title === 'string' && title.trim().length > 0 ? title : null,\n assetType: typeof asset_type === 'string' ? asset_type : '',\n updatedAt: typeof updated_at === 'string' ? updated_at : '',\n role:\n typeof role === 'string' && WORKSPACE_ROLES.has(role)\n ? (role as SessionWorkspaceRole)\n : 'asset',\n };\n}\n\nfunction readWorkspaceAssets(value: unknown): SessionWorkspaceAsset[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n const asset = readWorkspaceAsset(entry);\n return asset ? [asset] : [];\n });\n}\n\n/**\n * The route's snake_case body as the SDK's camelCase shape. Tolerant: a\n * malformed entry is dropped rather than failing the whole pane, and a body\n * that is not an object reads as an empty workspace for `threadId`.\n */\nexport function readSessionWorkspace(body: unknown, threadId: string): SessionWorkspace {\n if (!isRecord(body)) {\n return { threadId, sessionComputerAssetId: null, computers: [], assets: [] };\n }\n const sessionComputer = body.session_computer_asset_id;\n return {\n threadId: typeof body.thread_id === 'string' && body.thread_id ? body.thread_id : threadId,\n sessionComputerAssetId:\n typeof sessionComputer === 'string' && sessionComputer ? sessionComputer : null,\n computers: readWorkspaceAssets(body.computers),\n assets: readWorkspaceAssets(body.assets),\n };\n}\n\nexport function readComputerRuntimeStatus(body: unknown): ComputerRuntimeStatus | null {\n if (!isRecord(body) || typeof body.state !== 'string' || !body.state) return null;\n return {\n state: body.state,\n explicitStop: typeof body.explicit_stop === 'string' ? body.explicit_stop : null,\n provider: typeof body.provider === 'string' ? body.provider : null,\n };\n}\n\nexport function readComputerSshAccess(body: unknown): ComputerSshAccess | null {\n if (!isRecord(body) || typeof body.command !== 'string' || !body.command) return null;\n return {\n command: body.command,\n host: typeof body.host === 'string' ? body.host : '',\n port: typeof body.port === 'number' ? body.port : 22,\n username: typeof body.username === 'string' ? body.username : '',\n keyAuthEnabled: body.key_auth_enabled === true,\n };\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\nasync function getAgoraJson({\n backendUrl,\n auth,\n path,\n what,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n path: string;\n /** Names the read in the error message (`the session workspace`). */\n what: string;\n signal?: AbortSignal;\n}): Promise<unknown> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${path}`;\n const get = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, { method: 'GET', headers, signal: signal ?? null });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'session_workspace_failed',\n message: `Reading ${what} could not reach the Agora API.`,\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, get);\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n const detail: unknown = await response.json().catch(() => undefined);\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'session_workspace_failed',\n message: `Reading ${what} failed: ${response.status}`,\n status: response.status,\n detail,\n hint: refused\n ? 'The signed-in user cannot read this session or asset.'\n : isAssetMissingResponse({ status: response.status, body: detail })\n ? 'The asset does not exist, or the signed-in user may not read it.'\n : 'Check that backendUrl points at an Agora that serves this route.',\n });\n }\n return response.json();\n}\n\nexport async function fetchSessionWorkspace({\n backendUrl,\n auth,\n threadId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n threadId: string;\n signal?: AbortSignal;\n}): Promise<SessionWorkspace> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: sessionWorkspacePath(threadId),\n what: 'the session workspace',\n signal,\n });\n return readSessionWorkspace(body, threadId);\n}\n\nexport async function fetchComputerRuntimeStatus({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerRuntimeStatus | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerStatusPath(assetId),\n what: 'the computer status',\n signal,\n });\n return readComputerRuntimeStatus(body);\n}\n\nexport async function fetchComputerSshAccess({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerSshAccess | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerSshAccessPath(assetId),\n what: 'the computer SSH target',\n signal,\n });\n return readComputerSshAccess(body);\n}\n"],"names":["isRecord","getAthenaApiBaseUrl","AthenaSdkError","sendWithCredentialRenewal"],"mappings":"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"}
|
|
@@ -42,6 +42,16 @@ export interface ComputerSshAccess {
|
|
|
42
42
|
export declare function sessionWorkspacePath(threadId: string): string;
|
|
43
43
|
export declare function computerStatusPath(assetId: string): string;
|
|
44
44
|
export declare function computerSshAccessPath(assetId: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* agora's 404 for an asset that does not exist or that the caller may not learn
|
|
47
|
+
* of: `detail` "Asset <id> not found". Since gate finding F19 every refusal of a
|
|
48
|
+
* caller outside the asset's workspace reads exactly like this; a refused member
|
|
49
|
+
* of that workspace gets 403.
|
|
50
|
+
*/
|
|
51
|
+
export declare function isAssetMissingResponse({ status, body }: {
|
|
52
|
+
status: number;
|
|
53
|
+
body: unknown;
|
|
54
|
+
}): boolean;
|
|
45
55
|
/**
|
|
46
56
|
* The route's snake_case body as the SDK's camelCase shape. Tolerant: a
|
|
47
57
|
* malformed entry is dropped rather than failing the whole pane, and a body
|
|
@@ -11,6 +11,9 @@ function computerStatusPath(assetId) {
|
|
|
11
11
|
function computerSshAccessPath(assetId) {
|
|
12
12
|
return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;
|
|
13
13
|
}
|
|
14
|
+
function isAssetMissingResponse({ status, body }) {
|
|
15
|
+
return status === 404 && isRecord(body) && typeof body.detail === "string" && /^Asset (\S+ )?not found$/.test(body.detail);
|
|
16
|
+
}
|
|
14
17
|
const WORKSPACE_ROLES = /* @__PURE__ */ new Set(["session_computer", "computer", "asset"]);
|
|
15
18
|
function readWorkspaceAsset(value) {
|
|
16
19
|
if (!isRecord(value)) return null;
|
|
@@ -88,12 +91,13 @@ async function getAgoraJson({
|
|
|
88
91
|
const response = await sendWithCredentialRenewal(auth, get);
|
|
89
92
|
if (!response.ok) {
|
|
90
93
|
const refused = response.status === 401 || response.status === 403;
|
|
94
|
+
const detail = await response.json().catch(() => void 0);
|
|
91
95
|
throw new AthenaSdkError({
|
|
92
96
|
code: refused ? "auth_rejected" : "session_workspace_failed",
|
|
93
97
|
message: `Reading ${what} failed: ${response.status}`,
|
|
94
98
|
status: response.status,
|
|
95
|
-
detail
|
|
96
|
-
hint: refused ? "The signed-in user cannot read this session or asset." : "Check that backendUrl points at an Agora that serves this route."
|
|
99
|
+
detail,
|
|
100
|
+
hint: refused ? "The signed-in user cannot read this session or asset." : isAssetMissingResponse({ status: response.status, body: detail }) ? "The asset does not exist, or the signed-in user may not read it." : "Check that backendUrl points at an Agora that serves this route."
|
|
97
101
|
});
|
|
98
102
|
}
|
|
99
103
|
return response.json();
|
|
@@ -149,6 +153,7 @@ export {
|
|
|
149
153
|
fetchComputerRuntimeStatus,
|
|
150
154
|
fetchComputerSshAccess,
|
|
151
155
|
fetchSessionWorkspace,
|
|
156
|
+
isAssetMissingResponse,
|
|
152
157
|
readComputerRuntimeStatus,
|
|
153
158
|
readComputerSshAccess,
|
|
154
159
|
readSessionWorkspace,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-workspace-api.js","sources":["../../src/workbench/session-workspace-api.ts"],"sourcesContent":["/**\n * The Agora reads behind the session workbench, each one an existing,\n * permission-gated route:\n *\n * - `GET /api/conversations/threads/{thread_id}/workspace` — the computers and\n * assets a session works with (agora `web/api/session/workspace.py`; thread\n * access, then a narrow per-asset permission check, so it names only assets\n * the caller may open).\n * - `GET /api/sandbox/computer/{asset_id}/status` — a computer's runtime state\n * (asset VIEW; reads the runtime, never wakes it).\n * - `GET /api/v0/computer/{asset_id}/ssh-access` — the ready-made `ssh`\n * command (asset EDIT; mints nothing, wakes nothing).\n *\n * The credential rides like every SDK REST call: `Authorization: Bearer`\n * (resolved live through `getToken`, with one forced re-mint after a 401) or\n * `X-API-KEY`, else a same-origin host's cookie. Every failure is an\n * `AthenaSdkError` (`session_workspace_failed`, or `auth_rejected` for a\n * 401/403); an abort keeps its own identity.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** Why an asset is in a session's workspace. */\nexport type SessionWorkspaceRole = 'session_computer' | 'computer' | 'asset';\n\n/** One computer or asset a session works with. */\nexport interface SessionWorkspaceAsset {\n assetId: string;\n title: string | null;\n /** The asset's type (`computer`, `spreadsheet`, `document`, …). */\n assetType: string;\n /** ISO 8601 time of the asset's last update. */\n updatedAt: string;\n role: SessionWorkspaceRole;\n}\n\n/** What a session works with, as the caller may see it. */\nexport interface SessionWorkspace {\n threadId: string;\n /** The computer bound to the session, when it has one the caller may open. */\n sessionComputerAssetId: string | null;\n /** Session computer first, then the computers the session touched, most recent first. */\n computers: SessionWorkspaceAsset[];\n /** The assets the session created or edited, most recently touched first. */\n assets: SessionWorkspaceAsset[];\n}\n\n/** A computer's runtime state as `/status` reports it. */\nexport interface ComputerRuntimeStatus {\n /** `started`, `stopped`, `restoring`, `unknown`, … — the provider's word. */\n state: string;\n /** `stopping` / `stopped` while someone asked it to stop. */\n explicitStop: string | null;\n provider: string | null;\n}\n\n/** The SSH target of a computer (identity mode: a registered key logs in). */\nexport interface ComputerSshAccess {\n /** Ready to run: `ssh [-p <port>] <asset_id>@<host>`. */\n command: string;\n host: string;\n port: number;\n username: string;\n /** False where the gateway accepts temporary tokens only (the command is refused there). */\n keyAuthEnabled: boolean;\n}\n\nexport function sessionWorkspacePath(threadId: string): string {\n return `/api/conversations/threads/${encodeURIComponent(threadId)}/workspace`;\n}\n\nexport function computerStatusPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/status`;\n}\n\nexport function computerSshAccessPath(assetId: string): string {\n return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;\n}\n\nconst WORKSPACE_ROLES: ReadonlySet<string> = new Set(['session_computer', 'computer', 'asset']);\n\nfunction readWorkspaceAsset(value: unknown): SessionWorkspaceAsset | null {\n if (!isRecord(value)) return null;\n const { asset_id, title, asset_type, updated_at, role } = value;\n if (typeof asset_id !== 'string' || asset_id.length === 0) return null;\n return {\n assetId: asset_id,\n title: typeof title === 'string' && title.trim().length > 0 ? title : null,\n assetType: typeof asset_type === 'string' ? asset_type : '',\n updatedAt: typeof updated_at === 'string' ? updated_at : '',\n role:\n typeof role === 'string' && WORKSPACE_ROLES.has(role)\n ? (role as SessionWorkspaceRole)\n : 'asset',\n };\n}\n\nfunction readWorkspaceAssets(value: unknown): SessionWorkspaceAsset[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n const asset = readWorkspaceAsset(entry);\n return asset ? [asset] : [];\n });\n}\n\n/**\n * The route's snake_case body as the SDK's camelCase shape. Tolerant: a\n * malformed entry is dropped rather than failing the whole pane, and a body\n * that is not an object reads as an empty workspace for `threadId`.\n */\nexport function readSessionWorkspace(body: unknown, threadId: string): SessionWorkspace {\n if (!isRecord(body)) {\n return { threadId, sessionComputerAssetId: null, computers: [], assets: [] };\n }\n const sessionComputer = body.session_computer_asset_id;\n return {\n threadId: typeof body.thread_id === 'string' && body.thread_id ? body.thread_id : threadId,\n sessionComputerAssetId:\n typeof sessionComputer === 'string' && sessionComputer ? sessionComputer : null,\n computers: readWorkspaceAssets(body.computers),\n assets: readWorkspaceAssets(body.assets),\n };\n}\n\nexport function readComputerRuntimeStatus(body: unknown): ComputerRuntimeStatus | null {\n if (!isRecord(body) || typeof body.state !== 'string' || !body.state) return null;\n return {\n state: body.state,\n explicitStop: typeof body.explicit_stop === 'string' ? body.explicit_stop : null,\n provider: typeof body.provider === 'string' ? body.provider : null,\n };\n}\n\nexport function readComputerSshAccess(body: unknown): ComputerSshAccess | null {\n if (!isRecord(body) || typeof body.command !== 'string' || !body.command) return null;\n return {\n command: body.command,\n host: typeof body.host === 'string' ? body.host : '',\n port: typeof body.port === 'number' ? body.port : 22,\n username: typeof body.username === 'string' ? body.username : '',\n keyAuthEnabled: body.key_auth_enabled === true,\n };\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\nasync function getAgoraJson({\n backendUrl,\n auth,\n path,\n what,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n path: string;\n /** Names the read in the error message (`the session workspace`). */\n what: string;\n signal?: AbortSignal;\n}): Promise<unknown> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${path}`;\n const get = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, { method: 'GET', headers, signal: signal ?? null });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'session_workspace_failed',\n message: `Reading ${what} could not reach the Agora API.`,\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, get);\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'session_workspace_failed',\n message: `Reading ${what} failed: ${response.status}`,\n status: response.status,\n detail: await response.json().catch(() => undefined),\n hint: refused\n ? 'The signed-in user cannot read this session or asset.'\n : 'Check that backendUrl points at an Agora that serves this route.',\n });\n }\n return response.json();\n}\n\nexport async function fetchSessionWorkspace({\n backendUrl,\n auth,\n threadId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n threadId: string;\n signal?: AbortSignal;\n}): Promise<SessionWorkspace> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: sessionWorkspacePath(threadId),\n what: 'the session workspace',\n signal,\n });\n return readSessionWorkspace(body, threadId);\n}\n\nexport async function fetchComputerRuntimeStatus({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerRuntimeStatus | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerStatusPath(assetId),\n what: 'the computer status',\n signal,\n });\n return readComputerRuntimeStatus(body);\n}\n\nexport async function fetchComputerSshAccess({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerSshAccess | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerSshAccessPath(assetId),\n what: 'the computer SSH target',\n signal,\n });\n return readComputerSshAccess(body);\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"session-workspace-api.js","sources":["../../src/workbench/session-workspace-api.ts"],"sourcesContent":["/**\n * The Agora reads behind the session workbench, each one an existing,\n * permission-gated route:\n *\n * - `GET /api/conversations/threads/{thread_id}/workspace` — the computers and\n * assets a session works with (agora `web/api/session/workspace.py`; thread\n * access, then a narrow per-asset permission check, so it names only assets\n * the caller may open).\n * - `GET /api/sandbox/computer/{asset_id}/status` — a computer's runtime state\n * (asset VIEW; reads the runtime, never wakes it).\n * - `GET /api/v0/computer/{asset_id}/ssh-access` — the ready-made `ssh`\n * command (asset EDIT; mints nothing, wakes nothing).\n *\n * The credential rides like every SDK REST call: `Authorization: Bearer`\n * (resolved live through `getToken`, with one forced re-mint after a 401) or\n * `X-API-KEY`, else a same-origin host's cookie. Every failure is an\n * `AthenaSdkError` (`session_workspace_failed`, or `auth_rejected` for a\n * 401/403); an abort keeps its own identity. A computer or asset the caller may\n * not learn of answers like one that does not exist (404 \"Asset <id> not\n * found\", gate finding F19): `session_workspace_failed`, with a hint that says so.\n */\nimport { getAthenaApiBaseUrl } from '../collab/mint';\nimport { AthenaSdkError } from '../diagnostics/errors';\nimport { isRecord } from '../lib/utils';\nimport { sendWithCredentialRenewal } from '../runtime/credential-renewal';\nimport type { StatewireAuthSource } from '../runtime/statewire-auth-headers';\n\n/** Why an asset is in a session's workspace. */\nexport type SessionWorkspaceRole = 'session_computer' | 'computer' | 'asset';\n\n/** One computer or asset a session works with. */\nexport interface SessionWorkspaceAsset {\n assetId: string;\n title: string | null;\n /** The asset's type (`computer`, `spreadsheet`, `document`, …). */\n assetType: string;\n /** ISO 8601 time of the asset's last update. */\n updatedAt: string;\n role: SessionWorkspaceRole;\n}\n\n/** What a session works with, as the caller may see it. */\nexport interface SessionWorkspace {\n threadId: string;\n /** The computer bound to the session, when it has one the caller may open. */\n sessionComputerAssetId: string | null;\n /** Session computer first, then the computers the session touched, most recent first. */\n computers: SessionWorkspaceAsset[];\n /** The assets the session created or edited, most recently touched first. */\n assets: SessionWorkspaceAsset[];\n}\n\n/** A computer's runtime state as `/status` reports it. */\nexport interface ComputerRuntimeStatus {\n /** `started`, `stopped`, `restoring`, `unknown`, … — the provider's word. */\n state: string;\n /** `stopping` / `stopped` while someone asked it to stop. */\n explicitStop: string | null;\n provider: string | null;\n}\n\n/** The SSH target of a computer (identity mode: a registered key logs in). */\nexport interface ComputerSshAccess {\n /** Ready to run: `ssh [-p <port>] <asset_id>@<host>`. */\n command: string;\n host: string;\n port: number;\n username: string;\n /** False where the gateway accepts temporary tokens only (the command is refused there). */\n keyAuthEnabled: boolean;\n}\n\nexport function sessionWorkspacePath(threadId: string): string {\n return `/api/conversations/threads/${encodeURIComponent(threadId)}/workspace`;\n}\n\nexport function computerStatusPath(assetId: string): string {\n return `/api/sandbox/computer/${encodeURIComponent(assetId)}/status`;\n}\n\nexport function computerSshAccessPath(assetId: string): string {\n return `/api/v0/computer/${encodeURIComponent(assetId)}/ssh-access`;\n}\n\n/**\n * agora's 404 for an asset that does not exist or that the caller may not learn\n * of: `detail` \"Asset <id> not found\". Since gate finding F19 every refusal of a\n * caller outside the asset's workspace reads exactly like this; a refused member\n * of that workspace gets 403.\n */\nexport function isAssetMissingResponse({ status, body }: { status: number; body: unknown }): boolean {\n return (\n status === 404 &&\n isRecord(body) &&\n typeof body.detail === 'string' &&\n /^Asset (\\S+ )?not found$/.test(body.detail)\n );\n}\n\nconst WORKSPACE_ROLES: ReadonlySet<string> = new Set(['session_computer', 'computer', 'asset']);\n\nfunction readWorkspaceAsset(value: unknown): SessionWorkspaceAsset | null {\n if (!isRecord(value)) return null;\n const { asset_id, title, asset_type, updated_at, role } = value;\n if (typeof asset_id !== 'string' || asset_id.length === 0) return null;\n return {\n assetId: asset_id,\n title: typeof title === 'string' && title.trim().length > 0 ? title : null,\n assetType: typeof asset_type === 'string' ? asset_type : '',\n updatedAt: typeof updated_at === 'string' ? updated_at : '',\n role:\n typeof role === 'string' && WORKSPACE_ROLES.has(role)\n ? (role as SessionWorkspaceRole)\n : 'asset',\n };\n}\n\nfunction readWorkspaceAssets(value: unknown): SessionWorkspaceAsset[] {\n if (!Array.isArray(value)) return [];\n return value.flatMap((entry) => {\n const asset = readWorkspaceAsset(entry);\n return asset ? [asset] : [];\n });\n}\n\n/**\n * The route's snake_case body as the SDK's camelCase shape. Tolerant: a\n * malformed entry is dropped rather than failing the whole pane, and a body\n * that is not an object reads as an empty workspace for `threadId`.\n */\nexport function readSessionWorkspace(body: unknown, threadId: string): SessionWorkspace {\n if (!isRecord(body)) {\n return { threadId, sessionComputerAssetId: null, computers: [], assets: [] };\n }\n const sessionComputer = body.session_computer_asset_id;\n return {\n threadId: typeof body.thread_id === 'string' && body.thread_id ? body.thread_id : threadId,\n sessionComputerAssetId:\n typeof sessionComputer === 'string' && sessionComputer ? sessionComputer : null,\n computers: readWorkspaceAssets(body.computers),\n assets: readWorkspaceAssets(body.assets),\n };\n}\n\nexport function readComputerRuntimeStatus(body: unknown): ComputerRuntimeStatus | null {\n if (!isRecord(body) || typeof body.state !== 'string' || !body.state) return null;\n return {\n state: body.state,\n explicitStop: typeof body.explicit_stop === 'string' ? body.explicit_stop : null,\n provider: typeof body.provider === 'string' ? body.provider : null,\n };\n}\n\nexport function readComputerSshAccess(body: unknown): ComputerSshAccess | null {\n if (!isRecord(body) || typeof body.command !== 'string' || !body.command) return null;\n return {\n command: body.command,\n host: typeof body.host === 'string' ? body.host : '',\n port: typeof body.port === 'number' ? body.port : 22,\n username: typeof body.username === 'string' ? body.username : '',\n keyAuthEnabled: body.key_auth_enabled === true,\n };\n}\n\n/** A `fetch` the caller cancelled (unmount, a newer query): lifecycle, not failure. */\nfunction isAbortError(error: unknown): boolean {\n return (error instanceof DOMException || error instanceof Error) && error.name === 'AbortError';\n}\n\nasync function getAgoraJson({\n backendUrl,\n auth,\n path,\n what,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n path: string;\n /** Names the read in the error message (`the session workspace`). */\n what: string;\n signal?: AbortSignal;\n}): Promise<unknown> {\n const url = `${getAthenaApiBaseUrl(backendUrl)}${path}`;\n const get = async (headers: Record<string, string>): Promise<Response> => {\n try {\n return await fetch(url, { method: 'GET', headers, signal: signal ?? null });\n } catch (error) {\n if (isAbortError(error)) throw error;\n throw new AthenaSdkError({\n code: 'session_workspace_failed',\n message: `Reading ${what} could not reach the Agora API.`,\n cause: error,\n hint: 'Check the network and that backendUrl points at the Agora API base.',\n });\n }\n };\n const response = await sendWithCredentialRenewal(auth, get);\n if (!response.ok) {\n const refused = response.status === 401 || response.status === 403;\n const detail: unknown = await response.json().catch(() => undefined);\n throw new AthenaSdkError({\n code: refused ? 'auth_rejected' : 'session_workspace_failed',\n message: `Reading ${what} failed: ${response.status}`,\n status: response.status,\n detail,\n hint: refused\n ? 'The signed-in user cannot read this session or asset.'\n : isAssetMissingResponse({ status: response.status, body: detail })\n ? 'The asset does not exist, or the signed-in user may not read it.'\n : 'Check that backendUrl points at an Agora that serves this route.',\n });\n }\n return response.json();\n}\n\nexport async function fetchSessionWorkspace({\n backendUrl,\n auth,\n threadId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n threadId: string;\n signal?: AbortSignal;\n}): Promise<SessionWorkspace> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: sessionWorkspacePath(threadId),\n what: 'the session workspace',\n signal,\n });\n return readSessionWorkspace(body, threadId);\n}\n\nexport async function fetchComputerRuntimeStatus({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerRuntimeStatus | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerStatusPath(assetId),\n what: 'the computer status',\n signal,\n });\n return readComputerRuntimeStatus(body);\n}\n\nexport async function fetchComputerSshAccess({\n backendUrl,\n auth,\n assetId,\n signal,\n}: {\n backendUrl: string;\n auth: StatewireAuthSource;\n assetId: string;\n signal?: AbortSignal;\n}): Promise<ComputerSshAccess | null> {\n const body = await getAgoraJson({\n backendUrl,\n auth,\n path: computerSshAccessPath(assetId),\n what: 'the computer SSH target',\n signal,\n });\n return readComputerSshAccess(body);\n}\n"],"names":[],"mappings":"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"}
|