@datalayer/agent-runtimes 1.3.54 → 1.3.56

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 (57) 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/examples/AgentStrategyExample.d.ts +12 -0
  23. package/lib/examples/{AgentLoopExample.js → AgentStrategyExample.js} +62 -62
  24. package/lib/examples/example-selector.js +1 -1
  25. package/lib/examples/index.d.ts +1 -1
  26. package/lib/examples/index.js +1 -1
  27. package/lib/examples/main.js +3 -3
  28. package/lib/loop/plugins/chat/ChatView.js +7 -5
  29. package/lib/specs/actions.js +56 -0
  30. package/lib/specs/frontendTools.js +6 -6
  31. package/lib/specs/index.d.ts +1 -1
  32. package/lib/specs/index.js +1 -1
  33. package/lib/specs/mcpServers.d.ts +1 -0
  34. package/lib/specs/mcpServers.js +39 -16
  35. package/lib/specs/skills.js +7 -7
  36. package/lib/specs/strategies.d.ts +34 -0
  37. package/lib/specs/{loops.js → strategies.js} +30 -30
  38. package/lib/specs/tools.js +21 -21
  39. package/lib/types/chat.d.ts +4 -0
  40. package/lib/types/index.d.ts +1 -1
  41. package/lib/types/index.js +1 -1
  42. package/lib/types/mcp.d.ts +2 -2
  43. package/lib/types/skills.d.ts +2 -2
  44. package/lib/types/{loops.d.ts → strategies.d.ts} +17 -17
  45. package/lib/types/tools.d.ts +4 -4
  46. package/package.json +1 -1
  47. package/scripts/codegen/agentspecs_clone.py +5 -3
  48. package/scripts/codegen/generate_agents.py +1 -1
  49. package/scripts/codegen/generate_frontend_tools.py +2 -0
  50. package/scripts/codegen/generate_mcp_servers.py +2 -0
  51. package/scripts/codegen/generate_skills.py +2 -0
  52. package/scripts/codegen/{generate_loops.py → generate_strategies.py} +81 -81
  53. package/scripts/codegen/generate_tools.py +2 -0
  54. package/scripts/codegen/marks.py +33 -0
  55. package/lib/examples/AgentLoopExample.d.ts +0 -12
  56. package/lib/specs/loops.d.ts +0 -34
  57. /package/lib/types/{loops.js → strategies.js} +0 -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',
@@ -0,0 +1,12 @@
1
+ import { ServiceManager } from '@jupyterlab/services';
2
+ interface AgentStrategyExampleInnerProps {
3
+ serviceManager?: ServiceManager.IManager;
4
+ }
5
+ export declare function AgentStrategyExampleInner({ serviceManager, }: AgentStrategyExampleInnerProps): import("react").JSX.Element;
6
+ /**
7
+ * Main example component with Jupyter provider wrapper.
8
+ */
9
+ export declare function AgentStrategyExample({ serviceManager, }: {
10
+ serviceManager?: ServiceManager.IManager;
11
+ }): import("react").JSX.Element;
12
+ export default AgentStrategyExample;
@@ -4,27 +4,27 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
4
4
  * Distributed under the terms of the Modified BSD License.
5
5
  */
6
6
  /**
7
- * AgentLoopExample - Define and launch an agent execution *loop*.
7
+ * AgentStrategyExample - Define and launch an agent reasoning *strategy*.
8
8
  *
9
- * This example demonstrates the "loop" paradigm (observe → think → act →
9
+ * This example demonstrates the control-loop paradigm (observe → think → act →
10
10
  * evaluate) instead of one-shot prompting. It is fully driven by the generic
11
- * loop specifications defined in agentspecs (`src/specs/loops.ts`):
11
+ * strategy specifications defined in agentspecs (`src/specs/strategies.ts`):
12
12
  *
13
- * - Pick any loop spec (Data Analysis, Plan/Execute/Critic, OODA,
14
- * Human-in-the-Loop). The list is read from the loop catalogue, so adding a
15
- * new YAML loop spec automatically makes it available here.
16
- * - The agent's system prompt is composed generically from the selected loop's
13
+ * - Pick any strategy spec (Data Analysis, Plan/Execute/Critic, OODA,
14
+ * Human-in-the-Loop). The list is read from the strategy catalogue, so adding a
15
+ * new YAML strategy spec automatically makes it available here.
16
+ * - The agent's system prompt is composed generically from the selected strategy's
17
17
  * objective, phases, constraints, termination policy and human settings — no
18
- * loop-specific code.
19
- * - The agent then runs the loop against a live Jupyter notebook, where the
20
- * loop state (dataframes, charts, intermediate results) lives outside the
18
+ * strategy-specific code.
19
+ * - The agent then runs the strategy against a live Jupyter notebook, where the
20
+ * strategy state (dataframes, charts, intermediate results) lives outside the
21
21
  * model so each iteration stays small and focused.
22
22
  *
23
23
  * To run this example:
24
24
  * 1. Start the agent-runtimes server.
25
25
  * 2. Select this example from the header dropdown (Agent group).
26
26
  *
27
- * @module examples/AgentLoopExample
27
+ * @module examples/AgentStrategyExample
28
28
  */
