@datalayer/agent-runtimes 1.3.83 → 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.
Files changed (168) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +30 -11
  17. package/lib/chat/base/loopElement.d.ts +53 -0
  18. package/lib/chat/base/loopElement.js +78 -0
  19. package/lib/chat/base/loopWindow.d.ts +31 -0
  20. package/lib/chat/base/loopWindow.js +74 -0
  21. package/lib/chat/display/EmptyState.js +3 -1
  22. package/lib/chat/index.d.ts +2 -0
  23. package/lib/chat/index.js +3 -0
  24. package/lib/chat/messages/ChatMessages.js +3 -1
  25. package/lib/chat/messages/TurnFooter.js +3 -1
  26. package/lib/chat/prompt/InputPrompt.js +3 -1
  27. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  28. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  29. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  30. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  31. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  32. package/lib/chat/words.d.ts +102 -0
  33. package/lib/chat/words.js +460 -0
  34. package/lib/components/a2ui/custom/index.d.ts +39 -0
  35. package/lib/components/a2ui/custom/index.js +95 -0
  36. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  37. package/lib/components/a2ui/custom/sandbox.js +188 -0
  38. package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
  39. package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
  40. package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
  41. package/lib/components/a2ui/datalayer/Form.js +9 -2
  42. package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
  43. package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
  44. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/index.js +1 -1
  46. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  47. package/lib/components/a2ui/datalayer/schema.js +12 -1
  48. package/lib/components/a2ui/index.d.ts +1 -0
  49. package/lib/components/a2ui/index.js +1 -0
  50. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  51. package/lib/components/a2ui/visibleWhen.js +24 -3
  52. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  53. package/lib/components/inspector/AgentInspector.js +27 -215
  54. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  55. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  56. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  57. package/lib/components/inspector/a2aSpans.js +373 -0
  58. package/lib/components/inspector/agentSpans.d.ts +120 -0
  59. package/lib/components/inspector/agentSpans.js +253 -0
  60. package/lib/components/inspector/chatSpans.d.ts +36 -0
  61. package/lib/components/inspector/chatSpans.js +50 -0
  62. package/lib/components/inspector/classify.d.ts +11 -8
  63. package/lib/components/inspector/classify.js +23 -24
  64. package/lib/components/inspector/index.d.ts +7 -7
  65. package/lib/components/inspector/index.js +7 -7
  66. package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
  67. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  68. package/lib/components/teams/SceneTranscript.js +54 -0
  69. package/lib/components/teams/SceneView.d.ts +41 -0
  70. package/lib/components/teams/SceneView.js +28 -0
  71. package/lib/components/teams/index.d.ts +3 -0
  72. package/lib/components/teams/index.js +3 -0
  73. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  74. package/lib/components/teams/sceneTranscript.js +294 -0
  75. package/lib/components/teams/teamConnections.d.ts +2 -2
  76. package/lib/components/teams/teamConnections.js +3 -3
  77. package/lib/components/teams/useA2ATeam.d.ts +4 -4
  78. package/lib/components/teams/useA2ATeam.js +50 -46
  79. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  80. package/lib/examples/AgentA2ATeamExample.js +70 -60
  81. package/lib/examples/AssistantExample.js +2 -2
  82. package/lib/loop/apps/AppComposer.d.ts +40 -4
  83. package/lib/loop/apps/AppComposer.js +194 -19
  84. package/lib/loop/apps/AppRenderer.d.ts +21 -3
  85. package/lib/loop/apps/AppRenderer.js +88 -48
  86. package/lib/loop/apps/appspec.d.ts +13 -1
  87. package/lib/loop/apps/appspec.js +305 -12
  88. package/lib/loop/apps/checks.d.ts +14 -0
  89. package/lib/loop/apps/checks.js +276 -5
  90. package/lib/loop/apps/composer.d.ts +21 -10
  91. package/lib/loop/apps/composer.js +41 -8
  92. package/lib/loop/apps/customComponents.d.ts +41 -0
  93. package/lib/loop/apps/customComponents.js +260 -0
  94. package/lib/loop/apps/hostTools.d.ts +8 -1
  95. package/lib/loop/apps/hostTools.js +9 -0
  96. package/lib/loop/apps/index.d.ts +1 -0
  97. package/lib/loop/apps/index.js +1 -0
  98. package/lib/loop/apps/language.d.ts +68 -0
  99. package/lib/loop/apps/language.js +275 -0
  100. package/lib/loop/apps/settingsInputs.d.ts +29 -0
  101. package/lib/loop/apps/settingsInputs.js +147 -0
  102. package/lib/loop/apps/uploads.d.ts +43 -0
  103. package/lib/loop/apps/uploads.js +66 -0
  104. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  105. package/lib/loop/core/a2uiComponents.js +15 -0
  106. package/lib/loop/core/index.d.ts +15 -1
  107. package/lib/loop/core/index.js +3 -0
  108. package/lib/loop/embed/AppEmbed.d.ts +25 -3
  109. package/lib/loop/embed/AppEmbed.js +106 -18
  110. package/lib/loop/embed/element.d.ts +5 -0
  111. package/lib/loop/embed/element.js +84 -5
  112. package/lib/loop/embed/embedConfig.d.ts +8 -0
  113. package/lib/loop/embed/embedConfig.js +18 -0
  114. package/lib/loop/embed/embedSession.d.ts +14 -0
  115. package/lib/loop/embed/embedSession.js +34 -0
  116. package/lib/loop/embed/hostBridge.d.ts +50 -3
  117. package/lib/loop/embed/hostBridge.js +22 -2
  118. package/lib/loop/index.d.ts +4 -3
  119. package/lib/loop/index.js +4 -3
  120. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  121. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  122. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  123. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  124. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  125. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  126. package/lib/loop/plugins/app-components/index.js +56 -0
  127. package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
  128. package/lib/loop/plugins/app-elements/AppElements.js +79 -0
  129. package/lib/loop/plugins/app-elements/index.d.ts +20 -0
  130. package/lib/loop/plugins/app-elements/index.js +43 -0
  131. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  132. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  133. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  134. package/lib/loop/plugins/app-page/appPageModel.js +122 -4
  135. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  136. package/lib/loop/plugins/app-page/index.js +1 -0
  137. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  138. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  139. package/lib/loop/plugins/assistant/index.js +8 -6
  140. package/lib/loop/plugins/chat/ChatView.js +16 -6
  141. package/lib/loop/plugins/chat/index.d.ts +1 -1
  142. package/lib/loop/plugins/chat/index.js +2 -1
  143. package/lib/models/PageTag.d.ts +1 -1
  144. package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
  145. package/lib/protocols/BrowserAgentAdapter.js +13 -10
  146. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  147. package/lib/runtimes/browser/protocol.d.ts +1 -1
  148. package/lib/specs/actions.js +78 -1
  149. package/lib/specs/apps.d.ts +5 -0
  150. package/lib/specs/apps.js +1518 -216
  151. package/lib/specs/appspecSchema.js +630 -3
  152. package/lib/specs/teams/teams.d.ts +3 -0
  153. package/lib/specs/teams/teams.js +249 -0
  154. package/lib/specs/uiPlugins.js +6 -1
  155. package/lib/types/agentspecs.d.ts +200 -1
  156. package/lib/types/chat.d.ts +6 -5
  157. package/lib/types/teams.d.ts +13 -2
  158. package/package.json +2 -2
  159. package/scripts/codegen/generate_apps.py +20 -2
  160. package/scripts/codegen/generate_teams.py +15 -2
  161. package/lib/components/inspector/JsonTree.d.ts +0 -24
  162. package/lib/components/inspector/JsonTree.js +0 -89
  163. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  164. package/lib/components/inspector/a2aInspect.js +0 -478
  165. package/lib/components/inspector/agentInspector.d.ts +0 -179
  166. package/lib/components/inspector/agentInspector.js +0 -267
  167. package/lib/components/inspector/chatInspect.d.ts +0 -38
  168. 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: Sales' — its
