@datalayer/agent-runtimes 1.3.10 → 1.3.13

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 (107) hide show
  1. package/README.md +1 -2
  2. package/lib/AgentNode.js +3 -3
  3. package/lib/api/runtimes/environments.d.ts +22 -1
  4. package/lib/api/runtimes/environments.js +31 -0
  5. package/lib/api/runtimes/healthz.js +4 -2
  6. package/lib/api/spacer/healthz.js +4 -2
  7. package/lib/chat/ChatFloating.d.ts +8 -1
  8. package/lib/chat/ChatFloating.js +5 -2
  9. package/lib/chat/base/ChatBase.js +39 -25
  10. package/lib/chat/base/modelChoice.d.ts +62 -0
  11. package/lib/chat/base/modelChoice.js +56 -0
  12. package/lib/chat/display/ButtonGlow.d.ts +9 -0
  13. package/lib/chat/display/ButtonGlow.js +60 -0
  14. package/lib/chat/display/EmptyState.js +4 -1
  15. package/lib/chat/header/ChatHeaderBase.js +4 -56
  16. package/lib/chat/header/ChatViewModeToggle.d.ts +29 -0
  17. package/lib/chat/header/ChatViewModeToggle.js +55 -0
  18. package/lib/chat/index.d.ts +1 -0
  19. package/lib/chat/index.js +1 -0
  20. package/lib/chat/messages/ChatMessageList.js +13 -5
  21. package/lib/chat/messages/FormSubmissionMessage.d.ts +17 -0
  22. package/lib/chat/messages/FormSubmissionMessage.js +49 -0
  23. package/lib/chat/messages/formSubmission.d.ts +41 -0
  24. package/lib/chat/messages/formSubmission.js +90 -0
  25. package/lib/chat/prompt/menus/ModelSelector.js +11 -4
  26. package/lib/chat/styles/streamdownStyles.js +40 -0
  27. package/lib/client/mixins/RuntimesMixin.js +1 -1
  28. package/lib/client/mixins/SpacerMixin.js +3 -3
  29. package/lib/components/a2ui/A2uiMarkdownProvider.d.ts +11 -0
  30. package/lib/components/a2ui/A2uiMarkdownProvider.js +13 -0
  31. package/lib/components/a2ui/index.d.ts +1 -0
  32. package/lib/components/a2ui/index.js +1 -0
  33. package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +8 -0
  34. package/lib/components/code-sandboxes/CodeSandboxLauncher.js +54 -5
  35. package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +12 -1
  36. package/lib/components/code-sandboxes/codeSandboxEnvironments.js +38 -1
  37. package/lib/components/icons/ArtifactIcon.js +2 -1
  38. package/lib/examples/A2UiAgentExample.js +36 -223
  39. package/lib/examples/A2UiJupyterOutputExample.js +11 -1
  40. package/lib/examples/A2UiViewerExample.d.ts +9 -2
  41. package/lib/examples/A2UiViewerExample.js +33 -3
  42. package/lib/examples/DecksAgent.d.ts +1 -1
  43. package/lib/examples/DecksAgent.js +1 -1
  44. package/lib/examples/main.js +37 -35
  45. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +1 -7
  46. package/lib/examples/utils/a2uiMarkdownProvider.js +6 -8
  47. package/lib/loop/core/index.d.ts +44 -4
  48. package/lib/loop/core/index.js +29 -2
  49. package/lib/loop/embed/LoopEmbed.js +7 -0
  50. package/lib/loop/index.d.ts +1 -1
  51. package/lib/loop/index.js +4 -1
  52. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +22 -0
  53. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +64 -0
  54. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +38 -0
  55. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +64 -0
  56. package/lib/loop/plugins/a2ui-surface/index.d.ts +34 -0
  57. package/lib/loop/plugins/a2ui-surface/index.js +91 -0
  58. package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +52 -0
  59. package/lib/loop/plugins/a2ui-surface/toolResult.js +106 -0
  60. package/lib/loop/plugins/chat/ChatView.js +95 -15
  61. package/lib/loop/plugins/chat/index.d.ts +14 -0
  62. package/lib/loop/plugins/chat/index.js +1 -0
  63. package/lib/loop/plugins/document/index.js +4 -0
  64. package/lib/loop/plugins/notebook/index.js +4 -0
  65. package/lib/loop/plugins/page-layout/index.d.ts +36 -0
  66. package/lib/loop/plugins/page-layout/index.js +87 -7
  67. package/lib/loop/plugins/shell/useEditorPreviews.js +7 -0
  68. package/lib/loop/presets.d.ts +19 -0
  69. package/lib/loop/presets.js +9 -1
  70. package/lib/models/Deck.d.ts +14 -0
  71. package/lib/models/Deck.js +5 -0
  72. package/lib/models/Environment.d.ts +117 -0
  73. package/lib/models/ItemType.d.ts +1 -1
  74. package/lib/models/Runtime.d.ts +6 -0
  75. package/lib/models/SpaceItem.d.ts +2 -1
  76. package/lib/models/environments.generated.d.ts +35 -1
  77. package/lib/models/environments.generated.js +1 -1
  78. package/lib/models/index.d.ts +1 -0
  79. package/lib/models/index.js +1 -0
  80. package/lib/protocols/BrowserAgentAdapter.d.ts +9 -1
  81. package/lib/protocols/BrowserAgentAdapter.js +11 -4
  82. package/lib/runtimes/actions.d.ts +5 -0
  83. package/lib/runtimes/actions.js +19 -0
  84. package/lib/specs/agents/agents.d.ts +3 -1
  85. package/lib/specs/agents/agents.js +426 -130
  86. package/lib/specs/frontendTools.d.ts +1 -0
  87. package/lib/specs/frontendTools.js +31 -0
  88. package/lib/specs/mcpServers.js +13 -13
  89. package/lib/specs/models.js +7 -7
  90. package/lib/specs/outputs.js +9 -0
  91. package/lib/specs/skills.js +7 -7
  92. package/lib/specs/teams/teams.js +1 -1
  93. package/lib/tools/adapters/copilotkit/CopilotKitToolAdapter.js +1 -1
  94. package/lib/types/outputs.d.ts +2 -0
  95. package/package.json +10 -4
  96. package/patches/@primer+react+37.31.0.patch +390 -0
  97. package/patches/eslint-plugin-react+7.37.5.patch +65 -0
  98. package/scripts/apply-patches.sh +15 -1
  99. package/scripts/codegen/compose.py +4 -4
  100. package/scripts/codegen/generate_agents.py +1 -2
  101. package/scripts/codegen/generate_frontend_tools.py +1 -1
  102. package/scripts/codegen/generate_mcp_servers.py +2 -2
  103. package/scripts/codegen/generate_models.py +33 -8
  104. package/scripts/codegen/generate_outputs.py +2 -0
  105. package/scripts/codegen/generate_skills.py +2 -2
  106. package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
  107. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
