@datalayer/agent-runtimes 1.5.17 → 1.5.18

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.
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @module apps/apps/appspec
26
26
  */
27
- import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, PapProfile, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec } from '../../types/agentspecs';
27
+ import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, PapProfile, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec, UiChoice } from '../../types/agentspecs';
28
28
  /** The version of the spec itself. */
29
29
  export declare const APP_SCHEMA = "loop.app/v1";
30
30
  /** The language an application's own words are in, unless it says (LOOP P-26). */
@@ -53,6 +53,36 @@ export declare const APP_ASSISTANT_CHARACTER_ID: RegExp;
53
53
  export declare function isAssistantCharacterId(value: unknown): value is AppAssistantCharacter;
54
54
  /** The layout a kind starts with. */
55
55
  export declare const DEFAULT_LAYOUTS: Record<AppKind, AppLayout>;
56
+ /** The media type of an answer drawn as A2UI surfaces. */
57
+ export declare const A2UI_OUTPUT = "application/json+a2ui";
58
+ /** What `uiFor` reads of an application besides its kind. */
59
+ export interface AppUiFacts {
60
+ /** Its layout; its kind's own when unsaid. */
61
+ layout?: AppLayout;
62
+ /** The media types it answers in. */
63
+ outputs?: readonly string[];
64
+ /** It signs people in at companies (`pap.connections`). */
65
+ signsIn?: boolean;
66
+ /** It has components of its own (`interface.customComponents`). */
67
+ ownComponents?: boolean;
68
+ }
69
+ /**
70
+ * The UI plugins an application is drawn with (AGENTIC_UI R8), as its `ui`
71
+ * lists them: what `appPreset` mounts for it. A decision: its page alone,
72
+ * drawn by its host. Every other kind: the conversation — `chat`, which
73
+ * stands for the conversation base the runtime mounts — then the A2UI
74
+ * surfaces when it answers in `application/json+a2ui`, the sign-in card when
75
+ * it signs people in at companies, its page, its layout and the Canvas's
76
+ * blocks when its layout is not `chat`, and its own components when it has
77
+ * some. What a host adds (the Preview's sidebar, a frame) is not the
78
+ * application's.
79
+ *
80
+ * agentspecs' `ui_for` is the same function: both are held to
81
+ * `agent_runtimes/tests/fixtures/app_ui.json`.
82
+ */
83
+ export declare function uiFor(kind: AppKind, facts?: AppUiFacts): UiChoice[];
84
+ /** The UI plugins an application is drawn with, by what its spec says (`uiFor`). */
85
+ export declare function uiOf(app: Pick<AppSpec, 'kind' | 'interface' | 'pap'>): UiChoice[];
56
86
  /** What an application keeps of what it did, when it does not say. */
57
87
  export declare const DEFAULT_RECORD_INCLUDE: string[];
58
88
  type Data = Record<string, unknown>;
@@ -74,6 +74,50 @@ export const DEFAULT_LAYOUTS = {
74
74
  decision: 'page',
75
75
  worker: 'split',
76
76
  };
77
+ /** The media type of an answer drawn as A2UI surfaces. */
78
+ export const A2UI_OUTPUT = 'application/json+a2ui';
79
+ /**
80
+ * The UI plugins an application is drawn with (AGENTIC_UI R8), as its `ui`
81
+ * lists them: what `appPreset` mounts for it. A decision: its page alone,
82
+ * drawn by its host. Every other kind: the conversation — `chat`, which
83
+ * stands for the conversation base the runtime mounts — then the A2UI
84
+ * surfaces when it answers in `application/json+a2ui`, the sign-in card when
85
+ * it signs people in at companies, its page, its layout and the Canvas's
86
+ * blocks when its layout is not `chat`, and its own components when it has
87
+ * some. What a host adds (the Preview's sidebar, a frame) is not the
88
+ * application's.
89
+ *
90
+ * agentspecs' `ui_for` is the same function: both are held to
91
+ * `agent_runtimes/tests/fixtures/app_ui.json`.
92
+ */
93
+ export function uiFor(kind, facts = {}) {
94
+ if (kind === 'decision') {
95
+ return [{ plugin: 'app-page' }];
96
+ }
97
+ const drawn = [{ plugin: 'chat' }];
98
+ if (facts.outputs?.includes(A2UI_OUTPUT)) {
99
+ drawn.push({ plugin: 'a2ui-surface', renderer: 'a2ui' });
100
+ }
101
+ if (facts.signsIn) {
102
+ drawn.push({ plugin: 'pap-sign-in' });
103
+ }
104
+ if ((facts.layout ?? DEFAULT_LAYOUTS[kind]) !== 'chat') {
105
+ drawn.push({ plugin: 'page-layout' }, { plugin: 'app-page' }, { plugin: 'canvas-blocks' });
106
+ }
107
+ if (facts.ownComponents) {
108
+ drawn.push({ plugin: 'app-components' });
109
+ }
110
+ return drawn;
111
+ }
112
+ /** The UI plugins an application is drawn with, by what its spec says (`uiFor`). */
113
+ export function uiOf(app) {
114
+ return uiFor(app.kind, {
115
+ layout: app.interface.layout,
116
+ outputs: app.interface.outputs,
117
+ signsIn: Boolean(app.pap?.connections.length),
118
+ ownComponents: Boolean(app.interface.customComponents?.length),
119
+ });
120
+ }
77
121
  /** What an application keeps of what it did, when it does not say. */
78
122
  export const DEFAULT_RECORD_INCLUDE = ['conversations', 'actions', 'approvals'];
79
123
  const DEFAULT_KEEP_FOR = '1_years';
@@ -93,22 +137,27 @@ const texts = (value) => Array.isArray(value)
93
137
  const records = (value) => Array.isArray(value) ? value.filter(isData) : [];
