@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
@@ -0,0 +1,46 @@
1
+ /**
2
+ * A widget's page written in its code, run on its inputs (LOOP P-05).
3
+ *
4
+ * The page opens a session of the application's agent on its runtime
5
+ * (`POST /api/v1/apps/sessions`), then, each time an input changes, sends
6
+ * them all as the session's `page` action (`POST …/sessions/{uid}/actions`,
7
+ * `{name: "page", payload: {inputs}}`). The runtime checks them against the
8
+ * page's form — refused with 422 and a sentence — runs its code's
9
+ * `@app.page` on them, and answers its outputs as a `CUSTOM` event named
10
+ * `loop.page`, `{inputs, outputs}`, which the page shows in place. Nothing is
11
+ * said in the conversation.
12
+ *
13
+ * @module loop/plugins/app-page/pageRun
14
+ */
15
+ /** The `CUSTOM` event that says what a page shows. */
16
+ export declare const LOOP_PAGE = "loop.page";
17
+ /** Where the page reaches the application's session API, and as whom. */
18
+ export type PageRunContext = {
19
+ /** The agent-runtimes server that runs the application. */
20
+ serverUrl: string;
21
+ /** The application's agent there. */
22
+ agentId: string;
23
+ /** The person's token; none on the machine itself. */
24
+ token?: string;
25
+ fetcher?: typeof fetch;
26
+ };
27
+ /** What a turn of the session API said: the events of its stream, parsed. */
28
+ export declare function streamEvents(stream: string): Array<Record<string, unknown>>;
29
+ /** What a run of the page answered: its outputs, by name, or why there are none. */
30
+ export type PageTurn = {
31
+ outputs: Record<string, unknown>;
32
+ inputs: Record<string, unknown>;
33
+ } | {
34
+ refused: string;
35
+ };
36
+ /** The page's turn, read from the stream the runtime answered. */
37
+ export declare function pageTurnOf(stream: string): PageTurn;
38
+ /** Open the session its page runs in; its uid, as the runtime says it. */
39
+ export declare function openPageSession(context: PageRunContext): Promise<string>;
40
+ /**
41
+ * Run the page on its inputs in a session: its outputs, or the runtime's
42
+ * sentence — its form refusing an input, its code failing.
43
+ */
44
+ export declare function runPageIn(context: PageRunContext, uid: string, inputs: Record<string, unknown>): Promise<PageTurn>;
45
+ /** Each output as the page's data holds it, by path. */
46
+ export declare function outputsData(outputs: Record<string, unknown>): Record<string, unknown>;
@@ -0,0 +1,111 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * A widget's page written in its code, run on its inputs (LOOP P-05).
7
+ *
8
+ * The page opens a session of the application's agent on its runtime
9
+ * (`POST /api/v1/apps/sessions`), then, each time an input changes, sends
10
+ * them all as the session's `page` action (`POST …/sessions/{uid}/actions`,
11
+ * `{name: "page", payload: {inputs}}`). The runtime checks them against the
12
+ * page's form — refused with 422 and a sentence — runs its code's
13
+ * `@app.page` on them, and answers its outputs as a `CUSTOM` event named
14
+ * `loop.page`, `{inputs, outputs}`, which the page shows in place. Nothing is
15
+ * said in the conversation.
16
+ *
17
+ * @module loop/plugins/app-page/pageRun
18
+ */
19
+ /** The `CUSTOM` event that says what a page shows. */
20
+ export const LOOP_PAGE = 'loop.page';
21
+ /** What a turn of the session API said: the events of its stream, parsed. */
22
+ export function streamEvents(stream) {
23
+ const events = [];
24
+ for (const block of stream.split('\n\n')) {
25
+ const data = block
26
+ .split('\n')
27
+ .filter(line => line.startsWith('data:'))
28
+ .map(line => line.slice('data:'.length).trim())
29
+ .join('');
30
+ if (!data)
31
+ continue;
32
+ try {
33
+ events.push(JSON.parse(data));
34
+ }
35
+ catch {
36
+ // Not an event: skipped, as a reader of the stream skips it.
37
+ }
38
+ }
39
+ return events;
40
+ }
41
+ /** The page's turn, read from the stream the runtime answered. */
42
+ export function pageTurnOf(stream) {
43
+ let shown = null;
44
+ for (const event of streamEvents(stream)) {
45
+ if (event.type === 'RUN_ERROR') {
46
+ return { refused: String(event.message ?? 'Its page could not run.') };
47
+ }
48
+ if (event.type === 'CUSTOM' && event.name === LOOP_PAGE) {
49
+ const value = (event.value ?? {});
50
+ shown = { outputs: value.outputs ?? {}, inputs: value.inputs ?? {} };
51
+ }
52
+ }
53
+ return (shown ?? {
54
+ refused: 'Its page showed nothing: its code answered no outputs.',
55
+ });
56
+ }
57
+ const base = (context) => `${context.serverUrl.replace(/\/+$/, '')}/api/v1/apps/sessions`;
58
+ async function posted(context, url, body) {
59
+ const response = await (context.fetcher ?? fetch)(url, {
60
+ method: 'POST',
61
+ headers: {
62
+ 'content-type': 'application/json',
63
+ ...(context.token ? { Authorization: `Bearer ${context.token}` } : {}),
64
+ },
65
+ body: JSON.stringify(body),
66
+ });
67
+ if (!response.ok) {
68
+ let detail = '';
69
+ try {
70
+ const said = (await response.json());
71
+ detail = typeof said.detail === 'string' ? said.detail : '';
72
+ }
73
+ catch {
74
+ // No sentence: the status says it.
75
+ }
76
+ throw new Error(detail || `Its page was refused (${response.status}).`);
77
+ }
78
+ return response.text();
79
+ }
80
+ /** Open the session its page runs in; its uid, as the runtime says it. */
81
+ export async function openPageSession(context) {
82
+ const stream = await posted(context, base(context), {
83
+ agent: context.agentId,
84
+ });
85
+ for (const event of streamEvents(stream)) {
86
+ if (event.type === 'CUSTOM' && event.name === 'loop.session') {
87
+ const uid = event.value?.uid;
88
+ if (typeof uid === 'string' && uid) {
89
+ return uid;
90
+ }
91
+ }
92
+ }
93
+ throw new Error('The runtime opened no session for its page.');
94
+ }
95
+ /**
96
+ * Run the page on its inputs in a session: its outputs, or the runtime's
97
+ * sentence — its form refusing an input, its code failing.
98
+ */
99
+ export async function runPageIn(context, uid, inputs) {
100
+ try {
101
+ const stream = await posted(context, `${base(context)}/${encodeURIComponent(uid)}/actions`, { name: 'page', payload: { inputs } });
102
+ return pageTurnOf(stream);
103
+ }
104
+ catch (error) {
105
+ return { refused: error instanceof Error ? error.message : String(error) };
106
+ }
107
+ }
108
+ /** Each output as the page's data holds it, by path. */
109
+ export function outputsData(outputs) {
110
+ return Object.fromEntries(Object.entries(outputs).map(([name, value]) => [`/outputs/${name}`, value]));
111
+ }
@@ -9,7 +9,7 @@ import { pageLayoutPanelOpen, togglePagePanel, } from '@datalayer/primer-addons/
9
9
  import { AssistantStage } from '../../../chat/assistant/AssistantStage';
