@datalayer/agent-runtimes 1.3.84 → 1.3.85
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 +4 -4
- 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 +3 -0
- package/lib/components/teams/index.js +3 -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 +4 -4
- package/lib/components/teams/useA2ATeam.js +50 -46
- package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
- package/lib/examples/AgentA2ATeamExample.js +70 -60
- package/lib/examples/AssistantExample.js +2 -2
- 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/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/teams.d.ts +13 -2
- package/package.json +2 -2
- package/scripts/codegen/generate_apps.py +4 -2
- 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(() => {
|
|
@@ -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
|
|
@@ -58,6 +58,7 @@ import { LoopEmbed } from '../embed/LoopEmbed';
|
|
|
58
58
|
import { dumpAppspec } from './appspec';
|
|
59
59
|
import { defineAppActivityPlugin } from '../plugins/app-activity';
|
|
60
60
|
import { AppElementsPlugin } from '../plugins/app-elements';
|
|
61
|
+
import { defineAppComponentsPlugin, hasCustomComponents, } from '../plugins/app-components';
|
|
61
62
|
import { defineAppComputerPlugin } from '../plugins/app-computer';
|
|
62
63
|
import { defineAppRulesPlugin } from '../plugins/app-rules';
|
|
63
64
|
import { defineAppComposerPlugin, needsAppComposer, startersAsOpeners, } from './AppComposer';
|
|
@@ -67,6 +68,7 @@ import { defineAppFeedbackPlugin } from './AppFeedback';
|
|
|
67
68
|
import { defineAppKeptPlugin } from './AppKept';
|
|
68
69
|
import { keepsFeedback } from './feedback';
|
|
69
70
|
import { keptBeforeFirstMessage } from './kept';
|
|
71
|
+
import { ChatLanguage } from '../../chat/ChatLanguage';
|
|
70
72
|
/** The id of an agent or a Cog, without its version. */
|
|
71
73
|
export const agentIdOf = (app) => {
|
|
72
74
|
const reference = app.agent || app.team;
|
|
@@ -167,7 +169,8 @@ export function appLayoutOptions(app) {
|
|
|
167
169
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
168
170
|
* comment on each answer when its record keeps feedback (V-18); its
|
|
169
171
|
* commands in the composer's `/` menu and its modes beside it (P-19); the
|
|
170
|
-
* side panel and the pages its code opens (`app-elements`, P-18);
|
|
172
|
+
* side panel and the pages its code opens (`app-elements`, P-18); the
|
|
173
|
+
* components its developer wrote (`app-components`, P-17); what it
|
|
171
174
|
* keeps and for how long, under its prompt before the first message, when
|
|
172
175
|
* `kept` says it (R-31); with
|
|
173
176
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
@@ -203,6 +206,9 @@ export function appPreset(app, options = {}) {
|
|
|
203
206
|
plugins: [
|
|
204
207
|
defineAppPlugin(app, shown),
|
|
205
208
|
...(withPage ? [defineAppPagePlugin(shown), ...blocks] : []),
|
|
209
|
+
// The components its developer wrote (LOOP P-17): blocks of its page
|
|
210
|
+
// and renderers of its page, its elements and its answers — its own.
|
|
211
|
+
...(hasCustomComponents(app) ? [defineAppComponentsPlugin(app)] : []),
|
|
206
212
|
// Its commands in the composer's `/` menu, its modes and its profiles
|
|
207
213
|
// beside the composer (LOOP P-19, P-20) and what a person may attach
|
|
208
214
|
// there (P-21): only for an application that has some.
|
|
@@ -385,31 +391,34 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
385
391
|
if ('problem' in preset) {
|
|
386
392
|
return (_jsx("div", { role: "status", style: { padding: 16 }, children: preset.problem }));
|
|
387
393
|
}
|
|
388
|
-
return (
|
|
389
|
-
// The
|
|
390
|
-
//
|
|
391
|
-
, {
|
|
394
|
+
return (
|
|
395
|
+
// The chat's own words in the person's language too (P-26): the host's,
|
|
396
|
+
// else the browser's.
|
|
397
|
+
_jsx(ChatLanguage, { language: language, children: _jsx(LoopEmbed
|
|
392
398
|
// The application's own agent: created under its id, so that its
|
|
393
399
|
// spec is applied to an agent of its own, never to the one it extends.
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
name:
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
400
|
+
, {
|
|
401
|
+
// The application's own agent: created under its id, so that its
|
|
402
|
+
// spec is applied to an agent of its own, never to the one it extends.
|
|
403
|
+
agentId: app.id, ...preset.workspace, ...(frame
|
|
404
|
+
? {
|
|
405
|
+
frameTitle: [app.emoji, shown.name].filter(Boolean).join(' '),
|
|
406
|
+
}
|
|
407
|
+
: {}),
|
|
408
|
+
// Its rules, activity and computer on a rail, one at a time (T-07).
|
|
409
|
+
sidebarRail: sidebar || computer, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
410
|
+
// Where an application runs is decided by its host — the Studio's
|
|
411
|
+
// Preview, the hosted page — not offered to its user.
|
|
412
|
+
targetFixed: true, ...(named ? { themeVariant: named.variant } : {}), ...(namedMode ? { colorMode: namedMode } : {}), themeOverrides: themeOverrides,
|
|
413
|
+
// Its own face, name and welcome in the chat (T-08); no counters: a
|
|
414
|
+
// person using an application is not asking about tokens.
|
|
415
|
+
presence: {
|
|
416
|
+
name: shown.name,
|
|
417
|
+
face: app.emoji,
|
|
418
|
+
welcome: shown.interface.welcome || shown.description,
|
|
419
|
+
paused,
|
|
420
|
+
onPresence,
|
|
421
|
+
onSaying,
|
|
422
|
+
}, showTokenUsage: false, datalayerCreatePayload: datalayerCreatePayload, ...embed, plugins: allPlugins }) }));
|
|
414
423
|
}
|
|
415
424
|
export default AppRenderer;
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module loop/apps/appspec
|
|
26
26
|
*/
|
|
27
|
-
import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, AppVoiceSpec, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
|
|
27
|
+
import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec } from '../../types/agentspecs';
|
|
28
28
|
/** The version of the spec itself. */
|
|
29
29
|
export declare const APP_SCHEMA = "loop.app/v1";
|
|
30
30
|
/** The language an application's own words are in, unless it says (LOOP P-26). */
|
|
@@ -62,6 +62,16 @@ export declare function retentionDays(keepFor: string): number;
|
|
|
62
62
|
export declare function emptyAppspec(kind?: AppKind): AppSpec;
|
|
63
63
|
/** An application's voice (VOICE.md VO-41): off unless said. */
|
|
64
64
|
export declare const DEFAULT_VOICE: AppVoiceSpec;
|
|
65
|
+
/** What a page's output may be drawn with (LOOP P-05), and the property of each that shows its value. */
|
|
66
|
+
export declare const PAGE_OUTPUT_SHOWS: Record<AppPageOutputComponent, string>;
|
|
67
|
+
/** The height of a component its developer wrote (P-17), unless it says. */
|
|
68
|
+
export declare const DEFAULT_CUSTOM_HEIGHT = 240;
|
|
69
|
+
/**
|
|
70
|
+
* The property a page's output fills with its value (P-05): a catalog
|
|
71
|
+
* component's (`PAGE_OUTPUT_SHOWS`), or what a component of the
|
|
72
|
+
* application's own shows first (P-17). Undefined for any other.
|
|
73
|
+
*/
|
|
74
|
+
export declare function pageOutputShows(component: string, own?: readonly AppCustomComponentSpec[]): string | undefined;
|
|
65
75
|
/** What reading a document gives: the application, and what the document got wrong. */
|
|
66
76
|
export interface ParsedAppspec {
|
|
67
77
|
app: AppSpec;
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -363,6 +363,103 @@ function parseUploads(data) {
|
|
|
363
363
|
maxFiles: numberOf(data.max_files, DEFAULT_MAX_FILES),
|
|
364
364
|
};
|
|
365
365
|
}
|
|
366
|
+
/** What a page's output may be drawn with (LOOP P-05), and the property of each that shows its value. */
|
|
367
|
+
export const PAGE_OUTPUT_SHOWS = {
|
|
368
|
+
Text: 'text',
|
|
369
|
+
Image: 'url',
|
|
370
|
+
Table: 'rows',
|
|
371
|
+
Chart: 'points',
|
|
372
|
+
};
|
|
373
|
+
/** The height of a component its developer wrote (P-17), unless it says. */
|
|
374
|
+
export const DEFAULT_CUSTOM_HEIGHT = 240;
|
|
375
|
+
/**
|
|
376
|
+
* A component its developer wrote (LOOP P-17), as written: its schema and
|
|
377
|
+
* example kept whole, so that the checks say what is wrong with them.
|
|
378
|
+
*/
|
|
379
|
+
function parseCustomComponent(data) {
|
|
380
|
+
const component = {
|
|
381
|
+
name: text(data.name),
|
|
382
|
+
description: text(data.description),
|
|
383
|
+
props: isData(data.props)
|
|
384
|
+
? JSON.parse(JSON.stringify(data.props))
|
|
385
|
+
: { type: 'object', properties: {} },
|
|
386
|
+
shows: texts(data.shows),
|
|
387
|
+
sends: texts(data.sends),
|
|
388
|
+
source: text(data.source),
|
|
389
|
+
integrity: text(data.integrity),
|
|
390
|
+
height: numberOf(data.height, DEFAULT_CUSTOM_HEIGHT),
|
|
391
|
+
};
|
|
392
|
+
if (isData(data.example)) {
|
|
393
|
+
component.example = JSON.parse(JSON.stringify(data.example));
|
|
394
|
+
}
|
|
395
|
+
return component;
|
|
396
|
+
}
|
|
397
|
+
function dumpCustomComponent(component) {
|
|
398
|
+
const written = new Writer()
|
|
399
|
+
.text('name', component.name, '\u0000')
|
|
400
|
+
.text('description', component.description, '\u0000');
|
|
401
|
+
written.data.props = JSON.parse(JSON.stringify(component.props));
|
|
402
|
+
written
|
|
403
|
+
.list('shows', component.shows)
|
|
404
|
+
.list('sends', component.sends)
|
|
405
|
+
.text('source', component.source, '\u0000')
|
|
406
|
+
.text('integrity', component.integrity)
|
|
407
|
+
.value('height', component.height, DEFAULT_CUSTOM_HEIGHT);
|
|
408
|
+
if (component.example) {
|
|
409
|
+
written.data.example = JSON.parse(JSON.stringify(component.example));
|
|
410
|
+
}
|
|
411
|
+
return written.data;
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* The property a page's output fills with its value (P-05): a catalog
|
|
415
|
+
* component's (`PAGE_OUTPUT_SHOWS`), or what a component of the
|
|
416
|
+
* application's own shows first (P-17). Undefined for any other.
|
|
417
|
+
*/
|
|
418
|
+
export function pageOutputShows(component, own = []) {
|
|
419
|
+
if (component in PAGE_OUTPUT_SHOWS) {
|
|
420
|
+
return PAGE_OUTPUT_SHOWS[component];
|
|
421
|
+
}
|
|
422
|
+
return own.find(each => each.name === component)?.shows[0];
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* A widget's page written in its code (P-05), as written: its inputs a form
|
|
426
|
+
* kept whole, an output drawn with another component kept as written so that
|
|
427
|
+
* the checks say it.
|
|
428
|
+
*/
|
|
429
|
+
function parsePage(data) {
|
|
430
|
+
const page = {
|
|
431
|
+
function: text(data.function),
|
|
432
|
+
inputs: (isData(data.inputs)
|
|
433
|
+
? JSON.parse(JSON.stringify(data.inputs))
|
|
434
|
+
: {}),
|
|
435
|
+
outputs: records(data.outputs).map(output => ({
|
|
436
|
+
name: text(output.name),
|
|
437
|
+
title: text(output.title),
|
|
438
|
+
component: text(output.component, 'Text'),
|
|
439
|
+
props: isData(output.props)
|
|
440
|
+
? JSON.parse(JSON.stringify(output.props))
|
|
441
|
+
: {},
|
|
442
|
+
})),
|
|
443
|
+
live: data.live !== false,
|
|
444
|
+
};
|
|
445
|
+
if (isData(data.inputs_ui)) {
|
|
446
|
+
page.inputsUi = JSON.parse(JSON.stringify(data.inputs_ui));
|
|
447
|
+
}
|
|
448
|
+
return page;
|
|
449
|
+
}
|
|
450
|
+
function dumpPage(page) {
|
|
451
|
+
const written = new Writer().text('function', page.function, '\u0000');
|
|
452
|
+
written.data.inputs = JSON.parse(JSON.stringify(page.inputs));
|
|
453
|
+
if (page.inputsUi) {
|
|
454
|
+
written.data.inputs_ui = { ...page.inputsUi };
|
|
455
|
+
}
|
|
456
|
+
written.list('outputs', page.outputs.map(output => new Writer()
|
|
457
|
+
.text('name', output.name, '\u0000')
|
|
458
|
+
.text('title', output.title)
|
|
459
|
+
.text('component', output.component, 'Text')
|
|
460
|
+
.part('props', { ...output.props }).data));
|
|
461
|
+
return written.value('live', page.live, true).data;
|
|
462
|
+
}
|
|
366
463
|
function parseInterface(data, kind) {
|
|
367
464
|
const parsed = {
|
|
368
465
|
layout: oneOf(data.layout, ['chat', 'page', 'split'], DEFAULT_LAYOUTS[kind]),
|
|
@@ -392,9 +489,16 @@ function parseInterface(data, kind) {
|
|
|
392
489
|
if (isData(data.uploads)) {
|
|
393
490
|
parsed.uploads = parseUploads(data.uploads);
|
|
394
491
|
}
|
|
492
|
+
// Components its developer wrote (P-17): absent when it has none.
|
|
493
|
+
if (Array.isArray(data.custom_components)) {
|
|
494
|
+
parsed.customComponents = records(data.custom_components).map(parseCustomComponent);
|
|
495
|
+
}
|
|
395
496
|
if (isData(data.surface)) {
|
|
396
497
|
parsed.surface = parseSurface(data.surface);
|
|
397
498
|
}
|
|
499
|
+
if (isData(data.page)) {
|
|
500
|
+
parsed.page = parsePage(data.page);
|
|
501
|
+
}
|
|
398
502
|
if (isAssistantCharacterId(data.assistant)) {
|
|
399
503
|
parsed.assistant = data.assistant;
|
|
400
504
|
}
|
|
@@ -452,6 +556,11 @@ function parseDeployment(data) {
|
|
|
452
556
|
? {
|
|
453
557
|
host: {
|
|
454
558
|
context: texts(data.embedded.host.context),
|
|
559
|
+
// Kept as written when said: a value that is none is refused
|
|
560
|
+
// by the checks, never read as `claimed` (D-21).
|
|
561
|
+
...(data.embedded.host.user !== undefined
|
|
562
|
+
? { user: text(data.embedded.host.user) }
|
|
563
|
+
: {}),
|
|
455
564
|
functions: records(data.embedded.host.functions).map(fn => ({
|
|
456
565
|
name: text(fn.name),
|
|
457
566
|
description: text(fn.description),
|
|
@@ -752,9 +861,13 @@ function dumpInterface(spec, kind) {
|
|
|
752
861
|
.value('max_files', spec.uploads.maxFiles, DEFAULT_MAX_FILES).data;
|
|
753
862
|
}
|
|
754
863
|
writer.list('components', spec.components);
|
|
864
|
+
writer.list('custom_components', (spec.customComponents ?? []).map(dumpCustomComponent));
|
|
755
865
|
if (spec.surface) {
|
|
756
866
|
writer.data.surface = dumpSurface(spec.surface);
|
|
757
867
|
}
|
|
868
|
+
if (spec.page) {
|
|
869
|
+
writer.data.page = dumpPage(spec.page);
|
|
870
|
+
}
|
|
758
871
|
if (spec.assistant) {
|
|
759
872
|
writer.data.assistant = spec.assistant;
|
|
760
873
|
}
|
|
@@ -905,7 +1018,8 @@ export function dumpAppspec(app) {
|
|
|
905
1018
|
JSON.stringify(NO_HOST_PARAMETERS)
|
|
906
1019
|
? {}
|
|
907
1020
|
: { parameters: fn.parameters }),
|
|
908
|
-
})))
|
|
1021
|
+
})))
|
|
1022
|
+
.value('user', app.deployment.embedded.host.user ?? 'claimed', 'claimed').data).data).data)
|
|
909
1023
|
.text('goal', app.goal)
|
|
910
1024
|
.list('triggers', app.triggers.map(trigger => new Writer()
|
|
911
1025
|
.text('type', trigger.type, '\u0000')
|
|
@@ -20,6 +20,14 @@ export declare const componentNamed: (name: string) => ComponentSpec | undefined
|
|
|
20
20
|
* once; a tool says what it does, by class, each class once.
|
|
21
21
|
*/
|
|
22
22
|
export declare function ownCodeProblems(app: AppSpec): string[];
|
|
23
|
+
/**
|
|
24
|
+
* What agentspecs refuses of a widget's page written in its code (LOOP
|
|
25
|
+
* P-05): a page that is not a widget's, inputs that are not a form, outputs
|
|
26
|
+
* of one name twice, drawn with another component, saying in their `props`
|
|
27
|
+
* what their value fills or lacking what their component needs, and an input
|
|
28
|
+
* named as a setting — both are on the page at `/inputs/<name>`.
|
|
29
|
+
*/
|
|
30
|
+
export declare function pageProblems(app: AppSpec): string[];
|
|
23
31
|
/** How an organization's own context is named: never a catalogue id (agentspecs' `ORGANIZATION_FRAME_PREFIX`). */
|
|
24
32
|
/**
|
|
25
33
|
* What stops a Form block from asking (LOOP C-16), in agentspecs'
|