29
29
  import { useMemo, useState } from 'react';
30
30
  import { Box } from '@datalayer/primer-addons';
@@ -32,40 +32,40 @@ import { Notebook } from '@datalayer/jupyter-react';
32
32
  import { useNotebookTools } from '../tools/adapters/agent-runtimes/notebookHooks';
33
33
  import { ThemedJupyterProvider, ThemedProvider } from './utils/themedProvider';
34
34
  import { ChatSidebar } from '../chat';
35
- import { listLoops, getLoop, DEFAULT_LOOP } from '../specs';
35
+ import { listStrategies, getStrategy, DEFAULT_STRATEGY } from '../specs';
36
36
  import { useExampleJupyterAgent } from './hooks/useExampleJupyterAgent';
37
37
  import MatplotlibNotebook from './utils/notebooks/Matplotlib.ipynb.json';
38
38
  import { ExampleNotebookToolbar } from './utils/notebookToolbarItems';
39
39
  // Fixed notebook ID
40
- const NOTEBOOK_ID = 'agent-loop-example';
41
- // Use the imported Matplotlib notebook as the working surface for the loop.
40
+ const NOTEBOOK_ID = 'agent-strategy-example';
41
+ // Use the imported Matplotlib notebook as the working surface for the strategy.
42
42
  const NOTEBOOK_CONTENT = MatplotlibNotebook;
43
43
  // Default configuration
44
- const DEFAULT_AGENT_ID = import.meta.env.VITE_AGENT_ID || 'loop-agent-runtime-example';
44
+ const DEFAULT_AGENT_ID = import.meta.env.VITE_AGENT_ID || 'strategy-agent-runtime-example';
45
45
  /**
46
- * Compose an agent system prompt generically from a loop specification.
46
+ * Compose an agent system prompt generically from a strategy specification.
47
47
  *
48
- * This is intentionally spec-driven: every field comes from the loop YAML, so
49
- * the same code drives any loop (data analysis, plan/execute/critic, OODA, ...).
48
+ * This is intentionally spec-driven: every field comes from the strategy YAML, so
49
+ * the same code drives any strategy (data analysis, plan/execute/critic, OODA, ...).
50
50
  */