@@ -3,9 +3,12 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box, IconButton, SegmentedControl, Text, Truncate, } from '@primer/react';
4
4
  import { useColorPalette } from '@datalayer/primer-addons';
5
5
  import { ScreenFullIcon, ScreenNormalIcon } from '@primer/octicons-react';
6
- import { signal } from '@datalayer/reactor';
6
+ import { computed, signal } from '@datalayer/reactor';
7
+ /* A signal to read when the layout publishes no live stance: the hook needs one. */
8
+ const NO_STANCE = signal(undefined);
7
9
  import { useContributions, useSignalValue, useGate, useReactorPlatform, ReactorLazy, ReactorSlot, useSlotComponents, } from '@datalayer/reactor/react';
8
10
  import { ChatBase } from '../../../chat/base/ChatBase';
11
+ import { readServerCatalogue } from '../../../chat/base/modelChoice';
9
12
  import { SUGGESTION_CHIP_WIDTH } from '../../../chat/display/EmptyState';
10
13
  import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
11
14
  import { useAnonymousSessionStore } from '../../../runtimes/browser/anonymousToken';
@@ -123,6 +126,11 @@ export default function ChatView({ workspace }) {
123
126
  its own way; first contribution wins. */
124
127
  const layoutEntries = useContributions(LoopChatLayout);
125
128
  const layout = layoutEntries[0]?.value;
129
+ // The composer's stance: live when the layout publishes one (the page
130
+ // layout, moving the composer as the display mode changes), its starting
131
+ // stance otherwise.
132
+ const liveStance = useSignalValue(layout?.promptStance ?? NO_STANCE);
133
+ const promptStance = liveStance ?? layout?.prompt;
126
134
  /* The current turn, kept for whoever shows the conversation without the
127
135
  transcript. The chat plugin contributed the feed at build; this view is
128
136
  its writer — begin on send, the reply as it arrives, end when it stops. */
@@ -136,7 +144,16 @@ export default function ChatView({ workspace }) {
136
144
  handler memoised with empty deps. Set once the spec is resolved below. */
137
145
  const agentNameRef = useRef('');
138
146
  const extrasEntries = useContributions(LoopChatExtras);
139
- const chatExtras = useSignalValue(extrasEntries[0]?.value.extras ?? EMPTY_CHAT_EXTRAS);
147
+ /*
148
+ * Every contribution, merged, a later one winning a key both set.
149
+ *
150
+ * Only the first used to be read, which made the extras the property of
151
+ * whichever plugin registered first: the loop's own surface plugin
152
+ * contributes a tool-result renderer, and a host example contributing a
153
+ * banner beside it would have silenced one or the other.
154
+ */
155
+ const mergedExtras = useMemo(() => computed(() => extrasEntries.reduce((merged, entry) => ({ ...merged, ...entry.value.extras.value }), {})), [extrasEntries]);
156
+ const chatExtras = useSignalValue(extrasEntries.length > 0 ? mergedExtras : EMPTY_CHAT_EXTRAS);
140
157
  /* Which editors have ever been mounted; they are never unmounted after —
141
158
  see the render below. A ref, not state: adding to it during render is
142
159
  idempotent and must not schedule another one. */
@@ -301,18 +318,43 @@ export default function ChatView({ workspace }) {
301
318
  busy,
302
319
  stop: controlsRef.current?.stop,
303
320
  newChat: controlsRef.current?.newChat,
321
+ send: controlsRef.current?.send,
304
322
  }
305
323
  : null);
306
324
  }, [busy, sendReady, setViewControls]);
