@datalayer/agent-runtimes 1.3.87 → 1.3.89

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.
Files changed (62) hide show
  1. package/lib/components/a2ui/datalayer/Download.d.ts +21 -0
  2. package/lib/components/a2ui/datalayer/Download.js +66 -0
  3. package/lib/components/a2ui/datalayer/drawn.d.ts +1 -1
  4. package/lib/components/a2ui/datalayer/drawn.js +1 -0
  5. package/lib/components/a2ui/datalayer/index.d.ts +2 -1
  6. package/lib/components/a2ui/datalayer/index.js +3 -0
  7. package/lib/components/inspector/a2aSpans.d.ts +9 -0
  8. package/lib/components/inspector/a2aSpans.js +11 -1
  9. package/lib/components/teams/AnswerSurfaces.d.ts +56 -0
  10. package/lib/components/teams/AnswerSurfaces.js +27 -0
  11. package/lib/components/teams/AnswerSurfacesView.d.ts +14 -0
  12. package/lib/components/teams/AnswerSurfacesView.js +30 -0
  13. package/lib/components/teams/index.d.ts +1 -0
  14. package/lib/components/teams/index.js +1 -0
  15. package/lib/components/teams/useA2ATeam.d.ts +56 -2
  16. package/lib/components/teams/useA2ATeam.js +122 -3
  17. package/lib/hooks/useAgentRuntimes.d.ts +13 -0
  18. package/lib/hooks/useAgentRuntimes.js +11 -1
  19. package/lib/loop/apps/AppFeedback.js +11 -2
  20. package/lib/loop/apps/AppRenderer.d.ts +13 -1
  21. package/lib/loop/apps/AppRenderer.js +34 -1
  22. package/lib/loop/apps/AppRuntimePlugins.d.ts +8 -0
  23. package/lib/loop/apps/AppRuntimePlugins.js +87 -0
  24. package/lib/loop/apps/feedback.d.ts +7 -0
  25. package/lib/loop/apps/feedback.js +3 -0
  26. package/lib/loop/apps/pluginPair.d.ts +60 -0
  27. package/lib/loop/apps/pluginPair.js +44 -0
  28. package/lib/loop/apps/threads.d.ts +35 -0
  29. package/lib/loop/apps/threads.js +79 -0
  30. package/lib/loop/embed/LoopEmbed.d.ts +4 -3
  31. package/lib/loop/embed/hostBridge.d.ts +27 -0
  32. package/lib/loop/embed/hostBridge.js +47 -0
  33. package/lib/loop/index.d.ts +1 -1
  34. package/lib/loop/index.js +1 -1
  35. package/lib/loop/plugins/agent-capacity/index.d.ts +7 -0
  36. package/lib/loop/plugins/agent-capacity/index.js +3 -2
  37. package/lib/loop/plugins/agents/DatalayerAgentBridge.js +11 -0
  38. package/lib/loop/plugins/agents/appLaunch.d.ts +15 -0
  39. package/lib/loop/plugins/agents/appLaunch.js +28 -0
  40. package/lib/loop/plugins/chat/ChatView.js +177 -109
  41. package/lib/loop/shell/LoopWorkspace.d.ts +3 -3
  42. package/lib/loop/shell/LoopWorkspace.js +1 -1
  43. package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +10 -2
  44. package/lib/runtimes/actions.js +23 -0
  45. package/lib/runtimes/apis.d.ts +18 -0
  46. package/lib/runtimes/browser/a2aPeer.d.ts +19 -2
  47. package/lib/runtimes/browser/a2aPeer.js +13 -7
  48. package/lib/specs/apps.js +102 -30
  49. package/lib/specs/envvars.d.ts +2 -0
  50. package/lib/specs/envvars.js +22 -0
  51. package/lib/specs/outputs.d.ts +1 -0
  52. package/lib/specs/outputs.js +12 -0
  53. package/lib/specs/sceneSchema.js +12 -1
  54. package/lib/specs/scenes.js +238 -121
  55. package/lib/specs/teams/teams.js +23 -7
  56. package/lib/specs/uiPlugins.js +55 -0
  57. package/lib/stores/agentRuntimeStore.js +4 -0
  58. package/lib/types/chat.d.ts +6 -0
  59. package/lib/types/scenes.d.ts +7 -1
  60. package/package.json +1 -1
  61. package/scripts/codegen/generate_envvars.py +0 -1
  62. package/scripts/codegen/generate_mcp_servers.py +11 -7
