@datalayer/agent-runtimes 1.3.74 → 1.3.78

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 (143) hide show
  1. package/lib/agents/AgentDetails.d.ts +16 -2
  2. package/lib/agents/AgentDetails.js +381 -272
  3. package/lib/chat/ChatFloating.d.ts +27 -1
  4. package/lib/chat/ChatFloating.js +97 -13
  5. package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
  6. package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
  7. package/lib/chat/assistant/AssistantContextMenu.d.ts +67 -0
  8. package/lib/chat/assistant/AssistantContextMenu.js +68 -0
  9. package/lib/chat/assistant/AssistantStage.d.ts +144 -4
  10. package/lib/chat/assistant/AssistantStage.js +280 -10
  11. package/lib/chat/assistant/BalloonParts.d.ts +33 -5
  12. package/lib/chat/assistant/BalloonParts.js +66 -44
  13. package/lib/chat/assistant/BalloonVisual.d.ts +10 -2
  14. package/lib/chat/assistant/BalloonVisual.js +4 -3
  15. package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
  16. package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
  17. package/lib/chat/assistant/SpeechBalloon.d.ts +54 -6
  18. package/lib/chat/assistant/SpeechBalloon.js +73 -22
  19. package/lib/chat/assistant/assistantDetails.d.ts +84 -0
  20. package/lib/chat/assistant/assistantDetails.js +140 -0
  21. package/lib/chat/assistant/formats/files.d.ts +23 -0
  22. package/lib/chat/assistant/formats/files.js +48 -0
  23. package/lib/chat/assistant/formats/index.d.ts +2 -0
  24. package/lib/chat/assistant/formats/index.js +1 -0
  25. package/lib/chat/assistant/state.d.ts +7 -14
  26. package/lib/chat/assistant/state.js +21 -20
  27. package/lib/chat/assistant/toolLine.d.ts +16 -0
  28. package/lib/chat/assistant/toolLine.js +46 -0
  29. package/lib/chat/base/ChatBase.js +71 -1
  30. package/lib/chat/base/loopMessage.d.ts +37 -0
  31. package/lib/chat/base/loopMessage.js +37 -0
  32. package/lib/chat/indicators/TypingDots.d.ts +13 -0
  33. package/lib/chat/indicators/TypingDots.js +21 -0
  34. package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
  35. package/lib/chat/messages/ChatMarkdown.js +18 -0
  36. package/lib/chat/messages/ChatMessageList.d.ts +8 -1
  37. package/lib/chat/messages/ChatMessageList.js +25 -33
  38. package/lib/chat/styles/streamdownStyles.js +3 -1
  39. package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
  40. package/lib/chat/tools/ToolCallDisplay.js +8 -7
  41. package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
  42. package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
  43. package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
  44. package/lib/components/inspector/AgentInspector.d.ts +59 -0
  45. package/lib/components/inspector/AgentInspector.js +220 -0
  46. package/lib/components/inspector/AgentInspectorDialog.d.ts +19 -0
  47. package/lib/components/inspector/AgentInspectorDialog.js +9 -0
  48. package/lib/components/inspector/JsonTree.d.ts +24 -0
  49. package/lib/components/inspector/JsonTree.js +89 -0
  50. package/lib/components/inspector/a2aInspect.d.ts +56 -0
  51. package/lib/components/inspector/a2aInspect.js +478 -0
  52. package/lib/components/inspector/agentInspector.d.ts +179 -0
  53. package/lib/components/inspector/agentInspector.js +267 -0
  54. package/lib/components/inspector/chatInspect.d.ts +38 -0
  55. package/lib/components/inspector/chatInspect.js +65 -0
  56. package/lib/components/inspector/classify.d.ts +19 -0
  57. package/lib/components/inspector/classify.js +42 -0
  58. package/lib/components/inspector/index.d.ts +14 -0
  59. package/lib/components/inspector/index.js +18 -0
  60. package/lib/components/teams/A2ATeamGraph.d.ts +69 -1
  61. package/lib/components/teams/A2ATeamGraph.js +277 -37
  62. package/lib/components/teams/NotebookPreviewView.js +9 -1
  63. package/lib/components/teams/TeamNotebook.d.ts +3 -1
  64. package/lib/components/teams/TeamNotebook.js +3 -3
  65. package/lib/components/teams/TeamNotebookView.js +15 -0
  66. package/lib/components/teams/jupyterThemeVariables.d.ts +9 -0
  67. package/lib/components/teams/jupyterThemeVariables.js +441 -0
  68. package/lib/components/teams/teamConnections.d.ts +9 -1
  69. package/lib/components/teams/teamConnections.js +20 -0
  70. package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
  71. package/lib/components/teams/teamNotebookKernel.js +17 -0
  72. package/lib/components/teams/useA2ATeam.d.ts +20 -3
  73. package/lib/components/teams/useA2ATeam.js +108 -23
  74. package/lib/examples/AgentA2ATeamExample.d.ts +13 -0
  75. package/lib/examples/AgentA2ATeamExample.js +67 -13
  76. package/lib/examples/AssistantExample.js +65 -26
  77. package/lib/examples/AssistantGalleryExample.js +2 -1
  78. package/lib/examples/exampleGroups.d.ts +1 -1
  79. package/lib/examples/exampleGroups.js +1 -1
  80. package/lib/examples/utils/assistantGallery.d.ts +7 -6
  81. package/lib/examples/utils/assistantGallery.js +23 -8
  82. package/lib/jupyter/exec/Python.d.ts +0 -1
  83. package/lib/jupyter/exec/Python.js +0 -32
  84. package/lib/jupyter/exec/Snippets.d.ts +4 -12
  85. package/lib/jupyter/exec/Snippets.js +0 -8
  86. package/lib/loop/apps/AppFeedback.js +3 -1
  87. package/lib/loop/apps/AppKept.d.ts +22 -0
  88. package/lib/loop/apps/AppKept.js +36 -0
  89. package/lib/loop/apps/AppRenderer.d.ts +11 -1
  90. package/lib/loop/apps/AppRenderer.js +22 -4
  91. package/lib/loop/apps/checks.d.ts +7 -0
  92. package/lib/loop/apps/checks.js +39 -0
  93. package/lib/loop/apps/index.d.ts +3 -0
  94. package/lib/loop/apps/index.js +3 -0
  95. package/lib/loop/apps/kept.d.ts +30 -0
  96. package/lib/loop/apps/kept.js +52 -0
  97. package/lib/loop/apps/records.d.ts +12 -4
  98. package/lib/loop/apps/records.js +13 -5
  99. package/lib/loop/apps/saved.d.ts +30 -0
  100. package/lib/loop/apps/saved.js +40 -0
  101. package/lib/loop/core/index.d.ts +17 -0
  102. package/lib/loop/core/index.js +1 -0
  103. package/lib/loop/embed/AppEmbed.d.ts +8 -1
  104. package/lib/loop/embed/AppEmbed.js +4 -4
  105. package/lib/loop/embed/LoopEmbed.d.ts +6 -1
  106. package/lib/loop/embed/LoopEmbed.js +4 -2
  107. package/lib/loop/embed/element.js +1 -1
  108. package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
  109. package/lib/loop/plugins/agents/browserService.d.ts +7 -1
  110. package/lib/loop/plugins/agents/browserService.js +4 -0
  111. package/lib/loop/plugins/agents/plugin.d.ts +16 -0
  112. package/lib/loop/plugins/agents/switchable.d.ts +3 -0
  113. package/lib/loop/plugins/agents/switchable.js +1 -0
  114. package/lib/loop/plugins/app-activity/index.js +3 -0
  115. package/lib/loop/plugins/app-computer/index.js +3 -0
  116. package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
  117. package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
  118. package/lib/loop/plugins/app-rules/index.js +3 -0
  119. package/lib/loop/plugins/assistant/index.d.ts +7 -2
  120. package/lib/loop/plugins/assistant/index.js +58 -4
  121. package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
  122. package/lib/loop/plugins/assistant-characters/index.js +17 -0
  123. package/lib/loop/plugins/chat/ChatView.js +4 -1
  124. package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
  125. package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
  126. package/lib/loop/presets.d.ts +4 -0
  127. package/lib/loop/presets.js +3 -1
  128. package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
  129. package/lib/loop/shell/LoopWorkspace.js +5 -4
  130. package/lib/loop/shell/SidebarRail.d.ts +53 -0
  131. package/lib/loop/shell/SidebarRail.js +74 -0
  132. package/lib/loop/shell/index.d.ts +2 -0
  133. package/lib/loop/shell/index.js +2 -0
  134. package/lib/loop/shell/paneMotion.d.ts +21 -0
  135. package/lib/loop/shell/paneMotion.js +37 -0
  136. package/lib/runtimes/browser/a2aPeer.d.ts +4 -1
  137. package/lib/specs/apps.js +8 -5
  138. package/lib/specs/uiPlugins.js +529 -0
  139. package/lib/types/agentspecs.d.ts +5 -3
  140. package/lib/types/chat.d.ts +12 -0
  141. package/package.json +2 -2
  142. package/scripts/codegen/generate_jupyter_theme_variables.mjs +57 -0
  143. package/scripts/codegen/generate_ui_plugins.py +112 -2