307
325
  // Stop reporting when the view goes away, or the shell would keep a stop
308
326
  // button for something that is no longer there.
309
327
  useEffect(() => () => setViewControls(null), [setViewControls]);
328
+ /*
329
+ * Said before the adapter could take it.
330
+ *
331
+ * The composer is live while the chat is still starting — the shell shows
332
+ * it as soon as the view mounts — and a message sent in that moment used
333
+ * to be dropped, on the grounds that sending it late and out of context
334
+ * would be worse. It was worse: the message stood in the conversation with
335
+ * no reply and no reason, and the only way to find out was to send it
336
+ * again. Held instead, and sent the moment the adapter is ready, which is
337
+ * the next second or two rather than minutes.
338
+ */
339
+ const heldForAdapter = useRef([]);
310
340
  useEffect(() => workspace.prompts.subscribe(message => {
311
- // A prompt typed before the adapter is ready is dropped rather than
312
- // queued: silently sending it minutes later, out of context, is worse
313
- // than saying nothing happened.
314
- controlsRef.current?.send(message);
341
+ const send = controlsRef.current?.send;
342
+ if (!send) {
343
+ heldForAdapter.current.push(message);
344
+ return;
345
+ }
346
+ send(message);
315
347
  }), [workspace.prompts]);
348
+ // The adapter arrived: whatever was said while it was starting goes now.
349
+ useEffect(() => {
350
+ if (!sendReady || heldForAdapter.current.length === 0) {
351
+ return;
352
+ }
353
+ const held = heldForAdapter.current.splice(0, heldForAdapter.current.length);
354
+ for (const message of held) {
355
+ controlsRef.current?.send(message);
356
+ }
357
+ }, [sendReady]);
316
358
  /*
317
359
  * Fired once, on the first real send, for the life of this mounted chat —
318
360
  * a ref rather than a signal, because nothing here needs to re-render when
@@ -627,7 +669,11 @@ export default function ChatView({ workspace }) {
627
669
  */
628
670
  const suggestionEntries = useContributions(LoopChatSuggestion);
629
671
  const hiddenOpeners = chatExtras.hiddenOpeners;
