@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
package/lib/loop/apps/checks.js
CHANGED
|
@@ -39,8 +39,9 @@ import { getTeamSpec } from '../../specs/teams';
|
|
|
39
39
|
import { getBackendToolSpec } from '../../specs/backendTools';
|
|
40
40
|
import { getTrack } from '../../specs/tracks';
|
|
41
41
|
import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
|
|
42
|
-
import { isAssistantCharacterId, parseAppspec } from './appspec';
|
|
42
|
+
import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, parseAppspec, } from './appspec';
|
|
43
43
|
import { classesOf, splitRef, toolBehaviours } from './rules';
|
|
44
|
+
import { COMMAND_INPUT, COMMAND_NAME, MODE_ID } from './composer';
|
|
44
45
|
import { HOST_NAME, hostToolsOf } from './hostTools';
|
|
45
46
|
export const NOT_READY = 'Not ready';
|
|
46
47
|
export const NEEDS_ATTENTION = 'Needs attention';
|
|
@@ -94,6 +95,14 @@ function shapeProblems(app) {
|
|
|
94
95
|
if (Boolean(app.agent) === Boolean(app.team)) {
|
|
95
96
|
problems.push('An application names who does the work: an `agent`, or a `team`, and not both.');
|
|
96
97
|
}
|
|
98
|
+
// agentspecs refuses either way round (LOOP E-01): off says why, on says nothing.
|
|
99
|
+
const because = (app.unavailable_because ?? '').trim();
|
|
100
|
+
if (!app.enabled && !because) {
|
|
101
|
+
problems.push('An application that is not offered says why, under `unavailable_because`.');
|
|
102
|
+
}
|
|
103
|
+
if (app.enabled && because) {
|
|
104
|
+
problems.push('An application offered today is available: remove `unavailable_because`, or set `enabled: false`.');
|
|
105
|
+
}
|
|
97
106
|
if (app.kind === 'decision' && !app.decision) {
|
|
98
107
|
problems.push('A decision application says what it decides, under `decision`.');
|
|
99
108
|
}
|
|
@@ -269,6 +278,14 @@ function referenceProblems(app, organizationFrames) {
|
|
|
269
278
|
if (app.model && !getModel(app.model)) {
|
|
270
279
|
missing('model', app.model);
|
|
271
280
|
}
|
|
281
|
+
// The model a mode runs on is the catalogue's (LOOP P-19).
|
|
282
|
+
for (const mode of app.interface.modes ?? []) {
|
|
283
|
+
for (const option of mode.options) {
|
|
284
|
+
if (option.model && !getModel(option.model)) {
|
|
285
|
+
problems.push(`The mode “${mode.id}” runs “${option.id}” on “${option.model}”, which is no model.`);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
272
289
|
// The components it may use, and those its surface uses, are the catalog's (C-13).
|
|
273
290
|
for (const name of app.interface.components ?? []) {
|
|
274
291
|
if (!componentNamed(name)) {
|
|
@@ -463,7 +480,6 @@ const ENUMS = {
|
|
|
463
480
|
behaviour: ['do_it', 'if_asked', 'ask_first', 'leave_to_me'],
|
|
464
481
|
layout: ['chat', 'page', 'split'],
|
|
465
482
|
accent: ['green', 'rose', 'sky', 'lime', 'sun', 'violet'],
|
|
466
|
-
settingType: ['select', 'text', 'toggle', 'slider', 'number'],
|
|
467
483
|
record: [
|
|
468
484
|
'conversations',
|
|
469
485
|
'actions',
|
|
@@ -477,6 +493,8 @@ const ENUMS = {
|
|
|
477
493
|
visibility: ['private', 'invited', 'organization', 'link', 'public'],
|
|
478
494
|
mode: ['inline', 'bubble', 'panel', 'assistant'],
|
|
479
495
|
balloon: ['history', 'current'],
|
|
496
|
+
themeVariant: APP_THEME_VARIANTS,
|
|
497
|
+
themeMode: APP_THEME_MODES,
|
|
480
498
|
trigger: ['schedule', 'event', 'once'],
|
|
481
499
|
criterion: ['metric', 'noul', 'choice', 'score'],
|
|
482
500
|
direction: ['higher', 'lower'],
|
|
@@ -534,6 +552,13 @@ export function documentShapeProblems(document) {
|
|
|
534
552
|
if (item[key] === undefined)
|
|
535
553
|
at(`${where}.${key}`, 'is missing');
|
|
536
554
|
};
|
|
555
|
+
const unknownKeys = (item, known, where, what) => {
|
|
556
|
+
for (const key of Object.keys(item)) {
|
|
557
|
+
if (!known.includes(key)) {
|
|
558
|
+
at(`${where}.${key}`, `is not a key of ${what}: ${known.join(', ')}`);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
};
|
|
537
562
|
const d = document;
|
|
538
563
|
for (const key of [
|
|
539
564
|
'id',
|
|
@@ -605,6 +630,17 @@ export function documentShapeProblems(document) {
|
|
|
605
630
|
oneOf(ui.layout, ENUMS.layout, 'interface.layout');
|
|
606
631
|
oneOf(ui.accent, ENUMS.accent, 'interface.accent');
|
|
607
632
|
oneOf(ui.balloon, ENUMS.balloon, 'interface.balloon');
|
|
633
|
+
// The theme it runs in by default (T-30): a theme of Appearance's, a mode.
|
|
634
|
+
mapping(ui.theme, 'interface.theme', theme => {
|
|
635
|
+
required(theme, 'variant', 'interface.theme');
|
|
636
|
+
oneOf(theme.variant, ENUMS.themeVariant, 'interface.theme.variant');
|
|
637
|
+
oneOf(theme.mode, ENUMS.themeMode, 'interface.theme.mode');
|
|
638
|
+
for (const key of Object.keys(theme)) {
|
|
639
|
+
if (key !== 'variant' && key !== 'mode') {
|
|
640
|
+
at(`interface.theme.${key}`, 'is not a key of a theme: variant, mode');
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
});
|
|
608
644
|
if (ui.assistant !== undefined && !isAssistantCharacterId(ui.assistant)) {
|
|
609
645
|
at('interface.assistant', 'names a character by the id a plugin contributes it under, lowercase words joined by a hyphen: `paperclip`, `acme-owl`');
|
|
610
646
|
}
|
|
@@ -614,13 +650,94 @@ export function documentShapeProblems(document) {
|
|
|
614
650
|
required(item, 'label', where);
|
|
615
651
|
required(item, 'message', where);
|
|
616
652
|
});
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
653
|
+
// Its commands and modes in the composer (LOOP P-19), as agentspecs
|
|
654
|
+
// refuses them: names and ids slash-safe and said once, no key unknown.
|
|
655
|
+
const named = new Set();
|
|
656
|
+
records(ui.commands, 'interface.commands', (item, where) => {
|
|
657
|
+
for (const key of ['name', 'description', 'prompt']) {
|
|
658
|
+
required(item, key, where);
|
|
659
|
+
text(item[key], `${where}.${key}`);
|
|
660
|
+
}
|
|
661
|
+
unknownKeys(item, ['name', 'description', 'prompt'], where, 'a command');
|
|
662
|
+
if (typeof item.name === 'string') {
|
|
663
|
+
if (!COMMAND_NAME.test(item.name)) {
|
|
664
|
+
at(`${where}.name`, 'is what follows the slash: lower-case letters, digits and hyphens, a letter first');
|
|
665
|
+
}
|
|
666
|
+
if (named.has(item.name)) {
|
|
667
|
+
at(`${where}.name`, `names /${item.name} a second time`);
|
|
668
|
+
}
|
|
669
|
+
named.add(item.name);
|
|
670
|
+
}
|
|
671
|
+
if (typeof item.prompt === 'string') {
|
|
672
|
+
for (const placeholder of item.prompt.match(/\{[^{}]*\}/g) ?? []) {
|
|
673
|
+
if (placeholder !== COMMAND_INPUT) {
|
|
674
|
+
at(`${where}.prompt`, `takes the words typed after the command as ${COMMAND_INPUT}, not ${placeholder}`);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
});
|
|
679
|
+
const modeIds = new Set();
|
|
680
|
+
const choosingModel = [];
|
|
681
|
+
records(ui.modes, 'interface.modes', (mode, where) => {
|
|
682
|
+
required(mode, 'id', where);
|
|
683
|
+
required(mode, 'label', where);
|
|
684
|
+
required(mode, 'options', where);
|
|
685
|
+
text(mode.label, `${where}.label`);
|
|
686
|
+
text(mode.default, `${where}.default`);
|
|
687
|
+
unknownKeys(mode, ['id', 'label', 'options', 'default'], where, 'a mode');
|
|
688
|
+
if (typeof mode.id === 'string') {
|
|
689
|
+
if (!MODE_ID.test(mode.id)) {
|
|
690
|
+
at(`${where}.id`, 'is lower-case letters, digits, `_` and `-`, a letter first');
|
|
691
|
+
}
|
|
692
|
+
if (modeIds.has(mode.id)) {
|
|
693
|
+
at(`${where}.id`, `names the mode ${mode.id} a second time`);
|
|
694
|
+
}
|
|
695
|
+
modeIds.add(mode.id);
|
|
696
|
+
}
|
|
697
|
+
const optionIds = [];
|
|
698
|
+
let model = false;
|
|
699
|
+
records(mode.options, `${where}.options`, (option, place) => {
|
|
700
|
+
required(option, 'id', place);
|
|
701
|
+
required(option, 'label', place);
|
|
702
|
+
for (const key of ['label', 'description', 'instructions', 'model']) {
|
|
703
|
+
text(option[key], `${place}.${key}`);
|
|
704
|
+
}
|
|
705
|
+
unknownKeys(option, ['id', 'label', 'description', 'instructions', 'model'], place, 'an option');
|
|
706
|
+
if (typeof option.id === 'string') {
|
|
707
|
+
if (!MODE_ID.test(option.id)) {
|
|
708
|
+
at(`${place}.id`, 'is lower-case letters, digits, `_` and `-`, a letter first');
|
|
709
|
+
}
|
|
710
|
+
if (optionIds.includes(option.id)) {
|
|
711
|
+
at(`${place}.id`, `names the option ${option.id} a second time`);
|
|
712
|
+
}
|
|
713
|
+
optionIds.push(option.id);
|
|
714
|
+
}
|
|
715
|
+
model ||= typeof option.model === 'string' && option.model !== '';
|
|
716
|
+
});
|
|
717
|
+
if (Array.isArray(mode.options) && mode.options.length < 2) {
|
|
718
|
+
at(`${where}.options`, 'are two at least');
|
|
719
|
+
}
|
|
720
|
+
if (typeof mode.default === 'string' &&
|
|
721
|
+
!optionIds.includes(mode.default)) {
|
|
722
|
+
at(`${where}.default`, 'is one of its options');
|
|
723
|
+
}
|
|
724
|
+
if (model) {
|
|
725
|
+
choosingModel.push(String(mode.id));
|
|
726
|
+
}
|
|
623
727
|
});
|
|
728
|
+
if (choosingModel.length > 1) {
|
|
729
|
+
at('interface.modes', `let only one mode choose the model, not ${choosingModel.join(', ')}`);
|
|
730
|
+
}
|
|
731
|
+
// Its settings are the JSON Schema of a form (C-16), checked as a Form's
|
|
732
|
+
// schema is; the list of settings of earlier versions is refused.
|
|
733
|
+
if (Array.isArray(ui.settings)) {
|
|
734
|
+
at('interface.settings', 'is the JSON Schema of a form, an object of named fields (LOOP C-16), not a list of settings: say each one as a property, its title and its default');
|
|
735
|
+
}
|
|
736
|
+
else {
|
|
737
|
+
mapping(ui.settings, 'interface.settings', settings => {
|
|
738
|
+
problems.push(...formProblems({ id: 'settings', schema: settings }));
|
|
739
|
+
});
|
|
740
|
+
}
|
|
624
741
|
mapping(ui.surface, 'interface.surface', surface => {
|
|
625
742
|
records(surface.components, 'interface.surface.components', (item, where) => {
|
|
626
743
|
required(item, 'id', where);
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Commands and modes in the composer (LOOP P-19), as rules: what a command
|
|
3
|
+
* sends, and which option of each mode a run is in. agentspecs' own
|
|
4
|
+
* (`command_prompt`, `AppInterface.mode_choice`, `mode_effect`), said again
|
|
5
|
+
* for the page.
|
|
6
|
+
*
|
|
7
|
+
* Pure: no React, no network.
|
|
8
|
+
*
|
|
9
|
+
* @module loop/apps/composer
|
|
10
|
+
*/
|
|
11
|
+
import type { AppCommandSpec, AppModeEffect, AppSpec } from '../../types/agentspecs';
|
|
12
|
+
/** Where a command's prompt takes the words typed after it. */
|
|
13
|
+
export declare const COMMAND_INPUT = "{input}";
|
|
14
|
+
/** What follows the slash: lower-case letters, digits and hyphens, a letter first. */
|
|
15
|
+
export declare const COMMAND_NAME: RegExp;
|
|
16
|
+
/** The id of a mode or of one of its options. */
|
|
17
|
+
export declare const MODE_ID: RegExp;
|
|
18
|
+
/**
|
|
19
|
+
* What a command sends: its prompt, the words typed after it in place of
|
|
20
|
+
* `{input}` — or after the prompt, when it has no `{input}`.
|
|
21
|
+
*/
|
|
22
|
+
export declare function commandPrompt(command: Pick<AppCommandSpec, 'prompt'>, words?: string): string;
|
|
23
|
+
/** The option each mode starts on: its default, else its first. */
|
|
24
|
+
export declare function modeDefaults(app: Pick<AppSpec, 'interface'>): Record<string, string>;
|
|
25
|
+
/**
|
|
26
|
+
* The option of every mode a run is in: what was chosen, else where each
|
|
27
|
+
* starts. A mode or an option the application does not have is refused, in a
|
|
28
|
+
* sentence, as the runtime refuses it.
|
|
29
|
+
*/
|
|
30
|
+
export declare function modeChoice(app: Pick<AppSpec, 'name' | 'interface'>, chosen?: Record<string, string>): Record<string, string>;
|
|
31
|
+
/**
|
|
32
|
+
* What a run in the modes chosen is told, and the model it runs on —
|
|
33
|
+
* agentspecs' `mode_effect`: the instructions of every option chosen, in the
|
|
34
|
+
* order of the modes, and the model of the first that names one. Refused as
|
|
35
|
+
* {@link modeChoice} refuses.
|
|
36
|
+
*/
|
|
37
|
+
export declare function modeEffect(app: Pick<AppSpec, 'name' | 'interface'>, chosen?: Record<string, string>): AppModeEffect;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) 2025-2026 Datalayer, Inc.
|
|
3
|
+
* Distributed under the terms of the Modified BSD License.
|
|
4
|
+
*/
|
|
5
|
+
/** Where a command's prompt takes the words typed after it. */
|
|
6
|
+
export const COMMAND_INPUT = '{input}';
|
|
7
|
+
/** What follows the slash: lower-case letters, digits and hyphens, a letter first. */
|
|
8
|
+
export const COMMAND_NAME = /^[a-z](?:[a-z0-9-]{0,30}[a-z0-9])?$/;
|
|
9
|
+
/** The id of a mode or of one of its options. */
|
|
10
|
+
export const MODE_ID = /^[a-z][a-z0-9_-]{0,39}$/;
|
|
11
|
+
/**
|
|
12
|
+
* What a command sends: its prompt, the words typed after it in place of
|
|
13
|
+
* `{input}` — or after the prompt, when it has no `{input}`.
|
|
14
|
+
*/
|
|
15
|
+
export function commandPrompt(command, words = '') {
|
|
16
|
+
const said = words.trim();
|
|
17
|
+
if (command.prompt.includes(COMMAND_INPUT)) {
|
|
18
|
+
return command.prompt.split(COMMAND_INPUT).join(said).trim();
|
|
19
|
+
}
|
|
20
|
+
return said ? `${command.prompt}\n\n${said}` : command.prompt;
|
|
21
|
+
}
|
|
22
|
+
/** The option each mode starts on: its default, else its first. */
|
|
23
|
+
export function modeDefaults(app) {
|
|
24
|
+
return Object.fromEntries((app.interface.modes ?? [])
|
|
25
|
+
.filter(mode => mode.options.length > 0)
|
|
26
|
+
.map(mode => [mode.id, mode.default || mode.options[0].id]));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The option of every mode a run is in: what was chosen, else where each
|
|
30
|
+
* starts. A mode or an option the application does not have is refused, in a
|
|
31
|
+
* sentence, as the runtime refuses it.
|
|
32
|
+
*/
|
|
33
|
+
export function modeChoice(app, chosen = {}) {
|
|
34
|
+
const modes = app.interface.modes ?? [];
|
|
35
|
+
for (const [modeId, optionId] of Object.entries(chosen)) {
|
|
36
|
+
const mode = modes.find(candidate => candidate.id === modeId);
|
|
37
|
+
if (!mode) {
|
|
38
|
+
throw new Error(`${app.name} has no mode “${modeId}”.`);
|
|
39
|
+
}
|
|
40
|
+
if (!mode.options.some(option => option.id === optionId)) {
|
|
41
|
+
throw new Error(`The mode ${mode.label} has no option “${optionId}”.`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { ...modeDefaults(app), ...chosen };
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* What a run in the modes chosen is told, and the model it runs on —
|
|
48
|
+
* agentspecs' `mode_effect`: the instructions of every option chosen, in the
|
|
49
|
+
* order of the modes, and the model of the first that names one. Refused as
|
|
50
|
+
* {@link modeChoice} refuses.
|
|
51
|
+
*/
|
|
52
|
+
export function modeEffect(app, chosen = {}) {
|
|
53
|
+
const choice = modeChoice(app, chosen);
|
|
54
|
+
const options = (app.interface.modes ?? []).flatMap(mode => mode.options.filter(option => option.id === choice[mode.id]));
|
|
55
|
+
const instructions = options
|
|
56
|
+
.map(option => (option.instructions ?? '').trim())
|
|
57
|
+
.filter(Boolean)
|
|
58
|
+
.join('\n\n');
|
|
59
|
+
const model = options.find(option => option.model)?.model ?? undefined;
|
|
60
|
+
return model ? { instructions, model } : { instructions };
|
|
61
|
+
}
|
package/lib/loop/apps/index.d.ts
CHANGED
|
@@ -4,11 +4,13 @@
|
|
|
4
4
|
* @module loop/apps
|
|
5
5
|
*/
|
|
6
6
|
export * from './agent';
|
|
7
|
+
export * from './AppComposer';
|
|
7
8
|
export * from './AppFeedback';
|
|
8
9
|
export * from './AppKept';
|
|
9
10
|
export * from './AppRenderer';
|
|
10
11
|
export * from './appspec';
|
|
11
12
|
export * from './checks';
|
|
13
|
+
export * from './composer';
|
|
12
14
|
export * from './computer';
|
|
13
15
|
export * from './feedback';
|
|
14
16
|
export * from './kept';
|
package/lib/loop/apps/index.js
CHANGED
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
* @module loop/apps
|
|
9
9
|
*/
|
|
10
10
|
export * from './agent';
|
|
11
|
+
export * from './AppComposer';
|
|
11
12
|
export * from './AppFeedback';
|
|
12
13
|
export * from './AppKept';
|
|
13
14
|
export * from './AppRenderer';
|
|
14
15
|
export * from './appspec';
|
|
15
16
|
export * from './checks';
|
|
17
|
+
export * from './composer';
|
|
16
18
|
export * from './computer';
|
|
17
19
|
export * from './feedback';
|
|
18
20
|
export * from './kept';
|
package/lib/loop/core/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ComponentType, ReactNode } from 'react';
|
|
|
3
3
|
import type { ExecutionState } from '@datalayer/jupyter-react/kernel-indicator';
|
|
4
4
|
import type { ToolbarItem } from '@datalayer/primer-addons';
|
|
5
5
|
import type { FrontendToolDefinition } from '../../types/tools';
|
|
6
|
+
import type { AppModeEffect } from '../../types/agentspecs';
|
|
6
7
|
import type { ContextSnapshotData } from '../../types/context';
|
|
7
8
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
8
9
|
import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
|
|
@@ -331,8 +332,25 @@ export type CommandContribution = {
|
|
|
331
332
|
*/
|
|
332
333
|
keybinding?: string;
|
|
333
334
|
args?: readonly CommandArgSpec[];
|
|
335
|
+
/**
|
|
336
|
+
* Listed in the composer's `/` menu, as an application's commands are
|
|
337
|
+
* (LOOP P-19). Most commands are not: the menu is for what a person sends,
|
|
338
|
+
* the palette and `/help` for the rest.
|
|
339
|
+
*/
|
|
340
|
+
composer?: boolean;
|
|
334
341
|
run: (ctx: LoopCommandContext) => Promise<CommandResult | void>;
|
|
335
342
|
};
|
|
343
|
+
/**
|
|
344
|
+
* What goes with every run besides the message, as AG-UI's `forwardedProps`:
|
|
345
|
+
* an application's modes say there which option the person is in (LOOP
|
|
346
|
+
* P-19). Read when a message is sent, so it says what is true then; what a
|
|
347
|
+
* page says with its own message (its action, its files) is merged over it,
|
|
348
|
+
* the `loop` part key by key.
|
|
349
|
+
*/
|
|
350
|
+
export type RunPropsContribution = {
|
|
351
|
+
id: string;
|
|
352
|
+
props: () => Record<string, unknown>;
|
|
353
|
+
};
|
|
336
354
|
/** A candidate offered by an `@` namespace. */
|
|
337
355
|
export type MentionCandidate = {
|
|
338
356
|
id: string;
|
|
@@ -496,6 +514,12 @@ export type AgentBlueprintContribution = {
|
|
|
496
514
|
instructions?: string;
|
|
497
515
|
/** The model it runs on in the page, in place of its spec's. */
|
|
498
516
|
model?: string;
|
|
517
|
+
/**
|
|
518
|
+
* What the modes chosen in the composer (`forwardedProps.loop.modes`, LOOP
|
|
519
|
+
* P-19) tell a run turned in the page, and the model it runs on — as the
|
|
520
|
+
* runtime's session API applies them. Unsaid for an agent with no modes.
|
|
521
|
+
*/
|
|
522
|
+
modeEffect?: (chosen: Record<string, string>) => AppModeEffect;
|
|
499
523
|
};
|
|
500
524
|
export declare const LoopAgentBlueprint: import("@datalayer/reactor").ContributionPoint<AgentBlueprintContribution>;
|
|
501
525
|
/** One conversation opener a plugin offers on the empty chat. */
|
|
@@ -781,6 +805,13 @@ export { onSurfaceRequest, requestSurface, type SurfaceRequestListener, } from '
|
|
|
781
805
|
export { focusPrompt, onPromptFocusRequest } from './focusRequests';
|
|
782
806
|
/** Slash commands, shared in shape with the CLI. */
|
|
783
807
|
export declare const LoopCommand: import("@datalayer/reactor").ContributionPoint<CommandContribution>;
|
|
808
|
+
/** What goes with every run besides the message (`forwardedProps`). */
|
|
809
|
+
export declare const LoopRunProps: import("@datalayer/reactor").ContributionPoint<RunPropsContribution>;
|
|
810
|
+
/**
|
|
811
|
+
* The `forwardedProps` of a run: what the contributions say, then what the
|
|
812
|
+
* sender gave, the `loop` part merged key by key.
|
|
813
|
+
*/
|
|
814
|
+
export declare function runForwardedProps(contributed: readonly RunPropsContribution[], given?: Record<string, unknown>): Record<string, unknown> | undefined;
|
|
784
815
|
/** `@` namespaces. */
|
|
785
816
|
export declare const LoopMention: import("@datalayer/reactor").ContributionPoint<MentionContribution>;
|
|
786
817
|
/** Slot names the shell renders. Slots render everything; points choose one. */
|
package/lib/loop/core/index.js
CHANGED
|
@@ -189,6 +189,28 @@ export { onSurfaceRequest, requestSurface, } from './surfaceRequests';
|
|
|
189
189
|
export { focusPrompt, onPromptFocusRequest } from './focusRequests';
|
|
190
190
|
/** Slash commands, shared in shape with the CLI. */
|
|
191
191
|
export const LoopCommand = defineContributionPoint('loop.command');
|
|
192
|
+
/** What goes with every run besides the message (`forwardedProps`). */
|
|
193
|
+
export const LoopRunProps = defineContributionPoint('loop.runProps');
|
|
194
|
+
/**
|
|
195
|
+
* The `forwardedProps` of a run: what the contributions say, then what the
|
|
196
|
+
* sender gave, the `loop` part merged key by key.
|
|
197
|
+
*/
|
|
198
|
+
export function runForwardedProps(contributed, given) {
|
|
199
|
+
let merged;
|
|
200
|
+
for (const part of [...contributed.map(entry => entry.props()), given]) {
|
|
201
|
+
if (!part)
|
|
202
|
+
continue;
|
|
203
|
+
const loop = {
|
|
204
|
+
...(merged?.loop ?? {}),
|
|
205
|
+
...(part.loop ?? {}),
|
|
206
|
+
};
|
|
207
|
+
merged = { ...merged, ...part };
|
|
208
|
+
if (Object.keys(loop).length > 0) {
|
|
209
|
+
merged.loop = loop;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return merged;
|
|
213
|
+
}
|
|
192
214
|
/** `@` namespaces. */
|
|
193
215
|
export const LoopMention = defineContributionPoint('loop.mention');
|
|
194
216
|
/** Slot names the shell renders. Slots render everything; points choose one. */
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { CSSProperties, JSX, ReactNode } from 'react';
|
|
2
2
|
import type { PluginRef } from '@datalayer/reactor';
|
|
3
|
-
import type { AppAccent, AppEmbedMode, AppSpec } from '../../types/agentspecs';
|
|
4
|
-
import type { ThemeOverrides } from '../../types/chat';
|
|
3
|
+
import type { AppAccent, AppEmbedMode, AppSpec, AppThemeVariant } from '../../types/agentspecs';
|
|
4
|
+
import type { ResumedThread, ThemeOverrides } from '../../types/chat';
|
|
5
5
|
import type { AssistantCharacter } from '../../chat/assistant/characters';
|
|
6
6
|
import type { AssistantCharacterData } from '../../chat/assistant/formats/types';
|
|
7
7
|
import type { PresenceState } from '../../chat/presence/presenceStatus';
|
|
@@ -17,7 +17,10 @@ export type AppEmbedProps = {
|
|
|
17
17
|
mode?: AppEmbedMode;
|
|
18
18
|
/** The host's accent; the application's own by default. */
|
|
19
19
|
accent?: AppAccent;
|
|
20
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Light, dark, or the visitor's system: the host's, else the one the
|
|
22
|
+
* Appspec names (`interface.theme.mode`, T-30), else the system's.
|
|
23
|
+
*/
|
|
21
24
|
colorMode?: EmbedColorMode;
|
|
22
25
|
/** The host's face, a CSS font family; the theme's by default. */
|
|
23
26
|
font?: string;
|
|
@@ -56,7 +59,38 @@ export type AppEmbedProps = {
|
|
|
56
59
|
* gives under `deployment.embedded.host`.
|
|
57
60
|
*/
|
|
58
61
|
host?: AppEmbedHost;
|
|
62
|
+
/**
|
|
63
|
+
* Keep the visit's session in the host's storage, and pick it up again
|
|
64
|
+
* after the page reloads (LOOP D-13); on by default. Off, the session
|
|
65
|
+
* lasts as long as the page.
|
|
66
|
+
*/
|
|
67
|
+
resume?: boolean;
|
|
68
|
+
};
|
|
69
|
+
/** What `useEmbedSession` gives the conversation. */
|
|
70
|
+
export type EmbedSession = {
|
|
71
|
+
/** Whether the kept session was asked for: nothing is drawn before. */
|
|
72
|
+
ready: boolean;
|
|
73
|
+
/** The thread the chat goes on with; none where no session is kept. */
|
|
74
|
+
thread?: ResumedThread;
|
|
75
|
+
/** Said once, when the session kept is gone: a new one started. */
|
|
76
|
+
said?: string;
|
|
59
77
|
};
|
|
78
|
+
/**
|
|
79
|
+
* The visit's session, kept and picked up again (LOOP D-13): on the host's
|
|
80
|
+
* server with an embed token that names its visit, the session the chat
|
|
81
|
+
* opens is kept under the application and the visit — in the host's
|
|
82
|
+
* storage unless `resume` is off, in memory always — and, when the
|
|
83
|
+
* application is drawn again (the page reloaded, or a token renewed for the
|
|
84
|
+
* visit), its conversation is asked for and drawn again. One that is gone
|
|
85
|
+
* or refused is forgotten, said once, and a new one starts. Elsewhere —
|
|
86
|
+
* on a Datalayer runtime, without a token — nothing is kept.
|
|
87
|
+
*/
|
|
88
|
+
export declare function useEmbedSession({ app, serverUrl, embedToken, resume, }: {
|
|
89
|
+
app: Pick<AppSpec, 'id' | 'name'>;
|
|
90
|
+
serverUrl?: string;
|
|
91
|
+
embedToken?: string;
|
|
92
|
+
resume?: boolean;
|
|
93
|
+
}): EmbedSession;
|
|
60
94
|
/**
|
|
61
95
|
* The page and the application talking (LOOP D-10): the tools of the host
|
|
62
96
|
* the Appspec names, given to its agent through a chat-extras plugin made
|
|
@@ -81,6 +115,8 @@ export type AppFloatingProps = {
|
|
|
81
115
|
app: AppSpec;
|
|
82
116
|
view: 'floating-small' | 'panel' | 'assistant';
|
|
83
117
|
colorMode: 'light' | 'dark';
|
|
118
|
+
/** The theme it is drawn in (T-30); the page's when unsaid. */
|
|
119
|
+
themeVariant?: AppThemeVariant;
|
|
84
120
|
/** The accent and face inside the conversation. */
|
|
85
121
|
themeOverrides: ThemeOverrides;
|
|
86
122
|
/** The assistant's character, in the assistant mode. */
|
|
@@ -98,6 +134,8 @@ export type AppFloatingProps = {
|
|
|
98
134
|
renderer?: Partial<Pick<AppRendererProps, 'pluginsOff' | 'datalayerVisitors' | 'datalayerKept'>>;
|
|
99
135
|
/** What the page gives the application (D-10). */
|
|
100
136
|
host?: AppEmbedHost;
|
|
137
|
+
/** The visit's session, picked up again after a reload (D-13). */
|
|
138
|
+
session?: EmbedSession;
|
|
101
139
|
};
|
|
102
140
|
/**
|
|
103
141
|
* The bubble, the panel and the assistant: `ChatFloating`'s chrome in that
|
|
@@ -108,14 +146,15 @@ export type AppFloatingProps = {
|
|
|
108
146
|
* The workspace tells the chrome what the application is doing and what it
|
|
109
147
|
* last said (`onPresence`, `onSaying`).
|
|
110
148
|
*/
|
|
111
|
-
export declare function AppFloating({ app, view, colorMode, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, }: AppFloatingProps): JSX.Element;
|
|
112
|
-
/** The `loop`
|
|
113
|
-
export declare function EmbedThemed({ accent, colorMode, font, children, style, }: {
|
|
149
|
+
export declare function AppFloating({ app, view, colorMode, themeVariant, themeOverrides, character, serverUrl, instance, embedToken, renderer, host, session, }: AppFloatingProps): JSX.Element;
|
|
150
|
+
/** The application's theme (`loop` unless it names one), with its (or the host's) accent and face. */
|
|
151
|
+
export declare function EmbedThemed({ accent, colorMode, font, variant, children, style, }: {
|
|
114
152
|
accent: AppAccent;
|
|
115
153
|
colorMode: 'light' | 'dark';
|
|
116
154
|
font?: string;
|
|
155
|
+
variant: AppThemeVariant;
|
|
117
156
|
children: ReactNode;
|
|
118
157
|
style?: CSSProperties;
|
|
119
158
|
}): JSX.Element;
|
|
120
|
-
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, }: AppEmbedProps): JSX.Element;
|
|
159
|
+
export declare function AppEmbed({ app, mode, accent, colorMode, font, serverUrl, instance, embedToken, height, ownPortal, plugins, host, resume, }: AppEmbedProps): JSX.Element;
|
|
121
160
|
export default AppEmbed;
|