@datalayer/agent-runtimes 1.3.57 → 1.3.58

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.
@@ -15,6 +15,8 @@ import { type IconPackage, type IconRef } from './iconRef';
15
15
  export type MarkIcon = React.ComponentType<{
16
16
  size?: number;
17
17
  'aria-hidden'?: boolean | 'true' | 'false';
18
+ /** The Datalayer icons' own colours: a brand drawn as itself. */
19
+ colored?: boolean;
18
20
  }>;
19
21
  type IconModule = Record<string, unknown>;
20
22
  /** Import a package of icons, once. */
@@ -71,7 +71,9 @@ export function useMarkIcon(icon) {
71
71
  export function SpecMark({ icon, emoji, size = 16 }) {
72
72
  const Icon = useMarkIcon(icon);
73
73
  if (icon) {
74
- return Icon ? (_jsx("span", { "data-mark": icon, style: { display: 'inline-flex', flexShrink: 0, lineHeight: 0 }, children: _jsx(Icon, { size: size, "aria-hidden": "true" }) })) : (_jsx("span", { "data-mark": icon, "aria-hidden": "true", style: { display: 'inline-block', width: size, height: size } }));
74
+ return Icon ? (_jsx("span", { "data-mark": icon, style: { display: 'inline-flex', flexShrink: 0, lineHeight: 0 }, children: _jsx(Icon, { size: size, "aria-hidden": "true", ...(icon.startsWith('@datalayer/icons-react:')
75
+ ? { colored: true }
76
+ : {}) }) })) : (_jsx("span", { "data-mark": icon, "aria-hidden": "true", style: { display: 'inline-block', width: size, height: size } }));
75
77
  }
76
78
  if (emoji) {
77
79
  return (_jsx("span", { "data-mark": "emoji", "aria-hidden": "true", style: {
@@ -644,6 +644,7 @@ export const SERVER_ACTIONS = {
644
644
  export const APP_BEHAVIOURS = {
645
645
  'customer-interview': {},
646
646
  'data-quality': {},
647
+ decide: {},
647
648
  'inbox-triage': {
648
649
  'google-workspace.append_table_rows': 'leave_to_me',
649
650
  'google-workspace.batch_modify_gmail_message_labels': 'do_it',
@@ -788,6 +789,7 @@ export const APP_BEHAVIOURS = {
788
789
  export const APP_ESCALATIONS = {
789
790
  'customer-interview': {},
790
791
  'data-quality': {},
792
+ decide: {},
791
793
  'inbox-triage': {
792
794
  'google-workspace.batch_modify_gmail_message_labels': [
793
795
  {
@@ -10,6 +10,7 @@
10
10
  import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
11
11
  export declare const CUSTOMER_INTERVIEW_APP_0_0_1: AppSpec;
12
12
  export declare const DATA_QUALITY_APP_0_0_1: AppSpec;
13
+ export declare const DECIDE_APP_0_0_1: AppSpec;
13
14
  export declare const INBOX_TRIAGE_APP_0_0_1: AppSpec;
14
15
  export declare const MODEL_CHOICE_APP_0_0_1: AppSpec;
15
16
  export declare const PIPELINE_REPORT_APP_0_0_1: AppSpec;
package/lib/specs/apps.js CHANGED
@@ -269,6 +269,114 @@ export const DATA_QUALITY_APP_0_0_1 = {
269
269
  banner: '',
270
270
  setup: ["The agent 'jupyter-data-analyst:0.0.1' is not enabled."],
271
271
  };
272
+ export const DECIDE_APP_0_0_1 = {
273
+ schema: 'loop.app/v1',
274
+ id: 'decide',
275
+ version: '0.0.1',
276
+ name: 'Decide',
277
+ kind: 'chat',
278
+ description: 'Answers a question about a text — a ticket, a message, a review — by asking Jev a typed decision: yes or no, one of named options, or a score, each with its confidence.',
279
+ owner: 'Datalayer <info@datalayer.io>',
280
+ agent: 'example-simple:0.0.1',
281
+ team: '',
282
+ instructions: 'Answer every question about a text by asking a typed decision with the decide tool: the text as it was given is the state, and the question is one of noul (does a statement hold: yes or no), choice (which of the options named) or score (which step of a scale, lowest first). Then say the answer in plain words with its probability or its confidence, for example "Urgent: yes (0.87)". When the question names no options for a choice or no scale for a score, ask for them rather than inventing them. When nothing was decided, say why in a sentence.',
283
+ model: '',
284
+ skills: [],
285
+ tools: ['decide:0.0.1'],
286
+ context: [],
287
+ contents: [],
288
+ connections: [],
289
+ rules: [
290
+ {
291
+ action: 'Ask a decision',
292
+ appliesTo: ['decide'],
293
+ behaviour: 'do_it',
294
+ },
295
+ ],
296
+ permissions: {
297
+ spaces: [],
298
+ computer: {
299
+ browse: false,
300
+ files: false,
301
+ shell: false,
302
+ },
303
+ },
304
+ interface: {
305
+ layout: 'chat',
306
+ accent: 'sun',
307
+ welcome: 'Give me a text and a question about it. I ask Jev a typed decision — yes or no, a choice, or a score — and tell you the answer with its confidence.',
308
+ starters: [
309
+ {
310
+ label: 'Is it urgent?',
311
+ message: "Is this ticket urgent? 'Help! My payouts have been failing for 3 days.'",
312
+ },
313
+ {
314
+ label: 'Which team?',
315
+ message: "Which team should handle this: 'I was charged twice this month'? Billing, Tech or Sales.",
316
+ },
317
+ {
318
+ label: 'Score a review',
319
+ message: "Score how positive this review is from 1 to 5: 'Setup took an hour, but support answered fast and it works.'",
320
+ },
321
+ ],
322
+ settings: [],
323
+ components: [],
324
+ assistant: 'wizard',
325
+ },
326
+ tests: {
327
+ readyAt: 0.8,
328
+ evalset: '',
329
+ cases: [
330
+ {
331
+ ask: "Is this ticket urgent? 'Help! My payouts have been failing for 3 days.'",
332
+ expect: 'It calls decide with a noul question and answers yes, with its probability.',
333
+ },
334
+ {
335
+ ask: "Which team should handle this: 'I was charged twice this month'? Billing, Tech or Sales.",
336
+ expect: 'It calls decide with a choice among the three and answers Billing, with its confidence.',
337
+ },
338
+ {
339
+ ask: 'Score this review.',
340
+ expect: 'It asks for the review and the scale rather than inventing them.',
341
+ },
342
+ ],
343
+ verified: {
344
+ live: [],
345
+ recorded: [],
346
+ unverified: [
347
+ 'Its tests have not been run as a set: no validation run is attached to it.',
348
+ ],
349
+ },
350
+ },
351
+ record: {
352
+ keepFor: '30_days',
353
+ include: ['conversations', 'decisions'],
354
+ suggestTests: false,
355
+ retentionDays: 30,
356
+ },
357
+ checks: {
358
+ guards: [],
359
+ gates: [],
360
+ track: '',
361
+ },
362
+ deployment: {
363
+ hosted: {
364
+ visibility: 'private',
365
+ slug: '',
366
+ },
367
+ },
368
+ goal: '',
369
+ triggers: [],
370
+ memory: '',
371
+ notifications: [],
372
+ enabled: true,
373
+ tags: ['example', 'decisions', 'jev'],
374
+ icon: 'law',
375
+ emoji: '⚖️',
376
+ avatar: '',
377
+ banner: '',
378
+ setup: [],
379
+ };
272
380
  export const INBOX_TRIAGE_APP_0_0_1 = {
273
381
  schema: 'loop.app/v1',
274
382
  id: 'inbox-triage',
@@ -2072,6 +2180,7 @@ export const WEB_RESEARCH_APP_0_0_1 = {
2072
2180
  export const APP_CATALOGUE = {
2073
2181
  'customer-interview': CUSTOMER_INTERVIEW_APP_0_0_1,
2074
2182
  'data-quality': DATA_QUALITY_APP_0_0_1,
2183
+ decide: DECIDE_APP_0_0_1,
2075
2184
  'inbox-triage': INBOX_TRIAGE_APP_0_0_1,
2076
2185
  'model-choice': MODEL_CHOICE_APP_0_0_1,
2077
2186
  'pipeline-report': PIPELINE_REPORT_APP_0_0_1,
@@ -2266,6 +2375,74 @@ export const APP_SOURCES = {
2266
2375
  icon: 'filter',
2267
2376
  emoji: '🧹',
2268
2377
  },
2378
+ decide: {
2379
+ schema: 'loop.app/v1',
2380
+ id: 'decide',
2381
+ name: 'Decide',
2382
+ kind: 'chat',
2383
+ description: 'Answers a question about a text — a ticket, a message, a review — by asking Jev a typed decision: yes or no, one of named options, or a score, each with its confidence.',
2384
+ owner: 'Datalayer <info@datalayer.io>',
2385
+ agent: 'example-simple:0.0.1',
2386
+ instructions: 'Answer every question about a text by asking a typed decision with the decide tool: the text as it was given is the state, and the question is one of noul (does a statement hold: yes or no), choice (which of the options named) or score (which step of a scale, lowest first). Then say the answer in plain words with its probability or its confidence, for example "Urgent: yes (0.87)". When the question names no options for a choice or no scale for a score, ask for them rather than inventing them. When nothing was decided, say why in a sentence.',
2387
+ tools: ['decide:0.0.1'],
2388
+ rules: [
2389
+ {
2390
+ action: 'Ask a decision',
2391
+ applies_to: ['decide'],
2392
+ behaviour: 'do_it',
2393
+ },
2394
+ ],
2395
+ interface: {
2396
+ accent: 'sun',
2397
+ welcome: 'Give me a text and a question about it. I ask Jev a typed decision — yes or no, a choice, or a score — and tell you the answer with its confidence.',
2398
+ starters: [
2399
+ {
2400
+ label: 'Is it urgent?',
2401
+ message: "Is this ticket urgent? 'Help! My payouts have been failing for 3 days.'",
2402
+ },
2403
+ {
2404
+ label: 'Which team?',
2405
+ message: "Which team should handle this: 'I was charged twice this month'? Billing, Tech or Sales.",
2406
+ },
2407
+ {
2408
+ label: 'Score a review',
2409
+ message: "Score how positive this review is from 1 to 5: 'Setup took an hour, but support answered fast and it works.'",
2410
+ },
2411
+ ],
2412
+ assistant: 'wizard',
2413
+ },
2414
+ tests: {
2415
+ cases: [
2416
+ {
2417
+ ask: "Is this ticket urgent? 'Help! My payouts have been failing for 3 days.'",
2418
+ expect: 'It calls decide with a noul question and answers yes, with its probability.',
2419
+ },
2420
+ {
2421
+ ask: "Which team should handle this: 'I was charged twice this month'? Billing, Tech or Sales.",
2422
+ expect: 'It calls decide with a choice among the three and answers Billing, with its confidence.',
2423
+ },
2424
+ {
2425
+ ask: 'Score this review.',
2426
+ expect: 'It asks for the review and the scale rather than inventing them.',
2427
+ },
2428
+ ],
2429
+ verified: {
2430
+ unverified: [
2431
+ 'Its tests have not been run as a set: no validation run is attached to it.',
2432
+ ],
2433
+ },
2434
+ },
2435
+ record: {
2436
+ keep_for: '30_days',
2437
+ include: ['conversations', 'decisions'],
2438
+ },
2439
+ deployment: {
2440
+ hosted: {},
2441
+ },
2442
+ tags: ['example', 'decisions', 'jev'],
2443
+ icon: 'law',
2444
+ emoji: '⚖️',
2445
+ },
2269
2446
  'inbox-triage': {
2270
2447
  schema: 'loop.app/v1',
2271
2448
  id: 'inbox-triage',
@@ -3644,6 +3821,7 @@ export const APP_SOURCES = {
3644
3821
  export const APP_BUILT = {
3645
3822
  'customer-interview': 'python',
3646
3823
  'data-quality': 'written',
3824
+ decide: 'written',
3647
3825
  'inbox-triage': 'written',
3648
3826
  'model-choice': 'written',
3649
3827
  'pipeline-report': 'written',
@@ -17,5 +17,7 @@ export declare const BesideItsWork: Story;
17
17
  export declare const WorkerActivity: Story;
18
18
  /** An approval. */
19
19
  export declare const Approval: Story;
20
+ /** Tool calls, each led by the mark of whoever the tool belongs to. */
21
+ export declare const ToolMarks: Story;
20
22
  /** The floating assistant, in each of its states or stepped aside. */
21
23
  export declare const Assistant: Story;
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
- import { ASSISTANT_PICTURES, AssistantScreen, ApprovalScreen, BesideWorkScreen, ConversationScreen, REFERENCE_THEMES, ReferenceTheme, WorkerActivityScreen, } from './ReferenceScreens';
2
+ import { ASSISTANT_PICTURES, AssistantScreen, ApprovalScreen, BesideWorkScreen, ConversationScreen, REFERENCE_THEMES, ReferenceTheme, ToolMarksScreen, WorkerActivityScreen, } from './ReferenceScreens';
3
3
  const meta = {
4
4
  title: 'Loop/Reference screens',
5
5
  parameters: { layout: 'fullscreen' },
@@ -25,6 +25,8 @@ export const BesideItsWork = story(BesideWorkScreen);
25
25
  export const WorkerActivity = story(WorkerActivityScreen);
26
26
  /** An approval. */
27
27
  export const Approval = story(ApprovalScreen);
28
+ /** Tool calls, each led by the mark of whoever the tool belongs to. */
29
+ export const ToolMarks = story(ToolMarksScreen);
28
30
  /** The floating assistant, in each of its states or stepped aside. */
29
31
  export const Assistant = {
30
32
  render: ({ theme, mode, picture }) => (_jsx(ReferenceTheme, { theme: theme, mode: mode, children: _jsx(AssistantScreen, { picture: picture }) })),
@@ -1,7 +1,7 @@
1
1
  import type { JSX, ReactNode } from 'react';
2
2
  import { type ThemeVariant } from '@datalayer/primer-addons';
3
- /** The four screens, by the name a picture and a story take. */
4
- export declare const REFERENCE_SCREENS: readonly ["conversation", "beside-work", "worker-activity", "approval"];
3
+ /** The screens, by the name a picture and a story take. */
4
+ export declare const REFERENCE_SCREENS: readonly ["conversation", "beside-work", "worker-activity", "approval", "tool-marks"];
5
5
  export type ReferenceScreen = (typeof REFERENCE_SCREENS)[number];
6
6
  /** The floating assistant's pictures (T-27): its states, and stepped aside. */
7
7
  export declare const ASSISTANT_PICTURES: readonly ["idle", "thinking", "working", "waiting", "paused", "speaking", "aside"];
@@ -34,6 +34,8 @@ export declare function BesideWorkScreen(): JSX.Element;
34
34
  export declare function WorkerActivityScreen(): JSX.Element;
35
35
  /** An approval: the action, the rule that asks, and the one decision. */
36
36
  export declare function ApprovalScreen(): JSX.Element;
37
+ /** Tool calls, each led by the mark of whoever the tool belongs to. */
38
+ export declare function ToolMarksScreen(): JSX.Element;
37
39
  export declare const REFERENCE_SCREEN_COMPONENTS: Record<ReferenceScreen, () => JSX.Element>;
38
40
  /**
39
41
  * The paper clip in one state, in the page's corner. `aside` puts a dialog
@@ -31,12 +31,13 @@ import { ASSISTANT_WORDS, } from '../../chat/assistant/state';
31
31
  import { buildReactorFromPlugins } from '@datalayer/reactor';
32
32
  import { AssistantCharactersPlugin, assistantCharacterNamed, } from '../../loop/plugins/assistant-characters';
33
33
  import { OwlCharacterPlugin } from '../../examples/utils/owlCharacterPlugin';
34
- /** The four screens, by the name a picture and a story take. */
34
+ /** The screens, by the name a picture and a story take. */
35
35
  export const REFERENCE_SCREENS = [
36
36
  'conversation',
37
37
  'beside-work',
38
38
  'worker-activity',
39
39
  'approval',
40
+ 'tool-marks',
40
41
  ];
41
42
  /** The floating assistant's pictures (T-27): its states, and stepped aside. */
42
43
  export const ASSISTANT_PICTURES = [
@@ -145,6 +146,24 @@ const APPROVAL = [
145
146
  said('a2', 'assistant', 'Sending is a rule you keep: I ask first.'),
146
147
  tool('a3', 'send_email', { to: 'ada@example.com', subject: 'Your order 1042' }, 'inProgress', { pending_approval: true, approval_id: 'approval-1' }),
147
148
  ];
149
+ /*
150
+ * Whose tools: an MCP server's (the Odoo accounting server's, its icon the
151
+ * Datalayer icons' Odoo), a skill's and a frontend tool set's, each call led
152
+ * by its mark.
153
+ */
154
+ const TOOL_MARKS = [
155
+ said('t1', 'user', 'What is still open on the books, and does my notebook agree?'),
156
+ tool('t2', 'odoo_accounting_list_open_balances', { account: '400000' }, 'complete', { partners: 7 }),
157
+ tool('t3', 'run_skill_script', { skill_name: 'accounting', script: 'variance' }, 'complete', { variance: 0 }),
158
+ tool('t4', 'readCell', { index: 3 }, 'complete', { source: 'balances' }),
159
+ said('t5', 'assistant', 'Seven customers owe you, and your notebook says the same total.'),
160
+ ];
161
+ const TOOL_MARKS_SERVERS = [
162
+ {
163
+ id: 'odoo-accounting',
164
+ tools: [{ name: 'odoo_accounting_list_open_balances' }],
165
+ },
166
+ ];
148
167
  // ---------------------------------------------------------------------------
149
168
  // The conversation
150
169
  // ---------------------------------------------------------------------------
@@ -157,7 +176,7 @@ const AVATARS = {
157
176
  assistantAvatarBg: 'accent.emphasis',
158
177
  };
159
178
  /** A chat as an application shows it: its presence, its words, its composer. */
160
- function Conversation({ name, face, presence, items, composer = true, }) {
179
+ function Conversation({ name, face, presence, items, composer = true, mcpServers, }) {
161
180
  const endRef = useRef(null);
162
181
  const [input, setInput] = useState('');
163
182
  return (_jsxs(Box, { sx: {
@@ -166,7 +185,7 @@ function Conversation({ name, face, presence, items, composer = true, }) {
166
185
  display: 'flex',
167
186
  flexDirection: 'column',
168
187
  height: '100%',
169
- }, children: [_jsx(ChatBaseHeader, { title: name, brandIcon: _jsx(PresenceFace, { face: face, size: 20, state: presence }), headerContent: _jsx(PresenceLine, { state: presence }), padding: 3, messageCount: items.length, onNewChat: () => undefined, onClear: () => undefined }), _jsx(Box, { sx: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(ChatMessageList, { displayItems: items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 3, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined }) }), composer && (_jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Send a message", autoFocus: false, padding: 3, onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false }))] }));
188
+ }, children: [_jsx(ChatBaseHeader, { title: name, brandIcon: _jsx(PresenceFace, { face: face, size: 20, state: presence }), headerContent: _jsx(PresenceLine, { state: presence }), padding: 3, messageCount: items.length, onNewChat: () => undefined, onClear: () => undefined }), _jsx(Box, { sx: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(ChatMessageList, { displayItems: items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 3, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined, mcpServers: mcpServers }) }), composer && (_jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Send a message", autoFocus: false, padding: 3, onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false }))] }));
170
189
  }
171
190
  // ---------------------------------------------------------------------------
172
191
  // The four screens
@@ -198,11 +217,16 @@ export function WorkerActivityScreen() {
198
217
  export function ApprovalScreen() {
199
218
  return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 640, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "waiting", items: APPROVAL }) }) }) }));
200
219
  }
220
+ /** Tool calls, each led by the mark of whoever the tool belongs to. */
221
+ export function ToolMarksScreen() {
222
+ return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 680, display: 'flex' }, children: _jsx(Conversation, { name: "Bookkeeper", face: "\uD83E\uDDEE", presence: "idle", items: TOOL_MARKS, composer: false, mcpServers: TOOL_MARKS_SERVERS }) }) }) }));
223
+ }
201
224
  export const REFERENCE_SCREEN_COMPONENTS = {
202
225
  conversation: ConversationScreen,
203
226
  'beside-work': BesideWorkScreen,
204
227
  'worker-activity': WorkerActivityScreen,
205
228
  approval: ApprovalScreen,
229
+ 'tool-marks': ToolMarksScreen,
206
230
  };
207
231
  // ---------------------------------------------------------------------------
208
232
  // The floating assistant (T-27)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.57",
3
+ "version": "1.3.58",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",