672
+ const hideOpeners = chatExtras.hideOpeners;
630
673
  const suggestions = useMemo(() => {
674
+ if (hideOpeners) {
675
+ return [];
676
+ }
631
677
  // What the host took off this page, by the chip's words — the same key
632
678
  // the two-group merge below uses to tell openers apart.
633
679
  const hidden = new Set(hiddenOpeners ?? []);
@@ -652,7 +698,15 @@ export default function ChatView({ workspace }) {
652
698
  .filter(item => !listed.has(item.text))
653
699
  .map(item => ({ ...item, group: memberName })),
654
700
  ];
655
- }, [suggestionEntries, team, spec, member, agentId, hiddenOpeners]);
701
+ }, [
702
+ suggestionEntries,
703
+ team,
704
+ spec,
705
+ member,
706
+ agentId,
707
+ hiddenOpeners,
708
+ hideOpeners,
709
+ ]);
656
710
  /*
657
711
  * The same openers in the shape the chat's empty state asks for.
658
712
  *
@@ -736,13 +790,18 @@ export default function ChatView({ workspace }) {
736
790
  }
737
791
  // A host example's own frontend tools, folded in last (first name wins),
738
792
  // so a bespoke client tool like A2UI's run_jupyter_output_demo joins the
739
- // agent's toolset without a plugin of its own.
793
+ // agent's toolset without a plugin of its own. Vouched for in the chat
794
+ // view as well: the filter below keeps the tools that leave the editors
795
+ // alone, and a host's tool touches no editor — the Jupyter Output
796
+ // example's chat had lost its one tool to that filter and answered
797
+ // "use the demo button" instead of running the demonstration.
740
798
  for (const tool of chatExtras.frontendTools ?? []) {
741
799
  if (owners.has(tool.name)) {
742
800
  continue;
743
801
  }
744
802
  owners.set(tool.name, 'chat-extras');
745
803
  merged.push(tool);
804
+ keep.add(tool.name);
746
805
  }
747
806
  /*
748
807
  * The view decides the toolset. With an editor on screen the agent has
@@ -1070,7 +1129,10 @@ export default function ChatView({ workspace }) {
1070
1129
  .then(response => (response.ok ? response.json() : { models: [] }))
1071
1130
  .then(payload => {
1072
1131
  if (!cancelled) {
1073
- setCatalogModels(payload.models ?? []);
1132
+ // In the chat's shape: the route says `available`, the menu and
1133
+ // the opening pick read `isAvailable`, and an unmapped row passes
1134
+ // for usable. See `readServerCatalogue`.
1135
+ setCatalogModels(readServerCatalogue(payload));
1074
1136
  }
1075
1137
  })
1076
1138
  // No catalogue is not an error worth a banner: the menu simply has
@@ -1183,13 +1245,14 @@ export default function ChatView({ workspace }) {
1183
1245
  // In a draggable card over the workspace when the host asked for the
1184
1246
  // floating placement; the dragging is the prompt's own feature.
1185
1247
  placement: floatingPrompt ||
1186
- layout?.prompt === 'floating' ||
1187
- layout?.prompt === 'floating-top'
1248
+ promptStance === 'floating' ||
1249
+ promptStance === 'floating-top' ||
1250
+ promptStance === 'floating-bottom'
1188
1251
  ? 'floating'
1189
1252
  : 'docked',
1190
1253
  // Where a floating card starts: the page layout wants it over the top
1191
1254
  // of the sheet, like a document's title bar; everyone else, bottom.
1192
- floatingAnchor: layout?.prompt === 'floating-top' ? 'top' : 'bottom',
1255
+ floatingAnchor: promptStance === 'floating-top' ? 'top' : 'bottom',
1193
1256
  // The agent is working: `isLoading` disables the editor and turns the
1194
1257
  // send button into a stop — the person keeps a way out.
1195
1258
  isLoading: busy,
@@ -1206,8 +1269,9 @@ export default function ChatView({ workspace }) {
1206
1269
  // Both of the page layout's placements are that bar — one floating over
1207
1270
  // the page, one docked above it — so both take the tighter padding.
1208
1271
  padding: topPrompt ||
1209
- layout?.prompt === 'floating-top' ||
1210
- layout?.prompt === 'docked-top'
1272
+ promptStance === 'floating-top' ||
1273
+ promptStance === 'floating-bottom' ||
1274
+ promptStance === 'docked-top'
1211
1275
  ? 2
1212
1276
  : 3,
1213
1277
  promptVariant: 'lexical',
@@ -1340,6 +1404,16 @@ export default function ChatView({ workspace }) {
1340
1404
  // placeholder does: a dead control with no explanation is worse
1341
1405
  // than an absent one.
1342
1406
  disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
1407
+ /*
1408
+ The model this view is on, as the chat's opening pick.
1409
+
1410
+ `ChatBase` keeps the model it sends with, and opened on the
1411
+ first row of whatever catalogue it had before the server
1412
+ answered — Alibaba — while the footer, drawn from
1413
+ `activeModel`, said Claude. Told the same model, both agree
1414
+ from the first message.
1415
+ */
1416
+ initialModel: activeModel || undefined,
1343
1417
  /*
1344
1418
  Who is answering, in the words its spec uses.
1345
1419
 
@@ -1408,7 +1482,13 @@ export default function ChatView({ workspace }) {
1408
1482
  props through `renderHeader`. No contribution — or a host
1409
1483
  that set `hideHeader` — means no bar.
1410
1484
  */
1411
- showHeader: !config?.hideHeader && !!HeaderComponent, renderHeader: HeaderComponent
1485
+ showHeader: !config?.hideHeader && !!HeaderComponent,
1486
+ // The `+` and the bin in the title bar, when the host asked for
1487
+ // them; the header draws whatever `ChatBase` assembles here.
1488
+ headerButtons: {
1489
+ showNewChat: config?.headerButtons?.newChat ?? false,
1490
+ showClear: config?.headerButtons?.clear ?? false,
1491
+ }, renderHeader: HeaderComponent
1412
1492
  ? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
1413
1493
  : undefined,
1414
1494
  /*
@@ -44,6 +44,20 @@ export type ChatPluginConfig = {
44
44
  * directly beneath repeats the point in a smaller font.
45
45
  */
46
46
  hideHeader: boolean;