@@ -70,7 +70,7 @@ export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
70
70
  export { LoopEmbed, type LoopEmbedProps } from './embed/LoopEmbed';
71
71
  export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, type EmbedSession, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
72
72
  export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
73
- export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
73
+ export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, type SignedUserToken, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
74
74
  export { defineDatalayerAppElement, type DatalayerAppElementOptions, } from './embed/element';
75
75
  export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
76
76
  export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
package/lib/loop/index.js CHANGED
@@ -91,7 +91,7 @@ export { LoopEmbed } from './embed/LoopEmbed';
91
91
  export { AppEmbed, AppFloating, embedAssistantCharacter, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
92
92
  export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
93
93
  // The host page and an embedded application, talking (LOOP D-10).
94
- export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, } from './embed/hostBridge';
94
+ export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, } from './embed/hostBridge';
95
95
  export { defineDatalayerAppElement, } from './embed/element';
96
96
  export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, } from './embed/embedConfig';
97
97
  export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
@@ -40,6 +40,13 @@ export type AgentCapacityOptions = {
40
40
  model?: string;
41
41
  /** What its modes tell a run turned in the page (LOOP P-19). */
42
42
  modeEffect?: AgentBlueprintContribution['modeEffect'];
43
+ /**
44
+ * Its own name in place of `@datalayer/loop-plugin-agent-<key>`: an
45
+ * application's, the name its runtime's plugin has too (LOOP F-15).
46
+ */
47
+ name?: string;
48
+ /** The runtime plugins it cannot work without (Reactor's cross-tier declaration). */
49
+ requiredBackendPlugins?: string[];
43
50
  };
44
51
  export declare function defineAgentCapacityPlugin(options: AgentCapacityOptions): ReactorPlugin<Record<string, never>, unknown, unknown>;
45
52
  export default defineAgentCapacityPlugin;
@@ -23,9 +23,10 @@
23
23
  import { contribution, definePlugin, } from '@datalayer/reactor';
24
24
  import { LoopAgentBlueprint, LoopChatSuggestion, } from '../../core';