94
138
  /**
95
139
  * An application's UI plugins as its document says them (AGENTIC_UI R8):
96
- * `{plugin, renderer}` each. A bare id — the old shape, a renderer named as a
97
- * UI — is read as the plugin it names, which `uiProblems` refuses.
140
+ * `{plugin, renderer}` each. Anything else — a bare id, the old shape — is
141
+ * not read as a plugin: `parseAppspec` refuses it (`uiShapeProblems`).
98
142
  */
99
143
  const uiChoices = (value) => Array.isArray(value)
100
- ? value.flatMap((item) => typeof item === 'string'
101
- ? [{ plugin: item }]
102
- : isData(item)
103
- ? [
104
- {
105
- plugin: text(item.plugin),
106
- ...(text(item.renderer)
107
- ? { renderer: text(item.renderer) }
108
- : {}),
109
- },
110
- ]
111
- : [])
144
+ ? value.filter(isData).map(item => ({
145
+ plugin: text(item.plugin),
146
+ ...(text(item.renderer) ? { renderer: text(item.renderer) } : {}),
147
+ }))
148
+ : [];
149
+ /**
150
+ * An entry of `ui` that is not `{plugin, renderer}`, in agentspecs' sentence
151
+ * (`ui_choices_problems`): a bare id is refused, never read as the plugin it
152
+ * names, and one naming a renderer says how to write it.
153
+ */
154
+ const uiShapeProblems = (value) => Array.isArray(value)
155
+ ? value
156
+ .filter(item => !isData(item))
157
+ .map(item => `\`ui\` lists UI plugins, each as \`{plugin, renderer}\`: ${JSON.stringify(item)} is not one` +
158
+ (typeof item === 'string' && UI_RENDERER_CATALOGUE[item]
159
+ ? ` (${JSON.stringify(item)} is a UI renderer: name the plugin it renders, and say it as its \`renderer\`).`
160
+ : '.'))
112
161
  : [];
113
162
  /**
114
163
  * One turn of a test's conversation as its document says it (STUDIO E-01):
@@ -153,7 +202,8 @@ export function emptyAppspec(kind = 'chat') {
153
202
  contents: [],
154
203
  connections: [],
155
204
  protocols: [],
156
- ui: [],
205
+ // A blank application starts with its kind's UI plugins (AGENTIC_UI R8).
206
+ ui: uiFor(kind),
157
207
  rules: [],
158
208
  permissions: {
159
209
  spaces: [],
@@ -957,6 +1007,7 @@ export function parseAppspec(document) {
957
1007
  problems.push(`\`${key}\` is not a field of the spec.`);
958
1008
  }
959
1009
  }
1010
+ problems.push(...uiShapeProblems(data.ui));
960
1011
  const schema = text(data.schema, APP_SCHEMA);
961
1012
  if (schema !== APP_SCHEMA) {
962
1013
  problems.push(`This reads ${APP_SCHEMA}; the application is written in ${schema}.`);
@@ -1295,6 +1295,16 @@ export default function ChatView({ workspace }) {
1295
1295
  // not know (LOOP R-20): it runs a session of its deployment, and no more.
1296
1296
  const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
1297
1297
  const iamToken = visitors ? visitor.token : embedToken || memberToken;
1298
+ /*
1299
+ * A deployment's runtime, and the visitors', answer only the application's
1300
+ * own routes (OwnRoutesOnly, PAP_DATALAYER's release gates): its chat calls
1301
+ * nothing else — Stop is its session's, no monitoring stream, no sandbox
1302
+ * socket, no runtime-wide model switch. A Preview runs on the Maker's.
1303
+ */
1304
+ const ownRoutesOnly = Boolean(deploymentUid || visitors);
1305
+ if (ownRoutesOnly && !runsApp) {
1306
+ throw new Error('A deployment’s page runs its application: its host gave no app_spec.');
1307
+ }
1298
1308
  /*
1299
1309
  * A thread opened from the person's history (LOOP P-24): its conversation
1300
1310
  * read from the runtime once it answers — held there, else resumed from
@@ -1420,6 +1430,9 @@ export default function ChatView({ workspace }) {
1420
1430
  uiSocket: `${agentServerUrl.replace(/^http/, 'ws')}/api/v1/apps/ui`,
1421
1431
  // Its files, uploaded before what names them (RU-6).
1422
1432
  uiFiles: `${agentServerUrl}/api/v1/apps/agents/${encodeURIComponent(agentId)}/files`,
1433
+ // Each thread a session: Stop is its session's.
1434
+ appSessions: `${agentServerUrl}/api/v1/apps/sessions`,
1435
+ ...(ownRoutesOnly ? { ownRoutesOnly: true } : {}),
1423
1436
  }
1424
1437
  : {}),
1425
1438
  agentId,
@@ -1442,6 +1455,7 @@ export default function ChatView({ workspace }) {
1442
1455
  agentServerUrl,
1443
1456
  runsApp,
1444
1457
  iamToken,
1458
+ ownRoutesOnly,
1445
1459
  ]);
1446
1460
  /*
1447
1461
  * The model catalogue, and the builtin tools and skills the agent reports.
@@ -1556,6 +1570,10 @@ export default function ChatView({ workspace }) {
1556
1570
  * to agree.
1557
1571
  */
1558
1572
  const selectModel = useCallback(async (model) => {
1573
+ if (ownRoutesOnly) {
1574
+ // Runtime-wide, and not the application's own: never offered here.
1575
+ throw new Error('A deployment’s chat does not switch its runtime’s model.');
1576
+ }
1559
1577
  setPickedModel(model);
1560
1578
  if (inPage || agentServerUrl === undefined) {
1561
1579
  return;
@@ -1565,7 +1583,7 @@ export default function ChatView({ workspace }) {
1565
1583
  headers: { 'content-type': 'application/json' },
1566
1584
  body: JSON.stringify({ model }),
1567
1585
  }).catch(() => undefined);
1568
- }, [agentServerUrl, inPage]);
1586
+ }, [agentServerUrl, inPage, ownRoutesOnly]);
1569
1587
  /* `bottom-chat` keeps the prompt in the chat column; `bottom`, the default,
1570
1588
  spans the workspace. */
1571
1589
  const besideChat = config?.promptPlacement === 'bottom-chat';
