@datalayer/agent-runtimes 1.3.55 → 1.3.57

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 (43) hide show
  1. package/lib/chat/base/ChatBase.js +8 -3
  2. package/lib/chat/index.d.ts +1 -0
  3. package/lib/chat/index.js +3 -0
  4. package/lib/chat/marks/SpecMark.d.ts +46 -0
  5. package/lib/chat/marks/SpecMark.js +90 -0
  6. package/lib/chat/marks/iconRef.d.ts +29 -0
  7. package/lib/chat/marks/iconRef.js +40 -0
  8. package/lib/chat/marks/index.d.ts +8 -0
  9. package/lib/chat/marks/index.js +12 -0
  10. package/lib/chat/marks/toolMarks.d.ts +43 -0
  11. package/lib/chat/marks/toolMarks.js +110 -0
  12. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  13. package/lib/chat/messages/ChatMessageList.js +11 -18
  14. package/lib/chat/parts/ToolPart.js +6 -1
  15. package/lib/chat/prompt/InputPrompt.d.ts +4 -1
  16. package/lib/chat/prompt/InputPrompt.js +2 -2
  17. package/lib/chat/prompt/menus/SkillsMenu.js +2 -1
  18. package/lib/chat/prompt/menus/ToolsMenu.d.ts +3 -1
  19. package/lib/chat/prompt/menus/ToolsMenu.js +25 -9
  20. package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
  21. package/lib/chat/tools/ToolCallDisplay.js +3 -2
  22. package/lib/loop/apps/appspec.js +17 -2
  23. package/lib/loop/plugins/chat/ChatView.js +7 -5
  24. package/lib/specs/actions.js +56 -0
  25. package/lib/specs/apps.js +239 -4
  26. package/lib/specs/appspecSchema.js +37 -1
  27. package/lib/specs/frontendTools.js +6 -6
  28. package/lib/specs/mcpServers.d.ts +1 -0
  29. package/lib/specs/mcpServers.js +39 -16
  30. package/lib/specs/skills.js +7 -7
  31. package/lib/specs/tools.js +21 -21
  32. package/lib/types/agentspecs.d.ts +11 -0
  33. package/lib/types/chat.d.ts +4 -0
  34. package/lib/types/mcp.d.ts +2 -2
  35. package/lib/types/skills.d.ts +2 -2
  36. package/lib/types/tools.d.ts +4 -4
  37. package/package.json +1 -1
  38. package/scripts/codegen/agentspecs_clone.py +5 -3
  39. package/scripts/codegen/generate_frontend_tools.py +2 -0
  40. package/scripts/codegen/generate_mcp_servers.py +2 -0
  41. package/scripts/codegen/generate_skills.py +2 -0
  42. package/scripts/codegen/generate_tools.py +2 -0
  43. package/scripts/codegen/marks.py +33 -0
@@ -1,5 +1,5 @@
1
1
  import type { BuiltinTool, MCPServerConfig } from '../../../types';
2
- export declare function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, availableTools, }: {
2
+ export declare function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, availableTools, availableFrontendTools, }: {
3
3
  codemodeEnabled: boolean;
4
4
  onToggleCodemode?: (enabled: boolean) => void | Promise<void>;
5
5
  mcpServers: MCPServerConfig[];
@@ -10,4 +10,6 @@ export declare function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServer
10
10
  approvedMcpTools: Map<string, Set<string>>;
11
11
  onToggleMcpToolApproval: (serverId: string, toolName: string) => void;
12
12
  availableTools: BuiltinTool[];
13
+ /** The frontend tools: the ones the page runs for the agent. */
14
+ availableFrontendTools?: BuiltinTool[];
13
15
  }): import("react").JSX.Element;
