@datalayer/agent-runtimes 1.3.29 → 1.3.32

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/lib/AgentNode.js +14 -33
  2. package/lib/chat/ChatFloating.js +40 -8
  3. package/lib/chat/assistant/AssistantStage.d.ts +26 -7
  4. package/lib/chat/assistant/AssistantStage.js +115 -8
  5. package/lib/chat/assistant/SpeechBalloon.js +7 -3
  6. package/lib/chat/assistant/characters.d.ts +32 -1
  7. package/lib/chat/assistant/characters.js +180 -15
  8. package/lib/chat/assistant/state.d.ts +28 -0
  9. package/lib/chat/assistant/state.js +31 -0
  10. package/lib/chat/base/ChatBase.js +74 -50
  11. package/lib/chat/base/modelChoice.d.ts +40 -1
  12. package/lib/chat/base/modelChoice.js +42 -15
  13. package/lib/chat/prompt/InputPrompt.d.ts +7 -1
  14. package/lib/chat/prompt/InputPrompt.js +2 -2
  15. package/lib/chat/prompt/InputPromptBase.js +1 -1
  16. package/lib/chat/prompt/menus/ModelSelector.d.ts +13 -1
  17. package/lib/chat/prompt/menus/ModelSelector.js +17 -5
  18. package/lib/chat/useViewportDrag.d.ts +9 -1
  19. package/lib/chat/useViewportDrag.js +4 -4
  20. package/lib/components/a2ui/index.d.ts +2 -0
  21. package/lib/components/a2ui/index.js +2 -0
  22. package/lib/components/a2ui/visibility.d.ts +15 -0
  23. package/lib/components/a2ui/visibility.js +24 -0
  24. package/lib/components/a2ui/visibleWhen.d.ts +17 -0
  25. package/lib/components/a2ui/visibleWhen.js +54 -0
  26. package/lib/examples/ChatAssistantExample.d.ts +4 -0
  27. package/lib/examples/ChatAssistantExample.js +26 -5
  28. package/lib/examples/utils/owlCharacterPlugin.d.ts +7 -0
  29. package/lib/examples/utils/owlCharacterPlugin.js +44 -0
  30. package/lib/loop/apps/AppFeedback.d.ts +27 -0
  31. package/lib/loop/apps/AppFeedback.js +78 -0
  32. package/lib/loop/apps/AppRenderer.d.ts +33 -1
  33. package/lib/loop/apps/AppRenderer.js +111 -29
  34. package/lib/loop/apps/agent.d.ts +15 -0
  35. package/lib/loop/apps/agent.js +42 -0
  36. package/lib/loop/apps/appspec.js +6 -3
  37. package/lib/loop/apps/checks.d.ts +10 -2
  38. package/lib/loop/apps/checks.js +21 -10
  39. package/lib/loop/apps/feedback.d.ts +51 -0
  40. package/lib/loop/apps/feedback.js +61 -0
  41. package/lib/loop/apps/index.d.ts +3 -0
  42. package/lib/loop/apps/index.js +3 -0
  43. package/lib/loop/apps/rules.d.ts +14 -0
  44. package/lib/loop/apps/rules.js +24 -0
  45. package/lib/loop/core/canvasBlocks.d.ts +16 -0
  46. package/lib/loop/core/canvasBlocks.js +13 -0
  47. package/lib/loop/core/index.d.ts +15 -0
  48. package/lib/loop/core/index.js +3 -0
  49. package/lib/loop/embed/AppEmbed.d.ts +42 -0
  50. package/lib/loop/embed/AppEmbed.js +205 -0
  51. package/lib/loop/embed/datalayer-app.d.ts +1 -0
  52. package/lib/loop/embed/datalayer-app.js +16 -0
  53. package/lib/loop/embed/element.d.ts +57 -0
  54. package/lib/loop/embed/element.js +405 -0
  55. package/lib/loop/embed/embedConfig.d.ts +120 -0
  56. package/lib/loop/embed/embedConfig.js +148 -0
  57. package/lib/loop/embed/embedTheme.d.ts +24 -0
  58. package/lib/loop/embed/embedTheme.js +83 -0
  59. package/lib/loop/index.d.ts +6 -2
  60. package/lib/loop/index.js +8 -2
  61. package/lib/loop/plugins/a2ui/A2uiView.js +2 -3
  62. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +23 -3
  63. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +36 -9
  64. package/lib/loop/plugins/agent-capacity/index.d.ts +4 -0
  65. package/lib/loop/plugins/agent-capacity/index.js +3 -1
  66. package/lib/loop/plugins/agents/switchable.d.ts +7 -0
  67. package/lib/loop/plugins/agents/switchable.js +91 -88
  68. package/lib/loop/plugins/app-page/AppPage.d.ts +16 -0
  69. package/lib/loop/plugins/app-page/AppPage.js +53 -0
  70. package/lib/loop/plugins/app-page/appPageModel.d.ts +180 -0
  71. package/lib/loop/plugins/app-page/appPageModel.js +443 -0
  72. package/lib/loop/plugins/app-page/index.d.ts +8 -0
  73. package/lib/loop/plugins/app-page/index.js +34 -0
  74. package/lib/loop/plugins/canvas-blocks/index.d.ts +63 -0
  75. package/lib/loop/plugins/canvas-blocks/index.js +118 -0
  76. package/lib/loop/plugins/chat/ChatView.js +52 -26
  77. package/lib/loop/plugins/chat/turnState.d.ts +5 -2
  78. package/lib/loop/plugins/chat/turnState.js +2 -1
  79. package/lib/loop/plugins/page-layout/SplitLayout.d.ts +31 -0
  80. package/lib/loop/plugins/page-layout/SplitLayout.js +129 -0
  81. package/lib/loop/plugins/page-layout/index.d.ts +13 -0
  82. package/lib/loop/plugins/page-layout/index.js +10 -1
  83. package/lib/loop/presets.d.ts +12 -0
  84. package/lib/loop/presets.js +5 -3
  85. package/lib/specs/actions.js +9 -0
  86. package/lib/specs/agents/agents.js +20 -4
  87. package/lib/specs/apps.d.ts +10 -1
  88. package/lib/specs/apps.js +1779 -154
  89. package/lib/specs/appspecSchema.js +14 -8
  90. package/lib/specs/models.d.ts +1 -1
  91. package/lib/specs/models.js +8 -8
  92. package/lib/specs/tools.d.ts +1 -0
  93. package/lib/specs/tools.js +19 -0
  94. package/lib/stories/loop/LoopReference.stories.d.ts +21 -0
  95. package/lib/stories/loop/LoopReference.stories.js +31 -0
  96. package/lib/stories/loop/ReferenceScreens.d.ts +53 -0
  97. package/lib/stories/loop/ReferenceScreens.js +256 -0
  98. package/lib/stories/loop/pictures-main.d.ts +1 -0
  99. package/lib/stories/loop/pictures-main.js +64 -0
  100. package/lib/types/agentspecs.d.ts +17 -1
  101. package/lib/types/chat.d.ts +5 -0
  102. package/lib/types/config.d.ts +20 -0
  103. package/package.json +10 -6
  104. package/scripts/check-cloudflare-models.py +2 -2
  105. package/scripts/codegen/generate_agents.py +50 -2
  106. package/scripts/codegen/generate_apps.py +38 -7
  107. package/scripts/codegen/generate_models.py +4 -4