23
- * model turns, its `ask_accounting` call, the A2A messages it sends and
24
- * receives — and Accounting's — what it received over A2A, its Odoo MCP
25
- * calls, `write_notebook`, its status updates and artifacts, as the A2A
26
- * stream tells them. Both read one record (`useAgentInspector`), fed by the
27
- * team (`useA2ATeam`'s `inspector`) and by the fetch the A2A client sends
28
- * with (`inspectA2AFetch`); an A2A message shows in both, sent in one and
29
- * received in the other. Right-click a member for its menu: *Inspect the
30
- * agent…* opens its inspector over the page.
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, inspectA2AFetch, useAgentInspector, } from '../components/inspector';
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 record of what both members do, each inspector showing its own.
50
- const { sink: inspector } = useAgentInspector();
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, recorded: Sales sends, Accounting answers.
65
- fetch: inspectA2AFetch(globalThis.fetch.bind(globalThis), {
66
- sink: inspector,
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
- id: SALES.id,
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
- spec: `${SALES.id}:${SALES.version}`,
141
- model: SALES.model || undefined,
142
- where: 'in your browser',
143
- description: SALES.description || undefined,
144
- },
145
- }, peer: {
146
- id: ACCOUNTING.id,
147
- name: ACCOUNTING.name,
148
- // Its face as its card says it, once it is reached.
149
- emoji: peer?.face?.emoji ?? ACCOUNTING.emoji,
150
- character: ACCOUNTING.interface.assistant ?? 'wizard',
151
- where: accountingWhere,
152
- persona: team.peerPersona,
153
- onAway: team.setPeerAway,
154
- history: team.peerHistory,
155
- connections: ACCOUNTING_CONNECTIONS,
156
- inspector,
157
- sandbox: accountingSandbox,
158
- about: {
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
- spec: `${ACCOUNTING.id}:${ACCOUNTING.version}`,
161
- model: ACCOUNTING.model || undefined,
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
- // What its A2A card says, once it is reached.
164
- description: peer?.card.description || ACCOUNTING.description || undefined,
165
- skills: peer?.card.skills.map(skill => skill.name),
166
- protocol: peer ? 'a2a' : undefined,
167
- url: peer ? accountingUrl : undefined,
168
- },
169
- }, flow: team.flow, calls: team.calls,
170
- // Room for the balloons: suggestions, *more* and a notebook.
171
- 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: {
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, { sink: inspector, agent: member.name, maxHeight: 360, exportName: `agent-inspector-${member.id}`, emptyText: member === SALES
177
- ? 'Its model turns, its call to Accounting and the A2A messages it sends and receives show here.'
178
- : 'What it receives over A2A, 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 => {
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/agentInspector';
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 { sink: inspector } = useAgentInspector();
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(() => {
@@ -9,17 +9,53 @@
9
9
  * one menu each; the option picked goes with every run as
10
10
  * `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
11
11
  * agent its instructions, on the model it names, for that run.
12
+ * - What a person may **send** without being asked (LOOP P-21,
13
+ * `interface.uploads`): a paper clip beside the composer takes images,
14
+ * files and recordings of the kinds and sizes the Appspec says — one it
15
+ * does not take is refused in a sentence and never read — and the files
16
+ * held go with the next message as `forwardedProps.loop.files`, then are
17
+ * let go. The runtime refuses again what was sent all the same.
18
+ * - Its **profiles** (LOOP P-20, `interface.profiles`): a picker beside the
19
+ * composer, on the first unless another is picked. The profile picked goes
20
+ * with every run as `forwardedProps.loop.profile`, and its starters (else
21
+ * the application's) are what the empty chat offers, by category
22
+ * (`LoopChatExtras.openers`). Once the first message is sent, the
23
+ * conversation keeps its profile: the picker says so and picks no other.
24
+ *
25
+ * Given the application as the person reads it (`translatedAppspec`, LOOP
26
+ * P-26), its words are theirs; the composer's own, the chat's language's
27
+ * (`useChatLanguage`).
12
28
  *
13
29
  * @module loop/apps/AppComposer
14
30
  */
15
- import type { JSX } from 'react';
16
- import type { AppSpec } from '../../types/agentspecs';
31
+ import { type JSX } from 'react';
32
+ import type { AppSpec, AppStarterSpec } from '../../types/agentspecs';
33
+ import { type GivenFile } from '../../components/a2ui/datalayer/FileUpload';
34
+ import { type ChatSuggestionItem } from '../core';
35
+ /** What the composer holds to send: the files, and why the last were refused. */
36
+ export type HeldUploads = {
37
+ files: GivenFile[];
38
+ refused: string | null;
39
+ };
40
+ /**
41
+ * Files chosen in the composer, held for the next message: the refusal the
42
+ * runtime would say for what the application does not take, else the files
43
+ * read. Nothing is read when one is refused.
44
+ */
45
+ export declare function holdUploads(app: Pick<AppSpec, 'name' | 'interface'>, held: readonly GivenFile[], chosen: readonly File[], read?: (file: File) => Promise<GivenFile>): Promise<HeldUploads>;
17
46
  /** The plugin's name, before the application's id. */
18
47
  export declare const APP_COMPOSER_PLUGIN_NAME = "@datalayer/loop-plugin-app-composer";
48
+ /** Starters as the empty chat offers them: each under its category (P-20). */
49
+ export declare function startersAsOpeners(starters: readonly AppStarterSpec[]): ChatSuggestionItem[];
50
+ /**
51
+ * Whether an application needs its composer plugin: commands, modes,
52
+ * uploads or profiles to offer there.
53
+ */
54
+ export declare const needsAppComposer: (app: AppSpec) => boolean;
19
55
  /**
20
56
  * The plugin that puts an application's commands in the composer's `/` menu
21
- * and its modes beside the composer. Nothing for an application with
22
- * neither: its plugin contributes nothing.
57
+ * and its modes, its profiles and its paper clip beside the composer.
58
+ * Nothing for an application with none: its plugin contributes nothing.
23
59
  */
24
60
  export declare function defineAppComposerPlugin(app: AppSpec): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, {
25
61
  components: {
@@ -1,17 +1,118 @@
1
- import { jsxs as _jsxs, jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
2
- import { ActionList, ActionMenu } from '@primer/react';
3
- import { contribution, definePlugin, signal } from '@datalayer/reactor';
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * An application's commands and modes in the composer (LOOP P-19).
8
+ *
9
+ * - Each of its **commands** is a workspace command (`LoopCommand`), listed
10
+ * in the composer's `/` menu (`composer`): picking it writes `/<name> `,
11
+ * and sending runs it — its prompt goes to the agent, the words typed
12
+ * after it in place of `{input}`.
13
+ * - Its **modes** are switches beside the composer (`LoopSlots.promptAction`),
14
+ * one menu each; the option picked goes with every run as
15
+ * `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
16
+ * agent its instructions, on the model it names, for that run.
17
+ * - What a person may **send** without being asked (LOOP P-21,
18
+ * `interface.uploads`): a paper clip beside the composer takes images,
19
+ * files and recordings of the kinds and sizes the Appspec says — one it
20
+ * does not take is refused in a sentence and never read — and the files
21
+ * held go with the next message as `forwardedProps.loop.files`, then are
22
+ * let go. The runtime refuses again what was sent all the same.
23
+ * - Its **profiles** (LOOP P-20, `interface.profiles`): a picker beside the
24
+ * composer, on the first unless another is picked. The profile picked goes
25
+ * with every run as `forwardedProps.loop.profile`, and its starters (else
26
+ * the application's) are what the empty chat offers, by category
27
+ * (`LoopChatExtras.openers`). Once the first message is sent, the
28
+ * conversation keeps its profile: the picker says so and picks no other.
29
+ *
30
+ * Given the application as the person reads it (`translatedAppspec`, LOOP
31
+ * P-26), its words are theirs; the composer's own, the chat's language's
32
+ * (`useChatLanguage`).
33
+ *
34
+ * @module loop/apps/AppComposer
35
+ */
36
+ import { useRef } from 'react';
37
+ import { ActionList, ActionMenu, IconButton, Text, Token } from '@primer/react';
38
+ import { PaperclipIcon } from '@primer/octicons-react';
39
+ import { computed, contribution, definePlugin, signal, } from '@datalayer/reactor';
4
40
  import { useSignalValue } from '@datalayer/reactor/react';
5
- import { LoopCommand, LoopRunProps, LoopSlots } from '../core';
6
- import { commandPrompt, modeChoice, modeDefaults } from './composer';
41
+ import { readFile, } from '../../components/a2ui/datalayer/FileUpload';
42
+ import { LoopChatExtras, LoopCommand, LoopRunProps, LoopSlots, } from '../core';
43
+ import { commandPrompt, modeChoice, modeDefaults, profileChoice, startersFor, } from './composer';
44
+ import { interfaceWords } from './language';
45
+ import { useChatLanguage } from '../../chat/ChatLanguage';
46
+ import { uploadsAccept, uploadsInWords, uploadsRefusal } from './uploads';
47
+ /**
48
+ * Files chosen in the composer, held for the next message: the refusal the
49
+ * runtime would say for what the application does not take, else the files
50
+ * read. Nothing is read when one is refused.
51
+ */
52
+ export async function holdUploads(app, held, chosen, read = readFile) {
53
+ const refused = uploadsRefusal(app, [
54
+ ...held,
55
+ ...chosen.map(file => ({
56
+ name: file.name,
57
+ type: file.type,
58
+ size: file.size,
59
+ })),
60
+ ]);
61
+ if (refused) {
62
+ return { files: [...held], refused };
63
+ }
64
+ return {
65
+ files: [...held, ...(await Promise.all(chosen.map(read)))],
66
+ refused: null,
67
+ };
68
+ }
7
69
  /** The plugin's name, before the application's id. */
8
70
  export const APP_COMPOSER_PLUGIN_NAME = '@datalayer/loop-plugin-app-composer';
71
+ /** Starters as the empty chat offers them: each under its category (P-20). */
72
+ export function startersAsOpeners(starters) {
73
+ return starters.map(starter => ({
74
+ text: starter.label,
75
+ message: starter.message,
76
+ ...(starter.category ? { group: starter.category } : {}),
77
+ }));
78
+ }
79
+ /**
80
+ * Whether an application needs its composer plugin: commands, modes,
81
+ * uploads or profiles to offer there.
82
+ */
83
+ export const needsAppComposer = (app) => (app.interface.commands ?? []).length > 0 ||
84
+ (app.interface.modes ?? []).length > 0 ||
85
+ (app.interface.profiles ?? []).length > 0 ||
86
+ Boolean(app.interface.uploads);
9
87
  /**
10
88
  * The plugin that puts an application's commands in the composer's `/` menu
11
- * and its modes beside the composer. Nothing for an application with
12
- * neither: its plugin contributes nothing.
89
+ * and its modes, its profiles and its paper clip beside the composer.
90
+ * Nothing for an application with none: its plugin contributes nothing.
13
91
  */
14
92
  export function defineAppComposerPlugin(app) {
93
+ const profiles = app.interface.profiles ?? [];
94
+ /** The profile picked, and whether the conversation has started with it. */
95
+ const profile = signal(profileChoice(app)?.id);
96
+ const started = signal(false);
97
+ const openers = computed(() => ({
98
+ openers: startersAsOpeners(startersFor(app, profile.value)),
99
+ }));
100
+ function ProfilePicker() {
101
+ // In the chat's language, as the chat's own words (P-26).
102
+ const words = interfaceWords(useChatLanguage());
103
+ const now = profileChoice(app, useSignalValue(profile));
104
+ const kept = useSignalValue(started);
105
+ if (!now) {
106
+ return null;
107
+ }
108
+ const said = `${words.profile}: ${now.label}`;
109
+ if (kept) {
110
+ return (_jsx(Text, { title: `${said} (${words.profileKept})`, sx: { fontSize: 0, color: 'fg.muted', px: 2 }, children: said }));
111
+ }
112
+ return (_jsxs(ActionMenu, { children: [_jsx(ActionMenu.Button, { size: "small", variant: "invisible", "aria-label": said, children: said }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsx(ActionList, { selectionVariant: "single", children: _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: words.profile }), profiles.map(item => (_jsxs(ActionList.Item, { selected: item.id === now.id, onSelect: () => {
113
+ profile.value = profileChoice(app, item.id)?.id;
114
+ }, children: [item.label, item.description ? (_jsx(ActionList.Description, { variant: "block", children: item.description })) : null] }, item.id)))] }) }) })] }));
115
+ }
15
116
  const modes = app.interface.modes ?? [];