@@ -1660,7 +1678,8 @@ export default function ChatView({ workspace }) {
1660
1678
  showTokenUsage: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true,
1661
1679
  showContextRing: true,
1662
1680
  agentUsage: contextUsage ?? undefined,
1663
- showModelSelector: !presence,
1681
+ // Nor on a deployment's page: the model is its runtime's, for everyone.
1682
+ showModelSelector: !presence && !ownRoutesOnly,
1664
1683
  models: offeredModels,
1665
1684
  decisions,
1666
1685
  selectedModel: activeModel,
@@ -35,6 +35,7 @@ import { decisionsAskerAt } from './assistant/decisions';
35
35
  import { DecisionAsk } from './assistant/DecisionAsk';
36
36
  import { ServerSpeaker } from '../voice/speaker';
37
37
  import { useSpokenAnswers } from '../voice/useSpokenAnswers';
38
+ import { readAloud } from '../voice/ReadAloud';
38
39
  import { BalloonApprovalMessage, CONVERSATION_BALLOON_WIDTH, ConversationBalloonClose, balloonTailAt, conversationBalloonHeight, conversationBalloonSx, CURRENT_BALLOON_HEIGHT, peekLine, } from './assistant/ConversationBalloon';
39
40
  import { ConversationBalloonHeader, CurrentBalloonBody, } from './assistant/BalloonParts';
40
41
  import { DEFAULT_BALLOON_DISPLAY, conversationCount, newestToolLine, toolLineText, } from './assistant/toolLine';
@@ -165,7 +166,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
165
166
  const [speakerState, setSpeakerState] = useState({
166
167
  speaking: false,
167
168
  });
168
- const speaker = useMemo(() => voice && voice.output === 'always' && voice.speechUrl
169
+ const speaker = useMemo(() => voice && voice.output !== 'off' && voice.speechUrl
169
170
  ? new ServerSpeaker({
170
171
  speechUrl: voice.speechUrl,
171
172
  voice: voice.voice,
@@ -195,8 +196,15 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
195
196
  window.removeEventListener('keydown', unlock);
196
197
  };
197
198
  }, [speaker]);
198
- useSpokenAnswers(voiceItems, chatBusy, !!speaker && !speechMuted, speaker);
199
+ // `always`: each answer heard as it is written; `on_request`: a *Read
200
+ // aloud* under each (VO-21).
201
+ useSpokenAnswers(voiceItems, chatBusy, !!speaker && voice?.output === 'always' && !speechMuted, speaker);
199
202
  const stopSpeaking = useCallback(() => speaker?.stop(), [speaker]);
203
+ const readAnswerAloud = useCallback((answer) => {
204
+ if (speaker) {
205
+ readAloud(speaker, answer);
206
+ }
207
+ }, [speaker]);
200
208
  const mouthLevel = useMemo(() => (speaker ? () => speaker.level() : undefined), [speaker]);
201
209
  const [arriving, setArriving] = useState(false);
202
210
  const [leaving, setLeaving] = useState(false);
@@ -1057,7 +1065,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1057
1065
  ...poweredByProps,
1058
1066
  }, description: description, onStateUpdate: onStateUpdate, onNewChat: onNewChat, suggestions: suggestions, submitOnSuggestionClick: submitOnSuggestionClick, hideMessagesAfterToolUI: hideMessagesAfterToolUI, avatarConfig: {
1059
1067
  showAvatars: true,
1060
- }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt ?? suggestionPrompt, inspector: inspector, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
1068
+ }, placeholder: "Type a message...", backgroundColor: "canvas.subtle", frontendTools: frontendTools, showModelSelector: showModelSelector, availableModels: availableModels, showToolsMenu: showToolsMenu, showSkillsMenu: showSkillsMenu, showTokenUsage: showTokenUsage, showContextRing: showContextRing, runtimeId: runtimeId, historyEndpoint: historyEndpoint, historyAuthToken: historyAuthToken, pendingPrompt: pendingPrompt ?? suggestionPrompt, inspector: inspector, showInformation: showInformation, onInformationClick: onInformationClick, onToolCallStart: onToolCallStart, onToolCallComplete: onToolCallComplete, showToolApprovalBanner: showToolApprovalBanner, pendingApprovals: pendingApprovals, onApproveApproval: onApproveApproval, onRejectApproval: onRejectApproval, voice: voice, voiceSpeaking: speakerState.speaking, onStopSpeaking: speaker ? stopSpeaking : undefined, onReadAloud: speaker ? readAnswerAloud : undefined, ...panelProps, ...assistantChat, onLoadingChange: handleLoadingChange, onDisplayItemsChange: handleDisplayItemsChange, onSendReady: handleSendReady, children: conversationBalloon && currentOpen ? (
1061
1069
  // Current: the one thing said or done now, over the composer.
1062
1070
  _jsx(Box, { px: 3, pt: 3, pb: 2, overflow: "hidden", children: _jsx(CurrentBalloonBody, { text: toolSaid ? undefined : (currentWords ?? description), fullText: toolSaid ? undefined : currentWhole, tool: toolSaid, busy: atWork, speaking: answering && atWork, waiting: atWork && !answering }) })) : (children) }))] }));