10
10
  import { decisionsAskerAt } from '../../../chat/assistant/decisions';
11
11
  import { peekLine } from '../../../chat/assistant/ConversationBalloon';
12
- import { ASSISTANT_WORDS, assistantStateOf, keepAway, keptAway, latestSaying, } from '../../../chat/assistant/state';
12
+ import { assistantStateOf, keepAway, keptAway, latestSaying, } from '../../../chat/assistant/state';
13
13
  import { useApproveToolRequest, useRejectToolRequest, useToolApprovalsQuery, } from '../../../hooks/useToolApprovals';
14
14
  import { approvalsOfApp, ruleOfApproval } from '../app-rules/AppRulesCard';
15
15
  import { useViewportDrag } from '../../../chat/useViewportDrag';
@@ -17,6 +17,7 @@ import { LoopAssistantCharacter, LoopAssistantMenu, LoopChatLayout, LoopChatTurn
17
17
  import { assistantCharacterFor } from '../assistant-characters';
18
18
  import { useOptionalSandboxService } from '../agents/useSandboxService';
19
19
  import { TARGET_SPECS } from '../agents/switchable';
20
+ import { useChatWords } from '../../../chat/ChatLanguage';
20
21
  export const LOOP_ASSISTANT_PLUGIN_NAME = '@datalayer/loop-plugin-assistant';
21
22
  /** The character's size, in pixels, as the chat's own assistant. */
22
23
  const SIZE = 88;
@@ -72,6 +73,7 @@ function focusComposer() {
72
73
  composer?.focus();
73
74
  }
74
75
  export function LoopAssistant({ app, person, appId, decisions, balloon: displayGiven = 'history', workspace, }) {
76
+ const chatText = useChatWords();
75
77
  // The balloon's display, as its menu chose it; the Appspec's until then.
76
78
  const [chosenDisplay, setChosenDisplay] = useState();
77
79
  const display = chosenDisplay ?? displayGiven;
@@ -203,18 +205,18 @@ export function LoopAssistant({ app, person, appId, decisions, balloon: displayG
203
205
  const atWork = turn.status === 'streaming' || turn.status === 'thinking';
204
206
  const current = display === 'current';
205
207
  const balloon = state === 'paused'
206
- ? { text: ASSISTANT_WORDS.paused }
208
+ ? { text: chatText.paused }
207
209
  : approval
208
- ? { text: ASSISTANT_WORDS.approval, approval }
210
+ ? { text: chatText.waitingForYou, approval }
209
211
  : presence === 'waiting'
210
- ? { text: ASSISTANT_WORDS.waiting }
212
+ ? { text: chatText.waitingOpen }
211
213
  : tool
212
214
  ? { text: turn.activity ?? '', tool, busy: true }
213
215
  : current && (atWork || unheard)
214
216
  ? {
215
217
  text: turn.status === 'thinking'
216
- ? 'Thinking…'
217
- : (saying?.text ?? 'Thinking…'),
218
+ ? chatText.thinking
219
+ : (saying?.text ?? chatText.thinking),
218
220
  more: saying?.more,
219
221
  speaking: turn.status === 'streaming',
220
222
  busy: atWork,
@@ -49,6 +49,7 @@ import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/edito
49
49
  import { FaceDrawing, PresenceFace, PresenceLine, } from '../../../chat/presence/Presence';
50
50
  import { presenceState, presenceToolOf, } from '../../../chat/presence/presenceStatus';
51
51
  import { latestSaying, newestIsAnswer, } from '../../../chat/assistant/state';
52
+ import { useChatWords } from '../../../chat/ChatLanguage';
52
53
  /** No editor: the conversation on its own. */
53
54
  const NO_SURFACE = '';
54
55
  /**
@@ -64,6 +65,7 @@ const FULLSCREEN_HINT_PERIOD_MS = 1400;
64
65
  */
65
66
  const FACE_LARGE = parseInt(loopShapeVars['--loop-face-large'], 10);
66
67
  export default function ChatView({ workspace }) {
68
+ const chatText = useChatWords();
67
69
  /* The active theme's own colours. Read from the store rather than a
68
70
  provider, so a workspace mounted without the addons theme still gets the
69
71
  default palette instead of throwing. */
@@ -76,7 +78,7 @@ export default function ChatView({ workspace }) {
76
78
  // where it sets anything else about this plugin.
77
79
  const reactor = useReactorPlatform();
78
80
  const config = reactor.getConfig(CHAT_PLUGIN_NAME);
79
- const placeholder = config?.placeholder ?? 'Ask anything, type / for commands or @ for mention';
81
+ const placeholder = config?.placeholder || chatText.placeholder;
80
82
  const defaultSurface = config?.defaultSurface ?? 'notebook';
81
83
  /*
82
84
  * The input-prompt plugin's own configuration, read from here rather than
@@ -372,11 +374,12 @@ export default function ChatView({ workspace }) {
372
374
  * itself (`modeEffect`, `BrowserAgentAdapter`).
373
375
  */
374
376
  const sendNow = useCallback((message, given) => {
375
- const loop = given?.loop;
377
+ const forwardedProps = runForwardedProps(runPropsRef.current.map(entry => entry.value), given);
378
+ // The page's files, or those attached in the composer (LOOP P-21).
379
+ const loop = forwardedProps?.loop;
376
380
  if (inPageRef.current && loop?.files && loop.files.length > 0) {
377
381
  return 'A file is given to an application running on a runtime: run it on Datalayer or on your machine to give it one.';
378
382
  }
379
- const forwardedProps = runForwardedProps(runPropsRef.current.map(entry => entry.value), given);
380
383
  // A new turn: whatever the panel showed is gone, this message is it.
381
384
  turnFeedRef.current?.begin(message, controlsRef.current?.thread());
382
385
  const send = controlsRef.current?.send;
@@ -752,6 +755,7 @@ export default function ChatView({ workspace }) {
752
755
  const suggestionEntries = useContributions(LoopChatSuggestion);
753
756
  const hiddenOpeners = chatExtras.hiddenOpeners;
754
757
  const hideOpeners = chatExtras.hideOpeners;
758
+ const liveOpeners = chatExtras.openers;
755
759
  const suggestions = useMemo(() => {
756
760
  if (hideOpeners) {
757
761
  return [];
@@ -760,6 +764,11 @@ export default function ChatView({ workspace }) {
760
764
  // the two-group merge below uses to tell openers apart.
761
765
  const hidden = new Set(hiddenOpeners ?? []);
762
766
  const offered = (items) => hidden.size === 0 ? items : items.filter(item => !hidden.has(item.text));
767
+ // What a plugin offers live (an application's profile's starters,
768
+ // LOOP P-20) wins over what was contributed when the reactor was built.
769
+ if (liveOpeners) {
770
+ return offered(liveOpeners);
771
+ }
763
772
  const contributed = suggestionEntries.flatMap(entry => entry.value.suggestions);
764
773
  if (contributed.length > 0) {
765
774
  return offered(contributed);
@@ -788,6 +797,7 @@ export default function ChatView({ workspace }) {
788
797
  agentId,
789
798
  hiddenOpeners,
790
799
  hideOpeners,
800
+ liveOpeners,
791
801
  ]);
792
802
  /*
793
803
  * The same openers in the shape the chat's empty state asks for.
@@ -1032,8 +1042,8 @@ export default function ChatView({ workspace }) {
1032
1042
  const chatDisabled = gateBlocked || keyExpired || Boolean(inPageRefusal) || ambient.disabled;
1033
1043
  const disabledReason = keyExpired
1034
1044
  ? expiredKeyIsTemporary
1035
- ? 'This demo runs on a shared key, and its time is up. Sign in to keep going.'
1036
- : 'Your key has expired. Sign in to keep going.'
1045
+ ? chatText.demoKeyExpired
1046
+ : chatText.keyExpired
1037
1047
  : (inPageRefusal ??
1038
1048
  gateReason ??
1039
1049
  (ambient.disabled ? ambient.disableReason : undefined));
@@ -1765,7 +1775,7 @@ export default function ChatView({ workspace }) {
1765
1775
  renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? config?.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
1766
1776
  // Off when the host asked for the menu alone: the composer's suggestions
1767
1777
  // control carries the same openers (`suggestions` above).
1768
- const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": "Suggested prompts", sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
1778
+ const chipsEl = config?.suggestionLabels !== false && chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": chatText.suggestedPrompts, sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
1769
1779
  // The whole request, where the chip shows only its summary.
1770
1780
  title: item.message, disabled: busy || chatDisabled, onClick: () => void handleSend(item.message), sx: {
1771
1781
  appearance: 'none',
@@ -78,7 +78,7 @@ export type ChatPluginConfig = {
78
78
  * Configured on the plugin rather than passed to the workspace: the prompt
79
79
  * belongs to the chat, and a shell that has no chat plugin has no prompt to
80
80
  * put a placeholder in. A host that wants different words says so where the
81
- * words are.
81
+ * words are. Empty, the chat's own, in the person's language (LOOP P-26).
82
82
  */
83
83
  placeholder: string;
84
84
  /**
@@ -38,7 +38,8 @@ export const CHAT_PLUGIN_NAME = '@datalayer/loop-plugin-chat';
38
38
  export const ChatPlugin = definePlugin({
39
39
  name: CHAT_PLUGIN_NAME,
40
40
  config: {
41
- placeholder: 'Ask anything, type / for commands or @ for mention',
41
+ // The chat's own words, in the person's language (`chat/words`).
42
+ placeholder: '',
42
43
  // The notebook: it is what most of these agents work on, and a workspace
43
44
  // that opens on an empty chat hides the half of itself that does the work.
44
45
  defaultSurface: 'notebook',
@@ -15,5 +15,5 @@ export type PageTag = {
15
15
  export declare const PAGE_TAGE_NONE: PageTag;
16
16
  export declare const PAGE_TAGS: PageTag[];
17
17
  export declare const PAGE_TAGS_BY_NAME_MAP: Map<PageTagName | undefined, PageTag>;
18
- export declare const PAGE_TAGS_BY_COLOR_MAP: Map<"default" | "blue" | "coral" | "gray" | "green" | "indigo" | "lime" | "orange" | "pink" | "purple" | "red" | "teal" | "yellow" | "lemon", PageTag>;
18
+ export declare const PAGE_TAGS_BY_COLOR_MAP: Map<"default" | "green" | "lime" | "blue" | "coral" | "gray" | "indigo" | "orange" | "pink" | "purple" | "red" | "teal" | "yellow" | "lemon", PageTag>;
19
19
  export {};
@@ -70,7 +70,7 @@ export interface BrowserAgentAdapterConfig extends ProtocolAdapterConfig, Fronte
70
70
  * rules. Unsaid for an agent with no modes, which refuses a run sent in
71
71
  * some.
72
72
  */
73
- modeEffect?: (chosen: Record<string, string>) => AppModeEffect;
73
+ modeEffect?: (chosen: Record<string, string>, profile?: string) => AppModeEffect;
74
74
  }
75
75
  /**
76
76
  * An agent loop running in the page, speaking the protocol adapter interface.
@@ -120,12 +120,14 @@ export declare class BrowserAgentAdapter extends BaseProtocolAdapter {
120
120
  */
121
121
  private resolveModel;
122
122
  /**
123
- * What the modes a run is sent in tell it, and the model it runs on (LOOP
124
- * P-19) — the runtime's session API applies the same, for that run only.
123
+ * What the modes a run is sent in, and the profile its conversation is
124
+ * with, tell it, and the model it runs on (LOOP P-19, P-20) — the
125
+ * runtime's session API applies the same, for that run only.
125
126
  *
126
- * Refused, in a sentence, rather than run without them: modes sent to an
127
- * agent that has none, modes that are not the application's, and a mode
128
- * naming a model for an agent whose model is the host's own.
127
+ * Refused, in a sentence, rather than run without them: modes or a profile
128
+ * sent to an agent that has none, modes or a profile that are not the
129
+ * application's, and a model chosen for an agent whose model is the host's
130
+ * own.
129
131
  */
130
132
  private runModes;
131
133
  /**
@@ -141,26 +141,29 @@ export class BrowserAgentAdapter extends BaseProtocolAdapter {
141
141
  return createBrowserModel({ ...inference, model: requested || model });
142
142
  }
143
143
  /**
144
- * What the modes a run is sent in tell it, and the model it runs on (LOOP
145
- * P-19) — the runtime's session API applies the same, for that run only.
144
+ * What the modes a run is sent in, and the profile its conversation is
145
+ * with, tell it, and the model it runs on (LOOP P-19, P-20) — the
146
+ * runtime's session API applies the same, for that run only.
146
147
  *
147
- * Refused, in a sentence, rather than run without them: modes sent to an
148
- * agent that has none, modes that are not the application's, and a mode
149
- * naming a model for an agent whose model is the host's own.
148
+ * Refused, in a sentence, rather than run without them: modes or a profile
149
+ * sent to an agent that has none, modes or a profile that are not the
150
+ * application's, and a model chosen for an agent whose model is the host's
151
+ * own.
150
152
  */
151
153
  runModes(forwardedProps) {
152
154
  const loop = forwardedProps?.loop;
153
- const chosen = loop?.modes;
154
- if (!chosen || Object.keys(chosen).length === 0) {
155
+ const chosen = loop?.modes ?? {};
156
+ const profile = loop?.profile || undefined;
157
+ if (Object.keys(chosen).length === 0 && !profile) {
155
158
  return undefined;
156
159
  }
157
160
  const { modeEffect, languageModel } = this.browserConfig;
158
161
  if (!modeEffect) {
159
- throw new Error('This agent has no modes: the options chosen in the composer cannot be applied to it.');
162
+ throw new Error('This agent has no modes or profiles: what was chosen in the composer cannot be applied to it.');
160
163
  }
161
- const effect = modeEffect(chosen);
164
+ const effect = modeEffect(chosen, profile);
162
165
  if (effect.model && languageModel) {
163
- throw new Error(`The mode chosen runs on ${effect.model}, and this agent's model is its host's own: it cannot run on another.`);
166
+ throw new Error(`The mode or profile chosen runs on ${effect.model}, and this agent's model is its host's own: it cannot run on another.`);
164
167
  }
165
168
  return effect;
166
169
  }
@@ -56,7 +56,8 @@ export type ConnectA2APeerOptions = {
56
56
  key?: string;
57
57
  /**
58
58
  * The fetch to use, for tests and hosts that wrap the network — such as
59
- * the Agent Inspector's `inspectA2AFetch`, which records the traffic.
59
+ * the Agent Inspector's `traceA2AFetch`, which records the traffic as
60
+ * OpenTelemetry spans.
60
61
  */
61
62
  fetch?: typeof globalThis.fetch;
62
63
  };
@@ -39,7 +39,7 @@ export type BrowserProtocolOptions = {
39
39
  /** Told what a delegated run does, as it does it — see `subagentTools`. */
40
40
  onSubagentEvent?: (event: AgentStreamSubagentPayload) => void;
41
41
  /** What the modes a run is sent in tell it (LOOP P-19); none when unsaid. */
42
- modeEffect?: (chosen: Record<string, string>) => AppModeEffect;
42
+ modeEffect?: (chosen: Record<string, string>, profile?: string) => AppModeEffect;
43
43
  };
44
44
  /**
45
45
  * The protocol config for an agent whose loop runs in this page.
@@ -229,8 +229,13 @@ export const SERVER_ACTIONS = {
229
229
  conditions: {},
230
230
  },
231
231
  earthdata: {
232
+ checked: '2026-10-07',
232
233
  default: [],
233
- tools: {},
234
+ tools: {
235
+ search_earth_datasets: ['read'],
236
+ search_earth_datagranules: ['read'],
237
+ download_earth_data_granules: ['write'],
238
+ },
234
239
  conditions: {},
235
240
  },
236
241
  eurus: {
@@ -693,9 +698,25 @@ export const APP_BEHAVIOURS = {
693
698
  'odoo-accounting.odoo_accounting_trial_balance': 'do_it',
694
699
  'odoo-accounting.odoo_accounting_unreconcile_lines': 'leave_to_me',
695
700
  },
701
+ 'change-detection': {
702
+ 'earthdata.download_earth_data_granules': 'leave_to_me',
703
+ 'earthdata.search_earth_datagranules': 'do_it',
704
+ 'earthdata.search_earth_datasets': 'do_it',
705
+ },
706
+ 'crop-monitoring': {
707
+ 'earthdata.download_earth_data_granules': 'leave_to_me',
708
+ 'earthdata.search_earth_datagranules': 'do_it',
709
+ 'earthdata.search_earth_datasets': 'do_it',
710
+ },
696
711
  'customer-interview': {},
697
712
  'data-quality': {},
698
713
  decide: {},
714
+ 'disaster-assessment': {
715
+ 'earthdata.download_earth_data_granules': 'leave_to_me',
716
+ 'earthdata.search_earth_datagranules': 'do_it',
717
+ 'earthdata.search_earth_datasets': 'do_it',
718
+ },
719
+ 'event-response': {},
699
720
  'inbox-triage': {
700
721
  'google-workspace.append_table_rows': 'leave_to_me',
701
722
  'google-workspace.batch_modify_gmail_message_labels': 'do_it',
@@ -819,6 +840,57 @@ export const APP_BEHAVIOURS = {
819
840
  'google-workspace.update_paragraph_style': 'leave_to_me',
820
841
  },
821
842
  'model-choice': {},
843
+ 'month-end-close': {
844
+ 'odoo-accounting.odoo_accounting_aged_balance': 'do_it',
845
+ 'odoo-accounting.odoo_accounting_bank_status': 'do_it',
846
+ 'odoo-accounting.odoo_accounting_book_bank_line': 'leave_to_me',
847
+ 'odoo-accounting.odoo_accounting_check_tax_period': 'do_it',
848
+ 'odoo-accounting.odoo_accounting_create_customer_invoice': 'leave_to_me',
849
+ 'odoo-accounting.odoo_accounting_create_draft_invoice': 'leave_to_me',
850
+ 'odoo-accounting.odoo_accounting_create_draft_journal_entry': 'leave_to_me',
851
+ 'odoo-accounting.odoo_accounting_create_matching_model': 'leave_to_me',
852
+ 'odoo-accounting.odoo_accounting_create_vendor_bill': 'leave_to_me',
853
+ 'odoo-accounting.odoo_accounting_explain_account_balance': 'do_it',
854
+ 'odoo-accounting.odoo_accounting_general_ledger': 'do_it',
855
+ 'odoo-accounting.odoo_accounting_get_asset': 'do_it',
856
+ 'odoo-accounting.odoo_accounting_get_bank_line': 'do_it',
857
+ 'odoo-accounting.odoo_accounting_get_invoice': 'do_it',
858
+ 'odoo-accounting.odoo_accounting_get_journal_entry': 'do_it',
859
+ 'odoo-accounting.odoo_accounting_get_lock_dates': 'do_it',
860
+ 'odoo-accounting.odoo_accounting_get_tax_return': 'do_it',
861
+ 'odoo-accounting.odoo_accounting_get_tax_settings': 'do_it',
862
+ 'odoo-accounting.odoo_accounting_link_bank_feed': 'leave_to_me',
863
+ 'odoo-accounting.odoo_accounting_list_accounts': 'do_it',
864
+ 'odoo-accounting.odoo_accounting_list_assets': 'do_it',
865
+ 'odoo-accounting.odoo_accounting_list_bank_feeds': 'do_it',
866
+ 'odoo-accounting.odoo_accounting_list_bank_lines': 'do_it',
867
+ 'odoo-accounting.odoo_accounting_list_currency_rates': 'do_it',
868
+ 'odoo-accounting.odoo_accounting_list_invoices': 'do_it',
869
+ 'odoo-accounting.odoo_accounting_list_journal_entries': 'do_it',
870
+ 'odoo-accounting.odoo_accounting_list_journals': 'do_it',
871
+ 'odoo-accounting.odoo_accounting_list_open_balances': 'do_it',
872
+ 'odoo-accounting.odoo_accounting_list_partners': 'do_it',
873
+ 'odoo-accounting.odoo_accounting_list_tax_grid_lines': 'do_it',
874
+ 'odoo-accounting.odoo_accounting_list_tax_returns': 'do_it',
875
+ 'odoo-accounting.odoo_accounting_list_taxes': 'do_it',
876
+ 'odoo-accounting.odoo_accounting_list_untagged_tax_lines': 'do_it',
877
+ 'odoo-accounting.odoo_accounting_match_bank_line': 'leave_to_me',
878
+ 'odoo-accounting.odoo_accounting_partner_ledger': 'do_it',
879
+ 'odoo-accounting.odoo_accounting_post_invoice': 'leave_to_me',
880
+ 'odoo-accounting.odoo_accounting_post_journal_entry': 'leave_to_me',
881
+ 'odoo-accounting.odoo_accounting_reconcile_lines': 'leave_to_me',
882
+ 'odoo-accounting.odoo_accounting_remove_duplicate_bank_lines': 'leave_to_me',
883
+ 'odoo-accounting.odoo_accounting_reset_bank_line': 'leave_to_me',
884
+ 'odoo-accounting.odoo_accounting_set_currency_rates': 'leave_to_me',
885
+ 'odoo-accounting.odoo_accounting_set_lock_date': 'leave_to_me',
886
+ 'odoo-accounting.odoo_accounting_set_tax_grids': 'leave_to_me',
887
+ 'odoo-accounting.odoo_accounting_set_tax_periodicity': 'leave_to_me',
888
+ 'odoo-accounting.odoo_accounting_suggest_bank_matches': 'do_it',
889
+ 'odoo-accounting.odoo_accounting_suggest_reconciliations': 'do_it',
890
+ 'odoo-accounting.odoo_accounting_tax_grids': 'do_it',
891
+ 'odoo-accounting.odoo_accounting_trial_balance': 'do_it',
892
+ 'odoo-accounting.odoo_accounting_unreconcile_lines': 'leave_to_me',
893
+ },
822
894
  'pipeline-report': {},
823
895
  'quote-calculator': {},
824
896
  'report-from-a-file': {},
@@ -840,9 +912,13 @@ export const APP_BEHAVIOURS = {
840
912
  */
841
913
  export const APP_ESCALATIONS = {
842
914
  accounting: {},
915
+ 'change-detection': {},
916
+ 'crop-monitoring': {},
843
917
  'customer-interview': {},
844
918
  'data-quality': {},
845
919
  decide: {},
920
+ 'disaster-assessment': {},
921
+ 'event-response': {},
846
922
  'inbox-triage': {
847
923
  'google-workspace.batch_modify_gmail_message_labels': [
848
924
  {
@@ -878,6 +954,7 @@ export const APP_ESCALATIONS = {
878
954
  ],
879
955
  },
880
956
  'model-choice': {},
957
+ 'month-end-close': {},
881
958
  'pipeline-report': {},
882
959
  'quote-calculator': {},
883
960
  'report-from-a-file': {},
@@ -9,11 +9,16 @@
9
9
  */
10
10
  import type { AppBuilt, AppKind, AppSpec } from '../types/agentspecs';
11
11
  export declare const ACCOUNTING_APP_0_0_1: AppSpec;
12
+ export declare const CHANGE_DETECTION_APP_0_0_1: AppSpec;
13
+ export declare const CROP_MONITORING_APP_0_0_1: AppSpec;
12
14
  export declare const CUSTOMER_INTERVIEW_APP_0_0_1: AppSpec;
13
15
  export declare const DATA_QUALITY_APP_0_0_1: AppSpec;
14
16
  export declare const DECIDE_APP_0_0_1: AppSpec;
17
+ export declare const DISASTER_ASSESSMENT_APP_0_0_1: AppSpec;
18
+ export declare const EVENT_RESPONSE_APP_0_0_1: AppSpec;
15
19
  export declare const INBOX_TRIAGE_APP_0_0_1: AppSpec;
16
20
  export declare const MODEL_CHOICE_APP_0_0_1: AppSpec;
21
+ export declare const MONTH_END_CLOSE_APP_0_0_1: AppSpec;
17
22
  export declare const PIPELINE_REPORT_APP_0_0_1: AppSpec;
18
23
  export declare const QUOTE_CALCULATOR_APP_0_0_1: AppSpec;
19
24
  export declare const REPORT_FROM_A_FILE_APP_0_0_1: AppSpec;