@datalayer/agent-runtimes 1.3.82 → 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.
@@ -12,6 +12,11 @@ import { jsx as _jsx } from "react/jsx-runtime";
12
12
  * its action is dispatched, so the action's context reads them. A form the
13
13
  * schema refuses says why beside each field and sends nothing.
14
14
  *
15
+ * A form without an action is a set of settings (C-16) — an application's
16
+ * `interface.settings` drawn beside its conversation: no button, its values
17
+ * written where `values` points as they are filled, for the next run to take;
18
+ * the runtime checks them against the same schema.
19
+ *
15
20
  * @module components/a2ui/datalayer/Form
16
21
  */
17
22
  import { useEffect, useMemo, useState } from 'react';
@@ -34,11 +39,23 @@ export function FormView({ props }) {
34
39
  useEffect(() => {
35
40
  setLocal(JSON.parse(shownKey));
36
41
  }, [shownKey]);
37
- const uiSchema = useMemo(() => ({ 'ui:submitButtonOptions': { submitText: submitLabel } }), [submitLabel]);
42
+ // Without an action, nothing is sent: no button, the values written as filled.
43
+ const live = action === undefined;
44
+ const uiSchema = useMemo(() => ({
45
+ 'ui:submitButtonOptions': live
46
+ ? { norender: true }
47
+ : { submitText: submitLabel },
48
+ }), [live, submitLabel]);
38
49
  const problem = schema.type !== undefined && schema.type !== 'object'
39
50
  ? 'Its fields are not an object schema: a form asks for several named values.'
40
51
  : null;
41
- return (_jsx(BlockFrame, { title: title, label: "Form", weight: props.weight, testId: "a2ui-form", children: problem ? (_jsx(Problem, { children: problem })) : (_jsx(SchemaForm, { schema: schema, uiSchema: uiSchema, formData: values, validator: validator, liveValidate: touched, onBlur: () => setTouched(true), onError: () => setTouched(true), showErrorList: false, onChange: event => setLocal((event.formData ?? {})), onSubmit: event => {
52
+ return (_jsx(BlockFrame, { title: title, label: "Form", weight: props.weight, testId: "a2ui-form", children: problem ? (_jsx(Problem, { children: problem })) : (_jsx(SchemaForm, { schema: schema, uiSchema: uiSchema, formData: values, validator: validator, liveValidate: touched, onBlur: () => setTouched(true), onError: () => setTouched(true), showErrorList: false, onChange: event => {
53
+ const filled = (event.formData ?? {});
54
+ setLocal(filled);
55
+ if (live) {
56
+ setValues(filled);
57
+ }
58
+ }, onSubmit: event => {
42
59
  setValues((event.formData ?? {}));
43
60
  action?.();
44
61
  } })) }));
@@ -124,7 +124,6 @@ export function emptyAppspec(kind = 'chat') {
124
124
  starters: [],
125
125
  commands: [],
126
126
  modes: [],
127
- settings: [],
128
127
  components: [],
129
128
  outputs: [],
130
129
  },
@@ -148,6 +147,7 @@ export function emptyAppspec(kind = 'chat') {
148
147
  notifications: [],
149
148
  setup: [],
150
149
  enabled: true,
150
+ unavailable_because: '',
151
151
  tags: [],
152
152
  icon: DEFAULT_ICON,
153
153
  emoji: DEFAULT_EMOJI,
@@ -186,6 +186,7 @@ const KNOWN_KEYS = [
186
186
  'notifications',
187
187
  'decision',
188
188
  'enabled',
189
+ 'unavailable_because',
189
190
  'tags',
190
191
  'icon',
191
192
  'emoji',
@@ -242,24 +243,6 @@ function parseMode(data) {
242
243
  }
243
244
  return mode;
244
245
  }
245
- function parseSetting(data) {
246
- const setting = {
247
- id: text(data.id),
248
- type: oneOf(data.type, ['select', 'text', 'toggle', 'slider', 'number'], 'text'),
249
- label: text(data.label),
250
- options: texts(data.options),
251
- };
252
- if (['string', 'boolean', 'number'].includes(typeof data.default)) {
253
- setting.default = data.default;
254
- }
255
- if (typeof data.min === 'number') {
256
- setting.min = data.min;
257
- }
258
- if (typeof data.max === 'number') {
259
- setting.max = data.max;
260
- }
261
- return setting;
262
- }
263
246
  function parseSurface(data) {
264
247
  return {
265
248
  protocol: text(data.protocol, DEFAULT_PROTOCOL),
@@ -300,11 +283,16 @@ function parseInterface(data, kind) {
300
283
  })),
301
284
  commands: records(data.commands).map(parseCommand),
302
285
  modes: records(data.modes).map(parseMode),
303
- settings: records(data.settings).map(parseSetting),
304
286
  components: texts(data.components),
305
287
  voice: parseVoice(data.voice),
306
288
  outputs: texts(data.outputs),
307
289
  };
290
+ // Its settings are the JSON Schema of a form (C-16), kept as written: what
291
+ // is not one — the list of settings of earlier versions among it — is not
292
+ // read, and is said by the checks.
293
+ if (isData(data.settings)) {
294
+ parsed.settings = JSON.parse(JSON.stringify(data.settings));
295
+ }
308
296
  if (isData(data.surface)) {
309
297
  parsed.surface = parseSurface(data.surface);
310
298
  }
@@ -495,6 +483,7 @@ export function parseAppspec(document) {
495
483
  memory: text(data.memory),
496
484
  notifications: texts(data.notifications),
497
485
  enabled: flag(data.enabled, true),
486
+ unavailable_because: text(data.unavailable_because),
498
487
  tags: texts(data.tags),
499
488
  icon: text(data.icon, DEFAULT_ICON),
500
489
  emoji: text(data.emoji, DEFAULT_EMOJI) || DEFAULT_EMOJI,
@@ -598,19 +587,7 @@ function dumpInterface(spec, kind) {
598
587
  }
599
588
  return written.data;
600
589
  }))
601
- .list('settings', spec.settings.map(setting => {
602
- const written = new Writer()
603
- .text('id', setting.id, '\u0000')
604
- .text('type', setting.type, '\u0000')
605
- .text('label', setting.label, '\u0000')
606
- .list('options', setting.options);
607
- for (const key of ['default', 'min', 'max']) {
608
- if (setting[key] !== undefined) {
609
- written.data[key] = setting[key];
610
- }
611
- }
612
- return written.data;
613
- }))
590
+ .part('settings', spec.settings ? { ...spec.settings } : {})
614
591
  .list('components', spec.components);
615
592
  if (spec.surface) {
616
593
  writer.data.surface = dumpSurface(spec.surface);
@@ -765,6 +742,7 @@ export function dumpAppspec(app) {
765
742
  }
766
743
  writer
767
744
  .value('enabled', app.enabled, true)
745
+ .text('unavailable_because', app.unavailable_because)
768
746
  .list('tags', app.tags)
769
747
  .text('icon', app.icon ?? DEFAULT_ICON, DEFAULT_ICON)
770
748
  .text('emoji', app.emoji, DEFAULT_EMOJI)
@@ -95,6 +95,14 @@ function shapeProblems(app) {
95
95
  if (Boolean(app.agent) === Boolean(app.team)) {
96
96
  problems.push('An application names who does the work: an `agent`, or a `team`, and not both.');
97
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
+ }
98
106
  if (app.kind === 'decision' && !app.decision) {
99
107
  problems.push('A decision application says what it decides, under `decision`.');
100
108
  }
@@ -472,7 +480,6 @@ const ENUMS = {
472
480
  behaviour: ['do_it', 'if_asked', 'ask_first', 'leave_to_me'],
473
481
  layout: ['chat', 'page', 'split'],
474
482
  accent: ['green', 'rose', 'sky', 'lime', 'sun', 'violet'],
475
- settingType: ['select', 'text', 'toggle', 'slider', 'number'],
476
483
  record: [
477
484
  'conversations',
478
485
  'actions',
@@ -721,13 +728,16 @@ export function documentShapeProblems(document) {
721
728
  if (choosingModel.length > 1) {
722
729
  at('interface.modes', `let only one mode choose the model, not ${choosingModel.join(', ')}`);
723
730
  }
724
- records(ui.settings, 'interface.settings', (item, where) => {
725
- required(item, 'id', where);
726
- required(item, 'type', where);
727
- required(item, 'label', where);
728
- oneOf(item.type, ENUMS.settingType, `${where}.type`);
729
- texts(item.options, `${where}.options`);
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
+ }
731
741
  mapping(ui.surface, 'interface.surface', surface => {
732
742
  records(surface.components, 'interface.surface.components', (item, where) => {
733
743
  required(item, 'id', where);
@@ -23,7 +23,7 @@
23
23
  *
24
24
  * @module loop/plugins/app-page/appPageModel
25
25
  */
26
- import type { AppSettingSpec, AppSpec } from '../../../types/agentspecs';
26
+ import type { AppFormField, AppFormSchema, AppSpec } from '../../../types/agentspecs';
27
27
  import type { ChatTurnSnapshot, ConversationEntry } from '../../core';
28
28
  /** The id of the editor surface an application's page is drawn in. */
29
29
  export declare const APP_PAGE_SURFACE = "app-page";
@@ -77,6 +77,15 @@ export declare const APP_KIND_PATHS: Record<AppPageKind, AppKindPaths>;
77
77
  export declare const isAppPageKind: (kind: string) => kind is AppPageKind;
78
78
  /** The path a setting, an input of the page, is kept at. */
79
79
  export declare const inputPath: (id: string) => string;
80
+ /**
81
+ * The fields of an application's settings (C-16), by name, in the order its
82
+ * form says them; none when it has no settings.
83
+ */
84
+ export declare function settingFields(settings: AppFormSchema | undefined): Array<[string, AppFormField]>;
85
+ /** A setting as a person reads it: its field's `title`, else its name. */
86
+ export declare const settingTitle: (id: string, field: AppFormField) => string;
87
+ /** What each setting starts at: its field's `default`, for those that say one. */
88
+ export declare function settingDefaults(settings: AppFormSchema | undefined): Record<string, unknown>;
80
89
  /**
81
90
  * What an application publishes and takes, with its own settings: the kind's
82
91
  * list, and for a kind that has inputs each setting at `/inputs/<id>`, both
@@ -95,8 +104,6 @@ export declare function hasAppPage(app: Pick<AppSpec, 'kind' | 'interface' | 'ag
95
104
  * none): the application's layout is `chat`, the conversation alone.
96
105
  */
97
106
  export declare function surfaceUnshown(app: Pick<AppSpec, 'kind' | 'interface' | 'agent'>): string | null;
98
- /** A setting's first value, as its input holds it. */
99
- export declare function initialInput(setting: AppSettingSpec): unknown;
100
107
  /**
101
108
  * What the conversation publishes to the page, by path, now: from its
102
109
  * current turn, and for a chat its whole conversation.
@@ -136,8 +143,10 @@ export declare function givenFiles(files: unknown): {
136
143
  };
137
144
  /**
138
145
  * The application's settings as the page holds them, as the session API
139
- * takes them: a choice as the option chosen, a number as a number, a toggle
140
- * as on or off — each setting the page has a value for.
146
+ * takes them, each as its field's type says (C-16): a number as a number, a
147
+ * boolean as on or off, a word as the option chosen — a ChoicePicker holds a
148
+ * list of them — each setting the page has a value for. The runtime checks
149
+ * them against the settings' form.
141
150
  */
142
151
  export declare function settingsOf(app: Pick<AppSpec, 'interface'>, inputs: unknown): Record<string, unknown>;
143
152
  /**
@@ -141,6 +141,25 @@ export const APP_KIND_PATHS = {
141
141
  export const isAppPageKind = (kind) => Object.prototype.hasOwnProperty.call(APP_KIND_PATHS, kind);
142
142
  /** The path a setting, an input of the page, is kept at. */
143
143
  export const inputPath = (id) => `/inputs/${id}`;
144
+ /**
145
+ * The fields of an application's settings (C-16), by name, in the order its
146
+ * form says them; none when it has no settings.
147
+ */
148
+ export function settingFields(settings) {
149
+ const properties = settings?.properties;
150
+ if (!properties || typeof properties !== 'object') {
151
+ return [];
152
+ }
153
+ return Object.entries(properties).filter((entry) => typeof entry[1] === 'object' && entry[1] !== null);
154
+ }
155
+ /** A setting as a person reads it: its field's `title`, else its name. */
156
+ export const settingTitle = (id, field) => typeof field.title === 'string' && field.title ? field.title : id;
157
+ /** What each setting starts at: its field's `default`, for those that say one. */
158
+ export function settingDefaults(settings) {
159
+ return Object.fromEntries(settingFields(settings)
160
+ .filter(([, field]) => field.default !== undefined)
161
+ .map(([id, field]) => [id, field.default]));
162
+ }
144
163
  /**
145
164
  * What an application publishes and takes, with its own settings: the kind's
146
165
  * list, and for a kind that has inputs each setting at `/inputs/<id>`, both
@@ -154,10 +173,10 @@ export function appKindPaths(app) {
154
173
  if (!kind.inputs) {
155
174
  return kind;
156
175
  }
157
- const inputs = app.interface.settings.map(setting => ({
158
- path: inputPath(setting.id),
159
- meaning: `The setting “${setting.label || setting.id}”.`,
160
- words: setting.label || setting.id,
176
+ const inputs = settingFields(app.interface.settings).map(([id, field]) => ({
177
+ path: inputPath(id),
178
+ meaning: `The setting “${settingTitle(id, field)}”.`,
179
+ words: settingTitle(id, field),
161
180
  }));
162
181
  return {
163
182
  ...kind,
@@ -199,24 +218,6 @@ const STATUS_WORDS = {
199
218
  done: 'Answered',
200
219
  error: 'Stopped',
201
220
  };
202
- /** A setting's first value, as its input holds it. */
203
- export function initialInput(setting) {
204
- switch (setting.type) {
205
- case 'toggle':
206
- return Boolean(setting.default ?? false);
207
- case 'select':
208
- // A ChoicePicker holds a list of the values chosen.
209
- return setting.default === undefined ? [] : [String(setting.default)];
210
- case 'slider':
211
- return Number(setting.default ?? setting.min ?? 0);
212
- case 'number':
213
- return setting.max === undefined
214
- ? String(setting.default ?? '')
215
- : Number(setting.default ?? setting.min ?? 0);
216
- default:
217
- return String(setting.default ?? '');
218
- }
219
- }
220
221
  /**
221
222
  * What the conversation publishes to the page, by path, now: from its
222
223
  * current turn, and for a chat its whole conversation.
@@ -253,10 +254,20 @@ export function appPageInitialData(app) {
253
254
  }
254
255
  const paths = appKindPaths(app);
255
256
  if (paths.inputs) {
256
- tree.inputs = Object.fromEntries(app.interface.settings.map(setting => [
257
- setting.id,
258
- initialInput(setting),
259
- ]));
257
+ // Each setting at its default, as the settings' form holds it; a
258
+ // ChoicePicker a builder placed at a setting holds a list of the values
259
+ // chosen.
260
+ const inputs = settingDefaults(app.interface.settings);
261
+ for (const node of app.interface.surface?.components ?? []) {
262
+ const bound = node.value?.path;
263
+ const id = typeof bound === 'string' && bound.startsWith('/inputs/')
264
+ ? bound.slice('/inputs/'.length)
265
+ : undefined;
266
+ if (node.component === 'ChoicePicker' && id && id in inputs) {
267
+ inputs[id] = Array.isArray(inputs[id]) ? inputs[id] : [inputs[id]];
268
+ }
269
+ }
270
+ tree.inputs = inputs;
260
271
  }
261
272
  if (paths.accepts.some(entry => entry.path === DRAFT.path)) {
262
273
  tree.draft = '';
@@ -289,9 +300,9 @@ export function inputsInWords(app, inputs) {
289
300
  : {};
290
301
  const lines = [];
291
302
  const seen = new Set();
292
- for (const setting of app.interface.settings) {
293
- seen.add(setting.id);
294
- lines.push(inputWords(setting.label || setting.id, values[setting.id]));
303
+ for (const [id, field] of settingFields(app.interface.settings)) {
304
+ seen.add(id);
305
+ lines.push(inputWords(settingTitle(id, field), values[id]));
295
306
  }
296
307
  for (const [id, value] of Object.entries(values)) {
297
308
  if (!seen.has(id)) {
@@ -345,38 +356,43 @@ export function givenFiles(files) {
345
356
  }
346
357
  /**
347
358
  * The application's settings as the page holds them, as the session API
348
- * takes them: a choice as the option chosen, a number as a number, a toggle
349
- * as on or off — each setting the page has a value for.
359
+ * takes them, each as its field's type says (C-16): a number as a number, a
360
+ * boolean as on or off, a word as the option chosen — a ChoicePicker holds a
361
+ * list of them — each setting the page has a value for. The runtime checks
362
+ * them against the settings' form.
350
363
  */
351
364
  export function settingsOf(app, inputs) {
352
365
  const values = inputs && typeof inputs === 'object'
353
366
  ? inputs
354
367
  : {};
355
368
  const settings = {};
356
- for (const setting of app.interface.settings) {
357
- const value = values[setting.id];
369
+ for (const [id, field] of settingFields(app.interface.settings)) {
370
+ const held = values[id];
371
+ if (held === undefined || held === null) {
372
+ continue;
373
+ }
374
+ if (field.type === 'array' || field.type === 'object') {
375
+ settings[id] = held;
376
+ continue;
377
+ }
378
+ const value = Array.isArray(held) ? held[0] : held;
358
379
  if (value === undefined || value === null) {
359
380
  continue;
360
381
  }
361
- switch (setting.type) {
362
- case 'select': {
363
- const chosen = Array.isArray(value) ? value[0] : value;
364
- if (chosen !== undefined && chosen !== '') {
365
- settings[setting.id] = String(chosen);
366
- }
367
- break;
368
- }
369
- case 'toggle':
370
- settings[setting.id] = Boolean(value);
371
- break;
372
- case 'slider':
382
+ switch (field.type) {
383
+ case 'integer':
373
384
  case 'number':
374
385
  if (value !== '' && !Number.isNaN(Number(value))) {
375
- settings[setting.id] = Number(value);
386
+ settings[id] = Number(value);
376
387
  }
377
388
  break;
389
+ case 'boolean':
390
+ settings[id] = Boolean(value);
391
+ break;
378
392
  default:
379
- settings[setting.id] = String(value);
393
+ if (value !== '' || !Array.isArray(field.enum)) {
394
+ settings[id] = typeof value === 'string' ? value : String(value);
395
+ }
380
396
  }
381
397
  }
382
398
  return settings;
@@ -464,44 +480,22 @@ const button = (id, label, name, primary = false) => [
464
480
  },
465
481
  { id: `${id}-label`, component: 'Text', text: label },
466
482
  ];
467
- /** A setting as the input of the catalog that holds its value. */
468
- function settingInput(setting) {
469
- const base = {
470
- id: `input-${setting.id}`,
471
- label: setting.label || setting.id,
472
- value: { path: inputPath(setting.id) },
473
- };
474
- switch (setting.type) {
475
- case 'toggle':
476
- return { ...base, component: 'CheckBox' };
477
- case 'select':
478
- return {
479
- ...base,
480
- component: 'ChoicePicker',
481
- options: setting.options.map(option => ({
482
- label: option,
483
- value: option,
484
- })),
485
- };
486
- case 'slider':
487
- return {
488
- ...base,
489
- component: 'Slider',
490
- min: setting.min ?? 0,
491
- max: setting.max ?? 100,
492
- };
493
- case 'number':
494
- return setting.max === undefined
495
- ? { ...base, component: 'TextField', variant: 'number' }
496
- : {
497
- ...base,
498
- component: 'Slider',
499
- min: setting.min ?? 0,
500
- max: setting.max,
501
- };
502
- default:
503
- return { ...base, component: 'TextField' };
504
- }
483
+ /**
484
+ * The settings as one Form block (C-16): their form drawn with
485
+ * `@datalayer/primer-rjsf`, the values written at `/inputs` as they are
486
+ * filled — no action, no button: they go with the next run.
487
+ */
488
+ function settingsForm(app) {
489
+ return app.interface.settings && settingFields(app.interface.settings).length
490
+ ? [
491
+ {
492
+ id: 'settings',
493
+ component: 'Form',
494
+ schema: app.interface.settings,
495
+ values: { path: '/inputs' },
496
+ },
497
+ ]
498
+ : [];
505
499
  }
506
500
  /**
507
501
  * The page drawn when the application has a page layout and no surface
@@ -509,9 +503,7 @@ function settingInput(setting) {
509
503
  * buttons its kind handles that the conversation does not already give.
510
504
  */
511
505
  export function defaultAppSurface(app) {
512
- const inputs = appKindPaths(app).inputs
513
- ? app.interface.settings.map(settingInput)
514
- : [];
506
+ const inputs = appKindPaths(app).inputs ? settingsForm(app) : [];
515
507
  const body = [];
516
508
  switch (app.kind) {
517
509
  case 'chat':
@@ -4900,8 +4900,9 @@ export const JUPYTER_DATA_ANALYST_AGENTSPEC_0_0_1 = {
4900
4900
  'collaboration',
4901
4901
  ],
4902
4902
  domain: undefined,
4903
- enabled: false,
4903
+ enabled: true,
4904
4904
  model: 'bedrock:us.anthropic.claude-sonnet-4-6',
4905
+ modelAdditionals: ['alibaba:qwen-max'],
4905
4906
  mcpServers: [],
4906
4907
  skills: [].filter(Boolean),
4907
4908
  backendTools: [TOOL_MAP['runtime-echo:0.0.1']],
@@ -7966,8 +7967,9 @@ export const WORKER_CUSTOMER_INTERVIEWER_AGENTSPEC_0_0_1 = {
7966
7967
  description: `Conducts adaptive, AI-led interviews that respond intelligently to each answer. It asks relevant follow-up questions, uncovers motivations and decision-making patterns, and transforms conversations into structured, actionable insights.`,
7967
7968
  tags: ['research', 'customer-support', 'analysis'],
7968
7969
  domain: 'market-analyst',
7969
- enabled: false,
7970
+ enabled: true,
7970
7971
  model: 'bedrock:us.anthropic.claude-sonnet-4-6',
7972
+ modelAdditionals: ['alibaba:qwen-max'],
7971
7973
  mcpServers: [],
7972
7974
  skills: [
7973
7975
  SKILL_MAP['text-summarizer:0.0.1']
@@ -8249,8 +8251,9 @@ export const WORKER_DOCUMENT_QA_AGENTSPEC_0_0_1 = {
8249
8251
  'qa',
8250
8252
  ],
8251
8253
  domain: 'personal-assistant',
8252
- enabled: false,
8254
+ enabled: true,
8253
8255
  model: 'bedrock:us.anthropic.claude-sonnet-4-6',
8256
+ modelAdditionals: ['alibaba:qwen-max'],
8254
8257
  mcpServers: [],
8255
8258
  skills: [
8256
8259
  SKILL_MAP['events:0.0.1']