@@ -29,7 +29,7 @@ const EMPTY_ID_SET = new Set();
29
29
  // ---------------------------------------------------------------------------
30
30
  // Component
31
31
  // ---------------------------------------------------------------------------
32
- export function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor = 'bottom', draggable = false, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt = false, promptVariant, mentionableAgents, promptHistory, headerContent, showAgentsMenu = false, showInlineAgentsMenu = true, agents = [], selectedAgentId, onSelectAgent, showTokenUsage, showContextRing = false, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu = true, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading = false, models = [], selectedModel = '', onModelSelect = () => { }, availableTools = [], mcpServers = [], enabledMcpTools = EMPTY_TOOL_MAP, enabledMcpToolCount = 0, onToggleMcpTool = () => { }, onToggleAllMcpServerTools = () => { }, approvedMcpTools = EMPTY_TOOL_MAP, onToggleMcpToolApproval = () => { }, skills = [], skillsLoading = false, enabledSkills = EMPTY_ID_SET, onToggleSkill = () => { }, onToggleAllSkills = () => { }, approvedSkills = EMPTY_ID_SET, onToggleSkillApproval = () => { }, apiBase, authToken, mcpStatusData, }) {
32
+ export function InputPrompt({ input, setInput, isLoading, kernelStatus, connectionConfirmed, placeholder, typingSuggestions, autoFocus, focusTrigger, padding, placement, floatingAnchor = 'bottom', draggable = false, container, footerExtras, leadingPanel, trailingPanel, onSend, onStop, disableInputPrompt = false, promptVariant, mentionableAgents, promptHistory, headerContent, showAgentsMenu = false, showInlineAgentsMenu = true, agents = [], selectedAgentId, onSelectAgent, showTokenUsage, showContextRing = false, agentUsage, showModelSelector, showToolsMenu, showSkillsMenu, showSuggestionsMenu = true, suggestions, codemodeEnabled, onToggleCodemode, isA2AProtocol, hasConfigData, hasSkillsData, configLoading = false, models = [], decisions, selectedModel = '', onModelSelect = () => { }, availableTools = [], mcpServers = [], enabledMcpTools = EMPTY_TOOL_MAP, enabledMcpToolCount = 0, onToggleMcpTool = () => { }, onToggleAllMcpServerTools = () => { }, approvedMcpTools = EMPTY_TOOL_MAP, onToggleMcpToolApproval = () => { }, skills = [], skillsLoading = false, enabledSkills = EMPTY_ID_SET, onToggleSkill = () => { }, onToggleAllSkills = () => { }, approvedSkills = EMPTY_ID_SET, onToggleSkillApproval = () => { }, apiBase, authToken, mcpStatusData, }) {
33
33
  const isKernelBusy = kernelStatus === 'busy';
34
34
  /*
35
35
  * Each menu is offered when it is switched on *and* has something behind it.
@@ -143,7 +143,7 @@ export function InputPrompt({ input, setInput, isLoading, kernelStatus, connecti
143
143
  minHeight: 36,
144
144
  bordered: true,
145
145
  subtle: true,
146
- content: (_jsx(_Fragment, { children: anyOffered || extrasOffered || suggestionsOffered ? (_jsxs(_Fragment, { children: [agentsOffered && (_jsx(AgentsMenu, { agents: agents, selectedAgentId: selectedAgentId, onSelectAgent: onSelectAgent })), toolsOffered && (_jsx(ToolsMenu, { codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, mcpServers: mcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: enabledMcpToolCount, onToggleMcpTool: onToggleMcpTool, onToggleAllMcpServerTools: onToggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: onToggleMcpToolApproval, availableTools: availableTools })), skillsOffered && (_jsx(SkillsMenu, { skills: skills, skillsLoading: skillsLoading, enabledSkills: enabledSkills, onToggleSkill: onToggleSkill, onToggleAllSkills: onToggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: onToggleSkillApproval })), modelsOffered && (_jsx(ModelSelector, { models: models, selectedModel: selectedModel, onModelSelect: onModelSelect, isA2AProtocol: isA2AProtocol })), suggestionsOffered && (_jsx(SuggestionsMenu, { suggestions: suggestionItems, onPick: text => {
146
+ content: (_jsx(_Fragment, { children: anyOffered || extrasOffered || suggestionsOffered ? (_jsxs(_Fragment, { children: [agentsOffered && (_jsx(AgentsMenu, { agents: agents, selectedAgentId: selectedAgentId, onSelectAgent: onSelectAgent })), toolsOffered && (_jsx(ToolsMenu, { codemodeEnabled: codemodeEnabled, onToggleCodemode: onToggleCodemode, mcpServers: mcpServers, enabledMcpTools: enabledMcpTools, enabledMcpToolCount: enabledMcpToolCount, onToggleMcpTool: onToggleMcpTool, onToggleAllMcpServerTools: onToggleAllMcpServerTools, approvedMcpTools: approvedMcpTools, onToggleMcpToolApproval: onToggleMcpToolApproval, availableTools: availableTools })), skillsOffered && (_jsx(SkillsMenu, { skills: skills, skillsLoading: skillsLoading, enabledSkills: enabledSkills, onToggleSkill: onToggleSkill, onToggleAllSkills: onToggleAllSkills, approvedSkills: approvedSkills, onToggleSkillApproval: onToggleSkillApproval })), modelsOffered && (_jsx(ModelSelector, { models: models, decisions: decisions, selectedModel: selectedModel, onModelSelect: onModelSelect, isA2AProtocol: isA2AProtocol })), suggestionsOffered && (_jsx(SuggestionsMenu, { suggestions: suggestionItems, onPick: text => {
147
147
  setInput(text);
148
148
  setPickedFocus(n => n + 1);
149
149
  } })), extrasOffered && (_jsx(Box, { sx: {
@@ -192,7 +192,7 @@ export function InputPromptBase({ variant = 'lexical', placeholder = 'Ask anythi
192
192
  , {
193
193
  // Named so the turn-end refocus can ask whether the caret is already
194
194
  // somewhere inside this prompt before it reaches for it.
195
- ref: containerRef, onFocusCapture: () => {
195
+ ref: containerRef, "data-chat-composer": "", onFocusCapture: () => {
196
196
  if (selfFocused.current) {
197
197
  selfFocused.current = false;
198
198
  return;
@@ -1,6 +1,18 @@
1
1
  import type { ModelConfig } from '../../../types';
2
- export declare function ModelSelector({ models, selectedModel, onModelSelect, isA2AProtocol, }: {
2
+ import type { Decisions } from '../../base/modelChoice';
3
+ /**
4
+ * The typed-decision models, read-only under the models.
5
+ *
6
+ * A decision asks them through `/decisions`; an agent cannot run on one, so
7
+ * no row here selects anything. Each says so, in the runtime's sentence, and
8
+ * why it cannot be used when it cannot — the same rule as the models above.
9
+ */
10
+ export declare function DecisionsGroup({ decisions }: {
11
+ decisions: Decisions;
12
+ }): import("react").JSX.Element;
13
+ export declare function ModelSelector({ models, decisions, selectedModel, onModelSelect, isA2AProtocol, }: {
3
14
  models: ModelConfig[];
15
+ decisions?: Decisions;
4
16
  selectedModel: string;
5
17
  onModelSelect: (modelId: string) => void;
6
18
  isA2AProtocol: boolean;
@@ -1,4 +1,4 @@
1
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  /*
3
3
  * Copyright (c) 2025-2026 Datalayer, Inc.
4
4
  * Distributed under the terms of the Modified BSD License.
@@ -11,7 +11,19 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
11
11
  import { Text, Button, ActionMenu, ActionList, Tooltip } from '@primer/react';
12
12
  import { Box } from '@datalayer/primer-addons';
13
13
  import { AiModelIcon } from '@primer/octicons-react';
14
- export function ModelSelector({ models, selectedModel, onModelSelect, isA2AProtocol, }) {
14
+ /**
15
+ * The typed-decision models, read-only under the models.
16
+ *
17
+ * A decision asks them through `/decisions`; an agent cannot run on one, so
18
+ * no row here selects anything. Each says so, in the runtime's sentence, and
19
+ * why it cannot be used when it cannot — the same rule as the models above.
20
+ */
21
+ export function DecisionsGroup({ decisions }) {
22
+ return (_jsxs(ActionList.Group, { selectionVariant: false, children: [_jsx(ActionList.GroupHeading, { children: "Decisions" }), decisions.models.map(model => (_jsxs(ActionList.Item, { disabled: true, sx: { color: 'fg.muted' }, children: [model.name, _jsx(ActionList.Description, { variant: "block", children: model.isAvailable === false
23
+ ? `${model.unavailableReason ?? 'Not usable here'} · ${decisions.note}`
24
+ : decisions.note })] }, model.id)))] }));
25
+ }
26
+ export function ModelSelector({ models, decisions, selectedModel, onModelSelect, isA2AProtocol, }) {
15
27
  const active = models.find(model => model.id === selectedModel);
16
28
  /*
17
29
  * A count, not a name — the same trigger as tools, skills, agents and
@@ -28,7 +40,7 @@ export function ModelSelector({ models, selectedModel, onModelSelect, isA2AProto
28
40
  alignItems: 'flex-end',
29
41
  }, children: [_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Tooltip, { text: `Model — ${summary}`, direction: "n", children: _jsx(Button, { type: "button", variant: "invisible", size: "small", "aria-label": `Model — ${summary}`, leadingVisual: AiModelIcon, disabled: isA2AProtocol, sx: isA2AProtocol
30
42
  ? { opacity: 0.5, cursor: 'not-allowed' }
31
- : undefined, children: _jsx(Text, { sx: { fontSize: 0 }, children: models.length }) }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "end", children: _jsx(ActionList, { selectionVariant: "single", children: models.map(modelItem => (_jsxs(ActionList.Item, { selected: selectedModel === modelItem.id, onSelect: () => onModelSelect(modelItem.id), disabled: modelItem.isAvailable === false || isA2AProtocol, sx: modelItem.isAvailable === false
32
- ? { color: 'fg.muted' }
33
- : undefined, children: [modelItem.name, modelItem.isAvailable === false && (_jsx(ActionList.Description, { variant: "block", children: modelItem.unavailableReason ?? 'Missing API key' }))] }, modelItem.id))) }) })] }), isA2AProtocol && (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg', mt: 1 }, children: "A2A: Model set by agent config" }))] }));
43
+ : undefined, children: _jsx(Text, { sx: { fontSize: 0 }, children: models.length }) }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "end", children: _jsxs(ActionList, { selectionVariant: "single", children: [models.map(modelItem => (_jsxs(ActionList.Item, { selected: selectedModel === modelItem.id, onSelect: () => onModelSelect(modelItem.id), disabled: modelItem.isAvailable === false || isA2AProtocol, sx: modelItem.isAvailable === false
44
+ ? { color: 'fg.muted' }
45
+ : undefined, children: [modelItem.name, modelItem.isAvailable === false && (_jsx(ActionList.Description, { variant: "block", children: modelItem.unavailableReason ?? 'Missing API key' }))] }, modelItem.id))), decisions && decisions.models.length > 0 && (_jsxs(_Fragment, { children: [_jsx(ActionList.Divider, {}), _jsx(DecisionsGroup, { decisions: decisions })] }))] }) })] }), isA2AProtocol && (_jsx(Text, { sx: { fontSize: 0, color: 'attention.fg', mt: 1 }, children: "A2A: Model set by agent config" }))] }));
34
46
  }
@@ -3,7 +3,15 @@ export type DragPosition = {
3
3
  left: number;
4
4
  top: number;
5
5
  } | null;
6
- export declare function useViewportDrag(boxRef: React.RefObject<HTMLElement | null>): {
6
+ export interface ViewportDragOptions {
7
+ /**
8
+ * Keep the whole box inside the viewport, not only a margin of it: the
9
+ * floating assistant's character, whose balloon and conversation open
10
+ * from where it stands (LOOP T-23).
11
+ */
12
+ whole?: boolean;
13
+ }
14
+ export declare function useViewportDrag(boxRef: React.RefObject<HTMLElement | null>, { whole }?: ViewportDragOptions): {
7
15
  position: DragPosition;
8
16
  onHandlePointerDown: (event: React.PointerEvent<HTMLElement>) => void;
9
17
  reset: () => void;
@@ -17,7 +17,7 @@
17
17
  import { useCallback, useRef, useState } from 'react';
18
18
  /** How much of the box must stay inside the viewport. */
19
19
  const MARGIN = 24;
20
- export function useViewportDrag(boxRef) {
20
+ export function useViewportDrag(boxRef, { whole = false } = {}) {
21
21
  const [position, setPosition] = useState(null);
22
22
  /* Where the pointer took hold, so the box moves by the pointer's delta
23
23
  rather than snapping its corner to the cursor. */
@@ -39,8 +39,8 @@ export function useViewportDrag(boxRef) {
39
39
  if (!grip.current) {
40
40
  return;
41
41
  }
42
- const left = Math.min(Math.max(moved.clientX - grip.current.dx, MARGIN - rect.width), window.innerWidth - MARGIN);
43
- const top = Math.min(Math.max(moved.clientY - grip.current.dy, 0), window.innerHeight - MARGIN);
42
+ const left = Math.min(Math.max(moved.clientX - grip.current.dx, whole ? 0 : MARGIN - rect.width), window.innerWidth - (whole ? rect.width : MARGIN));
43
+ const top = Math.min(Math.max(moved.clientY - grip.current.dy, 0), window.innerHeight - (whole ? rect.height : MARGIN));
44
44
  setPosition({ left, top });
45
45
  };
46
46
  const up = () => {
@@ -53,7 +53,7 @@ export function useViewportDrag(boxRef) {
53
53
  handle.addEventListener('pointerup', up);
54
54
  handle.addEventListener('pointercancel', up);
55
55
  event.preventDefault();
56
- }, [boxRef]);
56
+ }, [boxRef, whole]);
57
57
  /** Back to wherever the box starts: called when the mode changes. */
58
58
  const reset = useCallback(() => setPosition(null), []);
59
59
  return { position, onHandlePointerDown, reset };
@@ -1,3 +1,5 @@
1
1
  export * from './A2uiMarkdownProvider';
2
2
  export * from './A2uiSurfaceComposed';
3
3
  export * from './styles';
4
+ export * from './visibleWhen';
5
+ export * from './visibility';
@@ -5,3 +5,5 @@
5
5
  export * from './A2uiMarkdownProvider';
6
6
  export * from './A2uiSurfaceComposed';
7
7
  export * from './styles';
8
+ export * from './visibleWhen';
9
+ export * from './visibility';
@@ -0,0 +1,15 @@
1
+ /**
2
+ * What `visible_when` is, without the renderer: the key and the rule, for
3
+ * whatever writes or checks a surface (the Studio's Canvas) as well as for
4
+ * what draws it (`visibleWhen`).
5
+ *
6
+ * @module components/a2ui/visibility
7
+ */
8
+ /** The key any block may carry to be shown only under a condition. */
9
+ export declare const VISIBLE_WHEN = "visible_when";
10
+ /**
11
+ * Whether a block whose condition resolved to `value` is shown: `true`, or a
12
+ * value that is not empty. `false`, `''`, `0`, `null`, an empty list, or
13
+ * nothing published at the path yet hide it.
14
+ */
15
+ export declare function isShown(value: unknown): boolean;
@@ -0,0 +1,24 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * What `visible_when` is, without the renderer: the key and the rule, for
7
+ * whatever writes or checks a surface (the Studio's Canvas) as well as for
8
+ * what draws it (`visibleWhen`).
9
+ *
10
+ * @module components/a2ui/visibility
11
+ */
12
+ /** The key any block may carry to be shown only under a condition. */
13
+ export const VISIBLE_WHEN = 'visible_when';
14
+ /**
15
+ * Whether a block whose condition resolved to `value` is shown: `true`, or a
16
+ * value that is not empty. `false`, `''`, `0`, `null`, an empty list, or
17
+ * nothing published at the path yet hide it.
18
+ */
19
+ export function isShown(value) {
20
+ if (Array.isArray(value)) {
21
+ return value.length > 0;
22
+ }
23
+ return Boolean(value);
24
+ }
@@ -0,0 +1,17 @@
1
+ import { type ReactComponentImplementation } from '@a2ui/react/v0_9';
2
+ import { Catalog } from '@a2ui/web_core/v0_9';
3
+ /**
4
+ * Whether a node's resolved properties let it be drawn: no condition, or one
5
+ * that holds. A2UI resolves a dynamic property to a binding whether the block
6
+ * gave it or not; a path's is writable, and its value may be nothing yet,
7
+ * which hides the block. A literal or a function call that resolved to
8
+ * nothing is a block without a condition.
9
+ */
10
+ export declare function nodeIsShown(props: Record<string, unknown>): boolean;
11
+ /** A component, its schema declaring `visible_when`, drawn behind the guard. */
12
+ export declare function withVisibleWhen(component: ReactComponentImplementation): ReactComponentImplementation;
13
+ /**
14
+ * The catalog Datalayer's renderer draws from: A2UI's basic catalog, under
15
+ * its own id — the payloads name it — with `visible_when` on every component.
16
+ */
17
+ export declare const datalayerCatalog: Catalog<ReactComponentImplementation, import("@a2ui/web_core/v0_9").FunctionImplementation>;
@@ -0,0 +1,54 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { basicCatalog, useSignalValue, } from '@a2ui/react/v0_9';
3
+ import { Catalog, DynamicBooleanSchema, ResolvedBinding, isWritable, } from '@a2ui/web_core/v0_9';
4
+ import { VISIBLE_WHEN, isShown } from './visibility';
5
+ /**
6
+ * Whether a node's resolved properties let it be drawn: no condition, or one
7
+ * that holds. A2UI resolves a dynamic property to a binding whether the block
8
+ * gave it or not; a path's is writable, and its value may be nothing yet,
9
+ * which hides the block. A literal or a function call that resolved to
10
+ * nothing is a block without a condition.
11
+ */
12
+ export function nodeIsShown(props) {
13
+ const condition = props[VISIBLE_WHEN];
14
+ if (!(condition instanceof ResolvedBinding)) {
15
+ throw new Error(`${VISIBLE_WHEN} resolved to ${String(condition)}, not a binding: the catalog's schema does not declare it.`);
16
+ }
17
+ if (isWritable(condition)) {
18
+ return isShown(condition.value);
19
+ }
20
+ return condition.value === undefined || isShown(condition.value);
21
+ }
22
+ const VISIBLE_WHEN_SCHEMA = DynamicBooleanSchema.describe('REF:#/$defs/DynamicBoolean|Datalayer: the block is shown only while this is true or not empty — a path the application publishes.').optional();
23
+ /** A component, its schema declaring `visible_when`, drawn behind the guard. */
24
+ export function withVisibleWhen(component) {
25
+ const View = component.view;
26
+ if (!View) {
27
+ throw new Error(`${component.name} has no node view: visible_when is read from the node, and A2uiSurface draws through it.`);
28
+ }
29
+ // A zod object: what every basic component's schema is.
30
+ const schema = component.schema;
31
+ if (!schema.shape || typeof schema.extend !== 'function') {
32
+ throw new Error(`${component.name}'s schema is not an object: visible_when cannot be declared on it.`);
33
+ }
34
+ if (VISIBLE_WHEN in schema.shape) {
35
+ throw new Error(`${component.name} already declares ${VISIBLE_WHEN}.`);
36
+ }
37
+ const Guarded = ({ node, buildChild }) => {
38
+ const props = useSignalValue(node.props);
39
+ return nodeIsShown(props) ? (_jsx(View, { node: node, buildChild: buildChild })) : null;
40
+ };
41
+ Guarded.displayName = `${component.name}.visibleWhen`;
42
+ return {
43
+ ...component,
44
+ schema: schema.extend({ [VISIBLE_WHEN]: VISIBLE_WHEN_SCHEMA }),
45
+ // A2uiSurface draws through the view; `render` is only its fallback for
46
+ // a component without one, which this one is not.
47
+ view: Guarded,
48
+ };
49
+ }
50
+ /**
51
+ * The catalog Datalayer's renderer draws from: A2UI's basic catalog, under
52
+ * its own id — the payloads name it — with `visible_when` on every component.
53
+ */
54
+ export const datalayerCatalog = new Catalog(basicCatalog.id, basicCatalog.protocolVersion, [...basicCatalog.components.values()].map(withVisibleWhen), [...basicCatalog.functions.values()], basicCatalog.themeSchema, basicCatalog.instructions);
@@ -2,6 +2,10 @@
2
2
  * The floating assistant (LOOP T-21 to T-27): the chat as a character on the
3
3
  * page, after the Office Assistant — Datalayer's own characters, chosen
4
4
  * here, acting out what the agent does and speaking in a balloon.
5
+ *
6
+ * The characters offered are what the enabled plugins contribute to
7
+ * `loop.assistant.character` (T-24): Datalayer's four, and an owl from a
8
+ * small example plugin (`utils/owlCharacterPlugin`).
5
9
  */
6
10
  import React from 'react';
7
11
  declare const ChatAssistantExample: React.FC;
@@ -7,14 +7,31 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
7
7
  * The floating assistant (LOOP T-21 to T-27): the chat as a character on the
8
8
  * page, after the Office Assistant — Datalayer's own characters, chosen
9
9
  * here, acting out what the agent does and speaking in a balloon.
10
+ *
11
+ * The characters offered are what the enabled plugins contribute to
12
+ * `loop.assistant.character` (T-24): Datalayer's four, and an owl from a
13
+ * small example plugin (`utils/owlCharacterPlugin`).
10
14
  */
11
15
  import { useState } from 'react';
16
+ import { buildReactorFromPlugins } from '@datalayer/reactor';
12
17
  import { Button, Heading, Text } from '@primer/react';
13
18
  import { Box } from '@datalayer/primer-addons';
14
19
  import { ThemedProvider } from './utils/themedProvider';
15
20
  import { ChatFloating } from '../chat';
16
- import { ASSISTANT_CHARACTERS } from '../chat/assistant/characters';
21
+ import { AssistantCharactersPlugin, assistantCharacterNamed, assistantCharactersOf, } from '../loop/plugins/assistant-characters';
22
+ import { OwlCharacterPlugin } from './utils/owlCharacterPlugin';
17
23
  import { readAcsCharacter, readClippyCharacter, } from '../chat/assistant/formats';
24
+ /** The plugins this page enables: Datalayer's characters and the owl's. */
25
+ const reactor = buildReactorFromPlugins([
26
+ AssistantCharactersPlugin,
27
+ OwlCharacterPlugin,
28
+ ]);
29
+ reactor.start();
30
+ /** The catalogue: what the enabled plugins contribute. */
31
+ const CATALOGUE = assistantCharactersOf(reactor).map(entry => ({
32
+ id: entry.id,
33
+ name: entry.character.name,
34
+ }));
18
35
  /**
19
36
  * A character file a person picked, read in the page (T-26): one `.acs`, or
20
37
  * a clippy.js character's `agent.js` and `map.png` (and its sounds file).
@@ -37,12 +54,16 @@ async function readPicked(files) {
37
54
  });
38
55
  }
39
56
  const ChatAssistantExample = () => {
40
- const [character, setCharacter] = useState(ASSISTANT_CHARACTERS[0].id);
57
+ const [character, setCharacter] = useState('paperclip');
58
+ // A character chosen by id is the one its plugin contributes.
59
+ const drawn = typeof character === 'string'
60
+ ? assistantCharacterNamed(reactor, character)
61
+ : character;
41
62
  const [loadError, setLoadError] = useState();
42
- return (_jsx(ThemedProvider, { children: _jsxs(Box, { sx: { minHeight: '100vh', bg: 'canvas.default', p: 4 }, children: [_jsxs(Box, { sx: { maxWidth: 720, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Floating assistant" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: "The chat as a character on the page. It greets you, acts out what the agent is doing, and says the agent\u2019s words in a balloon while the conversation is closed. Click it to talk, drag it to move it, hover it to send it away." }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: ASSISTANT_CHARACTERS.map(option => (_jsx(Button, { variant: option.id === character ? 'primary' : 'default', onClick: () => {
63
+ return (_jsx(ThemedProvider, { children: _jsxs(Box, { sx: { minHeight: '100vh', bg: 'canvas.default', p: 4 }, children: [_jsxs(Box, { sx: { maxWidth: 720, mx: 'auto' }, children: [_jsx(Heading, { as: "h1", sx: { mb: 2 }, children: "Floating assistant" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', mb: 3 }, children: "The chat as a character on the page. It greets you, acts out what the agent is doing, and says the agent\u2019s words in a balloon while the conversation is closed. Click it to talk, drag it to move it, hover it to send it away." }), _jsx(Box, { sx: { display: 'flex', gap: 2, flexWrap: 'wrap' }, children: CATALOGUE.map(option => (_jsx(Button, { variant: option.id === character ? 'primary' : 'default', onClick: () => {
43
64
  setCharacter(option.id);
44
65
  setLoadError(undefined);
45
- }, "aria-pressed": option.id === character, children: option.name }, option.id))) }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept: ".acs,.js,.png,.gif,.webp,.jpg,.jpeg", "aria-label": "Character files", onChange: async (event) => {
66
+ }, "aria-pressed": option.id === character, "data-assistant-character": option.id, children: option.name }, option.id))) }), _jsxs(Box, { as: "section", sx: { mt: 4 }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 2, mb: 1 }, children: "A character you bring" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mb: 2 }, children: ["The characters of Microsoft Office \u2014 Clippy, Merlin, Links and the others \u2014 are Microsoft\u2019s. Load only a character file you have the right to use: a Microsoft Agent ", _jsx("code", { children: ".acs" }), " file, or a clippy.js character\u2019s ", _jsx("code", { children: "agent.js" }), " and map image. It is read in this page and sent nowhere."] }), _jsx("input", { type: "file", multiple: true, accept: ".acs,.js,.png,.gif,.webp,.jpg,.jpeg", "aria-label": "Character files", onChange: async (event) => {
46
67
  const files = Array.from(event.target.files ?? []);
47
68
  if (!files.length) {
48
69
  return;
@@ -54,6 +75,6 @@ const ChatAssistantExample = () => {
54
75
  catch (error) {
55
76
  setLoadError(error instanceof Error ? error.message : String(error));
56
77
  }
57
- } }), loadError && (_jsx(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mt: 2 }, children: loadError })), typeof character !== 'string' && (_jsxs(Text, { as: "p", sx: { mt: 2 }, children: ["Playing ", character.name, "."] }))] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: character, protocol: "vercel-ai", endpoint: "http://127.0.0.1:8765/api/v1/vercel-ai/assistant", title: "Assistant", description: "Hello! Ask me anything about this page.", position: "bottom-right", useStore: false }, typeof character === 'string' ? character : character.sprite)] }) }));
78
+ } }), loadError && (_jsx(Text, { as: "p", role: "alert", sx: { color: 'danger.fg', mt: 2 }, children: loadError })), typeof character !== 'string' && (_jsxs(Text, { as: "p", sx: { mt: 2 }, children: ["Playing ", character.name, "."] }))] })] }), _jsx(ChatFloating, { defaultViewMode: "assistant", assistantCharacter: drawn, protocol: "vercel-ai", endpoint: "http://127.0.0.1:8765/api/v1/vercel-ai/assistant", title: "Assistant", description: "Hello! Ask me anything about this page.", position: "bottom-right", useStore: false }, typeof character === 'string' ? character : character.sprite)] }) }));
58
79
  };
59
80
  export default ChatAssistantExample;
@@ -0,0 +1,7 @@
1
+ import type { AssistantCharacter } from '../../chat/assistant/characters';
2
+ /** The owl, as the example plugin contributes it. */
3
+ export declare const OWL_CHARACTER: AssistantCharacter;
4
+ export declare const OWL_CHARACTER_PLUGIN_NAME = "@datalayer/example-plugin-owl-character";
5
+ /** The example plugin: one character, contributed to the point. */
6
+ export declare const OwlCharacterPlugin: import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
7
+ export default OwlCharacterPlugin;
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { contribution, definePlugin } from '@datalayer/reactor';
3
+ import { LoopAssistantCharacter } from '../../loop/core';
4
+ const OWL = {
5
+ light: {
6
+ feathers: '#8A5A3C',
7
+ belly: '#E9D3B8',
8
+ tufts: '#6B4329',
9
+ beak: '#E0A54A',
10
+ eye: '#FFFFFF',
11
+ ring: '#1D1D1F',
12
+ pupil: '#1D1D1F',
13
+ },
14
+ dark: {
15
+ feathers: '#C08B66',
16
+ belly: '#F1E1CC',
17
+ tufts: '#D9A57F',
18
+ beak: '#F8D469',
19
+ eye: '#F5F5F7',
20
+ ring: '#1D1D1F',
21
+ pupil: '#131314',
22
+ },
23
+ };
24
+ function Owl({ size, mode = 'light', }) {
25
+ const c = OWL[mode];
26
+ return (_jsx("svg", { width: size, height: size, viewBox: "0 0 96 96", role: "img", "aria-label": "Owl", "data-assistant-mode": mode, children: _jsxs("g", { className: "assistant-body", children: [_jsx("path", { d: "M26 30 L22 10 L40 22 Z", fill: c.tufts }), _jsx("path", { d: "M70 30 L74 10 L56 22 Z", fill: c.tufts }), _jsx("ellipse", { cx: 48, cy: 54, rx: 28, ry: 34, fill: c.feathers }), _jsx("ellipse", { cx: 48, cy: 66, rx: 17, ry: 20, fill: c.belly }), [34, 62].map(x => (_jsx("circle", { cx: x, cy: 40, r: 11, fill: c.eye, stroke: c.ring, strokeWidth: 1.5 }, x))), _jsxs("g", { className: "assistant-pupils", children: [_jsx("circle", { cx: 35, cy: 41, r: 5, fill: c.pupil }), _jsx("circle", { cx: 63, cy: 41, r: 5, fill: c.pupil })] }), _jsxs("g", { className: "assistant-lids", children: [_jsx("ellipse", { cx: 34, cy: 40, rx: 11.5, ry: 11.5, fill: c.feathers }), _jsx("ellipse", { cx: 62, cy: 40, rx: 11.5, ry: 11.5, fill: c.feathers })] }), _jsx("path", { className: "assistant-mouth", d: "M44 50 L52 50 L48 58 Z", fill: c.beak })] }) }));
27
+ }
28
+ /** The owl, as the example plugin contributes it. */
29
+ export const OWL_CHARACTER = {
30
+ id: 'owl',
31
+ name: 'Owl',
32
+ Drawing: Owl,
33
+ };
34
+ export const OWL_CHARACTER_PLUGIN_NAME = '@datalayer/example-plugin-owl-character';
35
+ /** The example plugin: one character, contributed to the point. */
36
+ export const OwlCharacterPlugin = definePlugin({
37
+ name: OWL_CHARACTER_PLUGIN_NAME,
38
+ displayName: 'Owl character (example)',
39
+ description: 'An owl for the floating assistant, contributed by a plugin.',
40
+ contributes: [
41
+ contribution(LoopAssistantCharacter, { id: OWL_CHARACTER.id, character: OWL_CHARACTER }, { id: OWL_CHARACTER.id }),
42
+ ],
43
+ });
44
+ export default OwlCharacterPlugin;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * The feedback strip above an application's prompt (LOOP V-18): once an
3
+ * answer is in, a thumb up or down and a comment, kept in the application's
4
+ * record under the conversation it was about.
5
+ *
6
+ * Read through what the workspace offers, not around it: the chat keeps the
7
+ * current turn — the question, the answer, the conversation's thread — in
8
+ * `LoopChatTurn`, and the composer hangs what is contributed to
9
+ * `LoopPromptPanel` beside the prompt. `AppRenderer` adds the plugin for an
10
+ * application whose record keeps feedback, in the Studio's Preview and on
11
+ * the hosted page alike.
12
+ *
13
+ * @module loop/apps/AppFeedback
14
+ */
15
+ import type { JSX } from 'react';
16
+ import type { AppSpec } from '../../types/agentspecs';
17
+ import { type LoopWorkspaceContext } from '../core';
18
+ export declare const APP_FEEDBACK_PLUGIN_NAME = "@datalayer/loop-plugin-app-feedback";
19
+ export declare function AppFeedback({ app, workspace, }: {
20
+ app: Pick<AppSpec, 'name'>;
21
+ workspace: LoopWorkspaceContext;
22
+ }): JSX.Element | null;
23
+ /**
24
+ * The plugin, for one application: the strip above its prompt. Made once per
25
+ * application — a new plugin rebuilds the workspace.
26
+ */
27
+ export declare function defineAppFeedbackPlugin(app: AppSpec): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, unknown>;
@@ -0,0 +1,78 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Button, IconButton, Text, Textarea } from '@primer/react';
4
+ import { ThumbsdownIcon, ThumbsupIcon } from '@primer/octicons-react';
5
+ import { contribution, definePlugin, signal } from '@datalayer/reactor';
6
+ import { useContributions, useSignalValue } from '@datalayer/reactor/react';
7
+ import { useIAMStore } from '../../state';
8
+ import { LoopChatTurn, LoopPromptPanel, } from '../core';
9
+ import { FEEDBACK_WORDS, sendFeedback } from './feedback';
10
+ export const APP_FEEDBACK_PLUGIN_NAME = '@datalayer/loop-plugin-app-feedback';
11
+ /* A signal to read when no chat contributed a turn: the hook needs one. */
12
+ const NO_TURN = signal({ id: 0, status: 'idle' });
13
+ export function AppFeedback({ app, workspace, }) {
14
+ const turns = useContributions(LoopChatTurn);
15
+ const turn = useSignalValue(turns[0]?.value.turn ?? NO_TURN);
16
+ const token = useIAMStore(state => state.token);
17
+ const [liked, setLiked] = useState(null);
18
+ const [comment, setComment] = useState('');
19
+ const [said, setSaid] = useState(null);
20
+ // Only under an answer that is in, in a conversation the runtime named.
21
+ if (turn.status !== 'done' || !turn.assistant || !turn.thread) {
22
+ return null;
23
+ }
24
+ const thread = turn.thread;
25
+ const choice = liked?.turn === turn.id ? liked.liked : null;
26
+ const done = said?.turn === turn.id ? said : null;
27
+ const agentBaseUrl = workspace.sandbox.agentBaseUrl || workspace.serverUrl || '';
28
+ const send = async () => {
29
+ if (choice === null) {
30
+ return;
31
+ }
32
+ setSaid({ turn: turn.id, state: 'sending', says: '' });
33
+ try {
34
+ await sendFeedback({ session: thread, liked: choice, comment }, { agentBaseUrl, token });
35
+ setSaid({
36
+ turn: turn.id,
37
+ state: 'kept',
38
+ says: FEEDBACK_WORDS.kept(app.name),
39
+ });
40
+ setComment('');
41
+ }
42
+ catch (error) {
43
+ setSaid({
44
+ turn: turn.id,
45
+ state: 'refused',
46
+ says: error instanceof Error ? error.message : String(error),
47
+ });
48
+ }
49
+ };
50
+ if (done?.state === 'kept') {
51
+ return (_jsx("div", { style: { padding: '4px 8px' }, children: _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: done.says }) }));
52
+ }
53
+ return (_jsxs("div", { role: "group", "aria-label": FEEDBACK_WORDS.ask, style: {
54
+ display: 'flex',
55
+ flexDirection: 'column',
56
+ gap: 4,
57
+ padding: '4px 8px',
58
+ }, children: [_jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 4 }, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: FEEDBACK_WORDS.ask }), _jsx(IconButton, { size: "small", variant: choice === true ? 'primary' : 'invisible', icon: ThumbsupIcon, "aria-label": FEEDBACK_WORDS.liked, "aria-pressed": choice === true, onClick: () => setLiked({ turn: turn.id, liked: true }) }), _jsx(IconButton, { size: "small", variant: choice === false ? 'danger' : 'invisible', icon: ThumbsdownIcon, "aria-label": FEEDBACK_WORDS.disliked, "aria-pressed": choice === false, onClick: () => setLiked({ turn: turn.id, liked: false }) })] }), choice !== null ? (_jsxs(_Fragment, { children: [_jsx(Textarea, { "aria-label": FEEDBACK_WORDS.comment, placeholder: FEEDBACK_WORDS.comment, value: comment, rows: 2, resize: "vertical", block: true, onChange: event => setComment(event.target.value) }), _jsxs("div", { style: { display: 'flex', alignItems: 'center', gap: 8 }, children: [_jsx(Button, { size: "small", variant: "primary", disabled: done?.state === 'sending', onClick: () => void send(), children: FEEDBACK_WORDS.send }), _jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: FEEDBACK_WORDS.where(app.name) })] })] })) : null, done?.state === 'refused' ? (_jsx(Text, { sx: { fontSize: 0, color: 'danger.fg' }, children: done.says })) : null] }));
59
+ }
60
+ /**
61
+ * The plugin, for one application: the strip above its prompt. Made once per
62
+ * application — a new plugin rebuilds the workspace.
63
+ */
64
+ export function defineAppFeedbackPlugin(app) {
65
+ function Panel({ workspace, }) {
66
+ return _jsx(AppFeedback, { app: app, workspace: workspace });
67
+ }
68
+ return definePlugin({
69
+ name: `${APP_FEEDBACK_PLUGIN_NAME}-${app.id}`,
70
+ displayName: `${app.name}: feedback`,
71
+ description: `A thumb and a comment on each answer of ${app.name}, kept in its record.`,
72
+ octicon: 'thumbsup',
73
+ emoji: '\u{1F44D}',
74
+ contributes: [
75
+ contribution(LoopPromptPanel, { id: 'app-feedback', placement: 'above', order: 20, Component: Panel }, { id: 'app-feedback' }),
76
+ ],
77
+ });
78
+ }
@@ -1,6 +1,7 @@
1
1
  import type { ReactorPlugin } from '@datalayer/reactor';
