@datalayer/agent-runtimes 1.3.87 → 1.3.88
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/components/a2ui/datalayer/Download.d.ts +21 -0
- package/lib/components/a2ui/datalayer/Download.js +66 -0
- package/lib/components/a2ui/datalayer/drawn.d.ts +1 -1
- package/lib/components/a2ui/datalayer/drawn.js +1 -0
- package/lib/components/a2ui/datalayer/index.d.ts +2 -1
- package/lib/components/a2ui/datalayer/index.js +3 -0
- package/lib/components/inspector/a2aSpans.d.ts +9 -0
- package/lib/components/inspector/a2aSpans.js +11 -1
- package/lib/components/teams/AnswerSurfaces.d.ts +56 -0
- package/lib/components/teams/AnswerSurfaces.js +27 -0
- package/lib/components/teams/AnswerSurfacesView.d.ts +14 -0
- package/lib/components/teams/AnswerSurfacesView.js +30 -0
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/useA2ATeam.d.ts +56 -2
- package/lib/components/teams/useA2ATeam.js +122 -3
- package/lib/hooks/useAgentRuntimes.d.ts +13 -0
- package/lib/hooks/useAgentRuntimes.js +11 -1
- package/lib/loop/apps/AppFeedback.js +11 -2
- package/lib/loop/apps/AppRenderer.d.ts +13 -1
- package/lib/loop/apps/AppRenderer.js +34 -1
- package/lib/loop/apps/AppRuntimePlugins.d.ts +8 -0
- package/lib/loop/apps/AppRuntimePlugins.js +87 -0
- package/lib/loop/apps/feedback.d.ts +7 -0
- package/lib/loop/apps/feedback.js +3 -0
- package/lib/loop/apps/pluginPair.d.ts +60 -0
- package/lib/loop/apps/pluginPair.js +44 -0
- package/lib/loop/apps/threads.d.ts +35 -0
- package/lib/loop/apps/threads.js +79 -0
- package/lib/loop/embed/LoopEmbed.d.ts +4 -3
- package/lib/loop/embed/hostBridge.d.ts +27 -0
- package/lib/loop/embed/hostBridge.js +47 -0
- package/lib/loop/index.d.ts +1 -1
- package/lib/loop/index.js +1 -1
- package/lib/loop/plugins/agent-capacity/index.d.ts +7 -0
- package/lib/loop/plugins/agent-capacity/index.js +3 -2
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +11 -0
- package/lib/loop/plugins/agents/appLaunch.d.ts +15 -0
- package/lib/loop/plugins/agents/appLaunch.js +28 -0
- package/lib/loop/plugins/chat/ChatView.js +177 -109
- package/lib/loop/shell/LoopWorkspace.d.ts +3 -3
- package/lib/loop/shell/LoopWorkspace.js +1 -1
- package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +10 -2
- package/lib/runtimes/actions.js +23 -0
- package/lib/runtimes/apis.d.ts +18 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +19 -2
- package/lib/runtimes/browser/a2aPeer.js +13 -7
- package/lib/specs/apps.js +102 -30
- package/lib/specs/envvars.d.ts +2 -0
- package/lib/specs/envvars.js +22 -0
- package/lib/specs/outputs.d.ts +1 -0
- package/lib/specs/outputs.js +12 -0
- package/lib/specs/sceneSchema.js +12 -1
- package/lib/specs/scenes.js +238 -121
- package/lib/specs/teams/teams.js +23 -7
- package/lib/specs/uiPlugins.js +55 -0
- package/lib/stores/agentRuntimeStore.js +4 -0
- package/lib/types/chat.d.ts +6 -0
- package/lib/types/scenes.d.ts +7 -1
- package/package.json +1 -1
- package/scripts/codegen/generate_mcp_servers.py +11 -7
package/lib/loop/index.d.ts
CHANGED
|
@@ -70,7 +70,7 @@ export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
|
|
|
70
70
|
export { LoopEmbed, type LoopEmbedProps } from './embed/LoopEmbed';
|
|
71
71
|
export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, type EmbedSession, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
72
72
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
73
|
-
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
73
|
+
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, type SignedUserToken, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
74
74
|
export { defineDatalayerAppElement, type DatalayerAppElementOptions, } from './embed/element';
|
|
75
75
|
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
76
76
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
package/lib/loop/index.js
CHANGED
|
@@ -91,7 +91,7 @@ export { LoopEmbed } from './embed/LoopEmbed';
|
|
|
91
91
|
export { AppEmbed, AppFloating, embedAssistantCharacter, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
92
92
|
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
93
93
|
// The host page and an embedded application, talking (LOOP D-10).
|
|
94
|
-
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, signedUser, } from './embed/hostBridge';
|
|
94
|
+
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, hostUserRunProps, fetchUserToken, signedUser, } from './embed/hostBridge';
|
|
95
95
|
export { defineDatalayerAppElement, } from './embed/element';
|
|
96
96
|
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, languageOf, resumeOf, } from './embed/embedConfig';
|
|
97
97
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
@@ -40,6 +40,13 @@ export type AgentCapacityOptions = {
|
|
|
40
40
|
model?: string;
|
|
41
41
|
/** What its modes tell a run turned in the page (LOOP P-19). */
|
|
42
42
|
modeEffect?: AgentBlueprintContribution['modeEffect'];
|
|
43
|
+
/**
|
|
44
|
+
* Its own name in place of `@datalayer/loop-plugin-agent-<key>`: an
|
|
45
|
+
* application's, the name its runtime's plugin has too (LOOP F-15).
|
|
46
|
+
*/
|
|
47
|
+
name?: string;
|
|
48
|
+
/** The runtime plugins it cannot work without (Reactor's cross-tier declaration). */
|
|
49
|
+
requiredBackendPlugins?: string[];
|
|
43
50
|
};
|
|
44
51
|
export declare function defineAgentCapacityPlugin(options: AgentCapacityOptions): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
45
52
|
export default defineAgentCapacityPlugin;
|
|
@@ -23,9 +23,10 @@
|
|
|
23
23
|
import { contribution, definePlugin, } from '@datalayer/reactor';
|
|
24
24
|
import { LoopAgentBlueprint, LoopChatSuggestion, } from '../../core';
|
|
25
25
|
export function defineAgentCapacityPlugin(options) {
|
|
26
|
-
const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, modeEffect, } = options;
|
|
26
|
+
const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, modeEffect, name, requiredBackendPlugins, } = options;
|
|
27
27
|
return definePlugin({
|
|
28
|
-
name: `@datalayer/loop-plugin-agent-${key}`,
|
|
28
|
+
name: name ?? `@datalayer/loop-plugin-agent-${key}`,
|
|
29
|
+
...(requiredBackendPlugins ? { requiredBackendPlugins } : {}),
|
|
29
30
|
displayName,
|
|
30
31
|
description,
|
|
31
32
|
octicon: octicon ?? 'dependabot',
|
|
@@ -7,6 +7,7 @@ import { useReactorPlatform, useSignalValue } from '@datalayer/reactor/react';
|
|
|
7
7
|
import { useAgentRuntimes } from '../../../hooks/useAgentRuntimes';
|
|
8
8
|
import { AGENTSPECS } from '../../../specs/agents/agents';
|
|
9
9
|
import { IDLE_SANDBOX_TARGET_SIGNAL } from '../../core';
|
|
10
|
+
import { appLaunchOf } from './appLaunch';
|
|
10
11
|
import { AGENTS_PLUGIN_NAME } from './plugin';
|
|
11
12
|
import { useOptionalSandboxService } from './useSandboxService';
|
|
12
13
|
/**
|
|
@@ -69,9 +70,19 @@ export function DatalayerAgentBridge() {
|
|
|
69
70
|
// An application's spec, when the workspace runs one.
|
|
70
71
|
...(createPayload ? { createPayload } : {}),
|
|
71
72
|
}), [agentSpecId, agentName, protocol, createPayload]);
|
|
73
|
+
/*
|
|
74
|
+
* The application the runtime is launched for (STUDIO R-19): its Appspec
|
|
75
|
+
* and where it runs, read from the payload its agent is created with — the
|
|
76
|
+
* Studio's Preview, the hosted page signed in. Sent with the launch, so the
|
|
77
|
+
* runtime is given the secrets its connections declare before its agent is
|
|
78
|
+
* made; without it, a connection needing a secret is refused on the
|
|
79
|
+
* runtime. A bare spec launches none.
|
|
80
|
+
*/
|
|
81
|
+
const appLaunch = useMemo(() => appLaunchOf(createPayload), [createPayload]);
|
|
72
82
|
const { runtime, status, error } = useAgentRuntimes({
|
|
73
83
|
agentSpecId,
|
|
74
84
|
agentConfig,
|
|
85
|
+
...(appLaunch ? { appLaunch } : {}),
|
|
75
86
|
variant: 'cloud-pydanticai',
|
|
76
87
|
autoStart: onDatalayer && !already,
|
|
77
88
|
autoCreateAgent: onDatalayer && !already,
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What an application's runtime is launched with (STUDIO R-19).
|
|
3
|
+
*
|
|
4
|
+
* Pure, so it is read without the agent hook's runtime stack.
|
|
5
|
+
*
|
|
6
|
+
* @module loop/plugins/agents/appLaunch
|
|
7
|
+
*/
|
|
8
|
+
import type { AppLaunch } from '../../../hooks/useAgentRuntimes';
|
|
9
|
+
/**
|
|
10
|
+
* The launch of the application an agent is created with, or none.
|
|
11
|
+
*
|
|
12
|
+
* `app_spec` and `app_instance` are what `appDatalayerCreatePayload` writes;
|
|
13
|
+
* the launch sends them as `RuntimesClient.create` does in Python.
|
|
14
|
+
*/
|
|
15
|
+
export declare function appLaunchOf(createPayload: Record<string, unknown> | undefined): AppLaunch | undefined;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* The launch of the application an agent is created with, or none.
|
|
7
|
+
*
|
|
8
|
+
* `app_spec` and `app_instance` are what `appDatalayerCreatePayload` writes;
|
|
9
|
+
* the launch sends them as `RuntimesClient.create` does in Python.
|
|
10
|
+
*/
|
|
11
|
+
export function appLaunchOf(createPayload) {
|
|
12
|
+
const appSpec = createPayload?.app_spec;
|
|
13
|
+
if (!appSpec || typeof appSpec !== 'object') {
|
|
14
|
+
return undefined;
|
|
15
|
+
}
|
|
16
|
+
const instance = (createPayload?.app_instance ?? {});
|
|
17
|
+
const appUid = typeof instance.app_uid === 'string' && instance.app_uid
|
|
18
|
+
? instance.app_uid
|
|
19
|
+
: undefined;
|
|
20
|
+
const deploymentUid = typeof instance.deployment_uid === 'string' && instance.deployment_uid
|
|
21
|
+
? instance.deployment_uid
|
|
22
|
+
: undefined;
|
|
23
|
+
return {
|
|
24
|
+
appSpec: appSpec,
|
|
25
|
+
...(appUid ? { appUid } : {}),
|
|
26
|
+
...(deploymentUid ? { deploymentUid } : {}),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -46,6 +46,8 @@ import { messageText, turnWritersOf } from './turnState';
|
|
|
46
46
|
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
47
47
|
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
48
48
|
import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
|
|
49
|
+
import { generateMessageId } from '../../../types/messages';
|
|
50
|
+
import { openThread, THREAD_WORDS } from '../../apps/threads';
|
|
49
51
|
import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
|
|
50
52
|
import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
|
|
51
53
|
import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
|
|
@@ -1180,6 +1182,74 @@ export default function ChatView({ workspace }) {
|
|
|
1180
1182
|
// not know (LOOP R-20): it runs a session of its deployment, and no more.
|
|
1181
1183
|
const embedToken = reactor.getConfig(AGENTS_PLUGIN_NAME)?.embedToken;
|
|
1182
1184
|
const iamToken = visitors ? visitor.token : embedToken || memberToken;
|
|
1185
|
+
/*
|
|
1186
|
+
* A thread opened from the person's history (LOOP P-24): its conversation
|
|
1187
|
+
* read from the runtime once it answers — held there, else resumed from
|
|
1188
|
+
* its record — then drawn and gone on with on the same thread. One that
|
|
1189
|
+
* cannot be opened is said, and a new one starts.
|
|
1190
|
+
*/
|
|
1191
|
+
const givenThread = config?.thread;
|
|
1192
|
+
const [openedThread, setOpenedThread] = useState(null);
|
|
1193
|
+
const opening = Boolean(givenThread?.fromRuntime && openedThread?.given !== givenThread.id);
|
|
1194
|
+
useEffect(() => {
|
|
1195
|
+
if (!givenThread?.fromRuntime || !runsApp || !agentServerUrl) {
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
let current = true;
|
|
1199
|
+
void (async () => {
|
|
1200
|
+
let failure = '';
|
|
1201
|
+
for (let attempt = 0; attempt < 4 && current; attempt += 1) {
|
|
1202
|
+
try {
|
|
1203
|
+
const messages = await openThread({
|
|
1204
|
+
agentBaseUrl: agentServerUrl,
|
|
1205
|
+
agentId,
|
|
1206
|
+
uid: givenThread.id,
|
|
1207
|
+
token: iamToken,
|
|
1208
|
+
});
|
|
1209
|
+
if (current) {
|
|
1210
|
+
setOpenedThread({
|
|
1211
|
+
given: givenThread.id,
|
|
1212
|
+
thread: { ...givenThread, fromRuntime: false, messages },
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
return;
|
|
1216
|
+
}
|
|
1217
|
+
catch (error) {
|
|
1218
|
+
failure = error instanceof Error ? error.message : String(error);
|
|
1219
|
+
// The runtime may still be making the application's agent.
|
|
1220
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
if (current) {
|
|
1224
|
+
setOpenedThread({
|
|
1225
|
+
given: givenThread.id,
|
|
1226
|
+
thread: {
|
|
1227
|
+
id: generateMessageId(),
|
|
1228
|
+
messages: [],
|
|
1229
|
+
onStarted: givenThread.onStarted,
|
|
1230
|
+
},
|
|
1231
|
+
said: THREAD_WORDS.notOpened(failure),
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
})();
|
|
1235
|
+
return () => {
|
|
1236
|
+
current = false;
|
|
1237
|
+
};
|
|
1238
|
+
// By the thread's id: its object is the host's.
|
|
1239
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
1240
|
+
}, [
|
|
1241
|
+
givenThread?.id,
|
|
1242
|
+
givenThread?.fromRuntime,
|
|
1243
|
+
runsApp,
|
|
1244
|
+
agentServerUrl,
|
|
1245
|
+
agentId,
|
|
1246
|
+
iamToken,
|
|
1247
|
+
]);
|
|
1248
|
+
const chatThread = givenThread?.fromRuntime
|
|
1249
|
+
? openedThread?.given === givenThread.id
|
|
1250
|
+
? openedThread.thread
|
|
1251
|
+
: undefined
|
|
1252
|
+
: givenThread;
|
|
1183
1253
|
const protocol = useMemo(() => inPage
|
|
1184
1254
|
? browserProtocolConfig({
|
|
1185
1255
|
agentId: member?.specId ?? agentId,
|
|
@@ -1561,44 +1631,42 @@ export default function ChatView({ workspace }) {
|
|
|
1561
1631
|
borderColor: chatExtras.errorBanner.variant === 'warning'
|
|
1562
1632
|
? 'attention.muted'
|
|
1563
1633
|
: 'danger.muted',
|
|
1564
|
-
}, children: chatExtras.errorBanner.message })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: _jsx(ChatBase
|
|
1634
|
+
}, children: chatExtras.errorBanner.message })) : null, openedThread?.said && openedThread.given === givenThread?.id ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, px: 3, py: 2, fontSize: 1, color: 'fg.muted' }, children: openedThread.said })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: opening ? (_jsx(Text, { as: "p", role: "status", sx: { m: 0, p: 3, fontSize: 1, color: 'fg.muted' }, children: THREAD_WORDS.opening })) : (_jsx(ChatBase
|
|
1565
1635
|
// The theme it wears, when the host names one (an application's).
|
|
1566
1636
|
, {
|
|
1567
1637
|
// The theme it wears, when the host names one (an application's).
|
|
1568
|
-
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode,
|
|
1569
|
-
// The session an embed goes on with after a reload (LOOP D-13).
|
|
1570
|
-
thread: config?.thread,
|
|
1638
|
+
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode, thread: chatThread,
|
|
1571
1639
|
// The header says why, beside the title, for the same reason the
|
|
1572
1640
|
// placeholder does: a dead control with no explanation is worse
|
|
1573
1641
|
// than an absent one.
|
|
1574
1642
|
disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
|
|
1575
1643
|
/*
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1644
|
+
The model this view is on, as the chat's opening pick.
|
|
1645
|
+
|
|
1646
|
+
`ChatBase` keeps the model it sends with, and opened on the
|
|
1647
|
+
first row of whatever catalogue it had before the server
|
|
1648
|
+
answered — Alibaba — while the footer, drawn from
|
|
1649
|
+
`activeModel`, said Claude. Told the same model, both agree
|
|
1650
|
+
from the first message.
|
|
1651
|
+
*/
|
|
1584
1652
|
initialModel: activeModel || undefined,
|
|
1585
1653
|
/*
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1654
|
+
Who is answering, in the words its spec uses.
|
|
1655
|
+
|
|
1656
|
+
`ChatBase` falls back to "Start a conversation with the AI
|
|
1657
|
+
agent", which is true of every chat ever built and says
|
|
1658
|
+
nothing about this one. The empty state is the first thing a
|
|
1659
|
+
person sees and the only place the agent introduces itself.
|
|
1660
|
+
*/
|
|
1593
1661
|
title: presence?.name ?? member?.name ?? spec?.name ?? agentId, description: presence?.welcome ?? spec?.description,
|
|
1594
1662
|
/*
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1663
|
+
Two sizes, because it is drawn in two places.
|
|
1664
|
+
|
|
1665
|
+
The header wants a mark beside a line of text; the empty state
|
|
1666
|
+
wants the thing a person's eye lands on first. One `brandIcon`
|
|
1667
|
+
served both, so a 48px icon meant for the empty state sat in
|
|
1668
|
+
the header at three times the height of the words next to it.
|
|
1669
|
+
*/
|
|
1602
1670
|
// Big enough to read as the agent's mark rather than as
|
|
1603
1671
|
// punctuation before its name, and still short enough not to
|
|
1604
1672
|
// set the header's height.
|
|
@@ -1615,11 +1683,11 @@ export default function ChatView({ workspace }) {
|
|
|
1615
1683
|
// conversation".
|
|
1616
1684
|
title: presence?.name ?? member?.name ?? spec?.name ?? agentId,
|
|
1617
1685
|
/*
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1686
|
+
Two levels when there is a team: the team first — its name,
|
|
1687
|
+
what it is for, what it can be asked — and under it the
|
|
1688
|
+
member being addressed, with its own description and its
|
|
1689
|
+
own openers. The groups match the ones the openers carry.
|
|
1690
|
+
*/
|
|
1623
1691
|
...(team && member
|
|
1624
1692
|
? {
|
|
1625
1693
|
sections: [
|
|
@@ -1640,11 +1708,11 @@ export default function ChatView({ workspace }) {
|
|
|
1640
1708
|
: null),
|
|
1641
1709
|
},
|
|
1642
1710
|
/*
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1711
|
+
And what it can be asked. From the team rather than the
|
|
1712
|
+
member: the supervisor answers first, and somebody who has
|
|
1713
|
+
just opened the workspace does not yet know there are two
|
|
1714
|
+
agents behind it.
|
|
1715
|
+
*/
|
|
1648
1716
|
// With the prompt on top the openers are already chips under
|
|
1649
1717
|
// it; the empty state repeating them was the same three buttons
|
|
1650
1718
|
// twice on one screen.
|
|
@@ -1652,11 +1720,11 @@ export default function ChatView({ workspace }) {
|
|
|
1652
1720
|
? []
|
|
1653
1721
|
: chatSuggestions,
|
|
1654
1722
|
/*
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1723
|
+
The title bar arrives as a plugin: the chat-header plugin
|
|
1724
|
+
contributes the component, this view hands it the assembled
|
|
1725
|
+
props through `renderHeader`. No contribution — or a host
|
|
1726
|
+
that set `hideHeader` — means no bar.
|
|
1727
|
+
*/
|
|
1660
1728
|
showHeader: !config?.hideHeader && !!HeaderComponent,
|
|
1661
1729
|
// The `+` and the bin in the title bar, when the host asked for
|
|
1662
1730
|
// them; the header draws whatever `ChatBase` assembles here.
|
|
@@ -1667,19 +1735,19 @@ export default function ChatView({ workspace }) {
|
|
|
1667
1735
|
? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
|
|
1668
1736
|
: undefined,
|
|
1669
1737
|
/*
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1738
|
+
The (i), which opens the agent's details over the transcript.
|
|
1739
|
+
|
|
1740
|
+
Worth having here in particular: this workspace can be moved
|
|
1741
|
+
between four runtimes and several agents, and the details pane
|
|
1742
|
+
is the only place that says which one is actually answering
|
|
1743
|
+
and where it is running.
|
|
1744
|
+
*/
|
|
1677
1745
|
showInformation: true,
|
|
1678
1746
|
/*
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1747
|
+
Full screen: this view, on the whole screen, editor and all.
|
|
1748
|
+
See `toggleFullScreen` for why it is the browser's API rather
|
|
1749
|
+
than a big box.
|
|
1750
|
+
*/
|
|
1683
1751
|
headerActions: _jsxs(Box, { sx: { display: 'inline-flex', alignItems: 'center', gap: 2 }, children: [workspace.chatHeaderActions, chatHeaderItems.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.chatHeader, props: { workspace } })) : null, _jsx(IconButton, { icon: fullScreen ? ScreenNormalIcon : ScreenFullIcon, "aria-label": fullScreen ? 'Exit full screen' : 'Enter full screen', variant: "invisible", size: "small", onClick: () => {
|
|
1684
1752
|
// Whether or not it takes, the hint has been answered:
|
|
1685
1753
|
// they found the control, which is all it was for.
|
|
@@ -1688,62 +1756,62 @@ export default function ChatView({ workspace }) {
|
|
|
1688
1756
|
}, sx: hintFullScreen
|
|
1689
1757
|
? {
|
|
1690
1758
|
/*
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1759
|
+
Colour and opacity, never geometry.
|
|
1760
|
+
|
|
1761
|
+
The colour is the theme's own `primary` — the
|
|
1762
|
+
same brand the page around this workspace is
|
|
1763
|
+
already using — because a grey icon in a row of
|
|
1764
|
+
grey icons cannot be picked out however hard it
|
|
1765
|
+
fades. Colour is what makes it findable; the
|
|
1766
|
+
fade is what makes it look like it is asking.
|
|
1767
|
+
|
|
1768
|
+
It never fades to nothing: a control that
|
|
1769
|
+
vanishes and returns reads as a rendering fault,
|
|
1770
|
+
not an invitation. And nothing here moves or
|
|
1771
|
+
resizes, so the header does not reflow and the
|
|
1772
|
+
controls beside it stay where a reader last saw
|
|
1773
|
+
them.
|
|
1774
|
+
*/
|
|
1707
1775
|
'@keyframes dla-fullscreen-hint': {
|
|
1708
1776
|
'0%, 100%': { opacity: 1 },
|
|
1709
1777
|
'50%': { opacity: 0.4 },
|
|
1710
1778
|
},
|
|
1711
1779
|
/* `&&` doubles the specificity, because Primer
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1780
|
+
ships Button as a CSS module and
|
|
1781
|
+
`prc-Button-*` outranks a single generated
|
|
1782
|
+
class — without it the keyframes register and
|
|
1783
|
+
the button sits there at `animation: none`.
|
|
1784
|
+
The colour needs the same treatment: an
|
|
1785
|
+
invisible IconButton sets `color` itself.
|
|
1786
|
+
|
|
1787
|
+
And no `:focus` or `:hover` clause to stop it:
|
|
1788
|
+
anything that matches the button while it is
|
|
1789
|
+
being pointed at would cancel the pointing —
|
|
1790
|
+
which is exactly how the first version of this
|
|
1791
|
+
silently did nothing. It ends when the reader
|
|
1792
|
+
takes the offer, or arrives without it. */
|
|
1725
1793
|
'&&': {
|
|
1726
1794
|
/*
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1795
|
+
The brand on the box, not on the glyph.
|
|
1796
|
+
|
|
1797
|
+
Painting the icon `palette.primary` was the
|
|
1798
|
+
obvious reading and it is measurably
|
|
1799
|
+
backwards: in the theme this workspace ships
|
|
1800
|
+
on, `primary` is #FFC107, which sits at 1.55
|
|
1801
|
+
contrast against the header — where the
|
|
1802
|
+
icon's ordinary olive sits at 5.07. Recolouring
|
|
1803
|
+
the stroke made the control roughly three
|
|
1804
|
+
times harder to see, and at the bottom of the
|
|
1805
|
+
fade it was 1.2 and simply gone.
|
|
1806
|
+
|
|
1807
|
+
A ring and a wash carry the same colour on a
|
|
1808
|
+
far larger area, and they do it without asking
|
|
1809
|
+
the glyph to be legible in a brand colour that
|
|
1810
|
+
changes with every theme — this palette has
|
|
1811
|
+
six, from a dark teal to this amber, and a
|
|
1812
|
+
rule that works for one fails for another. The
|
|
1813
|
+
glyph keeps the colour it can be read in.
|
|
1814
|
+
*/
|
|
1747
1815
|
bg: `color-mix(in srgb, ${palette.primary} 16%, transparent)`,
|
|
1748
1816
|
boxShadow: `inset 0 0 0 1.5px ${palette.primary}`,
|
|
1749
1817
|
animation: `dla-fullscreen-hint ${FULLSCREEN_HINT_PERIOD_MS}ms ease-in-out infinite`,
|
|
@@ -1761,18 +1829,18 @@ export default function ChatView({ workspace }) {
|
|
|
1761
1829
|
// second one: two input boxes on one screen is one too many.
|
|
1762
1830
|
showInput: false, onSendReady: handleSendReady,
|
|
1763
1831
|
/*
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1832
|
+
The notebook tools' results, drawn into the transcript — the
|
|
1833
|
+
chat's own machinery, switched on only while no editor is on
|
|
1834
|
+
screen: the hidden notebook is where the agent works, and the
|
|
1835
|
+
transcript is then the one place the reader can see what a
|
|
1836
|
+
tool did. With an editor open the change is visible where it
|
|
1837
|
+
happened, and the default tool row is enough.
|
|
1838
|
+
*/
|
|
1771
1839
|
notebookToolSurfacesId: active ? undefined : workspace.surfaceId,
|
|
1772
1840
|
// A host example's own tool-result renderer, when it feeds one
|
|
1773
1841
|
// through the extras channel — the A2UI examples draw their
|
|
1774
1842
|
// surface here. Wins over the notebook surfaces in ChatBase.
|
|
1775
|
-
renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
|
|
1843
|
+
renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }, chatThread?.id)) }), besideChat && !promptHidden ? prompt : null] }) }));
|
|
1776
1844
|
// Off when the host asked for the menu alone: the composer's suggestions
|
|
1777
1845
|
// control carries the same openers (`suggestions` above).
|
|
1778
1846
|
const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": chatText.suggestedPrompts, sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
*/
|
|
22
22
|
import type { JSX } from 'react';
|
|
23
23
|
import type { ReactNode } from 'react';
|
|
24
|
-
import { type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
|
|
24
|
+
import { type PlatformInput, type PluginRef, type ReactorPlatform } from '@datalayer/reactor';
|
|
25
25
|
import { type LoopWorkspaceContext, type SandboxSnapshot } from '../core';
|
|
26
26
|
export type LoopWorkspaceProps = {
|
|
27
27
|
/** Server backing this session. */
|
|
@@ -125,6 +125,6 @@ export type LoopWorkspaceProps = {
|
|
|
125
125
|
*/
|
|
126
126
|
sidebarRail?: boolean;
|
|
127
127
|
};
|
|
128
|
-
/** Build the platform for a set of plugins. */
|
|
129
|
-
export declare function buildLoopReactor(extensions:
|
|
128
|
+
/** Build the platform for a set of plugins, and of extensions grouping them. */
|
|
129
|
+
export declare function buildLoopReactor(extensions: PlatformInput[]): ReactorPlatform;
|
|
130
130
|
export declare function LoopWorkspace(props: LoopWorkspaceProps): JSX.Element;
|
|
@@ -8,7 +8,7 @@ import { LoopCommand, LoopSlots, LoopViewType, canOpenView, createPromptChannel,
|
|
|
8
8
|
import { SIDEBAR_WIDTH } from '../plugins/plugins-panel';
|
|
9
9
|
import { ViewSwitcher } from './ViewSwitcher';
|
|
10
10
|
import { SidebarRail } from './SidebarRail';
|
|
11
|
-
/** Build the platform for a set of plugins. */
|
|
11
|
+
/** Build the platform for a set of plugins, and of extensions grouping them. */
|
|
12
12
|
export function buildLoopReactor(extensions) {
|
|
13
13
|
return buildReactorFromPlugins(extensions);
|
|
14
14
|
}
|
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
"description": "Answers requests for financial reports, such as open invoices, aged balances, a trial balance or a customer's ledger, from the Odoo books, which it only reads.",
|
|
17
17
|
"tags": ["example", "accounting", "finance", "odoo", "a2a", "team"],
|
|
18
18
|
"inputModes": ["text/plain"],
|
|
19
|
-
"outputModes": [
|
|
19
|
+
"outputModes": [
|
|
20
|
+
"text/markdown",
|
|
21
|
+
"application/x-ipynb+json",
|
|
22
|
+
"application/json+a2ui"
|
|
23
|
+
],
|
|
20
24
|
"examples": [
|
|
21
25
|
"List the customer invoices that are still open, with the total due.",
|
|
22
26
|
"Give the aged receivables as of today, by customer.",
|
|
@@ -25,7 +29,11 @@
|
|
|
25
29
|
}
|
|
26
30
|
],
|
|
27
31
|
"defaultInputModes": ["text/plain"],
|
|
28
|
-
"defaultOutputModes": [
|
|
32
|
+
"defaultOutputModes": [
|
|
33
|
+
"text/markdown",
|
|
34
|
+
"application/x-ipynb+json",
|
|
35
|
+
"application/json+a2ui"
|
|
36
|
+
],
|
|
29
37
|
"capabilities": {
|
|
30
38
|
"streaming": true,
|
|
31
39
|
"pushNotifications": false,
|
package/lib/runtimes/actions.js
CHANGED
|
@@ -49,6 +49,20 @@ export async function createRuntime(options) {
|
|
|
49
49
|
throw new Error(`Invalid runtime creditsLimit for environment ${options.environmentName}. ` +
|
|
50
50
|
'A number of zero or more is required.');
|
|
51
51
|
}
|
|
52
|
+
/*
|
|
53
|
+
* An application's launch carries its Appspec (STUDIO R-19), as
|
|
54
|
+
* `RuntimesClient.create` does in Python: the Operator hands it to the
|
|
55
|
+
* companion, which gives the runtime the secrets its connections declare
|
|
56
|
+
* before its agent is made. Refused here, before any request, as the
|
|
57
|
+
* Operator would refuse it (422).
|
|
58
|
+
*/
|
|
59
|
+
if (options.deploymentUid && !options.appUid) {
|
|
60
|
+
throw new Error(`Deployment ${options.deploymentUid} is named without its application: give appUid.`);
|
|
61
|
+
}
|
|
62
|
+
if (options.appUid && !options.appSpec) {
|
|
63
|
+
throw new Error(`Application ${options.appUid} is launched without its Appspec: give appSpec, ` +
|
|
64
|
+
'so its runtime is given the secrets its connections declare.');
|
|
65
|
+
}
|
|
52
66
|
/*
|
|
53
67
|
* `environment: {name, version}` — and the version is ADDITIVE.
|
|
54
68
|
*
|
|
@@ -75,6 +89,15 @@ export async function createRuntime(options) {
|
|
|
75
89
|
if (options.snapshot) {
|
|
76
90
|
body['from'] = options.snapshot;
|
|
77
91
|
}
|
|
92
|
+
if (options.appUid) {
|
|
93
|
+
body['app_uid'] = options.appUid;
|
|
94
|
+
}
|
|
95
|
+
if (options.deploymentUid) {
|
|
96
|
+
body['deployment_uid'] = options.deploymentUid;
|
|
97
|
+
}
|
|
98
|
+
if (options.appSpec) {
|
|
99
|
+
body['app_spec'] = options.appSpec;
|
|
100
|
+
}
|
|
78
101
|
const data = await requestDatalayerAPI({
|
|
79
102
|
url: runtimesUrl(runtimesStore.getState().runtimesUrl),
|
|
80
103
|
method: 'POST',
|
package/lib/runtimes/apis.d.ts
CHANGED
|
@@ -51,6 +51,24 @@ export interface IRuntimeOptions {
|
|
|
51
51
|
* Kernel snapshot to restore
|
|
52
52
|
*/
|
|
53
53
|
snapshot?: string;
|
|
54
|
+
/**
|
|
55
|
+
* The Appspec of the application the runtime is launched for (STUDIO
|
|
56
|
+
* R-19), as its file holds it. The Operator hands it to the runtime's
|
|
57
|
+
* companion, which gives the runtime the secrets its connections declare
|
|
58
|
+
* before its agent is made. An application's launch carries it:
|
|
59
|
+
* `appUid` without it is refused.
|
|
60
|
+
*/
|
|
61
|
+
appSpec?: Record<string, unknown>;
|
|
62
|
+
/**
|
|
63
|
+
* The saved application the runtime runs for (LOOP R-09): written on its
|
|
64
|
+
* reservation, so its running time is counted per application.
|
|
65
|
+
*/
|
|
66
|
+
appUid?: string;
|
|
67
|
+
/**
|
|
68
|
+
* The deployment of that application it runs for. A deployment is always
|
|
69
|
+
* of an application: one named without `appUid` is refused.
|
|
70
|
+
*/
|
|
71
|
+
deploymentUid?: string;
|
|
54
72
|
}
|
|
55
73
|
/**
|
|
56
74
|
* Interface for the Environments Manager
|