16
117
  /** The option of each mode the person is in. */
17
118
  const chosen = signal(modeDefaults(app));
@@ -28,13 +129,49 @@ export function defineAppComposerPlugin(app) {
28
129
  }, children: [option.label, option.description ? (_jsx(ActionList.Description, { variant: "block", children: option.description })) : null] }, option.id)))] }) }) })] }, mode.id));
29
130
  }) }));
30
131
  }
132
+ const uploads = app.interface.uploads;
133
+ /** The files held for the next message, and the last refusal (P-21). */
134
+ const held = signal({ files: [], refused: null });
135
+ function Attach() {
136
+ const words = interfaceWords(useChatLanguage());
137
+ const now = useSignalValue(held);
138
+ const input = useRef(null);
139
+ if (!uploads) {
140
+ return null;
141
+ }
142
+ return (_jsxs(_Fragment, { children: [_jsx(IconButton, { icon: PaperclipIcon, size: "small", variant: "invisible", "aria-label": `${words.attach}. ${uploadsInWords(uploads)}`, onClick: () => input.current?.click() }), _jsx("input", { ref: input, type: "file", hidden: true, multiple: uploads.maxFiles > 1, accept: uploadsAccept(uploads), "aria-label": words.attach, onChange: event => {
143
+ const chosen = Array.from(event.target.files ?? []);
144
+ event.target.value = '';
145
+ void holdUploads(app, held.peek().files, chosen).then(next => {
146
+ held.value = next;
147
+ });
148
+ } }), now.files.map(file => (_jsx(Token, { text: file.name, size: "small", onRemove: () => {
149
+ held.value = {
150
+ files: held.peek().files.filter(kept => kept !== file),
151
+ refused: null,
152
+ };
153
+ } }, file.name))), now.refused ? (_jsx(Text, { role: "alert", sx: { fontSize: 0, color: 'danger.fg' }, children: now.refused })) : null] }));
154
+ }
31
155
  return definePlugin({
32
156
  name: `${APP_COMPOSER_PLUGIN_NAME}-${app.id}`,
33
- displayName: `${app.name}: commands and modes`,
34
- description: `The commands and modes ${app.name} offers in its composer.`,
157
+ displayName: `${app.name}: commands, modes, profiles and uploads`,
158
+ description: `The commands, modes, profiles and files ${app.name} takes in its composer.`,
35
159
  octicon: 'command-palette',
36
160
  emoji: '\u{2318}',
37
161
  contributes: [
162
+ ...(profiles.length > 0
163
+ ? [
164
+ contribution(LoopRunProps, {
165
+ id: 'app-profile',
166
+ // Sent with every run; from the first, the conversation keeps it.
167
+ props: () => {
168
+ started.value = true;
169
+ return { loop: { profile: profile.peek() } };
170
+ },
171
+ }, { id: 'app-profile' }),
172
+ contribution(LoopChatExtras, { id: 'app-profile-starters', extras: openers }, { id: 'app-profile-starters' }),
173
+ ]
174
+ : []),
38
175
  ...(app.interface.commands ?? []).map(command => contribution(LoopCommand, {
39
176
  name: command.name,
40
177
  description: command.description,
@@ -50,18 +187,56 @@ export function defineAppComposerPlugin(app) {
50
187
  }, { id: 'app-modes' }),
51
188
  ]
52
189
  : []),
53
- ],
54
- build: () => ({
55
- components: modes.length > 0
190
+ ...(uploads
56
191
  ? [
57
- {
58
- id: 'app-modes',
59
- slot: LoopSlots.promptAction,
60
- order: 5,
61
- Component: ModeSwitches,
62
- },
192
+ contribution(LoopRunProps, {
193
+ id: 'app-uploads',
194
+ // The files held go with this message, once, and are let go.
195
+ props: () => {
196
+ const files = held.peek().files;
197
+ if (files.length === 0) {
198
+ return {};
199
+ }
200
+ held.value = { files: [], refused: null };
201
+ return { loop: { files } };
202
+ },
203
+ }, { id: 'app-uploads' }),
63
204
  ]
64
- : [],
205
+ : []),
206
+ ],
207
+ build: () => ({
208
+ components: [
209
+ ...(profiles.length > 0
210
+ ? [
211
+ {
212
+ id: 'app-profile',
213
+ slot: LoopSlots.promptAction,
214
+ order: 3,
215
+ Component: ProfilePicker,
216
+ },
217
+ ]
218
+ : []),
219
+ ...(modes.length > 0
220
+ ? [
221
+ {
222
+ id: 'app-modes',
223
+ slot: LoopSlots.promptAction,
224
+ order: 5,
225
+ Component: ModeSwitches,
226
+ },
227
+ ]
228
+ : []),
229
+ ...(uploads
230
+ ? [
231
+ {
232
+ id: 'app-uploads',
233
+ slot: LoopSlots.promptAction,
234
+ order: 4,
235
+ Component: Attach,
236
+ },
237
+ ]
238
+ : []),
239
+ ],
65
240
  }),
66
241
  });
67
242
  }