2
2
  import type { AppSpec } from '../../types/agentspecs';
3
3
  import { type LoopEmbedProps } from '../embed/LoopEmbed';
4
+ import type { LoopPresetOptions } from '../presets';
4
5
  import type { PresenceState } from '../../chat/presence/presenceStatus';
5
6
  /** The id of an agent or a Cog, without its version. */
6
7
  export declare const agentIdOf: (app: Pick<AppSpec, "agent" | "team">) => string;
@@ -37,7 +38,38 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
37
38
  * stable function, such as a state setter.
38
39
  */
39
40
  onPresence?: (state: PresenceState) => void;
41
+ /**
42
+ * Draw the application in a window (the `window-frame` plugin), its title
43
+ * the application's face and name. A host's own `frameTitle` wins.
44
+ */
45
+ frame?: boolean;
40
46
  };
47
+ /** What `interface.layout` sets on the workspace. */
48
+ export type AppLayoutOptions = Pick<LoopPresetOptions, 'editors' | 'showViewSelector' | 'defaultEditor' | 'pageLayout' | 'pageLayoutArrangement' | 'pageLayoutPrompt' | 'pageLayoutPromptAnchor' | 'pageLayoutTurnPanelFooter'>;
49
+ /**
50
+ * The workspace an application's layout asks for (LOOP T-07).
51
+ *
52
+ * - `chat`: the conversation alone — no editor, no page, no strip.
53
+ * - `page`: the page layout — its page (or, without one, the editors) on the
54
+ * sheet; the composer a card over it, starting at the bottom, whose display
55
+ * modes put it in the conversation panel beside, over or in the corner of
56
+ * the page; the current turn under the composer, its copy and dismiss only.
57
+ * - `split`: the page layout's split — the conversation on the left with its
58
+ * composer, the page on the right, a hairline between them to drag.
59
+ *
60
+ * With a page, the page is the one editor, opened, without a strip to swap
61
+ * it; without one (a decision, whose page is the Studio's), the editors stay.
62
+ */
63
+ export declare function appLayoutOptions(app: Pick<AppSpec, 'kind' | 'interface' | 'agent'>): AppLayoutOptions;
64
+ /**
65
+ * What an application's agent is created with on a Datalayer runtime: under
66
+ * the application's id, over AG-UI, with the application's own document —
67
+ * so that the runtime registers it and decides every tool call by its rules
68
+ * (LOOP R-03, R-05), as `loop apps run --cloud` does. One payload for every
69
+ * place it runs: the workspace (`AppRenderer`) and the embed's floating
70
+ * modes (`AppEmbed`).
71
+ */
72
+ export declare function appDatalayerCreatePayload(app: AppSpec, instance?: AppInstance): Record<string, unknown>;
41
73
  /**
42
74
  * An application in a LOOP workspace: its agent, its starters, its layout.
43
75
  *
@@ -47,5 +79,5 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
47
79
  /** No host plugins: one array, so that it never reads as a change. */
48
80
  /** The agentspec a runtime is allocated with before an application's agent is created on it. */
49
81
  export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
50
- export declare function AppRenderer({ app, plugins, instance, onPresence, ...embed }: AppRendererProps): React.JSX.Element;
82
+ export declare function AppRenderer({ app, plugins, instance, onPresence, frame, ...embed }: AppRendererProps): React.JSX.Element;
51
83
  export default AppRenderer;