@datalayer/agent-runtimes 1.3.30 → 1.3.32

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 (79) hide show
  1. package/lib/chat/ChatFloating.js +40 -8
  2. package/lib/chat/assistant/AssistantStage.d.ts +26 -7
  3. package/lib/chat/assistant/AssistantStage.js +115 -8
  4. package/lib/chat/assistant/SpeechBalloon.js +7 -3
  5. package/lib/chat/assistant/characters.d.ts +32 -1
  6. package/lib/chat/assistant/characters.js +180 -15
  7. package/lib/chat/assistant/state.d.ts +28 -0
  8. package/lib/chat/assistant/state.js +31 -0
  9. package/lib/chat/base/ChatBase.js +34 -19
  10. package/lib/chat/base/modelChoice.d.ts +27 -1
  11. package/lib/chat/base/modelChoice.js +29 -15
  12. package/lib/chat/prompt/InputPrompt.d.ts +7 -1
  13. package/lib/chat/prompt/InputPrompt.js +2 -2
  14. package/lib/chat/prompt/InputPromptBase.js +1 -1
  15. package/lib/chat/prompt/menus/ModelSelector.d.ts +13 -1
  16. package/lib/chat/prompt/menus/ModelSelector.js +17 -5
  17. package/lib/chat/useViewportDrag.d.ts +9 -1
  18. package/lib/chat/useViewportDrag.js +4 -4
  19. package/lib/components/a2ui/index.d.ts +2 -0
  20. package/lib/components/a2ui/index.js +2 -0
  21. package/lib/components/a2ui/visibility.d.ts +15 -0
  22. package/lib/components/a2ui/visibility.js +24 -0
  23. package/lib/components/a2ui/visibleWhen.d.ts +17 -0
  24. package/lib/components/a2ui/visibleWhen.js +54 -0
  25. package/lib/examples/ChatAssistantExample.d.ts +4 -0
  26. package/lib/examples/ChatAssistantExample.js +26 -5
  27. package/lib/examples/utils/owlCharacterPlugin.d.ts +7 -0
  28. package/lib/examples/utils/owlCharacterPlugin.js +44 -0
  29. package/lib/loop/apps/AppFeedback.d.ts +27 -0
  30. package/lib/loop/apps/AppFeedback.js +78 -0
  31. package/lib/loop/apps/AppRenderer.js +17 -2
  32. package/lib/loop/apps/appspec.js +6 -3
  33. package/lib/loop/apps/checks.d.ts +10 -2
  34. package/lib/loop/apps/checks.js +21 -10
  35. package/lib/loop/apps/feedback.d.ts +51 -0
  36. package/lib/loop/apps/feedback.js +61 -0
  37. package/lib/loop/apps/index.d.ts +2 -0
  38. package/lib/loop/apps/index.js +2 -0
  39. package/lib/loop/apps/rules.d.ts +14 -0
  40. package/lib/loop/apps/rules.js +24 -0
  41. package/lib/loop/core/canvasBlocks.d.ts +16 -0
  42. package/lib/loop/core/canvasBlocks.js +13 -0
  43. package/lib/loop/core/index.d.ts +7 -0
  44. package/lib/loop/core/index.js +3 -0
  45. package/lib/loop/index.d.ts +1 -1
  46. package/lib/loop/plugins/a2ui/A2uiView.js +2 -3
  47. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +4 -2
  48. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +3 -4
  49. package/lib/loop/plugins/app-page/AppPage.js +1 -1
  50. package/lib/loop/plugins/app-page/{appPage.d.ts → appPageModel.d.ts} +1 -1
  51. package/lib/loop/plugins/app-page/index.d.ts +1 -1
  52. package/lib/loop/plugins/app-page/index.js +2 -2
  53. package/lib/loop/plugins/canvas-blocks/index.d.ts +63 -0
  54. package/lib/loop/plugins/canvas-blocks/index.js +118 -0
  55. package/lib/loop/plugins/chat/ChatView.js +7 -3
  56. package/lib/loop/plugins/chat/turnState.d.ts +5 -2
  57. package/lib/loop/plugins/chat/turnState.js +2 -1
  58. package/lib/specs/actions.js +1 -0
  59. package/lib/specs/agents/agents.js +16 -4
  60. package/lib/specs/apps.js +21 -10
  61. package/lib/specs/appspecSchema.js +13 -7
  62. package/lib/specs/models.d.ts +1 -1
  63. package/lib/specs/models.js +8 -8
  64. package/lib/specs/tools.d.ts +1 -0
  65. package/lib/specs/tools.js +19 -0
  66. package/lib/stories/loop/LoopReference.stories.d.ts +21 -0
  67. package/lib/stories/loop/LoopReference.stories.js +31 -0
  68. package/lib/stories/loop/ReferenceScreens.d.ts +53 -0
  69. package/lib/stories/loop/ReferenceScreens.js +256 -0
  70. package/lib/stories/loop/pictures-main.d.ts +1 -0
  71. package/lib/stories/loop/pictures-main.js +64 -0
  72. package/lib/types/agentspecs.d.ts +6 -1
  73. package/lib/types/chat.d.ts +5 -0
  74. package/lib/types/config.d.ts +8 -0
  75. package/package.json +9 -6
  76. package/scripts/check-cloudflare-models.py +2 -2
  77. package/scripts/codegen/generate_apps.py +2 -1
  78. package/scripts/codegen/generate_models.py +4 -4
  79. /package/lib/loop/plugins/app-page/{appPage.js → appPageModel.js} +0 -0