51
- function buildLoopSystemPrompt(loop) {
51
+ function buildStrategySystemPrompt(strategy) {
52
52
  const lines = [];
53
- lines.push(`You are an agent that operates as a control LOOP rather than answering in a single prompt.`);
54
- lines.push(`Loop: ${loop.name} (${loop.strategy}).`);
55
- if (loop.objective) {
56
- lines.push(`Objective: ${loop.objective}`);
53
+ lines.push(`You are an agent that operates as a control loop rather than answering in a single prompt.`);
54
+ lines.push(`Strategy: ${strategy.name} (${strategy.strategy}).`);
55
+ if (strategy.objective) {
56
+ lines.push(`Objective: ${strategy.objective}`);
57
57
  }
58
- if (loop.phases?.length) {
59
- lines.push(`Each iteration, execute these phases in order: ${loop.phases.join(' → ')}. ` +
58
+ if (strategy.phases?.length) {
59
+ lines.push(`Each iteration, execute these phases in order: ${strategy.phases.join(' → ')}. ` +
60
60
  `Decide the single best next action each iteration; do not try to solve everything at once.`);
61
61
  }
62
- if (loop.constraints?.length) {
63
- lines.push(`Constraints you must respect:\n${loop.constraints
62
+ if (strategy.constraints?.length) {
63
+ lines.push(`Constraints you must respect:\n${strategy.constraints
64
64
  .map(c => `- ${c}`)
65
65
  .join('\n')}`);
66
66
  }
67
- if (loop.termination) {
68
- const t = loop.termination;
67
+ if (strategy.termination) {
68
+ const t = strategy.termination;
69
69
  lines.push(`Termination: stop after at most ${t.maxIterations} iterations, or when the goal is reached.`);
70
70
  if (t.successCriteria?.length) {
71
71
  lines.push(`Success criteria:\n${t.successCriteria.map(s => `- ${s}`).join('\n')}`);
@@ -74,13 +74,13 @@ function buildLoopSystemPrompt(loop) {
74
74
  lines.push(`If blocked: ${t.onBlocked}.`);
75
75
  }
76
76
  }
77
- if (loop.human && loop.human.mode !== 'none') {
78
- lines.push(`Human-in-the-loop: mode "${loop.human.mode}"` +
79
- (loop.human.approvalRequired
80
- ? `; request explicit approval before: ${loop.human.approvalFor.join(', ') || 'sensitive actions'}.`
77
+ if (strategy.human && strategy.human.mode !== 'none') {
78
+ lines.push(`Human-in-the-loop: mode "${strategy.human.mode}"` +
79
+ (strategy.human.approvalRequired
80
+ ? `; request explicit approval before: ${strategy.human.approvalFor.join(', ') || 'sensitive actions'}.`
81
81
  : '.'));
82
82
  }
83
- lines.push(`Keep loop state (dataframes, charts, intermediate results) in the notebook/runtime, not in the prompt. ` +
83
+ lines.push(`Keep strategy state (dataframes, charts, intermediate results) in the notebook/runtime, not in the prompt. ` +
84
84
  `For notebook operations, always use the notebook frontend tools (runCell, readAllCells, readCell, insertCell, updateCell, deleteCells) so actions happen in the live notebook UI. ` +
85
85
  `Use executeCodeInNotebook only for temporary inspection code that should not modify notebook cells.`);
86
86
  return lines.join('\n\n');
@@ -108,9 +108,9 @@ function NotebookUI({ serviceManager }) {
108
108
  return (_jsx(ThemedJupyterProvider, { children: _jsx(Notebook, { nbformat: NOTEBOOK_CONTENT, id: NOTEBOOK_ID, Toolbar: ExampleNotebookToolbar, serviceManager: serviceManager, height: "100%", cellSidebarMargin: 120, startDefaultKernel: true }) }));
109
109
  }
110
110
  /**
111
- * A compact, read-only summary of the selected loop specification.
111
+ * A compact, read-only summary of the selected strategy specification.
112
112
  */
113
- function LoopSummary({ loop }) {
113
+ function StrategySummary({ strategy }) {
114
114
  return (_jsxs(Box, { sx: {
115
115
  mt: 2,
116
116
  p: 2,
@@ -119,7 +119,7 @@ function LoopSummary({ loop }) {
119
119
  borderRadius: 2,
120
120
  bg: 'canvas.default',
121
121
  fontSize: 0,
122
- }, children: [_jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 2, mb: 1 }, children: loop.phases.map((phase, index) => (_jsxs(Box, { sx: {
122
+ }, children: [_jsx(Box, { sx: { display: 'flex', flexWrap: 'wrap', gap: 2, mb: 1 }, children: strategy.phases.map((phase, index) => (_jsxs(Box, { sx: {
123
123
  display: 'flex',
124
124
  alignItems: 'center',
125
125
  gap: 1,
@@ -129,21 +129,21 @@ function LoopSummary({ loop }) {
129
129
  bg: 'neutral.muted',
130
130
  color: 'fg.default',
131
131
  fontWeight: 'bold',
132
- }, children: [index + 1, ". ", phase] }, phase))) }), loop.objective && (_jsxs(Box, { sx: { color: 'fg.muted' }, children: [_jsx("strong", { children: "Objective:" }), " ", loop.objective] })), loop.termination && (_jsxs(Box, { sx: { color: 'fg.muted', mt: 1 }, children: [_jsx("strong", { children: "Max iterations:" }), " ", loop.termination.maxIterations, " \u00B7", ' ', _jsx("strong", { children: "On blocked:" }), " ", loop.termination.onBlocked] }))] }));
132
+ }, children: [index + 1, ". ", phase] }, phase))) }), strategy.objective && (_jsxs(Box, { sx: { color: 'fg.muted' }, children: [_jsx("strong", { children: "Objective:" }), " ", strategy.objective] })), strategy.termination && (_jsxs(Box, { sx: { color: 'fg.muted', mt: 1 }, children: [_jsx("strong", { children: "Max iterations:" }), " ", strategy.termination.maxIterations, ' ', "\u00B7 ", _jsx("strong", { children: "On blocked:" }), " ", strategy.termination.onBlocked] }))] }));
133
133
  }
134
- export function AgentLoopExampleInner({ serviceManager, }) {
135
- // All available loops come from the generated loop catalogue.
136
- const loops = useMemo(() => listLoops(), []);
137
- const [selectedLoopId, setSelectedLoopId] = useState(DEFAULT_LOOP);
138
- const selectedLoop = useMemo(() => getLoop(selectedLoopId) ?? loops[0], [selectedLoopId, loops]);
139
- const systemPrompt = useMemo(() => buildLoopSystemPrompt(selectedLoop), [selectedLoop]);
134
+ export function AgentStrategyExampleInner({ serviceManager, }) {
135
+ // All available strategies come from the generated strategy catalogue.
136
+ const strategies = useMemo(() => listStrategies(), []);
137
+ const [selectedStrategyId, setSelectedStrategyId] = useState(DEFAULT_STRATEGY);
138
+ const selectedStrategy = useMemo(() => getStrategy(selectedStrategyId) ?? strategies[0], [selectedStrategyId, strategies]);
139
+ const systemPrompt = useMemo(() => buildStrategySystemPrompt(selectedStrategy), [selectedStrategy]);
140
140
  // The tools. Which of the chat and the harness runs them depends on
141
- // where the loop turns, and the hook decides that.
141
+ // where the strategy turns, and the hook decides that.
142
142
  const tools = useNotebookTools(NOTEBOOK_ID);
143
143
  const { agentReady, protocol: protocolConfig, chatFrontendTools, error, unavailableReason, createAttempted, } = useExampleJupyterAgent({
144
- exampleId: 'AgentLoopExample',
144
+ exampleId: 'AgentStrategyExample',
145
145
  agentName: DEFAULT_AGENT_ID,
146
- description: `Loop agent (${selectedLoop.name}) for AgentLoopExample`,
146
+ description: `Strategy agent (${selectedStrategy.name}) for AgentStrategyExample`,
147
147
  systemPrompt,
148
148
  serviceManager,
149
149
  frontendTools: tools,
@@ -153,16 +153,16 @@ export function AgentLoopExampleInner({ serviceManager, }) {
153
153
  // The example's own agent, not merely the runtime: on the cloud target the
154
154
  // runtime is ready before this agent has been registered on it.
155
155
  const effectiveReady = agentReady;
156
- const isLoopSelectorDisabled = createAttempted;
156
+ const isStrategySelectorDisabled = createAttempted;
157
157
  // Get notebook tools for ChatSidebar
158
158
  // Build Vercel AI protocol config
159
- // Chat suggestions derived from the selected loop.
159
+ // Chat suggestions derived from the selected strategy.
160
160
  const suggestions = useMemo(() => [
161
161
  {
162
- title: `${selectedLoop.emoji} Run the loop`,
163
- message: selectedLoop.objective
164
- ? `Run the ${selectedLoop.name}. ${selectedLoop.objective}`
165
- : `Run the ${selectedLoop.name} against the data in this notebook.`,
162
+ title: `${selectedStrategy.emoji} Run the strategy`,
163
+ message: selectedStrategy.objective
164
+ ? `Run the ${selectedStrategy.name}. ${selectedStrategy.objective}`
165
+ : `Run the ${selectedStrategy.name} against the data in this notebook.`,
166
166
  },
167
167
  {
168
168
  title: '🔁 Next iteration',
@@ -176,7 +176,7 @@ export function AgentLoopExampleInner({ serviceManager, }) {
176
176
  title: '📊 Summarize findings',
177
177
  message: 'Summarize the key findings and produce a final, decision-oriented report.',
178
178
  },
179
- ], [selectedLoop]);
179
+ ], [selectedStrategy]);
180
180
  return (_jsx(_Fragment, { children: _jsxs(Box, { sx: {
181
181
  // The wrapper is the viewport, not the window: it sits below the
182
182
  // header and beside the sidebar, so viewport units size this box to
@@ -204,9 +204,9 @@ export function AgentLoopExampleInner({ serviceManager, }) {
204
204
  justifyContent: 'space-between',
205
205
  gap: 3,
206
206
  flexWrap: 'wrap',
207
- }, children: [_jsxs(Box, { children: [_jsxs("h1", { style: { margin: 0, fontSize: '1.5rem' }, children: [selectedLoop.emoji, " Agent Loop Example"] }), _jsx("p", { style: { margin: '8px 0 0', color: 'var(--fgColor-muted)' }, children: "Define and launch an agent execution loop \u2014 observe, think, act, evaluate \u2014 over a live notebook." })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, title: isLoopSelectorDisabled
208
- ? 'Loop selector is temporarily disabled while launching the agent'
209
- : 'Choose a loop', children: [_jsx(Box, { as: "label", sx: { fontSize: 1, fontWeight: 'bold' }, children: "Loop" }), _jsx(Box, { as: "select", value: selectedLoopId, onChange: (e) => setSelectedLoopId(e.target.value), disabled: isLoopSelectorDisabled, "aria-label": "Loop specification", sx: {
207
+ }, children: [_jsxs(Box, { children: [_jsxs("h1", { style: { margin: 0, fontSize: '1.5rem' }, children: [selectedStrategy.emoji, " Agent Strategy Example"] }), _jsx("p", { style: { margin: '8px 0 0', color: 'var(--fgColor-muted)' }, children: "Define and launch an agent reasoning strategy \u2014 observe, think, act, evaluate \u2014 over a live notebook." })] }), _jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2 }, title: isStrategySelectorDisabled
208
+ ? 'Strategy selector is temporarily disabled while launching the agent'
209
+ : 'Choose a strategy', children: [_jsx(Box, { as: "label", sx: { fontSize: 1, fontWeight: 'bold' }, children: "Strategy" }), _jsx(Box, { as: "select", value: selectedStrategyId, onChange: (e) => setSelectedStrategyId(e.target.value), disabled: isStrategySelectorDisabled, "aria-label": "Strategy specification", sx: {
210
210
  px: 2,
211
211
  py: '6px',
212
212
  fontSize: 1,
@@ -215,7 +215,7 @@ export function AgentLoopExampleInner({ serviceManager, }) {
215
215
  borderColor: 'border.default',
216
216
  bg: 'canvas.default',
217
217
  color: 'fg.default',
218
- }, children: loops.map(loop => (_jsxs("option", { value: loop.id, children: [loop.emoji, " ", loop.name] }, loop.id))) })] })] }), _jsx(LoopSummary, { loop: selectedLoop })] }), _jsx(Box, { sx: {
218
+ }, children: strategies.map(strategy => (_jsxs("option", { value: strategy.id, children: [strategy.emoji, " ", strategy.name] }, strategy.id))) })] })] }), _jsx(StrategySummary, { strategy: selectedStrategy })] }), _jsx(Box, { sx: {
219
219
  flex: 1,
220
220
  display: 'flex',
221
221
  overflow: 'hidden',
@@ -227,7 +227,7 @@ export function AgentLoopExampleInner({ serviceManager, }) {
227
227
  borderColor: 'border.default',
228
228
  borderRadius: 2,
229
229
  overflow: 'hidden',
230
- }, children: _jsx(NotebookUI, { serviceManager: serviceManager }) }) })] }), effectiveReady && (_jsx(ChatSidebar, { title: `${selectedLoop.emoji} ${selectedLoop.name}`, protocol: protocolConfig, position: "right", width: 400, clickOutsideToClose: false, showNewChatButton: true, showClearButton: true, showSettingsButton: true, defaultOpen: true, panelProps: {
230
+ }, children: _jsx(NotebookUI, { serviceManager: serviceManager }) }) })] }), effectiveReady && (_jsx(ChatSidebar, { title: `${selectedStrategy.emoji} ${selectedStrategy.name}`, protocol: protocolConfig, position: "right", width: 400, clickOutsideToClose: false, showNewChatButton: true, showClearButton: true, showSettingsButton: true, defaultOpen: true, panelProps: {
231
231
  protocol: protocolConfig,
232
232
  frontendTools: chatFrontendTools,
233
233
  useStore: false,
@@ -250,7 +250,7 @@ export function AgentLoopExampleInner({ serviceManager, }) {
250
250
  /**
251
251
  * Main example component with Jupyter provider wrapper.
252
252
  */
253
- export function AgentLoopExample({ serviceManager, }) {
254
- return (_jsx(ThemedProvider, { children: _jsx(AgentLoopExampleInner, { serviceManager: serviceManager }) }));
253
+ export function AgentStrategyExample({ serviceManager, }) {
254
+ return (_jsx(ThemedProvider, { children: _jsx(AgentStrategyExampleInner, { serviceManager: serviceManager }) }));
255
255
  }
256
- export default AgentLoopExample;
256
+ export default AgentStrategyExample;
@@ -111,7 +111,7 @@ export const EXAMPLE_ENTRIES = [
111
111
  // `owns-sandbox-control`: the shell is pinned to the browser sandbox, so
112
112
  // the page's selector would offer three targets it cannot move to.
113
113
  ['example', 'loop', 'shell', 'prompt', 'editors', 'owns-sandbox-control']),
114
- makeEntry('AgentLoopExample', () => import('./AgentLoopExample'), 'Define and launch an agent execution loop (observe/think/act/evaluate) over a live notebook, driven by generic loop specs.', ['example', 'agent', 'loop', 'notebook', 'agentspecs']),
114
+ makeEntry('AgentStrategyExample', () => import('./AgentStrategyExample'), 'Define and launch an agent reasoning strategy (a control loop: observe/think/act/evaluate) over a live notebook, driven by generic strategy specs.', ['example', 'agent', 'strategy', 'notebook', 'agentspecs']),
115
115
  makeEntry('AgentToolApprovalsExample', () => import('./AgentToolApprovalsExample'), 'Tool approval workflows and manual decisions.'),
116
116
  makeEntry('AgentMemoryExample', () => import('./AgentMemoryExample'), 'Memory-aware conversation and retrieval example.'),
117
117
  makeEntry('AgentSkillsExample', () => import('./AgentSkillsExample'), 'Skills discovery, execution, and monitoring example.'),
@@ -28,7 +28,7 @@ export { default as AgentEvalsExample } from './AgentEvalsExample';
28
28
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
29
29
  export { default as AgentHooksExample } from './AgentHooksExample';
30
30
  export { default as AgentInferenceProviderExample } from './AgentInferenceProviderExample';
31
- export { default as AgentLoopExample } from './AgentLoopExample';
31
+ export { default as AgentStrategyExample } from './AgentStrategyExample';
32
32
  export { default as AgentToolApprovalsExample } from './AgentToolApprovalsExample';
33
33
  export { default as AgentMemoryExample } from './AgentMemoryExample';
34
34
  export { default as AgentOtelExample } from './AgentOtelExample';
@@ -34,7 +34,7 @@ export { default as AgentEvalsExample } from './AgentEvalsExample';
34
34
  export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
35
35
  export { default as AgentHooksExample } from './AgentHooksExample';
36
36
  export { default as AgentInferenceProviderExample } from './AgentInferenceProviderExample';
37
- export { default as AgentLoopExample } from './AgentLoopExample';
37
+ export { default as AgentStrategyExample } from './AgentStrategyExample';
38
38
  export { default as AgentToolApprovalsExample } from './AgentToolApprovalsExample';
39
39
  export { default as AgentMemoryExample } from './AgentMemoryExample';
40
40
  export { default as AgentOtelExample } from './AgentOtelExample';
@@ -86,7 +86,7 @@ const ANONYMOUS_EXAMPLES = new Set([
86
86
  ]);
87
87
  const getExampleGroup = (id) => {
88
88
  if (id === 'AgentspecsExample' ||
89
- id === 'AgentLoopExample' ||
89
+ id === 'AgentStrategyExample' ||
90
90
  id === 'LoopWorkspaceExample' ||
91
91
  id === 'LoopShellExample' ||
92
92
  id === 'DecksAgent') {
@@ -1068,12 +1068,12 @@ const ExampleAppThemed = ({ selectedExample, isChangingExample, error, ExampleCo
1068
1068
  for (const [groupName, examples] of groups) {
1069
1069
  examples.sort((left, right) => {
1070
1070
  if (groupName === 'Loop') {
1071
- // The shells first, most naked first; then the loop that drives a
1071
+ // The shells first, most naked first; then the strategy that drives a
1072
1072
  // notebook; then the library of specs behind them all.
1073
1073
  const LOOP_ORDER = [
1074
1074
  'LoopShellExample',
1075
1075
  'LoopWorkspaceExample',
1076
- 'AgentLoopExample',
1076
+ 'AgentStrategyExample',
1077
1077
  'AgentspecsExample',
1078
1078
  ];
1079
1079
  const loopOrder = (id) => {
@@ -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: {},
@@ -32,7 +32,7 @@ export const DECKS_FRONTEND_TOOL_SPEC_0_0_1 = {
32
32
  'decks_present',
33
33
  'decks_print',
34
34
  ],
35
- icon: 'project',
35
+ icon: '@primer/octicons-react:project',
36
36
  emoji: '🃏',
37
37
  };
38
38
  export const JUPYTER_NOTEBOOK_EDIT_FRONTEND_TOOL_SPEC_0_0_1 = {
@@ -50,7 +50,7 @@ export const JUPYTER_NOTEBOOK_EDIT_FRONTEND_TOOL_SPEC_0_0_1 = {
50
50
  'runCell',
51
51
  'executeCode',
52
52
  ],
53
- icon: 'notebook',
53
+ icon: '@datalayer/icons-react:jupyter',
54
54
  emoji: '📓',
55
55
  };
56
56
  export const JUPYTER_NOTEBOOK_PROPOSE_FRONTEND_TOOL_SPEC_0_0_1 = {
@@ -61,7 +61,7 @@ export const JUPYTER_NOTEBOOK_PROPOSE_FRONTEND_TOOL_SPEC_0_0_1 = {
61
61
  tags: ['frontend', 'notebook', 'propose'],
62
62
  enabled: true,
63
63
  toolset: ['readCell', 'readAllCells', 'proposeCellUpdate', 'runCell'],
64
- icon: 'notebook',
64
+ icon: '@datalayer/icons-react:jupyter',
65
65
  emoji: '📓',
66
66
  };
67
67
  export const JUPYTER_NOTEBOOK_READ_FRONTEND_TOOL_SPEC_0_0_1 = {
@@ -72,7 +72,7 @@ export const JUPYTER_NOTEBOOK_READ_FRONTEND_TOOL_SPEC_0_0_1 = {
72
72
  tags: ['frontend', 'notebook', 'read'],
73
73
  enabled: true,
74
74
  toolset: ['readCell', 'readAllCells'],
75
- icon: 'notebook',
75
+ icon: '@datalayer/icons-react:jupyter',
76
76
  emoji: '📓',
77
77
  };
78
78
  export const JUPYTER_NOTEBOOK_FRONTEND_TOOL_SPEC_0_0_1 = {
@@ -83,7 +83,7 @@ export const JUPYTER_NOTEBOOK_FRONTEND_TOOL_SPEC_0_0_1 = {
83
83
  tags: ['frontend', 'notebook', 'jupyter-server'],
84
84
  enabled: true,
85
85
  toolset: 'all',
86
- icon: 'notebook',
86
+ icon: '@datalayer/icons-react:jupyter',
87
87
  emoji: '📓',
88
88
  };
89
89
  export const LEXICAL_DOCUMENT_FRONTEND_TOOL_SPEC_0_0_1 = {
@@ -94,7 +94,7 @@ export const LEXICAL_DOCUMENT_FRONTEND_TOOL_SPEC_0_0_1 = {
94
94
  tags: ['frontend', 'document', 'lexical', 'drawing', 'diagram'],
95
95
  enabled: true,
96
96
  toolset: 'all',
97
- icon: 'file',
97
+ icon: '@primer/octicons-react:file',
98
98
  emoji: '📄',
99
99
  };
100
100
  // ============================================================================