@@ -28,6 +28,11 @@
28
28
  * address and embedded;
29
29
  * - its **frame**, when the host asks (`frame`): the `window-frame` plugin's
30
30
  * window, its title the application's face and name;
31
+ * - its **language** (LOOP P-26): what a person reads of it — its name,
32
+ * welcome, starters, settings, commands, modes and profiles — in the first
33
+ * language they prefer that its Appspec is translated into (`language`,
34
+ * else the browser's), its own words otherwise (`translatedAppspec`);
35
+ * what it does is the same in every language;
31
36
  * - its **sidebar**, when the host asks (`sidebar`, R-01b): its rules and
32
37
  * the approvals waiting for the person (`app-rules`), what it did
33
38
  * (`app-activity`), and its computer, live (`app-computer`, R-23), beside
@@ -62,7 +67,7 @@ export declare const agentIdOf: (app: Pick<AppSpec, "agent" | "team">) => string
62
67
  * An application run by a team is not supported in the workspace yet: a team
63
68
  * is not an agent spec, and is refused here rather than launched as one.
64
69
  */
65
- export declare function defineAppPlugin(app: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
70
+ export declare function defineAppPlugin(app: AppSpec, shown?: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
66
71
  /** Which instance of an application runs, as the platform knows it (LOOP R-07). */
67
72
  export type AppInstance = {
68
73
  /** Its `app` item. */
@@ -132,6 +137,12 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
132
137
  * Canvas's palette. Compared by content.
133
138
  */
134
139
  pluginsOff?: readonly string[];
140
+ /**
141
+ * The language the person reads it in, as BCP 47 tags it (`fr`, `pt-BR`):
142
+ * the platform's setting when the host knows it. The browser's languages
143
+ * when unsaid (LOOP P-26).
144
+ */
145
+ language?: string;
135
146
  };
136
147
  /** What `interface.layout` sets on the workspace. */
137
148
  export type AppLayoutOptions = Pick<LoopPresetOptions, 'editors' | 'showViewSelector' | 'defaultEditor' | 'pageLayout' | 'pageLayoutArrangement' | 'pageLayoutPrompt' | 'pageLayoutPromptAnchor' | 'pageLayoutTurnPanelFooter'>;
@@ -164,7 +175,9 @@ export type AppPreset = {
164
175
  * but those of the UI plugins its organization turned off (`pluginsOff`),
165
176
  * whose contributions are the blocks it may draw (R-01b); a thumb and a
166
177
  * comment on each answer when its record keeps feedback (V-18); its
167
- * commands in the composer's `/` menu and its modes beside it (P-19); what it
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); the
180
+ * components its developer wrote (`app-components`, P-17); what it
168
181
  * keeps and for how long, under its prompt before the first message, when
169
182
  * `kept` says it (R-31); with
170
183
  * `sidebar`, its rules and approvals card, its activity feed and its
@@ -186,6 +199,11 @@ export declare function appPreset(app: AppSpec, options?: {
186
199
  computer?: boolean;
187
200
  /** What its record is kept under: its activity's. */
188
201
  appUid?: string;
202
+ /**
203
+ * The application as the person reads it (P-26): its words in their
204
+ * language. Its own when unsaid.
205
+ */
206
+ shown?: AppSpec;
189
207
  /**
190
208
  * Say what it keeps before the first message (R-31): at its address and
191
209
  * embedded, not in its builder's Preview — to a `visitor` not signed
@@ -221,5 +239,5 @@ export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
221
239
  * colours: over another theme (T-30) nothing is laid, the theme's own rule.
222
240
  */
223
241
  export declare function appThemeOverrides(app: AppSpec, variant: string): ThemeOverrides | undefined;
224
- export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, paused, sidebar, computer, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
242
+ export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, paused, sidebar, computer, frame, page, pluginsOff, language, ...embed }: AppRendererProps): React.JSX.Element;
225
243
  export default AppRenderer;