25
25
  export function defineAgentCapacityPlugin(options) {
26
- const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, modeEffect, } = options;
26
+ const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, modeEffect, name, requiredBackendPlugins, } = options;
27
27
  return definePlugin({
28
- name: `@datalayer/loop-plugin-agent-${key}`,
28
+ name: name ?? `@datalayer/loop-plugin-agent-${key}`,
29
+ ...(requiredBackendPlugins ? { requiredBackendPlugins } : {}),
29
30
  displayName,
30
31
  description,
31
32
  octicon: octicon ?? 'dependabot',
@@ -7,6 +7,7 @@ import { useReactorPlatform, useSignalValue } from '@datalayer/reactor/react';
7
7
  import { useAgentRuntimes } from '../../../hooks/useAgentRuntimes';
8
8
  import { AGENTSPECS } from '../../../specs/agents/agents';
9
9
  import { IDLE_SANDBOX_TARGET_SIGNAL } from '../../core';
10
+ import { appLaunchOf } from './appLaunch';
10
11
  import { AGENTS_PLUGIN_NAME } from './plugin';
11
12
  import { useOptionalSandboxService } from './useSandboxService';
12
13
  /**
@@ -69,9 +70,19 @@ export function DatalayerAgentBridge() {
69
70
  // An application's spec, when the workspace runs one.
70
71
  ...(createPayload ? { createPayload } : {}),
71
72
  }), [agentSpecId, agentName, protocol, createPayload]);
73
+ /*
74
+ * The application the runtime is launched for (STUDIO R-19): its Appspec
75
+ * and where it runs, read from the payload its agent is created with — the
76
+ * Studio's Preview, the hosted page signed in. Sent with the launch, so the
77
+ * runtime is given the secrets its connections declare before its agent is
78
+ * made; without it, a connection needing a secret is refused on the
79
+ * runtime. A bare spec launches none.
80
+ */
81
+ const appLaunch = useMemo(() => appLaunchOf(createPayload), [createPayload]);
72
82
  const { runtime, status, error } = useAgentRuntimes({
73
83
  agentSpecId,
74
84
  agentConfig,
85
+ ...(appLaunch ? { appLaunch } : {}),
75
86
  variant: 'cloud-pydanticai',
76
87
  autoStart: onDatalayer && !already,
77
88
  autoCreateAgent: onDatalayer && !already,
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What an application's runtime is launched with (STUDIO R-19).
3
+ *
4
+ * Pure, so it is read without the agent hook's runtime stack.
5
+ *
6
+ * @module loop/plugins/agents/appLaunch
7
+ */
8
+ import type { AppLaunch } from '../../../hooks/useAgentRuntimes';
9
+ /**
10
+ * The launch of the application an agent is created with, or none.
11
+ *
12
+ * `app_spec` and `app_instance` are what `appDatalayerCreatePayload` writes;
13
+ * the launch sends them as `RuntimesClient.create` does in Python.
14
+ */
15
+ export declare function appLaunchOf(createPayload: Record<string, unknown> | undefined): AppLaunch | undefined;
@@ -0,0 +1,28 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The launch of the application an agent is created with, or none.
7
+ *
8
+ * `app_spec` and `app_instance` are what `appDatalayerCreatePayload` writes;
9
+ * the launch sends them as `RuntimesClient.create` does in Python.
10
+ */
11
+ export function appLaunchOf(createPayload) {
12
+ const appSpec = createPayload?.app_spec;
13
+ if (!appSpec || typeof appSpec !== 'object') {
14
+ return undefined;
15
+ }
16
+ const instance = (createPayload?.app_instance ?? {});
17
+ const appUid = typeof instance.app_uid === 'string' && instance.app_uid
18
+ ? instance.app_uid
19
+ : undefined;
20
+ const deploymentUid = typeof instance.deployment_uid === 'string' && instance.deployment_uid
21
+ ? instance.deployment_uid
22
+ : undefined;
23
+ return {
24
+ appSpec: appSpec,
25
+ ...(appUid ? { appUid } : {}),
26
+ ...(deploymentUid ? { deploymentUid } : {}),
27
+ };
28
+ }
@@ -46,6 +46,8 @@ import { messageText, turnWritersOf } from './turnState';
46
46
  import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
47
47
  import { useAgentCommandTools } from '../../../tools/adapters/commands';
48
48
  import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
49
+ import { generateMessageId } from '../../../types/messages';
50
+ import { openThread, THREAD_WORDS } from '../../apps/threads';
49
51
  import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
50
52
  import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
51
53
  import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
@@ -1180,6 +1182,74 @@ export default function ChatView({ workspace }) {
1180
1182
  // not know (LOOP R-20): it runs a session of its deployment, and no more.
1181
1183
  const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
1182
1184
  const iamToken = visitors ? visitor.token : embedToken || memberToken;
1185
+ /*
1186
+ * A thread opened from the person's history (LOOP P-24): its conversation
1187
+ * read from the runtime once it answers — held there, else resumed from
1188
+ * its record — then drawn and gone on with on the same thread. One that
1189
+ * cannot be opened is said, and a new one starts.
1190
+ */
1191
+ const givenThread = config?.thread;
1192
+ const [openedThread, setOpenedThread] = useState(null);
1193
+ const opening = Boolean(givenThread?.fromRuntime && openedThread?.given !== givenThread.id);
1194
+ useEffect(() => {
1195
+ if (!givenThread?.fromRuntime || !runsApp || !agentServerUrl) {
1196
+ return;
1197
+ }
1198
+ let current = true;
1199
+ void (async () => {
1200
+ let failure = '';
1201
+ for (let attempt = 0; attempt < 4 && current; attempt += 1) {
1202
+ try {
1203
+ const messages = await openThread({
1204
+ agentBaseUrl: agentServerUrl,
1205
+ agentId,
1206
+ uid: givenThread.id,
1207
+ token: iamToken,
1208
+ });
1209
+ if (current) {
1210
+ setOpenedThread({
1211
+ given: givenThread.id,
1212
+ thread: { ...givenThread, fromRuntime: false, messages },
1213
+ });
1214
+ }
1215
+ return;
1216
+ }
1217
+ catch (error) {
1218
+ failure = error instanceof Error ? error.message : String(error);
1219
+ // The runtime may still be making the application's agent.
1220
+ await new Promise(resolve => setTimeout(resolve, 1500));
1221
+ }
1222
+ }
1223
+ if (current) {
1224
+ setOpenedThread({
1225
+ given: givenThread.id,
1226
+ thread: {
1227
+ id: generateMessageId(),
1228
+ messages: [],
1229
+ onStarted: givenThread.onStarted,
1230
+ },
1231
+ said: THREAD_WORDS.notOpened(failure),
1232
+ });
1233
+ }
1234
+ })();
1235
+ return () => {
1236
+ current = false;
1237
+ };
1238
+ // By the thread's id: its object is the host's.
1239
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1240
+ }, [
1241
+ givenThread?.id,
1242
+ givenThread?.fromRuntime,
1243
+ runsApp,
1244
+ agentServerUrl,
1245
+ agentId,
1246
+ iamToken,
1247
+ ]);
1248
+ const chatThread = givenThread?.fromRuntime
1249
+ ? openedThread?.given === givenThread.id
1250
+ ? openedThread.thread
1251
+ : undefined
1252
+ : givenThread;
1183
1253
  const protocol = useMemo(() => inPage
1184
1254
  ? browserProtocolConfig({
1185
1255
  agentId: member?.specId ?? agentId,
@@ -1561,44 +1631,42 @@ export default function ChatView({ workspace }) {
1561
1631
  borderColor: chatExtras.errorBanner.variant === 'warning'
1562
1632
  ? 'attention.muted'
1563
1633
  : 'danger.muted',
1564
- }, children: chatExtras.errorBanner.message })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: _jsx(ChatBase
1634
+ }, children: chatExtras.errorBanner.message })) : null, openedThread?.said && openedThread.given === givenThread?.id ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, px: 3, py: 2, fontSize: 1, color: 'fg.muted' }, children: openedThread.said })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: opening ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, p: 3, fontSize: 1, color: 'fg.muted' }, children: THREAD_WORDS.opening })) : (_jsx(ChatBase
1565
1635
  // The theme it wears, when the host names one (an application's).
1566
1636
  , {
1567
1637
  // The theme it wears, when the host names one (an application's).
1568
- themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode,
1569
- // The session an embed goes on with after a reload (LOOP D-13).
1570
- thread: config?.thread,
1638
+ themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode, thread: chatThread,
1571
1639
  // The header says why, beside the title, for the same reason the
1572
1640
  // placeholder does: a dead control with no explanation is worse
1573
1641
  // than an absent one.
1574
1642
  disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
1575
1643
  /*
1576
- The model this view is on, as the chat's opening pick.
1577
-
1578
- `ChatBase` keeps the model it sends with, and opened on the
1579
- first row of whatever catalogue it had before the server
1580
- answered — Alibaba — while the footer, drawn from
1581
- `activeModel`, said Claude. Told the same model, both agree
1582
- from the first message.
1583
- */
1644
+ The model this view is on, as the chat's opening pick.
1645
+
1646
+ `ChatBase` keeps the model it sends with, and opened on the
1647
+ first row of whatever catalogue it had before the server
1648
+ answered — Alibaba — while the footer, drawn from
1649
+ `activeModel`, said Claude. Told the same model, both agree
1650
+ from the first message.
1651
+ */
1584
1652
  initialModel: activeModel || undefined,
1585
1653
  /*
1586
- Who is answering, in the words its spec uses.
1587
-
1588
- `ChatBase` falls back to "Start a conversation with the AI
1589
- agent", which is true of every chat ever built and says
1590
- nothing about this one. The empty state is the first thing a
1591
- person sees and the only place the agent introduces itself.
1592
- */
1654
+ Who is answering, in the words its spec uses.
1655
+
1656
+ `ChatBase` falls back to "Start a conversation with the AI
1657
+ agent", which is true of every chat ever built and says
1658
+ nothing about this one. The empty state is the first thing a
1659
+ person sees and the only place the agent introduces itself.
1660
+ */
1593
1661
  title: presence?.name ?? member?.name ?? spec?.name ?? agentId, description: presence?.welcome ?? spec?.description,
1594
1662
  /*
1595
- Two sizes, because it is drawn in two places.
1596
-
1597
- The header wants a mark beside a line of text; the empty state
1598
- wants the thing a person's eye lands on first. One `brandIcon`
1599
- served both, so a 48px icon meant for the empty state sat in
1600
- the header at three times the height of the words next to it.
1601
- */
1663
+ Two sizes, because it is drawn in two places.
1664
+
1665
+ The header wants a mark beside a line of text; the empty state
1666
+ wants the thing a person's eye lands on first. One `brandIcon`
1667
+ served both, so a 48px icon meant for the empty state sat in
1668
+ the header at three times the height of the words next to it.
1669
+ */
1602
1670
  // Big enough to read as the agent's mark rather than as
1603
1671
  // punctuation before its name, and still short enough not to
1604
1672
  // set the header's height.
@@ -1615,11 +1683,11 @@ export default function ChatView({ workspace }) {
1615
1683
  // conversation".
1616
1684
  title: presence?.name ?? member?.name ?? spec?.name ?? agentId,
1617
1685
  /*
1618
- Two levels when there is a team: the team first — its name,
1619
- what it is for, what it can be asked — and under it the
1620
- member being addressed, with its own description and its
1621
- own openers. The groups match the ones the openers carry.
1622
- */
1686
+ Two levels when there is a team: the team first — its name,
1687
+ what it is for, what it can be asked — and under it the
1688
+ member being addressed, with its own description and its
1689
+ own openers. The groups match the ones the openers carry.
1690
+ */
1623
1691
  ...(team && member
1624
1692
  ? {
1625
1693
  sections: [
@@ -1640,11 +1708,11 @@ export default function ChatView({ workspace }) {
1640
1708
  : null),
1641
1709
  },
1642
1710
  /*
1643
- And what it can be asked. From the team rather than the
1644
- member: the supervisor answers first, and somebody who has
1645
- just opened the workspace does not yet know there are two
1646
- agents behind it.
1647
- */
1711
+ And what it can be asked. From the team rather than the
1712
+ member: the supervisor answers first, and somebody who has
1713
+ just opened the workspace does not yet know there are two
1714
+ agents behind it.
1715
+ */
1648
1716
  // With the prompt on top the openers are already chips under
1649
1717
  // it; the empty state repeating them was the same three buttons
1650
1718
  // twice on one screen.
@@ -1652,11 +1720,11 @@ export default function ChatView({ workspace }) {
1652
1720
  ? []
1653
1721
  : chatSuggestions,
1654
1722
  /*
1655
- The title bar arrives as a plugin: the chat-header plugin
1656
- contributes the component, this view hands it the assembled
1657
- props through `renderHeader`. No contribution — or a host
1658
- that set `hideHeader` — means no bar.
1659
- */
1723
+ The title bar arrives as a plugin: the chat-header plugin
1724
+ contributes the component, this view hands it the assembled
1725
+ props through `renderHeader`. No contribution — or a host
1726
+ that set `hideHeader` — means no bar.
1727
+ */
1660
1728
  showHeader: !config?.hideHeader && !!HeaderComponent,
1661
1729
  // The `+` and the bin in the title bar, when the host asked for
1662
1730
  // them; the header draws whatever `ChatBase` assembles here.
@@ -1667,19 +1735,19 @@ export default function ChatView({ workspace }) {
1667
1735
  ? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
1668
1736
  : undefined,
1669
1737
  /*
1670
- The (i), which opens the agent's details over the transcript.
1671
-
1672
- Worth having here in particular: this workspace can be moved
1673
- between four runtimes and several agents, and the details pane
1674
- is the only place that says which one is actually answering
1675
- and where it is running.
1676
- */
1738
+ The (i), which opens the agent's details over the transcript.
1739
+
1740
+ Worth having here in particular: this workspace can be moved
1741
+ between four runtimes and several agents, and the details pane
1742
+ is the only place that says which one is actually answering
1743
+ and where it is running.
1744
+ */
1677
1745
  showInformation: true,
1678
1746
  /*
1679
- Full screen: this view, on the whole screen, editor and all.
1680
- See `toggleFullScreen` for why it is the browser's API rather
1681
- than a big box.
1682
- */
1747
+ Full screen: this view, on the whole screen, editor and all.
1748
+ See `toggleFullScreen` for why it is the browser's API rather
1749
+ than a big box.
1750
+ */
1683
1751
  headerActions: _jsxs(Box, { sx: { display: 'inline-flex', alignItems: 'center', gap: 2 }, children: [workspace.chatHeaderActions, chatHeaderItems.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.chatHeader, props: { workspace } })) : null, _jsx(IconButton, { icon: fullScreen ? ScreenNormalIcon : ScreenFullIcon, "aria-label": fullScreen ? 'Exit full screen' : 'Enter full screen', variant: "invisible", size: "small", onClick: () => {
1684
1752
  // Whether or not it takes, the hint has been answered:
1685
1753
  // they found the control, which is all it was for.
@@ -1688,62 +1756,62 @@ export default function ChatView({ workspace }) {
1688
1756
  }, sx: hintFullScreen
1689
1757
  ? {
1690
1758
  /*
1691
- Colour and opacity, never geometry.
1692
-
1693
- The colour is the theme's own `primary` — the
1694
- same brand the page around this workspace is
1695
- already using — because a grey icon in a row of
1696
- grey icons cannot be picked out however hard it
1697
- fades. Colour is what makes it findable; the
1698
- fade is what makes it look like it is asking.
1699
-
1700
- It never fades to nothing: a control that
1701
- vanishes and returns reads as a rendering fault,
1702
- not an invitation. And nothing here moves or
1703
- resizes, so the header does not reflow and the
1704
- controls beside it stay where a reader last saw
1705
- them.
1706
- */
1759
+ Colour and opacity, never geometry.
1760
+
1761
+ The colour is the theme's own `primary` — the
1762
+ same brand the page around this workspace is
1763
+ already using — because a grey icon in a row of
1764
+ grey icons cannot be picked out however hard it
1765
+ fades. Colour is what makes it findable; the
1766
+ fade is what makes it look like it is asking.
1767
+
1768
+ It never fades to nothing: a control that
1769
+ vanishes and returns reads as a rendering fault,
1770
+ not an invitation. And nothing here moves or
1771
+ resizes, so the header does not reflow and the
1772
+ controls beside it stay where a reader last saw
1773
+ them.
1774
+ */
1707
1775
  '@keyframes dla-fullscreen-hint': {
1708
1776
  '0%, 100%': { opacity: 1 },
1709
1777
  '50%': { opacity: 0.4 },
1710
1778
  },
1711
1779
  /* `&&` doubles the specificity, because Primer
1712
- ships Button as a CSS module and
1713
- `prc-Button-*` outranks a single generated
1714
- class — without it the keyframes register and
1715
- the button sits there at `animation: none`.
1716
- The colour needs the same treatment: an
1717
- invisible IconButton sets `color` itself.
1718
-
1719
- And no `:focus` or `:hover` clause to stop it:
1720
- anything that matches the button while it is
1721
- being pointed at would cancel the pointing —
1722
- which is exactly how the first version of this
1723
- silently did nothing. It ends when the reader
1724
- takes the offer, or arrives without it. */
1780
+ ships Button as a CSS module and
1781
+ `prc-Button-*` outranks a single generated
1782
+ class — without it the keyframes register and
1783
+ the button sits there at `animation: none`.
1784
+ The colour needs the same treatment: an
1785
+ invisible IconButton sets `color` itself.
1786
+
1787
+ And no `:focus` or `:hover` clause to stop it:
1788
+ anything that matches the button while it is
1789
+ being pointed at would cancel the pointing —
1790
+ which is exactly how the first version of this
1791
+ silently did nothing. It ends when the reader
1792
+ takes the offer, or arrives without it. */
1725
1793
  '&&': {
1726
1794
  /*
1727
- The brand on the box, not on the glyph.
1728
-
1729
- Painting the icon `palette.primary` was the
1730
- obvious reading and it is measurably
1731
- backwards: in the theme this workspace ships
1732
- on, `primary` is #FFC107, which sits at 1.55
1733
- contrast against the header — where the
1734
- icon's ordinary olive sits at 5.07. Recolouring
1735
- the stroke made the control roughly three
1736
- times harder to see, and at the bottom of the
1737
- fade it was 1.2 and simply gone.
1738
-
1739
- A ring and a wash carry the same colour on a
1740
- far larger area, and they do it without asking
1741
- the glyph to be legible in a brand colour that
1742
- changes with every theme — this palette has
1743
- six, from a dark teal to this amber, and a
1744
- rule that works for one fails for another. The
1745
- glyph keeps the colour it can be read in.
1746
- */
1795
+ The brand on the box, not on the glyph.
1796
+
1797
+ Painting the icon `palette.primary` was the
1798
+ obvious reading and it is measurably
1799
+ backwards: in the theme this workspace ships
1800
+ on, `primary` is #FFC107, which sits at 1.55
1801
+ contrast against the header — where the
1802
+ icon's ordinary olive sits at 5.07. Recolouring
1803
+ the stroke made the control roughly three
1804
+ times harder to see, and at the bottom of the
1805
+ fade it was 1.2 and simply gone.
1806
+
1807
+ A ring and a wash carry the same colour on a
1808
+ far larger area, and they do it without asking
1809
+ the glyph to be legible in a brand colour that
1810
+ changes with every theme — this palette has
1811
+ six, from a dark teal to this amber, and a
1812
+ rule that works for one fails for another. The
1813
+ glyph keeps the colour it can be read in.
1814
+ */
1747
1815
  bg: `color-mix(in srgb, ${palette.primary} 16%, transparent)`,
1748
1816
  boxShadow: `inset 0 0 0 1.5px ${palette.primary}`,
1749
1817
  animation: `dla-fullscreen-hint ${FULLSCREEN_HINT_PERIOD_MS}ms ease-in-out infinite`,
@@ -1761,18 +1829,18 @@ export default function ChatView({ workspace }) {
1761
1829
  // second one: two input boxes on one screen is one too many.
1762
1830
  showInput: false, onSendReady: handleSendReady,
1763
1831
  /*
1764
- The notebook tools' results, drawn into the transcript — the
1765
- chat's own machinery, switched on only while no editor is on
1766
- screen: the hidden notebook is where the agent works, and the
1767
- transcript is then the one place the reader can see what a
1768
- tool did. With an editor open the change is visible where it
1769
- happened, and the default tool row is enough.
1770
- */
1832
+ The notebook tools' results, drawn into the transcript — the
1833
+ chat's own machinery, switched on only while no editor is on
1834
+ screen: the hidden notebook is where the agent works, and the
1835
+ transcript is then the one place the reader can see what a
1836
+ tool did. With an editor open the change is visible where it
1837
+ happened, and the default tool row is enough.
1838
+ */
1771
1839
  notebookToolSurfacesId: active ? undefined : workspace.surfaceId,
1772
1840
  // A host example's own tool-result renderer, when it feeds one
1773
1841
  // through the extras channel — the A2UI examples draw their
1774
1842
  // surface here. Wins over the notebook surfaces in ChatBase.
1775
- renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
1843
+ renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }, chatThread?.id)) }), besideChat && !promptHidden ? prompt : null] }) }));
1776
1844
  // Off when the host asked for the menu alone: the composer's suggestions
1777
1845
  // control carries the same openers (`suggestions` above).
1778
1846
  const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": chatText.suggestedPrompts, sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
@@ -21,7 +21,7 @@
21
21
  */
22
22
  import type { JSX } from 'react';
23
23
  import type { ReactNode } from 'react';
24
- import { type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
24
+ import { type PlatformInput, type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
25
25
  import { type LoopWorkspaceContext, type SandboxSnapshot } from '../core';
26
26
  export type LoopWorkspaceProps = {
27
27
  /** Server backing this session. */
@@ -125,6 +125,6 @@ export type LoopWorkspaceProps = {
125
125
  */
126
126
  sidebarRail?: boolean;
127
127
  };
128
- /** Build the platform for a set of plugins. */
129
- export declare function buildLoopReactor(extensions: PluginRef[]): ReactorPlatform;
128
+ /** Build the platform for a set of plugins, and of extensions grouping them. */
129
+ export declare function buildLoopReactor(extensions: PlatformInput[]): ReactorPlatform;
130
130
  export declare function LoopWorkspace(props: LoopWorkspaceProps): JSX.Element;
@@ -8,7 +8,7 @@ import { LoopCommand, LoopSlots, LoopViewType, canOpenView, createPromptChannel,
8
8
  import { SIDEBAR_WIDTH } from '../plugins/plugins-panel';
9
9
  import { ViewSwitcher } from './ViewSwitcher';
10
10
  import { SidebarRail } from './SidebarRail';
11
- /** Build the platform for a set of plugins. */
11
+ /** Build the platform for a set of plugins, and of extensions grouping them. */
12
12
  export function buildLoopReactor(extensions) {
13
13
  return buildReactorFromPlugins(extensions);
14
14
  }
@@ -16,7 +16,11 @@
16
16
  "description": "Answers requests for financial reports, such as open invoices, aged balances, a trial balance or a customer's ledger, from the Odoo books, which it only reads.",
17
17
  "tags": ["example", "accounting", "finance", "odoo", "a2a", "team"],
18
18
  "inputModes": ["text/plain"],
19
- "outputModes": ["text/markdown", "application/x-ipynb+json"],
19
+ "outputModes": [
20
+ "text/markdown",
21
+ "application/x-ipynb+json",
22
+ "application/json+a2ui"
23
+ ],
20
24
  "examples": [
21
25
  "List the customer invoices that are still open, with the total due.",
22
26
  "Give the aged receivables as of today, by customer.",
@@ -25,7 +29,11 @@
25
29
  }
26
30
  ],
27
31
  "defaultInputModes": ["text/plain"],
28
- "defaultOutputModes": ["text/markdown", "application/x-ipynb+json"],
32
+ "defaultOutputModes": [
33
+ "text/markdown",
34
+ "application/x-ipynb+json",
35
+ "application/json+a2ui"
36
+ ],
29
37
  "capabilities": {
30
38
  "streaming": true,
31
39
  "pushNotifications": false,
@@ -49,6 +49,20 @@ export async function createRuntime(options) {
49
49
  throw new Error(`Invalid runtime creditsLimit for environment ${options.environmentName}. ` +
50
50
  'A number of zero or more is required.');
51
51
  }
52
+ /*
53
+ * An application's launch carries its Appspec (STUDIO R-19), as
54
+ * `RuntimesClient.create` does in Python: the Operator hands it to the
55
+ * companion, which gives the runtime the secrets its connections declare
56
+ * before its agent is made. Refused here, before any request, as the
57
+ * Operator would refuse it (422).
58
+ */
59
+ if (options.deploymentUid && !options.appUid) {
60
+ throw new Error(`Deployment ${options.deploymentUid} is named without its application: give appUid.`);
61
+ }
62
+ if (options.appUid && !options.appSpec) {
63
+ throw new Error(`Application ${options.appUid} is launched without its Appspec: give appSpec, ` +
64
+ 'so its runtime is given the secrets its connections declare.');
65
+ }
52
66
  /*
53
67
  * `environment: {name, version}` — and the version is ADDITIVE.
54
68
  *
@@ -75,6 +89,15 @@ export async function createRuntime(options) {
75
89
  if (options.snapshot) {
76
90
  body['from'] = options.snapshot;
77
91
  }
92
+ if (options.appUid) {
93
+ body['app_uid'] = options.appUid;
94
+ }
95
+ if (options.deploymentUid) {
96
+ body['deployment_uid'] = options.deploymentUid;
97
+ }
98
+ if (options.appSpec) {
99
+ body['app_spec'] = options.appSpec;
100
+ }
78
101
  const data = await requestDatalayerAPI({
79
102
  url: runtimesUrl(runtimesStore.getState().runtimesUrl),
80
103
  method: 'POST',
@@ -51,6 +51,24 @@ export interface IRuntimeOptions {
51
51
  * Kernel snapshot to restore
52
52
  */
53
53
  snapshot?: string;
54
+ /**
55
+ * The Appspec of the application the runtime is launched for (STUDIO
56
+ * R-19), as its file holds it. The Operator hands it to the runtime's
57
+ * companion, which gives the runtime the secrets its connections declare
58
+ * before its agent is made. An application's launch carries it:
59
+ * `appUid` without it is refused.
60
+ */
61
+ appSpec?: Record<string, unknown>;
62
+ /**
63
+ * The saved application the runtime runs for (LOOP R-09): written on its
64
+ * reservation, so its running time is counted per application.
65
+ */
66
+ appUid?: string;
67
+ /**
68
+ * The deployment of that application it runs for. A deployment is always
69
+ * of an application: one named without `appUid` is refused.
70
+ */
71
+ deploymentUid?: string;
54
72
  }
55
73
  /**
56
74
  * Interface for the Environments Manager