@@ -142,6 +142,9 @@ export const LoopChatHeader = defineContributionPoint('loop.chat.header');
142
142
  export const LoopAgentBlueprint = defineContributionPoint('loop.agent.blueprint');
143
143
  export const LoopChatSuggestion = defineContributionPoint('loop.chat.suggestion');
144
144
  export const LoopAssistantCharacter = defineContributionPoint('loop.assistant.character');
145
+ // A block the Canvas may place (LOOP C-12), in a module of its own so that
146
+ // the palette is read without the rest of the contracts.
147
+ export { LoopCanvasBlock } from './canvasBlocks';
145
148
  export const LoopChatExtras = defineContributionPoint('loop.chat.extras');
146
149
  export const LoopOpeningNotebook = defineContributionPoint('loop.notebook.opening');
147
150
  export const LoopOpeningDocument = defineContributionPoint('loop.document.opening');
@@ -70,4 +70,4 @@ export { EMBED_ATTRIBUTES, EMBED_HOST_VARIABLES, EMBED_TAG, embedLookOf, embedSn
70
70
  export { SubagentActivityPlugin, SubagentPulse, SUBAGENT_ACTIVITY_PLUGIN_NAME, } from './plugins/subagent-activity';
71
71
  export { loopPlugins, type LoopPresetOptions } from './presets';
72
72
  export { DocumentExtension, NotebookExtension } from './extensions';
73
- export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, type BehaviourOptions, type ToolArguments, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, type AppCheck, defineAppPlugin, type AppRendererProps, type AppInstance, appLayoutOptions, readAppspecYaml, writeAppspecYaml, type ParsedAppspec, } from './apps';
73
+ export { BEHAVIOURS as APP_BEHAVIOUR_ORDER, DEFAULT_BEHAVIOURS as APP_DEFAULT_BEHAVIOURS, behaviourFor, classesOf, isReadOnly, toolBehaviours, toolEscalations, type BehaviourOptions, type ToolArguments, APP_SCHEMA, APPSPEC_KEY_ORDER, DEFAULT_LAYOUTS as APP_DEFAULT_LAYOUTS, dumpAppspec, emptyAppspec, parseAppspec, AppRenderer, checkApp, checkAppspec, type AppCheck, type CheckContext, defineAppPlugin, type AppRendererProps, type AppInstance, appLayoutOptions, readAppspecYaml, writeAppspecYaml, type ParsedAppspec, } from './apps';
@@ -1,10 +1,9 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box, Text } from '@primer/react';
4
- import { basicCatalog, } from '@a2ui/react/v0_9';
5
4
  import { MessageProcessor, } from '@a2ui/web_core/v0_9';
6
5
  import { useSignalValue } from '@datalayer/reactor/react';
7
- import { A2uiSurfaceComposed } from '../../../components/a2ui';
6
+ import { A2uiSurfaceComposed, datalayerCatalog, } from '../../../components/a2ui';
8
7
  import { useSandboxService } from '../agents';
9
8
  /**
10
9
  * A2UI's basic catalog draws from inherited `--a2ui-*` custom properties, so a
@@ -27,7 +26,7 @@ export default function A2uiView({ workspace }) {
27
26
  // rather than whatever the sandbox happened to execute last.
28
27
  const sourceRef = useRef('');
29
28
  const renderRef = useRef(null);
30
- const processor = useMemo(() => new MessageProcessor([basicCatalog], action => {
29
+ const processor = useMemo(() => new MessageProcessor([datalayerCatalog], action => {
31
30
  // The round-trip: what the reader did goes back to the code that drew
32
31
  // the surface, and the surface it returns replaces this one. Without
33
32
  // this a surface is a screenshot with buttons on it.
@@ -11,12 +11,14 @@
11
11
  * The same renderer draws an application's page (`loop/plugins/app-page`):
12
12
  * there the surface is fed live `data` by path as the conversation moves, and
13
13
  * an action is handed the surface it came from, so that what a block wrote
14
- * into its data model can be read when a button is pressed.
14
+ * into its data model can be read when a button is pressed. It draws from
15
+ * Datalayer's catalog: the basic one, any block shown only while its
16
+ * `visible_when` holds.
15
17
  *
16
18
  * @module loop/plugins/a2ui-surface/InlineSurface
17
19
  */
18
20
  import type { JSX } from 'react';
19
- import { type ReactComponentImplementation } from '@a2ui/react/v0_9';
21
+ import type { ReactComponentImplementation } from '@a2ui/react/v0_9';
20
22
  import { type A2uiClientAction, type A2uiMessage, type SurfaceModel } from '@a2ui/web_core/v0_9';
21
23
  /** A surface as the renderer holds it: its data model is `dataModel`. */
22
24
  export type InlineSurfaceModel = SurfaceModel<ReactComponentImplementation>;
@@ -1,11 +1,10 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useEffect, useMemo, useRef, useState } from 'react';
3
3
  import { Box } from '@datalayer/primer-addons';
4
- import { basicCatalog, } from '@a2ui/react/v0_9';
5
4
  import { MessageProcessor, } from '@a2ui/web_core/v0_9';
6
- import { A2UI_RENDER_SCOPE_SX, A2uiMarkdownProvider, A2uiSurfaceComposed, } from '../../../components/a2ui';
5
+ import { A2UI_RENDER_SCOPE_SX, A2uiMarkdownProvider, A2uiSurfaceComposed, datalayerCatalog, } from '../../../components/a2ui';
7
6
  /** The catalogue a surface is rewritten to before it is drawn. */