47
+ /**
48
+ * Whether the title bar offers a way to start over.
49
+ *
50
+ * `newChat` is the `+`: a fresh thread, the transcript emptied. `clear`
51
+ * is the bin: the same emptying, behind a confirmation, and drawn only
52
+ * while there is something to empty. Both off by default — a workspace
53
+ * embedded in a page is usually one conversation — and on for an example
54
+ * whose reader will ask for one surface after another and want the last
55
+ * one gone.
56
+ */
57
+ headerButtons: {
58
+ newChat: boolean;
59
+ clear: boolean;
60
+ };
47
61
  /**
48
62
  * Whether the chat renders a prompt at all.
49
63
  *
@@ -44,6 +44,7 @@ export const ChatPlugin = definePlugin({
44
44
  defaultSurface: 'notebook',
45
45
  showSurfaceSelector: true,
46
46
  hideHeader: false,
47
+ headerButtons: { newChat: false, clear: false },
47
48
  hidePrompt: false,
48
49
  promptPlacement: 'bottom',
49
50
  autoFocusPrompt: true,
@@ -79,6 +79,10 @@ export const DocumentPlugin = definePlugin({
79
79
  id: 'document-tools',
80
80
  tools: workspace => createLexicalTools(workspace.surfaceId),
81
81
  }, { id: 'document-tools' });
82
+ }, error => {
83
+ // The chunk did not load, or the page went away while it did: the
84
+ // chat simply has no document tools.
85
+ console.warn('The document tools could not be loaded', error);
82
86
  });
83
87
  return {};
84
88
  },
@@ -84,6 +84,10 @@ export const NotebookPlugin = definePlugin({
84
84
  id: 'notebook-tools',
85
85
  tools: workspace => createNotebookTools(workspace.surfaceId),
86
86
  }, { id: 'notebook-tools' });
87
+ }, error => {
88
+ // The chunk did not load, or the page went away while it did: the
89
+ // chat simply has no notebook tools.
90
+ console.warn('The notebook tools could not be loaded', error);
87
91
  });
88
92
  return {};
89
93
  },
@@ -1,6 +1,9 @@
1
+ import { type PageSize } from '@datalayer/primer-addons/lib/reactor';
2
+ import type { ChatViewMode } from '../../../types/chat';
1
3
  import { type TurnPanelFooter } from './TurnPanel';
2
4
  export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversationPanel, } from '@datalayer/primer-addons/lib/reactor';
3
5
  export type { TurnPanelFooter } from './TurnPanel';
6
+ export type { PageSize, PageSizeFormat, } from '@datalayer/primer-addons/lib/reactor';
4
7
  export declare const LOOP_PAGE_LAYOUT_PLUGIN_NAME = "@datalayer/loop-plugin-page-layout";
5
8
  /** What a host may set on the page layout. */
