@datalayer/agent-runtimes 1.3.81 → 1.3.83
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/SpeechBalloon.js +16 -1
- package/lib/chat/base/ChatBase.js +40 -4
- 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/a2ui/datalayer/Form.js +19 -2
- 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 +88 -23
- package/lib/loop/apps/checks.js +125 -8
- package/lib/loop/apps/composer.d.ts +37 -0
- package/lib/loop/apps/composer.js +61 -0
- package/lib/loop/apps/index.d.ts +2 -0
- package/lib/loop/apps/index.js +2 -0
- package/lib/loop/core/index.d.ts +31 -0
- package/lib/loop/core/index.js +22 -0
- package/lib/loop/embed/AppEmbed.d.ts +46 -7
- package/lib/loop/embed/AppEmbed.js +108 -26
- package/lib/loop/embed/LoopEmbed.js +2 -0
- package/lib/loop/embed/element.js +19 -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/index.d.ts +4 -3
- package/lib/loop/index.js +4 -3
- 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/app-page/appPageModel.d.ts +14 -5
- package/lib/loop/plugins/app-page/appPageModel.js +80 -88
- 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 -3
- package/lib/specs/apps.js +216 -176
- package/lib/specs/appspecSchema.js +205 -84
- package/lib/specs/cogs.js +3 -2
- package/lib/types/agentspecs.d.ts +92 -7
- 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 +0 -1
- 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
|
@@ -2,46 +2,56 @@
|
|
|
2
2
|
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
3
|
* Distributed under the terms of the Modified BSD License.
|
|
4
4
|
*/
|
|
5
|
-
import { loopAccentStyles, loopFontFamily,
|
|
5
|
+
import { getThemeConfig, loopAccentStyles, loopFontFamily, scopeThemeCss, THEME_SCOPE_ATTRIBUTE, } from '@datalayer/primer-addons';
|
|
6
6
|
/** One mode's properties, with the theme's face replaced by the host's. */
|
|
7
|
-
function withFace(styles, font) {
|
|
8
|
-
if (!font) {
|
|
7
|
+
function withFace(styles, font, own) {
|
|
8
|
+
if (!font || !own) {
|
|
9
9
|
return styles;
|
|
10
10
|
}
|
|
11
11
|
return Object.fromEntries(Object.entries(styles).map(([name, value]) => [
|
|
12
12
|
name,
|
|
13
|
-
typeof value === 'string'
|
|
14
|
-
? value.split(loopFontFamily).join(font)
|
|
15
|
-
: value,
|
|
13
|
+
typeof value === 'string' ? value.split(own).join(font) : value,
|
|
16
14
|
]));
|
|
17
15
|
}
|
|
16
|
+
/** The face a theme names in a mode: `loop`'s own, or the theme's `fontFamily`. */
|
|
17
|
+
function faceOf(variant, styles) {
|
|
18
|
+
if (variant === 'loop') {
|
|
19
|
+
return loopFontFamily;
|
|
20
|
+
}
|
|
21
|
+
return typeof styles.fontFamily === 'string' ? styles.fontFamily : '';
|
|
22
|
+
}
|
|
23
|
+
/** The accent over a theme: `loop`'s colours, over `loop` only (T-30). */
|
|
24
|
+
const accentOver = (variant, accent, which) => variant === 'loop' ? loopAccentStyles(accent, which) : {};
|
|
18
25
|
/**
|
|
19
|
-
* The
|
|
20
|
-
*
|
|
26
|
+
* The theme as the embed wears it — `loop`, or the one the application
|
|
27
|
+
* names (T-30) — with the application's accent over `loop` in both modes
|
|
28
|
+
* (T-05), and the host's face when it named one.
|
|
21
29
|
*/
|
|
22
|
-
export function embedThemeStyles({ accent, font = '', }) {
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
export function embedThemeStyles({ accent, font = '', variant = 'loop', }) {
|
|
31
|
+
const base = getThemeConfig(variant).themeStyles;
|
|
32
|
+
const mode = (which) => {
|
|
33
|
+
const theme = base[which];
|
|
34
|
+
return withFace({ ...theme, ...accentOver(variant, accent, which) }, font, faceOf(variant, theme));
|
|
35
|
+
};
|
|
27
36
|
return {
|
|
28
|
-
...
|
|
37
|
+
...base,
|
|
29
38
|
light: mode('light'),
|
|
30
39
|
dark: mode('dark'),
|
|
31
40
|
};
|
|
32
41
|
}
|
|
33
42
|
/**
|
|
34
43
|
* The same accent and face laid over the conversation's own theme (T-05,
|
|
35
|
-
* D-11): the chat sets
|
|
36
|
-
* theme's
|
|
37
|
-
* accent's properties in each mode, and, when the host
|
|
38
|
-
* property of the theme that names its own.
|
|
44
|
+
* D-11): the chat sets its theme again inside it, which would put the
|
|
45
|
+
* theme's colours and face back over what the embed set around it. The
|
|
46
|
+
* accent's properties in each mode (over `loop` only), and, when the host
|
|
47
|
+
* named a face, every property of the theme that names its own.
|
|
39
48
|
*/
|
|
40
|
-
export function embedThemeOverrides({ accent, font = '', }) {
|
|
49
|
+
export function embedThemeOverrides({ accent, font = '', variant = 'loop', }) {
|
|
50
|
+
const base = getThemeConfig(variant).themeStyles;
|
|
41
51
|
const mode = (which) => {
|
|
42
|
-
const theme =
|
|
43
|
-
const faced = Object.fromEntries(Object.entries(withFace(theme, font)).filter(([name, value]) => typeof value === 'string' && value !== theme[name]));
|
|
44
|
-
return { ...faced, ...
|
|
52
|
+
const theme = base[which];
|
|
53
|
+
const faced = Object.fromEntries(Object.entries(withFace(theme, font, faceOf(variant, theme))).filter(([name, value]) => typeof value === 'string' && value !== theme[name]));
|
|
54
|
+
return { ...faced, ...accentOver(variant, accent, which) };
|
|
45
55
|
};
|
|
46
56
|
return { light: mode('light'), dark: mode('dark') };
|
|
47
57
|
}
|
package/lib/loop/index.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module loop
|
|
8
8
|
*/
|
|
9
|
-
export { LoopCommand, LoopDocumentToolbar, LoopMention, LoopNotebookToolbar, LoopSlots, LoopViewType, canOpenView, createPromptChannel, parseCommand, type CommandArgSpec, type CommandContribution, type CommandResult, type EditorToolbarContext, type EditorToolbarContribution, type LoopCommandContext, type LoopViewProps, type LoopWorkspaceContext, type MentionCandidate, type MentionContribution, type PromptChannel, type SandboxSnapshot, type SandboxState, type ViewControls, type ViewTypeContribution, useLoopPromptStore, suggestLoopPrompt, focusPrompt, onPromptFocusRequest, type LoopPromptState, type SuggestedPrompt, } from './core';
|
|
9
|
+
export { LoopCommand, LoopDocumentToolbar, LoopMention, LoopNotebookToolbar, LoopRunProps, LoopSlots, LoopViewType, canOpenView, createPromptChannel, parseCommand, runForwardedProps, type CommandArgSpec, type CommandContribution, type CommandResult, type EditorToolbarContext, type EditorToolbarContribution, type LoopCommandContext, type LoopViewProps, type LoopWorkspaceContext, type MentionCandidate, type MentionContribution, type PromptChannel, type RunPropsContribution, type SandboxSnapshot, type SandboxState, type ViewControls, type ViewTypeContribution, useLoopPromptStore, suggestLoopPrompt, focusPrompt, onPromptFocusRequest, type LoopPromptState, type SuggestedPrompt, } from './core';
|
|
10
10
|
export { useEditorToolbar } from './core/toolbar';
|
|
11
11
|
export { LoopWorkspace, PluginToggles, ViewSwitcher, WorkspaceFullScreenAction, buildLoopReactor, useWorkspaceFullScreen, type LoopWorkspaceProps, type PluginTogglesProps, type ViewSwitcherProps, } from './shell';
|
|
12
12
|
export { A2uiPlugin, A2UI_PLUGIN_NAME } from './plugins/a2ui';
|
|
@@ -67,10 +67,11 @@ export { WindowFrame, WindowFramePlugin, WINDOW_ACTIONS_SLOT, WINDOW_CONTROLS_SL
|
|
|
67
67
|
export { LoopCommandsPlugin, COMMANDS_PLUGIN_NAME } from './plugins/commands';
|
|
68
68
|
export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
|
|
69
69
|
export { LoopEmbed, type LoopEmbedProps } from './embed/LoopEmbed';
|
|
70
|
-
export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, useHostBridge, } from './embed/AppEmbed';
|
|
70
|
+
export { AppEmbed, AppFloating, embedAssistantCharacter, type AppEmbedProps, type AppFloatingProps, type EmbedSession, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
71
|
+
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
71
72
|
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, type AppEmbedHost, type HostEvent, type HostFunction, type HostQuestion, } from './embed/hostBridge';
|
|
72
73
|
export { defineDatalayerAppElement, type DatalayerAppElementOptions, } from './embed/element';
|
|
73
|
-
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
74
|
+
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, resumeOf, type EmbedLook, type EmbedSnippetOptions, } from './embed/embedConfig';
|
|
74
75
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
75
76
|
export { loopPlugins, type LoopPresetOptions } from './presets';
|
|
76
77
|
export { DocumentExtension, NotebookExtension } from './extensions';
|
package/lib/loop/index.js
CHANGED
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* @module loop
|
|
12
12
|
*/
|
|
13
13
|
// The contracts. A plugin depends on these and on the reactor, never on the shell.
|
|
14
|
-
export { LoopCommand, LoopDocumentToolbar, LoopMention, LoopNotebookToolbar, LoopSlots, LoopViewType, canOpenView, createPromptChannel, parseCommand, useLoopPromptStore, suggestLoopPrompt, focusPrompt, onPromptFocusRequest, } from './core';
|
|
14
|
+
export { LoopCommand, LoopDocumentToolbar, LoopMention, LoopNotebookToolbar, LoopRunProps, LoopSlots, LoopViewType, canOpenView, createPromptChannel, parseCommand, runForwardedProps, useLoopPromptStore, suggestLoopPrompt, focusPrompt, onPromptFocusRequest, } from './core';
|
|
15
15
|
export { useEditorToolbar } from './core/toolbar';
|
|
16
16
|
// The shell.
|
|
17
17
|
export { LoopWorkspace, PluginToggles, ViewSwitcher, WorkspaceFullScreenAction, buildLoopReactor, useWorkspaceFullScreen, } from './shell';
|
|
@@ -87,11 +87,12 @@ export { PromptPlugin, PROMPT_PLUGIN_NAME } from './plugins/prompt';
|
|
|
87
87
|
export { LoopEmbed } from './embed/LoopEmbed';
|
|
88
88
|
// An application in another product's page: the React component, the
|
|
89
89
|
// `<datalayer-app>` element, and the snippet a host pastes (LOOP D-07 to D-09).
|
|
90
|
-
export { AppEmbed, AppFloating, embedAssistantCharacter, useHostBridge, } from './embed/AppEmbed';
|
|
90
|
+
export { AppEmbed, AppFloating, embedAssistantCharacter, useEmbedSession, useHostBridge, } from './embed/AppEmbed';
|
|
91
|
+
export { EMBED_SESSION_KEY_PREFIX, embedSessionKey, visitOfToken, } from './embed/embedSession';
|
|
91
92
|
// The host page and an embedded application, talking (LOOP D-10).
|
|
92
93
|
export { HOST_CONTEXT_TOOL, hostFrontendTools, hostNotAllowed, hostRefused, hostTool, hostToolsOf, } from './embed/hostBridge';
|
|
93
94
|
export { defineDatalayerAppElement, } from './embed/element';
|
|
94
|
-
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, } from './embed/embedConfig';
|
|
95
|
+
export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSnippetOf, floatingViewOf, resumeOf, } from './embed/embedConfig';
|
|
95
96
|
export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
|
|
96
97
|
// The set a workspace normally has, so a host starts from a working Loop
|
|
97
98
|
// rather than assembling one — and never from the example.
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* @module loop/plugins/agent-capacity
|
|
18
18
|
*/
|
|
19
19
|
import { type ReactorPlugin } from '@datalayer/reactor';
|
|
20
|
-
import { type ChatSuggestionItem } from '../../core';
|
|
20
|
+
import { type AgentBlueprintContribution, type ChatSuggestionItem } from '../../core';
|
|
21
21
|
export type AgentCapacityOptions = {
|
|
22
22
|
/** Short slug: `hooks`, `codemode`, … names the plugin `…-agent-<key>`. */
|
|
23
23
|
key: string;
|
|
@@ -38,6 +38,8 @@ export type AgentCapacityOptions = {
|
|
|
38
38
|
instructions?: string;
|
|
39
39
|
/** The model it runs on in the page, in place of its spec's. */
|
|
40
40
|
model?: string;
|
|
41
|
+
/** What its modes tell a run turned in the page (LOOP P-19). */
|
|
42
|
+
modeEffect?: AgentBlueprintContribution['modeEffect'];
|
|
41
43
|
};
|
|
42
44
|
export declare function defineAgentCapacityPlugin(options: AgentCapacityOptions): ReactorPlugin<Record<string, never>, unknown, unknown>;
|
|
43
45
|
export default defineAgentCapacityPlugin;
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
import { contribution, definePlugin, } from '@datalayer/reactor';
|
|
24
24
|
import { LoopAgentBlueprint, LoopChatSuggestion, } from '../../core';
|
|
25
25
|
export function defineAgentCapacityPlugin(options) {
|
|
26
|
-
const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, } = options;
|
|
26
|
+
const { key, displayName, description, specId, octicon, emoji, codemode, createPayload, suggestions, instructions, model, modeEffect, } = options;
|
|
27
27
|
return definePlugin({
|
|
28
28
|
name: `@datalayer/loop-plugin-agent-${key}`,
|
|
29
29
|
displayName,
|
|
@@ -42,6 +42,7 @@ export function defineAgentCapacityPlugin(options) {
|
|
|
42
42
|
},
|
|
43
43
|
...(instructions ? { instructions } : {}),
|
|
44
44
|
...(model ? { model } : {}),
|
|
45
|
+
...(modeEffect ? { modeEffect } : {}),
|
|
45
46
|
}, { id: key }),
|
|
46
47
|
...(suggestions && suggestions.length > 0
|
|
47
48
|
? [
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
*
|
|
24
24
|
* @module loop/plugins/app-page/appPageModel
|
|
25
25
|
*/
|
|
26
|
-
import type {
|
|
26
|
+
import type { AppFormField, AppFormSchema, AppSpec } from '../../../types/agentspecs';
|
|
27
27
|
import type { ChatTurnSnapshot, ConversationEntry } from '../../core';
|
|
28
28
|
/** The id of the editor surface an application's page is drawn in. */
|
|
29
29
|
export declare const APP_PAGE_SURFACE = "app-page";
|
|
@@ -77,6 +77,15 @@ export declare const APP_KIND_PATHS: Record<AppPageKind, AppKindPaths>;
|
|
|
77
77
|
export declare const isAppPageKind: (kind: string) => kind is AppPageKind;
|
|
78
78
|
/** The path a setting, an input of the page, is kept at. */
|
|
79
79
|
export declare const inputPath: (id: string) => string;
|
|
80
|
+
/**
|
|
81
|
+
* The fields of an application's settings (C-16), by name, in the order its
|
|
82
|
+
* form says them; none when it has no settings.
|
|
83
|
+
*/
|
|
84
|
+
export declare function settingFields(settings: AppFormSchema | undefined): Array<[string, AppFormField]>;
|
|
85
|
+
/** A setting as a person reads it: its field's `title`, else its name. */
|
|
86
|
+
export declare const settingTitle: (id: string, field: AppFormField) => string;
|
|
87
|
+
/** What each setting starts at: its field's `default`, for those that say one. */
|
|
88
|
+
export declare function settingDefaults(settings: AppFormSchema | undefined): Record<string, unknown>;
|
|
80
89
|
/**
|
|
81
90
|
* What an application publishes and takes, with its own settings: the kind's
|
|
82
91
|
* list, and for a kind that has inputs each setting at `/inputs/<id>`, both
|
|
@@ -95,8 +104,6 @@ export declare function hasAppPage(app: Pick<AppSpec, 'kind' | 'interface' | 'ag
|
|
|
95
104
|
* none): the application's layout is `chat`, the conversation alone.
|
|
96
105
|
*/
|
|
97
106
|
export declare function surfaceUnshown(app: Pick<AppSpec, 'kind' | 'interface' | 'agent'>): string | null;
|
|
98
|
-
/** A setting's first value, as its input holds it. */
|
|
99
|
-
export declare function initialInput(setting: AppSettingSpec): unknown;
|
|
100
107
|
/**
|
|
101
108
|
* What the conversation publishes to the page, by path, now: from its
|
|
102
109
|
* current turn, and for a chat its whole conversation.
|
|
@@ -136,8 +143,10 @@ export declare function givenFiles(files: unknown): {
|
|
|
136
143
|
};
|
|
137
144
|
/**
|
|
138
145
|
* The application's settings as the page holds them, as the session API
|
|
139
|
-
* takes them
|
|
140
|
-
* as on or off
|
|
146
|
+
* takes them, each as its field's type says (C-16): a number as a number, a
|
|
147
|
+
* boolean as on or off, a word as the option chosen — a ChoicePicker holds a
|
|
148
|
+
* list of them — each setting the page has a value for. The runtime checks
|
|
149
|
+
* them against the settings' form.
|
|
141
150
|
*/
|
|
142
151
|
export declare function settingsOf(app: Pick<AppSpec, 'interface'>, inputs: unknown): Record<string, unknown>;
|
|
143
152
|
/**
|
|
@@ -141,6 +141,25 @@ export const APP_KIND_PATHS = {
|
|
|
141
141
|
export const isAppPageKind = (kind) => Object.prototype.hasOwnProperty.call(APP_KIND_PATHS, kind);
|
|
142
142
|
/** The path a setting, an input of the page, is kept at. */
|
|
143
143
|
export const inputPath = (id) => `/inputs/${id}`;
|
|
144
|
+
/**
|
|
145
|
+
* The fields of an application's settings (C-16), by name, in the order its
|
|
146
|
+
* form says them; none when it has no settings.
|
|
147
|
+
*/
|
|
148
|
+
export function settingFields(settings) {
|
|
149
|
+
const properties = settings?.properties;
|
|
150
|
+
if (!properties || typeof properties !== 'object') {
|
|
151
|
+
return [];
|
|
152
|
+
}
|
|
153
|
+
return Object.entries(properties).filter((entry) => typeof entry[1] === 'object' && entry[1] !== null);
|
|
154
|
+
}
|
|
155
|
+
/** A setting as a person reads it: its field's `title`, else its name. */
|
|
156
|
+
export const settingTitle = (id, field) => typeof field.title === 'string' && field.title ? field.title : id;
|
|
157
|
+
/** What each setting starts at: its field's `default`, for those that say one. */
|
|
158
|
+
export function settingDefaults(settings) {
|
|
159
|
+
return Object.fromEntries(settingFields(settings)
|
|
160
|
+
.filter(([, field]) => field.default !== undefined)
|
|
161
|
+
.map(([id, field]) => [id, field.default]));
|
|
162
|
+
}
|
|
144
163
|
/**
|
|
145
164
|
* What an application publishes and takes, with its own settings: the kind's
|
|
146
165
|
* list, and for a kind that has inputs each setting at `/inputs/<id>`, both
|
|
@@ -154,10 +173,10 @@ export function appKindPaths(app) {
|
|
|
154
173
|
if (!kind.inputs) {
|
|
155
174
|
return kind;
|
|
156
175
|
}
|
|
157
|
-
const inputs = app.interface.settings.map(
|
|
158
|
-
path: inputPath(
|
|
159
|
-
meaning: `The setting “${
|
|
160
|
-
words:
|
|
176
|
+
const inputs = settingFields(app.interface.settings).map(([id, field]) => ({
|
|
177
|
+
path: inputPath(id),
|
|
178
|
+
meaning: `The setting “${settingTitle(id, field)}”.`,
|
|
179
|
+
words: settingTitle(id, field),
|
|
161
180
|
}));
|
|
162
181
|
return {
|
|
163
182
|
...kind,
|
|
@@ -199,24 +218,6 @@ const STATUS_WORDS = {
|
|
|
199
218
|
done: 'Answered',
|
|
200
219
|
error: 'Stopped',
|
|
201
220
|
};
|
|
202
|
-
/** A setting's first value, as its input holds it. */
|
|
203
|
-
export function initialInput(setting) {
|
|
204
|
-
switch (setting.type) {
|
|
205
|
-
case 'toggle':
|
|
206
|
-
return Boolean(setting.default ?? false);
|
|
207
|
-
case 'select':
|
|
208
|
-
// A ChoicePicker holds a list of the values chosen.
|
|
209
|
-
return setting.default === undefined ? [] : [String(setting.default)];
|
|
210
|
-
case 'slider':
|
|
211
|
-
return Number(setting.default ?? setting.min ?? 0);
|
|
212
|
-
case 'number':
|
|
213
|
-
return setting.max === undefined
|
|
214
|
-
? String(setting.default ?? '')
|
|
215
|
-
: Number(setting.default ?? setting.min ?? 0);
|
|
216
|
-
default:
|
|
217
|
-
return String(setting.default ?? '');
|
|
218
|
-
}
|
|
219
|
-
}
|
|
220
221
|
/**
|
|
221
222
|
* What the conversation publishes to the page, by path, now: from its
|
|
222
223
|
* current turn, and for a chat its whole conversation.
|
|
@@ -253,10 +254,20 @@ export function appPageInitialData(app) {
|
|
|
253
254
|
}
|
|
254
255
|
const paths = appKindPaths(app);
|
|
255
256
|
if (paths.inputs) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
257
|
+
// Each setting at its default, as the settings' form holds it; a
|
|
258
|
+
// ChoicePicker a builder placed at a setting holds a list of the values
|
|
259
|
+
// chosen.
|
|
260
|
+
const inputs = settingDefaults(app.interface.settings);
|
|
261
|
+
for (const node of app.interface.surface?.components ?? []) {
|
|
262
|
+
const bound = node.value?.path;
|
|
263
|
+
const id = typeof bound === 'string' && bound.startsWith('/inputs/')
|
|
264
|
+
? bound.slice('/inputs/'.length)
|
|
265
|
+
: undefined;
|
|
266
|
+
if (node.component === 'ChoicePicker' && id && id in inputs) {
|
|
267
|
+
inputs[id] = Array.isArray(inputs[id]) ? inputs[id] : [inputs[id]];
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
tree.inputs = inputs;
|
|
260
271
|
}
|
|
261
272
|
if (paths.accepts.some(entry => entry.path === DRAFT.path)) {
|
|
262
273
|
tree.draft = '';
|
|
@@ -289,9 +300,9 @@ export function inputsInWords(app, inputs) {
|
|
|
289
300
|
: {};
|
|
290
301
|
const lines = [];
|
|
291
302
|
const seen = new Set();
|
|
292
|
-
for (const
|
|
293
|
-
seen.add(
|
|
294
|
-
lines.push(inputWords(
|
|
303
|
+
for (const [id, field] of settingFields(app.interface.settings)) {
|
|
304
|
+
seen.add(id);
|
|
305
|
+
lines.push(inputWords(settingTitle(id, field), values[id]));
|
|
295
306
|
}
|
|
296
307
|
for (const [id, value] of Object.entries(values)) {
|
|
297
308
|
if (!seen.has(id)) {
|
|
@@ -345,38 +356,43 @@ export function givenFiles(files) {
|
|
|
345
356
|
}
|
|
346
357
|
/**
|
|
347
358
|
* The application's settings as the page holds them, as the session API
|
|
348
|
-
* takes them
|
|
349
|
-
* as on or off
|
|
359
|
+
* takes them, each as its field's type says (C-16): a number as a number, a
|
|
360
|
+
* boolean as on or off, a word as the option chosen — a ChoicePicker holds a
|
|
361
|
+
* list of them — each setting the page has a value for. The runtime checks
|
|
362
|
+
* them against the settings' form.
|
|
350
363
|
*/
|
|
351
364
|
export function settingsOf(app, inputs) {
|
|
352
365
|
const values = inputs && typeof inputs === 'object'
|
|
353
366
|
? inputs
|
|
354
367
|
: {};
|
|
355
368
|
const settings = {};
|
|
356
|
-
for (const
|
|
357
|
-
const
|
|
369
|
+
for (const [id, field] of settingFields(app.interface.settings)) {
|
|
370
|
+
const held = values[id];
|
|
371
|
+
if (held === undefined || held === null) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
if (field.type === 'array' || field.type === 'object') {
|
|
375
|
+
settings[id] = held;
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const value = Array.isArray(held) ? held[0] : held;
|
|
358
379
|
if (value === undefined || value === null) {
|
|
359
380
|
continue;
|
|
360
381
|
}
|
|
361
|
-
switch (
|
|
362
|
-
case '
|
|
363
|
-
const chosen = Array.isArray(value) ? value[0] : value;
|
|
364
|
-
if (chosen !== undefined && chosen !== '') {
|
|
365
|
-
settings[setting.id] = String(chosen);
|
|
366
|
-
}
|
|
367
|
-
break;
|
|
368
|
-
}
|
|
369
|
-
case 'toggle':
|
|
370
|
-
settings[setting.id] = Boolean(value);
|
|
371
|
-
break;
|
|
372
|
-
case 'slider':
|
|
382
|
+
switch (field.type) {
|
|
383
|
+
case 'integer':
|
|
373
384
|
case 'number':
|
|
374
385
|
if (value !== '' && !Number.isNaN(Number(value))) {
|
|
375
|
-
settings[
|
|
386
|
+
settings[id] = Number(value);
|
|
376
387
|
}
|
|
377
388
|
break;
|
|
389
|
+
case 'boolean':
|
|
390
|
+
settings[id] = Boolean(value);
|
|
391
|
+
break;
|
|
378
392
|
default:
|
|
379
|
-
|
|
393
|
+
if (value !== '' || !Array.isArray(field.enum)) {
|
|
394
|
+
settings[id] = typeof value === 'string' ? value : String(value);
|
|
395
|
+
}
|
|
380
396
|
}
|
|
381
397
|
}
|
|
382
398
|
return settings;
|
|
@@ -464,44 +480,22 @@ const button = (id, label, name, primary = false) => [
|
|
|
464
480
|
},
|
|
465
481
|
{ id: `${id}-label`, component: 'Text', text: label },
|
|
466
482
|
];
|
|
467
|
-
/**
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
value: option,
|
|
484
|
-
})),
|
|
485
|
-
};
|
|
486
|
-
case 'slider':
|
|
487
|
-
return {
|
|
488
|
-
...base,
|
|
489
|
-
component: 'Slider',
|
|
490
|
-
min: setting.min ?? 0,
|
|
491
|
-
max: setting.max ?? 100,
|
|
492
|
-
};
|
|
493
|
-
case 'number':
|
|
494
|
-
return setting.max === undefined
|
|
495
|
-
? { ...base, component: 'TextField', variant: 'number' }
|
|
496
|
-
: {
|
|
497
|
-
...base,
|
|
498
|
-
component: 'Slider',
|
|
499
|
-
min: setting.min ?? 0,
|
|
500
|
-
max: setting.max,
|
|
501
|
-
};
|
|
502
|
-
default:
|
|
503
|
-
return { ...base, component: 'TextField' };
|
|
504
|
-
}
|
|
483
|
+
/**
|
|
484
|
+
* The settings as one Form block (C-16): their form drawn with
|
|
485
|
+
* `@datalayer/primer-rjsf`, the values written at `/inputs` as they are
|
|
486
|
+
* filled — no action, no button: they go with the next run.
|
|
487
|
+
*/
|
|
488
|
+
function settingsForm(app) {
|
|
489
|
+
return app.interface.settings && settingFields(app.interface.settings).length
|
|
490
|
+
? [
|
|
491
|
+
{
|
|
492
|
+
id: 'settings',
|
|
493
|
+
component: 'Form',
|
|
494
|
+
schema: app.interface.settings,
|
|
495
|
+
values: { path: '/inputs' },
|
|
496
|
+
},
|
|
497
|
+
]
|
|
498
|
+
: [];
|
|
505
499
|
}
|
|
506
500
|
/**
|
|
507
501
|
* The page drawn when the application has a page layout and no surface
|
|
@@ -509,9 +503,7 @@ function settingInput(setting) {
|
|
|
509
503
|
* buttons its kind handles that the conversation does not already give.
|
|
510
504
|
*/
|
|
511
505
|
export function defaultAppSurface(app) {
|
|
512
|
-
const inputs = appKindPaths(app).inputs
|
|
513
|
-
? app.interface.settings.map(settingInput)
|
|
514
|
-
: [];
|
|
506
|
+
const inputs = appKindPaths(app).inputs ? settingsForm(app) : [];
|
|
515
507
|
const body = [];
|
|
516
508
|
switch (app.kind) {
|
|
517
509
|
case 'chat':
|
|
@@ -41,7 +41,7 @@ import { AGENTS_PLUGIN_NAME } from '../agents/plugin';
|
|
|
41
41
|
import { useConfig } from '../../../hooks/useConfig';
|
|
42
42
|
import { useSkills, useSkillActions } from '../../../hooks/useSkills';
|
|
43
43
|
import { AI_MODEL_CATALOGUE, isChatModel } from '../../../specs/models';
|
|
44
|
-
import { LoopAgentBlueprint, LoopAgentGate, LoopChatComposer, LoopChatExtras, LoopChatHeader, LoopChatLayout, LoopChatSuggestion, LoopChatSurface, LoopChatTurn, LoopFrontendTool, LoopSlots, canOpenView, onPromptFocusRequest, onSurfaceRequest, useLoopPromptStore, } from '../../core';
|
|
44
|
+
import { LoopAgentBlueprint, LoopAgentGate, LoopChatComposer, LoopChatExtras, LoopChatHeader, LoopChatLayout, LoopChatSuggestion, LoopChatSurface, LoopChatTurn, LoopCommand, LoopFrontendTool, LoopRunProps, LoopSlots, canOpenView, runForwardedProps, onPromptFocusRequest, onSurfaceRequest, useLoopPromptStore, } from '../../core';
|
|
45
45
|
import { messageText, turnWritersOf } from './turnState';
|
|
46
46
|
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
47
47
|
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
@@ -353,6 +353,10 @@ export default function ChatView({ workspace }) {
|
|
|
353
353
|
const heldForAdapter = useRef([]);
|
|
354
354
|
/** Whether the agent is turned in this page: read by `sendNow`, set below. */
|
|
355
355
|
const inPageRef = useRef(false);
|
|
356
|
+
/** What goes with every run (an application's modes, LOOP P-19), read when it is sent. */
|
|
357
|
+
const runProps = useContributions(LoopRunProps);
|
|
358
|
+
const runPropsRef = useRef(runProps);
|
|
359
|
+
runPropsRef.current = runProps;
|
|
356
360
|
/*
|
|
357
361
|
* Every message that goes to the agent goes through here: the composer's
|
|
358
362
|
* (through the workspace's dispatch and the prompt channel), a host's, and
|
|
@@ -362,13 +366,17 @@ export default function ChatView({ workspace }) {
|
|
|
362
366
|
* What the page did besides the message goes with it as AG-UI's
|
|
363
367
|
* `forwardedProps` (`loop`, LOOP R-04): the application's session takes a
|
|
364
368
|
* file there. An agent turned in this page has no session, and no file is
|
|
365
|
-
* handed to it: said, rather than dropped.
|
|
369
|
+
* handed to it: said, rather than dropped. What the workspace's plugins
|
|
370
|
+
* send with every run (`LoopRunProps`: the modes the person is in, LOOP
|
|
371
|
+
* P-19) goes under it — and an agent turned in this page applies the modes
|
|
372
|
+
* itself (`modeEffect`, `BrowserAgentAdapter`).
|
|
366
373
|
*/
|
|
367
|
-
const sendNow = useCallback((message,
|
|
368
|
-
const loop =
|
|
374
|
+
const sendNow = useCallback((message, given) => {
|
|
375
|
+
const loop = given?.loop;
|
|
369
376
|
if (inPageRef.current && loop?.files && loop.files.length > 0) {
|
|
370
377
|
return 'A file is given to an application running on a runtime: run it on Datalayer or on your machine to give it one.';
|
|
371
378
|
}
|
|
379
|
+
const forwardedProps = runForwardedProps(runPropsRef.current.map(entry => entry.value), given);
|
|
372
380
|
// A new turn: whatever the panel showed is gone, this message is it.
|
|
373
381
|
turnFeedRef.current?.begin(message, controlsRef.current?.thread());
|
|
374
382
|
const send = controlsRef.current?.send;
|
|
@@ -936,6 +944,18 @@ export default function ChatView({ workspace }) {
|
|
|
936
944
|
* silent no-ops. Derived here rather than in the plugin because it changes
|
|
937
945
|
* with the selected member.
|
|
938
946
|
*/
|
|
947
|
+
/*
|
|
948
|
+
* The commands the composer lists while `/` is typed: those contributed for
|
|
949
|
+
* it, an application's (LOOP P-19). Picking one writes `/<name> `, and the
|
|
950
|
+
* workspace runs it when the message is sent.
|
|
951
|
+
*/
|
|
952
|
+
const commands = useContributions(LoopCommand);
|
|
953
|
+
const composerCommands = useMemo(() => commands
|
|
954
|
+
.filter(entry => entry.value.composer)
|
|
955
|
+
.map(entry => ({
|
|
956
|
+
name: entry.value.name,
|
|
957
|
+
description: entry.value.description,
|
|
958
|
+
})), [commands]);
|
|
939
959
|
const mentionable = useMemo(() => {
|
|
940
960
|
if (!team || !member) {
|
|
941
961
|
// No team: the spec's own subagents, which the in-page loop can reach
|
|
@@ -1175,6 +1195,10 @@ export default function ChatView({ workspace }) {
|
|
|
1175
1195
|
// server's monitoring stream fills, so the side panel and the
|
|
1176
1196
|
// composer's pulse draw an in-page delegation like any other.
|
|
1177
1197
|
onSubagentEvent: event => agentRuntimeStore.getState().appendSubagentEvent(event),
|
|
1198
|
+
// An application's modes (LOOP P-19): the options sent with a
|
|
1199
|
+
// run tell it their instructions, on the model one names, as
|
|
1200
|
+
// its session API does on a runtime.
|
|
1201
|
+
modeEffect: blueprintTurn?.modeEffect,
|
|
1178
1202
|
})
|
|
1179
1203
|
: {
|
|
1180
1204
|
type: 'ag-ui',
|
|
@@ -1402,6 +1426,7 @@ export default function ChatView({ workspace }) {
|
|
|
1402
1426
|
// because focusing an input on mount scrolls the browser to it.
|
|
1403
1427
|
autoFocus: config?.autoFocusPrompt ?? true,
|
|
1404
1428
|
mentionableAgents: mentionable,
|
|
1429
|
+
promptCommands: composerCommands,
|
|
1405
1430
|
headerContent: inpromptMenu.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.inpromptMenu, props: { workspace } })) : undefined,
|
|
1406
1431
|
footerExtras: promptActions.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.promptAction, props: { workspace } })) : undefined,
|
|
1407
1432
|
/*
|
|
@@ -1531,6 +1556,8 @@ export default function ChatView({ workspace }) {
|
|
|
1531
1556
|
, {
|
|
1532
1557
|
// The theme it wears, when the host names one (an application's).
|
|
1533
1558
|
themeVariant: config?.themeVariant, themeOverrides: config?.themeOverrides, colorMode: config?.colorMode,
|
|
1559
|
+
// The session an embed goes on with after a reload (LOOP D-13).
|
|
1560
|
+
thread: config?.thread,
|
|
1534
1561
|
// The header says why, beside the title, for the same reason the
|
|
1535
1562
|
// placeholder does: a dead control with no explanation is worse
|
|
1536
1563
|
// than an absent one.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { PresenceState } from '../../../chat/presence/presenceStatus';
|
|
2
2
|
import type { AssistantSaying } from '../../../chat/assistant/state';
|
|
3
|
-
import type { ThemeOverrides } from '../../../types/chat';
|
|
3
|
+
import type { ResumedThread, ThemeOverrides } from '../../../types/chat';
|
|
4
4
|
/** What a host may set on the chat. */
|
|
5
5
|
/** Who is answering, as the person meets them. */
|
|
6
6
|
export interface ChatPresence {
|
|
@@ -62,6 +62,11 @@ export type ChatPluginConfig = {
|
|
|
62
62
|
* application sets its own.
|
|
63
63
|
*/
|
|
64
64
|
presence?: ChatPresence;
|
|
65
|
+
/**
|
|
66
|
+
* The conversation to go on with (LOOP D-13): an embedded application's
|
|
67
|
+
* session, reattached after its page reloaded, and what it holds.
|
|
68
|
+
*/
|
|
69
|
+
thread?: ResumedThread;
|
|
65
70
|
/**
|
|
66
71
|
* Whether the counters — tokens, context — are shown under the prompt.
|
|
67
72
|
* On unless said: a person using an application is not asking.
|
package/lib/loop/presets.d.ts
CHANGED
|
@@ -23,7 +23,7 @@ import { type PluginRef } from '@datalayer/reactor';
|
|
|
23
23
|
import { type AgentsConfig, type SandboxTarget } from './plugins/agents';
|
|
24
24
|
import { type ChatPluginConfig, type ChatPresence } from './plugins/chat';
|
|
25
25
|
import { type PageSize } from './plugins/page-layout';
|
|
26
|
-
import type { ThemeOverrides } from '../types/chat';
|
|
26
|
+
import type { ResumedThread, ThemeOverrides } from '../types/chat';
|
|
27
27
|
export type LoopPresetOptions = {
|
|
28
28
|
/** Where the agent runtimes service is. */
|
|
29
29
|
serverUrl?: string;
|
|
@@ -155,6 +155,8 @@ export type LoopPresetOptions = {
|
|
|
155
155
|
presence?: ChatPresence;
|
|
156
156
|
/** Whether the counters are shown under the prompt; on unless said. */
|
|
157
157
|
showTokenUsage?: boolean;
|
|
158
|
+
/** The conversation to go on with: an embed's session after a reload (LOOP D-13). */
|
|
159
|
+
thread?: ResumedThread;
|
|
158
160
|
/**
|
|
159
161
|
* The agentspec the local agent is created from, by id.
|
|
160
162
|
*
|
package/lib/loop/presets.js
CHANGED
|
@@ -52,7 +52,7 @@ import { LoopPageLayoutPlugin } from './plugins/page-layout';
|
|
|
52
52
|
* be installed or signed into, and the switches a demonstration wants are off.
|
|
53
53
|
*/
|
|
54
54
|
export function loopPlugins(options = {}) {
|
|
55
|
-
const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, datalayerVisitors, datalayerKept, embedToken, targetFixed, themeVariant, themeOverrides, colorMode, presence, showTokenUsage, floatingPrompt = false, editorSelector = false, editors = true, conversation = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
|
|
55
|
+
const { serverUrl, target = 'browser', defaultEditor: askedEditor, showViewSelector = true, hideChatHeader = false, chatHeaderButtons = false, promptPlacement, autoFocusPrompt = true, suggestionLabels = true, fullScreenTopOffset = 0, showAgentVariants = false, agentSummary = true, teamId, teamPicker = true, pageLayout = false, pageLayoutArrangement = 'page', pageLayoutTurnPanel = 'below', pageLayoutPrompt = 'docked', pageLayoutPromptAnchor = 'top', pageLayoutSize, pageLayoutTurnPanelFooter = 'full', localAgent, localAgentSpec, datalayerAgentSpecId, datalayerCreatePayload, datalayerVisitors, datalayerKept, embedToken, targetFixed, themeVariant, themeOverrides, colorMode, presence, showTokenUsage, thread, floatingPrompt = false, editorSelector = false, editors = true, conversation = true, graph = false, commandPalette = false, pluginsPanel = false, windowFrame = false, firstPromptHook, } = options;
|
|
56
56
|
const defaultEditor = askedEditor ?? (editors ? 'notebook' : 'none');
|
|
57
57
|
// A workspace without a conversation: the theme, the shell's points, and
|
|
58
58
|
// the host's chrome; its view is a host's plugin's.
|
|
@@ -89,6 +89,7 @@ export function loopPlugins(options = {}) {
|
|
|
89
89
|
colorMode,
|
|
90
90
|
presence,
|
|
91
91
|
showTokenUsage,
|
|
92
|
+
...(thread ? { thread } : {}),
|
|
92
93
|
}),
|
|
93
94
|
// The composer and the title bar are plugins of their own: the chat
|
|
94
95
|
// assembles their props, these render them. In the preset by default —
|
|
@@ -91,8 +91,11 @@ function WorkspaceBody({ serverUrl, agentId: initialAgentId, conversationId, mod
|
|
|
91
91
|
workspace: currentWorkspace.current,
|
|
92
92
|
argv: command.argv,
|
|
93
93
|
});
|
|
94
|
+
// What a command sends goes to the agent as if typed: an
|
|
95
|
+
// application's command sends its prompt (LOOP P-19).
|
|
94
96
|
if (result?.prompt) {
|
|
95
97
|
onSend?.(result.prompt, currentWorkspace.current);
|
|
98
|
+
prompts.submit(result.prompt);
|
|
96
99
|
}
|
|
97
100
|
return {
|
|
98
101
|
handled: true,
|