@datalayer/agent-runtimes 1.3.30 → 1.3.33

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 +2 -0
  71. package/lib/stories/loop/pictures-main.js +75 -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
@@ -112,7 +112,7 @@ export const APPSPEC_SCHEMA = {
112
112
  },
113
113
  AppCriterion: {
114
114
  additionalProperties: false,
115
- description: 'What an alternative is judged on.',
115
+ description: 'What an alternative is weighed on.',
116
116
  properties: {
117
117
  name: {
118
118
  description: 'Its name',
@@ -133,7 +133,7 @@ export const APPSPEC_SCHEMA = {
133
133
  },
134
134
  instructions: {
135
135
  default: '',
136
- description: 'What a judgment model is asked, or how a metric is computed',
136
+ description: 'What a decision model is asked, or how a metric is computed',
137
137
  title: 'Instructions',
138
138
  type: 'string',
139
139
  },
@@ -182,7 +182,7 @@ export const APPSPEC_SCHEMA = {
182
182
  type: 'array',
183
183
  },
184
184
  criteria: {
185
- description: 'What each is judged on',
185
+ description: 'What each is weighed on',
186
186
  items: {
187
187
  $ref: '#/$defs/AppCriterion',
188
188
  },
@@ -191,7 +191,7 @@ export const APPSPEC_SCHEMA = {
191
191
  },
192
192
  min_confidence: {
193
193
  default: 0,
194
- description: 'A judgment less confident than this is put to the reader',
194
+ description: 'An answer less confident than this is put to the reader',
195
195
  maximum: 1,
196
196
  minimum: 0,
197
197
  title: 'Min Confidence',
@@ -205,10 +205,10 @@ export const APPSPEC_SCHEMA = {
205
205
  title: 'Scenarios',
206
206
  type: 'array',
207
207
  },
208
- judgment_model: {
208
+ decision_model: {
209
209
  default: '',
210
- description: 'The model that answers the judgments',
211
- title: 'Judgment Model',
210
+ description: "The model that answers the decision's typed questions",
211
+ title: 'Decision Model',
212
212
  type: 'string',
213
213
  },
214
214
  },
@@ -371,6 +371,12 @@ export const APPSPEC_SCHEMA = {
371
371
  title: 'Include',
372
372
  type: 'array',
373
373
  },
374
+ suggest_tests: {
375
+ default: false,
376
+ description: 'Whether its conversations may be used to suggest tests: a few, sampled from those kept while it is on, proposed to its builder; off unless said',
377
+ title: 'Suggest Tests',
378
+ type: 'boolean',
379
+ },
374
380
  },
375
381
  title: 'AppRecord',
376
382
  type: 'object',
@@ -97,7 +97,7 @@ export declare const AI_MODEL_ALIASES: Record<string, string>;
97
97
  /** A model by its id, or by an id it had before; undefined when the catalogue has neither. */
98
98
  export declare function getModel(modelId: string): AIModel | undefined;
99
99
  /**
100
- * A model a chat can run on: not a typed-judgment model (Jev), which
100
+ * A model a chat can run on: not a typed-decision model (Jev), which
101
101
  * answers typed questions about a state and nothing else. A spec that
102
102
  * states no capability is read as a chat model.
103
103
  */
@@ -333,7 +333,7 @@ export const BEDROCK_US_ANTHROPIC_CLAUDE_SONNET_4_6_0_0_1 = {
333
333
  'AWS_DEFAULT_REGION',
334
334
  ],
335
335
  tokensLimit: 64000,
336
- capabilities: ['chat', 'tools', 'judge'],
336
+ capabilities: ['chat', 'tools', 'decider'],
337
337
  contextWindow: 200000,
338
338
  };
339
339
  export const BEDROCK_US_ANTHROPIC_CLAUDE_SONNET_4_20250514_V1_0_0_0_1 = {
@@ -356,13 +356,13 @@ export const CLOUDFLARE_GTW_TYPESAFE_JEV_0_0_1 = {
356
356
  id: 'cloudflare:gtw/typesafe/jev',
357
357
  version: '0.0.1',
358
358
  name: 'Jev (Cloudflare AI Gateway)',
359
- description: "Typesafe's typed-judgment model through the account's AI Gateway - noul, choice and score questions answered as calibrated probabilities; 32k context; zero data retention; the gateway keeps the logs and bills from its credits",
359
+ description: "Typesafe's typed-decision model through the account's AI Gateway - noul, choice and score questions answered as calibrated probabilities; 32k context; zero data retention; the gateway keeps the logs and bills from its credits",
360
360
  provider: 'cloudflare',
361
361
  providerUrl: 'https://docs.typesafe.ai/models',
362
362
  default: false,
363
363
  available: true,
364
364
  requiredEnvVars: [],
365
- capabilities: ['judgments'],
365
+ capabilities: ['decisions'],
366
366
  billing: 'credits',
367
367
  route: 'ai-gateway',
368
368
  contextWindow: 32000,
@@ -415,7 +415,7 @@ export const CLOUDFLARE_WRK_OPENAI_GPT_OSS_120B_0_0_1 = {
415
415
  available: false,
416
416
  requiredEnvVars: [],
417
417
  tokensLimit: 32768,
418
- capabilities: ['chat', 'tools', 'codemode', 'judge'],
418
+ capabilities: ['chat', 'tools', 'codemode', 'decider'],
419
419
  billing: 'standard',
420
420
  route: 'workers-ai',
421
421
  contextWindow: 128000,
@@ -476,13 +476,13 @@ export const CLOUDFLARE_WRK_TYPESAFE_JEV_0_0_1 = {
476
476
  id: 'cloudflare:wrk/typesafe/jev',
477
477
  version: '0.0.1',
478
478
  name: 'Jev (Cloudflare Workers AI)',
479
- description: "Typesafe's typed-judgment model at Workers AI's own endpoint - noul, choice and score questions answered as calibrated probabilities; 32k context; zero data retention; billed from the account's credits, no gateway in the way",
479
+ description: "Typesafe's typed-decision model at Workers AI's own endpoint - noul, choice and score questions answered as calibrated probabilities; 32k context; zero data retention; billed from the account's credits, no gateway in the way",
480
480
  provider: 'cloudflare',
481
481
  providerUrl: 'https://docs.typesafe.ai/models',
482
482
  default: false,
483
483
  available: true,
484
484
  requiredEnvVars: [],
485
- capabilities: ['judgments'],
485
+ capabilities: ['decisions'],
486
486
  billing: 'credits',
487
487
  route: 'workers-ai',
488
488
  contextWindow: 32000,
@@ -665,12 +665,12 @@ export function getModel(modelId) {
665
665
  return AI_MODEL_CATALOGUE[AI_MODEL_ALIASES[modelId] ?? modelId];
666
666
  }
667
667
  /**
668
- * A model a chat can run on: not a typed-judgment model (Jev), which
668
+ * A model a chat can run on: not a typed-decision model (Jev), which
669
669
  * answers typed questions about a state and nothing else. A spec that
670
670
  * states no capability is read as a chat model.
671
671
  */
672
672
  export function isChatModel(model) {
673
- return !(model.capabilities ?? []).includes('judgments');
673
+ return !(model.capabilities ?? []).includes('decisions');
674
674
  }
675
675
  /** The models a person choosing a chat model is offered from. */
676
676
  export function listChatModels() {
@@ -9,6 +9,7 @@
9
9
  import type { ToolSpec } from '../types';
10
10
  export declare const CREATE_PLAN_TOOL_SPEC_0_0_1: ToolSpec;
11
11
  export declare const CURRENT_TIME_TOOL_SPEC_0_0_1: ToolSpec;
12
+ export declare const DECIDE_TOOL_SPEC_0_0_1: ToolSpec;
12
13
  export declare const DISPLAY_RECIPE_TOOL_SPEC_0_0_1: ToolSpec;
13
14
  export declare const EXAMPLE_CREATE_PLAN_TOOL_SPEC_0_0_1: ToolSpec;
14
15
  export declare const EXAMPLE_CURRENT_TIME_TOOL_SPEC_0_0_1: ToolSpec;
@@ -41,6 +41,24 @@ export const CURRENT_TIME_TOOL_SPEC_0_0_1 = {
41
41
  icon: 'clock',
42
42
  emoji: '🕒',
43
43
  };
44
+ export const DECIDE_TOOL_SPEC_0_0_1 = {
45
+ id: 'decide',
46
+ version: '0.0.1',
47
+ name: 'Decide',
48
+ description: 'Ask Jev typed questions about a text — noul (does a statement hold, with its probability), choice (one of named options) or score (a step on a rubric) — and get the typed answers back, through datalayer-ai-inference.',
49
+ tags: ['runtime', 'decisions', 'jev'],
50
+ enabled: true,
51
+ approval: 'auto',
52
+ timeout: undefined,
53
+ requiresApproval: false,
54
+ runtime: {
55
+ language: 'python',
56
+ package: 'agent_runtimes.tools.decisions',
57
+ method: 'decide',
58
+ },
59
+ icon: 'law',
60
+ emoji: '⚖️',
61
+ };
44
62
  export const DISPLAY_RECIPE_TOOL_SPEC_0_0_1 = {
45
63
  id: 'display-recipe',
46
64
  version: '0.0.1',
@@ -335,6 +353,7 @@ export const UPDATE_PLAN_STEP_TOOL_SPEC_0_0_1 = {
335
353
  export const TOOL_CATALOG = {
336
354
  'create-plan': CREATE_PLAN_TOOL_SPEC_0_0_1,
337
355
  'current-time': CURRENT_TIME_TOOL_SPEC_0_0_1,
356
+ decide: DECIDE_TOOL_SPEC_0_0_1,
338
357
  'display-recipe': DISPLAY_RECIPE_TOOL_SPEC_0_0_1,
339
358
  'example-create-plan': EXAMPLE_CREATE_PLAN_TOOL_SPEC_0_0_1,
340
359
  'example-current-time': EXAMPLE_CURRENT_TIME_TOOL_SPEC_0_0_1,
@@ -0,0 +1,21 @@
1
+ import type { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { type AssistantPicture, type ReferenceMode } from './ReferenceScreens';
3
+ import type { ThemeVariant } from '@datalayer/primer-addons';
4
+ interface ReferenceArgs {
5
+ theme: ThemeVariant;
6
+ mode: ReferenceMode;
7
+ picture: AssistantPicture;
8
+ }
9
+ declare const meta: Meta<ReferenceArgs>;
10
+ export default meta;
11
+ type Story = StoryObj<ReferenceArgs>;
12
+ /** A conversation. */
13
+ export declare const Conversation: Story;
14
+ /** A conversation beside its work. */
15
+ export declare const BesideItsWork: Story;
16
+ /** The activity of a worker. */
17
+ export declare const WorkerActivity: Story;
18
+ /** An approval. */
19
+ export declare const Approval: Story;
20
+ /** The floating assistant, in each of its states or stepped aside. */
21
+ export declare const Assistant: Story;
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { ASSISTANT_PICTURES, AssistantScreen, ApprovalScreen, BesideWorkScreen, ConversationScreen, REFERENCE_THEMES, ReferenceTheme, WorkerActivityScreen, } from './ReferenceScreens';
3
+ const meta = {
4
+ title: 'Loop/Reference screens',
5
+ parameters: { layout: 'fullscreen' },
6
+ args: { theme: 'loop', mode: 'light', picture: 'idle' },
7
+ argTypes: {
8
+ theme: { control: 'select', options: REFERENCE_THEMES },
9
+ mode: { control: 'inline-radio', options: ['light', 'dark'] },
10
+ picture: { control: 'select', options: [...ASSISTANT_PICTURES] },
11
+ },
12
+ };
13
+ export default meta;
14
+ function story(Screen) {
15
+ return {
16
+ argTypes: { picture: { table: { disable: true } } },
17
+ render: ({ theme, mode }) => (_jsx(ReferenceTheme, { theme: theme, mode: mode, children: _jsx(Screen, {}) })),
18
+ };
19
+ }
20
+ /** A conversation. */
21
+ export const Conversation = story(ConversationScreen);
22
+ /** A conversation beside its work. */
23
+ export const BesideItsWork = story(BesideWorkScreen);
24
+ /** The activity of a worker. */
25
+ export const WorkerActivity = story(WorkerActivityScreen);
26
+ /** An approval. */
27
+ export const Approval = story(ApprovalScreen);
28
+ /** The floating assistant, in each of its states or stepped aside. */
29
+ export const Assistant = {
30
+ render: ({ theme, mode, picture }) => (_jsx(ReferenceTheme, { theme: theme, mode: mode, children: _jsx(AssistantScreen, { picture: picture }) })),
31
+ };
@@ -0,0 +1,53 @@
1
+ import type { JSX, ReactNode } from 'react';
2
+ import { type ThemeVariant } from '@datalayer/primer-addons';
3
+ /** The four screens, by the name a picture and a story take. */
4
+ export declare const REFERENCE_SCREENS: readonly ["conversation", "beside-work", "worker-activity", "approval"];
5
+ export type ReferenceScreen = (typeof REFERENCE_SCREENS)[number];
6
+ /** The floating assistant's pictures (T-27): its states, and stepped aside. */
7
+ export declare const ASSISTANT_PICTURES: readonly ["idle", "thinking", "working", "waiting", "speaking", "aside"];
8
+ export type AssistantPicture = (typeof ASSISTANT_PICTURES)[number];
9
+ /**
10
+ * The characters pictured idle, each in light and dark (T-25, T-24):
11
+ * Datalayer's wizard, cat and L👀P eyes — the paper clip's idle is
12
+ * `assistant-idle` — and the owl an example plugin contributes, drawn
13
+ * through `loop.assistant.character`.
14
+ */
15
+ export declare const CHARACTER_PICTURES: readonly ["wizard", "cat", "eyes", "owl"];
16
+ export type CharacterPicture = (typeof CHARACTER_PICTURES)[number];
17
+ export type ReferenceMode = 'light' | 'dark';
18
+ /** Every theme of the registry, in its order. */
19
+ export declare const REFERENCE_THEMES: ThemeVariant[];
20
+ /**
21
+ * A screen in a theme and a mode, on its stage: the flat tint of the accent
22
+ * in `loop` (mint, the default), the muted canvas in every other theme.
23
+ */
24
+ export declare function ReferenceTheme({ theme, mode, children, }: {
25
+ theme: ThemeVariant;
26
+ mode: ReferenceMode;
27
+ children: ReactNode;
28
+ }): JSX.Element;
29
+ /** A conversation. */
30
+ export declare function ConversationScreen(): JSX.Element;
31
+ /** A conversation beside its work: the split, one hairline between them. */
32
+ export declare function BesideWorkScreen(): JSX.Element;
33
+ /** The activity of a worker: what it did on its own, as it did it. */
34
+ export declare function WorkerActivityScreen(): JSX.Element;
35
+ /** An approval: the action, the rule that asks, and the one decision. */
36
+ export declare function ApprovalScreen(): JSX.Element;
37
+ export declare const REFERENCE_SCREEN_COMPONENTS: Record<ReferenceScreen, () => JSX.Element>;
38
+ /**
39
+ * The paper clip in one state, in the page's corner. `aside` puts a dialog
40
+ * over it: the real `useKeepClear` steps it aside, and the picture shows the
41
+ * dialog alone.
42
+ */
43
+ export declare function AssistantScreen({ picture, }: {
44
+ picture: AssistantPicture;
45
+ }): JSX.Element;
46
+ /**
47
+ * One character idle in the page's corner, read from what the enabled
48
+ * plugins contribute; `ready` is drawn with it.
49
+ */
50
+ export declare function CharacterScreen({ character, ready, }: {
51
+ character: CharacterPicture;
52
+ ready?: ReactNode;
53
+ }): JSX.Element;
@@ -0,0 +1,256 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * The four reference screens of the `loop` theme (LOOP T-01), and the
8
+ * floating assistant's states (T-27), drawn from the real chat components
9
+ * with fixed data: no agent, no network, no clock.
10
+ *
11
+ * - a conversation;
12
+ * - a conversation beside its work;
13
+ * - the activity of a worker;
14
+ * - an approval.
15
+ *
16
+ * Each renders in any theme of the registry and in either mode, so the same
17
+ * screen is the design reference (the stories) and the picture a token change
18
+ * is compared against (`pictures/`, T-16).
19
+ *
20
+ * @module stories/loop/ReferenceScreens
21
+ */
22
+ import { useEffect, useRef, useState } from 'react';
23
+ import { Box, Button, Heading, Text } from '@primer/react';
24
+ import { DatalayerThemeProvider, loopAccentStyles, setupPrimerPortals, themeConfigs, } from '@datalayer/primer-addons';
25
+ import { ChatBaseHeader } from '../../chat/header/ChatHeaderBase';
26
+ import { ChatMessageList } from '../../chat/messages/ChatMessageList';
27
+ import { InputPrompt } from '../../chat/prompt/InputPrompt';
28
+ import { PresenceFace, PresenceLine } from '../../chat/presence/Presence';
29
+ import { AssistantStage } from '../../chat/assistant/AssistantStage';
30
+ import { buildReactorFromPlugins } from '@datalayer/reactor';
31
+ import { AssistantCharactersPlugin, assistantCharacterNamed, } from '../../loop/plugins/assistant-characters';
32
+ import { OwlCharacterPlugin } from '../../examples/utils/owlCharacterPlugin';
33
+ /** The four screens, by the name a picture and a story take. */
34
+ export const REFERENCE_SCREENS = [
35
+ 'conversation',
36
+ 'beside-work',
37
+ 'worker-activity',
38
+ 'approval',
39
+ ];
40
+ /** The floating assistant's pictures (T-27): its states, and stepped aside. */
41
+ export const ASSISTANT_PICTURES = [
42
+ 'idle',
43
+ 'thinking',
44
+ 'working',
45
+ 'waiting',
46
+ 'speaking',
47
+ 'aside',
48
+ ];
49
+ /**
50
+ * The characters pictured idle, each in light and dark (T-25, T-24):
51
+ * Datalayer's wizard, cat and L👀P eyes — the paper clip's idle is
52
+ * `assistant-idle` — and the owl an example plugin contributes, drawn
53
+ * through `loop.assistant.character`.
54
+ */
55
+ export const CHARACTER_PICTURES = ['wizard', 'cat', 'eyes', 'owl'];
56
+ /** Every theme of the registry, in its order. */
57
+ export const REFERENCE_THEMES = Object.keys(themeConfigs);
58
+ // ---------------------------------------------------------------------------
59
+ // The theme around a screen
60
+ // ---------------------------------------------------------------------------
61
+ /**
62
+ * A screen in a theme and a mode, on its stage: the flat tint of the accent
63
+ * in `loop` (mint, the default), the muted canvas in every other theme.
64
+ */
65
+ export function ReferenceTheme({ theme, mode, children, }) {
66
+ const config = themeConfigs[theme] ?? themeConfigs.loop;
67
+ useEffect(() => {
68
+ setupPrimerPortals();
69
+ }, []);
70
+ useEffect(() => {
71
+ document.documentElement.setAttribute('data-reference-theme', theme);
72
+ document.documentElement.setAttribute('data-reference-mode', mode);
73
+ }, [theme, mode]);
74
+ return (_jsx(DatalayerThemeProvider, { colorMode: mode, theme: config.primerTheme, themeStyles: config.themeStyles, baseStyles: theme === 'loop'
75
+ ? loopAccentStyles('green', mode)
76
+ : undefined, children: _jsx(Box, { "data-reference-stage": "", sx: {
77
+ position: 'fixed',
78
+ inset: 0,
79
+ p: 4,
80
+ bg: 'var(--loop-stage, var(--bgColor-muted))',
81
+ color: 'fg.default',
82
+ display: 'flex',
83
+ }, children: children }) }));
84
+ }
85
+ /** One rounded surface on the stage: the theme's frame. */
86
+ function Frame({ children }) {
87
+ return (_jsx(Box, { sx: {
88
+ flex: 1,
89
+ minWidth: 0,
90
+ display: 'flex',
91
+ overflow: 'hidden',
92
+ bg: 'canvas.default',
93
+ borderRadius: 'var(--theme-radius-frame, 12px)',
94
+ border: 'var(--theme-hairline, 1px) solid',
95
+ borderColor: 'border.muted',
96
+ boxShadow: 'var(--theme-shadow, none)',
97
+ }, children: children }));
98
+ }
99
+ // ---------------------------------------------------------------------------
100
+ // Fixed data
101
+ // ---------------------------------------------------------------------------
102
+ const AT = new Date('2026-10-04T09:30:00Z');
103
+ function said(id, role, content) {
104
+ return { id, role, content, createdAt: AT };
105
+ }
106
+ function tool(id, toolName, args, status, result) {
107
+ return {
108
+ id,
109
+ type: 'tool-call',
110
+ toolCallId: id,
111
+ toolName,
112
+ args,
113
+ status,
114
+ result,
115
+ };
116
+ }
117
+ const CONVERSATION = [
118
+ said('m1', 'user', 'Which customers wrote about late deliveries this week?'),
119
+ said('m2', 'assistant', 'Three of them: **Ada Lovelace** (order 1042, four days late), ' +
120
+ '**Grace Hopper** (order 1057, asks for a refund) and **Alan Turing** ' +
121
+ '(order 1061, no tracking number). The details are in the ' +
122
+ '[support log](https://example.com/log).'),
123
+ said('m3', 'user', 'Draft a reply to Ada.'),
124
+ said('m4', 'assistant', 'Here is a draft, beside the conversation. It apologises, gives the new ' +
125
+ 'date and offers free shipping on her next order.'),
126
+ ];
127
+ const BESIDE_WORK = CONVERSATION.slice(2);
128
+ const WORKER_ACTIVITY = [
129
+ said('w1', 'assistant', 'Morning run: 12 new messages in the inbox.'),
130
+ tool('w2', 'read_inbox', { since: '2026-10-04T06:00:00Z' }, 'complete', {
131
+ messages: 12,
132
+ }),
133
+ tool('w3', 'label_message', { id: 'msg-881', label: 'Refund' }, 'complete', {
134
+ ok: true,
135
+ }),
136
+ tool('w4', 'label_message', { id: 'msg-884', label: 'Delivery' }, 'complete', { ok: true }),
137
+ said('w5', 'assistant', 'Nine labelled, two answered from the help centre, one left for you: a ' +
138
+ 'refund above the limit.'),
139
+ tool('w6', 'draft_reply', { id: 'msg-887' }, 'executing'),
140
+ ];
141
+ const APPROVAL = [
142
+ said('a1', 'user', 'Send the reply to Ada.'),
143
+ said('a2', 'assistant', 'Sending is a rule you keep: I ask first.'),
144
+ tool('a3', 'send_email', { to: 'ada@example.com', subject: 'Your order 1042' }, 'inProgress', { pending_approval: true, approval_id: 'approval-1' }),
145
+ ];
146
+ // ---------------------------------------------------------------------------
147
+ // The conversation
148
+ // ---------------------------------------------------------------------------
149
+ const AVATARS = {
150
+ userAvatar: 'me',
151
+ assistantAvatar: 'ai',
152
+ showAvatars: false,
153
+ avatarSize: 24,
154
+ userAvatarBg: 'accent.subtle',
155
+ assistantAvatarBg: 'accent.emphasis',
156
+ };
157
+ /** A chat as an application shows it: its presence, its words, its composer. */
158
+ function Conversation({ name, face, presence, items, composer = true, }) {
159
+ const endRef = useRef(null);
160
+ const [input, setInput] = useState('');
161
+ return (_jsxs(Box, { sx: {
162
+ flex: 1,
163
+ minWidth: 0,
164
+ display: 'flex',
165
+ flexDirection: 'column',
166
+ height: '100%',
167
+ }, children: [_jsx(ChatBaseHeader, { title: name, brandIcon: _jsx(PresenceFace, { face: face, size: 20, state: presence }), headerContent: _jsx(PresenceLine, { state: presence }), padding: 3, messageCount: items.length, onNewChat: () => undefined, onClear: () => undefined }), _jsx(Box, { sx: { flex: 1, minHeight: 0, overflow: 'hidden' }, children: _jsx(ChatMessageList, { displayItems: items, isLoading: false, isStreaming: false, showLoadingIndicator: false, hideMessagesAfterToolUI: false, avatarConfig: AVATARS, padding: 3, emptyContent: null, messagesEndRef: endRef, onRespond: async () => undefined }) }), composer && (_jsx(InputPrompt, { input: input, setInput: setInput, isLoading: false, connectionConfirmed: true, placeholder: "Send a message", autoFocus: false, padding: 3, onSend: () => undefined, onStop: () => undefined, showTokenUsage: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false, codemodeEnabled: false, hasConfigData: false, hasSkillsData: false, isA2AProtocol: false }))] }));
168
+ }
169
+ // ---------------------------------------------------------------------------
170
+ // The four screens
171
+ // ---------------------------------------------------------------------------
172
+ /** A conversation. */
173
+ export function ConversationScreen() {
174
+ return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 640, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: CONVERSATION }) }) }) }));
175
+ }
176
+ /** A conversation beside its work: the split, one hairline between them. */
177
+ export function BesideWorkScreen() {
178
+ return (_jsxs(Frame, { children: [_jsx(Box, { sx: { width: 400, flexShrink: 0, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "idle", items: BESIDE_WORK }) }), _jsx(Box, { sx: {
179
+ width: 'var(--theme-hairline, 1px)',
180
+ bg: 'border.muted',
181
+ flexShrink: 0,
182
+ } }), _jsxs(Box, { "data-reference-work": "", sx: {
183
+ flex: 1,
184
+ minWidth: 0,
185
+ p: 4,
186
+ display: 'flex',
187
+ flexDirection: 'column',
188
+ gap: 3,
189
+ }, children: [_jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "Draft \u00B7 reply" }), _jsx(Heading, { as: "h2", sx: { fontSize: 3, fontWeight: 600 }, children: "Your order 1042" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 1 }, children: "To ada@example.com" }), _jsxs(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [_jsx(Text, { as: "p", sx: { m: 0 }, children: "Dear Ada," }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "We are sorry your order arrived four days late. It left our warehouse on Monday and is with you by Thursday, 8 October." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "Your next order ships free, with no code to enter." }), _jsx(Text, { as: "p", sx: { m: 0 }, children: "The support desk" })] }), _jsxs(Box, { sx: { display: 'flex', gap: 2, mt: 'auto' }, children: [_jsx(Button, { variant: "primary", children: "Send" }), _jsx(Button, { children: "Edit" })] })] })] }));
190
+ }
191
+ /** The activity of a worker: what it did on its own, as it did it. */
192
+ export function WorkerActivityScreen() {
193
+ return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 680, display: 'flex' }, children: _jsx(Conversation, { name: "Inbox triage", face: "\uD83D\uDCEC", presence: "working", items: WORKER_ACTIVITY, composer: false }) }) }) }));
194
+ }
195
+ /** An approval: the action, the rule that asks, and the one decision. */
196
+ export function ApprovalScreen() {
197
+ return (_jsx(Frame, { children: _jsx(Box, { sx: { flex: 1, display: 'flex', justifyContent: 'center' }, children: _jsx(Box, { sx: { width: '100%', maxWidth: 640, display: 'flex' }, children: _jsx(Conversation, { name: "Support desk", face: "\uD83E\uDD8A", presence: "waiting", items: APPROVAL }) }) }) }));
198
+ }
199
+ export const REFERENCE_SCREEN_COMPONENTS = {
200
+ conversation: ConversationScreen,
201
+ 'beside-work': BesideWorkScreen,
202
+ 'worker-activity': WorkerActivityScreen,
203
+ approval: ApprovalScreen,
204
+ };
205
+ // ---------------------------------------------------------------------------
206
+ // The floating assistant (T-27)
207
+ // ---------------------------------------------------------------------------
208
+ const BALLOONS = {
209
+ speaking: {
210
+ text: 'Three customers wrote about late deliveries this week: Ada, Grace and Alan.',
211
+ more: true,
212
+ },
213
+ waiting: { text: 'Waiting for you: send the reply to Ada?' },
214
+ };
215
+ /**
216
+ * The paper clip in one state, in the page's corner. `aside` puts a dialog
217
+ * over it: the real `useKeepClear` steps it aside, and the picture shows the
218
+ * dialog alone.
219
+ */
220
+ export function AssistantScreen({ picture, }) {
221
+ const stageRef = useRef(null);
222
+ const state = picture === 'aside' ? 'idle' : picture;
223
+ const balloon = BALLOONS[picture];
224
+ return (_jsxs(Box, { sx: { flex: 1, position: 'relative' }, children: [picture === 'aside' && (_jsxs(Box, { role: "dialog", "aria-label": "A dialog over the assistant", sx: {
225
+ position: 'fixed',
226
+ right: 24,
227
+ bottom: 24,
228
+ width: 280,
229
+ height: 160,
230
+ p: 3,
231
+ zIndex: 2000,
232
+ bg: 'canvas.overlay',
233
+ border: 'var(--theme-hairline, 1px) solid',
234
+ borderColor: 'border.default',
235
+ borderRadius: 'var(--theme-radius-card, 6px)',
236
+ boxShadow: 'var(--theme-shadow, none)',
237
+ }, children: [_jsx(Text, { sx: { fontWeight: 600 }, children: "A dialog" }), _jsx(Text, { as: "p", sx: { color: 'fg.muted', fontSize: 1 }, children: "The assistant steps aside while this is open." })] })), _jsx(AssistantStage, { character: "paperclip", state: state, place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: () => undefined, balloon: balloon, insist: !!balloon, onDismiss: () => undefined })] }));
238
+ }
239
+ // ---------------------------------------------------------------------------
240
+ // The characters (T-25, T-24)
241
+ // ---------------------------------------------------------------------------
242
+ /** Datalayer's characters and the example plugin's owl, as contributed. */
243
+ const characterReactor = buildReactorFromPlugins([
244
+ AssistantCharactersPlugin,
245
+ OwlCharacterPlugin,
246
+ ]);
247
+ characterReactor.start();
248
+ /**
249
+ * One character idle in the page's corner, read from what the enabled
250
+ * plugins contribute; `ready` is drawn with it.
251
+ */
252
+ export function CharacterScreen({ character, ready, }) {
253
+ const stageRef = useRef(null);
254
+ const drawn = assistantCharacterNamed(characterReactor, character);
255
+ return (_jsxs(Box, { sx: { flex: 1, position: 'relative' }, children: [_jsx(AssistantStage, { character: drawn, state: "idle", place: { right: 48, bottom: 48 }, stageRef: stageRef, onDragStart: () => undefined, open: false, onToggle: () => undefined, onDismiss: () => undefined }), ready] }));
256
+ }
@@ -0,0 +1,2 @@
1
+ import '../../../style/primer-primitives.css';
2
+ import '@datalayer/primer-addons/style/loop-face.css';
@@ -0,0 +1,75 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * Copyright (c) 2025-2026 Datalayer, Inc.
4
+ * Distributed under the terms of the Modified BSD License.
5
+ */
6
+ /**
7
+ * The page the pictures are taken of (LOOP T-16): one reference screen, or
8
+ * one state of the floating assistant, in one theme and one mode, chosen by
9
+ * the address — `html/pictures.html?screen=approval&theme=loop&mode=dark`,
10
+ * or `?assistant=thinking&mode=light`, or a character idle,
11
+ * `?character=wizard&mode=dark` (Datalayer's, or the owl a plugin contributes).
12
+ *
13
+ * It sets `data-pictures-ready` on the document once the fonts are in — Inter
14
+ * in its two weights, served by this page (T-04) — and two frames have
15
+ * painted, which is what the capture waits for.
16
+ *
17
+ * @module stories/loop/pictures-main
18
+ */
19
+ const globals = globalThis;
20
+ if (globals['__webpack_public_path__'] === undefined) {
21
+ globals['__webpack_public_path__'] = '';
22
+ }
23
+ import { useEffect } from 'react';
24
+ import { createRoot } from 'react-dom/client';
25
+ import { ASSISTANT_PICTURES, AssistantScreen, CHARACTER_PICTURES, CharacterScreen, REFERENCE_SCREEN_COMPONENTS, REFERENCE_SCREENS, REFERENCE_THEMES, ReferenceTheme, } from './ReferenceScreens';
26
+ import '../../../style/primer-primitives.css';
27
+ // The `loop` theme's face (LOOP T-04): Inter and its metric-matched fallback,
28
+ // served by this page's own Vite, as the landing serves them from its build.
29
+ import '@datalayer/primer-addons/style/loop-face.css';
30
+ const params = new URLSearchParams(window.location.search);
31
+ const theme = (REFERENCE_THEMES.includes(params.get('theme'))
32
+ ? params.get('theme')
33
+ : 'loop');
34
+ const mode = params.get('mode') === 'dark' ? 'dark' : 'light';
35
+ const screen = params.get('screen');
36
+ const assistant = params.get('assistant');
37
+ const character = params.get('character');
38
+ function Ready() {
39
+ useEffect(() => {
40
+ let cancelled = false;
41
+ // Inter is fetched only once text asks for it: ask for both weights, so
42
+ // that the picture is never taken in the fallback.
43
+ void Promise.all([
44
+ document.fonts.load('400 14px "Inter Variable"'),
45
+ document.fonts.load('600 14px "Inter Variable"'),
46
+ ])
47
+ .then(() => document.fonts.ready)
48
+ .then(() => {
49
+ requestAnimationFrame(() => requestAnimationFrame(() => {
50
+ if (!cancelled) {
51
+ document.documentElement.setAttribute('data-pictures-ready', '');
52
+ }
53
+ }));
54
+ });
55
+ return () => {
56
+ cancelled = true;
57
+ };
58
+ }, []);
59
+ return null;
60
+ }
61
+ function Page() {
62
+ if (character && CHARACTER_PICTURES.includes(character)) {
63
+ return (_jsx(ReferenceTheme, { theme: theme, mode: mode, children: _jsx(CharacterScreen, { character: character, ready: _jsx(Ready, {}) }) }));
64
+ }
65
+ if (assistant && ASSISTANT_PICTURES.includes(assistant)) {
66
+ return (_jsxs(ReferenceTheme, { theme: theme, mode: mode, children: [_jsx(AssistantScreen, { picture: assistant }), _jsx(Ready, {})] }));
67
+ }
68
+ const name = screen && REFERENCE_SCREENS.includes(screen) ? screen : 'conversation';
69
+ const Screen = REFERENCE_SCREEN_COMPONENTS[name];
70
+ return (_jsxs(ReferenceTheme, { theme: theme, mode: mode, children: [_jsx(Screen, {}), _jsx(Ready, {})] }));
71
+ }
72
+ const container = document.getElementById('root');
73
+ if (container) {
74
+ createRoot(container).render(_jsx(Page, {}));
75
+ }
@@ -476,6 +476,11 @@ export interface AppRecordSpec {
476
476
  keepFor: string;
477
477
  retentionDays: number;
478
478
  include: string[];
479
+ /**
480
+ * Whether its conversations may be used to suggest tests (LOOP V-16): off
481
+ * unless said, and only those kept while it is on are sampled.
482
+ */
483
+ suggestTests: boolean;
479
484
  }
480
485
  /** Optional checks from the catalogue. */
481
486
  export interface AppChecksSpec {
@@ -522,7 +527,7 @@ export interface AppDecisionSpec {
522
527
  criteria: AppCriterionSpec[];
523
528
  minConfidence: number;
524
529
  scenarios: AppScenarioSpec[];
525
- judgmentModel: string;
530
+ decisionModel: string;
526
531
  }
527
532
  /**
528
533
  * An application (`agentspecs/apps`): the Appspec. An agent with an interface,
@@ -653,6 +653,11 @@ export interface ChatBaseProps {
653
653
  stop: () => void;
654
654
  /** Start the conversation over — what the header's + does. */
655
655
  newChat: () => void;
656
+ /**
657
+ * The conversation the next message goes to, as the runtime knows it
658
+ * (AG-UI's thread): what an application's record keeps it under.
659
+ */
660
+ thread: () => string;
656
661
  } | null) => void;
657
662
  /**
658
663
  * Reports whether the chat is streaming a reply, so a host that owns the