@@ -4,14 +4,20 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
6
  /**
7
- * What the agent may call: builtin tools, MCP servers, and codemode.
7
+ * What the agent may call: the runtime's tools, the frontend tools (the ones
8
+ * the page runs), MCP servers, and codemode — each drawn with its mark.
8
9
  *
9
10
  * @module chat/prompt/menus/ToolsMenu
10
11
  */
11
12
  import { Text, Button, ActionMenu, ActionList, ToggleSwitch, Tooltip, } from '@primer/react';
12
13
  import { Box } from '@datalayer/primer-addons';
13
14
  import { ToolsIcon } from '@primer/octicons-react';
14
- export function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, availableTools, }) {
15
+ import { SpecMark, hasMark, marksOfFrontendTool, marksOfMcpServer, marksOfRuntimeTool, } from '../../marks';
16
+ /** A tool's mark, as the leading visual of its row. */
17
+ function LeadingMark({ marks }) {
18
+ return hasMark(marks) ? (_jsx(ActionList.LeadingVisual, { children: _jsx(SpecMark, { icon: marks.icon, emoji: marks.emoji, size: 16 }) })) : null;
19
+ }
20
+ export function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabledMcpTools, enabledMcpToolCount, onToggleMcpTool, onToggleAllMcpServerTools, approvedMcpTools, onToggleMcpToolApproval, availableTools, availableFrontendTools = [], }) {
15
21
  const hasUsableMcpServers = mcpServers.some(server => server.isAvailable);
16
22
  /*
17
23
  * A count, not a word.
@@ -23,12 +29,17 @@ export function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabl
23
29
  */
24
30
  const summary = [
25
31
  `${availableTools.length} tool${availableTools.length === 1 ? '' : 's'}`,
32
+ availableFrontendTools.length > 0
33
+ ? `${availableFrontendTools.length} frontend tool${availableFrontendTools.length === 1 ? '' : 's'}`
34
+ : null,
26
35
  enabledMcpToolCount > 0 ? `${enabledMcpToolCount} MCP enabled` : null,
27
36
  hasUsableMcpServers ? null : 'no MCP server available',
28
37
  ]
29
38
  .filter(Boolean)
30
39
  .join(' · ');
31
- return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Tooltip, { text: `Tools — ${summary}`, direction: "n", children: _jsx(Button, { type: "button", variant: "invisible", size: "small", "aria-label": `Tools — ${summary}`, leadingVisual: ToolsIcon, children: _jsx(Text, { sx: { fontSize: 0 }, children: availableTools.length + enabledMcpToolCount }) }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "start", width: "large", children: _jsx(Box, { sx: { maxHeight: '60vh', overflowY: 'auto' }, children: _jsxs(ActionList, { children: [_jsx(ActionList.Group, { title: "Codemode", children: _jsxs(Box, { sx: {
40
+ return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Anchor, { children: _jsx(Tooltip, { text: `Tools — ${summary}`, direction: "n", children: _jsx(Button, { type: "button", variant: "invisible", size: "small", "aria-label": `Tools — ${summary}`, leadingVisual: ToolsIcon, children: _jsx(Text, { sx: { fontSize: 0 }, children: availableTools.length +
41
+ availableFrontendTools.length +
42
+ enabledMcpToolCount }) }) }) }), _jsx(ActionMenu.Overlay, { side: "outside-top", align: "start", width: "large", children: _jsx(Box, { sx: { maxHeight: '60vh', overflowY: 'auto' }, children: _jsxs(ActionList, { children: [_jsx(ActionList.Group, { title: "Codemode", children: _jsxs(Box, { sx: {
32
43
  display: 'flex',
33
44
  alignItems: 'center',
34
45
  justifyContent: 'space-between',
@@ -43,12 +54,13 @@ export function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabl
43
54
  if (onToggleCodemode) {
44
55
  void onToggleCodemode(!codemodeEnabled);
45
56
  }
46
- }, "aria-labelledby": "toggle-codemode" })] }) }), availableTools.length > 0 ? (_jsx(ActionList.Group, { title: "Tools", children: availableTools.map(tool => (_jsx(ActionList.Item, { disabled: true, children: tool.name }, tool.id))) })) : null, mcpServers.length > 0 && hasUsableMcpServers ? (mcpServers.map(server => {
57
+ }, "aria-labelledby": "toggle-codemode" })] }) }), availableTools.length > 0 ? (_jsx(ActionList.Group, { title: "Tools", children: availableTools.map(tool => (_jsxs(ActionList.Item, { disabled: true, children: [_jsx(LeadingMark, { marks: marksOfRuntimeTool(tool.name) }), tool.name] }, tool.id))) })) : null, availableFrontendTools.length > 0 ? (_jsx(ActionList.Group, { title: "Frontend tools", children: availableFrontendTools.map(tool => (_jsxs(ActionList.Item, { disabled: true, children: [_jsx(LeadingMark, { marks: marksOfFrontendTool(tool.name) }), tool.name] }, tool.id))) })) : null, mcpServers.length > 0 && hasUsableMcpServers ? (mcpServers.map(server => {
47
58
  const serverTools = enabledMcpTools.get(server.id);
48
59
  const allToolNames = server.tools.map(t => t.name);
49
60
  const enabledCount = serverTools?.size ?? 0;
50
61
  const allEnabled = enabledCount === allToolNames.length &&
51
62
  allToolNames.length > 0;
63
+ const serverMarks = marksOfMcpServer(server);
52
64
  return (_jsxs(ActionList.Group, { title: `${server.name}${server.isAvailable ? '' : ' (unavailable)'}`, children: [server.isAvailable && server.tools.length > 0 && (_jsxs(Box, { sx: {
53
65
  display: 'flex',
54
66
  alignItems: 'center',
@@ -57,11 +69,15 @@ export function ToolsMenu({ codemodeEnabled, onToggleCodemode, mcpServers, enabl
57
69
  py: 2,
58
70
  borderBottom: '1px solid',
59
71
  borderColor: 'border.muted',
60
- }, children: [_jsxs(Text, { id: `toggle-all-${server.id}`, sx: {
61
- fontSize: 0,
62
- fontWeight: 'semibold',
63
- color: 'fg.muted',
64
- }, children: ["Enable all (", enabledCount, "/", allToolNames.length, ")"] }), _jsx(ToggleSwitch, { size: "small", checked: allEnabled, onClick: () => onToggleAllMcpServerTools(server.id, allToolNames, !allEnabled), "aria-labelledby": `toggle-all-${server.id}` })] })), server.isAvailable && server.tools.length > 0 ? (server.tools.map(tool => {
72
+ }, children: [_jsxs(Box, { sx: {
73
+ display: 'flex',
74
+ alignItems: 'center',
75
+ gap: 2,
76
+ }, children: [hasMark(serverMarks) && (_jsx(SpecMark, { icon: serverMarks.icon, emoji: serverMarks.emoji, size: 16 })), _jsxs(Text, { id: `toggle-all-${server.id}`, sx: {
77
+ fontSize: 0,
78
+ fontWeight: 'semibold',
79
+ color: 'fg.muted',
80
+ }, children: ["Enable all (", enabledCount, "/", allToolNames.length, ")"] })] }), _jsx(ToggleSwitch, { size: "small", checked: allEnabled, onClick: () => onToggleAllMcpServerTools(server.id, allToolNames, !allEnabled), "aria-labelledby": `toggle-all-${server.id}` })] })), server.isAvailable && server.tools.length > 0 ? (server.tools.map(tool => {
65
81
  const isEnabled = serverTools?.has(tool.name) ?? false;
66
82
  const serverApproved = approvedMcpTools.get(server.id);
67
83
  // Tools default to NOT approved — user must explicitly
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import React from 'react';
8
8
  import type { CodeError, ExecutionResult, DisplayToolCallStatus } from '../../types';
9
+ import { type Marks } from '../marks';
9
10
  /**
10
11
  * Error type classification for display purposes
11
12
  */
@@ -43,6 +44,12 @@ export interface ToolCallDisplayProps {
43
44
  onDeny?: () => void;
44
45
  /** Loading state for approval actions */
45
46
  approvalLoading?: boolean;
47
+ /**
48
+ * The marks of whoever the tool belongs to — its MCP server, skill,
49
+ * frontend tool set or runtime tool spec — drawn before its name: the
50
+ * icon, else the emoji, else nothing.
51
+ */
52
+ marks?: Marks | null;
46
53
  }
47
54
  /**
48
55
  * ToolCallDisplay component - Default display for tool calls in chat
@@ -54,5 +61,5 @@ export interface ToolCallDisplayProps {
54
61
  * - Color-coded status indicators
55
62
  * - Rich error display distinguishing execution errors from code errors
56
63
  */
57
- export declare function ToolCallDisplay({ toolCallId, toolName, args, result, status, error, executionResult, codeError, exitCode, executionError, approvalRequired, approvalState, approvalDecisionSource, onApprove, onDeny, approvalLoading, }: ToolCallDisplayProps): React.JSX.Element;
64
+ export declare function ToolCallDisplay({ toolCallId, toolName, args, result, status, error, executionResult, codeError, exitCode, executionError, approvalRequired, approvalState, approvalDecisionSource, onApprove, onDeny, approvalLoading, marks, }: ToolCallDisplayProps): React.JSX.Element;
58
65
  export default ToolCallDisplay;
@@ -13,6 +13,7 @@ import { useEffect, useState } from 'react';
13
13
  import { Button, Text } from '@primer/react';
14
14
  import { Box } from '@datalayer/primer-addons';
15
15
  import { ChevronDownIcon, ChevronRightIcon, ToolsIcon, CheckCircleIcon, XCircleIcon, ClockIcon, AlertIcon, } from '@primer/octicons-react';
16
+ import { SpecMark, hasMark } from '../marks';
16
17
  /**
17
18
  * Get status icon and color based on tool call status
18
19
  */
@@ -124,7 +125,7 @@ function getArgsSummary(args) {
124
125
  * - Color-coded status indicators
125
126
  * - Rich error display distinguishing execution errors from code errors
126
127
  */
127
- export function ToolCallDisplay({ toolCallId, toolName, args, result, status, error, executionResult, codeError, exitCode, executionError, approvalRequired = false, approvalState, approvalDecisionSource, onApprove, onDeny, approvalLoading = false, }) {
128
+ export function ToolCallDisplay({ toolCallId, toolName, args, result, status, error, executionResult, codeError, exitCode, executionError, approvalRequired = false, approvalState, approvalDecisionSource, onApprove, onDeny, approvalLoading = false, marks, }) {
128
129
  const [isExpanded, setIsExpanded] = useState(false);
129
130
  // Determine effective exit code from props or execution result
130
131
  const effectiveExitCode = exitCode ?? executionResult?.exit_code;
@@ -189,7 +190,7 @@ export function ToolCallDisplay({ toolCallId, toolName, args, result, status, er
189
190
  '&:hover': {
190
191
  backgroundColor: 'neutral.muted',
191
192
  },
192
- }, children: [_jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0 }, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 16 })) : (_jsx(ChevronRightIcon, { size: 16 })) }), _jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0 }, children: _jsx(ToolsIcon, { size: 16 }) }), _jsx(Text, { sx: {
193
+ }, children: [_jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0 }, children: isExpanded ? (_jsx(ChevronDownIcon, { size: 16 })) : (_jsx(ChevronRightIcon, { size: 16 })) }), hasMark(marks) && (_jsx(Box, { sx: { color: 'fg.muted', flexShrink: 0, display: 'flex' }, children: _jsx(SpecMark, { icon: marks?.icon, emoji: marks?.emoji, size: 16 }) })), _jsx(Text, { sx: {
193
194
  fontWeight: 'semibold',
194
195
  fontSize: 1,
195
196
  color: 'fg.default',
@@ -106,7 +106,12 @@ export function emptyAppspec(kind = 'chat') {
106
106
  settings: [],
107
107
  components: [],
108
108
  },
109
- tests: { readyAt: DEFAULT_READY_AT, evalset: '', cases: [] },
109
+ tests: {
110
+ readyAt: DEFAULT_READY_AT,
111
+ evalset: '',
112
+ cases: [],
113
+ verified: { live: [], recorded: [], unverified: [] },
114
+ },
110
115
  record: {
111
116
  keepFor: DEFAULT_KEEP_FOR,
112
117
  retentionDays: retentionDays(DEFAULT_KEEP_FOR),
@@ -315,6 +320,7 @@ export function parseAppspec(document) {
315
320
  const permissions = isData(data.permissions) ? data.permissions : {};
316
321
  const computer = isData(permissions.computer) ? permissions.computer : {};
317
322
  const tests = isData(data.tests) ? data.tests : {};
323
+ const verified = isData(tests.verified) ? tests.verified : {};
318
324
  const record = isData(data.record) ? data.record : {};
319
325
  const checks = isData(data.checks) ? data.checks : {};
320
326
  const keepFor = text(record.keep_for, DEFAULT_KEEP_FOR);
@@ -359,6 +365,11 @@ export function parseAppspec(document) {
359
365
  ask: text(testCase.ask),
360
366
  expect: text(testCase.expect),
361
367
  })),
368
+ verified: {
369
+ live: texts(verified.live),
370
+ recorded: texts(verified.recorded),
371
+ unverified: texts(verified.unverified),
372
+ },
362
373
  },
363
374
  record: {
364
375
  keepFor,
@@ -559,7 +570,11 @@ export function dumpAppspec(app) {
559
570
  .list('cases', app.tests.cases.map(testCase => ({
560
571
  ask: testCase.ask,
561
572
  expect: testCase.expect,
562
- }))).data)
573
+ })))
574
+ .part('verified', new Writer()
575
+ .list('live', app.tests.verified.live)
576
+ .list('recorded', app.tests.verified.recorded)
577
+ .list('unverified', app.tests.verified.unverified).data).data)
563
578
  .part('record', new Writer()
564
579
  .text('keep_for', app.record.keepFor, DEFAULT_KEEP_FOR)
565
580
  .list('include', app.record.include, DEFAULT_RECORD_INCLUDE)
@@ -1288,12 +1288,13 @@ export default function ChatView({ workspace }) {
1288
1288
  const offeredTools = useMemo(() => {
1289
1289
  const fromServer = configQuery.data?.builtinTools ?? [];
1290
1290
  const seen = new Set(fromServer.map(tool => tool.name));
1291
- return [
1292
- ...fromServer,
1293
- ...agentTools
1291
+ // The runtime's, and the frontend tools this page runs: named apart.
1292
+ return {
1293
+ runtime: fromServer,
1294
+ frontend: agentTools
1294
1295
  .filter(tool => !seen.has(tool.name))
1295
1296
  .map(tool => ({ id: tool.name, name: tool.name })),
1296
- ];
1297
+ };
1297
1298
  }, [configQuery.data?.builtinTools, agentTools]);
1298
1299
  /* Which skills are on, as the runtime last reported them. Derived rather
1299
1300
  than held: the source of truth is the agent, and a local copy would drift
@@ -1423,7 +1424,8 @@ export default function ChatView({ workspace }) {
1423
1424
  selectedModel: activeModel,
1424
1425
  onModelSelect: model => void selectModel(model),
1425
1426
  showToolsMenu: !presence,
1426
- availableTools: offeredTools,
1427
+ availableTools: offeredTools.runtime,
1428
+ availableFrontendTools: offeredTools.frontend,
1427
1429
  mcpServers: configQuery.data?.mcpServers ?? [],
1428
1430
  // Live from the host example, when one feeds it: the footer's MCP status
1429
1431
  // indicator. (Codemode surfaces through the toggle below, not a blob.)
@@ -547,6 +547,62 @@ export const SERVER_ACTIONS = {
547
547
  tools: {},
548
548
  conditions: {},
549
549
  },
550
+ 'odoo-accounting': {
551
+ checked: '2026-10-05',
552
+ default: [],
553
+ tools: {
554
+ odoo_accounting_list_invoices: ['read'],
555
+ odoo_accounting_get_invoice: ['read'],
556
+ odoo_accounting_list_journal_entries: ['read'],
557
+ odoo_accounting_list_open_balances: ['read'],
558
+ odoo_accounting_list_accounts: ['read'],
559
+ odoo_accounting_list_journals: ['read'],
560
+ odoo_accounting_get_journal_entry: ['read'],
561
+ odoo_accounting_trial_balance: ['read'],
562
+ odoo_accounting_general_ledger: ['read'],
563
+ odoo_accounting_create_draft_invoice: ['write'],
564
+ odoo_accounting_post_invoice: ['write'],
565
+ odoo_accounting_create_draft_journal_entry: ['write'],
566
+ odoo_accounting_post_journal_entry: ['write'],
567
+ odoo_accounting_list_partners: ['read'],
568
+ odoo_accounting_partner_ledger: ['read'],
569
+ odoo_accounting_aged_balance: ['read'],
570
+ odoo_accounting_list_taxes: ['read'],
571
+ odoo_accounting_list_assets: ['read'],
572
+ odoo_accounting_get_asset: ['read'],
573
+ odoo_accounting_reconcile_lines: ['write'],
574
+ odoo_accounting_unreconcile_lines: ['write'],
575
+ odoo_accounting_bank_status: ['read'],
576
+ odoo_accounting_list_bank_lines: ['read'],
577
+ odoo_accounting_get_bank_line: ['read'],
578
+ odoo_accounting_suggest_bank_matches: ['read'],
579
+ odoo_accounting_suggest_reconciliations: ['read'],
580
+ odoo_accounting_explain_account_balance: ['read'],
581
+ odoo_accounting_list_bank_feeds: ['read'],
582
+ odoo_accounting_list_currency_rates: ['read'],
583
+ odoo_accounting_get_lock_dates: ['read'],
584
+ odoo_accounting_book_bank_line: ['write'],
585
+ odoo_accounting_match_bank_line: ['write'],
586
+ odoo_accounting_reset_bank_line: ['write'],
587
+ odoo_accounting_remove_duplicate_bank_lines: ['delete'],
588
+ odoo_accounting_link_bank_feed: ['write'],
589
+ odoo_accounting_create_vendor_bill: ['write'],
590
+ odoo_accounting_create_customer_invoice: ['write'],
591
+ odoo_accounting_set_currency_rates: ['write'],
592
+ odoo_accounting_set_lock_date: ['write'],
593
+ odoo_accounting_create_matching_model: ['write'],
594
+ odoo_accounting_get_tax_settings: ['read'],
595
+ odoo_accounting_list_tax_returns: ['read'],
596
+ odoo_accounting_get_tax_return: ['read'],
597
+ odoo_accounting_tax_grids: ['read'],
598
+ odoo_accounting_list_tax_grid_lines: ['read'],
599
+ odoo_accounting_check_tax_period: ['read'],
600
+ odoo_accounting_list_untagged_tax_lines: ['read'],
601
+ odoo_accounting_set_tax_periodicity: ['write'],
602
+ odoo_accounting_set_tax_grids: ['write'],
603
+ },
604
+ conditions: {},
605
+ },
550
606
  salesforce: {
551
607
  default: [],
552
608
  tools: {},