@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.
Files changed (72) hide show
  1. package/lib/chat/assistant/SpeechBalloon.js +16 -1
  2. package/lib/chat/base/ChatBase.js +40 -4
  3. package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
  4. package/lib/chat/indicators/McpStatusIndicator.js +13 -6
  5. package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
  6. package/lib/chat/indicators/SkillsStatusIndicator.js +7 -3
  7. package/lib/chat/prompt/InputPrompt.d.ts +4 -1
  8. package/lib/chat/prompt/InputPrompt.js +2 -2
  9. package/lib/chat/prompt/InputPromptBase.d.ts +4 -1
  10. package/lib/chat/prompt/InputPromptBase.js +2 -2
  11. package/lib/chat/prompt/InputPromptLexical.d.ts +7 -1
  12. package/lib/chat/prompt/InputPromptLexical.js +5 -3
  13. package/lib/components/a2ui/datalayer/Form.js +19 -2
  14. package/lib/components/teams/index.d.ts +1 -0
  15. package/lib/components/teams/index.js +1 -0
  16. package/lib/components/teams/usePeerSandbox.d.ts +13 -0
  17. package/lib/components/teams/usePeerSandbox.js +59 -0
  18. package/lib/examples/AgentA2ATeamExample.js +7 -2
  19. package/lib/loop/apps/AppComposer.d.ts +31 -0
  20. package/lib/loop/apps/AppComposer.js +67 -0
  21. package/lib/loop/apps/AppRenderer.d.ts +16 -5
  22. package/lib/loop/apps/AppRenderer.js +49 -16
  23. package/lib/loop/apps/appspec.d.ts +8 -1
  24. package/lib/loop/apps/appspec.js +88 -23
  25. package/lib/loop/apps/checks.js +125 -8
  26. package/lib/loop/apps/composer.d.ts +37 -0
  27. package/lib/loop/apps/composer.js +61 -0
  28. package/lib/loop/apps/index.d.ts +2 -0
  29. package/lib/loop/apps/index.js +2 -0
  30. package/lib/loop/core/index.d.ts +31 -0
  31. package/lib/loop/core/index.js +22 -0
  32. package/lib/loop/embed/AppEmbed.d.ts +46 -7
  33. package/lib/loop/embed/AppEmbed.js +108 -26
  34. package/lib/loop/embed/LoopEmbed.js +2 -0
  35. package/lib/loop/embed/element.js +19 -5
  36. package/lib/loop/embed/embedConfig.d.ts +10 -1
  37. package/lib/loop/embed/embedConfig.js +12 -1
  38. package/lib/loop/embed/embedSession.d.ts +79 -0
  39. package/lib/loop/embed/embedSession.js +136 -0
  40. package/lib/loop/embed/embedTheme.d.ts +12 -9
  41. package/lib/loop/embed/embedTheme.js +32 -22
  42. package/lib/loop/index.d.ts +4 -3
  43. package/lib/loop/index.js +4 -3
  44. package/lib/loop/plugins/agent-capacity/index.d.ts +3 -1
  45. package/lib/loop/plugins/agent-capacity/index.js +2 -1
  46. package/lib/loop/plugins/app-page/appPageModel.d.ts +14 -5
  47. package/lib/loop/plugins/app-page/appPageModel.js +80 -88
  48. package/lib/loop/plugins/chat/ChatView.js +31 -4
  49. package/lib/loop/plugins/chat/index.d.ts +6 -1
  50. package/lib/loop/presets.d.ts +3 -1
  51. package/lib/loop/presets.js +2 -1
  52. package/lib/loop/shell/LoopWorkspace.js +3 -0
  53. package/lib/protocols/BrowserAgentAdapter.d.ts +18 -0
  54. package/lib/protocols/BrowserAgentAdapter.js +35 -2
  55. package/lib/runtimes/__tests__/fixtures/accounting-agent-card.json +8 -0
  56. package/lib/runtimes/browser/a2aPeer.d.ts +11 -0
  57. package/lib/runtimes/browser/a2aPeer.js +15 -1
  58. package/lib/runtimes/browser/protocol.d.ts +3 -0
  59. package/lib/runtimes/browser/protocol.js +2 -1
  60. package/lib/specs/agents/agents.js +6 -3
  61. package/lib/specs/apps.js +216 -176
  62. package/lib/specs/appspecSchema.js +205 -84
  63. package/lib/specs/cogs.js +3 -2
  64. package/lib/types/agentspecs.d.ts +92 -7
  65. package/lib/types/chat.d.ts +29 -1
  66. package/package.json +1 -1
  67. package/scripts/codegen/generate_agents.py +4 -2
  68. package/scripts/codegen/generate_apps.py +0 -1
  69. package/scripts/codegen/generate_backend_tools.py +4 -12
  70. package/scripts/codegen/generate_cogs.py +3 -1
  71. package/scripts/codegen/generate_ui_plugins.py +22 -9
  72. package/scripts/voice/transcribe.mjs +29 -21
@@ -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
- records(ui.settings, 'interface.settings', (item, where) => {
618
- required(item, 'id', where);
619
- required(item, 'type', where);
620
- required(item, 'label', where);
621
- oneOf(item.type, ENUMS.settingType, `${where}.type`);
622
- texts(item.options, `${where}.options`);
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
+ }
@@ -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';
@@ -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';
@@ -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. */
@@ -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
- /** Light, dark, or the visitor's system. */
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` theme, with the application's (or the host's) accent and face. */
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;