@datalayer/agent-runtimes 1.3.34 → 1.3.36

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 (55) hide show
  1. package/lib/chat/ChatFloating.d.ts +8 -2
  2. package/lib/chat/ChatFloating.js +2 -2
  3. package/lib/chat/base/ChatBase.js +10 -6
  4. package/lib/chat/display/EmptyState.js +8 -1
  5. package/lib/chat/presence/Presence.d.ts +15 -1
  6. package/lib/chat/presence/Presence.js +0 -13
  7. package/lib/components/a2ui/datalayer/Chart.d.ts +132 -0
  8. package/lib/components/a2ui/datalayer/Chart.js +173 -0
  9. package/lib/components/a2ui/datalayer/Chat.d.ts +21 -0
  10. package/lib/components/a2ui/datalayer/Chat.js +116 -0
  11. package/lib/components/a2ui/datalayer/Evidence.d.ts +22 -0
  12. package/lib/components/a2ui/datalayer/Evidence.js +73 -0
  13. package/lib/components/a2ui/datalayer/FileUpload.d.ts +22 -0
  14. package/lib/components/a2ui/datalayer/FileUpload.js +107 -0
  15. package/lib/components/a2ui/datalayer/Form.d.ts +12 -0
  16. package/lib/components/a2ui/datalayer/Form.js +46 -0
  17. package/lib/components/a2ui/datalayer/Table.d.ts +20 -0
  18. package/lib/components/a2ui/datalayer/Table.js +103 -0
  19. package/lib/components/a2ui/datalayer/drawn.d.ts +10 -0
  20. package/lib/components/a2ui/datalayer/drawn.js +20 -0
  21. package/lib/components/a2ui/datalayer/implementation.d.ts +17 -0
  22. package/lib/components/a2ui/datalayer/implementation.js +10 -0
  23. package/lib/components/a2ui/datalayer/index.d.ts +23 -0
  24. package/lib/components/a2ui/datalayer/index.js +44 -0
  25. package/lib/components/a2ui/datalayer/parts.d.ts +32 -0
  26. package/lib/components/a2ui/datalayer/parts.js +44 -0
  27. package/lib/components/a2ui/datalayer/schema.d.ts +34 -0
  28. package/lib/components/a2ui/datalayer/schema.js +108 -0
  29. package/lib/components/a2ui/index.d.ts +1 -0
  30. package/lib/components/a2ui/index.js +1 -0
  31. package/lib/components/a2ui/visibleWhen.d.ts +4 -2
  32. package/lib/components/a2ui/visibleWhen.js +6 -3
  33. package/lib/loop/apps/AppRenderer.d.ts +8 -0
  34. package/lib/loop/apps/AppRenderer.js +21 -1
  35. package/lib/loop/apps/appspec.d.ts +9 -2
  36. package/lib/loop/apps/appspec.js +14 -8
  37. package/lib/loop/apps/checks.js +4 -3
  38. package/lib/loop/embed/AppEmbed.d.ts +16 -1
  39. package/lib/loop/embed/AppEmbed.js +38 -9
  40. package/lib/loop/embed/LoopEmbed.js +2 -0
  41. package/lib/loop/embed/element.js +10 -1
  42. package/lib/loop/embed/embedTheme.d.ts +12 -0
  43. package/lib/loop/embed/embedTheme.js +15 -0
  44. package/lib/loop/plugins/assistant/index.d.ts +38 -0
  45. package/lib/loop/plugins/assistant/index.js +140 -0
  46. package/lib/loop/plugins/assistant-characters/index.d.ts +35 -1
  47. package/lib/loop/plugins/assistant-characters/index.js +45 -2
  48. package/lib/loop/plugins/chat/ChatView.js +21 -9
  49. package/lib/loop/plugins/chat/index.d.ts +6 -0
  50. package/lib/loop/presets.d.ts +3 -0
  51. package/lib/loop/presets.js +2 -1
  52. package/lib/specs/appspecSchema.js +5 -8
  53. package/lib/types/agentspecs.d.ts +10 -4
  54. package/lib/types/chat.d.ts +11 -0
  55. package/package.json +2 -1
