@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/node/index.cjs
CHANGED
|
@@ -282,8 +282,8 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
282
282
|
command_result: "reducer-state",
|
|
283
283
|
messages: "reducer-state",
|
|
284
284
|
history: "intentionally-not-rendered",
|
|
285
|
-
history_changed: "
|
|
286
|
-
context: "
|
|
285
|
+
history_changed: "visible-notice",
|
|
286
|
+
context: "reducer-state",
|
|
287
287
|
turn_source: "intentionally-not-rendered",
|
|
288
288
|
commands: "reducer-state",
|
|
289
289
|
session_status: "reducer-state",
|
|
@@ -311,8 +311,8 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
311
311
|
project_status: "reducer-state",
|
|
312
312
|
project_diff: "reducer-state",
|
|
313
313
|
project_memory: "reducer-state",
|
|
314
|
-
app_view: "
|
|
315
|
-
app_tool_result: "
|
|
314
|
+
app_view: "reducer-state",
|
|
315
|
+
app_tool_result: "reducer-state",
|
|
316
316
|
background_task_event: "intentionally-not-rendered",
|
|
317
317
|
background_job_group_event: "intentionally-not-rendered",
|
|
318
318
|
plan_event: "intentionally-not-rendered",
|
|
@@ -337,10 +337,10 @@ const SERVER_MESSAGE_HANDLING = {
|
|
|
337
337
|
};
|
|
338
338
|
//#endregion
|
|
339
339
|
//#region src/hooks/use-agent-switcher-state.ts
|
|
340
|
-
let requestCounter$
|
|
341
|
-
function nextRequestId$
|
|
342
|
-
requestCounter$
|
|
343
|
-
return `${prefix}_${requestCounter$
|
|
340
|
+
let requestCounter$7 = 0;
|
|
341
|
+
function nextRequestId$7(prefix) {
|
|
342
|
+
requestCounter$7 += 1;
|
|
343
|
+
return `${prefix}_${requestCounter$7}_${Date.now()}`;
|
|
344
344
|
}
|
|
345
345
|
/**
|
|
346
346
|
* #3282 §4 part b-3: the agent switcher sheet's own correlated request/response state, the same
|
|
@@ -375,7 +375,7 @@ function useAgentSwitcherState(send) {
|
|
|
375
375
|
return true;
|
|
376
376
|
}, []);
|
|
377
377
|
const requestAgentDefinitions = (0, react.useCallback)(() => {
|
|
378
|
-
const requestId = nextRequestId$
|
|
378
|
+
const requestId = nextRequestId$7("get_agent_definitions");
|
|
379
379
|
listRequestRef.current = requestId;
|
|
380
380
|
send({
|
|
381
381
|
type: "get-agent-definitions",
|
|
@@ -398,7 +398,7 @@ function useAgentSwitcherState(send) {
|
|
|
398
398
|
setAgentSwitcherOpen(false);
|
|
399
399
|
}, []),
|
|
400
400
|
selectAgent: (0, react.useCallback)((agentType) => {
|
|
401
|
-
const requestId = nextRequestId$
|
|
401
|
+
const requestId = nextRequestId$7("agent_switch");
|
|
402
402
|
switchRequestRef.current = requestId;
|
|
403
403
|
setAgentSwitchMessage(null);
|
|
404
404
|
send({
|
|
@@ -419,6 +419,126 @@ function useAgentSwitcherState(send) {
|
|
|
419
419
|
};
|
|
420
420
|
}
|
|
421
421
|
//#endregion
|
|
422
|
+
//#region src/hooks/use-app-view-requests.ts
|
|
423
|
+
let requestCounter$6 = 0;
|
|
424
|
+
function nextRequestId$6(prefix) {
|
|
425
|
+
requestCounter$6 += 1;
|
|
426
|
+
return `${prefix}_${requestCounter$6}_${Date.now()}`;
|
|
427
|
+
}
|
|
428
|
+
const CONNECTION_CLOSED = "The connection to the session closed.";
|
|
429
|
+
/**
|
|
430
|
+
* The key a finished call's app view is read and called by, taken from the tool row's `appView`
|
|
431
|
+
* reference; `undefined` when the row gives none. Everything outside this module treats it as an
|
|
432
|
+
* opaque string — it is read here and put on the two wire messages below, nowhere else.
|
|
433
|
+
*/
|
|
434
|
+
function appViewKey(tool) {
|
|
435
|
+
return tool.appView?.viewId;
|
|
436
|
+
}
|
|
437
|
+
function readAppViewMessage(requestId, viewKey) {
|
|
438
|
+
return {
|
|
439
|
+
type: "read-app-view",
|
|
440
|
+
requestId,
|
|
441
|
+
viewId: viewKey
|
|
442
|
+
};
|
|
443
|
+
}
|
|
444
|
+
function callAppToolMessage(requestId, viewKey, name, args) {
|
|
445
|
+
return {
|
|
446
|
+
type: "call-app-tool",
|
|
447
|
+
requestId,
|
|
448
|
+
viewId: viewKey,
|
|
449
|
+
name,
|
|
450
|
+
arguments: args
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* The app-view host's two requests to the session, each a promise settled by the reply that echoes
|
|
455
|
+
* its `requestId` (`app_view` / `app_tool_result`, or a `protocol_error` naming it). Unlike the
|
|
456
|
+
* panels' latest-wins state, several can be outstanding at once (one per frame on screen), so each
|
|
457
|
+
* keeps its own entry. A reply that lost its connection never arrives: the owner of the connection
|
|
458
|
+
* rejects every outstanding request when it drops, is replaced or goes away (`rejectPendingAppRequests`).
|
|
459
|
+
*/
|
|
460
|
+
function useAppViewRequests(send, isConnected) {
|
|
461
|
+
const pendingRef = (0, react.useRef)(/* @__PURE__ */ new Map());
|
|
462
|
+
const [requestOwner] = (0, react.useState)(() => nextRequestId$6("app_request"));
|
|
463
|
+
const rejectPendingAppRequests = (0, react.useCallback)((reason = CONNECTION_CLOSED) => {
|
|
464
|
+
const pending = [...pendingRef.current.values()];
|
|
465
|
+
pendingRef.current.clear();
|
|
466
|
+
for (const request of pending) {
|
|
467
|
+
request.dispose();
|
|
468
|
+
request.reject(new Error(reason));
|
|
469
|
+
}
|
|
470
|
+
}, []);
|
|
471
|
+
const request = (0, react.useCallback)((prefix, message, pending, signal) => new Promise((resolve, reject) => {
|
|
472
|
+
if (!isConnected() || signal?.aborted) {
|
|
473
|
+
reject(/* @__PURE__ */ new Error(signal?.aborted ? "The app view closed." : CONNECTION_CLOSED));
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
const requestId = nextRequestId$6(`${requestOwner}_${prefix}`);
|
|
477
|
+
const dispose = () => signal?.removeEventListener("abort", abort);
|
|
478
|
+
const abort = () => {
|
|
479
|
+
pendingRef.current.delete(requestId);
|
|
480
|
+
dispose();
|
|
481
|
+
reject(/* @__PURE__ */ new Error("The app view closed."));
|
|
482
|
+
};
|
|
483
|
+
pendingRef.current.set(requestId, pending(resolve, reject, dispose));
|
|
484
|
+
signal?.addEventListener("abort", abort, { once: true });
|
|
485
|
+
try {
|
|
486
|
+
send(message(requestId));
|
|
487
|
+
} catch (error) {
|
|
488
|
+
pendingRef.current.delete(requestId);
|
|
489
|
+
dispose();
|
|
490
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
491
|
+
}
|
|
492
|
+
}), [
|
|
493
|
+
isConnected,
|
|
494
|
+
requestOwner,
|
|
495
|
+
send
|
|
496
|
+
]);
|
|
497
|
+
return {
|
|
498
|
+
readAppView: (0, react.useCallback)((viewKey, signal) => request("app_view", (requestId) => readAppViewMessage(requestId, viewKey), (resolve, reject, dispose) => ({
|
|
499
|
+
answer: "app_view",
|
|
500
|
+
resolve,
|
|
501
|
+
reject,
|
|
502
|
+
dispose
|
|
503
|
+
}), signal), [request]),
|
|
504
|
+
callAppTool: (0, react.useCallback)((viewKey, name, args, signal) => request("app_tool", (requestId) => callAppToolMessage(requestId, viewKey, name, args), (resolve, reject, dispose) => ({
|
|
505
|
+
answer: "app_tool_result",
|
|
506
|
+
resolve,
|
|
507
|
+
reject,
|
|
508
|
+
dispose
|
|
509
|
+
}), signal), [request]),
|
|
510
|
+
handleAppViewMessage: (0, react.useCallback)((msg) => {
|
|
511
|
+
if (msg.type === "app_view" || msg.type === "app_tool_result") {
|
|
512
|
+
const pending = pendingRef.current.get(msg.requestId);
|
|
513
|
+
if (pending === void 0 || pending.answer !== msg.type) return true;
|
|
514
|
+
pendingRef.current.delete(msg.requestId);
|
|
515
|
+
pending.dispose();
|
|
516
|
+
if (msg.type === "app_view" && pending.answer === "app_view") pending.resolve(msg.view);
|
|
517
|
+
else if (msg.type === "app_tool_result" && pending.answer === "app_tool_result") {
|
|
518
|
+
const { success, data, parts, error } = msg;
|
|
519
|
+
pending.resolve({
|
|
520
|
+
success,
|
|
521
|
+
...data !== void 0 ? { data } : {},
|
|
522
|
+
...parts !== void 0 ? { parts } : {},
|
|
523
|
+
...error !== void 0 ? { error } : {}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return true;
|
|
527
|
+
}
|
|
528
|
+
if (msg.type === "protocol_error" && msg.requestId !== void 0) {
|
|
529
|
+
const pending = pendingRef.current.get(msg.requestId);
|
|
530
|
+
if (pending === void 0) return msg.requestId.startsWith(`${requestOwner}_`);
|
|
531
|
+
pendingRef.current.delete(msg.requestId);
|
|
532
|
+
pending.dispose();
|
|
533
|
+
pending.reject(new Error(msg.message));
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
return false;
|
|
537
|
+
}, [requestOwner]),
|
|
538
|
+
rejectPendingAppRequests
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
//#endregion
|
|
422
542
|
//#region src/hooks/use-execution-detail.ts
|
|
423
543
|
/**
|
|
424
544
|
* #3288 §1: the detail sheet's own correlated request/response state — which entry is open, its
|
|
@@ -1103,7 +1223,8 @@ function toolEndFields(state) {
|
|
|
1103
1223
|
...state.diffLines ? { diffLines: state.diffLines } : {},
|
|
1104
1224
|
...state.diffFile ? { diffFile: state.diffFile } : {},
|
|
1105
1225
|
...state.toolResultData !== void 0 ? { toolResultData: state.toolResultData } : {},
|
|
1106
|
-
...state.toolResultParts ? { toolResultParts: state.toolResultParts } : {}
|
|
1226
|
+
...state.toolResultParts ? { toolResultParts: state.toolResultParts } : {},
|
|
1227
|
+
...state.appView ? { appView: state.appView } : {}
|
|
1107
1228
|
};
|
|
1108
1229
|
}
|
|
1109
1230
|
/** Apply a `tool_end` to the ONE matching running tool across a turn's segments (never all of them). */
|
|
@@ -1201,6 +1322,7 @@ function buildConversationEntriesFromDisplaySegments(displaySegments) {
|
|
|
1201
1322
|
}
|
|
1202
1323
|
function useSessionClient(makeClient) {
|
|
1203
1324
|
const [status, setStatus] = (0, react.useState)("disconnected");
|
|
1325
|
+
const statusRef = (0, react.useRef)("disconnected");
|
|
1204
1326
|
const [messages, setMessages] = (0, react.useState)([]);
|
|
1205
1327
|
const [activeTools, setActiveTools] = (0, react.useState)([]);
|
|
1206
1328
|
const [streamingText, setStreamingText] = (0, react.useState)("");
|
|
@@ -1325,6 +1447,7 @@ function useSessionClient(makeClient) {
|
|
|
1325
1447
|
const { handleExecutionDetailMessage, closeExecutionDetail, ...executionDetailState } = useExecutionDetailState(send);
|
|
1326
1448
|
const { handleModelListMessage, ...modelListState } = useModelListState(send);
|
|
1327
1449
|
const { handleProjectMessage, ...projectPanelState } = useProjectPanelState(send);
|
|
1450
|
+
const { handleAppViewMessage, rejectPendingAppRequests, readAppView, callAppTool } = useAppViewRequests(send, (0, react.useCallback)(() => statusRef.current === "connected", []));
|
|
1328
1451
|
const { handleSessionsMessage, canListSessions, markCurrent, armRestore, ...sessionDirectoryState } = useSessionDirectoryState(send);
|
|
1329
1452
|
const { requestSessions, setSessionSidebarOpen } = sessionDirectoryState;
|
|
1330
1453
|
const { handleSettingsMessage, openSettings, refreshSettings, ...settingsState } = useSettingsState(send);
|
|
@@ -1332,12 +1455,16 @@ function useSessionClient(makeClient) {
|
|
|
1332
1455
|
settingsOpenRef.current = settingsState.settingsOpen;
|
|
1333
1456
|
const { handleAgentDefinitionsMessage, resolveSwitchResult, openAgentSwitcher, ...agentSwitcherState } = useAgentSwitcherState(send);
|
|
1334
1457
|
const { handleSchedulesMessage, requestSchedules, ...schedulesState } = useSchedulesState(send);
|
|
1458
|
+
const clearCompactionNotices = (0, react.useCallback)(() => {
|
|
1459
|
+
setSessionNotices((previous) => previous.filter((notice) => notice.kind !== "context-compacted"));
|
|
1460
|
+
}, []);
|
|
1335
1461
|
const handleMessage = (0, react.useCallback)((msg) => {
|
|
1336
1462
|
SERVER_MESSAGE_HANDLING[msg.type];
|
|
1337
1463
|
if (handleUsageMessage(msg)) return;
|
|
1338
1464
|
if (handleExecutionDetailMessage(msg)) return;
|
|
1339
1465
|
if (handleModelListMessage(msg)) return;
|
|
1340
1466
|
if (handleProjectMessage(msg)) return;
|
|
1467
|
+
if (handleAppViewMessage(msg)) return;
|
|
1341
1468
|
if (handleSessionsMessage(msg)) return;
|
|
1342
1469
|
if (handleSettingsMessage(msg)) return;
|
|
1343
1470
|
if (handleAgentDefinitionsMessage(msg)) return;
|
|
@@ -1474,9 +1601,11 @@ function useSessionClient(makeClient) {
|
|
|
1474
1601
|
requestSessions();
|
|
1475
1602
|
break;
|
|
1476
1603
|
case "session_switched":
|
|
1604
|
+
rejectPendingAppRequests("The active session changed.");
|
|
1477
1605
|
settleSessionChange();
|
|
1478
1606
|
sessionStatusRef.current = null;
|
|
1479
1607
|
setSessionStatus(null);
|
|
1608
|
+
clearCompactionNotices();
|
|
1480
1609
|
streamingTextRef.current = "";
|
|
1481
1610
|
turnSegmentsRef.current = [];
|
|
1482
1611
|
setStreamingText("");
|
|
@@ -1498,6 +1627,8 @@ function useSessionClient(makeClient) {
|
|
|
1498
1627
|
requestSchedules();
|
|
1499
1628
|
break;
|
|
1500
1629
|
case "history_cleared":
|
|
1630
|
+
clearCompactionNotices();
|
|
1631
|
+
rejectPendingAppRequests("The session history was cleared.");
|
|
1501
1632
|
streamingTextRef.current = "";
|
|
1502
1633
|
turnSegmentsRef.current = [];
|
|
1503
1634
|
setStreamingText("");
|
|
@@ -1583,6 +1714,25 @@ function useSessionClient(makeClient) {
|
|
|
1583
1714
|
setCommandCatalog(catalog);
|
|
1584
1715
|
break;
|
|
1585
1716
|
}
|
|
1717
|
+
case "context":
|
|
1718
|
+
if (sessionStatusRef.current !== null) {
|
|
1719
|
+
const next = {
|
|
1720
|
+
...sessionStatusRef.current,
|
|
1721
|
+
context: msg.state
|
|
1722
|
+
};
|
|
1723
|
+
sessionStatusRef.current = next;
|
|
1724
|
+
setSessionStatus(next);
|
|
1725
|
+
}
|
|
1726
|
+
break;
|
|
1727
|
+
case "history_changed": {
|
|
1728
|
+
const compact = msg.compact;
|
|
1729
|
+
if (compact?.trigger === "auto") setSessionNotices((previous) => [...previous, {
|
|
1730
|
+
id: nextId(),
|
|
1731
|
+
kind: "context-compacted",
|
|
1732
|
+
message: `Auto compacted context: ${Math.round(compact.beforeUsedPercentage)}% → ${Math.round(compact.afterUsedPercentage)}%`
|
|
1733
|
+
}]);
|
|
1734
|
+
break;
|
|
1735
|
+
}
|
|
1586
1736
|
case "session_status":
|
|
1587
1737
|
sessionStatusRef.current = msg.status;
|
|
1588
1738
|
setSessionStatus(msg.status);
|
|
@@ -1636,8 +1786,10 @@ function useSessionClient(makeClient) {
|
|
|
1636
1786
|
appendEntry,
|
|
1637
1787
|
canListSessions,
|
|
1638
1788
|
closeExecutionDetail,
|
|
1789
|
+
clearCompactionNotices,
|
|
1639
1790
|
finishTurn,
|
|
1640
1791
|
handleAgentDefinitionsMessage,
|
|
1792
|
+
handleAppViewMessage,
|
|
1641
1793
|
handleExecutionDetailMessage,
|
|
1642
1794
|
handleModelListMessage,
|
|
1643
1795
|
handleProjectMessage,
|
|
@@ -1652,6 +1804,7 @@ function useSessionClient(makeClient) {
|
|
|
1652
1804
|
requestSchedules,
|
|
1653
1805
|
requestSessions,
|
|
1654
1806
|
resolveSwitchResult,
|
|
1807
|
+
rejectPendingAppRequests,
|
|
1655
1808
|
send,
|
|
1656
1809
|
setSessionSidebarOpen,
|
|
1657
1810
|
settleSessionChange,
|
|
@@ -1670,7 +1823,12 @@ function useSessionClient(makeClient) {
|
|
|
1670
1823
|
}, []);
|
|
1671
1824
|
(0, react.useEffect)(() => {
|
|
1672
1825
|
const onStatusChange = (next) => {
|
|
1826
|
+
statusRef.current = next;
|
|
1673
1827
|
setStatus(next);
|
|
1828
|
+
if (next !== "connected") {
|
|
1829
|
+
rejectPendingAppRequests();
|
|
1830
|
+
clearCompactionNotices();
|
|
1831
|
+
}
|
|
1674
1832
|
if (next === "connected") {
|
|
1675
1833
|
commandsInFlightRef.current = 0;
|
|
1676
1834
|
silentCommandRequestIdsRef.current.clear();
|
|
@@ -1694,13 +1852,16 @@ function useSessionClient(makeClient) {
|
|
|
1694
1852
|
return () => {
|
|
1695
1853
|
client.disconnect();
|
|
1696
1854
|
clientRef.current = null;
|
|
1855
|
+
rejectPendingAppRequests();
|
|
1697
1856
|
};
|
|
1698
1857
|
}, [
|
|
1699
1858
|
makeClient,
|
|
1700
1859
|
handleMessage,
|
|
1860
|
+
clearCompactionNotices,
|
|
1701
1861
|
requestSessions,
|
|
1702
1862
|
requestSchedules,
|
|
1703
|
-
armRestore
|
|
1863
|
+
armRestore,
|
|
1864
|
+
rejectPendingAppRequests
|
|
1704
1865
|
]);
|
|
1705
1866
|
return {
|
|
1706
1867
|
status,
|
|
@@ -1732,6 +1893,8 @@ function useSessionClient(makeClient) {
|
|
|
1732
1893
|
openAgentSwitcher,
|
|
1733
1894
|
...agentSwitcherState,
|
|
1734
1895
|
...schedulesState,
|
|
1896
|
+
readAppView,
|
|
1897
|
+
callAppTool,
|
|
1735
1898
|
helpOpen,
|
|
1736
1899
|
openHelp,
|
|
1737
1900
|
closeHelp
|
|
@@ -1922,6 +2085,338 @@ function driverAttributionText(driverId) {
|
|
|
1922
2085
|
return `from ${humanDriverLabel(driverId)}`;
|
|
1923
2086
|
}
|
|
1924
2087
|
//#endregion
|
|
2088
|
+
//#region src/app-view/call-tool-result.ts
|
|
2089
|
+
function isRecord(value) {
|
|
2090
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2091
|
+
}
|
|
2092
|
+
function contentFromParts(parts) {
|
|
2093
|
+
const content = [];
|
|
2094
|
+
for (const part of parts ?? []) if (part.type === "text") content.push({
|
|
2095
|
+
type: "text",
|
|
2096
|
+
text: part.text
|
|
2097
|
+
});
|
|
2098
|
+
else if (part.type === "image_inline") content.push({
|
|
2099
|
+
type: "image",
|
|
2100
|
+
data: part.data,
|
|
2101
|
+
mimeType: part.mimeType
|
|
2102
|
+
});
|
|
2103
|
+
else if (part.type === "audio_inline") content.push({
|
|
2104
|
+
type: "audio",
|
|
2105
|
+
data: part.data,
|
|
2106
|
+
mimeType: part.mimeType
|
|
2107
|
+
});
|
|
2108
|
+
return content;
|
|
2109
|
+
}
|
|
2110
|
+
function outcomeToCallToolResult(outcome) {
|
|
2111
|
+
let content = contentFromParts(outcome.parts);
|
|
2112
|
+
if (content.length === 0 && typeof outcome.data === "string") content = [{
|
|
2113
|
+
type: "text",
|
|
2114
|
+
text: outcome.data
|
|
2115
|
+
}];
|
|
2116
|
+
if (!outcome.success && outcome.error !== void 0) content = [...content, {
|
|
2117
|
+
type: "text",
|
|
2118
|
+
text: outcome.error
|
|
2119
|
+
}];
|
|
2120
|
+
return {
|
|
2121
|
+
content,
|
|
2122
|
+
...isRecord(outcome.data) ? { structuredContent: outcome.data } : {},
|
|
2123
|
+
isError: !outcome.success
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function toCallToolResult(tool, view) {
|
|
2127
|
+
let content = contentFromParts(tool.toolResultParts);
|
|
2128
|
+
if (content.length === 0 && tool.toolResultData !== void 0) content = [{
|
|
2129
|
+
type: "text",
|
|
2130
|
+
text: tool.toolResultData
|
|
2131
|
+
}];
|
|
2132
|
+
return {
|
|
2133
|
+
content,
|
|
2134
|
+
...isRecord(view.structuredContent) ? { structuredContent: view.structuredContent } : {},
|
|
2135
|
+
isError: tool.status === "error"
|
|
2136
|
+
};
|
|
2137
|
+
}
|
|
2138
|
+
//#endregion
|
|
2139
|
+
//#region src/app-view/bridge.ts
|
|
2140
|
+
const APP_VIEW_PROTOCOL_VERSION = "2026-01-26";
|
|
2141
|
+
const INVALID_PARAMS = -32602;
|
|
2142
|
+
const METHOD_NOT_FOUND = -32601;
|
|
2143
|
+
const HOST_ERROR = -32e3;
|
|
2144
|
+
const SANDBOX_PROXY_READY = "ui/notifications/sandbox-proxy-ready";
|
|
2145
|
+
const SANDBOX_RESOURCE_READY = "ui/notifications/sandbox-resource-ready";
|
|
2146
|
+
function isPlainObject(value) {
|
|
2147
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return false;
|
|
2148
|
+
const prototype = Object.getPrototypeOf(value);
|
|
2149
|
+
return prototype === null || Object.getPrototypeOf(prototype) === null;
|
|
2150
|
+
}
|
|
2151
|
+
function errorMessage(error) {
|
|
2152
|
+
return error instanceof Error ? error.message : String(error);
|
|
2153
|
+
}
|
|
2154
|
+
function createAppViewBridge(options) {
|
|
2155
|
+
const { view } = options;
|
|
2156
|
+
let disposed = false;
|
|
2157
|
+
let sandboxAnswered = false;
|
|
2158
|
+
let initialization = "uninitialized";
|
|
2159
|
+
let callInFlight = false;
|
|
2160
|
+
const respond = (id, result) => {
|
|
2161
|
+
options.post({
|
|
2162
|
+
jsonrpc: "2.0",
|
|
2163
|
+
id,
|
|
2164
|
+
result
|
|
2165
|
+
});
|
|
2166
|
+
};
|
|
2167
|
+
const refuse = (id, code, message) => {
|
|
2168
|
+
options.post({
|
|
2169
|
+
jsonrpc: "2.0",
|
|
2170
|
+
id,
|
|
2171
|
+
error: {
|
|
2172
|
+
code,
|
|
2173
|
+
message
|
|
2174
|
+
}
|
|
2175
|
+
});
|
|
2176
|
+
};
|
|
2177
|
+
const callTool = (id, params) => {
|
|
2178
|
+
if (initialization !== "ready") {
|
|
2179
|
+
refuse(id, HOST_ERROR, "This app view has not initialized.");
|
|
2180
|
+
return;
|
|
2181
|
+
}
|
|
2182
|
+
const name = isPlainObject(params) ? params["name"] : void 0;
|
|
2183
|
+
const runtimeName = typeof name === "string" && Object.hasOwn(view.callableTools, name) ? view.callableTools[name] : void 0;
|
|
2184
|
+
if (!isPlainObject(params) || runtimeName === void 0) {
|
|
2185
|
+
refuse(id, INVALID_PARAMS, "This tool is not available to this view.");
|
|
2186
|
+
return;
|
|
2187
|
+
}
|
|
2188
|
+
const rawArguments = params["arguments"] === void 0 ? {} : params["arguments"];
|
|
2189
|
+
if (!isPlainObject(rawArguments)) {
|
|
2190
|
+
refuse(id, INVALID_PARAMS, "Tool arguments must be an object.");
|
|
2191
|
+
return;
|
|
2192
|
+
}
|
|
2193
|
+
if (callInFlight) {
|
|
2194
|
+
refuse(id, HOST_ERROR, "Another tool call from this view is still running.");
|
|
2195
|
+
return;
|
|
2196
|
+
}
|
|
2197
|
+
callInFlight = true;
|
|
2198
|
+
let outcome;
|
|
2199
|
+
try {
|
|
2200
|
+
outcome = options.callAppTool(runtimeName, rawArguments);
|
|
2201
|
+
} catch (error) {
|
|
2202
|
+
outcome = Promise.reject(error);
|
|
2203
|
+
}
|
|
2204
|
+
outcome.then((settled) => {
|
|
2205
|
+
callInFlight = false;
|
|
2206
|
+
if (!disposed) respond(id, outcomeToCallToolResult(settled));
|
|
2207
|
+
}, (error) => {
|
|
2208
|
+
callInFlight = false;
|
|
2209
|
+
if (!disposed) refuse(id, HOST_ERROR, errorMessage(error));
|
|
2210
|
+
});
|
|
2211
|
+
};
|
|
2212
|
+
const handleRequest = (id, method, params) => {
|
|
2213
|
+
switch (method) {
|
|
2214
|
+
case "ui/initialize":
|
|
2215
|
+
if (initialization === "uninitialized") initialization = "initializing";
|
|
2216
|
+
respond(id, {
|
|
2217
|
+
protocolVersion: APP_VIEW_PROTOCOL_VERSION,
|
|
2218
|
+
hostCapabilities: { serverTools: {} },
|
|
2219
|
+
hostInfo: options.hostInfo,
|
|
2220
|
+
hostContext: options.hostContext
|
|
2221
|
+
});
|
|
2222
|
+
return;
|
|
2223
|
+
case "tools/call":
|
|
2224
|
+
callTool(id, params);
|
|
2225
|
+
return;
|
|
2226
|
+
case "ping":
|
|
2227
|
+
respond(id, {});
|
|
2228
|
+
return;
|
|
2229
|
+
case "ui/request-display-mode":
|
|
2230
|
+
respond(id, { mode: "inline" });
|
|
2231
|
+
return;
|
|
2232
|
+
default: refuse(id, METHOD_NOT_FOUND, `This host does not support ${method}.`);
|
|
2233
|
+
}
|
|
2234
|
+
};
|
|
2235
|
+
const handleNotification = (method, params) => {
|
|
2236
|
+
switch (method) {
|
|
2237
|
+
case SANDBOX_PROXY_READY:
|
|
2238
|
+
if (sandboxAnswered) return;
|
|
2239
|
+
sandboxAnswered = true;
|
|
2240
|
+
options.post({
|
|
2241
|
+
jsonrpc: "2.0",
|
|
2242
|
+
method: SANDBOX_RESOURCE_READY,
|
|
2243
|
+
params: { html: view.html }
|
|
2244
|
+
});
|
|
2245
|
+
return;
|
|
2246
|
+
case "ui/notifications/initialized":
|
|
2247
|
+
if (initialization !== "initializing") return;
|
|
2248
|
+
initialization = "ready";
|
|
2249
|
+
options.post({
|
|
2250
|
+
jsonrpc: "2.0",
|
|
2251
|
+
method: "ui/notifications/tool-input",
|
|
2252
|
+
params: { arguments: view.toolInput ?? {} }
|
|
2253
|
+
});
|
|
2254
|
+
options.post({
|
|
2255
|
+
jsonrpc: "2.0",
|
|
2256
|
+
method: "ui/notifications/tool-result",
|
|
2257
|
+
params: options.toolResult
|
|
2258
|
+
});
|
|
2259
|
+
return;
|
|
2260
|
+
case "ui/notifications/size-changed": {
|
|
2261
|
+
const height = isPlainObject(params) ? params["height"] : void 0;
|
|
2262
|
+
if (typeof height !== "number" || !Number.isFinite(height)) return;
|
|
2263
|
+
options.onHeight(Math.min(640, Math.max(48, Math.round(height))));
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
default: return;
|
|
2267
|
+
}
|
|
2268
|
+
};
|
|
2269
|
+
return {
|
|
2270
|
+
handleMessage(event) {
|
|
2271
|
+
if (disposed) return;
|
|
2272
|
+
const frame = options.source();
|
|
2273
|
+
if (frame === null || event.source !== frame) return;
|
|
2274
|
+
const data = event.data;
|
|
2275
|
+
if (!isPlainObject(data) || data["jsonrpc"] !== "2.0") return;
|
|
2276
|
+
const { method, id } = data;
|
|
2277
|
+
if (typeof method !== "string") return;
|
|
2278
|
+
if (id === void 0) handleNotification(method, data["params"]);
|
|
2279
|
+
else if (typeof id === "string" || typeof id === "number") handleRequest(id, method, data["params"]);
|
|
2280
|
+
},
|
|
2281
|
+
dispose() {
|
|
2282
|
+
if (disposed) return;
|
|
2283
|
+
disposed = true;
|
|
2284
|
+
if (initialization === "ready") options.post({
|
|
2285
|
+
jsonrpc: "2.0",
|
|
2286
|
+
id: "teardown",
|
|
2287
|
+
method: "ui/resource-teardown",
|
|
2288
|
+
params: { reason: "closed" }
|
|
2289
|
+
});
|
|
2290
|
+
}
|
|
2291
|
+
};
|
|
2292
|
+
}
|
|
2293
|
+
//#endregion
|
|
2294
|
+
//#region src/app-view/host-context.ts
|
|
2295
|
+
const AppViewHostContext = (0, react.createContext)(null);
|
|
2296
|
+
function readAppViewHostContext(platform) {
|
|
2297
|
+
return {
|
|
2298
|
+
theme: typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light",
|
|
2299
|
+
displayMode: "inline",
|
|
2300
|
+
availableDisplayModes: ["inline"],
|
|
2301
|
+
containerDimensions: { maxHeight: 640 },
|
|
2302
|
+
platform,
|
|
2303
|
+
locale: typeof navigator === "undefined" ? "en-US" : navigator.language,
|
|
2304
|
+
timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone
|
|
2305
|
+
};
|
|
2306
|
+
}
|
|
2307
|
+
//#endregion
|
|
2308
|
+
//#region src/components/AppView.tsx
|
|
2309
|
+
const INITIAL_HEIGHT_PX = 160;
|
|
2310
|
+
const APP_VIEW_UNAVAILABLE_TEXT = "This tool returned an app view, which this window cannot show.";
|
|
2311
|
+
function AppView({ tool }) {
|
|
2312
|
+
const host = (0, react.useContext)(AppViewHostContext);
|
|
2313
|
+
if (tool.appView === void 0) return null;
|
|
2314
|
+
const viewKey = appViewKey(tool);
|
|
2315
|
+
if (host === null || host.sandboxUrl === void 0 || viewKey === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
2316
|
+
className: "text-[13px] text-subtle",
|
|
2317
|
+
children: APP_VIEW_UNAVAILABLE_TEXT
|
|
2318
|
+
});
|
|
2319
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppViewFrame, {
|
|
2320
|
+
tool,
|
|
2321
|
+
host,
|
|
2322
|
+
sandboxUrl: host.sandboxUrl,
|
|
2323
|
+
viewKey,
|
|
2324
|
+
sourceId: tool.appView.sourceId
|
|
2325
|
+
}, viewKey);
|
|
2326
|
+
}
|
|
2327
|
+
function AppViewFrame({ tool, host, sandboxUrl, viewKey, sourceId }) {
|
|
2328
|
+
const [load, setLoad] = (0, react.useState)({ status: "loading" });
|
|
2329
|
+
const [height, setHeight] = (0, react.useState)(INITIAL_HEIGHT_PX);
|
|
2330
|
+
const frameRef = (0, react.useRef)(null);
|
|
2331
|
+
const toolRef = (0, react.useRef)(tool);
|
|
2332
|
+
toolRef.current = tool;
|
|
2333
|
+
(0, react.useEffect)(() => {
|
|
2334
|
+
const controller = new AbortController();
|
|
2335
|
+
setLoad({ status: "loading" });
|
|
2336
|
+
host.readAppView(viewKey, controller.signal).then((view) => {
|
|
2337
|
+
if (!controller.signal.aborted) setLoad({
|
|
2338
|
+
status: "ready",
|
|
2339
|
+
view,
|
|
2340
|
+
host,
|
|
2341
|
+
sandboxUrl,
|
|
2342
|
+
signal: controller.signal
|
|
2343
|
+
});
|
|
2344
|
+
}, (error) => {
|
|
2345
|
+
if (!controller.signal.aborted) setLoad({
|
|
2346
|
+
status: "failed",
|
|
2347
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
2348
|
+
});
|
|
2349
|
+
});
|
|
2350
|
+
return () => {
|
|
2351
|
+
controller.abort();
|
|
2352
|
+
};
|
|
2353
|
+
}, [
|
|
2354
|
+
viewKey,
|
|
2355
|
+
host.readAppView,
|
|
2356
|
+
host.callAppTool,
|
|
2357
|
+
sandboxUrl
|
|
2358
|
+
]);
|
|
2359
|
+
const ready = load.status === "ready" && load.host.readAppView === host.readAppView && load.host.callAppTool === host.callAppTool && load.sandboxUrl === sandboxUrl ? load : null;
|
|
2360
|
+
(0, react.useLayoutEffect)(() => {
|
|
2361
|
+
const frame = frameRef.current;
|
|
2362
|
+
if (ready === null || frame === null) return void 0;
|
|
2363
|
+
const current = ready.host;
|
|
2364
|
+
const view = ready.view;
|
|
2365
|
+
const bridge = createAppViewBridge({
|
|
2366
|
+
view,
|
|
2367
|
+
toolResult: toCallToolResult(toolRef.current, view),
|
|
2368
|
+
hostInfo: current.hostInfo,
|
|
2369
|
+
hostContext: readAppViewHostContext(current.platform),
|
|
2370
|
+
post: (message) => frame.contentWindow?.postMessage(message, "*"),
|
|
2371
|
+
source: () => frame.contentWindow,
|
|
2372
|
+
callAppTool: (name, args) => current.callAppTool(viewKey, name, args, ready.signal),
|
|
2373
|
+
onHeight: setHeight
|
|
2374
|
+
});
|
|
2375
|
+
const onMessage = (event) => bridge.handleMessage(event);
|
|
2376
|
+
window.addEventListener("message", onMessage);
|
|
2377
|
+
return () => {
|
|
2378
|
+
window.removeEventListener("message", onMessage);
|
|
2379
|
+
bridge.dispose();
|
|
2380
|
+
};
|
|
2381
|
+
}, [ready, viewKey]);
|
|
2382
|
+
if (load.status === "failed") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2383
|
+
className: "text-[13px] text-subtle",
|
|
2384
|
+
children: [
|
|
2385
|
+
"The app from ",
|
|
2386
|
+
sourceId,
|
|
2387
|
+
" could not be shown: ",
|
|
2388
|
+
load.reason
|
|
2389
|
+
]
|
|
2390
|
+
});
|
|
2391
|
+
if (ready === null) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2392
|
+
className: "text-[13px] text-subtle",
|
|
2393
|
+
children: [
|
|
2394
|
+
"Loading the app from ",
|
|
2395
|
+
sourceId,
|
|
2396
|
+
"…"
|
|
2397
|
+
]
|
|
2398
|
+
});
|
|
2399
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2400
|
+
className: "my-1 flex flex-col gap-1",
|
|
2401
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
|
|
2402
|
+
className: "text-[12px] text-subtle",
|
|
2403
|
+
children: ["App from ", sourceId]
|
|
2404
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
|
|
2405
|
+
ref: frameRef,
|
|
2406
|
+
sandbox: "allow-scripts",
|
|
2407
|
+
src: sandboxUrl,
|
|
2408
|
+
title: `App from ${sourceId}`,
|
|
2409
|
+
referrerPolicy: "no-referrer",
|
|
2410
|
+
"data-testid": "app-view-frame",
|
|
2411
|
+
style: {
|
|
2412
|
+
width: "100%",
|
|
2413
|
+
height
|
|
2414
|
+
},
|
|
2415
|
+
className: "block rounded-lg border border-border bg-card"
|
|
2416
|
+
})]
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
//#endregion
|
|
1925
2420
|
//#region src/components/DiffLines.tsx
|
|
1926
2421
|
/**
|
|
1927
2422
|
* #3288: renders a unified diff already built server-side (the ONE diff builder, in
|
|
@@ -2435,42 +2930,49 @@ function ToolGroup({ tools }) {
|
|
|
2435
2930
|
if (visible.length === 0) return null;
|
|
2436
2931
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
2437
2932
|
className: "text-[14px]",
|
|
2438
|
-
children: [
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
|
|
2464
|
-
|
|
2465
|
-
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2472
|
-
|
|
2473
|
-
|
|
2933
|
+
children: [
|
|
2934
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
2935
|
+
type: "button",
|
|
2936
|
+
"aria-expanded": open,
|
|
2937
|
+
onClick: () => setOpen((value) => !value),
|
|
2938
|
+
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",
|
|
2939
|
+
children: [
|
|
2940
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Wrench, {
|
|
2941
|
+
size: 15,
|
|
2942
|
+
strokeWidth: 1.75,
|
|
2943
|
+
className: "flex-shrink-0 text-subtle"
|
|
2944
|
+
}),
|
|
2945
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2946
|
+
className: "flex-shrink-0",
|
|
2947
|
+
children: [
|
|
2948
|
+
visible.length,
|
|
2949
|
+
" tool ",
|
|
2950
|
+
visible.length === 1 ? "call" : "calls"
|
|
2951
|
+
]
|
|
2952
|
+
}),
|
|
2953
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
2954
|
+
className: "min-w-0 truncate text-subtle",
|
|
2955
|
+
children: names
|
|
2956
|
+
}),
|
|
2957
|
+
failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
2958
|
+
className: "flex-shrink-0 rounded-md bg-destructive/12 px-1.5 text-[12.5px] text-destructive",
|
|
2959
|
+
children: [failed, " failed"]
|
|
2960
|
+
}),
|
|
2961
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
|
|
2962
|
+
size: 14,
|
|
2963
|
+
className: `flex-shrink-0 text-subtle transition-transform ${open ? "rotate-90" : ""}`
|
|
2964
|
+
})
|
|
2965
|
+
]
|
|
2966
|
+
}),
|
|
2967
|
+
visible.filter((tool) => tool.appView !== void 0).map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2968
|
+
className: "pl-[25px]",
|
|
2969
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppView, { tool })
|
|
2970
|
+
}, tool.id)),
|
|
2971
|
+
open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2972
|
+
className: "mt-0.5 flex flex-col pl-[25px]",
|
|
2973
|
+
children: visible.map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCard, { tool }, tool.id))
|
|
2974
|
+
})
|
|
2975
|
+
]
|
|
2474
2976
|
});
|
|
2475
2977
|
}
|
|
2476
2978
|
/** #3288: a turn that changed files ends with this compact row — a click opens that file's diff. */
|
|
@@ -2596,7 +3098,10 @@ function ConversationView({ messages, activeTools, streamingText, isThinking, ow
|
|
|
2596
3098
|
isThinking && !streamingText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThinkingIndicator, {}),
|
|
2597
3099
|
activeTools.some((tool) => !tool.internal) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
2598
3100
|
className: "flex flex-col",
|
|
2599
|
-
children: activeTools.filter((tool) => !tool.internal).map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCard, { tool }, tool.
|
|
3101
|
+
children: activeTools.filter((tool) => !tool.internal).map((tool) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react.default.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCard, { tool }), tool.appView !== void 0 && tool.status !== "running" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
3102
|
+
className: "pl-[25px]",
|
|
3103
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppView, { tool })
|
|
3104
|
+
})] }, tool.id))
|
|
2600
3105
|
}),
|
|
2601
3106
|
streamingText && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AgentBlock, {
|
|
2602
3107
|
content: streamingText,
|
|
@@ -6464,7 +6969,7 @@ function noticeSentence(notice) {
|
|
|
6464
6969
|
}
|
|
6465
6970
|
}
|
|
6466
6971
|
/**
|
|
6467
|
-
* Session and protocol failures, as dismissible toasts over the top-right corner: they stay until
|
|
6972
|
+
* Session outcomes and protocol failures, as dismissible toasts over the top-right corner: they stay until
|
|
6468
6973
|
* dismissed, and never push the conversation or the composer out of view. Command output is not
|
|
6469
6974
|
* here — it belongs to the conversation. A provider error classified at the wire boundary shows one
|
|
6470
6975
|
* plain sentence with the next step; its raw text sits behind "Details" rather than as the headline.
|
|
@@ -6476,11 +6981,12 @@ function SessionNotices({ state }) {
|
|
|
6476
6981
|
className: "pointer-events-none absolute right-4 top-16 z-40 flex w-[min(400px,calc(100%-32px))] flex-col gap-2",
|
|
6477
6982
|
children: notices.slice(-3).map((notice) => {
|
|
6478
6983
|
const sentence = noticeSentence(notice);
|
|
6984
|
+
const compacted = notice.kind === "context-compacted";
|
|
6479
6985
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
6480
|
-
role: "alert",
|
|
6986
|
+
role: compacted ? "status" : "alert",
|
|
6481
6987
|
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",
|
|
6482
6988
|
children: [
|
|
6483
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className:
|
|
6989
|
+
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `mt-[7px] h-2 w-2 flex-shrink-0 rounded-full ${compacted ? "bg-primary" : "bg-destructive"}` }),
|
|
6484
6990
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
6485
6991
|
className: "max-h-40 flex-1 overflow-y-auto whitespace-pre-wrap break-words",
|
|
6486
6992
|
children: [sentence ?? notice.message, sentence && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("details", {
|
|
@@ -7536,6 +8042,7 @@ const NARROW_WINDOW_QUERY = "(max-width: 767.98px)";
|
|
|
7536
8042
|
*/
|
|
7537
8043
|
/** The column every part of the conversation shares, so messages, prompt and composer line up. */
|
|
7538
8044
|
const COLUMN = "mx-auto w-full max-w-[760px] px-6";
|
|
8045
|
+
const APP_VIEW_HOST_VERSION = "1.0.0";
|
|
7539
8046
|
/**
|
|
7540
8047
|
* Designed empty state shown before the first turn. Renders in the same slot as `ConversationView`
|
|
7541
8048
|
* (never together), so it carries the same `main` landmark and label (#3289 §3 review) — nothing
|
|
@@ -7602,8 +8109,25 @@ function SetupPanel({ onSetUp, disabled }) {
|
|
|
7602
8109
|
* The full desktop layout over an `IWsSessionState`. `surface` is an optional label shown next to the
|
|
7603
8110
|
* mark (e.g. "app").
|
|
7604
8111
|
*/
|
|
7605
|
-
function SessionSurface({ state, surface, personalUsageEnabled = false, onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor }) {
|
|
8112
|
+
function SessionSurface({ state, surface, personalUsageEnabled = false, onReconnect, pickFiles, getPathForFile, onOpenMemoryInEditor, appViewSandboxUrl }) {
|
|
7606
8113
|
const [view, setView] = (0, react.useState)("chat");
|
|
8114
|
+
const { identity } = useProductIdentity();
|
|
8115
|
+
const appViewHost = (0, react.useMemo)(() => ({
|
|
8116
|
+
...appViewSandboxUrl !== void 0 ? { sandboxUrl: appViewSandboxUrl } : {},
|
|
8117
|
+
readAppView: state.readAppView,
|
|
8118
|
+
callAppTool: state.callAppTool,
|
|
8119
|
+
hostInfo: {
|
|
8120
|
+
name: identity.displayName,
|
|
8121
|
+
version: APP_VIEW_HOST_VERSION
|
|
8122
|
+
},
|
|
8123
|
+
platform: surface === "app" ? "desktop" : "web"
|
|
8124
|
+
}), [
|
|
8125
|
+
appViewSandboxUrl,
|
|
8126
|
+
state.readAppView,
|
|
8127
|
+
state.callAppTool,
|
|
8128
|
+
identity.displayName,
|
|
8129
|
+
surface
|
|
8130
|
+
]);
|
|
7607
8131
|
const composerRef = (0, react.useRef)(null);
|
|
7608
8132
|
const tasks = state.executionWorkspace?.entries ?? [];
|
|
7609
8133
|
const hasTasks = tasks.some((entry) => entry.kind !== "main_thread");
|
|
@@ -7732,12 +8256,15 @@ function SessionSurface({ state, surface, personalUsageEnabled = false, onReconn
|
|
|
7732
8256
|
args: "add"
|
|
7733
8257
|
}),
|
|
7734
8258
|
disabled: state.status !== "connected"
|
|
7735
|
-
}) : isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(
|
|
7736
|
-
|
|
7737
|
-
|
|
7738
|
-
|
|
7739
|
-
|
|
7740
|
-
|
|
8259
|
+
}) : isEmpty ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(EmptyState, {}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AppViewHostContext.Provider, {
|
|
8260
|
+
value: appViewHost,
|
|
8261
|
+
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ConversationView, {
|
|
8262
|
+
messages: state.messages,
|
|
8263
|
+
activeTools: state.activeTools,
|
|
8264
|
+
streamingText: state.streamingText,
|
|
8265
|
+
isThinking: state.isThinking,
|
|
8266
|
+
ownDriverId: state.ownDriverId
|
|
8267
|
+
})
|
|
7741
8268
|
})
|
|
7742
8269
|
}),
|
|
7743
8270
|
/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|