@robota-sdk/agent-ui-web 3.0.0-beta.95 → 3.0.0-beta.99
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 +45 -0
- package/dist/browser/index.d.ts +19 -4
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +587 -60
- package/dist/browser/index.js.map +1 -1
- package/dist/node/index.cjs +586 -59
- package/dist/node/index.d.cts +19 -4
- package/dist/node/index.d.cts.map +1 -1
- package/dist/node/index.d.ts +19 -4
- package/dist/node/index.d.ts.map +1 -1
- package/dist/node/index.js +587 -60
- package/dist/node/index.js.map +1 -1
- package/docs/SPEC.md +7 -2
- package/package.json +6 -6
- package/src/app-view/bridge.ts +217 -0
- package/src/app-view/call-tool-result.ts +58 -0
- package/src/app-view/host-context.ts +37 -0
- package/src/app-view/types.ts +12 -0
- package/src/components/AppView.tsx +147 -0
- package/src/components/ConversationView.tsx +18 -1
- package/src/components/SessionSurface.tsx +39 -12
- package/src/components/SessionSurfaceChrome.tsx +6 -3
- package/src/hooks/server-message-handling.ts +7 -8
- package/src/hooks/session-client-types.ts +16 -1
- package/src/hooks/use-app-view-requests.ts +178 -0
- package/src/hooks/useSessionClient.ts +62 -2
- package/src/index.ts +7 -1
package/dist/browser/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useRef, useState } from "react";
|
|
1
|
+
import React, { createContext, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
2
|
import { decodeFrame, decodeServerMessage } from "@robota-sdk/agent-transport/client";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import ReactMarkdown from "react-markdown";
|
|
@@ -256,8 +256,8 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
256
256
|
command_result: "reducer-state",
|
|
257
257
|
messages: "reducer-state",
|
|
258
258
|
history: "intentionally-not-rendered",
|
|
259
|
-
history_changed: "
|
|
260
|
-
context: "
|
|
259
|
+
history_changed: "visible-notice",
|
|
260
|
+
context: "reducer-state",
|
|
261
261
|
turn_source: "intentionally-not-rendered",
|
|
262
262
|
commands: "reducer-state",
|
|
263
263
|
session_status: "reducer-state",
|
|
@@ -285,8 +285,8 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
285
285
|
project_status: "reducer-state",
|
|
286
286
|
project_diff: "reducer-state",
|
|
287
287
|
project_memory: "reducer-state",
|
|
288
|
-
app_view: "
|
|
289
|
-
app_tool_result: "
|
|
288
|
+
app_view: "reducer-state",
|
|
289
|
+
app_tool_result: "reducer-state",
|
|
290
290
|
background_task_event: "intentionally-not-rendered",
|
|
291
291
|
background_job_group_event: "intentionally-not-rendered",
|
|
292
292
|
plan_event: "intentionally-not-rendered",
|
|
@@ -311,10 +311,10 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
311
311
|
};
|
|
312
312
|
//#endregion
|
|
313
313
|
//#region src/hooks/use-agent-switcher-state.ts
|
|
314
|
-
let requestCounter$
|
|
315
|
-
function nextRequestId$
|
|
316
|
-
requestCounter$
|
|
317
|
-
return `${prefix}_${requestCounter$
|
|
314
|
+
let requestCounter$7 = 0;
|
|
315
|
+
function nextRequestId$7(prefix) {
|
|
316
|
+
requestCounter$7 += 1;
|
|
317
|
+
return `${prefix}_${requestCounter$7}_${Date.now()}`;
|
|
318
318
|
}
|
|
319
319
|
/**
|
|
320
320
|
* #3282 §4 part b-3: the agent switcher sheet's own correlated request/response state, the same
|
|
@@ -349,7 +349,7 @@ function useAgentSwitcherState(send) {
|
|
|
349
349
|
return true;
|
|
350
350
|
}, []);
|
|
351
351
|
const requestAgentDefinitions = useCallback(() => {
|
|
352
|
-
const requestId = nextRequestId$
|
|
352
|
+
const requestId = nextRequestId$7("get_agent_definitions");
|
|
353
353
|
listRequestRef.current = requestId;
|
|
354
354
|
send({
|
|
355
355
|
type: "get-agent-definitions",
|
|
@@ -372,7 +372,7 @@ function useAgentSwitcherState(send) {
|
|
|
372
372
|
setAgentSwitcherOpen(false);
|
|
373
373
|
}, []),
|
|
374
374
|
selectAgent: useCallback((agentType) => {
|
|
375
|
-
const requestId = nextRequestId$
|
|
375
|
+
const requestId = nextRequestId$7("agent_switch");
|
|
376
376
|
switchRequestRef.current = requestId;
|
|
377
377
|
setAgentSwitchMessage(null);
|
|
378
378
|
send({
|
|
@@ -393,6 +393,126 @@ function useAgentSwitcherState(send) {
|
|
|
393
393
|
};
|
|
394
394
|
}
|
|
395
395
|
//#endregion
|
|
396
|
+
//#region src/hooks/use-app-view-requests.ts
|
|
397
|
+
let requestCounter$6 = 0;
|
|
398
|
+
function nextRequestId$6(prefix) {
|
|
399
|
+
requestCounter$6 += 1;
|
|
400
|
+
return `${prefix}_${requestCounter$6}_${Date.now()}`;
|
|
401
|
+
}
|
|
402
|
+
const CONNECTION_CLOSED = "The connection to the session closed.";
|
|
403
|
+
/**
|
|
404
|
+
* The key a finished call's app view is read and called by, taken from the tool row's `appView`
|
|
405
|
+
* reference; `undefined` when the row gives none. Everything outside this module treats it as an
|
|
406
|
+
* opaque string — it is read here and put on the two wire messages below, nowhere else.
|
|
407
|
+
*/
|
|
408
|
+
function appViewKey(tool) {
|
|
409
|
+
return tool.appView?.viewId;
|
|
410
|
+
}
|
|
411
|
+
function readAppViewMessage(requestId, viewKey) {
|
|
412
|
+
return {
|
|
413
|
+
type: "read-app-view",
|
|
414
|
+
requestId,
|
|
415
|
+
viewId: viewKey
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
function callAppToolMessage(requestId, viewKey, name, args) {
|
|
419
|
+
return {
|
|
420
|
+
type: "call-app-tool",
|
|
421
|
+
requestId,
|
|
422
|
+
viewId: viewKey,
|
|
423
|
+
name,
|
|
424
|
+
arguments: args
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* The app-view host's two requests to the session, each a promise settled by the reply that echoes
|
|
429
|
+
* its `requestId` (`app_view` / `app_tool_result`, or a `protocol_error` naming it). Unlike the
|
|
430
|
+
* panels' latest-wins state, several can be outstanding at once (one per frame on screen), so each
|
|
431
|
+
* keeps its own entry. A reply that lost its connection never arrives: the owner of the connection
|
|
432
|
+
* rejects every outstanding request when it drops, is replaced or goes away (`rejectPendingAppRequests`).
|
|
433
|
+
*/
|
|
434
|
+
function useAppViewRequests(send, isConnected) {
|
|
435
|
+
const pendingRef = useRef(/* @__PURE__ */ new Map());
|
|
436
|
+
const [requestOwner] = useState(() => nextRequestId$6("app_request"));
|
|
437
|
+
const rejectPendingAppRequests = useCallback((reason = CONNECTION_CLOSED) => {
|
|
438
|
+
const pending = [...pendingRef.current.values()];
|
|
439
|
+
pendingRef.current.clear();
|
|
440
|
+
for (const request of pending) {
|
|
441
|
+
request.dispose();
|
|
442
|
+
request.reject(new Error(reason));
|
|
443
|
+
}
|
|
444
|
+
}, []);
|
|
445
|
+
const request = useCallback((prefix, message, pending, signal) => new Promise((resolve, reject) => {
|
|
446
|
+
if (!isConnected() || signal?.aborted) {
|
|
447
|
+
reject(/* @__PURE__ */ new Error(signal?.aborted ? "The app view closed." : CONNECTION_CLOSED));
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
const requestId = nextRequestId$6(`${requestOwner}_${prefix}`);
|
|
451
|
+
const dispose = () => signal?.removeEventListener("abort", abort);
|
|
452
|
+
const abort = () => {
|
|
453
|
+
pendingRef.current.delete(requestId);
|
|
454
|
+
dispose();
|
|
455
|
+
reject(/* @__PURE__ */ new Error("The app view closed."));
|
|
456
|
+
};
|
|
457
|
+
pendingRef.current.set(requestId, pending(resolve, reject, dispose));
|
|
458
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
459
|
+
try {
|
|
460
|
+
send(message(requestId));
|
|
461
|
+
} catch (error) {
|
|
462
|
+
pendingRef.current.delete(requestId);
|
|
463
|
+
dispose();
|
|
464
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
465
|
+
}
|
|
466
|
+
}), [
|
|
467
|
+
isConnected,
|
|
468
|
+
requestOwner,
|
|
469
|
+
send
|
|
470
|
+
]);
|
|
471
|
+
return {
|
|
472
|
+
readAppView: useCallback((viewKey, signal) => request("app_view", (requestId) => readAppViewMessage(requestId, viewKey), (resolve, reject, dispose) => ({
|
|
473
|
+
answer: "app_view",
|
|
474
|
+
resolve,
|
|
475
|
+
reject,
|
|
476
|
+
dispose
|
|
477
|
+
}), signal), [request]),
|
|
478
|
+
callAppTool: useCallback((viewKey, name, args, signal) => request("app_tool", (requestId) => callAppToolMessage(requestId, viewKey, name, args), (resolve, reject, dispose) => ({
|
|
479
|
+
answer: "app_tool_result",
|
|
480
|
+
resolve,
|
|
481
|
+
reject,
|
|
482
|
+
dispose
|
|
483
|
+
}), signal), [request]),
|
|
484
|
+
handleAppViewMessage: useCallback((msg) => {
|
|
485
|
+
if (msg.type === "app_view" || msg.type === "app_tool_result") {
|
|
486
|
+
const pending = pendingRef.current.get(msg.requestId);
|
|
487
|
+
if (pending === void 0 || pending.answer !== msg.type) return true;
|
|
488
|
+
pendingRef.current.delete(msg.requestId);
|
|
489
|
+
pending.dispose();
|
|
490
|
+
if (msg.type === "app_view" && pending.answer === "app_view") pending.resolve(msg.view);
|
|
491
|
+
else if (msg.type === "app_tool_result" && pending.answer === "app_tool_result") {
|
|
492
|
+
const { success, data, parts, error } = msg;
|
|
493
|
+
pending.resolve({
|
|
494
|
+
success,
|
|
495
|
+
...data !== void 0 ? { data } : {},
|
|
496
|
+
...parts !== void 0 ? { parts } : {},
|
|
497
|
+
...error !== void 0 ? { error } : {}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
return true;
|
|
501
|
+
}
|
|
502
|
+
if (msg.type === "protocol_error" && msg.requestId !== void 0) {
|
|
503
|
+
const pending = pendingRef.current.get(msg.requestId);
|
|
504
|
+
if (pending === void 0) return msg.requestId.startsWith(`${requestOwner}_`);
|
|
505
|
+
pendingRef.current.delete(msg.requestId);
|
|
506
|
+
pending.dispose();
|
|
507
|
+
pending.reject(new Error(msg.message));
|
|
508
|
+
return true;
|
|
509
|
+
}
|
|
510
|
+
return false;
|
|
511
|
+
}, [requestOwner]),
|
|
512
|
+
rejectPendingAppRequests
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
//#endregion
|
|
396
516
|
//#region src/hooks/use-execution-detail.ts
|
|
397
517
|
/**
|
|
398
518
|
* #3288 §1: the detail sheet's own correlated request/response state — which entry is open, its
|
|
@@ -1077,7 +1197,8 @@ function toolEndFields(state) {
|
|
|
1077
1197
|
...state.diffLines ? { diffLines: state.diffLines } : {},
|
|
1078
1198
|
...state.diffFile ? { diffFile: state.diffFile } : {},
|
|
1079
1199
|
...state.toolResultData !== void 0 ? { toolResultData: state.toolResultData } : {},
|
|
1080
|
-
...state.toolResultParts ? { toolResultParts: state.toolResultParts } : {}
|
|
1200
|
+
...state.toolResultParts ? { toolResultParts: state.toolResultParts } : {},
|
|
1201
|
+
...state.appView ? { appView: state.appView } : {}
|
|
1081
1202
|
};
|
|
1082
1203
|
}
|
|
1083
1204
|
/** Apply a `tool_end` to the ONE matching running tool across a turn's segments (never all of them). */
|
|
@@ -1175,6 +1296,7 @@ function buildConversationEntriesFromDisplaySegments(displaySegments) {
|
|
|
1175
1296
|
}
|
|
1176
1297
|
function useSessionClient(makeClient) {
|
|
1177
1298
|
const [status, setStatus] = useState("disconnected");
|
|
1299
|
+
const statusRef = useRef("disconnected");
|
|
1178
1300
|
const [messages, setMessages] = useState([]);
|
|
1179
1301
|
const [activeTools, setActiveTools] = useState([]);
|
|
1180
1302
|
const [streamingText, setStreamingText] = useState("");
|
|
@@ -1299,6 +1421,7 @@ function useSessionClient(makeClient) {
|
|
|
1299
1421
|
const { handleExecutionDetailMessage, closeExecutionDetail, ...executionDetailState } = useExecutionDetailState(send);
|
|
1300
1422
|
const { handleModelListMessage, ...modelListState } = useModelListState(send);
|
|
1301
1423
|
const { handleProjectMessage, ...projectPanelState } = useProjectPanelState(send);
|
|
1424
|
+
const { handleAppViewMessage, rejectPendingAppRequests, readAppView, callAppTool } = useAppViewRequests(send, useCallback(() => statusRef.current === "connected", []));
|
|
1302
1425
|
const { handleSessionsMessage, canListSessions, markCurrent, armRestore, ...sessionDirectoryState } = useSessionDirectoryState(send);
|
|
1303
1426
|
const { requestSessions, setSessionSidebarOpen } = sessionDirectoryState;
|
|
1304
1427
|
const { handleSettingsMessage, openSettings, refreshSettings, ...settingsState } = useSettingsState(send);
|
|
@@ -1306,12 +1429,16 @@ function useSessionClient(makeClient) {
|
|
|
1306
1429
|
settingsOpenRef.current = settingsState.settingsOpen;
|
|
1307
1430
|
const { handleAgentDefinitionsMessage, resolveSwitchResult, openAgentSwitcher, ...agentSwitcherState } = useAgentSwitcherState(send);
|
|
1308
1431
|
const { handleSchedulesMessage, requestSchedules, ...schedulesState } = useSchedulesState(send);
|
|
1432
|
+
const clearCompactionNotices = useCallback(() => {
|
|
1433
|
+
setSessionNotices((previous) => previous.filter((notice) => notice.kind !== "context-compacted"));
|
|
1434
|
+
}, []);
|
|
1309
1435
|
const handleMessage = useCallback((msg) => {
|
|
1310
1436
|
SERVER_MESSAGE_HANDLING[msg.type];
|
|
1311
1437
|
if (handleUsageMessage(msg)) return;
|
|
1312
1438
|
if (handleExecutionDetailMessage(msg)) return;
|
|
1313
1439
|
if (handleModelListMessage(msg)) return;
|
|
1314
1440
|
if (handleProjectMessage(msg)) return;
|
|
1441
|
+
if (handleAppViewMessage(msg)) return;
|
|
1315
1442
|
if (handleSessionsMessage(msg)) return;
|
|
1316
1443
|
if (handleSettingsMessage(msg)) return;
|
|
1317
1444
|
if (handleAgentDefinitionsMessage(msg)) return;
|
|
@@ -1448,9 +1575,11 @@ function useSessionClient(makeClient) {
|
|
|
1448
1575
|
requestSessions();
|
|
1449
1576
|
break;
|
|
1450
1577
|
case "session_switched":
|
|
1578
|
+
rejectPendingAppRequests("The active session changed.");
|
|
1451
1579
|
settleSessionChange();
|
|
1452
1580
|
sessionStatusRef.current = null;
|
|
1453
1581
|
setSessionStatus(null);
|
|
1582
|
+
clearCompactionNotices();
|
|
1454
1583
|
streamingTextRef.current = "";
|
|
1455
1584
|
turnSegmentsRef.current = [];
|
|
1456
1585
|
setStreamingText("");
|
|
@@ -1472,6 +1601,8 @@ function useSessionClient(makeClient) {
|
|
|
1472
1601
|
requestSchedules();
|
|
1473
1602
|
break;
|
|
1474
1603
|
case "history_cleared":
|
|
1604
|
+
clearCompactionNotices();
|
|
1605
|
+
rejectPendingAppRequests("The session history was cleared.");
|
|
1475
1606
|
streamingTextRef.current = "";
|
|
1476
1607
|
turnSegmentsRef.current = [];
|
|
1477
1608
|
setStreamingText("");
|
|
@@ -1557,6 +1688,25 @@ function useSessionClient(makeClient) {
|
|
|
1557
1688
|
setCommandCatalog(catalog);
|
|
1558
1689
|
break;
|
|
1559
1690
|
}
|
|
1691
|
+
case "context":
|
|
1692
|
+
if (sessionStatusRef.current !== null) {
|
|
1693
|
+
const next = {
|
|
1694
|
+
...sessionStatusRef.current,
|
|
1695
|
+
context: msg.state
|
|
1696
|
+
};
|
|
1697
|
+
sessionStatusRef.current = next;
|
|
1698
|
+
setSessionStatus(next);
|
|
1699
|
+
}
|
|
1700
|
+
break;
|
|
1701
|
+
case "history_changed": {
|
|
1702
|
+
const compact = msg.compact;
|
|
1703
|
+
if (compact?.trigger === "auto") setSessionNotices((previous) => [...previous, {
|
|
1704
|
+
id: nextId(),
|
|
1705
|
+
kind: "context-compacted",
|
|
1706
|
+
message: `Auto compacted context: ${Math.round(compact.beforeUsedPercentage)}% → ${Math.round(compact.afterUsedPercentage)}%`
|
|
1707
|
+
}]);
|
|
1708
|
+
break;
|
|
1709
|
+
}
|
|
1560
1710
|
case "session_status":
|
|
1561
1711
|
sessionStatusRef.current = msg.status;
|
|
1562
1712
|
setSessionStatus(msg.status);
|
|
@@ -1610,8 +1760,10 @@ function useSessionClient(makeClient) {
|
|
|
1610
1760
|
appendEntry,
|
|
1611
1761
|
canListSessions,
|
|
1612
1762
|
closeExecutionDetail,
|
|
1763
|
+
clearCompactionNotices,
|
|
1613
1764
|
finishTurn,
|
|
1614
1765
|
handleAgentDefinitionsMessage,
|
|
1766
|
+
handleAppViewMessage,
|
|
1615
1767
|
handleExecutionDetailMessage,
|
|
1616
1768
|
handleModelListMessage,
|
|
1617
1769
|
handleProjectMessage,
|
|
@@ -1626,6 +1778,7 @@ function useSessionClient(makeClient) {
|
|
|
1626
1778
|
requestSchedules,
|
|
1627
1779
|
requestSessions,
|
|
1628
1780
|
resolveSwitchResult,
|
|
1781
|
+
rejectPendingAppRequests,
|
|
1629
1782
|
send,
|
|
1630
1783
|
setSessionSidebarOpen,
|
|
1631
1784
|
settleSessionChange,
|
|
@@ -1644,7 +1797,12 @@ function useSessionClient(makeClient) {
|
|
|
1644
1797
|
}, []);
|
|
1645
1798
|
useEffect(() => {
|
|
1646
1799
|
const onStatusChange = (next) => {
|
|
1800
|
+
statusRef.current = next;
|
|
1647
1801
|
setStatus(next);
|
|
1802
|
+
if (next !== "connected") {
|
|
1803
|
+
rejectPendingAppRequests();
|
|
1804
|
+
clearCompactionNotices();
|
|
1805
|
+
}
|
|
1648
1806
|
if (next === "connected") {
|
|
1649
1807
|
commandsInFlightRef.current = 0;
|
|
1650
1808
|
silentCommandRequestIdsRef.current.clear();
|
|
@@ -1668,13 +1826,16 @@ function useSessionClient(makeClient) {
|
|
|
1668
1826
|
return () => {
|
|
1669
1827
|
client.disconnect();
|
|
1670
1828
|
clientRef.current = null;
|
|
1829
|
+
rejectPendingAppRequests();
|
|
1671
1830
|
};
|
|
1672
1831
|
}, [
|
|
1673
1832
|
makeClient,
|
|
1674
1833
|
handleMessage,
|
|
1834
|
+
clearCompactionNotices,
|
|
1675
1835
|
requestSessions,
|
|
1676
1836
|
requestSchedules,
|
|
1677
|
-
armRestore
|
|
1837
|
+
armRestore,
|
|
1838
|
+
rejectPendingAppRequests
|
|
1678
1839
|
]);
|
|
1679
1840
|
return {
|
|
1680
1841
|
status,
|
|
@@ -1706,6 +1867,8 @@ function useSessionClient(makeClient) {
|
|
|
1706
1867
|
openAgentSwitcher,
|
|
1707
1868
|
...agentSwitcherState,
|
|
1708
1869
|
...schedulesState,
|
|
1870
|
+
readAppView,
|
|
1871
|
+
callAppTool,
|
|
1709
1872
|
helpOpen,
|
|
1710
1873
|
openHelp,
|
|
1711
1874
|
closeHelp
|
|
@@ -1896,6 +2059,338 @@ function driverAttributionText(driverId) {
|
|
|
1896
2059
|
return `from ${humanDriverLabel(driverId)}`;
|
|
1897
2060
|
}
|
|
1898
2061
|
//#endregion
|
|
2062
|
+
//#region src/app-view/call-tool-result.ts
|
|
2063
|
+
function isRecord(value) {
|
|
2064
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2065
|
+
}
|
|
2066
|
+
function contentFromParts(parts) {
|
|
2067
|
+
const content = [];
|
|
2068
|
+
for (const part of parts ?? []) if (part.type === "text") content.push({
|
|
2069
|
+
type: "text",
|
|
2070
|
+
text: part.text
|
|
2071
|
+
});
|
|
2072
|
+
else if (part.type === "image_inline") content.push({
|
|
2073
|
+
type: "image",
|
|
2074
|
+
data: part.data,
|
|
2075
|
+
mimeType: part.mimeType
|
|
2076
|
+
});
|
|
2077
|
+
else if (part.type === "audio_inline") content.push({
|
|
2078
|
+
type: "audio",
|
|
2079
|
+
data: part.data,
|
|
2080
|
+
mimeType: part.mimeType
|
|
2081
|
+
});
|
|
2082
|
+
return content;
|
|
2083
|
+
}
|
|
2084
|
+
function outcomeToCallToolResult(outcome) {
|
|
2085
|
+
let content = contentFromParts(outcome.parts);
|
|
2086
|
+
if (content.length === 0 && typeof outcome.data === "string") content = [{
|
|
2087
|
+
type: "text",
|
|
2088
|
+
text: outcome.data
|
|
2089
|
+
}];
|
|
2090
|
+
if (!outcome.success && outcome.error !== void 0) content = [...content, {
|
|
2091
|
+
type: "text",
|
|
2092
|
+
text: outcome.error
|
|
2093
|
+
}];
|
|
2094
|
+
return {
|
|
2095
|
+
content,
|
|
2096
|
+
...isRecord(outcome.data) ? { structuredContent: outcome.data } : {},
|
|
2097
|
+
isError: !outcome.success
|
|
2098
|
+
};
|
|
2099
|
+
}
|
|
2100
|
+
function toCallToolResult(tool, view) {
|
|
2101
|
+
let content = contentFromParts(tool.toolResultParts);
|
|
2102
|
+
if (content.length === 0 && tool.toolResultData !== void 0) content = [{
|
|
2103
|
+
type: "text",
|
|
2104
|
+
text: tool.toolResultData
|
|
2105
|
+
}];
|
|
2106
|
+
return {
|
|
2107
|
+
content,
|
|
2108
|
+
...isRecord(view.structuredContent) ? { structuredContent: view.structuredContent } : {},
|
|
2109
|
+
isError: tool.status === "error"
|
|
2110
|
+
};
|
|
2111
|
+
}
|
|
2112
|
+
//#endregion
|
|
2113
|
+
//#region src/app-view/bridge.ts
|
|
2114
|
+
const APP_VIEW_PROTOCOL_VERSION = "2026-01-26";
|
|
2115
|
+
const INVALID_PARAMS = -32602;
|
|
2116
|
+
const METHOD_NOT_FOUND = -32601;
|
|
2117
|
+
const HOST_ERROR = -32e3;
|
|
2118
|
+
const SANDBOX_PROXY_READY = "ui/notifications/sandbox-proxy-ready";
|
|
2119
|
+
const SANDBOX_RESOURCE_READY = "ui/notifications/sandbox-resource-ready";
|
|
2120
|
+
function isPlainObject(value) {
|
|
2121
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2122
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2123
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
2124
|
+
}
|
|
2125
|
+
function errorMessage(error) {
|
|
2126
|
+
return error instanceof Error ? error.message : String(error);
|
|
2127
|
+
}
|
|
2128
|
+
function createAppViewBridge(options) {
|
|
2129
|
+
const { view } = options;
|
|
2130
|
+
let disposed = false;
|
|
2131
|
+
let sandboxAnswered = false;
|
|
2132
|
+
let initialization = "uninitialized";
|
|
2133
|
+
let callInFlight = false;
|
|
2134
|
+
const respond = (id, result) => {
|
|
2135
|
+
options.post({
|
|
2136
|
+
jsonrpc: "2.0",
|
|
2137
|
+
id,
|
|
2138
|
+
result
|
|
2139
|
+
});
|
|
2140
|
+
};
|
|
2141
|
+
const refuse = (id, code, message) => {
|
|
2142
|
+
options.post({
|
|
2143
|
+
jsonrpc: "2.0",
|
|
2144
|
+
id,
|
|
2145
|
+
error: {
|
|
2146
|
+
code,
|
|
2147
|
+
message
|
|
2148
|
+
}
|
|
2149
|
+
});
|
|
2150
|
+
};
|
|
2151
|
+
const callTool = (id, params) => {
|
|
2152
|
+
if (initialization !== "ready") {
|
|
2153
|
+
refuse(id, HOST_ERROR, "This app view has not initialized.");
|
|
2154
|
+
return;
|
|
2155
|
+
}
|
|
2156
|
+
const name = isPlainObject(params) ? params["name"] : void 0;
|
|
2157
|
+
const runtimeName = typeof name === "string" && Object.hasOwn(view.callableTools, name) ? view.callableTools[name] : void 0;
|
|
2158
|
+
if (!isPlainObject(params) || runtimeName === void 0) {
|
|
2159
|
+
refuse(id, INVALID_PARAMS, "This tool is not available to this view.");
|
|
2160
|
+
return;
|
|
2161
|
+
}
|
|
2162
|
+
const rawArguments = params["arguments"] === void 0 ? {} : params["arguments"];
|
|
2163
|
+
if (!isPlainObject(rawArguments)) {
|
|
2164
|
+
refuse(id, INVALID_PARAMS, "Tool arguments must be an object.");
|
|
2165
|
+
return;
|
|
2166
|
+
}
|
|
2167
|
+
if (callInFlight) {
|
|
2168
|
+
refuse(id, HOST_ERROR, "Another tool call from this view is still running.");
|
|
2169
|
+
return;
|
|
2170
|
+
}
|
|
2171
|
+
callInFlight = true;
|
|
2172
|
+
let outcome;
|
|
2173
|
+
try {
|
|
2174
|
+
outcome = options.callAppTool(runtimeName, rawArguments);
|
|
2175
|
+
} catch (error) {
|
|
2176
|
+
outcome = Promise.reject(error);
|
|
2177
|
+
}
|
|
2178
|
+
outcome.then((settled) => {
|
|
2179
|
+
callInFlight = false;
|
|
2180
|
+
if (!disposed) respond(id, outcomeToCallToolResult(settled));
|
|
2181
|
+
}, (error) => {
|
|
2182
|
+
callInFlight = false;
|
|
2183
|
+
if (!disposed) refuse(id, HOST_ERROR, errorMessage(error));
|
|
2184
|
+
});
|
|
2185
|
+
};
|
|
2186
|
+
const handleRequest = (id, method, params) => {
|
|
2187
|
+
switch (method) {
|
|
2188
|
+
case "ui/initialize":
|
|
2189
|
+
if (initialization === "uninitialized") initialization = "initializing";
|
|
2190
|
+
respond(id, {
|
|
2191
|
+
protocolVersion: APP_VIEW_PROTOCOL_VERSION,
|
|
2192
|
+
hostCapabilities: { serverTools: {} },
|
|
2193
|
+
hostInfo: options.hostInfo,
|
|
2194
|
+
hostContext: options.hostContext
|
|
2195
|
+
});
|
|
2196
|
+
return;
|
|
2197
|
+
case "tools/call":
|
|
2198
|
+
callTool(id, params);
|
|
2199
|
+
return;
|
|
2200
|
+
case "ping":
|
|
2201
|
+
respond(id, {});
|
|
2202
|
+
return;
|
|
2203
|
+
case "ui/request-display-mode":
|
|
2204
|
+
respond(id, { mode: "inline" });
|
|
2205
|
+
return;
|
|
2206
|
+
default: refuse(id, METHOD_NOT_FOUND, `This host does not support ${method}.`);
|
|
2207
|
+
}
|
|
2208
|
+
};
|
|
2209
|
+
const handleNotification = (method, params) => {
|
|
2210
|
+
switch (method) {
|
|
2211
|
+
case SANDBOX_PROXY_READY:
|
|
2212
|
+
if (sandboxAnswered) return;
|
|
2213
|
+
sandboxAnswered = true;
|
|
2214
|
+
options.post({
|
|
2215
|
+
jsonrpc: "2.0",
|
|
2216
|
+
method: SANDBOX_RESOURCE_READY,
|
|
2217
|
+
params: { html: view.html }
|
|
2218
|
+
});
|
|
2219
|
+
return;
|
|
2220
|
+
case "ui/notifications/initialized":
|
|
2221
|
+
if (initialization !== "initializing") return;
|
|
2222
|
+
initialization = "ready";
|
|
2223
|
+
options.post({
|
|
2224
|
+
jsonrpc: "2.0",
|
|
2225
|
+
method: "ui/notifications/tool-input",
|
|
2226
|
+
params: { arguments: view.toolInput ?? {} }
|
|
2227
|
+
});
|
|
2228
|
+
options.post({
|
|
2229
|
+
jsonrpc: "2.0",
|
|
2230
|
+
method: "ui/notifications/tool-result",
|
|
2231
|
+
params: options.toolResult
|
|
2232
|
+
});
|
|
2233
|
+
return;
|
|
2234
|
+
case "ui/notifications/size-changed": {
|
|
2235
|
+
const height = isPlainObject(params) ? params["height"] : void 0;
|
|
2236
|
+
if (typeof height !== "number" || !Number.isFinite(height)) return;
|
|
2237
|
+
options.onHeight(Math.min(640, Math.max(48, Math.round(height))));
|
|
2238
|
+
return;
|
|
2239
|
+
}
|
|
2240
|
+
default: return;
|
|
2241
|
+
}
|
|
2242
|
+
};
|
|
2243
|
+
return {
|
|
2244
|
+
handleMessage(event) {
|
|
2245
|
+
if (disposed) return;
|
|
2246
|
+
const frame = options.source();
|
|
2247
|
+
if (frame === null || event.source !== frame) return;
|
|
2248
|
+
const data = event.data;
|
|
2249
|
+
if (!isPlainObject(data) || data["jsonrpc"] !== "2.0") return;
|
|
2250
|
+
const { method, id } = data;
|
|
2251
|
+
if (typeof method !== "string") return;
|
|
2252
|
+
if (id === void 0) handleNotification(method, data["params"]);
|
|
2253
|
+
else if (typeof id === "string" || typeof id === "number") handleRequest(id, method, data["params"]);
|
|
2254
|
+
},
|
|
2255
|
+
dispose() {
|
|
2256
|
+
if (disposed) return;
|
|
2257
|
+
disposed = true;
|
|
2258
|
+
if (initialization === "ready") options.post({
|
|
2259
|
+
jsonrpc: "2.0",
|
|
2260
|
+
id: "teardown",
|
|
2261
|
+
method: "ui/resource-teardown",
|
|
2262
|
+
params: { reason: "closed" }
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
};
|
|
2266
|
+
}
|
|
2267
|
+
//#endregion
|
|
2268
|
+
//#region src/app-view/host-context.ts
|
|
2269
|
+
const AppViewHostContext = createContext(null);
|
|
2270
|
+
function readAppViewHostContext(platform) {
|
|
2271
|
+
return {
|
|
2272
|
+
theme: typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
|
|
2273
|
+
displayMode: "inline",
|
|
2274
|
+
availableDisplayModes: ["inline"],
|
|
2275
|
+
containerDimensions: { maxHeight: 640 },
|
|
2276
|
+
platform,
|
|
2277
|
+
locale: typeof navigator === "undefined" ? "en-US" : navigator.language,
|
|
2278
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
2279
|
+
};
|
|
2280
|
+
}
|
|
2281
|
+
//#endregion
|
|
2282
|
+
//#region src/components/AppView.tsx
|
|
2283
|
+
const INITIAL_HEIGHT_PX = 160;
|
|
2284
|
+
const APP_VIEW_UNAVAILABLE_TEXT = "This tool returned an app view, which this window cannot show.";
|
|
2285
|
+
function AppView({ tool }) {
|
|
2286
|
+
const host = useContext(AppViewHostContext);
|
|
2287
|
+
if (tool.appView === void 0) return null;
|
|
2288
|
+
const viewKey = appViewKey(tool);
|
|
2289
|
+
if (host === null || host.sandboxUrl === void 0 || viewKey === void 0) return /* @__PURE__ */ jsx("p", {
|
|
2290
|
+
className: "text-[13px] text-subtle",
|
|
2291
|
+
children: APP_VIEW_UNAVAILABLE_TEXT
|
|
2292
|
+
});
|
|
2293
|
+
return /* @__PURE__ */ jsx(AppViewFrame, {
|
|
2294
|
+
tool,
|
|
2295
|
+
host,
|
|
2296
|
+
sandboxUrl: host.sandboxUrl,
|
|
2297
|
+
viewKey,
|
|
2298
|
+
sourceId: tool.appView.sourceId
|
|
2299
|
+
}, viewKey);
|
|
2300
|
+
}
|
|
2301
|
+
function AppViewFrame({ tool, host, sandboxUrl, viewKey, sourceId }) {
|
|
2302
|
+
const [load, setLoad] = useState({ status: "loading" });
|
|
2303
|
+
const [height, setHeight] = useState(INITIAL_HEIGHT_PX);
|
|
2304
|
+
const frameRef = useRef(null);
|
|
2305
|
+
const toolRef = useRef(tool);
|
|
2306
|
+
toolRef.current = tool;
|
|
2307
|
+
useEffect(() => {
|
|
2308
|
+
const controller = new AbortController();
|
|
2309
|
+
setLoad({ status: "loading" });
|
|
2310
|
+
host.readAppView(viewKey, controller.signal).then((view) => {
|
|
2311
|
+
if (!controller.signal.aborted) setLoad({
|
|
2312
|
+
status: "ready",
|
|
2313
|
+
view,
|
|
2314
|
+
host,
|
|
2315
|
+
sandboxUrl,
|
|
2316
|
+
signal: controller.signal
|
|
2317
|
+
});
|
|
2318
|
+
}, (error) => {
|
|
2319
|
+
if (!controller.signal.aborted) setLoad({
|
|
2320
|
+
status: "failed",
|
|
2321
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2322
|
+
});
|
|
2323
|
+
});
|
|
2324
|
+
return () => {
|
|
2325
|
+
controller.abort();
|
|
2326
|
+
};
|
|
2327
|
+
}, [
|
|
2328
|
+
viewKey,
|
|
2329
|
+
host.readAppView,
|
|
2330
|
+
host.callAppTool,
|
|
2331
|
+
sandboxUrl
|
|
2332
|
+
]);
|
|
2333
|
+
const ready = load.status === "ready" && load.host.readAppView === host.readAppView && load.host.callAppTool === host.callAppTool && load.sandboxUrl === sandboxUrl ? load : null;
|
|
2334
|
+
useLayoutEffect(() => {
|
|
2335
|
+
const frame = frameRef.current;
|
|
2336
|
+
if (ready === null || frame === null) return void 0;
|
|
2337
|
+
const current = ready.host;
|
|
2338
|
+
const view = ready.view;
|
|
2339
|
+
const bridge = createAppViewBridge({
|
|
2340
|
+
view,
|
|
2341
|
+
toolResult: toCallToolResult(toolRef.current, view),
|
|
2342
|
+
hostInfo: current.hostInfo,
|
|
2343
|
+
hostContext: readAppViewHostContext(current.platform),
|
|
2344
|
+
post: (message) => frame.contentWindow?.postMessage(message, "*"),
|
|
2345
|
+
source: () => frame.contentWindow,
|
|
2346
|
+
callAppTool: (name, args) => current.callAppTool(viewKey, name, args, ready.signal),
|
|
2347
|
+
onHeight: setHeight
|
|
2348
|
+
});
|
|
2349
|
+
const onMessage = (event) => bridge.handleMessage(event);
|
|
2350
|
+
window.addEventListener("message", onMessage);
|
|
2351
|
+
return () => {
|
|
2352
|
+
window.removeEventListener("message", onMessage);
|
|
2353
|
+
bridge.dispose();
|
|
2354
|
+
};
|
|
2355
|
+
}, [ready, viewKey]);
|
|
2356
|
+
if (load.status === "failed") return /* @__PURE__ */ jsxs("p", {
|
|
2357
|
+
className: "text-[13px] text-subtle",
|
|
2358
|
+
children: [
|
|
2359
|
+
"The app from ",
|
|
2360
|
+
sourceId,
|
|
2361
|
+
" could not be shown: ",
|
|
2362
|
+
load.reason
|
|
2363
|
+
]
|
|
2364
|
+
});
|
|
2365
|
+
if (ready === null) return /* @__PURE__ */ jsxs("p", {
|
|
2366
|
+
className: "text-[13px] text-subtle",
|
|
2367
|
+
children: [
|
|
2368
|
+
"Loading the app from ",
|
|
2369
|
+
sourceId,
|
|
2370
|
+
"…"
|
|
2371
|
+
]
|
|
2372
|
+
});
|
|
2373
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
2374
|
+
className: "my-1 flex flex-col gap-1",
|
|
2375
|
+
children: [/* @__PURE__ */ jsxs("p", {
|
|
2376
|
+
className: "text-[12px] text-subtle",
|
|
2377
|
+
children: ["App from ", sourceId]
|
|
2378
|
+
}), /* @__PURE__ */ jsx("iframe", {
|
|
2379
|
+
ref: frameRef,
|
|
2380
|
+
sandbox: "allow-scripts",
|
|
2381
|
+
src: sandboxUrl,
|
|
2382
|
+
title: `App from ${sourceId}`,
|
|
2383
|
+
referrerPolicy: "no-referrer",
|
|
2384
|
+
"data-testid": "app-view-frame",
|
|
2385
|
+
style: {
|
|
2386
|
+
width: "100%",
|
|
2387
|
+
height
|
|
2388
|
+
},
|
|
2389
|
+
className: "block rounded-lg border border-border bg-card"
|
|
2390
|
+
})]
|
|
2391
|
+
});
|
|
2392
|
+
}
|
|
2393
|
+
//#endregion
|
|
1899
2394
|
//#region src/components/DiffLines.tsx
|
|
1900
2395
|
/**
|
|
1901
2396
|
* #3288: renders a unified diff already built server-side (the ONE diff builder, in
|
|
@@ -2409,42 +2904,49 @@ function ToolGroup({ tools }) {
|
|
|
2409
2904
|
if (visible.length === 0) return null;
|
|
2410
2905
|
return /* @__PURE__ */ jsxs("div", {
|
|
2411
2906
|
className: "text-[14px]",
|
|
2412
|
-
children: [
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2438
|
-
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2907
|
+
children: [
|
|
2908
|
+
/* @__PURE__ */ jsxs("button", {
|
|
2909
|
+
type: "button",
|
|
2910
|
+
"aria-expanded": open,
|
|
2911
|
+
onClick: () => setOpen((value) => !value),
|
|
2912
|
+
className: "group -mx-2 flex max-w-full items-center gap-2.5 rounded-lg px-2 py-1 text-left text-muted-foreground hover:bg-hover hover:text-foreground",
|
|
2913
|
+
children: [
|
|
2914
|
+
/* @__PURE__ */ jsx(Wrench, {
|
|
2915
|
+
size: 15,
|
|
2916
|
+
strokeWidth: 1.75,
|
|
2917
|
+
className: "flex-shrink-0 text-subtle"
|
|
2918
|
+
}),
|
|
2919
|
+
/* @__PURE__ */ jsxs("span", {
|
|
2920
|
+
className: "flex-shrink-0",
|
|
2921
|
+
children: [
|
|
2922
|
+
visible.length,
|
|
2923
|
+
" tool ",
|
|
2924
|
+
visible.length === 1 ? "call" : "calls"
|
|
2925
|
+
]
|
|
2926
|
+
}),
|
|
2927
|
+
/* @__PURE__ */ jsx("span", {
|
|
2928
|
+
className: "min-w-0 truncate text-subtle",
|
|
2929
|
+
children: names
|
|
2930
|
+
}),
|
|
2931
|
+
failed > 0 && /* @__PURE__ */ jsxs("span", {
|
|
2932
|
+
className: "flex-shrink-0 rounded-md bg-destructive/12 px-1.5 text-[12.5px] text-destructive",
|
|
2933
|
+
children: [failed, " failed"]
|
|
2934
|
+
}),
|
|
2935
|
+
/* @__PURE__ */ jsx(ChevronRight, {
|
|
2936
|
+
size: 14,
|
|
2937
|
+
className: `flex-shrink-0 text-subtle transition-transform ${open ? "rotate-90" : ""}`
|
|
2938
|
+
})
|
|
2939
|
+
]
|
|
2940
|
+
}),
|
|
2941
|
+
visible.filter((tool) => tool.appView !== void 0).map((tool) => /* @__PURE__ */ jsx("div", {
|
|
2942
|
+
className: "pl-[25px]",
|
|
2943
|
+
children: /* @__PURE__ */ jsx(AppView, { tool })
|
|
2944
|
+
}, tool.id)),
|
|
2945
|
+
open && /* @__PURE__ */ jsx("div", {
|
|
2946
|
+
className: "mt-0.5 flex flex-col pl-[25px]",
|
|
2947
|
+
children: visible.map((tool) => /* @__PURE__ */ jsx(ToolCard, { tool }, tool.id))
|
|
2948
|
+
})
|
|
2949
|
+
]
|
|
2448
2950
|
});
|
|
2449
2951
|
}
|
|
2450
2952
|
/** #3288: a turn that changed files ends with this compact row — a click opens that file's diff. */
|
|
@@ -2570,7 +3072,10 @@ function ConversationView({ messages, activeTools, streamingText, isThinking, ow
|
|
|
2570
3072
|
isThinking && !streamingText && /* @__PURE__ */ jsx(ThinkingIndicator, {}),
|
|
2571
3073
|
activeTools.some((tool) => !tool.internal) && /* @__PURE__ */ jsx("div", {
|
|
2572
3074
|
className: "flex flex-col",
|
|
2573
|
-
children: activeTools.filter((tool) => !tool.internal).map((tool) => /* @__PURE__ */ jsx(ToolCard, { tool }, tool.
|
|
3075
|
+
children: activeTools.filter((tool) => !tool.internal).map((tool) => /* @__PURE__ */ jsxs(React.Fragment, { children: [/* @__PURE__ */ jsx(ToolCard, { tool }), tool.appView !== void 0 && tool.status !== "running" && /* @__PURE__ */ jsx("div", {
|
|
3076
|
+
className: "pl-[25px]",
|
|
3077
|
+
children: /* @__PURE__ */ jsx(AppView, { tool })
|
|
3078
|
+
})] }, tool.id))
|
|
2574
3079
|
}),
|
|
2575
3080
|
streamingText && /* @__PURE__ */ jsx(AgentBlock, {
|
|
2576
3081
|
content: streamingText,
|
|
@@ -6438,7 +6943,7 @@ function noticeSentence(notice) {
|
|
|
6438
6943
|
}
|
|
6439
6944
|
}
|
|
6440
6945
|
/**
|
|
6441
|
-
* Session and protocol failures, as dismissible toasts over the top-right corner: they stay until
|
|
6946
|
+
* Session outcomes and protocol failures, as dismissible toasts over the top-right corner: they stay until
|
|
6442
6947
|
* dismissed, and never push the conversation or the composer out of view. Command output is not
|
|
6443
6948
|
* here — it belongs to the conversation. A provider error classified at the wire boundary shows one
|
|
6444
6949
|
* plain sentence with the next step; its raw text sits behind "Details" rather than as the headline.
|
|
@@ -6450,11 +6955,12 @@ function SessionNotices({ state }) {
|
|
|
6450
6955
|
className: "pointer-events-none absolute right-4 top-16 z-40 flex w-[min(400px,calc(100%-32px))] flex-col gap-2",
|
|
6451
6956
|
children: notices.slice(-3).map((notice) => {
|
|
6452
6957
|
const sentence = noticeSentence(notice);
|
|
6958
|
+
const compacted = notice.kind === "context-compacted";
|
|
6453
6959
|
return /* @__PURE__ */ jsxs("div", {
|
|
6454
|
-
role: "alert",
|
|
6960
|
+
role: compacted ? "status" : "alert",
|
|
6455
6961
|
className: "gui-rise pointer-events-auto flex items-start gap-3 rounded-xl bg-popover px-4 py-3 text-[14px] leading-snug text-popover-foreground shadow-xl shadow-black/30",
|
|
6456
6962
|
children: [
|
|
6457
|
-
/* @__PURE__ */ jsx("span", { className:
|
|
6963
|
+
/* @__PURE__ */ jsx("span", { className: `mt-[7px] h-2 w-2 flex-shrink-0 rounded-full ${compacted ? "bg-primary" : "bg-destructive"}` }),
|
|
6458
6964
|
/* @__PURE__ */ jsxs("span", {
|
|
6459
6965
|
className: "max-h-40 flex-1 overflow-y-auto whitespace-pre-wrap break-words",
|
|
6460
6966
|
children: [sentence ?? notice.message, sentence && /* @__PURE__ */ jsxs("details", {
|
|
@@ -7510,6 +8016,7 @@ const NARROW_WINDOW_QUERY = "(max-width: 767.98px)";
|
|
|
7510
8016
|
*/
|
|
7511
8017
|
/** The column every part of the conversation shares, so messages, prompt and composer line up. */
|
|
7512
8018
|
const COLUMN = "mx-auto w-full max-w-[760px] px-6";
|
|
8019
|
+
const APP_VIEW_HOST_VERSION = "1.0.0";
|
|
7513
8020
|
/**
|
|
7514
8021
|
* Designed empty state shown before the first turn. Renders in the same slot as `ConversationView`
|
|
7515
8022
|
* (never together), so it carries the same `main` landmark and label (#3289 §3 review) — nothing
|
|
@@ -7576,8 +8083,25 @@ function SetupPanel({ onSetUp, disabled }) {
|
|
|
7576
8083
|
* The full desktop layout over an `IWsSessionState`. `surface` is an optional label shown next to the
|
|
7577
8084
|
* mark (e.g. "app").
|
|
7578
8085
|
*/
|
|
7579
|
-
function SessionSurface({ state, surface, personalUsageEnabled = false, onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor }) {
|
|
8086
|
+
function SessionSurface({ state, surface, personalUsageEnabled = false, onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor, appViewSandboxUrl }) {
|
|
7580
8087
|
const [view, setView] = useState("chat");
|
|
8088
|
+
const { identity } = useProductIdentity();
|
|
8089
|
+
const appViewHost = useMemo(() => ({
|
|
8090
|
+
...appViewSandboxUrl !== void 0 ? { sandboxUrl: appViewSandboxUrl } : {},
|
|
8091
|
+
readAppView: state.readAppView,
|
|
8092
|
+
callAppTool: state.callAppTool,
|
|
8093
|
+
hostInfo: {
|
|
8094
|
+
name: identity.displayName,
|
|
8095
|
+
version: APP_VIEW_HOST_VERSION
|
|
8096
|
+
},
|
|
8097
|
+
platform: surface === "app" ? "desktop" : "web"
|
|
8098
|
+
}), [
|
|
8099
|
+
appViewSandboxUrl,
|
|
8100
|
+
state.readAppView,
|
|
8101
|
+
state.callAppTool,
|
|
8102
|
+
identity.displayName,
|
|
8103
|
+
surface
|
|
8104
|
+
]);
|
|
7581
8105
|
const composerRef = useRef(null);
|
|
7582
8106
|
const tasks = state.executionWorkspace?.entries ?? [];
|
|
7583
8107
|
const hasTasks = tasks.some((entry) => entry.kind !== "main_thread");
|
|
@@ -7706,12 +8230,15 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
7706
8230
|
args: "add"
|
|
7707
8231
|
}),
|
|
7708
8232
|
disabled: state.status !== "connected"
|
|
7709
|
-
}) : isEmpty ? /* @__PURE__ */ jsx(EmptyState, {}) : /* @__PURE__ */ jsx(
|
|
7710
|
-
|
|
7711
|
-
|
|
7712
|
-
|
|
7713
|
-
|
|
7714
|
-
|
|
8233
|
+
}) : isEmpty ? /* @__PURE__ */ jsx(EmptyState, {}) : /* @__PURE__ */ jsx(AppViewHostContext.Provider, {
|
|
8234
|
+
value: appViewHost,
|
|
8235
|
+
children: /* @__PURE__ */ jsx(ConversationView, {
|
|
8236
|
+
messages: state.messages,
|
|
8237
|
+
activeTools: state.activeTools,
|
|
8238
|
+
streamingText: state.streamingText,
|
|
8239
|
+
isThinking: state.isThinking,
|
|
8240
|
+
ownDriverId: state.ownDriverId
|
|
8241
|
+
})
|
|
7715
8242
|
})
|
|
7716
8243
|
}),
|
|
7717
8244
|
/* @__PURE__ */ jsxs("div", {
|