@@ -0,0 +1,140 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { definePlugin, signal } from '@datalayer/reactor';
4
+ import { useContributions, useSignalValue } from '@datalayer/reactor/react';
5
+ import { Text } from '@primer/react';
6
+ import { Box } from '@datalayer/primer-addons';
7
+ import { pageLayoutPanelOpen, togglePagePanel, } from '@datalayer/primer-addons/lib/reactor';
8
+ import { AssistantStage } from '../../../chat/assistant/AssistantStage';
9
+ import { assistantStateOf, keepAway, keptAway, latestSaying, } from '../../../chat/assistant/state';
10
+ import { useViewportDrag } from '../../../chat/useViewportDrag';
11
+ import { LoopAssistantCharacter, LoopChatLayout, LoopChatTurn, LoopSlots, } from '../../core';
12
+ import { assistantCharacterFor } from '../assistant-characters';
13
+ export const LOOP_ASSISTANT_PLUGIN_NAME = '@datalayer/loop-plugin-assistant';
14
+ /** The character's size, in pixels, as the chat's own assistant. */
15
+ const SIZE = 88;
16
+ /* A signal to read when no chat contributed a turn: the hook needs one. */
17
+ const NO_TURN = signal({ id: 0, status: 'idle' });
18
+ /**
19
+ * What the character acts out, from the workspace's chat turn (T-22): it
20
+ * thinks while the agent thinks, works while a tool runs (the turn's
21
+ * `activity`), speaks while the answer is written, and idles otherwise.
22
+ */
23
+ export function assistantStateOfTurn(turn, moment = {}) {
24
+ const busy = turn.status === 'thinking' || turn.status === 'streaming';
25
+ const presence = !busy
26
+ ? 'idle'
27
+ : turn.activity
28
+ ? 'working'
29
+ : 'thinking';
30
+ return assistantStateOf(presence, {
31
+ ...moment,
32
+ speaking: turn.status === 'streaming' && !turn.activity,
33
+ });
34
+ }
35
+ /** Take the person to the workspace's composer. */
36
+ function focusComposer() {
37
+ const composer = document.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"]');
38
+ composer?.focus();
39
+ }
40
+ export function LoopAssistant({ app, person, }) {
41
+ const contributed = useContributions(LoopAssistantCharacter).map(entry => entry.value);
42
+ const chosen = assistantCharacterFor(contributed, { app, person });
43
+ const turnEntries = useContributions(LoopChatTurn);
44
+ const turn = useSignalValue(turnEntries[0]?.value.turn ?? NO_TURN);
45
+ // On a page layout the conversation is its side panel; elsewhere it is
46
+ // always on screen.
47
+ const onPageLayout = useContributions(LoopChatLayout).some(entry => entry.value.id === 'page-layout');
48
+ const panelOpen = useSignalValue(pageLayoutPanelOpen);
49
+ const open = onPageLayout && panelOpen;
50
+ const [away, setAway] = useState(keptAway);
51
+ const [arriving, setArriving] = useState(true);
52
+ const [leaving, setLeaving] = useState(false);
53
+ useEffect(() => {
54
+ const timer = setTimeout(() => setArriving(false), 1800);
55
+ return () => clearTimeout(timer);
56
+ }, []);
57
+ /* The agent's newest words, said in the balloon until heard (T-23). */
58
+ const saying = turn.assistant
59
+ ? latestSaying([
60
+ { id: String(turn.id), role: 'assistant', content: turn.assistant },
61
+ ])
62
+ : undefined;
63
+ const [heardId, setHeardId] = useState();
64
+ const [fresh, setFresh] = useState(false);
65
+ useEffect(() => {
66
+ if (!saying || open) {
67
+ return;
68
+ }
69
+ setFresh(true);
70
+ const timer = setTimeout(() => setFresh(false), 12000);
71
+ return () => clearTimeout(timer);
72
+ }, [saying?.id, saying?.text, open]);
73
+ useEffect(() => {
74
+ if (open && saying) {
75
+ setHeardId(saying.id);
76
+ }
77
+ }, [open, saying?.id]);
78
+ const stageRef = useRef(null);
79
+ const drag = useViewportDrag(stageRef, { whole: true });
80
+ if (away !== 'none') {
81
+ return null;
82
+ }
83
+ if ('problem' in chosen) {
84
+ // Not replaced by another: what was named is said, where it would stand.
85
+ return (_jsx(Box, { role: "status", "data-assistant-problem": chosen.saidBy, sx: {
86
+ position: 'fixed',
87
+ right: 24,
88
+ bottom: 24,
89
+ zIndex: 1000,
90
+ maxWidth: 320,
91
+ p: 2,
92
+ borderRadius: 2,
93
+ border: '1px solid',
94
+ borderColor: 'attention.muted',
95
+ bg: 'attention.subtle',
96
+ boxShadow: 'shadow.medium',
97
+ }, children: _jsx(Text, { sx: { fontSize: 1 }, children: chosen.problem }) }));
98
+ }
99
+ const state = assistantStateOfTurn(turn, { arriving, leaving });
100
+ const unheard = !!saying && saying.id !== heardId;
101
+ const balloon = unheard && saying
102
+ ? { text: saying.text, more: saying.more }
103
+ : { text: 'Click me to open the conversation.' };
104
+ const insist = state === 'greeting' || (unheard && (state === 'speaking' || fresh));
105
+ const onToggle = () => {
106
+ if (onPageLayout) {
107
+ togglePagePanel();
108
+ }
109
+ else {
110
+ focusComposer();
111
+ }
112
+ };
113
+ const onDismiss = (next) => {
114
+ keepAway(next);
115
+ setLeaving(true);
116
+ setTimeout(() => {
117
+ setLeaving(false);
118
+ setAway(next);
119
+ }, 600);
120
+ };
121
+ return (_jsx(AssistantStage, { character: chosen.character, state: state, size: SIZE, place: drag.position
122
+ ? { left: `${drag.position.left}px`, top: `${drag.position.top}px` }
123
+ : { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, insist: insist, onDismiss: onDismiss }));
124
+ }
125
+ export const LoopAssistantPlugin = definePlugin({
126
+ name: LOOP_ASSISTANT_PLUGIN_NAME,
127
+ config: { app: undefined, person: undefined },
128
+ displayName: 'Floating assistant',
129
+ description: 'The chat as a character on the page: the application’s own, or the one the person chose, from the characters the enabled plugins contribute.',
130
+ octicon: 'paperclip',
131
+ build: ({ config }) => {
132
+ const Configured = () => (_jsx(LoopAssistant, { app: config.app, person: config.person }));
133
+ return {
134
+ components: [
135
+ { id: 'loop-assistant', slot: LoopSlots.root, Component: Configured },
136
+ ],
137
+ };
138
+ },
139
+ });
140
+ export default LoopAssistantPlugin;
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * @module loop/plugins/assistant-characters
7
7
  */
8
- import { type ReactorPlugin } from '@datalayer/reactor';
8
+ import { type PluginRef, type ReactorPlugin } from '@datalayer/reactor';
9
9
  import { type AssistantCharacter } from '../../../chat/assistant/characters';
10
10
  import type { AssistantCharacterData } from '../../../chat/assistant/formats/types';
11
11
  import { LoopAssistantCharacter, type AssistantCharacterContribution } from '../../core';
@@ -22,9 +22,43 @@ export declare function assistantCharactersOf(reactor: ContributionsReader): {
22
22
  id: string;
23
23
  character: AssistantCharacter | AssistantCharacterData;
24
24
  }[];
25
+ /**
26
+ * What these plugins contribute, enabled on their own: for a host with no
27
+ * workspace to read them from — the embed's floating assistant (D-07) —
28
+ * which enables Datalayer's characters and whatever plugins it is given.
29
+ */
30
+ export declare function assistantCharactersFrom(plugins: PluginRef[]): {
31
+ id: string;
32
+ character: AssistantCharacter | AssistantCharacterData;
33
+ }[];
25
34
  /**
26
35
  * The character an application names, from what is contributed; an id no
27
36
  * enabled plugin contributes is an error that says which ids there are.
28
37
  */
29
38
  export declare function assistantCharacterNamed(reactor: ContributionsReader, id: string): AssistantCharacter | AssistantCharacterData;
39
+ /** Whose choice the character drawn is (T-24). */
40
+ export type AssistantCharacterSaidBy = 'app' | 'person' | 'default';
41
+ /** The character drawn, or why none can be. */
42
+ export type AssistantCharacterChosen = {
43
+ id: string;
44
+ character: AssistantCharacter | AssistantCharacterData;
45
+ saidBy: AssistantCharacterSaidBy;
46
+ } | {
47
+ problem: string;
48
+ saidBy: AssistantCharacterSaidBy;
49
+ };
50
+ /**
51
+ * The character the floating assistant draws on a page (T-24): the one the
52
+ * application's Appspec names (`interface.assistant`), which wins; else the
53
+ * one the person chose in their settings; else the paper clip — each looked
54
+ * up in what the enabled plugins contribute. An id no enabled plugin gives
55
+ * is not replaced by another: it is said, with the ids there are.
56
+ */
57
+ export declare function assistantCharacterFor(contributed: ReadonlyArray<{
58
+ id: string;
59
+ character: AssistantCharacter | AssistantCharacterData;
60
+ }>, choice: {
61
+ app?: string;
62
+ person?: string;
63
+ }): AssistantCharacterChosen;
30
64
  export default AssistantCharactersPlugin;
@@ -9,8 +9,8 @@
9
9
  *
10
10
  * @module loop/plugins/assistant-characters
11
11
  */
12
- import { contribution, definePlugin, } from '@datalayer/reactor';
13
- import { ASSISTANT_CHARACTERS, } from '../../../chat/assistant/characters';
12
+ import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
13
+ import { ASSISTANT_CHARACTERS, DEFAULT_ASSISTANT_CHARACTER, } from '../../../chat/assistant/characters';
14
14
  import { LoopAssistantCharacter, } from '../../core';
15
15
  export const ASSISTANT_CHARACTERS_PLUGIN_NAME = '@datalayer/loop-plugin-assistant-characters';
16
16
  export const AssistantCharactersPlugin = definePlugin({
@@ -26,6 +26,21 @@ export function assistantCharactersOf(reactor) {
26
26
  .getContributions(LoopAssistantCharacter)
27
27
  .map(entry => entry.value);
28
28
  }
29
+ /**
30
+ * What these plugins contribute, enabled on their own: for a host with no
31
+ * workspace to read them from — the embed's floating assistant (D-07) —
32
+ * which enables Datalayer's characters and whatever plugins it is given.
33
+ */
34
+ export function assistantCharactersFrom(plugins) {
35
+ const reactor = buildReactorFromPlugins(plugins);
36
+ reactor.start();
37
+ try {
38
+ return assistantCharactersOf(reactor);
39
+ }
40
+ finally {
41
+ reactor.stop();
42
+ }
43
+ }
29
44
  /**
30
45
  * The character an application names, from what is contributed; an id no
31
46
  * enabled plugin contributes is an error that says which ids there are.
@@ -38,4 +53,32 @@ export function assistantCharacterNamed(reactor, id) {
38
53
  }
39
54
  return found.character;
40
55
  }
56
+ /**
57
+ * The character the floating assistant draws on a page (T-24): the one the
58
+ * application's Appspec names (`interface.assistant`), which wins; else the
59
+ * one the person chose in their settings; else the paper clip — each looked
60
+ * up in what the enabled plugins contribute. An id no enabled plugin gives
61
+ * is not replaced by another: it is said, with the ids there are.
62
+ */
63
+ export function assistantCharacterFor(contributed, choice) {
64
+ const saidBy = choice.app
65
+ ? 'app'
66
+ : choice.person
67
+ ? 'person'
68
+ : 'default';
69
+ const id = choice.app ?? choice.person ?? DEFAULT_ASSISTANT_CHARACTER;
70
+ const found = contributed.find(entry => entry.id === id);
71
+ if (found) {
72
+ return { id, character: found.character, saidBy };
73
+ }
74
+ const whose = saidBy === 'app'
75
+ ? 'This application names'
76
+ : saidBy === 'person'
77
+ ? 'Your settings name'
78
+ : 'The default is';
79
+ return {
80
+ saidBy,
81
+ problem: `${whose} the character “${id}”, which nothing enabled here draws; the characters are ${contributed.map(entry => entry.id).join(', ') || 'none'}.`,
82
+ };
83
+ }
41
84
  export default AssistantCharactersPlugin;
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box, IconButton, SegmentedControl, Text, Truncate, } from '@primer/react';
4
- import { useColorPalette } from '@datalayer/primer-addons';
4
+ import { loopShapeVars, useColorPalette } from '@datalayer/primer-addons';
5
5
  import { ScreenFullIcon, ScreenNormalIcon } from '@primer/octicons-react';
6
6
  import { computed, signal } from '@datalayer/reactor';
7
7
  /* A signal to read when the layout publishes no live stance: the hook needs one. */
@@ -54,6 +54,11 @@ const NO_SURFACE = '';
54
54
  * small control reads as an error state, and this one is an invitation.
55
55
  */
56
56
  const FULLSCREEN_HINT_PERIOD_MS = 1400;
57
+ /**
58
+ * An application's face in the empty state: the page's size of the three a
59
+ * face is drawn at (LOOP T-19, `--loop-face-large`, 72px).
60
+ */
61
+ const FACE_LARGE = parseInt(loopShapeVars['--loop-face-large'], 10);
57
62
  export default function ChatView({ workspace }) {
58
63
  /* The active theme's own colours. Read from the store rather than a
59
64
  provider, so a workspace mounted without the addons theme still gets the
@@ -1326,7 +1331,14 @@ export default function ChatView({ workspace }) {
1326
1331
  mentionableAgents: mentionable,
1327
1332
  headerContent: inpromptMenu.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.inpromptMenu, props: { workspace } })) : undefined,
1328
1333
  footerExtras: promptActions.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.promptAction, props: { workspace } })) : undefined,
1329
- showAgentsMenu: true,
1334
+ /*
1335
+ An application's conversation keeps none of the session's controls
1336
+ under its composer — the agents, the model, the tools and their servers,
1337
+ the skills (LOOP T-17, T-18): a person using an application is not
1338
+ choosing them, and its builder chose them in its spec. An agent's chat
1339
+ keeps them.
1340
+ */
1341
+ showAgentsMenu: !presence,
1330
1342
  showInlineAgentsMenu: false,
1331
1343
  agents: footerAgents,
1332
1344
  selectedAgentId: selectedMemberId,
@@ -1336,18 +1348,18 @@ export default function ChatView({ workspace }) {
1336
1348
  showTokenUsage: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true,
1337
1349
  showContextRing: true,
1338
1350
  agentUsage: contextUsage ?? undefined,
1339
- showModelSelector: true,
1351
+ showModelSelector: !presence,
1340
1352
  models: offeredModels,
1341
1353
  decisions,
1342
1354
  selectedModel: activeModel,
1343
1355
  onModelSelect: model => void selectModel(model),
1344
- showToolsMenu: true,
1356
+ showToolsMenu: !presence,
1345
1357
  availableTools: offeredTools,
1346
1358
  mcpServers: configQuery.data?.mcpServers ?? [],
1347
1359
  // Live from the host example, when one feeds it: the footer's MCP status
1348
1360
  // indicator. (Codemode surfaces through the toggle below, not a blob.)
1349
1361
  mcpStatusData: chatExtras.mcpStatusData ?? undefined,
1350
- showSkillsMenu: true,
1362
+ showSkillsMenu: !presence,
1351
1363
  skills: skillsQuery.data?.skills ?? [],
1352
1364
  skillsLoading: skillsQuery.isLoading,
1353
1365
  enabledSkills,
@@ -1444,7 +1456,7 @@ export default function ChatView({ workspace }) {
1444
1456
  // The theme it wears, when the host names one (an application's).
1445
1457
  , {
1446
1458
  // The theme it wears, when the host names one (an application's).
1447
- themeVariant: config?.themeVariant,
1459
+ themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides,
1448
1460
  // The header says why, beside the title, for the same reason the
1449
1461
  // placeholder does: a dead control with no explanation is worse
1450
1462
  // than an absent one.
@@ -1483,9 +1495,9 @@ export default function ChatView({ workspace }) {
1483
1495
  // An application says what it is doing, in plain words, beside
1484
1496
  // its name (T-08); an agent's chat keeps its header as it is.
1485
1497
  headerContent: presence ? _jsx(PresenceLine, { state: presenceNow }) : undefined, emptyState: {
1486
- // An application's own face, large: the thing a person's eye
1487
- // lands on first (LOOP T-08).
1488
- icon: presence?.face ? (_jsx("span", { "aria-hidden": true, style: { fontSize: 48, lineHeight: 1 }, children: presence.face })) : (_jsx(BrandIcon, { size: 48 })),
1498
+ // An application's own face, at the page's size of the three
1499
+ // (LOOP T-08, T-19): the thing a person's eye lands on first.
1500
+ icon: presence?.face ? (_jsx("span", { "aria-hidden": true, style: { fontSize: FACE_LARGE, lineHeight: 1 }, children: presence.face })) : (_jsx(BrandIcon, { size: 48 })),
1489
1501
  // `ChatEmptyState` reads its heading from here and nowhere
1490
1502
  // else — the `title` above reaches the header only — so
1491
1503
  // without this the agent introduced itself as "Start a
@@ -1,4 +1,5 @@
1
1
  import type { PresenceState } from '../../../chat/presence/presenceStatus';
2
+ import type { ThemeOverrides } from '../../../types/chat';
2
3
  /** What a host may set on the chat. */
3
4
  /** Who is answering, as the person meets them. */
4
5
  export interface ChatPresence {
@@ -22,6 +23,11 @@ export type ChatPluginConfig = {
22
23
  * the theme's bubbles, pills and colours whatever the page around it wears.
23
24
  */
24
25
  themeVariant?: string;
26
+ /**
27
+ * Laid over that theme, by mode: an application's accent (LOOP T-05), so
28
+ * that its bubbles and its one button wear its colour and not the theme's.
29
+ */
30
+ themeOverrides?: ThemeOverrides;
25
31
  /**
26
32
  * Who is answering, as the person meets them (LOOP T-08): a name, a face
27
33
  * and a welcome, in place of the agent's id and a generic icon. An
@@ -23,6 +23,7 @@ import { type PluginRef } from '@datalayer/reactor';
23
23
  import { type AgentsConfig, type SandboxTarget } from './plugins/agents';
24
24
  import { type ChatPluginConfig, type ChatPresence } from './plugins/chat';
25
25
  import { type PageSize } from './plugins/page-layout';
26
+ import type { ThemeOverrides } from '../types/chat';
26
27
  export type LoopPresetOptions = {
27
28
  /** Where the agent runtimes service is. */
28
29
  serverUrl?: string;
@@ -140,6 +141,8 @@ export type LoopPresetOptions = {
140
141
  targetFixed?: boolean;
141
142
  /** The theme the conversation wears, by name; the person's unless said. */
142
143
  themeVariant?: string;
144
+ /** Laid over that theme, by mode: an application's accent (LOOP T-05). */
145
+ themeOverrides?: ThemeOverrides;
143
146
  /** Who is answering, as the person meets them (LOOP T-08). */
144
147
  presence?: ChatPresence;
145
148
  /** Whether the counters are shown under the prompt; on unless said. */
@@ -52,7 +52,7 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
52
52
  * be installed or signed into, and the switches a demonstration wants are off.
53
53
  */
54
54
  export function loopPlugins(options = {}) {
55
- const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, targetFixed, themeVariant, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
55
+ const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, targetFixed, themeVariant, themeOverrides, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
56
56
  const defaultEditor = askedEditor ?? (editors ? 'notebook' : 'none');
57
57
  return [
58
58
  // The chat owns the editor beside it, so which one opens is its
@@ -70,6 +70,7 @@ export function loopPlugins(options = {}) {
70
70
  suggestionLabels,
71
71
  fullScreenTopOffset,
72
72
  themeVariant,
73
+ themeOverrides,
73
74
  presence,
74
75
  showTokenUsage,
75
76
  }),
@@ -314,14 +314,17 @@ export const APPSPEC_SCHEMA = {
314
314
  assistant: {
315
315
  anyOf: [
316
316
  {
317
- $ref: '#/$defs/AssistantCharacter',
317
+ maxLength: 64,
318
+ pattern: '^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$',
319
+ type: 'string',
318
320
  },
319
321
  {
320
322
  type: 'null',
321
323
  },
322
324
  ],
323
325
  default: null,
324
- description: 'The character its floating assistant shows: `paperclip`, `wizard`, `cat` or `eyes`. The paper clip when unsaid; a person may choose another in their settings',
326
+ description: "The character its floating assistant shows, by the id a plugin contributes it under (lowercase letters and digits, words joined by a hyphen): Datalayer's are `paperclip`, `wizard`, `cat` and `eyes`. The paper clip when unsaid; an id no enabled plugin contributes is refused where the plugins are known, the runtime and the page. Said here, it wins over a person's own choice in their settings",
327
+ title: 'Assistant',
325
328
  },
326
329
  },
327
330
  title: 'AppInterface',
@@ -677,12 +680,6 @@ export const APPSPEC_SCHEMA = {
677
680
  title: 'AppTrigger',
678
681
  type: 'object',
679
682
  },
680
- AssistantCharacter: {
681
- description: "The character an application's floating assistant shows (LOOP T-24):\none of those a UI plugin contributes. Datalayer's are these four.",
682
- enum: ['paperclip', 'wizard', 'cat', 'eyes'],
683
- title: 'AssistantCharacter',
684
- type: 'string',
685
- },
686
683
  Behaviour: {
687
684
  description: 'What an application does when it meets an action: the four a person chooses from.',
688
685
  enum: ['do_it', 'if_asked', 'ask_first', 'leave_to_me'],
@@ -384,10 +384,13 @@ export type AppLayout = 'chat' | 'page' | 'split';
384
384
  /** The one colour of an application; everything else is neutral. */
385
385
  export type AppAccent = 'green' | 'rose' | 'sky' | 'lime' | 'sun' | 'violet';
386
386
  /**
387
- * The character an application's floating assistant shows (LOOP T-24): one of
388
- * those Datalayer's plugin contributes.
387
+ * The character an application's floating assistant shows (LOOP T-24): the id
388
+ * an enabled plugin contributes it under to `loop.assistant.character` —
389
+ * Datalayer's are `paperclip`, `wizard`, `cat` and `eyes`. The spec checks
390
+ * its shape (`APP_ASSISTANT_CHARACTER_ID`); whether a plugin gives it is
391
+ * known only where the plugins are.
389
392
  */
390
- export type AppAssistantCharacter = 'paperclip' | 'wizard' | 'cat' | 'eyes';
393
+ export type AppAssistantCharacter = string;
391
394
  /** How an application sits in another product's page (LOOP D-07). */
392
395
  export type AppEmbedMode = 'inline' | 'bubble' | 'panel' | 'assistant';
393
396
  /** Something an application reaches. */
@@ -456,7 +459,10 @@ export interface AppInterfaceSpec {
456
459
  /** The components of the catalog the surface may use. */
457
460
  components: string[];
458
461
  surface?: AppSurfaceSpec;
459
- /** The character its floating assistant shows; the paper clip when unsaid. */
462
+ /**
463
+ * The character its floating assistant shows; the paper clip when unsaid.
464
+ * Said, it wins over the one the person chose in their settings.
465
+ */
460
466
  assistant?: AppAssistantCharacter;
461
467
  }
462
468
  export interface AppTestCaseSpec {
@@ -636,6 +636,11 @@ export interface ChatCommonProps {
636
636
  /**
637
637
  * ChatBase props
638
638
  */
639
+ /** Properties laid over a theme's own, by mode. */
640
+ export type ThemeOverrides = {
641
+ light?: Record<string, string>;
642
+ dark?: Record<string, string>;
643
+ };
639
644
  export interface ChatBaseProps {
640
645
  /**
641
646
  * Hands an imperative send function to the host, once the chat is able to
@@ -838,6 +843,12 @@ export interface ChatBaseProps {
838
843
  kernelGpu?: string;
839
844
  /** Optional theme variant override for companion notebook/document surfaces. */
840
845
  themeVariant?: string;
846
+ /**
847
+ * Properties laid over the theme's own, by mode — an application's accent
848
+ * over the theme it wears (LOOP T-05): without them the chat would wear the
849
+ * theme's default accent whatever the page around it set.
850
+ */
851
+ themeOverrides?: ThemeOverrides;
841
852
  /** Optional color mode override for companion notebook/document surfaces. */
842
853
  colorMode?: 'light' | 'dark' | 'auto';
843
854
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.34",
3
+ "version": "1.3.36",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -213,6 +213,7 @@
213
213
  "@radix-ui/react-toggle-group": "^1.1.11",
214
214
  "@radix-ui/react-tooltip": "^1.2.8",
215
215
  "@radix-ui/react-use-controllable-state": "^1.2.2",
216
+ "@rjsf/validator-ajv8": "^5.24.13",
216
217
  "@styled-system/css": "^5.1.5",
217
218
  "@tailwindcss/vite": "^4.1.13",
218
219
  "@tanstack/react-query": "^5.90.6",