1063
1071
  /*
@@ -1109,7 +1117,7 @@ export function ChatFloating({ endpoint, protocol: protocolProp, useStore: useSt
1109
1117
  }
1110
1118
  : getPositionStyles(), stageRef: stageRef, onDragStart: stageDrag.onHandlePointerDown, open: isOpen, onToggle: handleToggle, balloon: assistantBalloon, balloonDisplay: balloonDisplay, expandTarget: expandTarget, insist: balloonInsists, onDismiss: dismissAssistant, ownRef: popupRef, mouthLevel: mouthLevel, suggestions: balloonSuggestions, onSuggestion: sendSuggestion, inspector: inspector, onBalloonDisplayChange: changeBalloonDisplay, onStop: chatBusy && chatControls.current
1111
1119
  ? () => chatControls.current?.stop()
1112
- : undefined, onNewChat: !conversation && showNewChatButton ? handleNewChat : undefined, onClear: !conversation && useStoreMode ? handleClear : undefined, onChangeCharacter: onChangeCharacter, speech: speaker
1120
+ : undefined, onNewChat: !conversation && showNewChatButton ? handleNewChat : undefined, onClear: !conversation && useStoreMode ? handleClear : undefined, onChangeCharacter: onChangeCharacter, speech: speaker && voice?.output === 'always'
1113
1121
  ? {
1114
1122
  muted: speechMuted,
1115
1123
  onToggle: () => {
@@ -36,7 +36,7 @@ import { askedOf } from '../../apps/apps/records';
36
36
  import { extractChatMessagesFromFullContext } from './fullContextMessages';
37
37
  import { generateMessageId, createUserMessage, createAssistantMessage, } from '../../types/messages';
38
38
  import { AI_MODEL_CATALOGUE, DEFAULT_MODEL, isChatModel, } from '../../specs/models';
39
- import { internalQueryClient, isToolCallMessage, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
39
+ import { internalQueryClient, isToolCallMessage, getMessageText, convertHistoryToDisplayItems, createProtocolAdapter, getApiBaseFromConfig, sanitizeAssistantContent, } from '../../utils';
40
40
  import { useConfig, useSkills, useSkillActions, useContextSnapshot, useSandbox, } from '../../hooks';
41
41
  import { useAgentRuntimeWebSocket } from '../../hooks/useAgentRuntimes';
42
42
  import { agentRuntimeStore, useAgentRuntimeStore, useAgentRuntimeWsState, } from '../../stores/agentRuntimeStore';
@@ -50,6 +50,7 @@ import { ChatMessageList, } from '../messages/ChatMessageList';
50
50
  import { InputPrompt } from '../prompt/InputPrompt';
51
51
  import { DeviceHearing } from '../../voice/hearing';
52
52
  import { VoiceInput } from '../../voice/VoiceInput';
53
+ import { ReadAloud, readsAloud } from '../../voice/ReadAloud';
53
54
  import { isFloatingChatViewMode } from '../viewModes';
54
55
  import { ToolApprovalBanner, ToolApprovalDialog, } from '../tools';
55
56
  import { initialModelId, isOffered, offeredModels as offeredModelsFor, } from './modelChoice';
@@ -416,7 +417,7 @@ function ChatBaseInner({ title, subtitle, showHeader = false, showTokenUsage = t
416
417
  // Mode selection
417
418
  useStore: useStoreMode = true, protocol: protocolRaw, onSendMessage, onSendReady, onLoadingChange, onContextSnapshot, enableStreaming = false,
418
419
  // Extended props
419
- brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
420
+ brandIcon, avatarConfig, headerButtons, showPoweredBy = false, poweredByProps, emptyState, renderHeader, showTurnFooter = true, notebookToolSurfacesId, footerContent, welcome, trailingContent, voice, voiceSpeaking = false, onStopSpeaking, onReadAloud, showInformation = false, onInformationClick, headerContent, children, borderRadius, backgroundColor, border, boxShadow, compact = false, placeholder, description = 'Start a conversation with the AI agent.', onStateUpdate, onNewChat, onClear, onMessagesChange, onItemsChange, onDisplayItemsChange, autoFocus = false, suggestions, submitOnSuggestionClick = true, hideMessagesAfterToolUI = false, focusTrigger, frontendTools: frontendToolsProp, enableEphemeralNotebook = false, initialEphemeralNotebookOpen = true, onEphemeralNotebookOpenChange, enableEphemeralDocument = false, initialEphemeralSurfaceMode, onEphemeralSurfaceModeChange, collapsed = false, onExpandFromCollapsed, ephemeralNotebookToolbar, ephemeralNotebookToolbarExtraItems, ephemeralDocumentToolbarExtraItems, ephemeralNotebookCollaborationProvider, ephemeralNotebookCollaborationDocumentId, ephemeralDocumentCollaboration, ephemeralRuntimeOverride,
420
421
  // Tool invocation hooks
421
422
  onToolCallStart, onToolCallComplete, inspector: inspectorGiven,
422
423
  // Identity/Authorization props
@@ -1467,7 +1468,9 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1467
1468
  useEffect(() => {
1468
1469
  onContextSnapshot?.(agentUsage);
1469
1470
  }, [agentUsage, onContextSnapshot]);
1470
- const sandboxStatusQuery = useSandbox(configQueriesEnabled && showHeader, protocol?.configEndpoint, protocol?.authToken, protocol?.agentId);
1471
+ // Never on a runtime held to the application's own routes: it answers no
1472
+ // sandbox socket (OwnRoutesOnly).
1473
+ const sandboxStatusQuery = useSandbox(configQueriesEnabled && showHeader && !protocol?.ownRoutesOnly, protocol?.configEndpoint, protocol?.authToken, protocol?.agentId);
1471
1474
  // ---- Refs ----
1472
1475
  const adapterRef = useRef(null);
1473
1476
  const unsubscribeRef = useRef(null);
@@ -1794,8 +1797,16 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
1794
1797
  const wsBaseUrl = protocol?.configEndpoint
1795
1798
  ? protocol.configEndpoint.replace(/\/api\/v1\/(config|configure)\/?$/, '')
1796
1799
  : (protocol?.endpoint?.replace(/\/api\/v1\/.*$/, '') ?? '');
1800
+ /*
1801
+ * Not on a runtime held to the application's own routes (OwnRoutesOnly): it
1802
+ * answers no monitoring stream, and an application's session asks its
1803
+ * approvals in the conversation, over its ui socket.
1804
+ */
1797
1805
  useAgentRuntimeWebSocket({
1798
- enabled: !!protocol && !!wsBaseUrl && !isAgentNodeTunnelAgUi,
1806
+ enabled: !!protocol &&
1807
+ !!wsBaseUrl &&
1808
+ !isAgentNodeTunnelAgUi &&
1809
+ !protocol.ownRoutesOnly,
1799
1810
  baseUrl: wsBaseUrl,
1800
1811
  authToken: protocol?.authToken,
1801
1812
  agentId: protocol?.agentId,
@@ -3739,6 +3750,21 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3739
3750
  ...displayItems,
3740
3751
  ]
3741
3752
  : displayItems;
3753
+ /*
3754
+ * *Read aloud* under each answer the agent has finished (VOICE.md VO-21),
3755
+ * when the host reads answers aloud on request.
3756
+ */
3757
+ const writingId = isLoading || isStreaming
3758
+ ? [...welcomedItems]
3759
+ .reverse()
3760
+ .find((item) => !isToolCallMessage(item) && item.role === 'assistant')?.id
3761
+ : undefined;
3762
+ const readAloudFooter = onReadAloud
3763
+ ? (message) => {
3764
+ const text = getMessageText(message);
3765
+ return readsAloud(message, text, writingId) ? (_jsx(ReadAloud, { onRead: () => onReadAloud(text) })) : null;
3766
+ }
3767
+ : undefined;
3742
3768
  const messagesContent = children ? (children) : (_jsxs(Box, { display: "flex", flexDirection: "column", minHeight: 0,
3743
3769
  /*
3744
3770
  A reading column, not a full-bleed sheet.
@@ -3752,7 +3778,7 @@ showToolApprovalBanner = true, pendingApprovals: pendingApprovalsProp, onApprove
3752
3778
  // Tool calls are drawn by the page's Reactor plugins; a named
3753
3779
  // notebook's cell surfaces stand in for the default row of its tools,
3754
3780
  // so every chat with a headless notebook gets the same transcript.
3755
- notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
3781
+ notebookToolSurfacesId: notebookToolSurfacesId, approvalConfig: approvalConfig, messagesEndRef: messagesEndRef, onRespond: handleRespond, showTurnFooters: showTurnFooter, renderMessageFooter: readAloudFooter, agentUsage: agentUsage, onRemoveItems: handleRemoveItems, mcpServers: filteredMcpServers, emptyContent: launching ? null : (_jsx(ChatEmptyState, { emptyState: emptyState, brandIcon: brandIcon, description: resolvedDescription, suggestions: resolvedSuggestions, submitOnSuggestionClick: submitOnSuggestionClick, onSuggestionSubmit: handleSuggestionSubmit, onSuggestionFill: handleSuggestionFill })) }), trailingContent] }));
3756
3782
  // The microphone, in the composer's footer (VO-10).
3757
3783
  const voiceControl = hearing && voice ? (_jsx(VoiceInput, { hearing: hearing, language: voice.language, consentKey: voice.consentKey ?? 'chat', disabled: disabled || launching, speaking: voiceSpeaking, onStopSpeaking: onStopSpeaking, onListen: onStopSpeaking, onTranscript: (transcript) => {
3758
3784
  if (voice.sendWhatISay) {
@@ -611,7 +611,7 @@ export function ChatMessageList({ displayItems, actOn, upload, isLoading, isStre
611
611
  whiteSpace: 'pre-wrap',
612
612
  wordBreak: 'break-word',
613
613
  }, children: [message.metadata?.input === 'voice' ? (_jsx(Box, { as: "span", "data-spoken": "", title: "Said aloud", "aria-label": "Said aloud:", display: "inline-flex", verticalAlign: "text-bottom", mr: 1, opacity: 0.85, children: _jsx(MicrophoneIcon, { size: 14, "aria-hidden": "true" }) })) : null, text] }));
614
- })()) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(ChatMarkdown, { density: density, text: getMessageText(message) || (isStreaming ? '...' : '') }))] }), message.displays?.length ? (_jsx(Box, { "data-chat-displays": "", display: "flex", flexDirection: "column", gap: 2, mt: 2, width: "100%", children: message.displays.map(display => platform ? (_jsx(ReactorDisplay, { display: display, act: actOn?.(display), upload: upload }, display.id)) : (_jsx(DisplayText, { display: display }, display.id))) })) : null, renderMessageFooter ? (_jsx(Box, { display: "flex", gap: 2, alignItems: "center", mt: 1, flexWrap: "wrap", flexDirection: isUser ? 'row-reverse' : 'row', children: renderMessageFooter(message) })) : null] })] }) }, message.id));
614
+ })()) : message.live && !getMessageText(message) ? (_jsx(TypingDots, { size: 6 })) : (_jsx(ChatMarkdown, { density: density, text: getMessageText(message) || (isStreaming ? '...' : '') }))] }), message.displays?.length ? (_jsx(Box, { "data-chat-displays": "", display: "flex", flexDirection: "column", gap: 2, mt: 2, width: "100%", children: message.displays.map(display => platform ? (_jsx(ReactorDisplay, { display: display, act: actOn?.(display), upload: upload }, display.id)) : (_jsx(DisplayText, { display: display }, display.id))) })) : null, renderMessageFooter?.(message) ? (_jsx(Box, { display: "flex", gap: 2, alignItems: "center", mt: 1, flexWrap: "wrap", flexDirection: isUser ? 'row-reverse' : 'row', children: renderMessageFooter(message) })) : null] })] }) }, message.id));
615
615
  };
616
616
  return (_jsxs(_Fragment, { children: [displayItems.map((item, index) => {
617
617
  const itemUI = renderItem(item, index);
@@ -11,12 +11,13 @@ import { useBrowserInference } from '../hooks/useBrowserInference';
11
11
  import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
12
12
  import { connectA2APeer } from '../runtimes/browser/a2aPeer';
13
13
  import { A2ATeamGraph, SceneView, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
14
- import { ACCOUNTING_APP_0_0_1, SALES_APP_0_0_2 } from '../specs/apps';
14
+ import { APP_CATALOGUE } from '../specs/apps';
15
15
  import { AgentInspector, classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
16
- import { SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1 } from '../specs/teams/teams';
17
- const TEAM = SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1;
18
- const SALES = SALES_APP_0_0_2;
19
- const ACCOUNTING = ACCOUNTING_APP_0_0_1;
16
+ import { TEAM_SPECS } from '../specs/teams/teams';
17
+ // By id, never by the version-named constants: a version bump touches no caller.
18
+ const TEAM = TEAM_SPECS['sales-and-accounting'];
19
+ const SALES = APP_CATALOGUE['sales'];
20
+ const ACCOUNTING = APP_CATALOGUE['accounting'];
20
21
  /** What Accounting reaches, from its Appspec: Odoo, through the odoo-accounting MCP server. */
21
22
  const ACCOUNTING_CONNECTIONS = teamConnectionsOf(ACCOUNTING);
22
23
  /** The members of the team, as the team spec says them. */
@@ -30,13 +30,13 @@ import { uniqueAgentId } from './utils/agentId';
30
30
  import { useExampleAgentRuntime } from './hooks/useExampleAgentRuntime';
31
31
  import { BROWSER_SIGN_IN_REASON, useExampleAgentProtocol, } from './hooks/useExampleAgentProtocol';
32
32
  import { runsInBrowser } from '../runtimes/variants';
33
- import { EXAMPLE_SIMPLE_AGENTSPEC_0_0_1 } from '../specs/agents/agents';
33
+ import { AGENTSPECS } from '../specs/agents/agents';
34
34
  import { ErrorView } from './components';
35
35
  import { Chat } from '../chat';
36
36
  setupPrimerPortals();
37
37
  const AGENTSPEC_ID = 'example-simple';
38
38
  const AGENT_NAME = 'simple';
39
- const SPEC = EXAMPLE_SIMPLE_AGENTSPEC_0_0_1;
39
+ const SPEC = AGENTSPECS[AGENTSPEC_ID];
40
40
  const AgentRuntimeChatExample = () => {
41
41
  const agentName = useMemo(() => uniqueAgentId(AGENT_NAME), []);
42
42
  const { agentId, baseUrl, error, status, isReady, variant } = useExampleAgentRuntime({
@@ -45,21 +45,27 @@ const LANGUAGES = [
45
45
  { tag: 'en-US', label: 'English' },
46
46
  { tag: 'fr-FR', label: 'Français' },
47
47
  ];
48
+ /** When answers are heard (VO-21): on request is the Appspec's default. */
49
+ const OUTPUTS = [
50
+ { output: 'on_request', label: 'Read aloud on request' },
51
+ { output: 'always', label: 'Always read aloud' },
52
+ ];
48
53
  const VoiceChatExample = () => {
49
54
  const { token } = useSimpleAuthStore();
50
55
  const [language, setLanguage] = useState('en-US');
56
+ const [output, setOutput] = useState('on_request');
51
57
  const voiceSpec = voiceFor(language) ?? VOICE_CATALOGUE['kokoro-af-heart'];
52
58
  const voice = useMemo(() => ({
53
59
  language,
54
60
  input: 'push_to_talk',
55
- output: 'always',
61
+ output,
56
62
  voice: voiceSpec.id,
57
63
  modelsUrl: MODELS,
58
64
  engines: ENGINES,
59
65
  speechUrl: SPEECH,
60
66
  token: token || undefined,
61
67
  consentKey: 'voice-chat-example',
62
- }), [language, voiceSpec.id, token]);
63
- return (_jsx(ThemedProvider, { children: _jsxs(Box, { minHeight: "100vh", bg: "canvas.default", p: 4, children: [_jsxs(Box, { maxWidth: 720, mx: "auto", children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Voice chat" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: ["Talk to the assistant instead of typing: hold the microphone in its composer, or ", _jsx("kbd", { children: "Ctrl" }), "+", _jsx("kbd", { children: "Space" }), ", speak, and let go. What you say is heard in this page and never leaves it; the words go in the message box for you to send. Its answers are read aloud as they are written, by Datalayer\u2019s speech service, and not kept.", ' ', _jsx("kbd", { children: "Esc" }), " stops its voice."] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, mb: 3, children: [_jsx(SegmentedControl, { "aria-label": "Language", children: LANGUAGES.map(option => (_jsx(SegmentedControl.Button, { selected: language === option.tag, onClick: () => setLanguage(option.tag), "data-voice-language": option.tag, children: option.label }, option.tag))) }), _jsxs(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: ["Voice: ", voiceSpec.name] })] }), voiceSpec.attribution && (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: voiceSpec.attribution })), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted' }, children: ["Models from ", MODELS, ", speech from ", SPEECH, ", the agent at ", SERVER, ". See", ' ', _jsx(Link, { href: "https://agent-runtimes.datalayer.tech/chat/voice", children: "Voice" }), ' ', "for how to run each."] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: "paperclip", protocol: "vercel-ai", endpoint: `${SERVER}/api/v1/vercel-ai/assistant`, title: "Assistant", description: "Hello! Hold the microphone and ask me anything.", position: "bottom-right", useStore: false, voice: voice })] }) }));
68
+ }), [language, output, voiceSpec.id, token]);
69
+ return (_jsx(ThemedProvider, { children: _jsxs(Box, { minHeight: "100vh", bg: "canvas.default", p: 4, children: [_jsxs(Box, { maxWidth: 720, mx: "auto", children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Voice chat" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: ["Talk to the assistant instead of typing: hold the microphone in its composer, or ", _jsx("kbd", { children: "Ctrl" }), "+", _jsx("kbd", { children: "Space" }), ", speak, and let go. What you say is heard in this page and never leaves it; the words go in the message box for you to send. Its answers are read aloud by Datalayer\u2019s speech service, and not kept: on request, with", ' ', _jsx("em", { children: "Read aloud" }), " under an answer, or always, as they are written.", ' ', _jsx("kbd", { children: "Esc" }), " stops its voice."] }), _jsxs(Box, { display: "flex", alignItems: "center", gap: 3, mb: 3, flexWrap: "wrap", children: [_jsx(SegmentedControl, { "aria-label": "Language", children: LANGUAGES.map(option => (_jsx(SegmentedControl.Button, { selected: language === option.tag, onClick: () => setLanguage(option.tag), "data-voice-language": option.tag, children: option.label }, option.tag))) }), _jsx(SegmentedControl, { "aria-label": "When answers are heard", children: OUTPUTS.map(option => (_jsx(SegmentedControl.Button, { selected: output === option.output, onClick: () => setOutput(option.output), "data-voice-output": option.output, children: option.label }, option.output))) }), _jsxs(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: ["Voice: ", voiceSpec.name] })] }), voiceSpec.attribution && (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: voiceSpec.attribution })), _jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted' }, children: ["Models from ", MODELS, ", speech from ", SPEECH, ", the agent at ", SERVER, ". See", ' ', _jsx(Link, { href: "https://agent-runtimes.datalayer.tech/chat/voice", children: "Voice" }), ' ', "for how to run each."] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: "paperclip", protocol: "vercel-ai", endpoint: `${SERVER}/api/v1/vercel-ai/assistant`, title: "Assistant", description: "Hello! Hold the microphone and ask me anything.", position: "bottom-right", useStore: false, voice: voice })] }) }));
64
70
  };
65
71
  export default VoiceChatExample;
@@ -14,7 +14,7 @@ export type UseBrowserAgentOptions = CreateBrowserAgentOptions & {
14
14
  * ```tsx
15
15
  * const frontendTools = useNotebookTools(notebookId);
16
16
  * const { messages, sendMessage, status } = useBrowserAgent({
17
- * spec: JUPYTER_NOTEBOOK_COMPACTOR_AGENTSPEC_0_0_1,
17
+ * spec: AGENTSPECS['jupyter-notebook-compactor'],
18
18
  * frontendTools,
19
19
  * inference: { inferenceUrl, token },
20
20
  * });
@@ -35,7 +35,7 @@ import { createBrowserAgent, } from '../runtimes/browser';
35
35
  * ```tsx
36
36
  * const frontendTools = useNotebookTools(notebookId);
37
37
  * const { messages, sendMessage, status } = useBrowserAgent({
38
- * spec: JUPYTER_NOTEBOOK_COMPACTOR_AGENTSPEC_0_0_1,
38
+ * spec: AGENTSPECS['jupyter-notebook-compactor'],
39
39
  * frontendTools,
40
40
  * inference: { inferenceUrl, token },
41
41
  * });
@@ -15,6 +15,11 @@ export declare const RUN_CUT_SHORT = "The run stopped before it finished, withou
15
15
  export interface AGUIAdapterConfig extends ProtocolAdapterConfig {
16
16
  /** Runtime URL (e.g., /api/copilotkit) */
17
17
  runtimeUrl?: string;
18
+ /**
19
+ * An application's sessions (`…/api/v1/apps/sessions`): Stop is the
20
+ * thread's session's (`ProtocolConfig.appSessions`).
21
+ */
22
+ appSessions?: string;
18
23
  }
19
24
  /**
20
25
  * AG-UI protocol adapter
@@ -63,6 +68,12 @@ export declare class AGUIAdapter extends BaseProtocolAdapter {
63
68
  * Terminate a running agent thread on the backend
64
69
  */
65
70
  terminateAgent(threadId?: string): Promise<void>;
71
+ /**
72
+ * Stop an application's session (`POST {sessions}/{uid}/stop`): what runs
73
+ * is cancelled and it waits to be resumed. Its thread is the session; with
74
+ * none, nothing was sent and there is nothing to stop.
75
+ */
76
+ private stopSession;
66
77
  /**
67
78
  * Send a message through AG-UI protocol
68
79
  * @param message - The new message to send
@@ -86,8 +86,10 @@ export class AGUIAdapter extends BaseProtocolAdapter {
86
86
  disconnect() {
87
87
  // Abort every ongoing fetch request, not merely the newest.
88
88
  this.stopGeneration();
89
- // Send terminate request to backend if we have a thread ID
90
- if (this.currentThreadId) {
89
+ // Send terminate request to backend if we have a thread ID. Never an
90
+ // application's session: a page going away, or the chat rebuilt with a
91
+ // fresh token, is not the person's Stop — it waits for them to come back.
92
+ if (this.currentThreadId && !this.aguiConfig.appSessions) {
91
93
  this.terminateAgent(this.currentThreadId).catch(err => {
92
94
  console.warn('[AGUIAdapter] Failed to terminate agent:', err);
93
95
  });
@@ -98,6 +100,11 @@ export class AGUIAdapter extends BaseProtocolAdapter {
98
100
  * Terminate a running agent thread on the backend
99
101
  */
100
102
  async terminateAgent(threadId) {
103
+ const sessions = this.aguiConfig.appSessions;
104
+ if (sessions) {
105
+ await this.stopSession(sessions, threadId || this.currentThreadId);
106
+ return;
107
+ }
101
108
  try {
102
109
  // Derive the terminate endpoint from the base URL
103
110
  // e.g., http://localhost:8765/api/v1/ag-ui/default -> http://localhost:8765/api/v1/ag-ui/terminate
@@ -121,6 +128,24 @@ export class AGUIAdapter extends BaseProtocolAdapter {
121
128
  // Ignore errors - agent may have already completed
122
129
  }
123
130
  }
131
+ /**
132
+ * Stop an application's session (`POST {sessions}/{uid}/stop`): what runs
133
+ * is cancelled and it waits to be resumed. Its thread is the session; with
134
+ * none, nothing was sent and there is nothing to stop.
135
+ */
136
+ async stopSession(sessions, threadId) {
137
+ if (!threadId) {
138
+ return;
139
+ }
140
+ const url = `${sessions.replace(/\/+$/, '')}/${encodeURIComponent(threadId)}/stop`;
141
+ const response = await this.fetchOf()(url, {
142
+ method: 'POST',
143
+ headers: this.buildHeaders(),
144
+ });
145
+ if (!response.ok) {
146
+ throw new Error(`The session ${threadId} could not be stopped (${response.status}).`);
147
+ }
148
+ }
124
149
  /**
125
150
  * Send a message through AG-UI protocol
126
151
  * @param message - The new message to send
@@ -8,38 +8,38 @@
8
8
  * DO NOT EDIT MANUALLY - run 'make specs' to regenerate.
9
9
  */
10
10
  import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
11
- export declare const ACCOUNTING_APP_0_0_1: AppSpec;
12
- export declare const BOOKKEEPING_DESK_APP_0_0_1: AppSpec;
13
- export declare const CHANGE_DETECTION_APP_0_0_1: AppSpec;
14
- export declare const CROP_MONITORING_APP_0_0_1: AppSpec;
15
- export declare const CUSTOMER_INTERVIEW_APP_0_0_2: AppSpec;
16
- export declare const DATA_QUALITY_APP_0_0_2: AppSpec;
17
- export declare const DECIDE_APP_0_0_1: AppSpec;
18
- export declare const DISASTER_ASSESSMENT_APP_0_0_1: AppSpec;
19
- export declare const ERRAND_RUNNER_APP_0_0_1: AppSpec;
20
- export declare const EVENT_RESPONSE_APP_0_0_2: AppSpec;
21
- export declare const EXCHANGE_DESK_APP_0_0_2: AppSpec;
22
- export declare const GUEST_DESK_APP_0_0_1: AppSpec;
23
- export declare const INBOX_TRIAGE_APP_0_0_1: AppSpec;
24
- export declare const LIVE_SHOP_CHAT_APP_0_0_1: AppSpec;
25
- export declare const MODEL_CHOICE_APP_0_0_2: AppSpec;
26
- export declare const MONTH_END_CLOSE_APP_0_0_1: AppSpec;
27
- export declare const ORDER_DESK_APP_0_0_1: AppSpec;
28
- export declare const ORDER_TRACKER_APP_0_0_1: AppSpec;
29
- export declare const PIPELINE_REPORT_APP_0_0_1: AppSpec;
30
- export declare const QUOTE_CALCULATOR_APP_0_0_5: AppSpec;
31
- export declare const REPORT_FROM_A_FILE_APP_0_0_4: AppSpec;
32
- export declare const RETURNS_DESK_APP_0_0_1: AppSpec;
33
- export declare const SALES_APP_0_0_2: AppSpec;
34
- export declare const SHIP_OR_FIX_APP_0_0_2: AppSpec;
35
- export declare const SHOP_CONCIERGE_APP_0_0_1: AppSpec;
36
- export declare const SHOPPING_DESK_APP_0_0_1: AppSpec;
37
- export declare const SIGN_IN_DESK_APP_0_0_1: AppSpec;
38
- export declare const SIGNED_READER_APP_0_0_1: AppSpec;
39
- export declare const SUPPLIER_COMPARISON_APP_0_0_2: AppSpec;
40
- export declare const SUPPORT_DESK_APP_0_0_4: AppSpec;
41
- export declare const TRAVEL_DESK_APP_0_0_1: AppSpec;
42
- export declare const WEB_RESEARCH_APP_0_0_1: AppSpec;
11
+ export declare const ACCOUNTING_APP_0_0_2: AppSpec;
12
+ export declare const BOOKKEEPING_DESK_APP_0_0_2: AppSpec;
13
+ export declare const CHANGE_DETECTION_APP_0_0_2: AppSpec;
14
+ export declare const CROP_MONITORING_APP_0_0_2: AppSpec;
15
+ export declare const CUSTOMER_INTERVIEW_APP_0_0_3: AppSpec;
16
+ export declare const DATA_QUALITY_APP_0_0_3: AppSpec;
17
+ export declare const DECIDE_APP_0_0_2: AppSpec;
18
+ export declare const DISASTER_ASSESSMENT_APP_0_0_2: AppSpec;
19
+ export declare const ERRAND_RUNNER_APP_0_0_2: AppSpec;
20
+ export declare const EVENT_RESPONSE_APP_0_0_3: AppSpec;
21
+ export declare const EXCHANGE_DESK_APP_0_0_3: AppSpec;
22
+ export declare const GUEST_DESK_APP_0_0_2: AppSpec;
23
+ export declare const INBOX_TRIAGE_APP_0_0_2: AppSpec;
24
+ export declare const LIVE_SHOP_CHAT_APP_0_0_2: AppSpec;
25
+ export declare const MODEL_CHOICE_APP_0_0_3: AppSpec;
26
+ export declare const MONTH_END_CLOSE_APP_0_0_2: AppSpec;
27
+ export declare const ORDER_DESK_APP_0_0_2: AppSpec;
28
+ export declare const ORDER_TRACKER_APP_0_0_2: AppSpec;
29
+ export declare const PIPELINE_REPORT_APP_0_0_2: AppSpec;
30
+ export declare const QUOTE_CALCULATOR_APP_0_0_6: AppSpec;
31
+ export declare const REPORT_FROM_A_FILE_APP_0_0_5: AppSpec;
32
+ export declare const RETURNS_DESK_APP_0_0_2: AppSpec;
33
+ export declare const SALES_APP_0_0_3: AppSpec;
34
+ export declare const SHIP_OR_FIX_APP_0_0_3: AppSpec;
35
+ export declare const SHOP_CONCIERGE_APP_0_0_2: AppSpec;
36
+ export declare const SHOPPING_DESK_APP_0_0_2: AppSpec;
37
+ export declare const SIGN_IN_DESK_APP_0_0_2: AppSpec;
38
+ export declare const SIGNED_READER_APP_0_0_2: AppSpec;
39
+ export declare const SUPPLIER_COMPARISON_APP_0_0_3: AppSpec;
40
+ export declare const SUPPORT_DESK_APP_0_0_5: AppSpec;
41
+ export declare const TRAVEL_DESK_APP_0_0_2: AppSpec;
42
+ export declare const WEB_RESEARCH_APP_0_0_2: AppSpec;
43
43
  export declare const APP_CATALOGUE: Record<string, AppSpec>;
44
44
  /** An application, by `id` or `id:version`, or undefined. */
45
45
  export declare function getApp(ref: string): AppSpec | undefined;