@athenaintel/react 0.38.1 → 0.39.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 +14 -2
- package/dist/chat-ui/thread/statewire-history-edge.cjs +13 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.js +13 -0
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/diagnostics/errors.cjs +5 -0
- package/dist/diagnostics/errors.cjs.map +1 -1
- package/dist/diagnostics/errors.d.ts +5 -0
- package/dist/diagnostics/errors.js +5 -0
- package/dist/diagnostics/errors.js.map +1 -1
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +6 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +5 -2
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.js +5 -2
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/provider/statewire-history-context.cjs +48 -0
- package/dist/provider/statewire-history-context.cjs.map +1 -0
- package/dist/provider/statewire-history-context.d.ts +36 -0
- package/dist/provider/statewire-history-context.js +48 -0
- package/dist/provider/statewire-history-context.js.map +1 -0
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/workbench/ComputerLiveDesktop.cjs +287 -0
- package/dist/workbench/ComputerLiveDesktop.cjs.map +1 -0
- package/dist/workbench/ComputerLiveDesktop.d.ts +27 -0
- package/dist/workbench/ComputerLiveDesktop.js +287 -0
- package/dist/workbench/ComputerLiveDesktop.js.map +1 -0
- package/dist/workbench/SessionWorkbench.cjs +2 -2
- package/dist/workbench/SessionWorkbench.cjs.map +1 -1
- package/dist/workbench/SessionWorkbench.js +2 -2
- package/dist/workbench/SessionWorkbench.js.map +1 -1
- package/dist/workbench/live-desktop-api.cjs +139 -0
- package/dist/workbench/live-desktop-api.cjs.map +1 -0
- package/dist/workbench/live-desktop-api.d.ts +69 -0
- package/dist/workbench/live-desktop-api.js +139 -0
- package/dist/workbench/live-desktop-api.js.map +1 -0
- package/dist/workbench/session-workbench-context.cjs.map +1 -1
- package/dist/workbench/session-workbench-context.d.ts +7 -0
- package/dist/workbench/session-workbench-context.js.map +1 -1
- package/dist/workbench/session-workbench-panes.cjs +38 -2
- package/dist/workbench/session-workbench-panes.cjs.map +1 -1
- package/dist/workbench/session-workbench-panes.js +38 -2
- package/dist/workbench/session-workbench-panes.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { StatewireAuthSource } from '../runtime/statewire-auth-headers';
|
|
2
|
+
export declare const LiveDesktopMode: {
|
|
3
|
+
readonly WATCH: 'watch';
|
|
4
|
+
readonly CONTROL: 'control';
|
|
5
|
+
};
|
|
6
|
+
export type LiveDesktopMode = (typeof LiveDesktopMode)[keyof typeof LiveDesktopMode];
|
|
7
|
+
/** What the session route answered. */
|
|
8
|
+
export type LiveDesktopSession = {
|
|
9
|
+
status: 'ready';
|
|
10
|
+
/** A path on agora's own origin, carrying the single-use ticket. */
|
|
11
|
+
viewerPath: string;
|
|
12
|
+
mode: LiveDesktopMode;
|
|
13
|
+
/** The remote screen, when known: what the frame keeps the aspect of. */
|
|
14
|
+
display: {
|
|
15
|
+
width: number;
|
|
16
|
+
height: number;
|
|
17
|
+
} | null;
|
|
18
|
+
} | {
|
|
19
|
+
status: 'display_not_running';
|
|
20
|
+
detail: string;
|
|
21
|
+
} | {
|
|
22
|
+
status: 'unsupported';
|
|
23
|
+
detail: string;
|
|
24
|
+
};
|
|
25
|
+
export declare function computerLiveDesktopSessionPath(assetId: string): string;
|
|
26
|
+
/** The route's body as a session, or `null` for a shape the pane cannot use. */
|
|
27
|
+
export declare function readLiveDesktopSession(body: unknown): LiveDesktopSession | null;
|
|
28
|
+
/**
|
|
29
|
+
* The viewer page's URL on agora's origin. Throws when *viewerPath* would
|
|
30
|
+
* leave that origin: the frame is given agora's page or nothing.
|
|
31
|
+
*/
|
|
32
|
+
export declare function liveDesktopViewerUrl({ backendUrl, viewerPath, }: {
|
|
33
|
+
backendUrl: string;
|
|
34
|
+
viewerPath: string;
|
|
35
|
+
}): string;
|
|
36
|
+
/** Ask for a viewer of the computer's desktop. Every failure is an `AthenaSdkError`. */
|
|
37
|
+
export declare function createComputerLiveDesktopSession({ backendUrl, auth, assetId, mode, start, embedOrigin, signal, }: {
|
|
38
|
+
backendUrl: string;
|
|
39
|
+
auth: StatewireAuthSource;
|
|
40
|
+
assetId: string;
|
|
41
|
+
mode: LiveDesktopMode;
|
|
42
|
+
/** Start the computer's display when it is down (this also opens its browser). */
|
|
43
|
+
start: boolean;
|
|
44
|
+
/** The origin of the page that will frame the viewer (this page's). */
|
|
45
|
+
embedOrigin: string | null;
|
|
46
|
+
signal?: AbortSignal;
|
|
47
|
+
}): Promise<LiveDesktopSession>;
|
|
48
|
+
/** Names the viewer page's messages (`LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE` in agora). */
|
|
49
|
+
export declare const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = "athena-live-desktop";
|
|
50
|
+
/** The viewer page's messages the pane acts on; the rest are ignored. */
|
|
51
|
+
export type LiveDesktopViewerMessage = {
|
|
52
|
+
status: 'ready';
|
|
53
|
+
} | {
|
|
54
|
+
status: 'error';
|
|
55
|
+
detail: string;
|
|
56
|
+
} | {
|
|
57
|
+
status: 'open';
|
|
58
|
+
} | {
|
|
59
|
+
status: 'closed';
|
|
60
|
+
code: number;
|
|
61
|
+
reason: string;
|
|
62
|
+
};
|
|
63
|
+
export declare function readLiveDesktopViewerMessage(data: unknown): LiveDesktopViewerMessage | null;
|
|
64
|
+
/**
|
|
65
|
+
* Close codes agora's relay ends a view with for good
|
|
66
|
+
* (`LIVE_DESKTOP_CLOSE_*` in agora `live_desktop.py`): the view is over and
|
|
67
|
+
* the pane says why. Any other close is a drop Selkies re-dials by itself.
|
|
68
|
+
*/
|
|
69
|
+
export declare function finalLiveDesktopCloseNotice(code: number): string | null;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { getAthenaApiBaseUrl } from "../collab/mint.js";
|
|
2
|
+
import { AthenaSdkError } from "../diagnostics/errors.js";
|
|
3
|
+
import { isRecord } from "../lib/utils.js";
|
|
4
|
+
import { sendWithCredentialRenewal } from "../runtime/credential-renewal.js";
|
|
5
|
+
const LiveDesktopMode = { WATCH: "watch", CONTROL: "control" };
|
|
6
|
+
function computerLiveDesktopSessionPath(assetId) {
|
|
7
|
+
return `/api/sandbox/computer/${encodeURIComponent(assetId)}/live-desktop/session`;
|
|
8
|
+
}
|
|
9
|
+
function readDisplay(value) {
|
|
10
|
+
if (!isRecord(value)) return null;
|
|
11
|
+
const { width, height } = value;
|
|
12
|
+
return typeof width === "number" && width > 0 && typeof height === "number" && height > 0 ? { width, height } : null;
|
|
13
|
+
}
|
|
14
|
+
function readLiveDesktopSession(body) {
|
|
15
|
+
if (!isRecord(body)) return null;
|
|
16
|
+
const detail = typeof body.detail === "string" ? body.detail : "";
|
|
17
|
+
if (body.status === "display_not_running") return { status: "display_not_running", detail };
|
|
18
|
+
if (body.status === "unsupported") return { status: "unsupported", detail };
|
|
19
|
+
if (body.status !== "ready") return null;
|
|
20
|
+
const { viewer_path: viewerPath, mode } = body;
|
|
21
|
+
if (typeof viewerPath !== "string" || !/^\/(?!\/)/.test(viewerPath)) return null;
|
|
22
|
+
if (mode !== LiveDesktopMode.WATCH && mode !== LiveDesktopMode.CONTROL) return null;
|
|
23
|
+
return { status: "ready", viewerPath, mode, display: readDisplay(body.display) };
|
|
24
|
+
}
|
|
25
|
+
function liveDesktopViewerUrl({
|
|
26
|
+
backendUrl,
|
|
27
|
+
viewerPath
|
|
28
|
+
}) {
|
|
29
|
+
const base = new URL(`${getAthenaApiBaseUrl(backendUrl)}/`);
|
|
30
|
+
const url = new URL(viewerPath, base);
|
|
31
|
+
if (url.origin !== base.origin) {
|
|
32
|
+
throw new Error("The live desktop viewer must be served by the Athena API");
|
|
33
|
+
}
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
function refusalDetail(body) {
|
|
37
|
+
if (!isRecord(body)) return null;
|
|
38
|
+
return typeof body.detail === "string" && body.detail ? body.detail : null;
|
|
39
|
+
}
|
|
40
|
+
async function createComputerLiveDesktopSession({
|
|
41
|
+
backendUrl,
|
|
42
|
+
auth,
|
|
43
|
+
assetId,
|
|
44
|
+
mode,
|
|
45
|
+
start,
|
|
46
|
+
embedOrigin,
|
|
47
|
+
signal
|
|
48
|
+
}) {
|
|
49
|
+
const url = `${getAthenaApiBaseUrl(backendUrl)}${computerLiveDesktopSessionPath(assetId)}`;
|
|
50
|
+
const body = JSON.stringify({
|
|
51
|
+
start,
|
|
52
|
+
mode,
|
|
53
|
+
...embedOrigin ? { embed_origin: embedOrigin } : {}
|
|
54
|
+
});
|
|
55
|
+
const send = async (headers) => {
|
|
56
|
+
try {
|
|
57
|
+
return await fetch(url, {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { ...headers, "Content-Type": "application/json" },
|
|
60
|
+
body,
|
|
61
|
+
signal: signal ?? null
|
|
62
|
+
});
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if ((error instanceof DOMException || error instanceof Error) && error.name === "AbortError") {
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
throw new AthenaSdkError({
|
|
68
|
+
code: "live_desktop_failed",
|
|
69
|
+
message: "The computer’s desktop could not be reached.",
|
|
70
|
+
cause: error,
|
|
71
|
+
hint: "Check the network and that backendUrl points at the Agora API base."
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const response = await sendWithCredentialRenewal(auth, send);
|
|
76
|
+
const parsed = await response.json().catch(() => void 0);
|
|
77
|
+
if (!response.ok) {
|
|
78
|
+
throw new AthenaSdkError({
|
|
79
|
+
code: response.status === 401 ? "auth_rejected" : "live_desktop_failed",
|
|
80
|
+
message: refusalDetail(parsed) ?? `Opening the computer’s desktop failed (${response.status}).`,
|
|
81
|
+
status: response.status,
|
|
82
|
+
detail: parsed,
|
|
83
|
+
hint: response.status === 403 ? "Watching a computer’s desktop needs edit access to it, in its own workspace." : void 0
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
const session = readLiveDesktopSession(parsed);
|
|
87
|
+
if (!session) {
|
|
88
|
+
throw new AthenaSdkError({
|
|
89
|
+
code: "live_desktop_failed",
|
|
90
|
+
message: "The computer’s desktop answered in a shape this app does not understand.",
|
|
91
|
+
status: response.status,
|
|
92
|
+
detail: parsed
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
return session;
|
|
96
|
+
}
|
|
97
|
+
const LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE = "athena-live-desktop";
|
|
98
|
+
function readLiveDesktopViewerMessage(data) {
|
|
99
|
+
if (!isRecord(data) || data.source !== LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE) return null;
|
|
100
|
+
switch (data.status) {
|
|
101
|
+
case "ready":
|
|
102
|
+
return { status: "ready" };
|
|
103
|
+
case "open":
|
|
104
|
+
return { status: "open" };
|
|
105
|
+
case "error":
|
|
106
|
+
return typeof data.detail === "string" ? { status: "error", detail: data.detail.slice(0, 300) } : null;
|
|
107
|
+
case "closed":
|
|
108
|
+
return typeof data.code === "number" ? {
|
|
109
|
+
status: "closed",
|
|
110
|
+
code: data.code,
|
|
111
|
+
reason: typeof data.reason === "string" ? data.reason.slice(0, 120) : ""
|
|
112
|
+
} : null;
|
|
113
|
+
default:
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function finalLiveDesktopCloseNotice(code) {
|
|
118
|
+
switch (code) {
|
|
119
|
+
case 4001:
|
|
120
|
+
return "The desktop session expired. Reconnect to start a new one.";
|
|
121
|
+
case 4003:
|
|
122
|
+
return "You no longer have access to this computer’s desktop.";
|
|
123
|
+
case 4008:
|
|
124
|
+
return "The desktop session reached its time limit, so the computer can go idle. Reconnect to keep watching.";
|
|
125
|
+
default:
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export {
|
|
130
|
+
LIVE_DESKTOP_VIEWER_MESSAGE_SOURCE,
|
|
131
|
+
LiveDesktopMode,
|
|
132
|
+
computerLiveDesktopSessionPath,
|
|
133
|
+
createComputerLiveDesktopSession,
|
|
134
|
+
finalLiveDesktopCloseNotice,
|
|
135
|
+
liveDesktopViewerUrl,
|
|
136
|
+
readLiveDesktopSession,
|
|
137
|
+
readLiveDesktopViewerMessage
|
|
138
|
+
};
|
|
139
|
+
//# sourceMappingURL=live-desktop-api.js.map
|
|
@@ -0,0 +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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-workbench-context.cjs","sources":["../../src/workbench/session-workbench-context.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\nimport type { SessionWorkspace } from './session-workspace-api';\nimport { useAthenaSessionWorkspace } from './use-session-workspace';\n\n/** What every pane knows about the thread the workbench shows. */\nexport interface SessionWorkbenchPaneContext {\n /**\n * The thread the chat is attached to (defined for a never-started chat too);\n * `null` outside `<AthenaProvider>`.\n */\n threadId: string | null;\n /**\n * Whether the backend knows the thread yet. A never-started chat has no\n * workspace, pull requests or history to read, so panes show their empty\n * state instead of asking.\n */\n isPersisted: boolean;\n /** Whether the thread's run is live — panes that read the backend poll while it is. */\n isRunning: boolean;\n}\n\n/**\n * One tab the workbench can open. Hosts add their own beside the built-in\n * ones (`DEFAULT_SESSION_WORKBENCH_PANES`) — a Harvey-style \"Matter\" pane, a\n * Hebbia-style \"Grid\" — and choose which open by default.\n */\nexport interface SessionWorkbenchPane {\n /** Stable id; also what `defaultOpenPaneIds` names. */\n id: string;\n /** Tab label. */\n title: string;\n /**\n * One line on what the pane shows (\"Commands the agent ran and their\n * output\"). The workbench lists it under the title when no pane is open.\n */\n description?: string;\n /** Tab icon (a lucide icon, or any component taking `className`). */\n icon?: ComponentType<{ className?: string }>;\n /** The pane body for the workbench's current thread. */\n render: (context: SessionWorkbenchPaneContext) => ReactNode;\n}\n\nexport const SessionWorkbenchContext = createContext<SessionWorkbenchPaneContext | null>(null);\n\n/**\n * The workbench's thread context, for a custom pane's components. Throws\n * outside `SessionWorkbench`.\n */\nexport function useSessionWorkbench(): SessionWorkbenchPaneContext {\n const context = useContext(SessionWorkbenchContext);\n if (context === null) {\n throw new Error('[AthenaSDK] useSessionWorkbench must be used within <SessionWorkbench>');\n }\n return context;\n}\n\n/** How often a pane re-reads the workspace while the run is live. */\nexport const SESSION_WORKSPACE_LIVE_REFETCH_MS = 15_000;\n\n/**\n * The workbench thread's workspace (computers and assets), shared by every\n * pane through one query: polled while the run is live, idle for a\n * never-started chat.\n */\nexport function useSessionWorkbenchWorkspace(): UseQueryResult<SessionWorkspace, Error> {\n const { threadId, isPersisted, isRunning } = useSessionWorkbench();\n return useAthenaSessionWorkspace({\n threadId,\n enabled: isPersisted,\n refetchIntervalMs: isRunning ? SESSION_WORKSPACE_LIVE_REFETCH_MS : false,\n });\n}\n"],"names":["createContext","useContext","useAthenaSessionWorkspace"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"session-workbench-context.cjs","sources":["../../src/workbench/session-workbench-context.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\nimport type { SessionWorkspace } from './session-workspace-api';\nimport { useAthenaSessionWorkspace } from './use-session-workspace';\n\n/** What every pane knows about the thread the workbench shows. */\nexport interface SessionWorkbenchPaneContext {\n /**\n * The thread the chat is attached to (defined for a never-started chat too);\n * `null` outside `<AthenaProvider>`.\n */\n threadId: string | null;\n /**\n * Whether the backend knows the thread yet. A never-started chat has no\n * workspace, pull requests or history to read, so panes show their empty\n * state instead of asking.\n */\n isPersisted: boolean;\n /** Whether the thread's run is live — panes that read the backend poll while it is. */\n isRunning: boolean;\n /**\n * Whether the pane is on screen: `false` while the workbench is collapsed to\n * its rail (the pane stays mounted, hidden). A pane that streams — the\n * Computer pane's live desktop — stops while hidden. Optional for hosts that\n * build this context themselves; absent reads as visible.\n */\n isVisible?: boolean;\n}\n\n/**\n * One tab the workbench can open. Hosts add their own beside the built-in\n * ones (`DEFAULT_SESSION_WORKBENCH_PANES`) — a Harvey-style \"Matter\" pane, a\n * Hebbia-style \"Grid\" — and choose which open by default.\n */\nexport interface SessionWorkbenchPane {\n /** Stable id; also what `defaultOpenPaneIds` names. */\n id: string;\n /** Tab label. */\n title: string;\n /**\n * One line on what the pane shows (\"Commands the agent ran and their\n * output\"). The workbench lists it under the title when no pane is open.\n */\n description?: string;\n /** Tab icon (a lucide icon, or any component taking `className`). */\n icon?: ComponentType<{ className?: string }>;\n /** The pane body for the workbench's current thread. */\n render: (context: SessionWorkbenchPaneContext) => ReactNode;\n}\n\nexport const SessionWorkbenchContext = createContext<SessionWorkbenchPaneContext | null>(null);\n\n/**\n * The workbench's thread context, for a custom pane's components. Throws\n * outside `SessionWorkbench`.\n */\nexport function useSessionWorkbench(): SessionWorkbenchPaneContext {\n const context = useContext(SessionWorkbenchContext);\n if (context === null) {\n throw new Error('[AthenaSDK] useSessionWorkbench must be used within <SessionWorkbench>');\n }\n return context;\n}\n\n/** How often a pane re-reads the workspace while the run is live. */\nexport const SESSION_WORKSPACE_LIVE_REFETCH_MS = 15_000;\n\n/**\n * The workbench thread's workspace (computers and assets), shared by every\n * pane through one query: polled while the run is live, idle for a\n * never-started chat.\n */\nexport function useSessionWorkbenchWorkspace(): UseQueryResult<SessionWorkspace, Error> {\n const { threadId, isPersisted, isRunning } = useSessionWorkbench();\n return useAthenaSessionWorkspace({\n threadId,\n enabled: isPersisted,\n refetchIntervalMs: isRunning ? SESSION_WORKSPACE_LIVE_REFETCH_MS : false,\n });\n}\n"],"names":["createContext","useContext","useAthenaSessionWorkspace"],"mappings":";;;;AAmDO,MAAM,0BAA0BA,MAAAA,cAAkD,IAAI;AAMtF,SAAS,sBAAmD;AACjE,QAAM,UAAUC,MAAAA,WAAW,uBAAuB;AAClD,MAAI,YAAY,MAAM;AACpB,UAAM,IAAI,MAAM,wEAAwE;AAAA,EAC1F;AACA,SAAO;AACT;AAGO,MAAM,oCAAoC;AAO1C,SAAS,+BAAwE;AACtF,QAAM,EAAE,UAAU,aAAa,UAAA,IAAc,oBAAA;AAC7C,SAAOC,8CAA0B;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,IACT,mBAAmB,YAAY,oCAAoC;AAAA,EAAA,CACpE;AACH;;;;;"}
|
|
@@ -16,6 +16,13 @@ export interface SessionWorkbenchPaneContext {
|
|
|
16
16
|
isPersisted: boolean;
|
|
17
17
|
/** Whether the thread's run is live — panes that read the backend poll while it is. */
|
|
18
18
|
isRunning: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Whether the pane is on screen: `false` while the workbench is collapsed to
|
|
21
|
+
* its rail (the pane stays mounted, hidden). A pane that streams — the
|
|
22
|
+
* Computer pane's live desktop — stops while hidden. Optional for hosts that
|
|
23
|
+
* build this context themselves; absent reads as visible.
|
|
24
|
+
*/
|
|
25
|
+
isVisible?: boolean;
|
|
19
26
|
}
|
|
20
27
|
/**
|
|
21
28
|
* One tab the workbench can open. Hosts add their own beside the built-in
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-workbench-context.js","sources":["../../src/workbench/session-workbench-context.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\nimport type { SessionWorkspace } from './session-workspace-api';\nimport { useAthenaSessionWorkspace } from './use-session-workspace';\n\n/** What every pane knows about the thread the workbench shows. */\nexport interface SessionWorkbenchPaneContext {\n /**\n * The thread the chat is attached to (defined for a never-started chat too);\n * `null` outside `<AthenaProvider>`.\n */\n threadId: string | null;\n /**\n * Whether the backend knows the thread yet. A never-started chat has no\n * workspace, pull requests or history to read, so panes show their empty\n * state instead of asking.\n */\n isPersisted: boolean;\n /** Whether the thread's run is live — panes that read the backend poll while it is. */\n isRunning: boolean;\n}\n\n/**\n * One tab the workbench can open. Hosts add their own beside the built-in\n * ones (`DEFAULT_SESSION_WORKBENCH_PANES`) — a Harvey-style \"Matter\" pane, a\n * Hebbia-style \"Grid\" — and choose which open by default.\n */\nexport interface SessionWorkbenchPane {\n /** Stable id; also what `defaultOpenPaneIds` names. */\n id: string;\n /** Tab label. */\n title: string;\n /**\n * One line on what the pane shows (\"Commands the agent ran and their\n * output\"). The workbench lists it under the title when no pane is open.\n */\n description?: string;\n /** Tab icon (a lucide icon, or any component taking `className`). */\n icon?: ComponentType<{ className?: string }>;\n /** The pane body for the workbench's current thread. */\n render: (context: SessionWorkbenchPaneContext) => ReactNode;\n}\n\nexport const SessionWorkbenchContext = createContext<SessionWorkbenchPaneContext | null>(null);\n\n/**\n * The workbench's thread context, for a custom pane's components. Throws\n * outside `SessionWorkbench`.\n */\nexport function useSessionWorkbench(): SessionWorkbenchPaneContext {\n const context = useContext(SessionWorkbenchContext);\n if (context === null) {\n throw new Error('[AthenaSDK] useSessionWorkbench must be used within <SessionWorkbench>');\n }\n return context;\n}\n\n/** How often a pane re-reads the workspace while the run is live. */\nexport const SESSION_WORKSPACE_LIVE_REFETCH_MS = 15_000;\n\n/**\n * The workbench thread's workspace (computers and assets), shared by every\n * pane through one query: polled while the run is live, idle for a\n * never-started chat.\n */\nexport function useSessionWorkbenchWorkspace(): UseQueryResult<SessionWorkspace, Error> {\n const { threadId, isPersisted, isRunning } = useSessionWorkbench();\n return useAthenaSessionWorkspace({\n threadId,\n enabled: isPersisted,\n refetchIntervalMs: isRunning ? SESSION_WORKSPACE_LIVE_REFETCH_MS : false,\n });\n}\n"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"session-workbench-context.js","sources":["../../src/workbench/session-workbench-context.ts"],"sourcesContent":["import type { UseQueryResult } from '@tanstack/react-query';\nimport type { ComponentType, ReactNode } from 'react';\nimport { createContext, useContext } from 'react';\nimport type { SessionWorkspace } from './session-workspace-api';\nimport { useAthenaSessionWorkspace } from './use-session-workspace';\n\n/** What every pane knows about the thread the workbench shows. */\nexport interface SessionWorkbenchPaneContext {\n /**\n * The thread the chat is attached to (defined for a never-started chat too);\n * `null` outside `<AthenaProvider>`.\n */\n threadId: string | null;\n /**\n * Whether the backend knows the thread yet. A never-started chat has no\n * workspace, pull requests or history to read, so panes show their empty\n * state instead of asking.\n */\n isPersisted: boolean;\n /** Whether the thread's run is live — panes that read the backend poll while it is. */\n isRunning: boolean;\n /**\n * Whether the pane is on screen: `false` while the workbench is collapsed to\n * its rail (the pane stays mounted, hidden). A pane that streams — the\n * Computer pane's live desktop — stops while hidden. Optional for hosts that\n * build this context themselves; absent reads as visible.\n */\n isVisible?: boolean;\n}\n\n/**\n * One tab the workbench can open. Hosts add their own beside the built-in\n * ones (`DEFAULT_SESSION_WORKBENCH_PANES`) — a Harvey-style \"Matter\" pane, a\n * Hebbia-style \"Grid\" — and choose which open by default.\n */\nexport interface SessionWorkbenchPane {\n /** Stable id; also what `defaultOpenPaneIds` names. */\n id: string;\n /** Tab label. */\n title: string;\n /**\n * One line on what the pane shows (\"Commands the agent ran and their\n * output\"). The workbench lists it under the title when no pane is open.\n */\n description?: string;\n /** Tab icon (a lucide icon, or any component taking `className`). */\n icon?: ComponentType<{ className?: string }>;\n /** The pane body for the workbench's current thread. */\n render: (context: SessionWorkbenchPaneContext) => ReactNode;\n}\n\nexport const SessionWorkbenchContext = createContext<SessionWorkbenchPaneContext | null>(null);\n\n/**\n * The workbench's thread context, for a custom pane's components. Throws\n * outside `SessionWorkbench`.\n */\nexport function useSessionWorkbench(): SessionWorkbenchPaneContext {\n const context = useContext(SessionWorkbenchContext);\n if (context === null) {\n throw new Error('[AthenaSDK] useSessionWorkbench must be used within <SessionWorkbench>');\n }\n return context;\n}\n\n/** How often a pane re-reads the workspace while the run is live. */\nexport const SESSION_WORKSPACE_LIVE_REFETCH_MS = 15_000;\n\n/**\n * The workbench thread's workspace (computers and assets), shared by every\n * pane through one query: polled while the run is live, idle for a\n * never-started chat.\n */\nexport function useSessionWorkbenchWorkspace(): UseQueryResult<SessionWorkspace, Error> {\n const { threadId, isPersisted, isRunning } = useSessionWorkbench();\n return useAthenaSessionWorkspace({\n threadId,\n enabled: isPersisted,\n refetchIntervalMs: isRunning ? SESSION_WORKSPACE_LIVE_REFETCH_MS : false,\n });\n}\n"],"names":[],"mappings":";;AAmDO,MAAM,0BAA0B,cAAkD,IAAI;AAMtF,SAAS,sBAAmD;AACjE,QAAM,UAAU,WAAW,uBAAuB;AAClD,MAAI,YAAY,MAAM;AACpB,UAAM,IAAI,MAAM,wEAAwE;AAAA,EAC1F;AACA,SAAO;AACT;AAGO,MAAM,oCAAoC;AAO1C,SAAS,+BAAwE;AACtF,QAAM,EAAE,UAAU,aAAa,UAAA,IAAc,oBAAA;AAC7C,SAAO,0BAA0B;AAAA,IAC/B;AAAA,IACA,SAAS;AAAA,IACT,mBAAmB,YAAY,oCAAoC;AAAA,EAAA,CACpE;AACH;"}
|
|
@@ -8,6 +8,7 @@ const react$1 = require("react");
|
|
|
8
8
|
const assetPanelStore = require("../assets/asset-panel-store.cjs");
|
|
9
9
|
const athenaUrls = require("../lib/athena-urls.cjs");
|
|
10
10
|
const AthenaContext = require("../provider/AthenaContext.cjs");
|
|
11
|
+
const statewireHistoryContext = require("../provider/statewire-history-context.cjs");
|
|
11
12
|
const pullRequestDisplay = require("../threads/pull-request-display.cjs");
|
|
12
13
|
const sessionListModel = require("../threads/session-list-model.cjs");
|
|
13
14
|
const statewireThreadList = require("../threads/statewire-thread-list.cjs");
|
|
@@ -15,6 +16,7 @@ const useAthenaSessionAttention = require("../threads/use-athena-session-attenti
|
|
|
15
16
|
const writeTodosToolUi = require("../tools/write-todos-tool-ui.cjs");
|
|
16
17
|
const sessionWorkbenchContext = require("./session-workbench-context.cjs");
|
|
17
18
|
const sessionWorkbenchModel = require("./session-workbench-model.cjs");
|
|
19
|
+
const ComputerLiveDesktop = require("./ComputerLiveDesktop.cjs");
|
|
18
20
|
const useSessionWorkspace = require("./use-session-workspace.cjs");
|
|
19
21
|
const threadExtras = require("../agent-runtime/src/thread-extras.cjs");
|
|
20
22
|
const statewireState = require("../agent-runtime/src/statewire-state.cjs");
|
|
@@ -195,9 +197,37 @@ const ShellRowItem = ({
|
|
|
195
197
|
] }) : null
|
|
196
198
|
] });
|
|
197
199
|
};
|
|
200
|
+
const ShellHistoryFooter = ({
|
|
201
|
+
history,
|
|
202
|
+
empty
|
|
203
|
+
}) => {
|
|
204
|
+
const { canLoadOlder, loading, loadOlder } = history;
|
|
205
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center gap-2 px-3 py-4 text-center", children: [
|
|
206
|
+
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: empty ? "No commands in the part of this session loaded so far." : "Earlier commands are in the part of this session not loaded yet." }),
|
|
207
|
+
canLoadOlder && loadOlder ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
208
|
+
"button",
|
|
209
|
+
{
|
|
210
|
+
type: "button",
|
|
211
|
+
onClick: loadOlder,
|
|
212
|
+
disabled: loading,
|
|
213
|
+
className: cnfast.cn(
|
|
214
|
+
"inline-flex items-center gap-1.5 rounded-md border border-border px-2 py-1 font-medium text-xs transition-colors hover:bg-muted disabled:opacity-60",
|
|
215
|
+
FOCUS_RING_CLASS
|
|
216
|
+
),
|
|
217
|
+
children: [
|
|
218
|
+
loading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.LoaderIcon, { className: "size-3.5 animate-spin", "aria-hidden": true }) : null,
|
|
219
|
+
loading ? "Loading earlier commands…" : "Load earlier commands"
|
|
220
|
+
]
|
|
221
|
+
}
|
|
222
|
+
) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-[11px] text-muted-foreground", children: "The rest is further back than one view of the conversation loads." })
|
|
223
|
+
] });
|
|
224
|
+
};
|
|
198
225
|
const ShellPaneContent = () => {
|
|
226
|
+
const { threadId } = sessionWorkbenchContext.useSessionWorkbench();
|
|
199
227
|
const messages = react.useAuiState((state) => state.thread.messages);
|
|
200
228
|
const workspace = sessionWorkbenchContext.useSessionWorkbenchWorkspace().data ?? null;
|
|
229
|
+
const history = statewireHistoryContext.useStatewireHistory(threadId);
|
|
230
|
+
const partial = (history == null ? void 0 : history.hasOlder) === true;
|
|
201
231
|
const rows = react$1.useMemo(() => sessionWorkbenchModel.deriveShellRows({ messages, workspace }), [messages, workspace]);
|
|
202
232
|
const [query, setQuery] = react$1.useState("");
|
|
203
233
|
const [expanded, setExpanded] = react$1.useState(() => /* @__PURE__ */ new Set());
|
|
@@ -209,6 +239,7 @@ const ShellPaneContent = () => {
|
|
|
209
239
|
return next;
|
|
210
240
|
});
|
|
211
241
|
if (rows.length === 0) {
|
|
242
|
+
if (partial && history) return /* @__PURE__ */ jsxRuntime.jsx(ShellHistoryFooter, { history, empty: true });
|
|
212
243
|
return /* @__PURE__ */ jsxRuntime.jsx(PaneEmpty, { icon: lucideReact.TerminalIcon, title: "No commands yet", children: "Every shell command the agent runs shows here with where it ran and how it ended." });
|
|
213
244
|
}
|
|
214
245
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col", children: [
|
|
@@ -227,7 +258,10 @@ const ShellPaneContent = () => {
|
|
|
227
258
|
}
|
|
228
259
|
)
|
|
229
260
|
] }),
|
|
230
|
-
/* @__PURE__ */ jsxRuntime.
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.jsxs("p", { className: "pt-1.5 text-[11px] text-muted-foreground", "aria-live": "polite", children: [
|
|
262
|
+
query.trim() ? `${visible.length} of ${rows.length} commands` : `${rows.length} command${rows.length === 1 ? "" : "s"}`,
|
|
263
|
+
partial ? " so far" : ""
|
|
264
|
+
] })
|
|
231
265
|
] }),
|
|
232
266
|
visible.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "px-3 py-6 text-center text-muted-foreground text-xs", children: "No command matches." }) : /* @__PURE__ */ jsxRuntime.jsx("ul", { "aria-label": "Shell commands", children: visible.map((row) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
233
267
|
ShellRowItem,
|
|
@@ -237,7 +271,8 @@ const ShellPaneContent = () => {
|
|
|
237
271
|
onToggle: () => toggle(row.id)
|
|
238
272
|
},
|
|
239
273
|
row.id
|
|
240
|
-
)) })
|
|
274
|
+
)) }),
|
|
275
|
+
partial && history ? /* @__PURE__ */ jsxRuntime.jsx(ShellHistoryFooter, { history, empty: false }) : null
|
|
241
276
|
] });
|
|
242
277
|
};
|
|
243
278
|
const SessionShellPane = () => /* @__PURE__ */ jsxRuntime.jsx(RequiresStatewireThread, { icon: lucideReact.TerminalIcon, children: /* @__PURE__ */ jsxRuntime.jsx(ShellPaneContent, {}) });
|
|
@@ -381,6 +416,7 @@ const SessionComputerPane = () => {
|
|
|
381
416
|
return /* @__PURE__ */ jsxRuntime.jsx(PaneEmpty, { icon: lucideReact.MonitorIcon, title: "No computer used yet", children: "When the agent runs commands on a computer, it shows here with a link and its SSH command." });
|
|
382
417
|
}
|
|
383
418
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col pb-3", children: [
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsx(ComputerLiveDesktop.ComputerLiveDesktop, { assetId: primary.assetId, className: "mx-3 mt-3" }),
|
|
384
420
|
/* @__PURE__ */ jsxRuntime.jsx(ComputerCard, { computer: primary }),
|
|
385
421
|
others.length > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
386
422
|
/* @__PURE__ */ jsxRuntime.jsx(SectionHeading, { children: "Also used" }),
|