@datalayer/agent-runtimes 1.3.84 → 1.3.86
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/chat/ChatFloating.d.ts +5 -4
- package/lib/chat/ChatFloating.js +8 -6
- package/lib/chat/ChatLanguage.d.ts +17 -0
- package/lib/chat/ChatLanguage.js +29 -0
- package/lib/chat/assistant/AssistantStage.d.ts +4 -4
- package/lib/chat/assistant/AssistantStage.js +4 -2
- package/lib/chat/assistant/BalloonParts.d.ts +2 -1
- package/lib/chat/assistant/BalloonParts.js +12 -5
- package/lib/chat/assistant/BalloonVisual.js +3 -1
- package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
- package/lib/chat/assistant/ConversationBalloon.js +3 -2
- package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
- package/lib/chat/assistant/SpeechBalloon.js +10 -7
- package/lib/chat/assistant/toolLine.d.ts +8 -4
- package/lib/chat/assistant/toolLine.js +13 -9
- package/lib/chat/base/ChatBase.js +14 -11
- package/lib/chat/display/EmptyState.js +3 -1
- package/lib/chat/index.d.ts +2 -0
- package/lib/chat/index.js +3 -0
- package/lib/chat/messages/ChatMessages.js +3 -1
- package/lib/chat/messages/TurnFooter.js +3 -1
- package/lib/chat/prompt/InputPrompt.js +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +3 -1
- package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
- package/lib/chat/tools/ToolApprovalBanner.js +5 -3
- package/lib/chat/tools/ToolApprovalDialog.js +5 -3
- package/lib/chat/tools/ToolCallDisplay.js +29 -25
- package/lib/chat/words.d.ts +102 -0
- package/lib/chat/words.js +460 -0
- package/lib/components/a2ui/custom/index.d.ts +39 -0
- package/lib/components/a2ui/custom/index.js +95 -0
- package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
- package/lib/components/a2ui/custom/sandbox.js +188 -0
- package/lib/components/a2ui/datalayer/index.d.ts +1 -1
- package/lib/components/a2ui/datalayer/index.js +1 -1
- package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
- package/lib/components/a2ui/datalayer/schema.js +12 -1
- package/lib/components/a2ui/index.d.ts +1 -0
- package/lib/components/a2ui/index.js +1 -0
- package/lib/components/a2ui/visibleWhen.d.ts +12 -1
- package/lib/components/a2ui/visibleWhen.js +24 -3
- package/lib/components/inspector/AgentInspector.d.ts +25 -44
- package/lib/components/inspector/AgentInspector.js +27 -215
- package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
- package/lib/components/inspector/AgentInspectorDialog.js +2 -2
- package/lib/components/inspector/a2aSpans.d.ts +51 -0
- package/lib/components/inspector/a2aSpans.js +373 -0
- package/lib/components/inspector/agentSpans.d.ts +120 -0
- package/lib/components/inspector/agentSpans.js +253 -0
- package/lib/components/inspector/chatSpans.d.ts +36 -0
- package/lib/components/inspector/chatSpans.js +50 -0
- package/lib/components/inspector/classify.d.ts +11 -8
- package/lib/components/inspector/classify.js +23 -24
- package/lib/components/inspector/index.d.ts +7 -7
- package/lib/components/inspector/index.js +7 -7
- package/lib/components/teams/A2ATeamGraph.d.ts +117 -22
- package/lib/components/teams/A2ATeamGraph.js +217 -97
- package/lib/components/teams/SceneTranscript.d.ts +29 -0
- package/lib/components/teams/SceneTranscript.js +54 -0
- package/lib/components/teams/SceneView.d.ts +41 -0
- package/lib/components/teams/SceneView.js +28 -0
- package/lib/components/teams/index.d.ts +4 -0
- package/lib/components/teams/index.js +4 -0
- package/lib/components/teams/sceneStage.d.ts +120 -0
- package/lib/components/teams/sceneStage.js +170 -0
- package/lib/components/teams/sceneTranscript.d.ts +74 -0
- package/lib/components/teams/sceneTranscript.js +294 -0
- package/lib/components/teams/teamConnections.d.ts +2 -2
- package/lib/components/teams/teamConnections.js +3 -3
- package/lib/components/teams/useA2ATeam.d.ts +94 -22
- package/lib/components/teams/useA2ATeam.js +356 -140
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- package/lib/examples/ScenesExample.d.ts +26 -0
- package/lib/examples/ScenesExample.js +241 -0
- package/lib/examples/example-selector.js +1 -0
- package/lib/examples/exampleGroups.js +3 -0
- package/lib/examples/index.d.ts +1 -0
- package/lib/examples/index.js +1 -0
- package/lib/examples/main.js +4 -0
- package/lib/loop/apps/AppComposer.d.ts +2 -1
- package/lib/loop/apps/AppComposer.js +6 -3
- package/lib/loop/apps/AppRenderer.d.ts +2 -1
- package/lib/loop/apps/AppRenderer.js +34 -25
- package/lib/loop/apps/appspec.d.ts +11 -1
- package/lib/loop/apps/appspec.js +115 -1
- package/lib/loop/apps/checks.d.ts +8 -0
- package/lib/loop/apps/checks.js +106 -4
- package/lib/loop/apps/customComponents.d.ts +41 -0
- package/lib/loop/apps/customComponents.js +260 -0
- package/lib/loop/apps/hostTools.d.ts +8 -1
- package/lib/loop/apps/hostTools.js +9 -0
- package/lib/loop/apps/index.d.ts +1 -0
- package/lib/loop/apps/index.js +1 -0
- package/lib/loop/core/a2uiComponents.d.ts +10 -0
- package/lib/loop/core/a2uiComponents.js +15 -0
- package/lib/loop/core/index.d.ts +1 -0
- package/lib/loop/core/index.js +3 -0
- package/lib/loop/embed/AppEmbed.d.ts +11 -3
- package/lib/loop/embed/AppEmbed.js +42 -16
- package/lib/loop/embed/element.d.ts +5 -0
- package/lib/loop/embed/element.js +61 -5
- package/lib/loop/embed/embedConfig.d.ts +8 -0
- package/lib/loop/embed/embedConfig.js +18 -0
- package/lib/loop/embed/hostBridge.d.ts +28 -2
- package/lib/loop/embed/hostBridge.js +22 -2
- package/lib/loop/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
- package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
- package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
- package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
- package/lib/loop/plugins/app-components/index.d.ts +26 -0
- package/lib/loop/plugins/app-components/index.js +56 -0
- package/lib/loop/plugins/app-elements/AppElements.js +4 -1
- package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
- package/lib/loop/plugins/app-page/AppPage.js +106 -8
- package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
- package/lib/loop/plugins/app-page/appPageModel.js +120 -4
- package/lib/loop/plugins/app-page/index.d.ts +1 -0
- package/lib/loop/plugins/app-page/index.js +1 -0
- package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
- package/lib/loop/plugins/app-page/pageRun.js +111 -0
- package/lib/loop/plugins/assistant/index.js +8 -6
- package/lib/loop/plugins/chat/ChatView.js +6 -4
- package/lib/loop/plugins/chat/index.d.ts +1 -1
- package/lib/loop/plugins/chat/index.js +2 -1
- package/lib/models/PageTag.d.ts +1 -1
- package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
- package/lib/specs/actions.js +78 -1
- package/lib/specs/apps.d.ts +5 -0
- package/lib/specs/apps.js +1384 -231
- package/lib/specs/appspecSchema.js +191 -1
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/mcpServers.js +8 -2
- package/lib/specs/scenes.d.ts +21 -0
- package/lib/specs/scenes.js +1191 -0
- package/lib/specs/teams/teams.d.ts +3 -0
- package/lib/specs/teams/teams.js +249 -0
- package/lib/types/agentspecs.d.ts +70 -0
- package/lib/types/chat.d.ts +6 -5
- package/lib/types/index.d.ts +1 -0
- package/lib/types/index.js +1 -0
- package/lib/types/scenes.d.ts +207 -0
- package/lib/types/scenes.js +5 -0
- package/lib/types/teams.d.ts +13 -2
- package/package.json +4 -4
- package/scripts/codegen/generate_agents.py +1 -0
- package/scripts/codegen/generate_apps.py +4 -2
- package/scripts/codegen/generate_scenes.py +301 -0
- package/scripts/codegen/generate_teams.py +15 -2
- package/lib/components/inspector/JsonTree.d.ts +0 -24
- package/lib/components/inspector/JsonTree.js +0 -89
- package/lib/components/inspector/a2aInspect.d.ts +0 -56
- package/lib/components/inspector/a2aInspect.js +0 -478
- package/lib/components/inspector/agentInspector.d.ts +0 -179
- package/lib/components/inspector/agentInspector.js +0 -267
- package/lib/components/inspector/chatInspect.d.ts +0 -38
- package/lib/components/inspector/chatInspect.js +0 -65
|
@@ -19,15 +19,16 @@
|
|
|
19
19
|
* list_invoices…" while Accounting calls Odoo. Then the report appears. The
|
|
20
20
|
* page's own state is `useA2ATeam`'s, which the landing's home page runs too.
|
|
21
21
|
*
|
|
22
|
-
* Under the graph, each member has its own Agent Inspector
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
22
|
+
* Under the graph, each member has its own Agent Inspector, core's OTEL
|
|
23
|
+
* view of the page's OpenTelemetry spans: Sales' — its model turns
|
|
24
|
+
* (`invoke_agent`), its `ask_accounting` call (`execute_tool`), the A2A
|
|
25
|
+
* requests it sends and what came back, event by event — and Accounting's —
|
|
26
|
+
* the A2A requests sent to it (`peer.service`), its Odoo MCP calls,
|
|
27
|
+
* `write_notebook`, as the A2A stream tells them. Both read one tracer
|
|
28
|
+
* (`useAgentInspector`), fed by the team (`useA2ATeam`'s `inspector`) and by
|
|
29
|
+
* the fetch the A2A client sends with (`traceA2AFetch`); an A2A request
|
|
30
|
+
* shows in both. Right-click a member for its menu: *Inspect the agent…*
|
|
31
|
+
* opens its inspector over the page.
|
|
31
32
|
*
|
|
32
33
|
* Sales' suggestions — its Appspec's starters — are chips in its balloon
|
|
33
34
|
* while it waits for a question: one clicked is asked, as if typed.
|
|
@@ -10,9 +10,9 @@ import { useExampleAgentRuntimesUrl } from './utils/useExampleAgentRuntimesUrl';
|
|
|
10
10
|
import { useBrowserInference } from '../hooks/useBrowserInference';
|
|
11
11
|
import { readJwtClaims, tokenLifetimeMs, useAnonymousSessionStore, } from '../runtimes/browser/anonymousToken';
|
|
12
12
|
import { connectA2APeer } from '../runtimes/browser/a2aPeer';
|
|
13
|
-
import { A2ATeamGraph, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
|
|
13
|
+
import { A2ATeamGraph, SceneView, NOTEBOOK_AND_WORDS, teamConnectionsOf, teamToolClassifier, useA2ATeam, usePeerSandbox, } from '../components/teams';
|
|
14
14
|
import { ACCOUNTING_APP_0_0_1, SALES_APP_0_0_1 } from '../specs/apps';
|
|
15
|
-
import { AgentInspector, classifyToolCall,
|
|
15
|
+
import { AgentInspector, classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
|
|
16
16
|
import { SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1 } from '../specs/teams/teams';
|
|
17
17
|
const TEAM = SALES_AND_ACCOUNTING_TEAM_SPEC_0_0_1;
|
|
18
18
|
const SALES = SALES_APP_0_0_1;
|
|
@@ -46,8 +46,18 @@ function AgentA2ATeam() {
|
|
|
46
46
|
}, [accountingKey]);
|
|
47
47
|
const [accountingKeyEnded, setAccountingKeyEnded] = useState(() => accountingKeyClock !== undefined &&
|
|
48
48
|
accountingKeyClock.expiresAt <= Date.now());
|
|
49
|
-
// One
|
|
50
|
-
const {
|
|
49
|
+
// One tracer of what both members do, each inspector showing its own.
|
|
50
|
+
const { tracer: inspector } = useAgentInspector();
|
|
51
|
+
// The members as the transcript names them: the spans' services, and the
|
|
52
|
+
// connections a tool call is of (Accounting → Odoo).
|
|
53
|
+
const sceneMembers = useMemo(() => [
|
|
54
|
+
{ id: SALES.id, name: SALES.name },
|
|
55
|
+
{
|
|
56
|
+
id: ACCOUNTING.id,
|
|
57
|
+
name: ACCOUNTING.name,
|
|
58
|
+
connections: ACCOUNTING_CONNECTIONS,
|
|
59
|
+
},
|
|
60
|
+
], []);
|
|
51
61
|
const [peer, setPeer] = useState(null);
|
|
52
62
|
const [peerError, setPeerError] = useState(null);
|
|
53
63
|
const [draft, setDraft] = useState('');
|
|
@@ -61,9 +71,9 @@ function AgentA2ATeam() {
|
|
|
61
71
|
connectA2APeer({
|
|
62
72
|
url: accountingUrl,
|
|
63
73
|
key: accountingKey,
|
|
64
|
-
// The A2A traffic,
|
|
65
|
-
fetch:
|
|
66
|
-
|
|
74
|
+
// The A2A traffic, as spans: Sales sends, Accounting answers.
|
|
75
|
+
fetch: traceA2AFetch(globalThis.fetch.bind(globalThis), {
|
|
76
|
+
tracer: inspector,
|
|
67
77
|
asker: SALES.name,
|
|
68
78
|
peer: ACCOUNTING.name,
|
|
69
79
|
classifyTool: teamToolClassifier(ACCOUNTING_CONNECTIONS, classifyToolCall),
|
|
@@ -116,66 +126,66 @@ function AgentA2ATeam() {
|
|
|
116
126
|
px: 2,
|
|
117
127
|
pt: 2,
|
|
118
128
|
mb: 3,
|
|
119
|
-
}, children: _jsx(A2ATeamGraph, { entry: {
|
|
120
|
-
|
|
121
|
-
name: SALES.name,
|
|
122
|
-
emoji: SALES.emoji,
|
|
123
|
-
character: SALES.interface.assistant ?? 'paperclip',
|
|
124
|
-
where: salesMember.runsIn === 'browser'
|
|
125
|
-
? 'in your browser'
|
|
126
|
-
: 'on a runtime',
|
|
127
|
-
persona: team.entryPersona,
|
|
128
|
-
onToggle: () => composer.current?.focus(),
|
|
129
|
-
conversationLabel: `Ask ${SALES.name}`,
|
|
130
|
-
onAway: team.setEntryAway,
|
|
131
|
-
history: team.entryHistory,
|
|
132
|
-
expandTarget: notebookArea,
|
|
133
|
-
notebookTitle: `${ACCOUNTING.name}\u2019s notebook`,
|
|
134
|
-
suggestions: team.ready && !team.busy ? suggestions : undefined,
|
|
135
|
-
onSuggestion: suggestion => send(suggestion.prompt),
|
|
136
|
-
inspector,
|
|
137
|
-
onStop: team.busy ? team.stop : undefined,
|
|
138
|
-
about: {
|
|
129
|
+
}, children: _jsx(SceneView, { members: sceneMembers, tracer: inspector, emptyText: "Ask Sales: what it says to Accounting, what Accounting reads in Odoo and answers, line by line, shows here.", children: _jsx(A2ATeamGraph, { entry: {
|
|
130
|
+
id: SALES.id,
|
|
139
131
|
name: SALES.name,
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
where:
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
132
|
+
emoji: SALES.emoji,
|
|
133
|
+
character: SALES.interface.assistant ?? 'paperclip',
|
|
134
|
+
where: salesMember.runsIn === 'browser'
|
|
135
|
+
? 'in your browser'
|
|
136
|
+
: 'on a runtime',
|
|
137
|
+
persona: team.entryPersona,
|
|
138
|
+
onToggle: () => composer.current?.focus(),
|
|
139
|
+
conversationLabel: `Ask ${SALES.name}`,
|
|
140
|
+
onAway: team.setEntryAway,
|
|
141
|
+
history: team.entryHistory,
|
|
142
|
+
expandTarget: notebookArea,
|
|
143
|
+
notebookTitle: `${ACCOUNTING.name}\u2019s notebook`,
|
|
144
|
+
suggestions: team.ready && !team.busy ? suggestions : undefined,
|
|
145
|
+
onSuggestion: suggestion => send(suggestion.prompt),
|
|
146
|
+
inspector,
|
|
147
|
+
onStop: team.busy ? team.stop : undefined,
|
|
148
|
+
about: {
|
|
149
|
+
name: SALES.name,
|
|
150
|
+
spec: `${SALES.id}:${SALES.version}`,
|
|
151
|
+
model: SALES.model || undefined,
|
|
152
|
+
where: 'in your browser',
|
|
153
|
+
description: SALES.description || undefined,
|
|
154
|
+
},
|
|
155
|
+
}, peer: {
|
|
156
|
+
id: ACCOUNTING.id,
|
|
159
157
|
name: ACCOUNTING.name,
|
|
160
|
-
|
|
161
|
-
|
|
158
|
+
// Its face as its card says it, once it is reached.
|
|
159
|
+
emoji: peer?.face?.emoji ?? ACCOUNTING.emoji,
|
|
160
|
+
character: ACCOUNTING.interface.assistant ?? 'wizard',
|
|
162
161
|
where: accountingWhere,
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
persona: team.peerPersona,
|
|
163
|
+
onAway: team.setPeerAway,
|
|
164
|
+
history: team.peerHistory,
|
|
165
|
+
connections: ACCOUNTING_CONNECTIONS,
|
|
166
|
+
inspector,
|
|
167
|
+
sandbox: accountingSandbox,
|
|
168
|
+
about: {
|
|
169
|
+
name: ACCOUNTING.name,
|
|
170
|
+
spec: `${ACCOUNTING.id}:${ACCOUNTING.version}`,
|
|
171
|
+
model: ACCOUNTING.model || undefined,
|
|
172
|
+
where: accountingWhere,
|
|
173
|
+
// What its A2A card says, once it is reached.
|
|
174
|
+
description: peer?.card.description || ACCOUNTING.description || undefined,
|
|
175
|
+
skills: peer?.card.skills.map(skill => skill.name),
|
|
176
|
+
protocol: peer ? 'a2a' : undefined,
|
|
177
|
+
url: peer ? accountingUrl : undefined,
|
|
178
|
+
},
|
|
179
|
+
}, flow: team.flow, calls: team.calls,
|
|
180
|
+
// Room for the balloons: suggestions, *more* and a notebook.
|
|
181
|
+
balloonRoom: 320, connected: peer !== null, label: peer ? `A2A · ${peer.skill.name}` : 'A2A · not connected' }) }) }), _jsx(Box, { ref: notebookArea, sx: { mb: team.notebook ? 3 : 0 }, "data-team-notebook-placement": "under-graph" }), _jsx(Box, { sx: {
|
|
172
182
|
display: 'grid',
|
|
173
183
|
gridTemplateColumns: ['1fr', '1fr', '1fr 1fr'],
|
|
174
184
|
gap: 3,
|
|
175
185
|
mb: 3,
|
|
176
|
-
}, "data-team-inspectors": "", children: [SALES, ACCOUNTING].map(member => (_jsxs(Box, { sx: { minWidth: 0 }, "data-team-inspector": member.id, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: [member.emoji, " ", member.name, "\u2019s Agent Inspector"] }), _jsx(AgentInspector, {
|
|
177
|
-
? 'Its model turns, its call to Accounting and the A2A
|
|
178
|
-
: '
|
|
186
|
+
}, "data-team-inspectors": "", children: [SALES, ACCOUNTING].map(member => (_jsxs(Box, { sx: { minWidth: 0 }, "data-team-inspector": member.id, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: [member.emoji, " ", member.name, "\u2019s Agent Inspector"] }), _jsx(AgentInspector, { tracer: inspector, agent: member.name, compact: true, maxHeight: 360, exportName: `agent-inspector-${member.id}`, emptyText: member === SALES
|
|
187
|
+
? 'Its model turns, its call to Accounting and the A2A requests it sends, with what came back, show here.'
|
|
188
|
+
: 'The A2A requests sent to it, its Odoo MCP calls and what it sends back show here.' })] }, member.id))) }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsxs(Box, { sx: { flex: '1 1 420px', minWidth: 0 }, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: ["Ask ", SALES.name] }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mt: 0 }, children: ["Or choose one of ", SALES.name, "\u2019s suggestions in its balloon."] }), _jsx(Textarea, { ref: composer, block: true, rows: 3, value: draft, placeholder: "Ask for a financial report\u2026", onChange: event => setDraft(event.target.value), onKeyDown: event => {
|
|
179
189
|
if (event.key === 'Enter' && !event.shiftKey) {
|
|
180
190
|
event.preventDefault();
|
|
181
191
|
send(draft);
|
|
@@ -15,7 +15,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
15
15
|
* and so are the characters clippy.js publishes, fetched when picked.
|
|
16
16
|
*/
|
|
17
17
|
import { useEffect, useRef, useState } from 'react';
|
|
18
|
-
import { useAgentInspector } from '../components/inspector/
|
|
18
|
+
import { useAgentInspector } from '../components/inspector/agentSpans';
|
|
19
19
|
import { buildReactorFromPlugins } from '@datalayer/reactor';
|
|
20
20
|
import { Button, Heading, Text, ToggleSwitch } from '@primer/react';
|
|
21
21
|
import { Box } from '@datalayer/primer-addons';
|
|
@@ -96,7 +96,7 @@ const AssistantExample = () => {
|
|
|
96
96
|
// How the balloon shows the conversation (T-23): history, or current.
|
|
97
97
|
const [balloon, setBalloon] = useState('history');
|
|
98
98
|
// What the agent does, for the menu's *Inspect the agent…*.
|
|
99
|
-
const {
|
|
99
|
+
const { tracer: inspector } = useAgentInspector();
|
|
100
100
|
// Pixel, the test sprite, read once through the clippy.js reader.
|
|
101
101
|
const [pixel, setPixel] = useState();
|
|
102
102
|
useEffect(() => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The scenes of the catalogue (agentspecs `scenes`, LOOP A-15), in tabs:
|
|
3
|
+
* each scene's face and name, its `assumes` line under them, its graph and
|
|
4
|
+
* its transcript behind *Graph · Transcript* (`SceneView`), its members
|
|
5
|
+
* standing where its stage directions put them (`positions`), the entry's
|
|
6
|
+
* balloon open first (`opens_first`), and the script's cues as the entry's
|
|
7
|
+
* suggestions. A scene is tried here before it is deployed anywhere.
|
|
8
|
+
*
|
|
9
|
+
* Played against the local servers (`npm run examples`): the entry in the
|
|
10
|
+
* browser asks its model through the examples' inference — a signed-in
|
|
11
|
+
* person's token or a visitor's trial key — and the members on a runtime
|
|
12
|
+
* are served over A2A by `examples/sales-accounting-a2a/serve_scenes.py`,
|
|
13
|
+
* Accounting on :8767 as before and each other member on a runtime of its
|
|
14
|
+
* own from :8768 (`sceneRuntimePorts`), or wherever `VITE_A2A_<MEMBER>_URL`
|
|
15
|
+
* says. A scene whose entry runs on a runtime itself (Month-end close, Crop
|
|
16
|
+
* monitoring) is asked over A2A from the page, as the person
|
|
17
|
+
* (`useA2ATeam`'s `entryPeer`). A member that is not reached is drawn all
|
|
18
|
+
* the same, and the box says what its application needs (`setup`) and how
|
|
19
|
+
* to serve it, rather than failing.
|
|
20
|
+
*
|
|
21
|
+
* Right-click a member for its menu: *Inspect the agent…* opens its Agent
|
|
22
|
+
* Inspector over the page, on the one tracer every member records into.
|
|
23
|
+
*/
|
|
24
|
+
import type { JSX } from 'react';
|
|
25
|
+
export declare function ScenesExample(): JSX.Element;
|
|
26
|
+
export default ScenesExample;
|
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Button, Flash, Heading, Text, Textarea, UnderlineNav, } from '@primer/react';
|
|
4
|
+
import { Box } from '@datalayer/primer-addons';
|
|
5
|
+
import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
|
|
6
|
+
import { AnonymousKeyTimer } from '@datalayer/core/lib/components/anonymous/AnonymousKeyTimer';
|
|
7
|
+
import { Streamdown } from 'streamdown';
|
|
8
|
+
import { ThemedProvider } from './utils/themedProvider';
|
|
9
|
+
import { useBrowserInference } from '../hooks/useBrowserInference';
|
|
10
|
+
import { useAnonymousSessionStore } from '../runtimes/browser/anonymousToken';
|
|
11
|
+
import { connectA2APeer } from '../runtimes/browser/a2aPeer';
|
|
12
|
+
import { A2ATeamGraph, NOTEBOOK_AND_WORDS, PERSON, SceneView, sceneCuesOf, sceneMemberUrl, sceneRuntimePorts, sceneStageOf, teamConnectionsOf, teamToolClassifier, useA2ATeam, } from '../components/teams';
|
|
13
|
+
import { listSceneSpecs } from '../specs/scenes';
|
|
14
|
+
import { classifyToolCall, traceA2AFetch, useAgentInspector, } from '../components/inspector';
|
|
15
|
+
/** Where the members on a runtime are, by application id: the local ports, unless a variable says. */
|
|
16
|
+
const PORTS = sceneRuntimePorts();
|
|
17
|
+
/**
|
|
18
|
+
* Reach a member on a runtime: read its card at `url`, its traffic recorded
|
|
19
|
+
* as spans for the inspectors and the transcript, as asked by `asker`.
|
|
20
|
+
*/
|
|
21
|
+
function useReached(url, asker, name, connections, tracer) {
|
|
22
|
+
const [reached, setReached] = useState({ peer: null, error: null });
|
|
23
|
+
useEffect(() => {
|
|
24
|
+
let current = true;
|
|
25
|
+
setReached({ peer: null, error: null });
|
|
26
|
+
connectA2APeer({
|
|
27
|
+
url,
|
|
28
|
+
fetch: traceA2AFetch(globalThis.fetch.bind(globalThis), {
|
|
29
|
+
tracer,
|
|
30
|
+
asker,
|
|
31
|
+
peer: name,
|
|
32
|
+
classifyTool: teamToolClassifier(connections, classifyToolCall),
|
|
33
|
+
}),
|
|
34
|
+
})
|
|
35
|
+
.then(peer => current && setReached({ peer, error: null }))
|
|
36
|
+
.catch(error => current &&
|
|
37
|
+
setReached({
|
|
38
|
+
peer: null,
|
|
39
|
+
error: error instanceof Error ? error.message : String(error),
|
|
40
|
+
}));
|
|
41
|
+
return () => {
|
|
42
|
+
current = false;
|
|
43
|
+
};
|
|
44
|
+
}, [url, asker, name, connections, tracer]);
|
|
45
|
+
return reached;
|
|
46
|
+
}
|
|
47
|
+
/** The members of a scene that run on a runtime, each with its address and connections. */
|
|
48
|
+
function runtimeMembersOf(stage) {
|
|
49
|
+
const env = import.meta.env;
|
|
50
|
+
return stage.members
|
|
51
|
+
.filter(member => member.runsIn === 'runtime')
|
|
52
|
+
.map(member => ({
|
|
53
|
+
...member,
|
|
54
|
+
url: sceneMemberUrl(member.app.id, env, PORTS),
|
|
55
|
+
connections: teamConnectionsOf(member.app),
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
/** One member on a runtime: reached, and told to the scene. */
|
|
59
|
+
function ReachMember({ member, asker, tracer, onReached, }) {
|
|
60
|
+
const reached = useReached(member.url, asker, member.app.name, member.connections, tracer);
|
|
61
|
+
useEffect(() => onReached(member.id, reached), [member.id, reached, onReached]);
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
/** What a member not reached needs: its application's setup, and how to serve it. */
|
|
65
|
+
function memberNeeds(member, error) {
|
|
66
|
+
return [
|
|
67
|
+
`${member.app.name} is not reachable at ${member.url}: ${error}`,
|
|
68
|
+
...member.setup,
|
|
69
|
+
`Serve it with python examples/sales-accounting-a2a/serve_scenes.py (npm run examples does), or set VITE_A2A_${member.app.id
|
|
70
|
+
.replace(/-/g, '_')
|
|
71
|
+
.toUpperCase()}_URL to where it runs.`,
|
|
72
|
+
];
|
|
73
|
+
}
|
|
74
|
+
function Scene({ scene }) {
|
|
75
|
+
const stage = useMemo(() => sceneStageOf(scene), [scene]);
|
|
76
|
+
const entry = stage.entry;
|
|
77
|
+
const entryInBrowser = entry.runsIn === 'browser';
|
|
78
|
+
const runtimeMembers = useMemo(() => runtimeMembersOf(stage), [stage]);
|
|
79
|
+
const { inference, needsSignIn, anonymous } = useBrowserInference(entryInBrowser);
|
|
80
|
+
// One tracer of what every member does: each inspector shows its own.
|
|
81
|
+
const { tracer: inspector } = useAgentInspector();
|
|
82
|
+
const [reached, setReached] = useState({});
|
|
83
|
+
const onReached = useMemo(() => (id, found) => setReached(prev => prev[id]?.peer === found.peer && prev[id]?.error === found.error
|
|
84
|
+
? prev
|
|
85
|
+
: { ...prev, [id]: found }), []);
|
|
86
|
+
const reachedOf = (id) => reached[id] ?? { peer: null, error: null };
|
|
87
|
+
// The entry's peers over A2A, in the cast's order; the entry itself when
|
|
88
|
+
// it runs on a runtime.
|
|
89
|
+
const peerOptions = useMemo(() => stage.peers.map(peer => {
|
|
90
|
+
const member = runtimeMembers.find(one => one.id === peer.id);
|
|
91
|
+
return {
|
|
92
|
+
app: peer.app,
|
|
93
|
+
peer: reached[peer.id]?.peer ?? null,
|
|
94
|
+
connections: member?.connections ?? [],
|
|
95
|
+
...(stage.opensFirst === peer.id
|
|
96
|
+
? { greeting: peer.cast.persona.line }
|
|
97
|
+
: {}),
|
|
98
|
+
};
|
|
99
|
+
}), [stage, runtimeMembers, reached]);
|
|
100
|
+
const team = useA2ATeam({
|
|
101
|
+
entry: entry.app,
|
|
102
|
+
peers: peerOptions,
|
|
103
|
+
entryPeer: entryInBrowser ? undefined : reachedOf(entry.id).peer,
|
|
104
|
+
inference,
|
|
105
|
+
accept: NOTEBOOK_AND_WORDS,
|
|
106
|
+
inspector,
|
|
107
|
+
});
|
|
108
|
+
// The script's cues, labelled by the entry's starters (A-13): chips in
|
|
109
|
+
// its balloon while it waits for a question.
|
|
110
|
+
const suggestions = useMemo(() => sceneCuesOf(scene, entry.app.interface.starters ?? []), [scene, entry.app]);
|
|
111
|
+
const [draft, setDraft] = useState('');
|
|
112
|
+
const composer = useRef(null);
|
|
113
|
+
// Under the graph: where a notebook a member gives runs.
|
|
114
|
+
const notebookArea = useRef(null);
|
|
115
|
+
const send = (text) => {
|
|
116
|
+
setDraft('');
|
|
117
|
+
void team.send(text);
|
|
118
|
+
};
|
|
119
|
+
// The members as the transcript names them: the spans' services, and the
|
|
120
|
+
// connections a tool call is of.
|
|
121
|
+
const transcriptMembers = useMemo(() => stage.members.map(member => ({
|
|
122
|
+
id: member.app.id,
|
|
123
|
+
name: member.app.name,
|
|
124
|
+
connections: runtimeMembers.find(one => one.id === member.id)?.connections ?? [],
|
|
125
|
+
})), [stage, runtimeMembers]);
|
|
126
|
+
const whereOf = (member) => member.runsIn === 'browser'
|
|
127
|
+
? 'in your browser'
|
|
128
|
+
: `on a runtime, ${new URL(runtimeMembers.find(one => one.id === member.id)?.url ?? 'http://runtime').host}`;
|
|
129
|
+
const aboutOf = (member) => {
|
|
130
|
+
const peer = reachedOf(member.id).peer;
|
|
131
|
+
const url = runtimeMembers.find(one => one.id === member.id)?.url;
|
|
132
|
+
return {
|
|
133
|
+
name: member.app.name,
|
|
134
|
+
spec: `${member.app.id}:${member.app.version}`,
|
|
135
|
+
model: member.app.model || undefined,
|
|
136
|
+
where: whereOf(member),
|
|
137
|
+
description: peer?.card.description || member.app.description || undefined,
|
|
138
|
+
skills: peer?.card.skills.map(skill => skill.name),
|
|
139
|
+
protocol: peer ? 'a2a' : undefined,
|
|
140
|
+
url: peer ? url : undefined,
|
|
141
|
+
};
|
|
142
|
+
};
|
|
143
|
+
const notReached = runtimeMembers.filter(member => reachedOf(member.id).error);
|
|
144
|
+
const notebookGiver = stage.peers[0] ?? entry;
|
|
145
|
+
return (_jsxs(Box, { "data-scene-example": scene.id, children: [_jsx(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, "data-scene-assumes": "", children: stage.assumes }), anonymous.status === 'active' && anonymous.expiresAt && (_jsx(Box, { sx: { mb: 3 }, children: _jsx(AnonymousKeyTimer, { expiresAt: anonymous.expiresAt, grantedMs: anonymous.grantedMs, label: `${entry.app.name}'s trial key`, onExpire: () => useAnonymousSessionStore.getState().expire() }) })), anonymous.status === 'expired' && (_jsx(Box, { sx: { mb: 3 }, children: _jsx(AnonymousKeyExpired, { agentName: entry.app.name, onSignedIn: () => useAnonymousSessionStore.getState().clear() }) })), entryInBrowser && needsSignIn && (_jsxs(Flash, { variant: "warning", sx: { mb: 3 }, children: [entry.app.name, " asks its model through the Datalayer inference service: sign in, or wait for a visitor\u2019s trial key."] })), notReached.map(member => (_jsx(Flash, { variant: "danger", sx: { mb: 3 }, "data-scene-needs": member.app.id, children: memberNeeds(member, reachedOf(member.id).error ?? '').map(line => (_jsx(Text, { as: "p", sx: { m: 0 }, children: line }, line))) }, member.id))), runtimeMembers.map(member => (_jsx(ReachMember, { member: member,
|
|
146
|
+
// Its requests are the entry's, or the person's own when the
|
|
147
|
+
// page asks it itself.
|
|
148
|
+
asker: member.entry ? PERSON : entry.app.name, tracer: inspector, onReached: onReached }, member.id))), _jsx(Box, { sx: {
|
|
149
|
+
border: '1px solid',
|
|
150
|
+
borderColor: 'border.default',
|
|
151
|
+
borderRadius: 2,
|
|
152
|
+
bg: 'canvas.subtle',
|
|
153
|
+
px: 2,
|
|
154
|
+
pt: 2,
|
|
155
|
+
mb: 3,
|
|
156
|
+
}, children: _jsx(SceneView, { members: transcriptMembers, tracer: inspector, emptyText: `Nothing said yet: ask ${entry.app.name} with one of the suggestions.`, children: _jsx(A2ATeamGraph, { entry: {
|
|
157
|
+
id: entry.app.id,
|
|
158
|
+
name: entry.app.name,
|
|
159
|
+
emoji: entry.cast.persona.face || entry.app.emoji,
|
|
160
|
+
character: entry.app.interface.assistant ?? 'paperclip',
|
|
161
|
+
where: whereOf(entry),
|
|
162
|
+
persona: team.entryPersona,
|
|
163
|
+
onToggle: () => composer.current?.focus(),
|
|
164
|
+
conversationLabel: `Ask ${entry.app.name}`,
|
|
165
|
+
onAway: team.setEntryAway,
|
|
166
|
+
history: team.entryHistory,
|
|
167
|
+
connections: runtimeMembers.find(one => one.id === entry.id)
|
|
168
|
+
?.connections,
|
|
169
|
+
expandTarget: notebookArea,
|
|
170
|
+
notebookTitle: `${notebookGiver.app.name}’s notebook`,
|
|
171
|
+
suggestions: team.ready && !team.busy ? suggestions : undefined,
|
|
172
|
+
onSuggestion: suggestion => send(suggestion.prompt),
|
|
173
|
+
inspector,
|
|
174
|
+
onStop: team.busy ? team.stop : undefined,
|
|
175
|
+
about: aboutOf(entry),
|
|
176
|
+
}, peers: stage.peers.map(peer => {
|
|
177
|
+
const kept = team.peers.find(one => one.id === peer.app.id);
|
|
178
|
+
return {
|
|
179
|
+
id: peer.app.id,
|
|
180
|
+
name: peer.app.name,
|
|
181
|
+
emoji: reachedOf(peer.id).peer?.face?.emoji ||
|
|
182
|
+
peer.cast.persona.face ||
|
|
183
|
+
peer.app.emoji,
|
|
184
|
+
character: peer.app.interface.assistant ?? 'wizard',
|
|
185
|
+
where: whereOf(peer),
|
|
186
|
+
persona: kept?.persona ?? team.peerPersona,
|
|
187
|
+
onAway: kept?.setAway,
|
|
188
|
+
history: kept?.history,
|
|
189
|
+
connections: runtimeMembers.find(one => one.id === peer.id)
|
|
190
|
+
?.connections,
|
|
191
|
+
inspector,
|
|
192
|
+
about: aboutOf(peer),
|
|
193
|
+
};
|
|
194
|
+
}), flows: team.flows, calls: team.calls, links: stage.links.map(link => ({
|
|
195
|
+
from: stage.members.find(m => m.id === link.from)?.app.id ??
|
|
196
|
+
link.from,
|
|
197
|
+
to: stage.members.find(m => m.id === link.to)?.app.id ?? link.to,
|
|
198
|
+
})), positions: Object.fromEntries(stage.members
|
|
199
|
+
.filter(member => stage.positions[member.id])
|
|
200
|
+
.map(member => [member.app.id, stage.positions[member.id]])), connected: Object.fromEntries(stage.peers.map(peer => [
|
|
201
|
+
peer.app.id,
|
|
202
|
+
reachedOf(peer.id).peer !== null,
|
|
203
|
+
])), labels: Object.fromEntries(stage.peers.map(peer => {
|
|
204
|
+
const found = reachedOf(peer.id).peer;
|
|
205
|
+
return [
|
|
206
|
+
peer.app.id,
|
|
207
|
+
found ? `A2A · ${found.skill.name}` : 'A2A · not connected',
|
|
208
|
+
];
|
|
209
|
+
})),
|
|
210
|
+
// Room for the balloons: suggestions, *more* and a notebook.
|
|
211
|
+
balloonRoom: 320 }) }) }), _jsx(Box, { ref: notebookArea, sx: { mb: team.notebook ? 3 : 0 }, "data-team-notebook-placement": "under-graph" }), _jsxs(Box, { sx: { display: 'flex', gap: 3, flexWrap: 'wrap' }, children: [_jsxs(Box, { sx: { flex: '1 1 420px', minWidth: 0 }, children: [_jsxs(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: ["Ask ", entry.app.name] }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1, mt: 0 }, children: "Or choose one of its suggestions in its balloon: the script\u2019s cues." }), _jsx(Textarea, { ref: composer, block: true, rows: 3, value: draft, placeholder: suggestions[0]?.prompt ?? 'Ask…', onChange: event => setDraft(event.target.value), onKeyDown: event => {
|
|
212
|
+
if (event.key === 'Enter' && !event.shiftKey) {
|
|
213
|
+
event.preventDefault();
|
|
214
|
+
send(draft);
|
|
215
|
+
}
|
|
216
|
+
} }), _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 2 }, children: [_jsx(Button, { variant: "primary", disabled: !team.ready || team.busy || !draft.trim(), onClick: () => send(draft), children: "Ask" }), team.busy && _jsx(Button, { onClick: team.stop, children: "Stop" })] }), _jsx(Box, { sx: { mt: 3 }, "data-team-conversation": "", children: team.turns.map((turn, index) => (_jsxs(Box, { sx: {
|
|
217
|
+
p: 2,
|
|
218
|
+
mb: 2,
|
|
219
|
+
borderRadius: 2,
|
|
220
|
+
bg: turn.role === 'user' ? 'accent.subtle' : 'canvas.subtle',
|
|
221
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block' }, children: turn.role === 'user' ? PERSON : entry.app.name }), _jsx(Streamdown, { children: turn.text })] }, index))) })] }), _jsxs(Box, { sx: { flex: '1 1 360px', minWidth: 0 }, children: [!team.notebook && stage.peers.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h3", sx: { fontSize: 2, mb: 2 }, children: "The report" }), _jsx(Box, { sx: {
|
|
222
|
+
p: 3,
|
|
223
|
+
border: '1px solid',
|
|
224
|
+
borderColor: 'border.default',
|
|
225
|
+
borderRadius: 2,
|
|
226
|
+
minHeight: 120,
|
|
227
|
+
}, "data-team-report": "", children: team.report ? (_jsx(Streamdown, { children: team.report })) : (_jsx(Text, { sx: { color: 'fg.muted' }, children: "What the last member asked returns appears here, as it returned it, or as a notebook to run when it gives one." })) })] })), scene.setup.length > 0 && (_jsxs(Box, { sx: { mt: 3 }, "data-scene-setup": "", children: [_jsx(Heading, { as: "h3", sx: { fontSize: 1, mb: 1 }, children: "What this scene needs" }), scene.setup.map(line => (_jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', m: 0 }, children: line }, line)))] }))] })] })] }));
|
|
228
|
+
}
|
|
229
|
+
function Scenes() {
|
|
230
|
+
const scenes = useMemo(() => listSceneSpecs(), []);
|
|
231
|
+
const [selected, setSelected] = useState(scenes[0]?.id ?? '');
|
|
232
|
+
const scene = scenes.find(one => one.id === selected) ?? scenes[0];
|
|
233
|
+
return (_jsxs(Box, { sx: { p: 4, maxWidth: 1080, mx: 'auto' }, children: [_jsx(Heading, { as: "h2", sx: { fontSize: 4, mb: 1 }, children: "Scenes" }), _jsxs(Text, { as: "p", sx: { color: 'fg.muted', mt: 0 }, children: ["The catalogue\u2019s scenes, played against the local servers: each member on a runtime is served over A2A by ", _jsx("code", { children: "serve_scenes.py" }), "; the entry in your browser asks on your token or a visitor\u2019s trial key."] }), _jsx(UnderlineNav, { "aria-label": "Scenes", sx: { mb: 3 }, children: scenes.map(one => (_jsxs(UnderlineNav.Item, { "aria-current": one.id === scene?.id ? 'page' : undefined, onSelect: event => {
|
|
234
|
+
event.preventDefault();
|
|
235
|
+
setSelected(one.id);
|
|
236
|
+
}, "data-scene-tab": one.id, children: [one.emoji, " ", one.name] }, one.id))) }), scene && _jsx(Scene, { scene: scene }, scene.id)] }));
|
|
237
|
+
}
|
|
238
|
+
export function ScenesExample() {
|
|
239
|
+
return (_jsx(ThemedProvider, { children: _jsx(Scenes, {}) }));
|
|
240
|
+
}
|
|
241
|
+
export default ScenesExample;
|
|
@@ -112,6 +112,7 @@ export const EXAMPLE_ENTRIES = [
|
|
|
112
112
|
// for one sandbox — and a second one that would not agree with the first.
|
|
113
113
|
['example', 'loop', 'workspace', 'sandbox', 'owns-sandbox-control']),
|
|
114
114
|
makeEntry('LoopAppComputerExample', () => import('./LoopAppComputerExample'), 'An application beside its computer: what its agent ran on its sandbox, its files, and Take over and Hand back (LOOP R-23).', ['example', 'loop', 'app', 'computer', 'sandbox', 'owns-sandbox-control']),
|
|
115
|
+
makeEntry('ScenesExample', () => import('./ScenesExample'), 'The scenes of the catalogue in tabs — Sales & Accounting, Month-end close, Crop monitoring, Disaster assessment — each its graph and transcript, its cues as suggestions, played against the local servers (LOOP A-15).', ['example', 'loop', 'scenes', 'a2a', 'agentspecs', 'team']),
|
|
115
116
|
makeEntry('LoopShellExample', () => import('./LoopShellExample'), 'The Loop shell at its most naked: a blank canvas, a floating draggable prompt, and an editor selector in the corner — none, notebook or document.',
|
|
116
117
|
// `owns-sandbox-control`: the shell is pinned to the browser sandbox, so
|
|
117
118
|
// the page's selector would offer three targets it cannot move to.
|
|
@@ -23,7 +23,10 @@ export const EXAMPLE_GROUP_ORDER = [
|
|
|
23
23
|
'CopilotKit',
|
|
24
24
|
];
|
|
25
25
|
export const getExampleGroup = (id) => {
|
|
26
|
+
// The library of specs, the Loop shells, and the scenes of the catalogue
|
|
27
|
+
// (LOOP A-15): the applications and what is staged with them.
|
|
26
28
|
if (id === 'AgentspecsExample' ||
|
|
29
|
+
id === 'ScenesExample' ||
|
|
27
30
|
id.startsWith('Loop') ||
|
|
28
31
|
id === 'DecksAgent') {
|
|
29
32
|
return 'Apps';
|
package/lib/examples/index.d.ts
CHANGED
|
@@ -27,6 +27,7 @@ export { default as AgentCodemodeExample } from './AgentCodemodeExample';
|
|
|
27
27
|
export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
|
|
28
28
|
export { default as AgentDecideExample } from './AgentDecideExample';
|
|
29
29
|
export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
|
|
30
|
+
export { default as ScenesExample } from './ScenesExample';
|
|
30
31
|
export { default as AgentEvalsExample } from './AgentEvalsExample';
|
|
31
32
|
export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
|
|
32
33
|
export { default as AgentHooksExample } from './AgentHooksExample';
|
package/lib/examples/index.js
CHANGED
|
@@ -33,6 +33,7 @@ export { default as AgentCodemodeExample } from './AgentCodemodeExample';
|
|
|
33
33
|
export { default as AgentCodeSandboxesExample } from './AgentCodeSandboxesExample';
|
|
34
34
|
export { default as AgentDecideExample } from './AgentDecideExample';
|
|
35
35
|
export { default as AgentA2ATeamExample } from './AgentA2ATeamExample';
|
|
36
|
+
export { default as ScenesExample } from './ScenesExample';
|
|
36
37
|
export { default as AgentEvalsExample } from './AgentEvalsExample';
|
|
37
38
|
export { default as AgentGuardrailsExample } from './AgentGuardrailsExample';
|
|
38
39
|
export { default as AgentHooksExample } from './AgentHooksExample';
|
package/lib/examples/main.js
CHANGED
|
@@ -62,6 +62,9 @@ const ANONYMOUS_EXAMPLES = new Set([
|
|
|
62
62
|
// The Sales and Accounting team: Sales in the page on a visitor's trial
|
|
63
63
|
// key, Accounting on the local server, which answers the machine itself.
|
|
64
64
|
'AgentA2ATeamExample',
|
|
65
|
+
// The scenes of the catalogue: each entry in the page on a visitor's trial
|
|
66
|
+
// key, the members on a runtime on local servers that answer the machine.
|
|
67
|
+
'ScenesExample',
|
|
65
68
|
// Decide, on the local agent-runtimes server as above: Jev is asked
|
|
66
69
|
// through the server's ai-inference, with the server's own key.
|
|
67
70
|
'AgentDecideExample',
|
|
@@ -1045,6 +1048,7 @@ const ExampleAppThemed = ({ selectedExample, isChangingExample, error, ExampleCo
|
|
|
1045
1048
|
'LoopStrategyExample',
|
|
1046
1049
|
'LoopAppComputerExample',
|
|
1047
1050
|
'AgentspecsExample',
|
|
1051
|
+
'ScenesExample',
|
|
1048
1052
|
];
|
|
1049
1053
|
const loopOrder = (id) => {
|
|
1050
1054
|
const index = LOOP_ORDER.indexOf(id);
|
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
* conversation keeps its profile: the picker says so and picks no other.
|
|
24
24
|
*
|
|
25
25
|
* Given the application as the person reads it (`translatedAppspec`, LOOP
|
|
26
|
-
* P-26), its words are theirs; the composer's own,
|
|
26
|
+
* P-26), its words are theirs; the composer's own, the chat's language's
|
|
27
|
+
* (`useChatLanguage`).
|
|
27
28
|
*
|
|
28
29
|
* @module loop/apps/AppComposer
|
|
29
30
|
*/
|
|
@@ -28,7 +28,8 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
28
28
|
* conversation keeps its profile: the picker says so and picks no other.
|
|
29
29
|
*
|
|
30
30
|
* Given the application as the person reads it (`translatedAppspec`, LOOP
|
|
31
|
-
* P-26), its words are theirs; the composer's own,
|
|
31
|
+
* P-26), its words are theirs; the composer's own, the chat's language's
|
|
32
|
+
* (`useChatLanguage`).
|
|
32
33
|
*
|
|
33
34
|
* @module loop/apps/AppComposer
|
|
34
35
|
*/
|
|
@@ -40,8 +41,8 @@ import { useSignalValue } from '@datalayer/reactor/react';
|
|
|
40
41
|
import { readFile, } from '../../components/a2ui/datalayer/FileUpload';
|
|
41
42
|
import { LoopChatExtras, LoopCommand, LoopRunProps, LoopSlots, } from '../core';
|
|
42
43
|
import { commandPrompt, modeChoice, modeDefaults, profileChoice, startersFor, } from './composer';
|
|
43
|
-
import { DEFAULT_LANGUAGE } from './appspec';
|
|
44
44
|
import { interfaceWords } from './language';
|
|
45
|
+
import { useChatLanguage } from '../../chat/ChatLanguage';
|
|
45
46
|
import { uploadsAccept, uploadsInWords, uploadsRefusal } from './uploads';
|
|
46
47
|
/**
|
|
47
48
|
* Files chosen in the composer, held for the next message: the refusal the
|
|
@@ -89,7 +90,6 @@ export const needsAppComposer = (app) => (app.interface.commands ?? []).length >
|
|
|
89
90
|
* Nothing for an application with none: its plugin contributes nothing.
|
|
90
91
|
*/
|
|
91
92
|
export function defineAppComposerPlugin(app) {
|
|
92
|
-
const words = interfaceWords(app.interface.language ?? DEFAULT_LANGUAGE);
|
|
93
93
|
const profiles = app.interface.profiles ?? [];
|
|
94
94
|
/** The profile picked, and whether the conversation has started with it. */
|
|
95
95
|
const profile = signal(profileChoice(app)?.id);
|
|
@@ -98,6 +98,8 @@ export function defineAppComposerPlugin(app) {
|
|
|
98
98
|
openers: startersAsOpeners(startersFor(app, profile.value)),
|
|
99
99
|
}));
|
|
100
100
|
function ProfilePicker() {
|
|
101
|
+
// In the chat's language, as the chat's own words (P-26).
|
|
102
|
+
const words = interfaceWords(useChatLanguage());
|
|
101
103
|
const now = profileChoice(app, useSignalValue(profile));
|
|
102
104
|
const kept = useSignalValue(started);
|
|
103
105
|
if (!now) {
|
|
@@ -131,6 +133,7 @@ export function defineAppComposerPlugin(app) {
|
|
|
131
133
|
/** The files held for the next message, and the last refusal (P-21). */
|
|
132
134
|
const held = signal({ files: [], refused: null });
|
|
133
135
|
function Attach() {
|
|
136
|
+
const words = interfaceWords(useChatLanguage());
|
|
134
137
|
const now = useSignalValue(held);
|
|
135
138
|
const input = useRef(null);
|
|
136
139
|
if (!uploads) {
|
|
@@ -176,7 +176,8 @@ export type AppPreset = {
|
|
|
176
176
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
177
177
|
* comment on each answer when its record keeps feedback (V-18); its
|
|
178
178
|
* commands in the composer's `/` menu and its modes beside it (P-19); the
|
|
179
|
-
* side panel and the pages its code opens (`app-elements`, P-18);
|
|
179
|
+
* side panel and the pages its code opens (`app-elements`, P-18); the
|
|
180
|
+
* components its developer wrote (`app-components`, P-17); what it
|
|
180
181
|
* keeps and for how long, under its prompt before the first message, when
|
|
181
182
|
* `kept` says it (R-31); with
|
|
182
183
|
* `sidebar`, its rules and approvals card, its activity feed and its
|