@robota-sdk/agent-ui-web 3.1.2 → 3.3.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/CHANGELOG.md +43 -0
- package/dist/index.cjs +475 -97
- package/dist/index.d.cts +31 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.ts +31 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +476 -99
- package/dist/index.js.map +1 -1
- package/docs/SPEC.md +5 -0
- package/package.json +6 -6
- package/src/client/desktop-session-client.ts +78 -0
- package/src/components/AgentActivityPanel.tsx +38 -31
- package/src/components/Composer.tsx +71 -24
- package/src/components/ExecutionDetailSheet.tsx +116 -6
- package/src/components/SessionSurface.tsx +89 -36
- package/src/hooks/server-message-handling.ts +2 -0
- package/src/hooks/session-client-types.ts +12 -1
- package/src/hooks/use-app-view-requests.ts +8 -1
- package/src/hooks/use-execution-detail.ts +133 -33
- package/src/hooks/use-session-directory.ts +7 -2
- package/src/hooks/useSessionClient.ts +81 -9
- package/src/index.ts +2 -0
package/dist/index.cjs
CHANGED
|
@@ -21,6 +21,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
+
let _robota_sdk_agent_transport = require("@robota-sdk/agent-transport");
|
|
24
25
|
let react = require("react");
|
|
25
26
|
react = __toESM(react, 1);
|
|
26
27
|
let _robota_sdk_agent_transport_client = require("@robota-sdk/agent-transport/client");
|
|
@@ -332,6 +333,7 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
332
333
|
background_task_control_result: "visible-notice",
|
|
333
334
|
protocol_error: "visible-notice",
|
|
334
335
|
resume_gap: "transport-control",
|
|
336
|
+
surface_role: "reducer-state",
|
|
335
337
|
settings: "reducer-state",
|
|
336
338
|
settings_error: "reducer-state"
|
|
337
339
|
};
|
|
@@ -426,6 +428,7 @@ function nextRequestId$6(prefix) {
|
|
|
426
428
|
return `${prefix}_${requestCounter$6}_${Date.now()}`;
|
|
427
429
|
}
|
|
428
430
|
const CONNECTION_CLOSED = "The connection to the session closed.";
|
|
431
|
+
const READ_ONLY = "This session is read-only.";
|
|
429
432
|
/**
|
|
430
433
|
* The key a finished call's app view is read and called by, taken from the tool row's `appView`
|
|
431
434
|
* reference; `undefined` when the row gives none. Everything outside this module treats it as an
|
|
@@ -457,7 +460,7 @@ function callAppToolMessage(requestId, viewKey, name, args) {
|
|
|
457
460
|
* keeps its own entry. A reply that lost its connection never arrives: the owner of the connection
|
|
458
461
|
* rejects every outstanding request when it drops, is replaced or goes away (`rejectPendingAppRequests`).
|
|
459
462
|
*/
|
|
460
|
-
function useAppViewRequests(send, isConnected) {
|
|
463
|
+
function useAppViewRequests(send, isConnected, isObserving) {
|
|
461
464
|
const pendingRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
462
465
|
const [requestOwner] = (0, react.useState)(() => nextRequestId$6("app_request"));
|
|
463
466
|
const rejectPendingAppRequests = (0, react.useCallback)((reason = CONNECTION_CLOSED) => {
|
|
@@ -473,6 +476,10 @@ function useAppViewRequests(send, isConnected) {
|
|
|
473
476
|
reject(/* @__PURE__ */ new Error(signal?.aborted ? "The app view closed." : CONNECTION_CLOSED));
|
|
474
477
|
return;
|
|
475
478
|
}
|
|
479
|
+
if (isObserving()) {
|
|
480
|
+
reject(/* @__PURE__ */ new Error(READ_ONLY));
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
476
483
|
const requestId = nextRequestId$6(`${requestOwner}_${prefix}`);
|
|
477
484
|
const dispose = () => signal?.removeEventListener("abort", abort);
|
|
478
485
|
const abort = () => {
|
|
@@ -491,6 +498,7 @@ function useAppViewRequests(send, isConnected) {
|
|
|
491
498
|
}
|
|
492
499
|
}), [
|
|
493
500
|
isConnected,
|
|
501
|
+
isObserving,
|
|
494
502
|
requestOwner,
|
|
495
503
|
send
|
|
496
504
|
]);
|
|
@@ -540,13 +548,6 @@ function useAppViewRequests(send, isConnected) {
|
|
|
540
548
|
}
|
|
541
549
|
//#endregion
|
|
542
550
|
//#region src/hooks/use-execution-detail.ts
|
|
543
|
-
/**
|
|
544
|
-
* #3288 §1: the detail sheet's own correlated request/response state — which entry is open, its
|
|
545
|
-
* accumulated transcript records, and whether more can be loaded. Owned independently from chat
|
|
546
|
-
* state, the same way `use-personal-usage.ts` owns the usage-report protocol: one `requestId` per
|
|
547
|
-
* outstanding read, so a stale reply for an entry the operator already closed (or a page they moved
|
|
548
|
-
* past) is dropped rather than clobbering what is now on screen.
|
|
549
|
-
*/
|
|
550
551
|
let requestCounter$5 = 0;
|
|
551
552
|
function nextRequestId$5() {
|
|
552
553
|
requestCounter$5 += 1;
|
|
@@ -558,14 +559,27 @@ function useExecutionDetailState(send) {
|
|
|
558
559
|
const [records, setRecords] = (0, react.useState)([]);
|
|
559
560
|
const [error, setError] = (0, react.useState)(null);
|
|
560
561
|
const [complete, setComplete] = (0, react.useState)(false);
|
|
562
|
+
const [mode, setMode] = (0, react.useState)("live");
|
|
563
|
+
const [unreadable, setUnreadable] = (0, react.useState)();
|
|
561
564
|
const requestIdRef = (0, react.useRef)(null);
|
|
562
565
|
const entryIdRef = (0, react.useRef)(null);
|
|
566
|
+
const modeRef = (0, react.useRef)("live");
|
|
563
567
|
const nextCursorRef = (0, react.useRef)(void 0);
|
|
564
|
-
const
|
|
568
|
+
const queuedRef = (0, react.useRef)(null);
|
|
569
|
+
const timerRef = (0, react.useRef)(null);
|
|
570
|
+
const lastReadAtRef = (0, react.useRef)(0);
|
|
571
|
+
const readPage = (0, react.useCallback)((entryId, cursor, refresh = false) => {
|
|
572
|
+
if (requestIdRef.current !== null) {
|
|
573
|
+
if (!refresh || queuedRef.current === null || queuedRef.current.refresh) queuedRef.current = {
|
|
574
|
+
cursor,
|
|
575
|
+
refresh
|
|
576
|
+
};
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
565
579
|
const requestId = nextRequestId$5();
|
|
566
580
|
requestIdRef.current = requestId;
|
|
567
|
-
|
|
568
|
-
setStatus("loading");
|
|
581
|
+
lastReadAtRef.current = Date.now();
|
|
582
|
+
if (!refresh) setStatus("loading");
|
|
569
583
|
setError(null);
|
|
570
584
|
send({
|
|
571
585
|
type: "read-execution-detail",
|
|
@@ -574,51 +588,118 @@ function useExecutionDetailState(send) {
|
|
|
574
588
|
cursor
|
|
575
589
|
});
|
|
576
590
|
}, [send]);
|
|
591
|
+
const clearPending = (0, react.useCallback)(() => {
|
|
592
|
+
if (timerRef.current !== null) clearTimeout(timerRef.current);
|
|
593
|
+
timerRef.current = null;
|
|
594
|
+
queuedRef.current = null;
|
|
595
|
+
}, []);
|
|
596
|
+
(0, react.useEffect)(() => clearPending, [clearPending]);
|
|
597
|
+
const openExecutionDetail = (0, react.useCallback)((entryId) => {
|
|
598
|
+
clearPending();
|
|
599
|
+
requestIdRef.current = null;
|
|
600
|
+
entryIdRef.current = entryId;
|
|
601
|
+
modeRef.current = "live";
|
|
602
|
+
setMode("live");
|
|
603
|
+
setOpenEntryId(entryId);
|
|
604
|
+
setRecords([]);
|
|
605
|
+
setUnreadable(void 0);
|
|
606
|
+
setComplete(false);
|
|
607
|
+
nextCursorRef.current = void 0;
|
|
608
|
+
readPage(entryId);
|
|
609
|
+
}, [clearPending, readPage]);
|
|
610
|
+
const loadMoreExecutionDetail = (0, react.useCallback)(() => {
|
|
611
|
+
if (!openEntryId || status === "loading" || complete) return;
|
|
612
|
+
readPage(openEntryId, nextCursorRef.current);
|
|
613
|
+
}, [
|
|
614
|
+
openEntryId,
|
|
615
|
+
status,
|
|
616
|
+
complete,
|
|
617
|
+
readPage
|
|
618
|
+
]);
|
|
619
|
+
const changeMode = (0, react.useCallback)((next) => {
|
|
620
|
+
const entryId = entryIdRef.current;
|
|
621
|
+
if (!entryId) return;
|
|
622
|
+
clearPending();
|
|
623
|
+
modeRef.current = next;
|
|
624
|
+
setMode(next);
|
|
625
|
+
setRecords([]);
|
|
626
|
+
setUnreadable(void 0);
|
|
627
|
+
setComplete(false);
|
|
628
|
+
setStatus("loading");
|
|
629
|
+
nextCursorRef.current = void 0;
|
|
630
|
+
readPage(entryId, next === "transcript" ? { offset: 0 } : void 0);
|
|
631
|
+
}, [clearPending, readPage]);
|
|
632
|
+
const showEarlierExecutionDetail = (0, react.useCallback)(() => changeMode("transcript"), [changeMode]);
|
|
633
|
+
const showLiveExecutionDetail = (0, react.useCallback)(() => changeMode("live"), [changeMode]);
|
|
634
|
+
const closeExecutionDetail = (0, react.useCallback)(() => {
|
|
635
|
+
clearPending();
|
|
636
|
+
setOpenEntryId(null);
|
|
637
|
+
requestIdRef.current = null;
|
|
638
|
+
entryIdRef.current = null;
|
|
639
|
+
setRecords([]);
|
|
640
|
+
setUnreadable(void 0);
|
|
641
|
+
setStatus("idle");
|
|
642
|
+
setError(null);
|
|
643
|
+
setComplete(false);
|
|
644
|
+
}, [clearPending]);
|
|
645
|
+
const scheduleRefresh = (0, react.useCallback)((entryId) => {
|
|
646
|
+
if (timerRef.current !== null) return;
|
|
647
|
+
const delay = Math.max(0, 250 - (Date.now() - lastReadAtRef.current));
|
|
648
|
+
timerRef.current = setTimeout(() => {
|
|
649
|
+
timerRef.current = null;
|
|
650
|
+
if (entryIdRef.current === entryId && modeRef.current === "live") readPage(entryId, void 0, true);
|
|
651
|
+
}, delay);
|
|
652
|
+
}, [readPage]);
|
|
653
|
+
const handleExecutionWorkspace = (0, react.useCallback)((snapshot) => {
|
|
654
|
+
const entryId = entryIdRef.current;
|
|
655
|
+
if (!entryId || modeRef.current !== "live") return;
|
|
656
|
+
const entry = snapshot.entries.find((item) => item.id === entryId);
|
|
657
|
+
if (entry?.resumeSessionId && entry.controls.includes("attach")) scheduleRefresh(entryId);
|
|
658
|
+
}, [scheduleRefresh]);
|
|
577
659
|
return {
|
|
578
660
|
openEntryId,
|
|
579
661
|
executionDetailStatus: status,
|
|
580
662
|
executionDetailRecords: records,
|
|
581
663
|
executionDetailError: error,
|
|
582
664
|
executionDetailComplete: complete,
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
loadMoreExecutionDetail: (0, react.useCallback)(() => {
|
|
591
|
-
if (!openEntryId || status === "loading" || complete) return;
|
|
592
|
-
readPage(openEntryId, nextCursorRef.current);
|
|
593
|
-
}, [
|
|
594
|
-
openEntryId,
|
|
595
|
-
status,
|
|
596
|
-
complete,
|
|
597
|
-
readPage
|
|
598
|
-
]),
|
|
599
|
-
closeExecutionDetail: (0, react.useCallback)(() => {
|
|
600
|
-
setOpenEntryId(null);
|
|
601
|
-
requestIdRef.current = null;
|
|
602
|
-
entryIdRef.current = null;
|
|
603
|
-
setRecords([]);
|
|
604
|
-
setStatus("idle");
|
|
605
|
-
setError(null);
|
|
606
|
-
setComplete(false);
|
|
607
|
-
}, []),
|
|
665
|
+
executionDetailMode: mode,
|
|
666
|
+
executionDetailUnreadable: unreadable,
|
|
667
|
+
openExecutionDetail,
|
|
668
|
+
loadMoreExecutionDetail,
|
|
669
|
+
showEarlierExecutionDetail,
|
|
670
|
+
showLiveExecutionDetail,
|
|
671
|
+
closeExecutionDetail,
|
|
608
672
|
handleExecutionDetailMessage: (0, react.useCallback)((msg) => {
|
|
609
673
|
if (msg.type !== "execution_detail" && msg.type !== "execution_detail_error") return false;
|
|
610
674
|
if (requestIdRef.current !== msg.requestId) return true;
|
|
611
|
-
|
|
675
|
+
requestIdRef.current = null;
|
|
676
|
+
const queued = queuedRef.current;
|
|
677
|
+
queuedRef.current = null;
|
|
678
|
+
if (!queued || queued.refresh) if (msg.type === "execution_detail_error") {
|
|
612
679
|
setStatus("error");
|
|
613
680
|
setError(msg.message);
|
|
614
|
-
|
|
681
|
+
} else {
|
|
682
|
+
setRecords((previous) => msg.page.cursor === void 0 || msg.page.cursor.offset === 0 ? msg.page.records : [...previous, ...msg.page.records]);
|
|
683
|
+
setUnreadable((previous) => {
|
|
684
|
+
const current = msg.page.unreadable;
|
|
685
|
+
if (msg.page.cursor === void 0 || msg.page.cursor.offset === 0) return current;
|
|
686
|
+
if (!previous) return current;
|
|
687
|
+
if (!current) return previous;
|
|
688
|
+
return {
|
|
689
|
+
locked: previous.locked + current.locked,
|
|
690
|
+
noKeys: previous.noKeys + current.noKeys,
|
|
691
|
+
doesNotOpen: previous.doesNotOpen + current.doesNotOpen
|
|
692
|
+
};
|
|
693
|
+
});
|
|
694
|
+
nextCursorRef.current = msg.page.nextCursor;
|
|
695
|
+
setComplete(msg.page.nextCursor === void 0);
|
|
696
|
+
setStatus("ready");
|
|
615
697
|
}
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
setComplete(msg.page.nextCursor === void 0);
|
|
619
|
-
setStatus("ready");
|
|
698
|
+
if (queued && entryIdRef.current) if (queued.refresh) scheduleRefresh(entryIdRef.current);
|
|
699
|
+
else readPage(entryIdRef.current, queued.cursor);
|
|
620
700
|
return true;
|
|
621
|
-
}, [])
|
|
701
|
+
}, [readPage, scheduleRefresh]),
|
|
702
|
+
handleExecutionWorkspace
|
|
622
703
|
};
|
|
623
704
|
}
|
|
624
705
|
//#endregion
|
|
@@ -970,7 +1051,7 @@ function initialSidebarOpen() {
|
|
|
970
1051
|
* on its primary one. After a reconnect to such a host this surface goes back to the session it was
|
|
971
1052
|
* on; an older host has one session for everyone, so there is nothing to go back to.
|
|
972
1053
|
*/
|
|
973
|
-
function useSessionDirectoryState(send) {
|
|
1054
|
+
function useSessionDirectoryState(send, isObserving) {
|
|
974
1055
|
const { storage: { browserNamespace } } = useProductIdentity();
|
|
975
1056
|
const [sessionListing, setSessionListing] = (0, react.useState)(null);
|
|
976
1057
|
const [sessionsError, setSessionsError] = (0, react.useState)(null);
|
|
@@ -1021,6 +1102,7 @@ function useSessionDirectoryState(send) {
|
|
|
1021
1102
|
if (target === null) return;
|
|
1022
1103
|
clearStoredRestoreId(browserNamespace);
|
|
1023
1104
|
if (target === listing.currentSessionId) return;
|
|
1105
|
+
if (isObserving()) return;
|
|
1024
1106
|
if (!keepsSessionsLive(listing)) return;
|
|
1025
1107
|
if (!listing.sessions.some((row) => row.id === target)) return;
|
|
1026
1108
|
send({
|
|
@@ -1028,7 +1110,11 @@ function useSessionDirectoryState(send) {
|
|
|
1028
1110
|
sessionId: target,
|
|
1029
1111
|
requestId: nextRequestId$1("session_change")
|
|
1030
1112
|
});
|
|
1031
|
-
}, [
|
|
1113
|
+
}, [
|
|
1114
|
+
send,
|
|
1115
|
+
browserNamespace,
|
|
1116
|
+
isObserving
|
|
1117
|
+
]);
|
|
1032
1118
|
return {
|
|
1033
1119
|
sessionListing,
|
|
1034
1120
|
sessionsError,
|
|
@@ -1323,6 +1409,9 @@ function buildConversationEntriesFromDisplaySegments(displaySegments) {
|
|
|
1323
1409
|
function useSessionClient(makeClient) {
|
|
1324
1410
|
const [status, setStatus] = (0, react.useState)("disconnected");
|
|
1325
1411
|
const statusRef = (0, react.useRef)("disconnected");
|
|
1412
|
+
const [role, setRole] = (0, react.useState)(null);
|
|
1413
|
+
const roleRef = (0, react.useRef)(null);
|
|
1414
|
+
const isObserving = (0, react.useCallback)(() => roleRef.current === "observe", []);
|
|
1326
1415
|
const [messages, setMessages] = (0, react.useState)([]);
|
|
1327
1416
|
const [activeTools, setActiveTools] = (0, react.useState)([]);
|
|
1328
1417
|
const [streamingText, setStreamingText] = (0, react.useState)("");
|
|
@@ -1387,7 +1476,18 @@ function useSessionClient(makeClient) {
|
|
|
1387
1476
|
const silentCommandRequestIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1388
1477
|
const sessionChangesInFlightRef = (0, react.useRef)(0);
|
|
1389
1478
|
const sessionChangeRequestIdsRef = (0, react.useRef)(/* @__PURE__ */ new Set());
|
|
1479
|
+
const [executionDetailInput, setExecutionDetailInput] = (0, react.useState)();
|
|
1480
|
+
const executionDetailInputRef = (0, react.useRef)(null);
|
|
1390
1481
|
const send = (0, react.useCallback)((msg) => {
|
|
1482
|
+
const refused = isObserving() && !(0, _robota_sdk_agent_transport.isObserverMessageType)(msg.type);
|
|
1483
|
+
if (msg.type === "send-background-task") {
|
|
1484
|
+
if (refused || executionDetailInputRef.current !== null) return;
|
|
1485
|
+
executionDetailInputRef.current = msg.taskId;
|
|
1486
|
+
setExecutionDetailInput({
|
|
1487
|
+
taskId: msg.taskId,
|
|
1488
|
+
status: "pending"
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1391
1491
|
if (msg.type === "command") {
|
|
1392
1492
|
if (msg.name === "help") {
|
|
1393
1493
|
setHelpOpen(true);
|
|
@@ -1404,14 +1504,16 @@ function useSessionClient(makeClient) {
|
|
|
1404
1504
|
});
|
|
1405
1505
|
return;
|
|
1406
1506
|
}
|
|
1507
|
+
if (refused) return;
|
|
1407
1508
|
commandsInFlightRef.current += 1;
|
|
1408
1509
|
}
|
|
1510
|
+
if (refused) return;
|
|
1409
1511
|
if (msg.type === "switch-session" || msg.type === "new-session") {
|
|
1410
1512
|
sessionChangesInFlightRef.current += 1;
|
|
1411
1513
|
if (msg.requestId !== void 0) sessionChangeRequestIdsRef.current.add(msg.requestId);
|
|
1412
1514
|
}
|
|
1413
1515
|
clientRef.current?.send(msg);
|
|
1414
|
-
}, [appendEntry]);
|
|
1516
|
+
}, [appendEntry, isObserving]);
|
|
1415
1517
|
/** One session change this surface asked for has been answered. */
|
|
1416
1518
|
const settleSessionChange = (0, react.useCallback)((requestId) => {
|
|
1417
1519
|
sessionChangesInFlightRef.current = Math.max(0, sessionChangesInFlightRef.current - 1);
|
|
@@ -1444,11 +1546,11 @@ function useSessionClient(makeClient) {
|
|
|
1444
1546
|
});
|
|
1445
1547
|
}, [send]);
|
|
1446
1548
|
const { handleUsageMessage, ...personalUsageState } = usePersonalUsageState(send);
|
|
1447
|
-
const { handleExecutionDetailMessage, closeExecutionDetail, ...executionDetailState } = useExecutionDetailState(send);
|
|
1549
|
+
const { handleExecutionDetailMessage, handleExecutionWorkspace, closeExecutionDetail, ...executionDetailState } = useExecutionDetailState(send);
|
|
1448
1550
|
const { handleModelListMessage, ...modelListState } = useModelListState(send);
|
|
1449
1551
|
const { handleProjectMessage, ...projectPanelState } = useProjectPanelState(send);
|
|
1450
|
-
const { handleAppViewMessage, rejectPendingAppRequests, readAppView, callAppTool } = useAppViewRequests(send, (0, react.useCallback)(() => statusRef.current === "connected", []));
|
|
1451
|
-
const { handleSessionsMessage, canListSessions, markCurrent, armRestore, ...sessionDirectoryState } = useSessionDirectoryState(send);
|
|
1552
|
+
const { handleAppViewMessage, rejectPendingAppRequests, readAppView, callAppTool } = useAppViewRequests(send, (0, react.useCallback)(() => statusRef.current === "connected", []), isObserving);
|
|
1553
|
+
const { handleSessionsMessage, canListSessions, markCurrent, armRestore, ...sessionDirectoryState } = useSessionDirectoryState(send, isObserving);
|
|
1452
1554
|
const { requestSessions, setSessionSidebarOpen } = sessionDirectoryState;
|
|
1453
1555
|
const { handleSettingsMessage, openSettings, refreshSettings, ...settingsState } = useSettingsState(send);
|
|
1454
1556
|
const settingsOpenRef = (0, react.useRef)(false);
|
|
@@ -1470,6 +1572,10 @@ function useSessionClient(makeClient) {
|
|
|
1470
1572
|
if (handleAgentDefinitionsMessage(msg)) return;
|
|
1471
1573
|
if (handleSchedulesMessage(msg)) return;
|
|
1472
1574
|
switch (msg.type) {
|
|
1575
|
+
case "surface_role":
|
|
1576
|
+
roleRef.current = msg.role;
|
|
1577
|
+
setRole(msg.role);
|
|
1578
|
+
break;
|
|
1473
1579
|
case "messages": {
|
|
1474
1580
|
const reconstructed = msg.display ? buildConversationEntriesFromDisplaySegments(msg.display) : msg.messages.flatMap((m) => {
|
|
1475
1581
|
if (m.role !== "user" && m.role !== "assistant") return [];
|
|
@@ -1528,13 +1634,21 @@ function useSessionClient(makeClient) {
|
|
|
1528
1634
|
break;
|
|
1529
1635
|
}
|
|
1530
1636
|
case "execution_workspace_event":
|
|
1637
|
+
handleExecutionWorkspace(msg.snapshot);
|
|
1531
1638
|
setExecutionWorkspace(msg.snapshot);
|
|
1532
1639
|
break;
|
|
1533
1640
|
case "background_task_control_result":
|
|
1641
|
+
if (msg.action === "send" && executionDetailInputRef.current === msg.taskId) {
|
|
1642
|
+
executionDetailInputRef.current = null;
|
|
1643
|
+
setExecutionDetailInput({
|
|
1644
|
+
taskId: msg.taskId,
|
|
1645
|
+
status: msg.success ? "sent" : "failed"
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1534
1648
|
if (!msg.success) setSessionNotices((previous) => [...previous, {
|
|
1535
1649
|
id: nextId(),
|
|
1536
1650
|
kind: "background-task-control-failed",
|
|
1537
|
-
message: msg.message ?? `Could not stop ${msg.taskId}.`
|
|
1651
|
+
message: msg.message ?? `Could not ${msg.action === "send" ? "send to" : "stop"} ${msg.taskId}.`
|
|
1538
1652
|
}]);
|
|
1539
1653
|
break;
|
|
1540
1654
|
case "pending":
|
|
@@ -1679,6 +1793,13 @@ function useSessionClient(makeClient) {
|
|
|
1679
1793
|
}]);
|
|
1680
1794
|
break;
|
|
1681
1795
|
case "protocol_error": {
|
|
1796
|
+
if (executionDetailInputRef.current !== null && msg.message === "Not permitted for an observer: send-background-task") {
|
|
1797
|
+
setExecutionDetailInput({
|
|
1798
|
+
taskId: executionDetailInputRef.current,
|
|
1799
|
+
status: "failed"
|
|
1800
|
+
});
|
|
1801
|
+
executionDetailInputRef.current = null;
|
|
1802
|
+
}
|
|
1682
1803
|
if (sessionChangesInFlightRef.current > 0) {
|
|
1683
1804
|
settleSessionChange();
|
|
1684
1805
|
setSessionNotices((previous) => [...previous, {
|
|
@@ -1791,6 +1912,7 @@ function useSessionClient(makeClient) {
|
|
|
1791
1912
|
handleAgentDefinitionsMessage,
|
|
1792
1913
|
handleAppViewMessage,
|
|
1793
1914
|
handleExecutionDetailMessage,
|
|
1915
|
+
handleExecutionWorkspace,
|
|
1794
1916
|
handleModelListMessage,
|
|
1795
1917
|
handleProjectMessage,
|
|
1796
1918
|
handleSchedulesMessage,
|
|
@@ -1825,9 +1947,21 @@ function useSessionClient(makeClient) {
|
|
|
1825
1947
|
const onStatusChange = (next) => {
|
|
1826
1948
|
statusRef.current = next;
|
|
1827
1949
|
setStatus(next);
|
|
1950
|
+
if (next !== "connected") {
|
|
1951
|
+
roleRef.current = null;
|
|
1952
|
+
setRole(null);
|
|
1953
|
+
}
|
|
1828
1954
|
if (next !== "connected") {
|
|
1829
1955
|
rejectPendingAppRequests();
|
|
1830
1956
|
clearCompactionNotices();
|
|
1957
|
+
closeExecutionDetail();
|
|
1958
|
+
if (executionDetailInputRef.current !== null) {
|
|
1959
|
+
setExecutionDetailInput({
|
|
1960
|
+
taskId: executionDetailInputRef.current,
|
|
1961
|
+
status: "failed"
|
|
1962
|
+
});
|
|
1963
|
+
executionDetailInputRef.current = null;
|
|
1964
|
+
}
|
|
1831
1965
|
}
|
|
1832
1966
|
if (next === "connected") {
|
|
1833
1967
|
commandsInFlightRef.current = 0;
|
|
@@ -1835,6 +1969,7 @@ function useSessionClient(makeClient) {
|
|
|
1835
1969
|
sessionChangesInFlightRef.current = 0;
|
|
1836
1970
|
sessionChangeRequestIdsRef.current.clear();
|
|
1837
1971
|
pendingIntentRef.current = null;
|
|
1972
|
+
client.send({ type: "get-surface-role" });
|
|
1838
1973
|
client.send({ type: "get-commands" });
|
|
1839
1974
|
client.send({ type: "get-status" });
|
|
1840
1975
|
client.send({ type: "get-pending" });
|
|
@@ -1843,10 +1978,18 @@ function useSessionClient(makeClient) {
|
|
|
1843
1978
|
requestSchedules();
|
|
1844
1979
|
}
|
|
1845
1980
|
};
|
|
1846
|
-
const
|
|
1981
|
+
const inner = makeClient({
|
|
1847
1982
|
onMessage: handleMessage,
|
|
1848
1983
|
onStatusChange
|
|
1849
1984
|
});
|
|
1985
|
+
const client = {
|
|
1986
|
+
connect: () => inner.connect(),
|
|
1987
|
+
disconnect: () => inner.disconnect(),
|
|
1988
|
+
send: (msg) => {
|
|
1989
|
+
if (roleRef.current === "observe" && !(0, _robota_sdk_agent_transport.isObserverMessageType)(msg.type)) return;
|
|
1990
|
+
inner.send(msg);
|
|
1991
|
+
}
|
|
1992
|
+
};
|
|
1850
1993
|
clientRef.current = client;
|
|
1851
1994
|
client.connect();
|
|
1852
1995
|
return () => {
|
|
@@ -1858,6 +2001,7 @@ function useSessionClient(makeClient) {
|
|
|
1858
2001
|
makeClient,
|
|
1859
2002
|
handleMessage,
|
|
1860
2003
|
clearCompactionNotices,
|
|
2004
|
+
closeExecutionDetail,
|
|
1861
2005
|
requestSessions,
|
|
1862
2006
|
requestSchedules,
|
|
1863
2007
|
armRestore,
|
|
@@ -1874,6 +2018,7 @@ function useSessionClient(makeClient) {
|
|
|
1874
2018
|
commandCatalog,
|
|
1875
2019
|
sessionStatus,
|
|
1876
2020
|
ownDriverId,
|
|
2021
|
+
role,
|
|
1877
2022
|
send,
|
|
1878
2023
|
sendCommandSilently,
|
|
1879
2024
|
pendingPrompts,
|
|
@@ -1882,6 +2027,7 @@ function useSessionClient(makeClient) {
|
|
|
1882
2027
|
answerAsk,
|
|
1883
2028
|
...personalUsageState,
|
|
1884
2029
|
...executionDetailState,
|
|
2030
|
+
executionDetailInput,
|
|
1885
2031
|
closeExecutionDetail,
|
|
1886
2032
|
...modelListState,
|
|
1887
2033
|
...projectPanelState,
|
|
@@ -1916,6 +2062,63 @@ function useWsSession(url) {
|
|
|
1916
2062
|
};
|
|
1917
2063
|
}
|
|
1918
2064
|
//#endregion
|
|
2065
|
+
//#region src/client/desktop-session-client.ts
|
|
2066
|
+
function createDesktopSessionClient(bridge, callbacks) {
|
|
2067
|
+
let connected = false;
|
|
2068
|
+
let subscribed = false;
|
|
2069
|
+
let release;
|
|
2070
|
+
let failed = false;
|
|
2071
|
+
let revoked = false;
|
|
2072
|
+
const close = () => {
|
|
2073
|
+
revoked = true;
|
|
2074
|
+
connected = false;
|
|
2075
|
+
if (!failed && subscribed) callbacks.onStatusChange("disconnected");
|
|
2076
|
+
};
|
|
2077
|
+
return {
|
|
2078
|
+
connect() {
|
|
2079
|
+
if (revoked || failed) return;
|
|
2080
|
+
if (subscribed) return;
|
|
2081
|
+
subscribed = true;
|
|
2082
|
+
callbacks.onStatusChange("connecting");
|
|
2083
|
+
try {
|
|
2084
|
+
release = bridge.subscribe((frame) => {
|
|
2085
|
+
if (!subscribed || failed || revoked) return;
|
|
2086
|
+
const decoded = (0, _robota_sdk_agent_transport_client.decodeFrame)(frame, _robota_sdk_agent_transport_client.decodeServerMessage);
|
|
2087
|
+
if (!decoded.ok) {
|
|
2088
|
+
failed = true;
|
|
2089
|
+
connected = false;
|
|
2090
|
+
bridge.close();
|
|
2091
|
+
callbacks.onStatusChange("error");
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
callbacks.onMessage(decoded.message);
|
|
2095
|
+
}, close, () => {
|
|
2096
|
+
if (!subscribed || failed || revoked) return;
|
|
2097
|
+
connected = true;
|
|
2098
|
+
callbacks.onStatusChange("connected");
|
|
2099
|
+
bridge.send(JSON.stringify({ type: "get-messages" }));
|
|
2100
|
+
});
|
|
2101
|
+
} catch {
|
|
2102
|
+
close();
|
|
2103
|
+
}
|
|
2104
|
+
},
|
|
2105
|
+
disconnect() {
|
|
2106
|
+
subscribed = false;
|
|
2107
|
+
connected = false;
|
|
2108
|
+
release?.();
|
|
2109
|
+
release = void 0;
|
|
2110
|
+
callbacks.onStatusChange("disconnected");
|
|
2111
|
+
},
|
|
2112
|
+
send(message) {
|
|
2113
|
+
if (!connected || failed) return;
|
|
2114
|
+
if (!bridge.send(JSON.stringify(message))) {
|
|
2115
|
+
bridge.close();
|
|
2116
|
+
close();
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
};
|
|
2120
|
+
}
|
|
2121
|
+
//#endregion
|
|
1919
2122
|
//#region src/client/runtime-host.ts
|
|
1920
2123
|
/** Resolve host capabilities without importing Electron or a Node CLI into the browser graph. */
|
|
1921
2124
|
function resolveClientRuntimeHost(environment) {
|
|
@@ -3429,7 +3632,7 @@ function AgentActivityPanel({ tasks, schedules = [], goal = null, onPauseSchedul
|
|
|
3429
3632
|
task,
|
|
3430
3633
|
onPause: onPauseSchedule,
|
|
3431
3634
|
onResume: onResumeSchedule,
|
|
3432
|
-
onRequestDelete: () => setPendingDeleteId(task.id)
|
|
3635
|
+
onRequestDelete: onDeleteSchedule ? () => setPendingDeleteId(task.id) : void 0
|
|
3433
3636
|
}, task.id))
|
|
3434
3637
|
})] }),
|
|
3435
3638
|
goal && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
@@ -3530,33 +3733,33 @@ function ScheduleCard({ task, onPause, onResume, onRequestDelete }) {
|
|
|
3530
3733
|
describeScheduleStatus(task)
|
|
3531
3734
|
]
|
|
3532
3735
|
}),
|
|
3533
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3736
|
+
onResume || onPause || onRequestDelete ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
3534
3737
|
className: "mt-2 flex items-center gap-3",
|
|
3535
|
-
children: [isPaused ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3738
|
+
children: [isPaused ? onResume && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3536
3739
|
type: "button",
|
|
3537
|
-
onClick: () => onResume
|
|
3740
|
+
onClick: () => onResume(task.id),
|
|
3538
3741
|
className: "flex items-center gap-1.5 rounded-lg px-2 py-1 text-[13px] text-muted-foreground hover:bg-hover hover:text-foreground",
|
|
3539
3742
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Play, {
|
|
3540
3743
|
size: 11,
|
|
3541
3744
|
fill: "currentColor",
|
|
3542
3745
|
"aria-hidden": "true"
|
|
3543
3746
|
}), "Resume"]
|
|
3544
|
-
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3747
|
+
}) : onPause && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3545
3748
|
type: "button",
|
|
3546
|
-
onClick: () => onPause
|
|
3749
|
+
onClick: () => onPause(task.id),
|
|
3547
3750
|
className: "flex items-center gap-1.5 rounded-lg px-2 py-1 text-[13px] text-muted-foreground hover:bg-hover hover:text-foreground",
|
|
3548
3751
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Pause, {
|
|
3549
3752
|
size: 11,
|
|
3550
3753
|
fill: "currentColor",
|
|
3551
3754
|
"aria-hidden": "true"
|
|
3552
3755
|
}), "Pause"]
|
|
3553
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3756
|
+
}), onRequestDelete && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
3554
3757
|
type: "button",
|
|
3555
3758
|
onClick: onRequestDelete,
|
|
3556
3759
|
className: "flex items-center gap-1.5 rounded-lg px-2 py-1 text-[13px] text-destructive hover:bg-destructive/10",
|
|
3557
3760
|
children: "Delete…"
|
|
3558
3761
|
})]
|
|
3559
|
-
})
|
|
3762
|
+
}) : null
|
|
3560
3763
|
]
|
|
3561
3764
|
});
|
|
3562
3765
|
}
|
|
@@ -3580,7 +3783,7 @@ function GoalCard({ goal, onCancel }) {
|
|
|
3580
3783
|
className: "mt-1.5 pl-[26px] text-[13px] text-muted-foreground",
|
|
3581
3784
|
children: describeGoalStatus(goal)
|
|
3582
3785
|
}),
|
|
3583
|
-
goal.status === "active" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3786
|
+
goal.status === "active" && onCancel && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3584
3787
|
className: "mt-2 pl-[26px]",
|
|
3585
3788
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
3586
3789
|
type: "button",
|
|
@@ -5881,16 +6084,32 @@ function writeDraft(namespace, sessionId, value) {
|
|
|
5881
6084
|
* context). The row's controls run the session's own commands (`/provider`, `/mode`, `/effort`), so a
|
|
5882
6085
|
* setting changes through the one path every client shares.
|
|
5883
6086
|
*/
|
|
5884
|
-
const
|
|
6087
|
+
const ComposerInstance = (0, react.forwardRef)(function Composer({ draftMode = "persistent", onSubmit, onCommand, catalog, status, connected = true, running, onStop, queued, onCancelQueue, pickFiles, getPathForFile, modelList = null, onRequestModelList = () => {}, onSilentCommand = onCommand, onManageProviders = () => onCommand("provider") }, ref) {
|
|
5885
6088
|
const { identity, storage: { browserNamespace } } = useProductIdentity();
|
|
5886
6089
|
const textareaRef = (0, react.useRef)(null);
|
|
5887
6090
|
(0, react.useImperativeHandle)(ref, () => ({ focus: () => textareaRef.current?.focus() }), []);
|
|
5888
6091
|
const sessionId = status?.sessionId;
|
|
6092
|
+
const workspacePath = status?.workspace?.path;
|
|
6093
|
+
const pickerGeneration = (0, react.useRef)(0);
|
|
6094
|
+
const [fileInputEpoch, setFileInputEpoch] = (0, react.useState)(0);
|
|
6095
|
+
const browserPickerOpen = (0, react.useRef)(false);
|
|
6096
|
+
(0, react.useLayoutEffect)(() => {
|
|
6097
|
+
pickerGeneration.current += 1;
|
|
6098
|
+
browserPickerOpen.current = false;
|
|
6099
|
+
return () => {
|
|
6100
|
+
pickerGeneration.current += 1;
|
|
6101
|
+
};
|
|
6102
|
+
}, [sessionId, workspacePath]);
|
|
6103
|
+
const invalidatePickers = () => {
|
|
6104
|
+
pickerGeneration.current += 1;
|
|
6105
|
+
browserPickerOpen.current = false;
|
|
6106
|
+
setFileInputEpoch((epoch) => epoch + 1);
|
|
6107
|
+
};
|
|
5889
6108
|
const sessionIdRef = (0, react.useRef)(sessionId);
|
|
5890
6109
|
const hasKnownSessionRef = (0, react.useRef)(sessionId !== void 0);
|
|
5891
6110
|
const stateRef = (0, react.useRef)(EMPTY_DRAFT);
|
|
5892
6111
|
const [draft, setDraftState] = (0, react.useState)(() => {
|
|
5893
|
-
const initial = readDraft(browserNamespace, sessionId);
|
|
6112
|
+
const initial = draftMode === "volatile" ? EMPTY_DRAFT : readDraft(browserNamespace, sessionId);
|
|
5894
6113
|
stateRef.current = initial;
|
|
5895
6114
|
return initial.text;
|
|
5896
6115
|
});
|
|
@@ -5914,7 +6133,7 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
5914
6133
|
text: value
|
|
5915
6134
|
};
|
|
5916
6135
|
setDraftState(value);
|
|
5917
|
-
writeDraft(browserNamespace, sessionIdRef.current, stateRef.current);
|
|
6136
|
+
if (draftMode === "persistent") writeDraft(browserNamespace, sessionIdRef.current, stateRef.current);
|
|
5918
6137
|
};
|
|
5919
6138
|
/** #3282 §4d: attachment chips persist alongside the text, under the same per-session draft key. */
|
|
5920
6139
|
const setAttachments = (updater) => {
|
|
@@ -5924,9 +6143,10 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
5924
6143
|
attachments: next
|
|
5925
6144
|
};
|
|
5926
6145
|
setAttachmentsState(next);
|
|
5927
|
-
writeDraft(browserNamespace, sessionIdRef.current, stateRef.current);
|
|
6146
|
+
if (draftMode === "persistent") writeDraft(browserNamespace, sessionIdRef.current, stateRef.current);
|
|
5928
6147
|
};
|
|
5929
6148
|
(0, react.useEffect)(() => {
|
|
6149
|
+
if (draftMode === "volatile") return;
|
|
5930
6150
|
if (sessionId === void 0 && hasKnownSessionRef.current) return;
|
|
5931
6151
|
if (sessionIdRef.current === sessionId) return;
|
|
5932
6152
|
const firstArrival = !hasKnownSessionRef.current;
|
|
@@ -5945,23 +6165,26 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
5945
6165
|
setDraftState(stored.text);
|
|
5946
6166
|
setAttachmentsState(stored.attachments);
|
|
5947
6167
|
setAttachmentNotice(null);
|
|
5948
|
-
}, [
|
|
6168
|
+
}, [
|
|
6169
|
+
browserNamespace,
|
|
6170
|
+
draftMode,
|
|
6171
|
+
sessionId
|
|
6172
|
+
]);
|
|
5949
6173
|
const submit = () => {
|
|
5950
6174
|
if (!connected) return;
|
|
5951
6175
|
const text = draft.trim();
|
|
5952
6176
|
if (!text && attachments.length === 0) return;
|
|
6177
|
+
invalidatePickers();
|
|
5953
6178
|
onSubmit(buildPromptWithAttachments(text, attachments));
|
|
5954
6179
|
setDraft("");
|
|
5955
6180
|
setAttachments([]);
|
|
5956
6181
|
setAttachmentNotice(null);
|
|
5957
6182
|
};
|
|
5958
|
-
const workspacePath = status?.workspace?.path;
|
|
5959
|
-
const totalAttachedBytes = attachments.reduce((sum, a) => sum + a.size, 0);
|
|
5960
6183
|
/** Evaluate every dropped/picked file in order, so a mixed batch attaches what it can. */
|
|
5961
6184
|
const addCandidates = (candidates) => {
|
|
5962
6185
|
if (candidates.length === 0) return;
|
|
5963
|
-
let total =
|
|
5964
|
-
let count = attachments.length;
|
|
6186
|
+
let total = stateRef.current.attachments.reduce((sum, attachment) => sum + attachment.size, 0);
|
|
6187
|
+
let count = stateRef.current.attachments.length;
|
|
5965
6188
|
const added = [];
|
|
5966
6189
|
let notice = null;
|
|
5967
6190
|
for (const candidate of candidates) {
|
|
@@ -5985,19 +6208,44 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
5985
6208
|
absolutePath: getPathForFile ? getPathForFile(file) || void 0 : void 0
|
|
5986
6209
|
});
|
|
5987
6210
|
const fileInputRef = (0, react.useRef)(null);
|
|
6211
|
+
(0, react.useEffect)(() => {
|
|
6212
|
+
const input = fileInputRef.current;
|
|
6213
|
+
if (!input) return;
|
|
6214
|
+
const cancel = () => {
|
|
6215
|
+
browserPickerOpen.current = false;
|
|
6216
|
+
};
|
|
6217
|
+
input.addEventListener("cancel", cancel);
|
|
6218
|
+
return () => {
|
|
6219
|
+
input.removeEventListener("cancel", cancel);
|
|
6220
|
+
};
|
|
6221
|
+
}, [
|
|
6222
|
+
sessionId,
|
|
6223
|
+
workspacePath,
|
|
6224
|
+
fileInputEpoch
|
|
6225
|
+
]);
|
|
5988
6226
|
const handleAttachClick = () => {
|
|
5989
6227
|
if (!connected) return;
|
|
5990
6228
|
if (pickFiles) {
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
6229
|
+
const generation = pickerGeneration.current;
|
|
6230
|
+
pickFiles().then((picked) => {
|
|
6231
|
+
if (generation !== pickerGeneration.current) return;
|
|
6232
|
+
addCandidates(picked.map((f) => ({
|
|
6233
|
+
name: f.name,
|
|
6234
|
+
size: f.size,
|
|
6235
|
+
absolutePath: f.path
|
|
6236
|
+
})));
|
|
6237
|
+
}).catch(() => {
|
|
6238
|
+
if (generation !== pickerGeneration.current) return;
|
|
6239
|
+
setAttachmentNotice("Could not open the file picker. Try again.");
|
|
6240
|
+
});
|
|
5996
6241
|
return;
|
|
5997
6242
|
}
|
|
6243
|
+
if (browserPickerOpen.current) return;
|
|
6244
|
+
browserPickerOpen.current = true;
|
|
5998
6245
|
fileInputRef.current?.click();
|
|
5999
6246
|
};
|
|
6000
6247
|
const handleFileInputChange = (event) => {
|
|
6248
|
+
browserPickerOpen.current = false;
|
|
6001
6249
|
const files = Array.from(event.target.files ?? []);
|
|
6002
6250
|
event.target.value = "";
|
|
6003
6251
|
addCandidates(files.map(toCandidateFromFile));
|
|
@@ -6029,6 +6277,7 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
6029
6277
|
/** Choosing a command from the menu, by mouse or by keyboard (`acceptMenu` below). */
|
|
6030
6278
|
const chooseMenuItem = (item) => {
|
|
6031
6279
|
if (isImmediateScreenCommand(item)) {
|
|
6280
|
+
invalidatePickers();
|
|
6032
6281
|
setDraft("");
|
|
6033
6282
|
onCommand(item.name);
|
|
6034
6283
|
return;
|
|
@@ -6191,7 +6440,11 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
6191
6440
|
className: "sr-only",
|
|
6192
6441
|
tabIndex: -1,
|
|
6193
6442
|
"aria-hidden": "true"
|
|
6194
|
-
}
|
|
6443
|
+
}, JSON.stringify([
|
|
6444
|
+
sessionId ?? null,
|
|
6445
|
+
workspacePath ?? null,
|
|
6446
|
+
fileInputEpoch
|
|
6447
|
+
])),
|
|
6195
6448
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
6196
6449
|
ref: textareaRef,
|
|
6197
6450
|
"aria-label": "message",
|
|
@@ -6283,6 +6536,15 @@ const Composer = (0, react.forwardRef)(function Composer({ onSubmit, onCommand,
|
|
|
6283
6536
|
]
|
|
6284
6537
|
});
|
|
6285
6538
|
});
|
|
6539
|
+
/** Volatile state belongs to one session and never crosses a mode or session boundary. */
|
|
6540
|
+
const Composer = (0, react.forwardRef)(function Composer(props, ref) {
|
|
6541
|
+
const key = props.draftMode === "volatile" ? JSON.stringify(["volatile", props.status?.sessionId ?? null]) : "persistent";
|
|
6542
|
+
return /* @__PURE__ */ (0, react.createElement)(ComposerInstance, {
|
|
6543
|
+
...props,
|
|
6544
|
+
key,
|
|
6545
|
+
ref
|
|
6546
|
+
});
|
|
6547
|
+
});
|
|
6286
6548
|
/**
|
|
6287
6549
|
* The goal being pursued (`/goal`), above the composer while it is active — the objective, how far
|
|
6288
6550
|
* the turn budget has gone, and a way to stop it (`/goal cancel`).
|
|
@@ -6317,7 +6579,7 @@ function GoalBar({ status, onStop }) {
|
|
|
6317
6579
|
goal.maxIterations
|
|
6318
6580
|
]
|
|
6319
6581
|
}),
|
|
6320
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6582
|
+
onStop && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
6321
6583
|
type: "button",
|
|
6322
6584
|
"aria-label": "Stop goal",
|
|
6323
6585
|
onClick: onStop,
|
|
@@ -6343,6 +6605,7 @@ const STATUS_TAG_TONE = {
|
|
|
6343
6605
|
destructive: "bg-destructive/12 text-destructive",
|
|
6344
6606
|
muted: "bg-subtle/15 text-subtle"
|
|
6345
6607
|
};
|
|
6608
|
+
const FOLLOW_BOTTOM_MARGIN_PX = 32;
|
|
6346
6609
|
const RECORD_KIND_LABEL = {
|
|
6347
6610
|
message: "",
|
|
6348
6611
|
tool_activity: "Tool",
|
|
@@ -6357,10 +6620,40 @@ const RECORD_KIND_LABEL = {
|
|
|
6357
6620
|
* headline), its transcript so far (paginated via `onLoadMore`) and, once it has one, its result.
|
|
6358
6621
|
* `entry === null` renders nothing; the caller owns showing/hiding this over the panel.
|
|
6359
6622
|
*/
|
|
6360
|
-
function ExecutionDetailSheet({ entry, status, records, error, complete, onClose, onLoadMore, onStop }) {
|
|
6623
|
+
function ExecutionDetailSheet({ entry, status, records, error, complete, onClose, onLoadMore, onStop, onSend, inputState, onContinue, mode = "live", unreadable, onShowEarlier, onShowLive }) {
|
|
6624
|
+
const [prompt, setPrompt] = react.default.useState("");
|
|
6625
|
+
const submitted = react.default.useRef(null);
|
|
6626
|
+
const outputRef = react.default.useRef(null);
|
|
6627
|
+
const followingRef = react.default.useRef(true);
|
|
6628
|
+
const isFork = Boolean(entry?.resumeSessionId && entry.controls.includes("attach"));
|
|
6629
|
+
react.default.useLayoutEffect(() => {
|
|
6630
|
+
followingRef.current = true;
|
|
6631
|
+
}, [entry?.id, mode]);
|
|
6632
|
+
react.default.useLayoutEffect(() => {
|
|
6633
|
+
const output = outputRef.current;
|
|
6634
|
+
if (output && isFork && mode === "live" && followingRef.current) output.scrollTop = output.scrollHeight;
|
|
6635
|
+
}, [
|
|
6636
|
+
entry?.id,
|
|
6637
|
+
isFork,
|
|
6638
|
+
mode,
|
|
6639
|
+
records
|
|
6640
|
+
]);
|
|
6641
|
+
react.default.useEffect(() => {
|
|
6642
|
+
setPrompt("");
|
|
6643
|
+
submitted.current = null;
|
|
6644
|
+
}, [entry?.id]);
|
|
6645
|
+
react.default.useEffect(() => {
|
|
6646
|
+
if (!entry || inputState?.taskId !== entry.sourceId || inputState.status === "pending") return;
|
|
6647
|
+
if (inputState.status === "sent" && submitted.current !== null) {
|
|
6648
|
+
const sent = submitted.current;
|
|
6649
|
+
setPrompt((current) => current.trim() === sent ? "" : current);
|
|
6650
|
+
}
|
|
6651
|
+
submitted.current = null;
|
|
6652
|
+
}, [entry, inputState]);
|
|
6361
6653
|
if (!entry) return null;
|
|
6362
6654
|
const described = describeExecutionEntryStatus(entry);
|
|
6363
6655
|
const canStop = onStop !== void 0 && entry.controls.includes("cancel");
|
|
6656
|
+
const ended = entry.status === "completed" || entry.status === "failed" || entry.status === "cancelled";
|
|
6364
6657
|
const askedLine = entry.headline?.text ?? entry.preview;
|
|
6365
6658
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6366
6659
|
role: "dialog",
|
|
@@ -6415,10 +6708,37 @@ function ExecutionDetailSheet({ entry, status, records, error, complete, onClose
|
|
|
6415
6708
|
entry.deniedToolCalls,
|
|
6416
6709
|
" tool ",
|
|
6417
6710
|
entry.deniedToolCalls === 1 ? "call was" : "calls were",
|
|
6418
|
-
"
|
|
6711
|
+
" ",
|
|
6712
|
+
"refused."
|
|
6419
6713
|
]
|
|
6420
6714
|
}),
|
|
6715
|
+
isFork && onShowEarlier && onShowLive && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6716
|
+
className: "flex gap-3 border-b border-subtle/15 px-4 py-2 text-[12px]",
|
|
6717
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6718
|
+
type: "button",
|
|
6719
|
+
onClick: onShowLive,
|
|
6720
|
+
disabled: mode === "live",
|
|
6721
|
+
children: "Live output"
|
|
6722
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6723
|
+
type: "button",
|
|
6724
|
+
onClick: onShowEarlier,
|
|
6725
|
+
disabled: mode === "transcript",
|
|
6726
|
+
children: "Earlier output"
|
|
6727
|
+
})]
|
|
6728
|
+
}),
|
|
6421
6729
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6730
|
+
ref: outputRef,
|
|
6731
|
+
role: isFork ? mode === "live" ? "log" : "region" : void 0,
|
|
6732
|
+
"aria-label": isFork ? mode === "live" ? "Live fork output" : "Earlier fork output" : void 0,
|
|
6733
|
+
"aria-live": isFork && mode === "live" ? "polite" : void 0,
|
|
6734
|
+
"aria-relevant": isFork && mode === "live" ? "additions text" : void 0,
|
|
6735
|
+
"aria-atomic": isFork && mode === "live" ? false : void 0,
|
|
6736
|
+
tabIndex: isFork ? 0 : void 0,
|
|
6737
|
+
onScroll: (event) => {
|
|
6738
|
+
if (!isFork || mode !== "live") return;
|
|
6739
|
+
const output = event.currentTarget;
|
|
6740
|
+
followingRef.current = output.scrollHeight - output.scrollTop - output.clientHeight <= FOLLOW_BOTTOM_MARGIN_PX;
|
|
6741
|
+
},
|
|
6422
6742
|
className: "flex-1 space-y-2 overflow-y-auto px-4 py-3",
|
|
6423
6743
|
children: [
|
|
6424
6744
|
records.map((record) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
@@ -6439,17 +6759,54 @@ function ExecutionDetailSheet({ entry, status, records, error, complete, onClose
|
|
|
6439
6759
|
className: "text-[13px] text-destructive",
|
|
6440
6760
|
children: error
|
|
6441
6761
|
}),
|
|
6442
|
-
|
|
6762
|
+
unreadable && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
6763
|
+
className: "text-[12px] text-warning",
|
|
6764
|
+
children: [
|
|
6765
|
+
"Some records could not be read: ",
|
|
6766
|
+
unreadable.locked,
|
|
6767
|
+
" locked, ",
|
|
6768
|
+
unreadable.noKeys,
|
|
6769
|
+
" without keys, ",
|
|
6770
|
+
unreadable.doesNotOpen,
|
|
6771
|
+
" unavailable."
|
|
6772
|
+
]
|
|
6773
|
+
}),
|
|
6774
|
+
(!isFork || mode === "transcript") && !complete && status !== "loading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6443
6775
|
type: "button",
|
|
6444
6776
|
onClick: onLoadMore,
|
|
6445
6777
|
className: "text-[12px] text-subtle underline hover:text-foreground",
|
|
6446
6778
|
children: "Load more"
|
|
6447
6779
|
}),
|
|
6448
|
-
!complete && status === "loading" && records.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6780
|
+
(!isFork || mode === "transcript") && !complete && status === "loading" && records.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
6449
6781
|
className: "text-[12px] text-subtle",
|
|
6450
6782
|
children: "Loading more…"
|
|
6451
6783
|
})
|
|
6452
6784
|
]
|
|
6785
|
+
}),
|
|
6786
|
+
isFork && !ended && entry.controls.includes("send") && onSend && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
|
|
6787
|
+
className: "border-t border-subtle/15 p-3",
|
|
6788
|
+
onSubmit: (event) => {
|
|
6789
|
+
event.preventDefault();
|
|
6790
|
+
if (!prompt.trim() || submitted.current !== null || inputState?.status === "pending") return;
|
|
6791
|
+
submitted.current = prompt.trim();
|
|
6792
|
+
onSend(submitted.current);
|
|
6793
|
+
},
|
|
6794
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
|
|
6795
|
+
"aria-label": "Message the fork",
|
|
6796
|
+
value: prompt,
|
|
6797
|
+
onChange: (event) => setPrompt(event.target.value),
|
|
6798
|
+
className: "w-full rounded border border-subtle/20 bg-background p-2 text-foreground"
|
|
6799
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6800
|
+
type: "submit",
|
|
6801
|
+
disabled: !prompt.trim() || inputState?.status === "pending",
|
|
6802
|
+
children: "Send to fork"
|
|
6803
|
+
})]
|
|
6804
|
+
}),
|
|
6805
|
+
isFork && ended && onContinue && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
6806
|
+
type: "button",
|
|
6807
|
+
onClick: onContinue,
|
|
6808
|
+
className: "border-t border-subtle/15 p-3 text-foreground",
|
|
6809
|
+
children: "Continue as session"
|
|
6453
6810
|
})
|
|
6454
6811
|
]
|
|
6455
6812
|
});
|
|
@@ -8127,7 +8484,8 @@ function SetupPanel({ onSetUp, disabled }) {
|
|
|
8127
8484
|
* The full desktop layout over an `IWsSessionState`. `surface` is an optional label shown next to the
|
|
8128
8485
|
* mark (e.g. "app").
|
|
8129
8486
|
*/
|
|
8130
|
-
function SessionSurface({ state, surface, personalUsageEnabled = false, onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor, appViewSandboxUrl }) {
|
|
8487
|
+
function SessionSurface({ state, surface, personalUsageEnabled: personalUsageRequested = false, draftMode = "persistent", onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor, appViewSandboxUrl }) {
|
|
8488
|
+
const readOnly = state.role === "observe";
|
|
8131
8489
|
const [view, setView] = (0, react.useState)("chat");
|
|
8132
8490
|
const { identity } = useProductIdentity();
|
|
8133
8491
|
const appViewHost = (0, react.useMemo)(() => ({
|
|
@@ -8146,6 +8504,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8146
8504
|
identity.displayName,
|
|
8147
8505
|
surface
|
|
8148
8506
|
]);
|
|
8507
|
+
const personalUsageEnabled = personalUsageRequested && !readOnly;
|
|
8149
8508
|
const composerRef = (0, react.useRef)(null);
|
|
8150
8509
|
const tasks = state.executionWorkspace?.entries ?? [];
|
|
8151
8510
|
const hasTasks = tasks.some((entry) => entry.kind !== "main_thread");
|
|
@@ -8165,7 +8524,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8165
8524
|
const hasWorkInProgress = hasTasks || state.scheduledTasks.length > 0 || currentGoal !== null;
|
|
8166
8525
|
const isEmpty = state.messages.length === 0 && !state.streamingText && !state.isThinking && state.activeTools.length === 0;
|
|
8167
8526
|
const setupRequired = state.sessionStatus?.setupRequired === true;
|
|
8168
|
-
const hasSessionList = (state.sessionListing ?? null) !== null || state.sessionsError?.code === "list_failed";
|
|
8527
|
+
const hasSessionList = !readOnly && ((state.sessionListing ?? null) !== null || state.sessionsError?.code === "list_failed");
|
|
8169
8528
|
const sidebarOpen = hasSessionList && state.sessionSidebarOpen;
|
|
8170
8529
|
const usage = personalUsageEnabled && view === "usage";
|
|
8171
8530
|
const project = view === "project";
|
|
@@ -8235,7 +8594,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8235
8594
|
connectionLost: state.connectionLost ?? false
|
|
8236
8595
|
}), usage ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
8237
8596
|
className: "min-h-0 flex-1",
|
|
8238
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PersonalUsageDashboard, { state }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8597
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PersonalUsageDashboard, { state }), readOnly ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8239
8598
|
layout: "modal",
|
|
8240
8599
|
prompts: state.pendingPrompts,
|
|
8241
8600
|
onAnswerPermission: state.answerPermission,
|
|
@@ -8247,7 +8606,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8247
8606
|
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ProjectPanel, {
|
|
8248
8607
|
state,
|
|
8249
8608
|
onOpenMemoryInEditor
|
|
8250
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8609
|
+
}), readOnly ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8251
8610
|
layout: "modal",
|
|
8252
8611
|
prompts: state.pendingPrompts,
|
|
8253
8612
|
onAnswerPermission: state.answerPermission,
|
|
@@ -8267,7 +8626,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8267
8626
|
}),
|
|
8268
8627
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
8269
8628
|
className: "min-h-0 flex-1 overflow-hidden",
|
|
8270
|
-
children: setupRequired ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupPanel, {
|
|
8629
|
+
children: setupRequired && !readOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SetupPanel, {
|
|
8271
8630
|
onSetUp: () => state.send({
|
|
8272
8631
|
type: "command",
|
|
8273
8632
|
name: "provider",
|
|
@@ -8275,7 +8634,7 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8275
8634
|
}),
|
|
8276
8635
|
disabled: state.status !== "connected"
|
|
8277
8636
|
}) : isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppViewHostContext.Provider, {
|
|
8278
|
-
value: appViewHost,
|
|
8637
|
+
value: readOnly ? null : appViewHost,
|
|
8279
8638
|
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationView, {
|
|
8280
8639
|
messages: state.messages,
|
|
8281
8640
|
activeTools: state.activeTools,
|
|
@@ -8290,13 +8649,13 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8290
8649
|
children: [
|
|
8291
8650
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(GoalBar, {
|
|
8292
8651
|
status: state.sessionStatus ?? null,
|
|
8293
|
-
onStop: () => state.send({
|
|
8652
|
+
onStop: readOnly ? void 0 : () => state.send({
|
|
8294
8653
|
type: "command",
|
|
8295
8654
|
name: "goal",
|
|
8296
8655
|
args: "cancel"
|
|
8297
8656
|
})
|
|
8298
8657
|
}),
|
|
8299
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8658
|
+
readOnly ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PermissionPrompt, {
|
|
8300
8659
|
layout: "dock",
|
|
8301
8660
|
prompts: state.pendingPrompts,
|
|
8302
8661
|
onAnswerPermission: state.answerPermission,
|
|
@@ -8304,8 +8663,13 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8304
8663
|
ownDriverId: state.ownDriverId,
|
|
8305
8664
|
onFocusReturn: () => composerRef.current?.focus()
|
|
8306
8665
|
}),
|
|
8307
|
-
|
|
8666
|
+
readOnly ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
8667
|
+
role: "status",
|
|
8668
|
+
className: "rounded-2xl bg-card px-4 py-3 text-center text-[14px] text-muted-foreground",
|
|
8669
|
+
children: "Read-only: you are watching this session."
|
|
8670
|
+
}) : setupRequired ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Composer, {
|
|
8308
8671
|
ref: composerRef,
|
|
8672
|
+
draftMode,
|
|
8309
8673
|
catalog: state.commandCatalog ?? null,
|
|
8310
8674
|
status: state.sessionStatus ?? null,
|
|
8311
8675
|
connected: state.status === "connected",
|
|
@@ -8358,13 +8722,13 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8358
8722
|
selectedEntryId: state.openEntryId ?? void 0,
|
|
8359
8723
|
onSelect: (entry) => state.openExecutionDetail(entry.id),
|
|
8360
8724
|
onReturnToConversation: () => state.closeExecutionDetail(),
|
|
8361
|
-
onStop: stopExecutionEntry,
|
|
8725
|
+
onStop: readOnly ? void 0 : stopExecutionEntry,
|
|
8362
8726
|
schedules: state.scheduledTasks,
|
|
8363
8727
|
goal: currentGoal,
|
|
8364
|
-
onPauseSchedule: state.pauseSchedule,
|
|
8365
|
-
onResumeSchedule: state.resumeSchedule,
|
|
8366
|
-
onDeleteSchedule: state.deleteSchedule,
|
|
8367
|
-
onCancelGoal: () => state.send({
|
|
8728
|
+
onPauseSchedule: readOnly ? void 0 : state.pauseSchedule,
|
|
8729
|
+
onResumeSchedule: readOnly ? void 0 : state.resumeSchedule,
|
|
8730
|
+
onDeleteSchedule: readOnly ? void 0 : state.deleteSchedule,
|
|
8731
|
+
onCancelGoal: readOnly ? void 0 : () => state.send({
|
|
8368
8732
|
type: "command",
|
|
8369
8733
|
name: "goal",
|
|
8370
8734
|
args: "cancel"
|
|
@@ -8379,14 +8743,27 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
8379
8743
|
complete: state.executionDetailComplete,
|
|
8380
8744
|
onClose: () => state.closeExecutionDetail(),
|
|
8381
8745
|
onLoadMore: () => state.loadMoreExecutionDetail(),
|
|
8382
|
-
onStop: openEntry ? () => stopExecutionEntry(openEntry) : void 0
|
|
8746
|
+
onStop: openEntry && !readOnly ? () => stopExecutionEntry(openEntry) : void 0,
|
|
8747
|
+
inputState: state.executionDetailInput,
|
|
8748
|
+
mode: state.executionDetailMode,
|
|
8749
|
+
unreadable: state.executionDetailUnreadable,
|
|
8750
|
+
onShowEarlier: state.showEarlierExecutionDetail,
|
|
8751
|
+
onShowLive: state.showLiveExecutionDetail,
|
|
8752
|
+
onSend: openEntry && !readOnly ? (prompt) => state.send({
|
|
8753
|
+
type: "send-background-task",
|
|
8754
|
+
taskId: openEntry.sourceId,
|
|
8755
|
+
input: { prompt }
|
|
8756
|
+
}) : void 0,
|
|
8757
|
+
onContinue: openEntry?.resumeSessionId && !readOnly ? () => {
|
|
8758
|
+
if (openEntry.resumeSessionId) state.switchSession(openEntry.resumeSessionId);
|
|
8759
|
+
} : void 0
|
|
8383
8760
|
})
|
|
8384
8761
|
]
|
|
8385
8762
|
})]
|
|
8386
8763
|
}),
|
|
8387
8764
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionNotices, { state }),
|
|
8388
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsScreen, { state }),
|
|
8389
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentSwitcherSheet, { state }),
|
|
8765
|
+
readOnly ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SettingsScreen, { state }),
|
|
8766
|
+
readOnly ? null : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentSwitcherSheet, { state }),
|
|
8390
8767
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(HelpSheet, {
|
|
8391
8768
|
open: state.helpOpen,
|
|
8392
8769
|
onClose: () => state.closeHelp(),
|
|
@@ -8582,6 +8959,7 @@ exports.SessionSurface = SessionSurface;
|
|
|
8582
8959
|
exports.SettingsScreen = SettingsScreen;
|
|
8583
8960
|
exports.applyPromptEvent = applyPromptEvent;
|
|
8584
8961
|
exports.askResponse = askResponse;
|
|
8962
|
+
exports.createDesktopSessionClient = createDesktopSessionClient;
|
|
8585
8963
|
exports.createWsSessionClient = createWsSessionClient;
|
|
8586
8964
|
exports.describeUiIntentForGui = describeUiIntentForGui;
|
|
8587
8965
|
exports.guiScreenForUiIntent = guiScreenForUiIntent;
|