@datalayer/agent-runtimes 1.3.80 → 1.3.82
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/assistant/AssistantStage.d.ts +9 -4
- package/lib/chat/assistant/AssistantStage.js +18 -6
- package/lib/chat/assistant/SpeechBalloon.js +16 -1
- package/lib/chat/assistant/SpriteCharacter.d.ts +25 -3
- package/lib/chat/assistant/SpriteCharacter.js +68 -3
- package/lib/chat/assistant/formats/acf.d.ts +56 -0
- package/lib/chat/assistant/formats/acf.js +317 -0
- package/lib/chat/assistant/formats/acs.d.ts +85 -7
- package/lib/chat/assistant/formats/acs.js +111 -39
- package/lib/chat/assistant/formats/files.d.ts +6 -2
- package/lib/chat/assistant/formats/files.js +18 -4
- package/lib/chat/assistant/formats/index.d.ts +6 -3
- package/lib/chat/assistant/formats/index.js +4 -2
- package/lib/chat/assistant/formats/types.d.ts +14 -0
- package/lib/chat/assistant/state.d.ts +2 -0
- package/lib/chat/assistant/state.js +9 -0
- package/lib/chat/base/ChatBase.js +47 -4
- package/lib/chat/base/loopStep.d.ts +40 -0
- package/lib/chat/base/loopStep.js +73 -0
- package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/McpStatusIndicator.js +13 -6
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/SkillsStatusIndicator.js +7 -3
- package/lib/chat/prompt/InputPrompt.d.ts +4 -1
- package/lib/chat/prompt/InputPrompt.js +2 -2
- package/lib/chat/prompt/InputPromptBase.d.ts +4 -1
- package/lib/chat/prompt/InputPromptBase.js +2 -2
- package/lib/chat/prompt/InputPromptLexical.d.ts +7 -1
- package/lib/chat/prompt/InputPromptLexical.js +5 -3
- package/lib/components/teams/index.d.ts +1 -0
- package/lib/components/teams/index.js +1 -0
- package/lib/components/teams/usePeerSandbox.d.ts +13 -0
- package/lib/components/teams/usePeerSandbox.js +59 -0
- package/lib/examples/AgentA2ATeamExample.js +7 -2
- package/lib/loop/apps/AppComposer.d.ts +31 -0
- package/lib/loop/apps/AppComposer.js +67 -0
- package/lib/loop/apps/AppRenderer.d.ts +16 -5
- package/lib/loop/apps/AppRenderer.js +49 -16
- package/lib/loop/apps/appspec.d.ts +8 -1
- package/lib/loop/apps/appspec.js +119 -2
- package/lib/loop/apps/checks.js +161 -1
- package/lib/loop/apps/composer.d.ts +37 -0
- package/lib/loop/apps/composer.js +61 -0
- package/lib/loop/apps/hostTools.d.ts +17 -0
- package/lib/loop/apps/hostTools.js +20 -0
- package/lib/loop/apps/index.d.ts +2 -0
- package/lib/loop/apps/index.js +2 -0
- package/lib/loop/apps/rules.d.ts +22 -1
- package/lib/loop/apps/rules.js +49 -1
- package/lib/loop/core/index.d.ts +31 -0
- package/lib/loop/core/index.js +22 -0
- package/lib/loop/embed/AppEmbed.d.ts +100 -6
- package/lib/loop/embed/AppEmbed.js +172 -27
- package/lib/loop/embed/LoopEmbed.js +2 -0
- package/lib/loop/embed/element.js +58 -5
- package/lib/loop/embed/embedConfig.d.ts +10 -1
- package/lib/loop/embed/embedConfig.js +12 -1
- package/lib/loop/embed/embedSession.d.ts +79 -0
- package/lib/loop/embed/embedSession.js +136 -0
- package/lib/loop/embed/embedTheme.d.ts +12 -9
- package/lib/loop/embed/embedTheme.js +32 -22
- package/lib/loop/embed/hostBridge.d.ts +79 -0
- package/lib/loop/embed/hostBridge.js +104 -0
- package/lib/loop/index.d.ts +5 -3
- package/lib/loop/index.js +6 -3
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.d.ts +5 -2
- package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +7 -1
- package/lib/loop/plugins/a2ui-surface/index.d.ts +1 -1
- package/lib/loop/plugins/a2ui-surface/index.js +4 -4
- package/lib/loop/plugins/a2ui-surface/toolResult.d.ts +27 -0
- package/lib/loop/plugins/a2ui-surface/toolResult.js +22 -0
- package/lib/loop/plugins/agent-capacity/index.d.ts +3 -1
- package/lib/loop/plugins/agent-capacity/index.js +2 -1
- package/lib/loop/plugins/chat/ChatView.js +31 -4
- package/lib/loop/plugins/chat/index.d.ts +6 -1
- package/lib/loop/presets.d.ts +3 -1
- package/lib/loop/presets.js +2 -1
- package/lib/loop/shell/LoopWorkspace.js +3 -0
- package/lib/protocols/BrowserAgentAdapter.d.ts +18 -0
- package/lib/protocols/BrowserAgentAdapter.js +35 -2
- package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +8 -0
- package/lib/runtimes/browser/a2aPeer.d.ts +11 -0
- package/lib/runtimes/browser/a2aPeer.js +15 -1
- package/lib/runtimes/browser/protocol.d.ts +3 -0
- package/lib/runtimes/browser/protocol.js +2 -1
- package/lib/specs/agents/agents.js +6 -25
- package/lib/specs/apps.js +50 -8
- package/lib/specs/appspecSchema.js +258 -0
- package/lib/types/agentspecs.d.ts +86 -0
- package/lib/types/chat.d.ts +29 -1
- package/package.json +1 -1
- package/scripts/codegen/generate_agents.py +4 -2
- package/scripts/codegen/generate_apps.py +7 -0
- package/scripts/codegen/generate_backend_tools.py +4 -12
- package/scripts/codegen/generate_cogs.py +3 -1
- package/scripts/codegen/generate_ui_plugins.py +22 -9
- package/scripts/voice/transcribe.mjs +29 -21
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An application's commands and modes in the composer (LOOP P-19).
|
|
3
|
+
*
|
|
4
|
+
* - Each of its **commands** is a workspace command (`LoopCommand`), listed
|
|
5
|
+
* in the composer's `/` menu (`composer`): picking it writes `/<name> `,
|
|
6
|
+
* and sending runs it — its prompt goes to the agent, the words typed
|
|
7
|
+
* after it in place of `{input}`.
|
|
8
|
+
* - Its **modes** are switches beside the composer (`LoopSlots.promptAction`),
|
|
9
|
+
* one menu each; the option picked goes with every run as
|
|
10
|
+
* `forwardedProps.loop.modes` (`LoopRunProps`), and the runtime tells the
|
|
11
|
+
* agent its instructions, on the model it names, for that run.
|
|
12
|
+
*
|
|
13
|
+
* @module loop/apps/AppComposer
|
|
14
|
+
*/
|
|
15
|
+
import type { JSX } from 'react';
|
|
16
|
+
import type { AppSpec } from '../../types/agentspecs';
|
|
17
|
+
/** The plugin's name, before the application's id. */
|
|
18
|
+
export declare const APP_COMPOSER_PLUGIN_NAME = "@datalayer/loop-plugin-app-composer";
|
|
19
|
+
/**
|
|
20
|
+
* 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.
|
|
23
|
+
*/
|
|
24
|
+
export declare function defineAppComposerPlugin(app: AppSpec): import("@datalayer/reactor").ReactorPlugin<Record<string, never>, unknown, {
|
|
25
|
+
components: {
|
|
26
|
+
id: string;
|
|
27
|
+
slot: "loop.promptAction";
|
|
28
|
+
order: number;
|
|
29
|
+
Component: () => JSX.Element | null;
|
|
30
|
+
}[];
|
|
31
|
+
}>;
|
|
@@ -0,0 +1,67 @@
|
|
|
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';
|
|
4
|
+
import { useSignalValue } from '@datalayer/reactor/react';
|
|
5
|
+
import { LoopCommand, LoopRunProps, LoopSlots } from '../core';
|
|
6
|
+
import { commandPrompt, modeChoice, modeDefaults } from './composer';
|
|
7
|
+
/** The plugin's name, before the application's id. */
|
|
8
|
+
export const APP_COMPOSER_PLUGIN_NAME = '@datalayer/loop-plugin-app-composer';
|
|
9
|
+
/**
|
|
10
|
+
* 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.
|
|
13
|
+
*/
|
|
14
|
+
export function defineAppComposerPlugin(app) {
|
|
15
|
+
const modes = app.interface.modes ?? [];
|
|
16
|
+
/** The option of each mode the person is in. */
|
|
17
|
+
const chosen = signal(modeDefaults(app));
|
|
18
|
+
function ModeSwitches() {
|
|
19
|
+
const now = useSignalValue(chosen);
|
|
20
|
+
return (_jsx(_Fragment, { children: modes.map(mode => {
|
|
21
|
+
const on = mode.options.find(option => option.id === now[mode.id]) ??
|
|
22
|
+
mode.options[0];
|
|
23
|
+
return (_jsxs(ActionMenu, { children: [_jsxs(ActionMenu.Button, { size: "small", variant: "invisible", "aria-label": `${mode.label}: ${on.label}`, children: [mode.label, ": ", on.label] }), _jsx(ActionMenu.Overlay, { width: "medium", children: _jsx(ActionList, { selectionVariant: "single", children: _jsxs(ActionList.Group, { children: [_jsx(ActionList.GroupHeading, { children: mode.label }), mode.options.map(option => (_jsxs(ActionList.Item, { selected: option.id === on.id, onSelect: () => {
|
|
24
|
+
chosen.value = modeChoice(app, {
|
|
25
|
+
...chosen.peek(),
|
|
26
|
+
[mode.id]: option.id,
|
|
27
|
+
});
|
|
28
|
+
}, children: [option.label, option.description ? (_jsx(ActionList.Description, { variant: "block", children: option.description })) : null] }, option.id)))] }) }) })] }, mode.id));
|
|
29
|
+
}) }));
|
|
30
|
+
}
|
|
31
|
+
return definePlugin({
|
|
32
|
+
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.`,
|
|
35
|
+
octicon: 'command-palette',
|
|
36
|
+
emoji: '\u{2318}',
|
|
37
|
+
contributes: [
|
|
38
|
+
...(app.interface.commands ?? []).map(command => contribution(LoopCommand, {
|
|
39
|
+
name: command.name,
|
|
40
|
+
description: command.description,
|
|
41
|
+
group: app.name,
|
|
42
|
+
composer: true,
|
|
43
|
+
run: async ({ argv }) => ({ prompt: commandPrompt(command, argv) }),
|
|
44
|
+
}, { id: `app-command-${command.name}` })),
|
|
45
|
+
...(modes.length > 0
|
|
46
|
+
? [
|
|
47
|
+
contribution(LoopRunProps, {
|
|
48
|
+
id: 'app-modes',
|
|
49
|
+
props: () => ({ loop: { modes: { ...chosen.peek() } } }),
|
|
50
|
+
}, { id: 'app-modes' }),
|
|
51
|
+
]
|
|
52
|
+
: []),
|
|
53
|
+
],
|
|
54
|
+
build: () => ({
|
|
55
|
+
components: modes.length > 0
|
|
56
|
+
? [
|
|
57
|
+
{
|
|
58
|
+
id: 'app-modes',
|
|
59
|
+
slot: LoopSlots.promptAction,
|
|
60
|
+
order: 5,
|
|
61
|
+
Component: ModeSwitches,
|
|
62
|
+
},
|
|
63
|
+
]
|
|
64
|
+
: [],
|
|
65
|
+
}),
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -108,6 +108,12 @@ export type AppRendererProps = Omit<LoopEmbedProps, 'agentId'> & {
|
|
|
108
108
|
* `instance.appUid`.
|
|
109
109
|
*/
|
|
110
110
|
sidebar?: boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Draw its computer alone beside its page (R-23): what a deployment's
|
|
113
|
+
* owner and the editors of its application see of it at its address — its
|
|
114
|
+
* runtime shows it to them only (decided 2026-10-06). Implied by `sidebar`.
|
|
115
|
+
*/
|
|
116
|
+
computer?: boolean;
|
|
111
117
|
/**
|
|
112
118
|
* Draw the application in a window (the `window-frame` plugin), its title
|
|
113
119
|
* the application's face and name. A host's own `frameTitle` wins.
|
|
@@ -157,11 +163,13 @@ export type AppPreset = {
|
|
|
157
163
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
158
164
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
159
165
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
160
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
166
|
+
* 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
|
|
161
168
|
* keeps and for how long, under its prompt before the first message, when
|
|
162
169
|
* `kept` says it (R-31); with
|
|
163
170
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
164
|
-
* computer (R-01b, R-23);
|
|
171
|
+
* computer (R-01b, R-23); with `computer`, its computer alone — at its
|
|
172
|
+
* address, for its owner and the editors of its application;
|
|
165
173
|
* laid out as `interface.layout` says;
|
|
166
174
|
* - a **decision**: the page its host draws (`page`), as the one view of a
|
|
167
175
|
* workspace without a conversation (R-02).
|
|
@@ -174,6 +182,8 @@ export declare function appPreset(app: AppSpec, options?: {
|
|
|
174
182
|
pluginsOff?: readonly string[];
|
|
175
183
|
/** Its rules and approvals, its activity and its computer, beside its page. */
|
|
176
184
|
sidebar?: boolean;
|
|
185
|
+
/** Its computer alone beside its page (R-23): for its owner and editors at its address. */
|
|
186
|
+
computer?: boolean;
|
|
177
187
|
/** What its record is kept under: its activity's. */
|
|
178
188
|
appUid?: string;
|
|
179
189
|
/**
|
|
@@ -207,8 +217,9 @@ export declare const DATALAYER_BOOTSTRAP_AGENTSPEC = "example-simple";
|
|
|
207
217
|
* The application's accent over the theme its conversation wears, in both
|
|
208
218
|
* modes (LOOP T-05): its bubbles and its one button in its own colour, and
|
|
209
219
|
* not in the theme's default — the chat sets its theme again inside it, so
|
|
210
|
-
* what the page around it set does not reach in.
|
|
220
|
+
* what the page around it set does not reach in. The accents are `loop`'s
|
|
221
|
+
* colours: over another theme (T-30) nothing is laid, the theme's own rule.
|
|
211
222
|
*/
|
|
212
|
-
export declare function appThemeOverrides(app: AppSpec): ThemeOverrides | undefined;
|
|
213
|
-
export declare function AppRenderer({ app, plugins, instance, onPresence, onSaying, paused, sidebar, frame, page, pluginsOff, ...embed }: AppRendererProps): React.JSX.Element;
|
|
223
|
+
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;
|
|
214
225
|
export default AppRenderer;
|
|
@@ -45,7 +45,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
|
|
|
45
45
|
* @module loop/apps/AppRenderer
|
|
46
46
|
*/
|
|
47
47
|
import { useMemo } from 'react';
|
|
48
|
-
import { loopAccentStyles } from '@datalayer/primer-addons';
|
|
48
|
+
import { loopAccentStyles, useSystemColorMode, useThemeStore, } from '@datalayer/primer-addons';
|
|
49
49
|
import { defineAgentCapacityPlugin } from '../plugins/agent-capacity';
|
|
50
50
|
import { APP_PAGE_SURFACE, defineAppHostPagePlugin, defineAppPagePlugin, hasAppPage, } from '../plugins/app-page';
|
|
51
51
|
import { CANVAS_BLOCK_PLUGINS, canvasBlocksPluginName, } from '../plugins/canvas-blocks';
|
|
@@ -54,6 +54,8 @@ import { dumpAppspec } from './appspec';
|
|
|
54
54
|
import { defineAppActivityPlugin } from '../plugins/app-activity';
|
|
55
55
|
import { defineAppComputerPlugin } from '../plugins/app-computer';
|
|
56
56
|
import { defineAppRulesPlugin } from '../plugins/app-rules';
|
|
57
|
+
import { defineAppComposerPlugin } from './AppComposer';
|
|
58
|
+
import { modeEffect } from './composer';
|
|
57
59
|
import { defineAppFeedbackPlugin } from './AppFeedback';
|
|
58
60
|
import { defineAppKeptPlugin } from './AppKept';
|
|
59
61
|
import { keepsFeedback } from './feedback';
|
|
@@ -105,6 +107,14 @@ export function defineAppPlugin(app) {
|
|
|
105
107
|
// application's model when it names one (`loop/apps/agent`).
|
|
106
108
|
instructions: app.instructions,
|
|
107
109
|
model: app.model,
|
|
110
|
+
// Its modes (LOOP P-19), as the session API applies them: the
|
|
111
|
+
// instructions of the options chosen and the model one names, for the
|
|
112
|
+
// run they are sent with.
|
|
113
|
+
...((app.interface.modes ?? []).length > 0
|
|
114
|
+
? {
|
|
115
|
+
modeEffect: (chosen) => modeEffect(app, chosen),
|
|
116
|
+
}
|
|
117
|
+
: {}),
|
|
108
118
|
});
|
|
109
119
|
}
|
|
110
120
|
/**
|
|
@@ -146,11 +156,13 @@ export function appLayoutOptions(app) {
|
|
|
146
156
|
* its page (`app-page`) when it has one, with the Canvas's block plugins
|
|
147
157
|
* but those of the UI plugins its organization turned off (`pluginsOff`),
|
|
148
158
|
* whose contributions are the blocks it may draw (R-01b); a thumb and a
|
|
149
|
-
* comment on each answer when its record keeps feedback (V-18);
|
|
159
|
+
* comment on each answer when its record keeps feedback (V-18); its
|
|
160
|
+
* commands in the composer's `/` menu and its modes beside it (P-19); what it
|
|
150
161
|
* keeps and for how long, under its prompt before the first message, when
|
|
151
162
|
* `kept` says it (R-31); with
|
|
152
163
|
* `sidebar`, its rules and approvals card, its activity feed and its
|
|
153
|
-
* computer (R-01b, R-23);
|
|
164
|
+
* computer (R-01b, R-23); with `computer`, its computer alone — at its
|
|
165
|
+
* address, for its owner and the editors of its application;
|
|
154
166
|
* laid out as `interface.layout` says;
|
|
155
167
|
* - a **decision**: the page its host draws (`page`), as the one view of a
|
|
156
168
|
* workspace without a conversation (R-02).
|
|
@@ -180,6 +192,12 @@ export function appPreset(app, options = {}) {
|
|
|
180
192
|
plugins: [
|
|
181
193
|
defineAppPlugin(app),
|
|
182
194
|
...(withPage ? [defineAppPagePlugin(app), ...blocks] : []),
|
|
195
|
+
// Its commands in the composer's `/` menu and its modes beside the
|
|
196
|
+
// composer (LOOP P-19): only for an application that has some.
|
|
197
|
+
...((app.interface.commands ?? []).length > 0 ||
|
|
198
|
+
(app.interface.modes ?? []).length > 0
|
|
199
|
+
? [defineAppComposerPlugin(app)]
|
|
200
|
+
: []),
|
|
183
201
|
// A thumb and a comment on each answer, kept in its record (LOOP
|
|
184
202
|
// V-18): only for an application whose record keeps feedback.
|
|
185
203
|
...(keepsFeedback(app) ? [defineAppFeedbackPlugin(app)] : []),
|
|
@@ -196,7 +214,9 @@ export function appPreset(app, options = {}) {
|
|
|
196
214
|
// Its computer, live (R-23).
|
|
197
215
|
defineAppComputerPlugin(app),
|
|
198
216
|
]
|
|
199
|
-
:
|
|
217
|
+
: options.computer
|
|
218
|
+
? [defineAppComputerPlugin(app)]
|
|
219
|
+
: []),
|
|
200
220
|
],
|
|
201
221
|
workspace: appLayoutOptions(app),
|
|
202
222
|
};
|
|
@@ -249,18 +269,19 @@ const NO_PLUGINS = [];
|
|
|
249
269
|
* The application's accent over the theme its conversation wears, in both
|
|
250
270
|
* modes (LOOP T-05): its bubbles and its one button in its own colour, and
|
|
251
271
|
* not in the theme's default — the chat sets its theme again inside it, so
|
|
252
|
-
* what the page around it set does not reach in.
|
|
272
|
+
* what the page around it set does not reach in. The accents are `loop`'s
|
|
273
|
+
* colours: over another theme (T-30) nothing is laid, the theme's own rule.
|
|
253
274
|
*/
|
|
254
|
-
export function appThemeOverrides(app) {
|
|
275
|
+
export function appThemeOverrides(app, variant) {
|
|
255
276
|
const accent = app.interface?.accent;
|
|
256
|
-
return accent
|
|
277
|
+
return accent && variant === 'loop'
|
|
257
278
|
? {
|
|
258
279
|
light: loopAccentStyles(accent, 'light'),
|
|
259
280
|
dark: loopAccentStyles(accent, 'dark'),
|
|
260
281
|
}
|
|
261
282
|
: undefined;
|
|
262
283
|
}
|
|
263
|
-
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, onSaying, paused = false, sidebar = false, frame = false, page, pluginsOff, ...embed }) {
|
|
284
|
+
export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, onSaying, paused = false, sidebar = false, computer = false, frame = false, page, pluginsOff, ...embed }) {
|
|
264
285
|
/*
|
|
265
286
|
* The kind's plugins, made once per application. `LoopEmbed` rebuilds its
|
|
266
287
|
* whole reactor when its plugins change, so a new plugin on every render
|
|
@@ -282,6 +303,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
282
303
|
page,
|
|
283
304
|
pluginsOff,
|
|
284
305
|
sidebar,
|
|
306
|
+
computer,
|
|
285
307
|
appUid: instance?.appUid,
|
|
286
308
|
...(deployed ? { kept: { visitor } } : {}),
|
|
287
309
|
});
|
|
@@ -293,7 +315,7 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
293
315
|
}
|
|
294
316
|
},
|
|
295
317
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
296
|
-
[source, page, off, sidebar, instance?.appUid, deployed, visitor]);
|
|
318
|
+
[source, page, off, sidebar, computer, instance?.appUid, deployed, visitor]);
|
|
297
319
|
const allPlugins = useMemo(() => ('problem' in preset ? plugins : [...preset.plugins, ...plugins]), [preset, plugins]);
|
|
298
320
|
/*
|
|
299
321
|
* On Datalayer, the application runs on a runtime: allocated with a plain
|
|
@@ -311,10 +333,24 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
311
333
|
instance?.deploymentUid,
|
|
312
334
|
instance?.version,
|
|
313
335
|
]);
|
|
336
|
+
/*
|
|
337
|
+
* The theme it runs in (T-30): the one its host says, else the one its
|
|
338
|
+
* Appspec names, else the page's — the person's own. Its mode likewise;
|
|
339
|
+
* `auto` is the device's.
|
|
340
|
+
*/
|
|
341
|
+
const pageTheme = useThemeStore(state => state.theme);
|
|
342
|
+
const system = useSystemColorMode();
|
|
343
|
+
const named = app.interface?.theme;
|
|
344
|
+
const worn = embed.themeVariant ?? named?.variant ?? pageTheme;
|
|
345
|
+
const namedMode = named?.mode === 'auto'
|
|
346
|
+
? system
|
|
347
|
+
: named?.mode === 'light' || named?.mode === 'dark'
|
|
348
|
+
? named.mode
|
|
349
|
+
: undefined;
|
|
314
350
|
const accent = app.interface?.accent;
|
|
315
|
-
const themeOverrides = useMemo(() => appThemeOverrides(app),
|
|
351
|
+
const themeOverrides = useMemo(() => appThemeOverrides(app, worn),
|
|
316
352
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
317
|
-
[accent]);
|
|
353
|
+
[accent, worn]);
|
|
318
354
|
if ('problem' in preset) {
|
|
319
355
|
return (_jsx("div", { role: "status", style: { padding: 16 }, children: preset.problem }));
|
|
320
356
|
}
|
|
@@ -330,13 +366,10 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
|
|
|
330
366
|
}
|
|
331
367
|
: {}),
|
|
332
368
|
// Its rules, activity and computer on a rail, one at a time (T-07).
|
|
333
|
-
sidebarRail: sidebar, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
369
|
+
sidebarRail: sidebar || computer, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
|
|
334
370
|
// Where an application runs is decided by its host — the Studio's
|
|
335
371
|
// Preview, the hosted page — not offered to its user.
|
|
336
|
-
targetFixed: true,
|
|
337
|
-
// Applications first (LOOP T-12): an application's conversation wears
|
|
338
|
-
// the `loop` theme; a host may say otherwise.
|
|
339
|
-
themeVariant: "loop", themeOverrides: themeOverrides,
|
|
372
|
+
targetFixed: true, ...(named ? { themeVariant: named.variant } : {}), ...(namedMode ? { colorMode: namedMode } : {}), themeOverrides: themeOverrides,
|
|
340
373
|
// Its own face, name and welcome in the chat (T-08); no counters: a
|
|
341
374
|
// person using an application is not asking about tokens.
|
|
342
375
|
presence: {
|
|
@@ -24,13 +24,20 @@
|
|
|
24
24
|
*
|
|
25
25
|
* @module loop/apps/appspec
|
|
26
26
|
*/
|
|
27
|
-
import type { AppAccent, AppAssistantCharacter, AppEmbedMode, AppVoiceSpec, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
|
|
27
|
+
import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, AppVoiceSpec, AppKind, AppLayout, 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 face of an application that has not chosen one. */
|
|
31
31
|
export declare const DEFAULT_EMOJI = "\uD83D\uDC40";
|
|
32
32
|
export declare const APP_KINDS: AppKind[];
|
|
33
33
|
export declare const APP_ACCENTS: AppAccent[];
|
|
34
|
+
/**
|
|
35
|
+
* The themes an application may run in (LOOP T-30): Appearance's, as
|
|
36
|
+
* primer-addons lists them (`themeVariants`), in its order.
|
|
37
|
+
*/
|
|
38
|
+
export declare const APP_THEME_VARIANTS: AppThemeVariant[];
|
|
39
|
+
/** The colour modes a theme is worn in. */
|
|
40
|
+
export declare const APP_THEME_MODES: AppThemeMode[];
|
|
34
41
|
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
35
42
|
export declare const APP_EMBED_MODES: AppEmbedMode[];
|
|
36
43
|
/**
|
package/lib/loop/apps/appspec.js
CHANGED
|
@@ -16,6 +16,23 @@ export const APP_ACCENTS = [
|
|
|
16
16
|
'sun',
|
|
17
17
|
'violet',
|
|
18
18
|
];
|
|
19
|
+
/**
|
|
20
|
+
* The themes an application may run in (LOOP T-30): Appearance's, as
|
|
21
|
+
* primer-addons lists them (`themeVariants`), in its order.
|
|
22
|
+
*/
|
|
23
|
+
export const APP_THEME_VARIANTS = [
|
|
24
|
+
'datalayer',
|
|
25
|
+
'spatial',
|
|
26
|
+
'lovely',
|
|
27
|
+
'matrix',
|
|
28
|
+
'earth',
|
|
29
|
+
'sand',
|
|
30
|
+
'ivory',
|
|
31
|
+
'sun',
|
|
32
|
+
'loop',
|
|
33
|
+
];
|
|
34
|
+
/** The colour modes a theme is worn in. */
|
|
35
|
+
export const APP_THEME_MODES = ['light', 'dark', 'auto'];
|
|
19
36
|
/** The four ways an application sits in another product's page (LOOP D-07). */
|
|
20
37
|
export const APP_EMBED_MODES = [
|
|
21
38
|
'inline',
|
|
@@ -58,6 +75,8 @@ const DEFAULT_READY_AT = 0.8;
|
|
|
58
75
|
const DEFAULT_PROTOCOL = 'a2ui/v0.9';
|
|
59
76
|
const DEFAULT_ICON = 'apps';
|
|
60
77
|
const DEFAULT_VERSION = '0.0.1';
|
|
78
|
+
/** A host function's arguments when it names none: an empty object. */
|
|
79
|
+
const NO_HOST_PARAMETERS = { type: 'object', properties: {} };
|
|
61
80
|
const isData = (value) => Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
62
81
|
const text = (value, otherwise = '') => typeof value === 'string' ? value : otherwise;
|
|
63
82
|
const flag = (value, otherwise) => typeof value === 'boolean' ? value : otherwise;
|
|
@@ -103,6 +122,8 @@ export function emptyAppspec(kind = 'chat') {
|
|
|
103
122
|
accent: 'green',
|
|
104
123
|
welcome: '',
|
|
105
124
|
starters: [],
|
|
125
|
+
commands: [],
|
|
126
|
+
modes: [],
|
|
106
127
|
settings: [],
|
|
107
128
|
components: [],
|
|
108
129
|
outputs: [],
|
|
@@ -188,6 +209,39 @@ function parseRule(data) {
|
|
|
188
209
|
behaviour: oneOf(data.behaviour, BEHAVIOURS, 'ask_first'),
|
|
189
210
|
};
|
|
190
211
|
}
|
|
212
|
+
/** A slash command (LOOP P-19); a wrong one is said by the checks. */
|
|
213
|
+
function parseCommand(data) {
|
|
214
|
+
return {
|
|
215
|
+
name: text(data.name),
|
|
216
|
+
description: text(data.description),
|
|
217
|
+
prompt: text(data.prompt),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
/** One position of a mode switch, what it does said only when said. */
|
|
221
|
+
function parseModeOption(data) {
|
|
222
|
+
const option = {
|
|
223
|
+
id: text(data.id),
|
|
224
|
+
label: text(data.label),
|
|
225
|
+
};
|
|
226
|
+
for (const key of ['description', 'instructions', 'model']) {
|
|
227
|
+
if (typeof data[key] === 'string' && data[key]) {
|
|
228
|
+
option[key] = data[key];
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return option;
|
|
232
|
+
}
|
|
233
|
+
/** A mode switch (LOOP P-19). */
|
|
234
|
+
function parseMode(data) {
|
|
235
|
+
const mode = {
|
|
236
|
+
id: text(data.id),
|
|
237
|
+
label: text(data.label),
|
|
238
|
+
options: records(data.options).map(parseModeOption),
|
|
239
|
+
};
|
|
240
|
+
if (typeof data.default === 'string' && data.default) {
|
|
241
|
+
mode.default = data.default;
|
|
242
|
+
}
|
|
243
|
+
return mode;
|
|
244
|
+
}
|
|
191
245
|
function parseSetting(data) {
|
|
192
246
|
const setting = {
|
|
193
247
|
id: text(data.id),
|
|
@@ -244,6 +298,8 @@ function parseInterface(data, kind) {
|
|
|
244
298
|
label: text(starter.label),
|
|
245
299
|
message: text(starter.message),
|
|
246
300
|
})),
|
|
301
|
+
commands: records(data.commands).map(parseCommand),
|
|
302
|
+
modes: records(data.modes).map(parseMode),
|
|
247
303
|
settings: records(data.settings).map(parseSetting),
|
|
248
304
|
components: texts(data.components),
|
|
249
305
|
voice: parseVoice(data.voice),
|
|
@@ -258,6 +314,14 @@ function parseInterface(data, kind) {
|
|
|
258
314
|
if (data.balloon === 'history' || data.balloon === 'current') {
|
|
259
315
|
parsed.balloon = data.balloon;
|
|
260
316
|
}
|
|
317
|
+
// A theme of Appearance's, or none: an unknown one is said by the checks.
|
|
318
|
+
if (isData(data.theme) &&
|
|
319
|
+
APP_THEME_VARIANTS.includes(data.theme.variant)) {
|
|
320
|
+
parsed.theme = { variant: data.theme.variant };
|
|
321
|
+
if (APP_THEME_MODES.includes(data.theme.mode)) {
|
|
322
|
+
parsed.theme.mode = data.theme.mode;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
261
325
|
return parsed;
|
|
262
326
|
}
|
|
263
327
|
function parseCriterion(data) {
|
|
@@ -290,12 +354,27 @@ function parseDeployment(data) {
|
|
|
290
354
|
deployment.hosted = {
|
|
291
355
|
visibility: oneOf(data.hosted.visibility, ['private', 'invited', 'organization', 'link', 'public'], 'private'),
|
|
292
356
|
slug: text(data.hosted.slug),
|
|
357
|
+
...(data.hosted.character_alone === true ? { characterAlone: true } : {}),
|
|
293
358
|
};
|
|
294
359
|
}
|
|
295
360
|
if (isData(data.embedded)) {
|
|
296
361
|
deployment.embedded = {
|
|
297
362
|
mode: oneOf(data.embedded.mode, APP_EMBED_MODES, 'inline'),
|
|
298
363
|
origins: texts(data.embedded.origins),
|
|
364
|
+
...(isData(data.embedded.host)
|
|
365
|
+
? {
|
|
366
|
+
host: {
|
|
367
|
+
context: texts(data.embedded.host.context),
|
|
368
|
+
functions: records(data.embedded.host.functions).map(fn => ({
|
|
369
|
+
name: text(fn.name),
|
|
370
|
+
description: text(fn.description),
|
|
371
|
+
parameters: isData(fn.parameters)
|
|
372
|
+
? fn.parameters
|
|
373
|
+
: { ...NO_HOST_PARAMETERS },
|
|
374
|
+
})),
|
|
375
|
+
},
|
|
376
|
+
}
|
|
377
|
+
: {}),
|
|
299
378
|
};
|
|
300
379
|
}
|
|
301
380
|
return deployment;
|
|
@@ -499,6 +578,26 @@ function dumpInterface(spec, kind) {
|
|
|
499
578
|
label: starter.label,
|
|
500
579
|
message: starter.message,
|
|
501
580
|
})))
|
|
581
|
+
.list('commands', spec.commands.map(command => ({
|
|
582
|
+
name: command.name,
|
|
583
|
+
description: command.description,
|
|
584
|
+
prompt: command.prompt,
|
|
585
|
+
})))
|
|
586
|
+
.list('modes', spec.modes.map(mode => {
|
|
587
|
+
const written = new Writer()
|
|
588
|
+
.text('id', mode.id, '\u0000')
|
|
589
|
+
.text('label', mode.label, '\u0000')
|
|
590
|
+
.list('options', mode.options.map(option => new Writer()
|
|
591
|
+
.text('id', option.id, '\u0000')
|
|
592
|
+
.text('label', option.label, '\u0000')
|
|
593
|
+
.text('description', option.description ?? '')
|
|
594
|
+
.text('instructions', option.instructions ?? '')
|
|
595
|
+
.text('model', option.model ?? '').data));
|
|
596
|
+
if (mode.default) {
|
|
597
|
+
written.data.default = mode.default;
|
|
598
|
+
}
|
|
599
|
+
return written.data;
|
|
600
|
+
}))
|
|
502
601
|
.list('settings', spec.settings.map(setting => {
|
|
503
602
|
const written = new Writer()
|
|
504
603
|
.text('id', setting.id, '\u0000')
|
|
@@ -522,6 +621,11 @@ function dumpInterface(spec, kind) {
|
|
|
522
621
|
if (spec.balloon) {
|
|
523
622
|
writer.data.balloon = spec.balloon;
|
|
524
623
|
}
|
|
624
|
+
if (spec.theme) {
|
|
625
|
+
writer.data.theme = spec.theme.mode
|
|
626
|
+
? { variant: spec.theme.variant, mode: spec.theme.mode }
|
|
627
|
+
: { variant: spec.theme.variant };
|
|
628
|
+
}
|
|
525
629
|
// Its voice, when it says one (VO-41): a spec made before voice has none.
|
|
526
630
|
if (spec.voice) {
|
|
527
631
|
writer.part('voice', new Writer()
|
|
@@ -628,11 +732,24 @@ export function dumpAppspec(app) {
|
|
|
628
732
|
.present('hosted', app.deployment.hosted &&
|
|
629
733
|
new Writer()
|
|
630
734
|
.value('visibility', app.deployment.hosted.visibility, 'private')
|
|
631
|
-
.text('slug', app.deployment.hosted.slug)
|
|
735
|
+
.text('slug', app.deployment.hosted.slug)
|
|
736
|
+
.value('character_alone', app.deployment.hosted.characterAlone === true, false).data)
|
|
632
737
|
.present('embedded', app.deployment.embedded &&
|
|
633
738
|
new Writer()
|
|
634
739
|
.value('mode', app.deployment.embedded.mode, 'inline')
|
|
635
|
-
.list('origins', app.deployment.embedded.origins)
|
|
740
|
+
.list('origins', app.deployment.embedded.origins)
|
|
741
|
+
.present('host', app.deployment.embedded.host &&
|
|
742
|
+
new Writer()
|
|
743
|
+
.list('context', app.deployment.embedded.host.context)
|
|
744
|
+
.list('functions', app.deployment.embedded.host.functions.map(fn => ({
|
|
745
|
+
name: fn.name,
|
|
746
|
+
description: fn.description,
|
|
747
|
+
// No arguments is what it is unless said.
|
|
748
|
+
...(JSON.stringify(fn.parameters) ===
|
|
749
|
+
JSON.stringify(NO_HOST_PARAMETERS)
|
|
750
|
+
? {}
|
|
751
|
+
: { parameters: fn.parameters }),
|
|
752
|
+
}))).data).data).data)
|
|
636
753
|
.text('goal', app.goal)
|
|
637
754
|
.list('triggers', app.triggers.map(trigger => new Writer()
|
|
638
755
|
.text('type', trigger.type, '\u0000')
|