6
9
  export type LoopPageLayoutConfig = {
@@ -22,6 +25,20 @@ export type LoopPageLayoutConfig = {
22
25
  * kept clear for it — the document application's title bar, movable.
23
26
  */
24
27
  prompt: 'docked' | 'floating';
28
+ /**
29
+ * Which edge of the page a floating composer starts at: `top` (the
30
+ * default), where a document's title bar would be, or `bottom`, where a
31
+ * chat's composer waits. Either way it is dragged from there. Only read
32
+ * with a `floating` prompt.
33
+ */
34
+ promptAnchor?: 'top' | 'bottom';
35
+ /**
36
+ * The sheet's size: free (the default) at the layout's reading width, or
37
+ * a paper — `{ format: 'letter' }`, `{ format: 'a4' }` — whose width the
38
+ * sheet takes and whose height it is at least, with a free `width` or
39
+ * `height` over either. Omitted: free.
40
+ */
41
+ pageSize?: PageSize;
25
42
  /**
26
43
  * What the turn panel draws under the reply.
27
44
  *
@@ -31,5 +48,24 @@ export type LoopPageLayoutConfig = {
31
48
  */
32
49
  turnPanelFooter: TurnPanelFooter;
33
50
  };
51
+ /**
52
+ * How the page layout shows the chat, when the composer is the floating
53
+ * card: the card itself over the page (`floating-draggable`, where it
54
+ * starts), or the conversation panel with the composer standing in it —
55
+ * beside the page (`sidebar`), over its right edge at full height
56
+ * (`floating`), or as a window in its corner (`floating-small`). The four
57
+ * modes of the chat header's toggle, which the card carries in its own
58
+ * footer. One signal for the workspace, as `pageLayoutPanelOpen` is: the
59
+ * mode is the person's, not a build's.
60
+ */
61
+ export declare const pageLayoutChatMode: import("@datalayer/reactor").Signal<ChatViewMode>;
62
+ /** Where a mode puts the composer (the band) and the conversation (the panel). */
63
+ export type PageLayoutChatPlacement = {
64
+ bandMode: 'floating' | 'panel';
65
+ panelMode: 'docked' | 'overlay' | 'popup';
66
+ };
67
+ export declare function pageLayoutForChatMode(mode: ChatViewMode): PageLayoutChatPlacement;
68
+ /** Picks a mode: the layout follows, and so does the composer's stance. */
69
+ export declare function setPageLayoutChatMode(mode: ChatViewMode): void;
34
70
  export declare const LoopPageLayoutPlugin: import("@datalayer/reactor").ReactorPlugin<LoopPageLayoutConfig, unknown, unknown>;
35
71
  export default LoopPageLayoutPlugin;
@@ -2,7 +2,8 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { CommentDiscussionIcon } from '@primer/octicons-react';
3
3
  import { definePlugin, signal } from '@datalayer/reactor';
4
4
  import { useContributions, useSignalValue } from '@datalayer/reactor/react';
5
- import { PageLayout, PagePanelToggle, } from '@datalayer/primer-addons/lib/reactor';
5
+ import { PageLayout, PagePanelToggle, openPagePanel, } from '@datalayer/primer-addons/lib/reactor';
6
+ import { ChatViewModeToggle } from '../../../chat/header/ChatViewModeToggle';
6
7
  import { LoopChatLayout, LoopChatTurn, LoopPromptPanel, LoopSlots, } from '../../core';
7
8
  import { TurnPanel } from './TurnPanel';
8
9
  /*
@@ -14,6 +15,53 @@ export { pageLayoutPanelOpen, pageLayoutSheet, openPagePanel as openConversation
14
15
  export const LOOP_PAGE_LAYOUT_PLUGIN_NAME = '@datalayer/loop-plugin-page-layout';
15
16
  /* A signal to read when no chat contributed a turn: the hook needs one. */
16
17
  const NO_TURN = signal({ id: 0, status: 'idle' });
18
+ /**
19
+ * How the page layout shows the chat, when the composer is the floating
20
+ * card: the card itself over the page (`floating-draggable`, where it
21
+ * starts), or the conversation panel with the composer standing in it —
22
+ * beside the page (`sidebar`), over its right edge at full height
23
+ * (`floating`), or as a window in its corner (`floating-small`). The four
24
+ * modes of the chat header's toggle, which the card carries in its own
25
+ * footer. One signal for the workspace, as `pageLayoutPanelOpen` is: the
26
+ * mode is the person's, not a build's.
27
+ */
28
+ export const pageLayoutChatMode = signal('floating-draggable');
29
+ /** The composer's stance for the chat view, following the mode above. */
30
+ const promptStance = signal('floating-top');
31
+ /*
32
+ * Which edge the card returns to when the mode puts it back over the page:
33
+ * the build's own `promptAnchor`, kept here because the mode is picked
34
+ * through a module-level function, as `pageLayoutChatMode` itself is. One
35
+ * page layout to a page, the assumption that signal already makes.
36
+ */
37
+ let floatingStance = 'floating-top';
38
+ export function pageLayoutForChatMode(mode) {
39
+ switch (mode) {
40
+ case 'sidebar':
41
+ return { bandMode: 'panel', panelMode: 'docked' };
42
+ case 'floating':
43
+ return { bandMode: 'panel', panelMode: 'overlay' };
44
+ case 'floating-small':
45
+ return { bandMode: 'panel', panelMode: 'popup' };
46
+ default:
47
+ return { bandMode: 'floating', panelMode: 'docked' };
48
+ }
49
+ }
50
+ /** Picks a mode: the layout follows, and so does the composer's stance. */
51
+ export function setPageLayoutChatMode(mode) {
52
+ pageLayoutChatMode.value = mode;
53
+ const { bandMode } = pageLayoutForChatMode(mode);
54
+ promptStance.value = bandMode === 'floating' ? floatingStance : 'docked-top';
55
+ // The composer went into the panel: the panel opens to show it.
56
+ if (bandMode === 'panel') {
57
+ openPagePanel();
58
+ }
59
+ }
60
+ /** The toggle in the floating composer's footer. */
61
+ function ChatModeAction() {
62
+ const mode = useSignalValue(pageLayoutChatMode);
63
+ return _jsx(ChatViewModeToggle, { value: mode, onChange: setPageLayoutChatMode });
64
+ }
17
65
  /**
18
66
  * The chat view's parts on the page layout.
19
67
  *
@@ -21,10 +69,16 @@ const NO_TURN = signal({ id: 0, status: 'idle' });
21
69
  * what the agent is doing — the turn's `activity` — is the line at the top
22
70
  * of the sheet, so the change is seen where it happens.
23
71
  */
24
- function LoopPageLayout({ parts, promptMode, }) {
72
+ function LoopPageLayout({ parts, promptMode, pageSize, }) {
25
73
  const turnEntries = useContributions(LoopChatTurn);
26
74
  const turn = useSignalValue(turnEntries[0]?.value.turn ?? NO_TURN);
27
- return (_jsx(PageLayout, { page: parts.editors, hasPage: parts.hasEditor, panel: parts.transcript, band: parts.prompt, chips: parts.chips, picker: parts.picker, transient: parts.transient, activity: turn.activity, bandMode: promptMode }));
75
+ const chatMode = useSignalValue(pageLayoutChatMode);
76
+ // The display modes are the floating card's own; a docked composer stays
77
+ // docked above the page, whatever mode was picked elsewhere.
78
+ const placement = promptMode === 'floating'
79
+ ? pageLayoutForChatMode(chatMode)
80
+ : { bandMode: 'docked', panelMode: 'docked' };
81
+ return (_jsx(PageLayout, { page: parts.editors, hasPage: parts.hasEditor, panel: parts.transcript, band: parts.prompt, chips: parts.chips, picker: parts.picker, transient: parts.transient, activity: turn.activity, bandMode: placement.bandMode, panelMode: placement.panelMode, pageSize: pageSize }));
28
82
  }
29
83
  /** The header button that opens and closes the conversation panel. */
30
84
  function ConversationToggle() {
@@ -32,7 +86,13 @@ function ConversationToggle() {
32
86
  }
33
87
  export const LoopPageLayoutPlugin = definePlugin({
34
88
  name: LOOP_PAGE_LAYOUT_PLUGIN_NAME,
35
- config: { turnPanel: 'below', turnPanelFooter: 'full', prompt: 'docked' },
89
+ config: {
90
+ turnPanel: 'below',
91
+ turnPanelFooter: 'full',
92
+ prompt: 'docked',
93
+ promptAnchor: 'top',
94
+ pageSize: undefined,
95
+ },
36
96
  displayName: 'Page layout',
37
97
  description: 'The editor — or, in the chat view, the conversation — on a centred sheet, the prompt docked above it at the same width or floating over it as a draggable card, the current turn under the prompt, the conversation in a side panel.',
38
98
  octicon: 'file',
@@ -42,15 +102,24 @@ export const LoopPageLayoutPlugin = definePlugin({
42
102
  The layout, contributed per build because the composer's stance is
43
103
  configuration: `docked-top` means the layout owns the composer's width
44
104
  — `PageLayout` gives it a mount point the width of the sheet, so the
45
- prompt and the page read as one column; `floating-top` is the draggable
105
+ prompt and the page read as one column; `floating-top` and
106
+ `floating-bottom` are the draggable
46
107
  card, sized by itself, over a strip of canvas kept clear for it. The
47
108
  component closes over the choice once, per build.
48
109
  */
49
110
  const promptMode = config.prompt;
50
- const ConfiguredLayout = (parts) => (_jsx(LoopPageLayout, { parts: parts, promptMode: promptMode }));
111
+ const pageSize = config.pageSize;
112
+ floatingStance =
113
+ config.promptAnchor === 'bottom' ? 'floating-bottom' : 'floating-top';
114
+ if (promptMode === 'floating') {
115
+ promptStance.value = floatingStance;
116
+ }
117
+ const ConfiguredLayout = (parts) => (_jsx(LoopPageLayout, { parts: parts, promptMode: promptMode, pageSize: pageSize }));
51
118
  ctx.contribute(LoopChatLayout, {
52
119
  id: 'page-layout',
53
- prompt: promptMode === 'floating' ? 'floating-top' : 'docked-top',
120
+ prompt: promptMode === 'floating' ? floatingStance : 'docked-top',
121
+ // Live only for the card: its display modes move the composer.
122
+ promptStance: promptMode === 'floating' ? promptStance : undefined,
54
123
  Component: ConfiguredLayout,
55
124
  }, { id: 'page-layout' });
56
125
  // The turn panel is configuration-dependent, so it is contributed here
@@ -73,6 +142,17 @@ export const LoopPageLayoutPlugin = definePlugin({
73
142
  slot: LoopSlots.header,
74
143
  Component: ConversationToggle,
75
144
  },
145
+ // The display modes, in the floating card's footer — and only
146
+ // there: a docked composer has none to pick.
147
+ ...(promptMode === 'floating'
148
+ ? [
149
+ {
150
+ id: 'page-layout-chat-mode',
151
+ slot: LoopSlots.promptAction,
152
+ Component: ChatModeAction,
153
+ },
154
+ ]
155
+ : []),
76
156
  ],
77
157
  };
78
158
  },
@@ -179,6 +179,13 @@ function useDocumentRows(surfaceId, enabled) {
179
179
  };
180
180
  refresh();
181
181
  unsubscribe = lexicalStore.subscribe(refresh);
182
+ })
183
+ .catch(() => {
184
+ // Lexical could not load (or the page went away while it did): the
185
+ // document offers no preview, which is all this hook was for.
186
+ if (!cancelled) {
187
+ setRows(null);
188
+ }
182
189
  });
183
190
  return () => {
184
191
  cancelled = true;
@@ -22,6 +22,7 @@
22
22
  import { type PluginRef } from '@datalayer/reactor';
23
23
  import { type AgentsConfig, type SandboxTarget } from './plugins/agents';
24
24
  import { type ChatPluginConfig } from './plugins/chat';
25
+ import { type PageSize } from './plugins/page-layout';
25
26
  export type LoopPresetOptions = {
26
27
  /** Where the agent runtimes service is. */
27
28
  serverUrl?: string;
@@ -39,6 +40,11 @@ export type LoopPresetOptions = {
39
40
  showViewSelector?: boolean;
40
41
  /** Whether the chat draws its own header. */
41
42
  hideChatHeader?: boolean;
43
+ /**
44
+ * Whether the chat's header offers `+` (a new chat) and the bin (clear).
45
+ * Off by default; see `ChatPluginConfig.headerButtons`.
46
+ */
47
+ chatHeaderButtons?: boolean;
42
48
  /** Where the prompt sits. Passed through to the chat plugin. */
43
49
  promptPlacement?: ChatPluginConfig['promptPlacement'];
44
50
  /**
@@ -81,6 +87,19 @@ export type LoopPresetOptions = {
81
87
  * as a draggable card over the top of the canvas.
82
88
  */
83
89
  pageLayoutPrompt?: 'docked' | 'floating';
90
+ /**
91
+ * Which edge a floating composer starts at: `top` (the default) or
92
+ * `bottom`. It is dragged from there either way. Only read with
93
+ * `pageLayoutPrompt: 'floating'`.
94
+ */
95
+ pageLayoutPromptAnchor?: 'top' | 'bottom';
96
+ /**
97
+ * The page layout's sheet: free (the default) at its reading width, or a
98
+ * paper — `{ format: 'letter' }`, `{ format: 'a4' }` — whose width it
99
+ * takes and whose height it is at least; a free `width` or `height` over
100
+ * either. Only read with `pageLayout`.
101
+ */
102
+ pageLayoutSize?: PageSize;
84
103
  /**
85
104
  * What the turn panel draws under the reply: the full turn footer
86
105
  * (counters, copy, dismiss — the default), `actions` only, or `none`.
@@ -33,6 +33,7 @@ import { AgentspecsPlugin } from './plugins/agentspecs';
33
33
  import { AgentsPlugin, } from './plugins/agents';
34
34
  import { ChatPlugin } from './plugins/chat';
35
35
  import { ChatHeaderPlugin } from './plugins/chat-header';
36
+ import { A2uiSurfacePlugin } from './plugins/a2ui-surface';
36
37
  import { ChatViewPlugin } from './plugins/chat-view';
37
38
  import { DocumentViewPlugin } from './plugins/document-view';
38
39
  import { InputPromptPlugin } from './plugins/input-prompt';
@@ -51,7 +52,7 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
51
52
  * be installed or signed into, and the switches a demonstration wants are off.
52
53
  */
53
54
  export function loopPlugins(options = {}) {
54
- const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, promptPlacement, autoFocusPrompt = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
55
+ const { serverUrl, target = 'browser', defaultEditor = 'notebook', showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
55
56
  return [
56
57
  // The chat owns the editor beside it, so which one opens is its
57
58
  // configuration rather than the workspace's.
@@ -62,6 +63,7 @@ export function loopPlugins(options = {}) {
62
63
  // to choose.
63
64
  showSurfaceSelector: editors && showViewSelector && !editorSelector,
64
65
  hideHeader: hideChatHeader,
66
+ headerButtons: { newChat: chatHeaderButtons, clear: chatHeaderButtons },
65
67
  promptPlacement: floatingPrompt ? 'floating' : promptPlacement,
66
68
  autoFocusPrompt,
67
69
  fullScreenTopOffset,
@@ -72,6 +74,10 @@ export function loopPlugins(options = {}) {
72
74
  // still switched individually in the plugins panel.
73
75
  configurePlugin(InputPromptPlugin, { firstPromptHook }),
74
76
  ChatHeaderPlugin,
77
+ // An agent whose spec binds a tool to the `a2ui-surface` renderer gets
78
+ // its surfaces drawn and submitted without a line of host code. Idle
79
+ // for every other agent: it answers only for tools some spec bound.
80
+ A2uiSurfacePlugin,
75
81
  // One footer icon per view, in the composer where the writing hand
76
82
  // already is. Each editor's icon withdraws while its editor is not
77
83
  // contributed, so mounting these unconditionally costs an absent editor
@@ -127,6 +133,8 @@ export function loopPlugins(options = {}) {
127
133
  turnPanel: pageLayoutTurnPanel,
128
134
  turnPanelFooter: pageLayoutTurnPanelFooter,
129
135
  prompt: pageLayoutPrompt,
136
+ promptAnchor: pageLayoutPromptAnchor,
137
+ pageSize: pageLayoutSize,
130
138
  }),
131
139
  ]
132
140
  : []),
@@ -0,0 +1,14 @@
1
+ import { IItem } from './Item';
2
+ /**
3
+ * A deck: a presentation described as data, on the `@datalayer/decks`
4
+ * engine. `spec` is the deck specification the engine renders — the
5
+ * item's content, the way `nbformat` is a notebook's — present when the
6
+ * item was read on its own and absent in a listing.
7
+ */
8
+ export type IBaseDeck = IItem & {
9
+ spec?: any;
10
+ };
11
+ export type IDeck = IBaseDeck & {
12
+ type: 'deck';
13
+ };
14
+ export default IDeck;
@@ -0,0 +1,5 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ export {};