@datalayer/agent-runtimes 1.3.75 → 1.3.79
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.
- package/lib/agents/AgentDetails.d.ts +16 -2
- package/lib/agents/AgentDetails.js +381 -272
- package/lib/chat/ChatFloating.d.ts +12 -2
- package/lib/chat/ChatFloating.js +48 -11
- package/lib/chat/assistant/AgentDetailsDialog.d.ts +21 -0
- package/lib/chat/assistant/AgentDetailsDialog.js +19 -0
- package/lib/chat/assistant/AssistantStage.d.ts +51 -5
- package/lib/chat/assistant/AssistantStage.js +60 -10
- package/lib/chat/assistant/BalloonParts.d.ts +18 -4
- package/lib/chat/assistant/BalloonParts.js +46 -41
- package/lib/chat/assistant/BalloonVisual.js +2 -1
- package/lib/chat/assistant/SandboxDetailsDialog.d.ts +44 -0
- package/lib/chat/assistant/SandboxDetailsDialog.js +214 -0
- package/lib/chat/assistant/SpeechBalloon.d.ts +8 -6
- package/lib/chat/assistant/SpeechBalloon.js +29 -30
- package/lib/chat/assistant/assistantDetails.d.ts +84 -0
- package/lib/chat/assistant/assistantDetails.js +140 -0
- package/lib/chat/assistant/formats/files.d.ts +23 -0
- package/lib/chat/assistant/formats/files.js +48 -0
- package/lib/chat/assistant/formats/index.d.ts +2 -0
- package/lib/chat/assistant/formats/index.js +1 -0
- package/lib/chat/assistant/state.d.ts +7 -14
- package/lib/chat/assistant/state.js +21 -20
- package/lib/chat/assistant/toolLine.d.ts +16 -0
- package/lib/chat/assistant/toolLine.js +46 -0
- package/lib/chat/base/ChatBase.js +20 -0
- package/lib/chat/base/loopMessage.d.ts +37 -0
- package/lib/chat/base/loopMessage.js +37 -0
- package/lib/chat/messages/ChatMarkdown.d.ts +16 -0
- package/lib/chat/messages/ChatMarkdown.js +18 -0
- package/lib/chat/messages/ChatMessageList.d.ts +8 -1
- package/lib/chat/messages/ChatMessageList.js +24 -18
- package/lib/chat/styles/streamdownStyles.js +3 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/ToolCallDisplay.js +8 -7
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +5 -11
- package/lib/components/code-sandboxes/CodeSandboxVariables.d.ts +8 -0
- package/lib/components/code-sandboxes/CodeSandboxVariables.js +17 -11
- package/lib/components/teams/A2ATeamGraph.d.ts +30 -3
- package/lib/components/teams/A2ATeamGraph.js +264 -41
- package/lib/components/teams/TeamNotebookView.js +15 -0
- package/lib/components/teams/teamNotebookKernel.d.ts +3 -0
- package/lib/components/teams/teamNotebookKernel.js +17 -0
- package/lib/components/teams/useA2ATeam.d.ts +10 -6
- package/lib/components/teams/useA2ATeam.js +29 -8
- package/lib/examples/AgentA2ATeamExample.js +6 -0
- package/lib/examples/AssistantExample.js +3 -24
- package/lib/examples/AssistantGalleryExample.js +2 -1
- package/lib/examples/exampleGroups.d.ts +1 -1
- package/lib/examples/exampleGroups.js +1 -1
- package/lib/examples/utils/assistantGallery.d.ts +7 -6
- package/lib/examples/utils/assistantGallery.js +23 -8
- package/lib/jupyter/exec/Python.d.ts +0 -1
- package/lib/jupyter/exec/Python.js +0 -32
- package/lib/jupyter/exec/Snippets.d.ts +4 -12
- package/lib/jupyter/exec/Snippets.js +0 -8
- package/lib/loop/apps/AppFeedback.js +3 -1
- package/lib/loop/apps/AppKept.d.ts +22 -0
- package/lib/loop/apps/AppKept.js +36 -0
- package/lib/loop/apps/AppRenderer.d.ts +11 -1
- package/lib/loop/apps/AppRenderer.js +21 -3
- package/lib/loop/apps/checks.d.ts +7 -0
- package/lib/loop/apps/checks.js +39 -0
- package/lib/loop/apps/index.d.ts +3 -0
- package/lib/loop/apps/index.js +3 -0
- package/lib/loop/apps/kept.d.ts +30 -0
- package/lib/loop/apps/kept.js +52 -0
- package/lib/loop/apps/records.d.ts +12 -4
- package/lib/loop/apps/records.js +13 -5
- package/lib/loop/apps/saved.d.ts +30 -0
- package/lib/loop/apps/saved.js +40 -0
- package/lib/loop/embed/AppEmbed.d.ts +8 -1
- package/lib/loop/embed/AppEmbed.js +4 -4
- package/lib/loop/embed/LoopEmbed.d.ts +6 -1
- package/lib/loop/embed/LoopEmbed.js +4 -2
- package/lib/loop/embed/element.js +1 -1
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +12 -6
- package/lib/loop/plugins/agents/browserService.d.ts +7 -1
- package/lib/loop/plugins/agents/browserService.js +4 -0
- package/lib/loop/plugins/agents/plugin.d.ts +16 -0
- package/lib/loop/plugins/agents/switchable.d.ts +3 -0
- package/lib/loop/plugins/agents/switchable.js +1 -0
- package/lib/loop/plugins/app-activity/index.js +3 -0
- package/lib/loop/plugins/app-computer/index.js +3 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.d.ts +5 -0
- package/lib/loop/plugins/app-rules/AppRulesCard.js +25 -6
- package/lib/loop/plugins/app-rules/index.js +3 -0
- package/lib/loop/plugins/assistant/index.d.ts +7 -2
- package/lib/loop/plugins/assistant/index.js +53 -4
- package/lib/loop/plugins/assistant-characters/index.d.ts +7 -0
- package/lib/loop/plugins/assistant-characters/index.js +17 -0
- package/lib/loop/plugins/chat/ChatView.js +4 -1
- package/lib/loop/plugins/page-layout/SplitLayout.d.ts +2 -1
- package/lib/loop/plugins/page-layout/SplitLayout.js +4 -0
- package/lib/loop/presets.d.ts +4 -0
- package/lib/loop/presets.js +3 -1
- package/lib/loop/shell/LoopWorkspace.d.ts +7 -0
- package/lib/loop/shell/LoopWorkspace.js +5 -4
- package/lib/loop/shell/SidebarRail.d.ts +53 -0
- package/lib/loop/shell/SidebarRail.js +74 -0
- package/lib/loop/shell/index.d.ts +2 -0
- package/lib/loop/shell/index.js +2 -0
- package/lib/loop/shell/paneMotion.d.ts +21 -0
- package/lib/loop/shell/paneMotion.js +37 -0
- package/lib/specs/apps.js +8 -5
- package/lib/specs/uiPlugins.js +529 -0
- package/lib/types/agentspecs.d.ts +5 -3
- package/lib/types/chat.d.ts +5 -0
- package/package.json +2 -2
- package/scripts/codegen/generate_ui_plugins.py +112 -2
|
@@ -13,8 +13,10 @@ import { ASSISTANT_WORDS, assistantStateOf, keepAway, keptAway, latestSaying, }
|
|
|
13
13
|
import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
|
|
14
14
|
import { approvalsOfApp, ruleOfApproval } from '../app-rules/AppRulesCard';
|
|
15
15
|
import { useViewportDrag } from '../../../chat/useViewportDrag';
|
|
16
|
-
import { LoopAssistantCharacter, LoopAssistantMenu, LoopChatLayout, LoopChatTurn, LoopSlots, } from '../../core';
|
|
16
|
+
import { LoopAssistantCharacter, LoopAssistantMenu, LoopChatLayout, LoopChatTurn, LoopSlots, IDLE_SANDBOX_SNAPSHOT_SIGNAL, IDLE_SANDBOX_TARGET_SIGNAL, } from '../../core';
|
|
17
17
|
import { assistantCharacterFor } from '../assistant-characters';
|
|
18
|
+
import { useOptionalSandboxService } from '../agents/useSandboxService';
|
|
19
|
+
import { TARGET_SPECS } from '../agents/switchable';
|
|
18
20
|
export const LOOP_ASSISTANT_PLUGIN_NAME = '@datalayer/loop-plugin-assistant';
|
|
19
21
|
/** The character's size, in pixels, as the chat's own assistant. */
|
|
20
22
|
const SIZE = 88;
|
|
@@ -69,7 +71,7 @@ function focusComposer() {
|
|
|
69
71
|
const composer = document.querySelector('[data-chat-composer] textarea, [data-chat-composer] [contenteditable="true"]');
|
|
70
72
|
composer?.focus();
|
|
71
73
|
}
|
|
72
|
-
export function LoopAssistant({ app, person, appId, decisions, balloon: displayGiven = 'history', }) {
|
|
74
|
+
export function LoopAssistant({ app, person, appId, decisions, balloon: displayGiven = 'history', workspace, }) {
|
|
73
75
|
// The balloon's display, as its menu chose it; the Appspec's until then.
|
|
74
76
|
const [chosenDisplay, setChosenDisplay] = useState();
|
|
75
77
|
const display = chosenDisplay ?? displayGiven;
|
|
@@ -115,6 +117,53 @@ export function LoopAssistant({ app, person, appId, decisions, balloon: displayG
|
|
|
115
117
|
setHeardId(saying.id);
|
|
116
118
|
}
|
|
117
119
|
}, [open, saying?.id]);
|
|
120
|
+
// The workspace's sandbox (the agents plugin's), for the menu's *Agent
|
|
121
|
+
// Details…* and *Code Sandbox Details…*.
|
|
122
|
+
const sandboxService = useOptionalSandboxService();
|
|
123
|
+
const snapshot = useSignalValue(sandboxService?.snapshot ?? IDLE_SANDBOX_SNAPSHOT_SIGNAL);
|
|
124
|
+
const target = useSignalValue(sandboxService?.target ?? IDLE_SANDBOX_TARGET_SIGNAL);
|
|
125
|
+
const agentId = workspace?.agentId || undefined;
|
|
126
|
+
const agentAbout = useMemo(() => ({
|
|
127
|
+
name: appId,
|
|
128
|
+
spec: appId,
|
|
129
|
+
model: workspace?.model,
|
|
130
|
+
where: target ? TARGET_SPECS[target]?.label : undefined,
|
|
131
|
+
agentId,
|
|
132
|
+
apiBase: snapshot.agentBaseUrl ??
|
|
133
|
+
(workspace?.serverUrl || sandboxService?.serverUrl || undefined),
|
|
134
|
+
}), [
|
|
135
|
+
appId,
|
|
136
|
+
target,
|
|
137
|
+
agentId,
|
|
138
|
+
workspace?.model,
|
|
139
|
+
workspace?.serverUrl,
|
|
140
|
+
sandboxService,
|
|
141
|
+
snapshot.agentBaseUrl,
|
|
142
|
+
]);
|
|
143
|
+
const agentSandbox = useMemo(() => {
|
|
144
|
+
// Offered whenever the workspace has a sandbox, running or not yet.
|
|
145
|
+
if (!sandboxService || !target) {
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
const live = snapshot.state === 'running' || snapshot.state === 'starting';
|
|
149
|
+
const kinds = {
|
|
150
|
+
browser: 'browser',
|
|
151
|
+
local: 'local',
|
|
152
|
+
jupyter: 'jupyter',
|
|
153
|
+
datalayer: 'cloud',
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
kind: kinds[target] ?? 'runtime',
|
|
157
|
+
status: live ? snapshot.state : 'not running',
|
|
158
|
+
variant: snapshot.variant,
|
|
159
|
+
url: snapshot.jupyterUrl,
|
|
160
|
+
token: snapshot.jupyterToken,
|
|
161
|
+
kernelId: snapshot.kernelId,
|
|
162
|
+
agentId,
|
|
163
|
+
connection: target === 'browser' ? sandboxService.getKernelConnection() : undefined,
|
|
164
|
+
serverUrl: snapshot.agentBaseUrl ?? sandboxService.serverUrl,
|
|
165
|
+
};
|
|
166
|
+
}, [sandboxService, target, snapshot, agentId]);
|
|
118
167
|
const stageRef = useRef(null);
|
|
119
168
|
const drag = useViewportDrag(stageRef, { whole: true });
|
|
120
169
|
const decide = useMemo(() => decisions?.serverUrl
|
|
@@ -204,7 +253,7 @@ export function LoopAssistant({ app, person, appId, decisions, balloon: displayG
|
|
|
204
253
|
};
|
|
205
254
|
return (_jsxs(_Fragment, { children: [appId && signedIn ? (_jsx(WaitingApprovals, { appId: appId, onWaiting: setApproval })) : null, _jsx(AssistantStage, { character: chosen.character, state: state, size: SIZE, place: drag.position
|
|
206
255
|
? { left: `${drag.position.left}px`, top: `${drag.position.top}px` }
|
|
207
|
-
: { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, balloonDisplay: display, insist: insist, onDismiss: onDismiss, decide: decide, onResetPosition: drag.position ? drag.reset : undefined, onBalloonDisplayChange: setChosenDisplay, about:
|
|
256
|
+
: { right: '24px', bottom: '24px' }, stageRef: stageRef, onDragStart: drag.onHandlePointerDown, open: open, onToggle: onToggle, balloon: balloon, balloonDisplay: display, insist: insist, onDismiss: onDismiss, decide: decide, onResetPosition: drag.position ? drag.reset : undefined, onBalloonDisplayChange: setChosenDisplay, about: agentAbout, sandbox: agentSandbox, contextMenu: menuContributions })] }));
|
|
208
257
|
}
|
|
209
258
|
export const LoopAssistantPlugin = definePlugin({
|
|
210
259
|
name: LOOP_ASSISTANT_PLUGIN_NAME,
|
|
@@ -219,7 +268,7 @@ export const LoopAssistantPlugin = definePlugin({
|
|
|
219
268
|
description: 'The chat as a character on the page: the application’s own, or the one the person chose, from the characters the enabled plugins contribute.',
|
|
220
269
|
octicon: 'paperclip',
|
|
221
270
|
build: ({ config }) => {
|
|
222
|
-
const Configured = () => (_jsx(LoopAssistant, { app: config.app, person: config.person, appId: config.appId, decisions: config.decisions, balloon: config.balloon }));
|
|
271
|
+
const Configured = ({ workspace, }) => (_jsx(LoopAssistant, { workspace: workspace, app: config.app, person: config.person, appId: config.appId, decisions: config.decisions, balloon: config.balloon }));
|
|
223
272
|
return {
|
|
224
273
|
components: [
|
|
225
274
|
{ id: 'loop-assistant', slot: LoopSlots.root, Component: Configured },
|
|
@@ -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
|
-
|
|
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
|
{
|
package/lib/loop/presets.d.ts
CHANGED
|
@@ -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. */
|
package/lib/loop/presets.js
CHANGED
|
@@ -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';
|
package/lib/loop/shell/index.js
CHANGED
|
@@ -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
|
+
}
|
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
|
-
|
|
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
|
-
|
|
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
|
},
|