@@ -17,6 +17,13 @@ type ContributionsReader = {
17
17
  };
18
18
  export declare const ASSISTANT_CHARACTERS_PLUGIN_NAME = "@datalayer/loop-plugin-assistant-characters";
19
19
  export declare const AssistantCharactersPlugin: ReactorPlugin<Record<string, never>, unknown, unknown>;
20
+ /**
21
+ * A plugin contributing one character (LOOP T-24, T-26): a character a
22
+ * person brought — a `.acs` or a clippy.js character read in their page —
23
+ * offered beside the contributed ones under the id it is chosen by. The
24
+ * character stays in the page: the plugin only says it is there.
25
+ */
26
+ export declare function defineAssistantCharacterPlugin(id: string, character: AssistantCharacter | AssistantCharacterData): ReactorPlugin<Record<string, never>, unknown, unknown>;
20
27
  /** The characters the enabled plugins contribute, in contribution order. */
21
28
  export declare function assistantCharactersOf(reactor: ContributionsReader): {
22
29
  id: string;
@@ -20,6 +20,23 @@ export const AssistantCharactersPlugin = definePlugin({
20
20
  octicon: 'paperclip',
21
21
  contributes: ASSISTANT_CHARACTERS.map(character => contribution(LoopAssistantCharacter, { id: character.id, character }, { id: character.id })),
22
22
  });
23
+ /**
24
+ * A plugin contributing one character (LOOP T-24, T-26): a character a
25
+ * person brought — a `.acs` or a clippy.js character read in their page —
26
+ * offered beside the contributed ones under the id it is chosen by. The
27
+ * character stays in the page: the plugin only says it is there.
28
+ */
29
+ export function defineAssistantCharacterPlugin(id, character) {
30
+ return definePlugin({
31
+ name: `${ASSISTANT_CHARACTERS_PLUGIN_NAME}-${id}`,
32
+ displayName: `Assistant character: ${character.name}`,
33
+ description: `${character.name}, a character for the floating assistant.`,
34
+ octicon: 'paperclip',
35
+ contributes: [
36
+ contribution(LoopAssistantCharacter, { id, character }, { id }),
37
+ ],
38
+ });
39
+ }
23
40
  /** The characters the enabled plugins contribute, in contribution order. */
24
41
  export function assistantCharactersOf(reactor) {
25
42
  return reactor
@@ -1146,7 +1146,10 @@ export default function ChatView({ workspace }) {
1146
1146
  */
1147
1147
  const visitors = reactor.getConfig(AGENTS_PLUGIN_NAME)?.datalayerVisitors;
1148
1148
  const visitor = useVisitorToken(visitors);
1149
- const iamToken = visitors ? visitor.token : memberToken;
1149
+ // An embedded application's own token, for a visitor the platform does
1150
+ // not know (LOOP R-20): it runs a session of its deployment, and no more.
1151
+ const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
1152
+ const iamToken = visitors ? visitor.token : embedToken || memberToken;
1150
1153
  const protocol = useMemo(() => inPage
1151
1154
  ? browserProtocolConfig({
1152
1155
  agentId: member?.specId ?? agentId,
@@ -10,7 +10,8 @@
10
10
  *
11
11
  * The hairline is a separator a person can move with the pointer or the
12
12
  * keyboard (arrows by a step, Home and End to the bounds); the share it
13
- * leaves the conversation is kept while the workspace is mounted.
13
+ * leaves the conversation is kept while the workspace is mounted. The page
14
+ * opens at the theme's pace (T-10, `paneOpening`).
14
15
  *
15
16
  * @module loop/plugins/page-layout/SplitLayout
16
17
  */
@@ -1,6 +1,7 @@
1
1
  import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useRef, useState } from 'react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
+ import { paneOpening } from '../../shell/paneMotion';
4
5
  /** The conversation's share of the width when the split opens. */
5
6
  export const SPLIT_DEFAULT_SHARE = 0.4;
6
7
  /** The narrowest and widest the conversation may be made. */
@@ -116,6 +117,9 @@ export function SplitLayout({ editors, hasEditor, transcript, prompt, chips, pic
116
117
  minWidth: 0,
117
118
  minHeight: 0,
118
119
  display: 'flex',
120
+ // The page opening beside the conversation, at the theme's
121
+ // pace (T-10): no motion where the theme sets none.
122
+ ...paneOpening('right'),
119
123
  }
120
124
  : // Mounted but out of sight: the editors keep their place.
121
125
  {
@@ -139,6 +139,10 @@ export type LoopPresetOptions = {
139
139
  datalayerCreatePayload?: Record<string, unknown>;
140
140
  /** A conversation without an account, on the visitors' runtime (LOOP R-30). */
141
141
  datalayerVisitors?: AgentsConfig['datalayerVisitors'];
142
+ /** A deployment's agent, already on the runtime it is kept on (LOOP R-33). */
143
+ datalayerKept?: AgentsConfig['datalayerKept'];
144
+ /** An embed token: the embedded application's session, and nothing else (LOOP R-20). */
145
+ embedToken?: string;
142
146
  /** The target is the host's to fix, even with the agent control hidden. */
143
147
  targetFixed?: boolean;
144
148
  /** The theme the conversation wears, by name; the person's 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, datalayerVisitors, targetFixed, themeVariant, themeOverrides, colorMode, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, conversation = 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, datalayerVisitors, datalayerKept, embedToken, targetFixed, themeVariant, themeOverrides, colorMode, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, conversation = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
56
56
  const defaultEditor = askedEditor ?? (editors ? 'notebook' : 'none');
57
57
  // A workspace without a conversation: the theme, the shell's points, and
58
58
  // the host's chrome; its view is a host's plugin's.
@@ -112,6 +112,8 @@ export function loopPlugins(options = {}) {
112
112
  datalayerAgentSpecId,
113
113
  datalayerCreatePayload,
114
114
  datalayerVisitors,
115
+ datalayerKept,
116
+ embedToken,
115
117
  targetFixed,
116
118
  showAgentVariants,
117
119
  showAgentSummary: agentSummary,
@@ -117,6 +117,13 @@ export type LoopWorkspaceProps = {
117
117
  * and no row is drawn in its place.
118
118
  */
119
119
  headerContainer?: HTMLElement | null;
120
+ /**
121
+ * The sidebar as a rail (LOOP T-07): a narrow column of line icons, one per
122
+ * panel, and the chosen panel beside it — rather than every panel stacked
123
+ * in one column. False by default; an application's shell asks for it. In
124
+ * a `panel` layout the sidebar stays a stack under the view.
125
+ */
126
+ sidebarRail?: boolean;
120
127
  };
121
128
  /** Build the platform for a set of plugins. */
122
129
  export declare function buildLoopReactor(extensions: PluginRef[]): ReactorPlatform;
@@ -7,26 +7,27 @@ import { ReactorSlot, ReactorViewHost, useContributions, useReactorEvent, useSlo
7
7
  import { LoopCommand, LoopSlots, LoopViewType, canOpenView, createPromptChannel, parseCommand, loopSurfaceId, } from '../core';
8
8
  import { SIDEBAR_WIDTH } from '../plugins/plugins-panel';
9
9
  import { ViewSwitcher } from './ViewSwitcher';
10
+ import { SidebarRail } from './SidebarRail';
10
11
  /** Build the platform for a set of plugins. */
11
12
  export function buildLoopReactor(extensions) {
12
13
  return buildReactorFromPlugins(extensions);
13
14
  }
14
15
  export function LoopWorkspace(props) {
15
- const { serverUrl, agentId, conversationId, model, sandbox = { state: 'idle' }, extensions = [], reactor: providedReactor, manageReactor = true, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, } = props;
16
+ const { serverUrl, agentId, conversationId, model, sandbox = { state: 'idle' }, extensions = [], reactor: providedReactor, manageReactor = true, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, sidebarRail = false, } = props;
16
17
  // Building the platform is a one-time act: rebuilding it on every render
17
18
  // would restart every plugin.
18
19
  const reactor = useMemo(() => providedReactor ?? buildLoopReactor(extensions),
19
20
  // eslint-disable-next-line react-hooks/exhaustive-deps
20
21
  [providedReactor]);
21
22
  useReactor(reactor, { autoStart: manageReactor });
22
- return (_jsx(WorkspaceBody, { serverUrl: serverUrl, agentId: agentId, conversationId: conversationId, model: model, initialSandbox: sandbox, initialViewType: initialViewType, onSend: onSend, layout: layout, showViewSelector: showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer }));
23
+ return (_jsx(WorkspaceBody, { serverUrl: serverUrl, agentId: agentId, conversationId: conversationId, model: model, initialSandbox: sandbox, initialViewType: initialViewType, onSend: onSend, layout: layout, showViewSelector: showViewSelector, showHeader: showHeader, chatHeaderActions: chatHeaderActions, headerActions: headerActions, headerContainer: headerContainer, sidebarRail: sidebarRail }));
23
24
  }
24
25
  /**
25
26
  * Split from `LoopWorkspace` so the hooks below run with the platform already
26
27
  * registered — `useContributions` needs a reactor in the store, and `useReactor`
27
28
  * registers it during the parent's render.
28
29
  */
29
- function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, model, initialSandbox, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, }) {
30
+ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, model, initialSandbox, initialViewType, onSend, layout = 'page', showViewSelector = true, showHeader = true, chatHeaderActions, headerActions, headerContainer, sidebarRail = false, }) {
30
31
  /*
31
32
  * Which agent the session is talking to.
32
33
  *
@@ -210,7 +211,7 @@ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, mod
210
211
  minWidth: 0,
211
212
  minHeight: 0,
212
213
  position: 'relative',
213
- }, children: _jsx(ReactorViewHost, { point: LoopViewType, active: effectiveViewType, props: { viewType: effectiveViewType, workspace }, fallback: _jsx(Centered, { children: "Loading\u2026" }), empty: _jsx(Centered, { children: "No view is available yet." }), errorFallback: error => (_jsxs(Centered, { children: ["This view failed to load: ", error.message] })) }) }), sidebar.length > 0 ? (_jsx(Box, { as: "aside", sx: {
214
+ }, children: _jsx(ReactorViewHost, { point: LoopViewType, active: effectiveViewType, props: { viewType: effectiveViewType, workspace }, fallback: _jsx(Centered, { children: "Loading\u2026" }), empty: _jsx(Centered, { children: "No view is available yet." }), errorFallback: error => (_jsxs(Centered, { children: ["This view failed to load: ", error.message] })) }) }), sidebar.length > 0 && sidebarRail && !compact ? (_jsx(SidebarRail, { components: sidebar, props: { workspace }, width: SIDEBAR_WIDTH })) : sidebar.length > 0 ? (_jsx(Box, { as: "aside", sx: {
214
215
  flex: '0 0 auto',
215
216
  // Wide enough for a plugin's name, its description and a switch
216
217
  // on one line. The panel truncates to the same number, and takes
@@ -0,0 +1,53 @@
1
+ /**
2
+ * The rail (LOOP T-07): the workspace's sidebar as a narrow column of line
3
+ * icons, one per panel, and the one panel chosen beside it.
4
+ *
5
+ * The sidebar stacks every panel its plugins contribute — an application's
6
+ * rules and approvals, its activity, its computer — one under the other.
7
+ * With the rail, each is an icon: one weight, no colour, the chosen one on a
8
+ * grey rounded square, as the reference screens draw their rail. A click
9
+ * shows that panel, a click on the chosen one closes the column, and the
10
+ * panel opens at the theme's pace (T-10).
11
+ *
12
+ * A panel names itself on the rail by the `rail` its plugin gives its
13
+ * component (`LoopSidebarComponent`); without one it is shown by its id
14
+ * under a generic icon. Every panel stays mounted while another is shown,
15
+ * so a feed keeps its place and a live computer its connection.
16
+ *
17
+ * @module loop/shell/SidebarRail
18
+ */
19
+ import type { ElementType, JSX } from 'react';
20
+ import type { ReactorSlotComponent } from '@datalayer/reactor/react';
21
+ /** How a sidebar panel shows on the rail. */
22
+ export type LoopSidebarRail = {
23
+ /** Its name, said to a screen reader and shown on hover. */
24
+ label: string;
25
+ /** A line icon (an octicon). */
26
+ icon: ElementType;
27
+ };
28
+ /** A component of the sidebar slot, with how it shows on the rail. */
29
+ export type LoopSidebarComponent = ReactorSlotComponent & {
30
+ rail?: LoopSidebarRail;
31
+ };
32
+ /** One entry of the rail. */
33
+ export type RailItem = {
34
+ id: string;
35
+ label: string;
36
+ icon: ElementType;
37
+ };
38
+ /** The rail's entries, in the slot's order. */
39
+ export declare function railItemsOf(components: ReadonlyArray<LoopSidebarComponent>): RailItem[];
40
+ /** What a click on an entry leaves chosen: it, or nothing when it was. */
41
+ export declare function railChoiceAfter(chosen: string | null, clicked: string): string | null;
42
+ /** The rail's width: one icon and its air. */
43
+ export declare const RAIL_WIDTH = 48;
44
+ export type SidebarRailProps = {
45
+ /** The sidebar's components, as the slot gives them. */
46
+ components: ReadonlyArray<LoopSidebarComponent>;
47
+ /** What each panel is rendered with. */
48
+ props: Record<string, unknown>;
49
+ /** The panel's width when open. */
50
+ width: number;
51
+ };
52
+ export declare function SidebarRail({ components, props, width, }: SidebarRailProps): JSX.Element;
53
+ export default SidebarRail;
@@ -0,0 +1,74 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useState } from 'react';
3
+ import { Box } from '@datalayer/primer-addons';
4
+ import { SidebarExpandIcon } from '@primer/octicons-react';
5
+ import { paneOpening } from './paneMotion';
6
+ /** The rail's entries, in the slot's order. */
7
+ export function railItemsOf(components) {
8
+ return components.map(component => ({
9
+ id: component.id,
10
+ label: component.rail?.label ?? component.id,
11
+ icon: component.rail?.icon ?? SidebarExpandIcon,
12
+ }));
13
+ }
14
+ /** What a click on an entry leaves chosen: it, or nothing when it was. */
15
+ export function railChoiceAfter(chosen, clicked) {
16
+ return chosen === clicked ? null : clicked;
17
+ }
18
+ /** The rail's width: one icon and its air. */
19
+ export const RAIL_WIDTH = 48;
20
+ export function SidebarRail({ components, props, width, }) {
21
+ const items = railItemsOf(components);
22
+ // The first panel open to start with: what the column is for.
23
+ const [chosen, setChosen] = useState(items[0]?.id ?? null);
24
+ // A panel that left the slot is no longer chosen.
25
+ const open = items.some(item => item.id === chosen) ? chosen : null;
26
+ return (_jsxs(Box, { as: "aside", "data-loop-rail-sidebar": "", sx: { flex: '0 0 auto', display: 'flex', minHeight: 0 }, children: [_jsx(Box, { "data-loop-rail-panel": open ?? undefined, sx: {
27
+ display: open ? 'block' : 'none',
28
+ width: `${width}px`,
29
+ minWidth: 0,
30
+ borderLeft: 'var(--theme-hairline, 1px) solid',
31
+ borderColor: 'border.default',
32
+ overflowY: 'auto',
33
+ px: 3,
34
+ py: 3,
35
+ }, children: components.map(component => {
36
+ const { Component } = component;
37
+ const shown = component.id === open;
38
+ return (_jsx(Box, { id: `loop-rail-panel-${component.id}`, role: "region", "aria-label": railItemsOf([component])[0].label, hidden: !shown, sx: shown ? paneOpening('right') : undefined, children: _jsx(Component, { ...props }) }, component.id));
39
+ }) }), _jsx(Box, { as: "nav", "aria-label": "Panels", sx: {
40
+ width: `${RAIL_WIDTH}px`,
41
+ flex: `0 0 ${RAIL_WIDTH}px`,
42
+ display: 'flex',
43
+ flexDirection: 'column',
44
+ alignItems: 'center',
45
+ gap: 1,
46
+ py: 2,
47
+ borderLeft: 'var(--theme-hairline, 1px) solid',
48
+ borderColor: 'border.default',
49
+ }, children: items.map(item => {
50
+ const Icon = item.icon;
51
+ const active = item.id === open;
52
+ return (_jsx(Box, { as: "button", type: "button", "data-loop-rail-item": item.id, "aria-label": item.label, title: item.label, "aria-pressed": active, "aria-controls": `loop-rail-panel-${item.id}`, onClick: () => setChosen(railChoiceAfter(open, item.id)), sx: {
53
+ width: 32,
54
+ height: 32,
55
+ display: 'inline-flex',
56
+ alignItems: 'center',
57
+ justifyContent: 'center',
58
+ p: 0,
59
+ border: 'none',
60
+ borderRadius: 2,
61
+ cursor: 'pointer',
62
+ // One weight, no colour: the ink, quieter when not chosen.
63
+ color: active ? 'fg.default' : 'fg.muted',
64
+ bg: active ? 'neutral.muted' : 'transparent',
65
+ '&:hover': { color: 'fg.default', bg: 'neutral.subtle' },
66
+ '&:focus-visible': {
67
+ outline: '2px solid',
68
+ outlineColor: 'var(--focus-outlineColor, #0969da)',
69
+ outlineOffset: '1px',
70
+ },
71
+ }, children: _jsx(Icon, { size: 16 }) }, item.id));
72
+ }) })] }));
73
+ }
74
+ export default SidebarRail;
@@ -8,3 +8,5 @@ export { ViewSwitcher, type ViewSwitcherProps } from './ViewSwitcher';
8
8
  export { PluginToggles, type PluginTogglesProps } from './PluginToggles';
9
9
  export { fullScreenNode, useWorkspaceFullScreen, type WorkspaceFullScreen, } from './useWorkspaceFullScreen';
10
10
  export { WorkspaceFullScreenAction } from './WorkspaceFullScreenAction';
11
+ export { SidebarRail, RAIL_WIDTH, railChoiceAfter, railItemsOf, type LoopSidebarComponent, type LoopSidebarRail, type RailItem, type SidebarRailProps, } from './SidebarRail';
12
+ export { paneOpening, paneOpenAnimation, paneOpenKeyframes, } from './paneMotion';
@@ -12,3 +12,5 @@ export { ViewSwitcher } from './ViewSwitcher';
12
12
  export { PluginToggles } from './PluginToggles';
13
13
  export { fullScreenNode, useWorkspaceFullScreen, } from './useWorkspaceFullScreen';
14
14
  export { WorkspaceFullScreenAction } from './WorkspaceFullScreenAction';
15
+ export { SidebarRail, RAIL_WIDTH, railChoiceAfter, railItemsOf, } from './SidebarRail';
16
+ export { paneOpening, paneOpenAnimation, paneOpenKeyframes, } from './paneMotion';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * A pane opening, at the theme's pace (LOOP T-10): the third of the three
3
+ * things that move — a message arriving, a status changing, a pane opening.
4
+ *
5
+ * Read from the theme's tokens, `--theme-motion-pane` and
6
+ * `--theme-motion-easing`: a theme that sets no motion (every theme but
7
+ * `loop`) gives `0ms`, and nothing moves; nor does anything when the person
8
+ * asked for reduced motion.
9
+ *
10
+ * @module loop/shell/paneMotion
11
+ */
12
+ /** The keyframes' name for a pane opening from a side: one per side. */
13
+ export declare const paneOpenKeyframes: (from: "left" | "right") => string;
14
+ /** The CSS `animation` of a pane opening from a side. */
15
+ export declare const paneOpenAnimation: (from: "left" | "right") => string;
16
+ /**
17
+ * The styles of a pane that opens from a side: it comes in a little from
18
+ * that side as it appears. Applied to an element when it is shown (mounted,
19
+ * or out of `display: none`), which is when a CSS animation starts.
20
+ */
21
+ export declare function paneOpening(from: 'left' | 'right'): Record<string, unknown>;
@@ -0,0 +1,37 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A pane opening, at the theme's pace (LOOP T-10): the third of the three
7
+ * things that move — a message arriving, a status changing, a pane opening.
8
+ *
9
+ * Read from the theme's tokens, `--theme-motion-pane` and
10
+ * `--theme-motion-easing`: a theme that sets no motion (every theme but
11
+ * `loop`) gives `0ms`, and nothing moves; nor does anything when the person
12
+ * asked for reduced motion.
13
+ *
14
+ * @module loop/shell/paneMotion
15
+ */
16
+ /** The keyframes' name for a pane opening from a side: one per side. */
17
+ export const paneOpenKeyframes = (from) => from === 'left' ? 'loopPaneOpenLeft' : 'loopPaneOpenRight';
18
+ /** The CSS `animation` of a pane opening from a side. */
19
+ export const paneOpenAnimation = (from) => `${paneOpenKeyframes(from)} var(--theme-motion-pane, 0ms) var(--theme-motion-easing, ease) both`;
20
+ /**
21
+ * The styles of a pane that opens from a side: it comes in a little from
22
+ * that side as it appears. Applied to an element when it is shown (mounted,
23
+ * or out of `display: none`), which is when a CSS animation starts.
24
+ */
25
+ export function paneOpening(from) {
26
+ const shift = from === 'left' ? '-12px' : '12px';
27
+ return {
28
+ animation: paneOpenAnimation(from),
29
+ [`@keyframes ${paneOpenKeyframes(from)}`]: {
30
+ from: { opacity: 0, transform: `translateX(${shift})` },
31
+ to: { opacity: 1, transform: 'none' },
32
+ },
33
+ '@media (prefers-reduced-motion: reduce)': {
34
+ animation: 'none',
35
+ },
36
+ };
37
+ }
@@ -43,7 +43,10 @@ export type ConnectA2APeerOptions = {
43
43
  url: string;
44
44
  /** A key granted to the peer's route, sent as a bearer token. */
45
45
  key?: string;
46
- /** The fetch to use, for tests and hosts that wrap the network. */
46
+ /**
47
+ * The fetch to use, for tests and hosts that wrap the network — such as
48
+ * the Agent Inspector's `inspectA2AFetch`, which records the traffic.
49
+ */
47
50
  fetch?: typeof globalThis.fetch;
48
51
  };
49
52
  /**
package/lib/specs/apps.js CHANGED
@@ -99,10 +99,11 @@ export const ACCOUNTING_APP_0_0_1 = {
99
99
  },
100
100
  ],
101
101
  verified: {
102
- live: [],
102
+ live: [
103
+ "Answered live over A2A on a developer's machine (2026-10-06), asked for the open customer invoices: it read the aged receivables from the Odoo books and said that the list of invoices had failed. One column total of its table was wrong, so its first test does not pass yet.",
104
+ ],
103
105
  recorded: [],
104
106
  unverified: [
105
- "It has not run against Odoo live over A2A: agent-runtimes' tests serve it with fasta2a in process, on a fake agent.",
106
107
  'Its tests have not been run as a set: no validation run is attached to it.',
107
108
  ],
108
109
  },
@@ -1788,7 +1789,7 @@ export const SALES_APP_0_0_1 = {
1788
1789
  live: [],
1789
1790
  recorded: [],
1790
1791
  unverified: [
1791
- 'It has not talked to Accounting live: the agent-runtimes example AgentA2ATeamExample runs it in the browser against an Accounting runtime that someone starts.',
1792
+ "It has not talked to Accounting live yet: a developer's example runs it in the browser, against an Accounting that someone starts.",
1792
1793
  'Its tests have not been run as a set: no validation run is attached to it.',
1793
1794
  ],
1794
1795
  },
@@ -2639,8 +2640,10 @@ export const APP_SOURCES = {
2639
2640
  },
2640
2641
  ],
2641
2642
  verified: {
2643
+ live: [
2644
+ "Answered live over A2A on a developer's machine (2026-10-06), asked for the open customer invoices: it read the aged receivables from the Odoo books and said that the list of invoices had failed. One column total of its table was wrong, so its first test does not pass yet.",
2645
+ ],
2642
2646
  unverified: [
2643
- "It has not run against Odoo live over A2A: agent-runtimes' tests serve it with fasta2a in process, on a fake agent.",
2644
2647
  'Its tests have not been run as a set: no validation run is attached to it.',
2645
2648
  ],
2646
2649
  },
@@ -3835,7 +3838,7 @@ export const APP_SOURCES = {
3835
3838
  ],
3836
3839
  verified: {
3837
3840
  unverified: [
3838
- 'It has not talked to Accounting live: the agent-runtimes example AgentA2ATeamExample runs it in the browser against an Accounting runtime that someone starts.',
3841
+ "It has not talked to Accounting live yet: a developer's example runs it in the browser, against an Accounting that someone starts.",
3839
3842
  'Its tests have not been run as a set: no validation run is attached to it.',
3840
3843
  ],
3841
3844
  },