8
- export const SURFACE_CATALOG_ID = basicCatalog.id;
7
+ export const SURFACE_CATALOG_ID = datalayerCatalog.id;
9
8
  const INHERIT_THEME = {
10
9
  ['--a2ui-color-surface']: 'var(--bgColor-muted)',
11
10
  ['--a2ui-color-on-surface']: 'var(--fgColor-default)',
@@ -17,7 +16,7 @@ export function InlineSurface({ messages, onAction, validationError, data, }) {
17
16
  // was built with must not go stale when the host's does not.
18
17
  const onActionRef = useRef(onAction);
19
18
  onActionRef.current = onAction;
20
- const processor = useMemo(() => new MessageProcessor([basicCatalog], action => onActionRef.current(action, processorRef.current?.model.getSurface(action.surfaceId))), []);
19
+ const processor = useMemo(() => new MessageProcessor([datalayerCatalog], action => onActionRef.current(action, processorRef.current?.model.getSurface(action.surfaceId))), []);
21
20
  const processorRef = useRef(undefined);
22
21
  processorRef.current = processor;
23
22
  const [surfaces, setSurfaces] = useState([]);
@@ -5,7 +5,7 @@ import { signal } from '@datalayer/reactor';
5
5
  import { useContributions, useSignalValue } from '@datalayer/reactor/react';
6
6
  import { LoopChatTurn, } from '../../core';
7
7
  import { InlineSurface, SURFACE_CATALOG_ID, } from '../a2ui-surface/InlineSurface';
8
- import { appPageAction, appPageData, appPageMessages } from './appPage';
8
+ import { appPageAction, appPageData, appPageMessages } from './appPageModel';
9
9
  /** No chat in the workspace: the page reads a turn that never starts. */
10
10
  const NO_TURN = signal({ id: 0, status: 'idle' });
11
11
  export function AppPage({ app, workspace }) {
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * Pure: the plugin (`./index`) and its view (`./AppPage`) do the drawing.
20
20
  *
21
- * @module loop/plugins/app-page/appPage
21
+ * @module loop/plugins/app-page/appPageModel
22
22
  */
23
23
  import type { AppSettingSpec, AppSpec } from '../../../types/agentspecs';
24
24
  import type { ChatTurnSnapshot } from '../../core';
@@ -4,5 +4,5 @@ export declare const APP_PAGE_PLUGIN_NAME = "@datalayer/loop-plugin-app-page";
4
4
  /** The plugin that draws an application's page beside its conversation. */
5
5
  export declare function defineAppPagePlugin(app: AppSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
6
6
  export { AppPage, type AppPageProps } from './AppPage';
7
- export * from './appPage';
7
+ export * from './appPageModel';
8
8
  export default defineAppPagePlugin;
@@ -3,7 +3,7 @@ import { BrowserIcon } from '@primer/octicons-react';
3
3
  import { contribution, definePlugin, } from '@datalayer/reactor';
4
4
  import { LoopEditorView } from '../../core';
5
5
  import { AppPage } from './AppPage';
6
- import { APP_PAGE_SURFACE } from './appPage';
6
+ import { APP_PAGE_SURFACE } from './appPageModel';
7
7
  export const APP_PAGE_PLUGIN_NAME = '@datalayer/loop-plugin-app-page';
8
8
  /** The plugin that draws an application's page beside its conversation. */
9
9
  export function defineAppPagePlugin(app) {
@@ -30,5 +30,5 @@ export function defineAppPagePlugin(app) {
30
30
  return plugin;
31
31
  }
32
32
  export { AppPage } from './AppPage';
33
- export * from './appPage';
33
+ export * from './appPageModel';
34
34
  export default defineAppPagePlugin;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * The Canvas's palette as a contribution point (LOOP C-12): each UI plugin of
3
+ * the catalogue that renders components (`agentspecs/ui-plugins`, C-13) is a
4
+ * Reactor plugin contributing its components to `loop.canvas.block`. The
5
+ * palette lists what the enabled plugins contribute, and nothing else; a
6
+ * plugin disabled takes its blocks off it, and an application that uses one
7
+ * of them says so in its setup notes (`blockSetupNotes`).
8
+ *
9
+ * A new kind of block arrives as a plugin contributing to the point, never as
10
+ * a change to the Canvas.
11
+ *
12
+ * @module loop/plugins/canvas-blocks
13
+ */
14
+ import { type ReactorPlatform, type ReactorPlugin } from '@datalayer/reactor';
15
+ import type { AppSpec, ComponentSpec, UIPluginSpec } from '../../../types/agentspecs';
16
+ import { LoopCanvasBlock, type CanvasBlockContribution } from '../../core/canvasBlocks';
17
+ /** What is read of a reactor here: its contributions to the point, and who is enabled. */
18
+ type BlocksReader = {
19
+ getContributions: (point: typeof LoopCanvasBlock) => ReadonlyArray<{
20
+ value: CanvasBlockContribution;
21
+ }>;
22
+ };
23
+ /** The name of the Reactor plugin that contributes a UI plugin's blocks. */
24
+ export declare const canvasBlocksPluginName: (uiPlugin: string) => string;
25
+ /** A UI plugin of the catalogue as the Reactor plugin contributing its components as blocks. */
26
+ export declare function uiPluginBlocks(uiPlugin: UIPluginSpec): ReactorPlugin<Record<string, never>, unknown, unknown>;
27
+ /**
28
+ * The plugins of the catalogue's UI plugins that render components and may
29
+ * be named today (`enabled` in the catalogue).
30
+ */
31
+ export declare const CANVAS_BLOCK_PLUGINS: ReactorPlugin<Record<string, never>, unknown, unknown>[];
32
+ /**
33
+ * A reactor holding the block plugins given, started, with those named in
34
+ * `disabled` switched off. A name no plugin has is an error.
35
+ */
36
+ export declare function canvasBlocksReactor(disabled?: readonly string[], plugins?: ReactorPlugin<any, any, any>[]): ReactorPlatform;
37
+ /**
38
+ * The palette of an organization: the block plugins of the UI plugins it has
39
+ * turned off (`plugins_off` in IAM, catalogue ids such as 'a2ui') switched
40
+ * off. A UI plugin of the catalogue that contributes no block takes nothing
41
+ * off; an id the catalogue does not have is said by `unknownPluginsOff`.
42
+ */
43
+ export declare function canvasBlocksReactorOf(pluginsOff: readonly string[]): ReactorPlatform;
44
+ /** The ids an organization turns off that no UI plugin of the catalogue has. */
45
+ export declare function unknownPluginsOff(pluginsOff: readonly string[]): string[];
46
+ /**
47
+ * What an application's page uses from a UI plugin its organization has
48
+ * turned off, as its setup notes say it (`blockSetupNotes`, on the palette of
49
+ * `canvasBlocksReactorOf`).
50
+ */
51
+ export declare function pluginsOffSetupNotes(app: Pick<AppSpec, 'interface'>, pluginsOff: readonly string[]): string[];
52
+ /** The blocks the enabled plugins contribute, in contribution order: the palette. */
53
+ export declare function canvasBlocksOf(reactor: BlocksReader): ComponentSpec[];
54
+ /** The components an application's page names: those it may use, and those its surface places. */
55
+ export declare function componentsUsedBy(app: Pick<AppSpec, 'interface'>): string[];
56
+ /**
57
+ * What an application's page uses that no enabled plugin contributes, as its
58
+ * setup notes say it: one sentence per UI plugin of the catalogue whose
59
+ * blocks it uses while its plugin is off. A component the catalogue does not
60
+ * have is not a note but a problem of the instant checks.
61
+ */
62
+ export declare function blockSetupNotes(app: Pick<AppSpec, 'interface'>, reactor: BlocksReader): string[];
63
+ export {};
@@ -0,0 +1,118 @@
1
+ /*
2
+ * Copyright (c) 2025-2026 Datalayer, Inc.
3
+ * Distributed under the terms of the Modified BSD License.
4
+ */
5
+ /**
6
+ * The Canvas's palette as a contribution point (LOOP C-12): each UI plugin of
7
+ * the catalogue that renders components (`agentspecs/ui-plugins`, C-13) is a
8
+ * Reactor plugin contributing its components to `loop.canvas.block`. The
9
+ * palette lists what the enabled plugins contribute, and nothing else; a
10
+ * plugin disabled takes its blocks off it, and an application that uses one
11
+ * of them says so in its setup notes (`blockSetupNotes`).
12
+ *
13
+ * A new kind of block arrives as a plugin contributing to the point, never as
14
+ * a change to the Canvas.
15
+ *
16
+ * @module loop/plugins/canvas-blocks
17
+ */
18
+ import { buildReactorFromPlugins, contribution, definePlugin, } from '@datalayer/reactor';
19
+ import { listUIPlugins } from '../../../specs/uiPlugins';
20
+ import { LoopCanvasBlock, } from '../../core/canvasBlocks';
21
+ /** The name of the Reactor plugin that contributes a UI plugin's blocks. */
22
+ export const canvasBlocksPluginName = (uiPlugin) => `@datalayer/loop-plugin-ui-${uiPlugin}`;
23
+ /** A UI plugin of the catalogue as the Reactor plugin contributing its components as blocks. */
24
+ export function uiPluginBlocks(uiPlugin) {
25
+ return definePlugin({
26
+ name: canvasBlocksPluginName(uiPlugin.id),
27
+ displayName: uiPlugin.name,
28
+ description: `The blocks of ${uiPlugin.name} on the Canvas: ${uiPlugin.components.map(component => component.name).join(', ')}.`,
29
+ octicon: 'apps',
30
+ contributes: uiPlugin.components.map(component => contribution(LoopCanvasBlock, { id: component.id, uiPlugin: uiPlugin.id, component }, { id: component.id })),
31
+ });
32
+ }
33
+ /**
34
+ * The plugins of the catalogue's UI plugins that render components and may
35
+ * be named today (`enabled` in the catalogue).
36
+ */
37
+ export const CANVAS_BLOCK_PLUGINS = listUIPlugins()
38
+ .filter(uiPlugin => uiPlugin.enabled && uiPlugin.components.length > 0)
39
+ .map(uiPluginBlocks);
40
+ /**
41
+ * A reactor holding the block plugins given, started, with those named in
42
+ * `disabled` switched off. A name no plugin has is an error.
43
+ */
44
+ export function canvasBlocksReactor(disabled = [], plugins = CANVAS_BLOCK_PLUGINS) {
45
+ const reactor = buildReactorFromPlugins(plugins);
46
+ reactor.start();
47
+ for (const name of disabled) {
48
+ if (!reactor.hasPlugin(name)) {
49
+ throw new Error(`No plugin of the Canvas is named "${name}"; they are ${plugins.map(plugin => plugin.name).join(', ') || 'none'}.`);
50
+ }
51
+ reactor.disable(name);
52
+ }
53
+ return reactor;
54
+ }
55
+ /**
56
+ * The palette of an organization: the block plugins of the UI plugins it has
57
+ * turned off (`plugins_off` in IAM, catalogue ids such as 'a2ui') switched
58
+ * off. A UI plugin of the catalogue that contributes no block takes nothing
59
+ * off; an id the catalogue does not have is said by `unknownPluginsOff`.
60
+ */
61
+ export function canvasBlocksReactorOf(pluginsOff) {
62
+ const blockPlugins = new Set(CANVAS_BLOCK_PLUGINS.map(plugin => plugin.name));
63
+ return canvasBlocksReactor(pluginsOff
64
+ .map(canvasBlocksPluginName)
65
+ .filter(name => blockPlugins.has(name)));
66
+ }
67
+ /** The ids an organization turns off that no UI plugin of the catalogue has. */
68
+ export function unknownPluginsOff(pluginsOff) {
69
+ const known = new Set(listUIPlugins().map(uiPlugin => uiPlugin.id));
70
+ return pluginsOff.filter(id => !known.has(id));
71
+ }
72
+ /**
73
+ * What an application's page uses from a UI plugin its organization has
74
+ * turned off, as its setup notes say it (`blockSetupNotes`, on the palette of
75
+ * `canvasBlocksReactorOf`).
76
+ */
77
+ export function pluginsOffSetupNotes(app, pluginsOff) {
78
+ return pluginsOff.length > 0
79
+ ? blockSetupNotes(app, canvasBlocksReactorOf(pluginsOff))
80
+ : [];
81
+ }
82
+ /** The blocks the enabled plugins contribute, in contribution order: the palette. */
83
+ export function canvasBlocksOf(reactor) {
84
+ return reactor
85
+ .getContributions(LoopCanvasBlock)
86
+ .map(entry => entry.value.component);
87
+ }
88
+ /** The components an application's page names: those it may use, and those its surface places. */
89
+ export function componentsUsedBy(app) {
90
+ return [
91
+ ...new Set([
92
+ ...(app.interface.components ?? []),
93
+ ...(app.interface.surface?.components ?? []).map(node => String(node.component)),
94
+ ]),
95
+ ];
96
+ }
97
+ /**
98
+ * What an application's page uses that no enabled plugin contributes, as its
99
+ * setup notes say it: one sentence per UI plugin of the catalogue whose
100
+ * blocks it uses while its plugin is off. A component the catalogue does not
101
+ * have is not a note but a problem of the instant checks.
102
+ */
103
+ export function blockSetupNotes(app, reactor) {
104
+ const contributed = new Set(reactor.getContributions(LoopCanvasBlock).map(entry => entry.value.id));
105
+ const off = new Map();
106
+ for (const name of componentsUsedBy(app)) {
107
+ if (contributed.has(name)) {
108
+ continue;
109
+ }
110
+ for (const uiPlugin of listUIPlugins()) {
111
+ const component = uiPlugin.components.find(each => each.id === name);
112
+ if (component) {
113
+ off.set(uiPlugin, [...(off.get(uiPlugin) ?? []), component.name]);
114
+ }
115
+ }
116
+ }
117
+ return [...off.entries()].map(([uiPlugin, names]) => `The UI plugin “${uiPlugin.name}” is not enabled, and its page uses its ${names.length === 1 ? 'block' : 'blocks'} ${names.join(', ')}: ${names.length === 1 ? 'it is' : 'they are'} off the Canvas until it is.`);
118
+ }
@@ -8,7 +8,7 @@ import { computed, signal } from '@datalayer/reactor';
8
8
  const NO_STANCE = signal(undefined);
9
9
  import { useContributions, useSignalValue, useGate, useReactorPlatform, ReactorLazy, ReactorSlot, useSlotComponents, } from '@datalayer/reactor/react';
10
10
  import { ChatBase } from '../../../chat/base/ChatBase';
11
- import { offeredModels as offeredModelsFor, readServerCatalogue, } from '../../../chat/base/modelChoice';
11
+ import { offeredModels as offeredModelsFor, readServerCatalogue, readServerDecisions, } from '../../../chat/base/modelChoice';
12
12
  import { SUGGESTION_CHIP_WIDTH } from '../../../chat/display/EmptyState';
13
13
  import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
14
14
  import { useAnonymousSessionStore } from '../../../runtimes/browser/anonymousToken';
@@ -393,7 +393,7 @@ export default function ChatView({ workspace }) {
393
393
  }
394
394
  setTransient(null);
395
395
  // A new turn: whatever the panel showed is gone, this message is it.
396
- turnFeedRef.current?.begin(message);
396
+ turnFeedRef.current?.begin(message, controlsRef.current?.thread());
397
397
  const outcome = await workspace.submit(message);
398
398
  if (!outcome.handled) {
399
399
  /*
@@ -1145,6 +1145,8 @@ export default function ChatView({ workspace }) {
1145
1145
  const skillActions = useSkillActions(agentId);
1146
1146
  // `undefined` until the runtime answers: its answer is then the list.
1147
1147
  const [catalogModels, setCatalogModels] = useState(undefined);
1148
+ // The typed-decision models the runtime lists apart: shown, never picked.
1149
+ const [decisions, setDecisions] = useState(undefined);
1148
1150
  useEffect(() => {
1149
1151
  /*
1150
1152
  * Only where there is a server to ask.
@@ -1171,6 +1173,7 @@ export default function ChatView({ workspace }) {
1171
1173
  // A runtime that did not answer offers nothing: the agentspecs
1172
1174
  // catalogue is not its answer.
1173
1175
  setCatalogModels(payload ? readServerCatalogue(payload) : []);
1176
+ setDecisions(payload ? readServerDecisions(payload) : undefined);
1174
1177
  }
1175
1178
  })
1176
1179
  // No catalogue is not an error worth a banner: the menu simply has
@@ -1193,7 +1196,7 @@ export default function ChatView({ workspace }) {
1193
1196
  * agentspecs catalogue stands in, filtered to the models marked available.
1194
1197
  */
1195
1198
  const offeredModels = useMemo(() => offeredModelsFor(catalogModels, () => Object.values(AI_MODEL_CATALOGUE)
1196
- // A typed-judgment model answers typed questions, not a chat.
1199
+ // A typed-decision model answers typed questions, not a chat.
1197
1200
  .filter(model => model.available && isChatModel(model))
1198
1201
  .map(model => ({
1199
1202
  id: model.id,
@@ -1335,6 +1338,7 @@ export default function ChatView({ workspace }) {
1335
1338
  agentUsage: contextUsage ?? undefined,
1336
1339
  showModelSelector: true,
1337
1340
  models: offeredModels,
1341
+ decisions,
1338
1342
  selectedModel: activeModel,
1339
1343
  onModelSelect: model => void selectModel(model),
1340
1344
  showToolsMenu: true,
@@ -15,8 +15,11 @@ import type { ChatTurnSnapshot, ChatTurnStatus } from '../../core';
15
15
  export type TurnFeed = {
16
16
  /** The turn, for readers. */
17
17
  turn: ReadonlySignal<ChatTurnSnapshot>;
18
- /** A new turn: the previous one is gone, this one holds the message. */
19
- begin: (user: string) => void;
18
+ /**
19
+ * A new turn: the previous one is gone, this one holds the message, and
20
+ * the conversation it went to, when the chat knows it.
21
+ */
22
+ begin: (user: string, thread?: string) => void;
20
23
  /** The reply so far. Moves the turn to `streaming` on its first text. */
21
24
  assistant: (text: string) => void;
22
25
  /** The agent stopped, one way or another. */
@@ -19,13 +19,14 @@ export function createTurnFeed() {
19
19
  const turn = signal(IDLE);
20
20
  return {
21
21
  turn,
22
- begin: user => {
22
+ begin: (user, thread) => {
23
23
  // The window's fill carries over: it is the conversation's, not the
24
24
  // turn's, and the footer under a fresh turn should not read empty
25
25
  // until the agent reports again.
26
26
  turn.value = {
27
27
  id: turn.value.id + 1,
28
28
  user,
29
+ ...(thread ? { thread } : {}),
29
30
  status: 'thinking',
30
31
  usage: turn.value.usage,
31
32
  };
@@ -15,6 +15,7 @@ export const ACTION_CLASSES = [
15
15
  export const TOOL_ACTIONS = {
16
16
  'create-plan': ['read'],
17
17
  'current-time': ['read'],
18
+ decide: ['read'],
18
19
  'display-recipe': ['read'],
19
20
  'example-create-plan': ['read'],
20
21
  'example-current-time': ['read'],
@@ -4385,7 +4385,7 @@ export const EXAMPLE_SIMPLE_AGENTSPEC_0_0_1 = {
4385
4385
  id: 'example-simple',
4386
4386
  version: '0.0.1',
4387
4387
  name: 'A Simple Agent',
4388
- description: `A simple conversational agent. No tools, no MCP servers, no skills — just a helpful AI assistant you can chat with.`,
4388
+ description: `A simple conversational agent with one tool, decide: it asks Jev typed questions (yes or no with a probability, a choice, a score) through datalayer-ai-inference. No MCP servers, no skills beyond events.`,
4389
4389
  tags: ['workflow', 'automation', 'analysis'],
4390
4390
  domain: undefined,
4391
4391
  enabled: true,
@@ -4397,7 +4397,7 @@ export const EXAMPLE_SIMPLE_AGENTSPEC_0_0_1 = {
4397
4397
  ? toAgentSkillSpec(SKILL_MAP['events:0.0.1'])
4398
4398
  : undefined,
4399
4399
  ].filter(Boolean),
4400
- tools: [TOOL_MAP['runtime-echo:0.0.1']],
4400
+ tools: [TOOL_MAP['runtime-echo:0.0.1'], TOOL_MAP['decide:0.0.1']],
4401
4401
  frontendTools: [
4402
4402
  FRONTEND_TOOL_MAP['jupyter-notebook:0.0.1'],
4403
4403
  FRONTEND_TOOL_MAP['lexical-document:0.0.1'],
@@ -4420,13 +4420,25 @@ export const EXAMPLE_SIMPLE_AGENTSPEC_0_0_1 = {
4420
4420
  text: 'Summarize the key points of a topic I describe',
4421
4421
  summary: 'Summarize a topic',
4422
4422
  },
4423
+ {
4424
+ text: "Is this support ticket urgent? 'Payouts have failed for 3 days.'",
4425
+ summary: 'Decide: is it urgent?',
4426
+ },
4427
+ {
4428
+ text: "Which team should handle: 'I was charged twice'? Billing, Tech or Sales.",
4429
+ summary: 'Decide: which team?',
4430
+ },
4431
+ {
4432
+ text: "Score how positive this review is from 1 to 5: 'Setup took an hour, but support answered fast and it works.'",
4433
+ summary: 'Decide: score a review',
4434
+ },
4423
4435
  ],
4424
- welcomeMessage: "Hi! I'm a simple assistant. I don't have any special tools, but I'm happy to chat, answer questions, and help you think through ideas.\n",
4436
+ welcomeMessage: "Hi! I'm a simple assistant. I'm happy to chat, answer questions and help you think through ideas, and I can decide: ask me whether a ticket is urgent, which team should take it, or how positive a review is.\n",
4425
4437
  welcomeNotebook: undefined,
4426
4438
  welcomeDocument: undefined,
4427
4439
  sandboxVariant: 'jupyter-server',
4428
4440
  harness: 'pydantic-ai',
4429
- systemPrompt: `You are a helpful, friendly AI assistant. You do not have access to any external tools, MCP servers, or skills. Answer questions using your training knowledge, be concise, and let the user know if a question is outside your knowledge.
4441
+ systemPrompt: `You are a helpful, friendly AI assistant. You have no MCP servers. You have a decide tool: when asked whether something holds (yes or no), which of named options fits, or where something sits on a scale, call decide with the text as the state and one typed question (noul, choice or score), then answer with what it decided and its probability or confidence. For anything else, answer from your training knowledge, be concise, and let the user know if a question is outside your knowledge.
4430
4442
  `,
4431
4443
  systemPromptCodemodeAddons: undefined,
4432
4444
  goal: undefined,
package/lib/specs/apps.js CHANGED
@@ -94,6 +94,7 @@ export const CUSTOMER_INTERVIEW_APP_0_0_1 = {
94
94
  record: {
95
95
  keepFor: '1_years',
96
96
  include: ['conversations', 'outputs', 'feedback'],
97
+ suggestTests: false,
97
98
  retentionDays: 365,
98
99
  },
99
100
  checks: {
@@ -172,6 +173,7 @@ export const DATA_QUALITY_APP_0_0_1 = {
172
173
  record: {
173
174
  keepFor: '1_years',
174
175
  include: ['decisions', 'sources', 'checks'],
176
+ suggestTests: false,
175
177
  retentionDays: 365,
176
178
  },
177
179
  checks: {
@@ -238,7 +240,7 @@ export const DATA_QUALITY_APP_0_0_1 = {
238
240
  ],
239
241
  minConfidence: 0.0,
240
242
  scenarios: [],
241
- judgmentModel: 'cloudflare:gtw/typesafe/jev',
243
+ decisionModel: 'cloudflare:gtw/typesafe/jev',
242
244
  },
243
245
  enabled: true,
244
246
  tags: ['example', 'decision', 'data-quality'],
@@ -362,6 +364,7 @@ export const INBOX_TRIAGE_APP_0_0_1 = {
362
364
  record: {
363
365
  keepFor: '1_years',
364
366
  include: ['conversations', 'actions', 'decisions', 'approvals', 'checks'],
367
+ suggestTests: false,
365
368
  retentionDays: 365,
366
369
  },
367
370
  checks: {
@@ -413,7 +416,7 @@ export const MODEL_CHOICE_APP_0_0_1 = {
413
416
  version: '0.0.1',
414
417
  name: 'Model Choice',
415
418
  kind: 'decision',
416
- description: 'Which chat model should this use case run on? For a team choosing a model for one job — a summarizer, a classifier, an agent — from what a benchmark run measured and what a judge reads in the answers.',
419
+ description: 'Which chat model should this use case run on? For a team choosing a model for one job — a summarizer, a classifier, an agent — from what a benchmark run measured and what a decision model reads in the answers.',
417
420
  owner: 'Datalayer <info@datalayer.io>',
418
421
  agent: 'jupyter-data-analyst:0.0.1',
419
422
  team: '',
@@ -463,6 +466,7 @@ export const MODEL_CHOICE_APP_0_0_1 = {
463
466
  record: {
464
467
  keepFor: '1_years',
465
468
  include: ['decisions', 'sources', 'checks'],
469
+ suggestTests: false,
466
470
  retentionDays: 365,
467
471
  },
468
472
  checks: {
@@ -582,7 +586,7 @@ export const MODEL_CHOICE_APP_0_0_1 = {
582
586
  },
583
587
  },
584
588
  ],
585
- judgmentModel: 'cloudflare:gtw/typesafe/jev',
589
+ decisionModel: 'cloudflare:gtw/typesafe/jev',
586
590
  },
587
591
  enabled: true,
588
592
  tags: ['example', 'decision', 'benchmarks', 'models'],
@@ -800,6 +804,7 @@ export const PIPELINE_REPORT_APP_0_0_1 = {
800
804
  'sources',
801
805
  'outputs',
802
806
  ],
807
+ suggestTests: false,
803
808
  retentionDays: 2555,
804
809
  },
805
810
  checks: {
@@ -1060,6 +1065,7 @@ export const QUOTE_CALCULATOR_APP_0_0_1 = {
1060
1065
  record: {
1061
1066
  keepFor: '90_days',
1062
1067
  include: ['actions', 'outputs'],
1068
+ suggestTests: false,
1063
1069
  retentionDays: 90,
1064
1070
  },
1065
1071
  checks: {
@@ -1273,6 +1279,7 @@ export const REPORT_FROM_A_FILE_APP_0_0_1 = {
1273
1279
  record: {
1274
1280
  keepFor: '90_days',
1275
1281
  include: ['actions', 'outputs'],
1282
+ suggestTests: false,
1276
1283
  retentionDays: 90,
1277
1284
  },
1278
1285
  checks: {
@@ -1355,6 +1362,7 @@ export const SHIP_OR_FIX_APP_0_0_1 = {
1355
1362
  record: {
1356
1363
  keepFor: '1_years',
1357
1364
  include: ['decisions', 'sources', 'checks'],
1365
+ suggestTests: false,
1358
1366
  retentionDays: 365,
1359
1367
  },
1360
1368
  checks: {
@@ -1449,7 +1457,7 @@ export const SHIP_OR_FIX_APP_0_0_1 = {
1449
1457
  },
1450
1458
  },
1451
1459
  ],
1452
- judgmentModel: 'cloudflare:gtw/typesafe/jev',
1460
+ decisionModel: 'cloudflare:gtw/typesafe/jev',
1453
1461
  },
1454
1462
  enabled: true,
1455
1463
  tags: ['example', 'decision', 'benchmarks'],
@@ -1512,6 +1520,7 @@ export const SUPPLIER_COMPARISON_APP_0_0_1 = {
1512
1520
  record: {
1513
1521
  keepFor: '1_years',
1514
1522
  include: ['decisions', 'sources', 'checks'],
1523
+ suggestTests: false,
1515
1524
  retentionDays: 365,
1516
1525
  },
1517
1526
  checks: {
@@ -1591,7 +1600,7 @@ export const SUPPLIER_COMPARISON_APP_0_0_1 = {
1591
1600
  ],
1592
1601
  minConfidence: 0.0,
1593
1602
  scenarios: [],
1594
- judgmentModel: 'cloudflare:gtw/typesafe/jev',
1603
+ decisionModel: 'cloudflare:gtw/typesafe/jev',
1595
1604
  },
1596
1605
  enabled: true,
1597
1606
  tags: ['example', 'decision', 'procurement'],
@@ -1802,6 +1811,7 @@ export const SUPPORT_DESK_APP_0_0_1 = {
1802
1811
  record: {
1803
1812
  keepFor: '1_years',
1804
1813
  include: ['conversations', 'sources', 'feedback'],
1814
+ suggestTests: false,
1805
1815
  retentionDays: 365,
1806
1816
  },
1807
1817
  checks: {
@@ -1914,6 +1924,7 @@ export const WEB_RESEARCH_APP_0_0_1 = {
1914
1924
  record: {
1915
1925
  keepFor: '90_days',
1916
1926
  include: ['conversations', 'sources', 'feedback'],
1927
+ suggestTests: false,
1917
1928
  retentionDays: 90,
1918
1929
  },
1919
1930
  checks: {
@@ -2111,7 +2122,7 @@ export const APP_SOURCES = {
2111
2122
  instructions: 'Can the proposed correction be applied without a person checking each row?',
2112
2123
  },
2113
2124
  ],
2114
- judgment_model: 'cloudflare:gtw/typesafe/jev',
2125
+ decision_model: 'cloudflare:gtw/typesafe/jev',
2115
2126
  },
2116
2127
  tags: ['example', 'decision', 'data-quality'],
2117
2128
  icon: 'filter',
@@ -2240,7 +2251,7 @@ export const APP_SOURCES = {
2240
2251
  id: 'model-choice',
2241
2252
  name: 'Model Choice',
2242
2253
  kind: 'decision',
2243
- description: 'Which chat model should this use case run on? For a team choosing a model for one job — a summarizer, a classifier, an agent — from what a benchmark run measured and what a judge reads in the answers.',
2254
+ description: 'Which chat model should this use case run on? For a team choosing a model for one job — a summarizer, a classifier, an agent — from what a benchmark run measured and what a decision model reads in the answers.',
2244
2255
  owner: 'Datalayer <info@datalayer.io>',
2245
2256
  agent: 'jupyter-data-analyst:0.0.1',
2246
2257
  contents: [
@@ -2353,7 +2364,7 @@ export const APP_SOURCES = {
2353
2364
  },
2354
2365
  },
2355
2366
  ],
2356
- judgment_model: 'cloudflare:gtw/typesafe/jev',
2367
+ decision_model: 'cloudflare:gtw/typesafe/jev',
2357
2368
  },
2358
2369
  tags: ['example', 'decision', 'benchmarks', 'models'],
2359
2370
  icon: 'cpu',
@@ -3046,7 +3057,7 @@ export const APP_SOURCES = {
3046
3057
  },
3047
3058
  },
3048
3059
  ],
3049
- judgment_model: 'cloudflare:gtw/typesafe/jev',
3060
+ decision_model: 'cloudflare:gtw/typesafe/jev',
3050
3061
  },
3051
3062
  tags: ['example', 'decision', 'benchmarks'],
3052
3063
  icon: 'checklist',
@@ -3124,7 +3135,7 @@ export const APP_SOURCES = {
3124
3135
  ],
3125
3136
  },
3126
3137
  ],
3127
- judgment_model: 'cloudflare:gtw/typesafe/jev',
3138
+ decision_model: 'cloudflare:gtw/typesafe/jev',
3128
3139
  },
3129
3140
  tags: ['example', 'decision', 'procurement'],
3130
3141
  icon: 'package',