@datalayer/agent-runtimes 1.3.83 → 1.3.85

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 (168) hide show
  1. package/lib/chat/ChatFloating.d.ts +5 -4
  2. package/lib/chat/ChatFloating.js +8 -6
  3. package/lib/chat/ChatLanguage.d.ts +17 -0
  4. package/lib/chat/ChatLanguage.js +29 -0
  5. package/lib/chat/assistant/AssistantStage.d.ts +4 -4
  6. package/lib/chat/assistant/AssistantStage.js +4 -2
  7. package/lib/chat/assistant/BalloonParts.d.ts +2 -1
  8. package/lib/chat/assistant/BalloonParts.js +12 -5
  9. package/lib/chat/assistant/BalloonVisual.js +3 -1
  10. package/lib/chat/assistant/ConversationBalloon.d.ts +1 -1
  11. package/lib/chat/assistant/ConversationBalloon.js +3 -2
  12. package/lib/chat/assistant/SpeechBalloon.d.ts +1 -1
  13. package/lib/chat/assistant/SpeechBalloon.js +10 -7
  14. package/lib/chat/assistant/toolLine.d.ts +8 -4
  15. package/lib/chat/assistant/toolLine.js +13 -9
  16. package/lib/chat/base/ChatBase.js +30 -11
  17. package/lib/chat/base/loopElement.d.ts +53 -0
  18. package/lib/chat/base/loopElement.js +78 -0
  19. package/lib/chat/base/loopWindow.d.ts +31 -0
  20. package/lib/chat/base/loopWindow.js +74 -0
  21. package/lib/chat/display/EmptyState.js +3 -1
  22. package/lib/chat/index.d.ts +2 -0
  23. package/lib/chat/index.js +3 -0
  24. package/lib/chat/messages/ChatMessages.js +3 -1
  25. package/lib/chat/messages/TurnFooter.js +3 -1
  26. package/lib/chat/prompt/InputPrompt.js +3 -1
  27. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  28. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  29. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  30. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  31. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  32. package/lib/chat/words.d.ts +102 -0
  33. package/lib/chat/words.js +460 -0
  34. package/lib/components/a2ui/custom/index.d.ts +39 -0
  35. package/lib/components/a2ui/custom/index.js +95 -0
  36. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  37. package/lib/components/a2ui/custom/sandbox.js +188 -0
  38. package/lib/components/a2ui/datalayer/FileUpload.d.ts +10 -2
  39. package/lib/components/a2ui/datalayer/FileUpload.js +10 -7
  40. package/lib/components/a2ui/datalayer/Form.d.ts +2 -0
  41. package/lib/components/a2ui/datalayer/Form.js +9 -2
  42. package/lib/components/a2ui/datalayer/formWidgets.d.ts +19 -0
  43. package/lib/components/a2ui/datalayer/formWidgets.js +51 -0
  44. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  45. package/lib/components/a2ui/datalayer/index.js +1 -1
  46. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  47. package/lib/components/a2ui/datalayer/schema.js +12 -1
  48. package/lib/components/a2ui/index.d.ts +1 -0
  49. package/lib/components/a2ui/index.js +1 -0
  50. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  51. package/lib/components/a2ui/visibleWhen.js +24 -3
  52. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  53. package/lib/components/inspector/AgentInspector.js +27 -215
  54. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  55. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  56. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  57. package/lib/components/inspector/a2aSpans.js +373 -0
  58. package/lib/components/inspector/agentSpans.d.ts +120 -0
  59. package/lib/components/inspector/agentSpans.js +253 -0
  60. package/lib/components/inspector/chatSpans.d.ts +36 -0
  61. package/lib/components/inspector/chatSpans.js +50 -0
  62. package/lib/components/inspector/classify.d.ts +11 -8
  63. package/lib/components/inspector/classify.js +23 -24
  64. package/lib/components/inspector/index.d.ts +7 -7
  65. package/lib/components/inspector/index.js +7 -7
  66. package/lib/components/teams/A2ATeamGraph.d.ts +4 -4
  67. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  68. package/lib/components/teams/SceneTranscript.js +54 -0
  69. package/lib/components/teams/SceneView.d.ts +41 -0
  70. package/lib/components/teams/SceneView.js +28 -0
  71. package/lib/components/teams/index.d.ts +3 -0
  72. package/lib/components/teams/index.js +3 -0
  73. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  74. package/lib/components/teams/sceneTranscript.js +294 -0
  75. package/lib/components/teams/teamConnections.d.ts +2 -2
  76. package/lib/components/teams/teamConnections.js +3 -3
  77. package/lib/components/teams/useA2ATeam.d.ts +4 -4
  78. package/lib/components/teams/useA2ATeam.js +50 -46
  79. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  80. package/lib/examples/AgentA2ATeamExample.js +70 -60
  81. package/lib/examples/AssistantExample.js +2 -2
  82. package/lib/loop/apps/AppComposer.d.ts +40 -4
  83. package/lib/loop/apps/AppComposer.js +194 -19
  84. package/lib/loop/apps/AppRenderer.d.ts +21 -3
  85. package/lib/loop/apps/AppRenderer.js +88 -48
  86. package/lib/loop/apps/appspec.d.ts +13 -1
  87. package/lib/loop/apps/appspec.js +305 -12
  88. package/lib/loop/apps/checks.d.ts +14 -0
  89. package/lib/loop/apps/checks.js +276 -5
  90. package/lib/loop/apps/composer.d.ts +21 -10
  91. package/lib/loop/apps/composer.js +41 -8
  92. package/lib/loop/apps/customComponents.d.ts +41 -0
  93. package/lib/loop/apps/customComponents.js +260 -0
  94. package/lib/loop/apps/hostTools.d.ts +8 -1
  95. package/lib/loop/apps/hostTools.js +9 -0
  96. package/lib/loop/apps/index.d.ts +1 -0
  97. package/lib/loop/apps/index.js +1 -0
  98. package/lib/loop/apps/language.d.ts +68 -0
  99. package/lib/loop/apps/language.js +275 -0
  100. package/lib/loop/apps/settingsInputs.d.ts +29 -0
  101. package/lib/loop/apps/settingsInputs.js +147 -0
  102. package/lib/loop/apps/uploads.d.ts +43 -0
  103. package/lib/loop/apps/uploads.js +66 -0
  104. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  105. package/lib/loop/core/a2uiComponents.js +15 -0
  106. package/lib/loop/core/index.d.ts +15 -1
  107. package/lib/loop/core/index.js +3 -0
  108. package/lib/loop/embed/AppEmbed.d.ts +25 -3
  109. package/lib/loop/embed/AppEmbed.js +106 -18
  110. package/lib/loop/embed/element.d.ts +5 -0
  111. package/lib/loop/embed/element.js +84 -5
  112. package/lib/loop/embed/embedConfig.d.ts +8 -0
  113. package/lib/loop/embed/embedConfig.js +18 -0
  114. package/lib/loop/embed/embedSession.d.ts +14 -0
  115. package/lib/loop/embed/embedSession.js +34 -0
  116. package/lib/loop/embed/hostBridge.d.ts +50 -3
  117. package/lib/loop/embed/hostBridge.js +22 -2
  118. package/lib/loop/index.d.ts +4 -3
  119. package/lib/loop/index.js +4 -3
  120. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  121. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  122. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  123. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  124. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  125. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  126. package/lib/loop/plugins/app-components/index.js +56 -0
  127. package/lib/loop/plugins/app-elements/AppElements.d.ts +27 -0
  128. package/lib/loop/plugins/app-elements/AppElements.js +79 -0
  129. package/lib/loop/plugins/app-elements/index.d.ts +20 -0
  130. package/lib/loop/plugins/app-elements/index.js +43 -0
  131. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  132. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  133. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  134. package/lib/loop/plugins/app-page/appPageModel.js +122 -4
  135. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  136. package/lib/loop/plugins/app-page/index.js +1 -0
  137. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  138. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  139. package/lib/loop/plugins/assistant/index.js +8 -6
  140. package/lib/loop/plugins/chat/ChatView.js +16 -6
  141. package/lib/loop/plugins/chat/index.d.ts +1 -1
  142. package/lib/loop/plugins/chat/index.js +2 -1
  143. package/lib/models/PageTag.d.ts +1 -1
  144. package/lib/protocols/BrowserAgentAdapter.d.ts +8 -6
  145. package/lib/protocols/BrowserAgentAdapter.js +13 -10
  146. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  147. package/lib/runtimes/browser/protocol.d.ts +1 -1
  148. package/lib/specs/actions.js +78 -1
  149. package/lib/specs/apps.d.ts +5 -0
  150. package/lib/specs/apps.js +1518 -216
  151. package/lib/specs/appspecSchema.js +630 -3
  152. package/lib/specs/teams/teams.d.ts +3 -0
  153. package/lib/specs/teams/teams.js +249 -0
  154. package/lib/specs/uiPlugins.js +6 -1
  155. package/lib/types/agentspecs.d.ts +200 -1
  156. package/lib/types/chat.d.ts +6 -5
  157. package/lib/types/teams.d.ts +13 -2
  158. package/package.json +2 -2
  159. package/scripts/codegen/generate_apps.py +20 -2
  160. package/scripts/codegen/generate_teams.py +15 -2
  161. package/lib/components/inspector/JsonTree.d.ts +0 -24
  162. package/lib/components/inspector/JsonTree.js +0 -89
  163. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  164. package/lib/components/inspector/a2aInspect.js +0 -478
  165. package/lib/components/inspector/agentInspector.d.ts +0 -179
  166. package/lib/components/inspector/agentInspector.js +0 -267
  167. package/lib/components/inspector/chatInspect.d.ts +0 -38
  168. package/lib/components/inspector/chatInspect.js +0 -65
@@ -3,8 +3,11 @@
3
3
  * Distributed under the terms of the Modified BSD License.
4
4
  */
5
5
  import { BEHAVIOURS } from './rules';
6
+ import { DEFAULT_MAX_FILES, DEFAULT_UPLOAD_MB } from './uploads';
6
7
  /** The version of the spec itself. */
7
8
  export const APP_SCHEMA = 'loop.app/v1';
9
+ /** The language an application's own words are in, unless it says (LOOP P-26). */
10
+ export const DEFAULT_LANGUAGE = 'en';
8
11
  /** The face of an application that has not chosen one. */
9
12
  export const DEFAULT_EMOJI = '\u{1F440}';
10
13
  export const APP_KINDS = ['chat', 'widget', 'decision', 'worker'];
@@ -124,6 +127,9 @@ export function emptyAppspec(kind = 'chat') {
124
127
  starters: [],
125
128
  commands: [],
126
129
  modes: [],
130
+ profiles: [],
131
+ language: DEFAULT_LANGUAGE,
132
+ translations: {},
127
133
  components: [],
128
134
  outputs: [],
129
135
  },
@@ -170,6 +176,7 @@ const KNOWN_KEYS = [
170
176
  'model',
171
177
  'skills',
172
178
  'backend_tools',
179
+ 'tools',
173
180
  'context',
174
181
  'contents',
175
182
  'connections',
@@ -193,6 +200,19 @@ const KNOWN_KEYS = [
193
200
  'avatar',
194
201
  'banner',
195
202
  ];
203
+ /** Where a check of its code runs (LOOP P-06). */
204
+ const CHECK_STAGES = ['answer', 'tool_call'];
205
+ /** A tool of its own, written in its code (LOOP P-06); a wrong one is said by the checks. */
206
+ function parseTool(data) {
207
+ return {
208
+ name: text(data.name),
209
+ description: text(data.description),
210
+ parameters: isData(data.parameters)
211
+ ? data.parameters
212
+ : { ...NO_HOST_PARAMETERS },
213
+ does: texts(data.does),
214
+ };
215
+ }
196
216
  function parseConnection(data) {
197
217
  return {
198
218
  server: text(data.server),
@@ -243,6 +263,67 @@ function parseMode(data) {
243
263
  }
244
264
  return mode;
245
265
  }
266
+ /** A starter, its category said only when it has one (LOOP P-20). */
267
+ function parseStarter(data) {
268
+ const starter = {
269
+ label: text(data.label),
270
+ message: text(data.message),
271
+ };
272
+ if (typeof data.category === 'string' && data.category) {
273
+ starter.category = data.category;
274
+ }
275
+ return starter;
276
+ }
277
+ /** One of several assistants in one application (LOOP P-20). */
278
+ function parseProfile(data) {
279
+ return {
280
+ id: text(data.id),
281
+ label: text(data.label),
282
+ description: text(data.description),
283
+ instructions: text(data.instructions),
284
+ model: typeof data.model === 'string' && data.model ? data.model : null,
285
+ starters: records(data.starters).map(parseStarter),
286
+ };
287
+ }
288
+ /** Words by key, what is not a word left out. */
289
+ function wordsBy(value) {
290
+ return Object.fromEntries(Object.entries(isData(value) ? value : {}).filter((entry) => typeof entry[1] === 'string'));
291
+ }
292
+ /** Records by key, what is not one left out. */
293
+ function recordsBy(value) {
294
+ return Object.fromEntries(Object.entries(isData(value) ? value : {}).filter((entry) => isData(entry[1])));
295
+ }
296
+ const mapValues = (value, map) => Object.fromEntries(Object.entries(value).map(([key, item]) => [key, map(item)]));
297
+ /** An application's words in one other language (LOOP P-26), every key present. */
298
+ function parseTranslation(data) {
299
+ return {
300
+ name: text(data.name),
301
+ description: text(data.description),
302
+ welcome: text(data.welcome),
303
+ starters: mapValues(recordsBy(data.starters), starter => ({
304
+ label: text(starter.label),
305
+ message: text(starter.message),
306
+ })),
307
+ categories: wordsBy(data.categories),
308
+ settings: mapValues(recordsBy(data.settings), field => ({
309
+ title: text(field.title),
310
+ description: text(field.description),
311
+ options: wordsBy(field.options),
312
+ })),
313
+ commands: wordsBy(data.commands),
314
+ modes: mapValues(recordsBy(data.modes), mode => ({
315
+ label: text(mode.label),
316
+ options: mapValues(recordsBy(mode.options), option => ({
317
+ label: text(option.label),
318
+ description: text(option.description),
319
+ })),
320
+ })),
321
+ profiles: mapValues(recordsBy(data.profiles), profile => ({
322
+ label: text(profile.label),
323
+ description: text(profile.description),
324
+ })),
325
+ };
326
+ }
246
327
  function parseSurface(data) {
247
328
  return {
248
329
  protocol: text(data.protocol, DEFAULT_PROTOCOL),
@@ -272,17 +353,124 @@ function parseVoice(data) {
272
353
  where: oneOf(voice.where, ['auto', 'device', 'server'], DEFAULT_VOICE.where),
273
354
  };
274
355
  }
356
+ /** What a person may send without being asked (P-21), as the spec says it. */
357
+ function parseUploads(data) {
358
+ return {
359
+ kinds: records(data.kinds).map(kind => ({
360
+ type: text(kind.type),
361
+ maxMb: numberOf(kind.max_mb, DEFAULT_UPLOAD_MB),
362
+ })),
363
+ maxFiles: numberOf(data.max_files, DEFAULT_MAX_FILES),
364
+ };
365
+ }
366
+ /** What a page's output may be drawn with (LOOP P-05), and the property of each that shows its value. */
367
+ export const PAGE_OUTPUT_SHOWS = {
368
+ Text: 'text',
369
+ Image: 'url',
370
+ Table: 'rows',
371
+ Chart: 'points',
372
+ };
373
+ /** The height of a component its developer wrote (P-17), unless it says. */
374
+ export const DEFAULT_CUSTOM_HEIGHT = 240;
375
+ /**
376
+ * A component its developer wrote (LOOP P-17), as written: its schema and
377
+ * example kept whole, so that the checks say what is wrong with them.
378
+ */
379
+ function parseCustomComponent(data) {
380
+ const component = {
381
+ name: text(data.name),
382
+ description: text(data.description),
383
+ props: isData(data.props)
384
+ ? JSON.parse(JSON.stringify(data.props))
385
+ : { type: 'object', properties: {} },
386
+ shows: texts(data.shows),
387
+ sends: texts(data.sends),
388
+ source: text(data.source),
389
+ integrity: text(data.integrity),
390
+ height: numberOf(data.height, DEFAULT_CUSTOM_HEIGHT),
391
+ };
392
+ if (isData(data.example)) {
393
+ component.example = JSON.parse(JSON.stringify(data.example));
394
+ }
395
+ return component;
396
+ }
397
+ function dumpCustomComponent(component) {
398
+ const written = new Writer()
399
+ .text('name', component.name, '\u0000')
400
+ .text('description', component.description, '\u0000');
401
+ written.data.props = JSON.parse(JSON.stringify(component.props));
402
+ written
403
+ .list('shows', component.shows)
404
+ .list('sends', component.sends)
405
+ .text('source', component.source, '\u0000')
406
+ .text('integrity', component.integrity)
407
+ .value('height', component.height, DEFAULT_CUSTOM_HEIGHT);
408
+ if (component.example) {
409
+ written.data.example = JSON.parse(JSON.stringify(component.example));
410
+ }
411
+ return written.data;
412
+ }
413
+ /**
414
+ * The property a page's output fills with its value (P-05): a catalog
415
+ * component's (`PAGE_OUTPUT_SHOWS`), or what a component of the
416
+ * application's own shows first (P-17). Undefined for any other.
417
+ */
418
+ export function pageOutputShows(component, own = []) {
419
+ if (component in PAGE_OUTPUT_SHOWS) {
420
+ return PAGE_OUTPUT_SHOWS[component];
421
+ }
422
+ return own.find(each => each.name === component)?.shows[0];
423
+ }
424
+ /**
425
+ * A widget's page written in its code (P-05), as written: its inputs a form
426
+ * kept whole, an output drawn with another component kept as written so that
427
+ * the checks say it.
428
+ */
429
+ function parsePage(data) {
430
+ const page = {
431
+ function: text(data.function),
432
+ inputs: (isData(data.inputs)
433
+ ? JSON.parse(JSON.stringify(data.inputs))
434
+ : {}),
435
+ outputs: records(data.outputs).map(output => ({
436
+ name: text(output.name),
437
+ title: text(output.title),
438
+ component: text(output.component, 'Text'),
439
+ props: isData(output.props)
440
+ ? JSON.parse(JSON.stringify(output.props))
441
+ : {},
442
+ })),
443
+ live: data.live !== false,
444
+ };
445
+ if (isData(data.inputs_ui)) {
446
+ page.inputsUi = JSON.parse(JSON.stringify(data.inputs_ui));
447
+ }
448
+ return page;
449
+ }
450
+ function dumpPage(page) {
451
+ const written = new Writer().text('function', page.function, '\u0000');
452
+ written.data.inputs = JSON.parse(JSON.stringify(page.inputs));
453
+ if (page.inputsUi) {
454
+ written.data.inputs_ui = { ...page.inputsUi };
455
+ }
456
+ written.list('outputs', page.outputs.map(output => new Writer()
457
+ .text('name', output.name, '\u0000')
458
+ .text('title', output.title)
459
+ .text('component', output.component, 'Text')
460
+ .part('props', { ...output.props }).data));
461
+ return written.value('live', page.live, true).data;
462
+ }
275
463
  function parseInterface(data, kind) {
276
464
  const parsed = {
277
465
  layout: oneOf(data.layout, ['chat', 'page', 'split'], DEFAULT_LAYOUTS[kind]),
278
466
  accent: oneOf(data.accent, APP_ACCENTS, 'green'),
279
467
  welcome: text(data.welcome),
280
- starters: records(data.starters).map(starter => ({
281
- label: text(starter.label),
282
- message: text(starter.message),
283
- })),
468
+ starters: records(data.starters).map(parseStarter),
284
469
  commands: records(data.commands).map(parseCommand),
285
470
  modes: records(data.modes).map(parseMode),
471
+ profiles: records(data.profiles).map(parseProfile),
472
+ language: text(data.language, DEFAULT_LANGUAGE),
473
+ translations: mapValues(recordsBy(data.translations), parseTranslation),
286
474
  components: texts(data.components),
287
475
  voice: parseVoice(data.voice),
288
476
  outputs: texts(data.outputs),
@@ -293,9 +481,24 @@ function parseInterface(data, kind) {
293
481
  if (isData(data.settings)) {
294
482
  parsed.settings = JSON.parse(JSON.stringify(data.settings));
295
483
  }
484
+ // How its settings' fields are drawn (P-20), kept as written: a wrong
485
+ // widget is said by the checks.
486
+ if (isData(data.settings_ui)) {
487
+ parsed.settingsUi = JSON.parse(JSON.stringify(data.settings_ui));
488
+ }
489
+ if (isData(data.uploads)) {
490
+ parsed.uploads = parseUploads(data.uploads);
491
+ }
492
+ // Components its developer wrote (P-17): absent when it has none.
493
+ if (Array.isArray(data.custom_components)) {
494
+ parsed.customComponents = records(data.custom_components).map(parseCustomComponent);
495
+ }
296
496
  if (isData(data.surface)) {
297
497
  parsed.surface = parseSurface(data.surface);
298
498
  }
499
+ if (isData(data.page)) {
500
+ parsed.page = parsePage(data.page);
501
+ }
299
502
  if (isAssistantCharacterId(data.assistant)) {
300
503
  parsed.assistant = data.assistant;
301
504
  }
@@ -353,6 +556,11 @@ function parseDeployment(data) {
353
556
  ? {
354
557
  host: {
355
558
  context: texts(data.embedded.host.context),
559
+ // Kept as written when said: a value that is none is refused
560
+ // by the checks, never read as `claimed` (D-21).
561
+ ...(data.embedded.host.user !== undefined
562
+ ? { user: text(data.embedded.host.user) }
563
+ : {}),
356
564
  functions: records(data.embedded.host.functions).map(fn => ({
357
565
  name: text(fn.name),
358
566
  description: text(fn.description),
@@ -416,6 +624,12 @@ export function parseAppspec(document) {
416
624
  const verified = isData(tests.verified) ? tests.verified : {};
417
625
  const record = isData(data.record) ? data.record : {};
418
626
  const checks = isData(data.checks) ? data.checks : {};
627
+ // A check of its code runs on an answer or a tool call (LOOP P-06).
628
+ for (const check of records(checks.code)) {
629
+ if (!CHECK_STAGES.includes(check.on)) {
630
+ problems.push(`The check \`${text(check.name)}\` of its code runs on answer or tool_call, not \`${String(check.on)}\`.`);
631
+ }
632
+ }
419
633
  const keepFor = text(record.keep_for, DEFAULT_KEEP_FOR);
420
634
  if (retentionDays(keepFor) === 0) {
421
635
  problems.push(`Cannot read the retention \`${keepFor}\`: write 90_days, 18_months or 1_years.`);
@@ -435,6 +649,7 @@ export function parseAppspec(document) {
435
649
  model: text(data.model),
436
650
  skills: texts(data.skills),
437
651
  backendTools: texts(data.backend_tools),
652
+ tools: records(data.tools).map(parseTool),
438
653
  context: texts(data.context),
439
654
  contents: texts(data.contents),
440
655
  connections: records(data.connections).map(parseConnection),
@@ -457,6 +672,8 @@ export function parseAppspec(document) {
457
672
  cases: records(tests.cases).map(testCase => ({
458
673
  ask: text(testCase.ask),
459
674
  expect: text(testCase.expect),
675
+ // Decided by its code (LOOP P-06), when it names the function.
676
+ ...(text(testCase.code) ? { code: text(testCase.code) } : {}),
460
677
  })),
461
678
  verified: {
462
679
  live: texts(verified.live),
@@ -476,6 +693,11 @@ export function parseAppspec(document) {
476
693
  guards: texts(checks.guards),
477
694
  gates: texts(checks.gates),
478
695
  track: text(checks.track),
696
+ code: records(checks.code).map(check => ({
697
+ name: text(check.name),
698
+ on: oneOf(check.on, CHECK_STAGES, 'answer'),
699
+ description: text(check.description),
700
+ })),
479
701
  },
480
702
  deployment: parseDeployment(isData(data.deployment) ? data.deployment : {}),
481
703
  goal: text(data.goal),
@@ -557,16 +779,44 @@ function dumpSurface(surface) {
557
779
  .text('composed_by', surface.composedBy)
558
780
  .text('composed_at', surface.composedAt).data;
559
781
  }
782
+ function dumpStarter(starter) {
783
+ return new Writer()
784
+ .text('label', starter.label, '\u0000')
785
+ .text('message', starter.message, '\u0000')
786
+ .text('category', starter.category ?? '').data;
787
+ }
788
+ /** Words by key, what is empty left out. */
789
+ const saidWords = (words) => Object.fromEntries(Object.entries(words).filter(([, said]) => said !== ''));
790
+ /** One translation as it is written (P-26): only what it says. */
791
+ function dumpTranslation(words) {
792
+ const parts = (value, dump) => Object.fromEntries(Object.entries(value).map(([key, item]) => [key, dump(item)]));
793
+ return new Writer()
794
+ .text('name', words.name)
795
+ .text('description', words.description)
796
+ .text('welcome', words.welcome)
797
+ .part('starters', parts(words.starters, starter => new Writer()
798
+ .text('label', starter.label)
799
+ .text('message', starter.message).data))
800
+ .part('categories', saidWords(words.categories))
801
+ .part('settings', parts(words.settings, field => new Writer()
802
+ .text('title', field.title)
803
+ .text('description', field.description)
804
+ .part('options', saidWords(field.options)).data))
805
+ .part('commands', saidWords(words.commands))
806
+ .part('modes', parts(words.modes, mode => new Writer().text('label', mode.label).part('options', parts(mode.options, option => new Writer()
807
+ .text('label', option.label)
808
+ .text('description', option.description).data)).data))
809
+ .part('profiles', parts(words.profiles, profile => new Writer()
810
+ .text('label', profile.label)
811
+ .text('description', profile.description).data)).data;
812
+ }
560
813
  function dumpInterface(spec, kind) {
561
814
  const writer = new Writer()
562
815
  // A layout is written when it is not the one its kind starts with.
563
816
  .value('layout', spec.layout, DEFAULT_LAYOUTS[kind])
564
817
  .value('accent', spec.accent, 'green')
565
818
  .text('welcome', spec.welcome)
566
- .list('starters', spec.starters.map(starter => ({
567
- label: starter.label,
568
- message: starter.message,
569
- })))
819
+ .list('starters', spec.starters.map(dumpStarter))
570
820
  .list('commands', spec.commands.map(command => ({
571
821
  name: command.name,
572
822
  description: command.description,
@@ -587,11 +837,37 @@ function dumpInterface(spec, kind) {
587
837
  }
588
838
  return written.data;
589
839
  }))
590
- .part('settings', spec.settings ? { ...spec.settings } : {})
591
- .list('components', spec.components);
840
+ .list('profiles', (spec.profiles ?? []).map(profile => new Writer()
841
+ .text('id', profile.id, '\u0000')
842
+ .text('label', profile.label, '\u0000')
843
+ .text('description', profile.description)
844
+ .text('instructions', profile.instructions)
845
+ .text('model', profile.model ?? '')
846
+ .list('starters', profile.starters.map(dumpStarter)).data))
847
+ .part('settings', spec.settings ? { ...spec.settings } : {});
848
+ // How its settings' fields are drawn (P-20): written when said.
849
+ if (spec.settingsUi) {
850
+ writer.data.settings_ui = { ...spec.settingsUi };
851
+ }
852
+ writer
853
+ .text('language', spec.language ?? DEFAULT_LANGUAGE, DEFAULT_LANGUAGE)
854
+ .part('translations', mapValues(spec.translations ?? {}, dumpTranslation));
855
+ // What a person may send without being asked (P-21): written when said.
856
+ if (spec.uploads) {
857
+ writer.data.uploads = new Writer()
858
+ .list('kinds', spec.uploads.kinds.map(kind => new Writer()
859
+ .text('type', kind.type, '\u0000')
860
+ .value('max_mb', kind.maxMb, DEFAULT_UPLOAD_MB).data))
861
+ .value('max_files', spec.uploads.maxFiles, DEFAULT_MAX_FILES).data;
862
+ }
863
+ writer.list('components', spec.components);
864
+ writer.list('custom_components', (spec.customComponents ?? []).map(dumpCustomComponent));
592
865
  if (spec.surface) {
593
866
  writer.data.surface = dumpSurface(spec.surface);
594
867
  }
868
+ if (spec.page) {
869
+ writer.data.page = dumpPage(spec.page);
870
+ }
595
871
  if (spec.assistant) {
596
872
  writer.data.assistant = spec.assistant;
597
873
  }
@@ -664,6 +940,16 @@ export function dumpAppspec(app) {
664
940
  .text('model', app.model)
665
941
  .list('skills', app.skills)
666
942
  .list('backend_tools', app.backendTools)
943
+ .list('tools', (app.tools ?? []).map(tool => ({
944
+ name: tool.name,
945
+ description: tool.description,
946
+ // No arguments is what it is unless said.
947
+ ...(JSON.stringify(tool.parameters) ===
948
+ JSON.stringify(NO_HOST_PARAMETERS)
949
+ ? {}
950
+ : { parameters: tool.parameters }),
951
+ does: tool.does,
952
+ })))
667
953
  .list('context', app.context)
668
954
  .list('contents', app.contents)
669
955
  .list('connections', app.connections.map(connection => new Writer()
@@ -692,6 +978,7 @@ export function dumpAppspec(app) {
692
978
  .list('cases', app.tests.cases.map(testCase => ({
693
979
  ask: testCase.ask,
694
980
  expect: testCase.expect,
981
+ ...(testCase.code ? { code: testCase.code } : {}),
695
982
  })))
696
983
  .part('verified', new Writer()
697
984
  .list('live', app.tests.verified.live)
@@ -704,7 +991,12 @@ export function dumpAppspec(app) {
704
991
  .part('checks', new Writer()
705
992
  .list('guards', app.checks.guards)
706
993
  .list('gates', app.checks.gates)
707
- .text('track', app.checks.track).data)
994
+ .text('track', app.checks.track)
995
+ .list('code', (app.checks.code ?? []).map(check => ({
996
+ name: check.name,
997
+ on: check.on,
998
+ description: check.description,
999
+ }))).data)
708
1000
  .part('deployment', new Writer()
709
1001
  .present('hosted', app.deployment.hosted &&
710
1002
  new Writer()
@@ -726,7 +1018,8 @@ export function dumpAppspec(app) {
726
1018
  JSON.stringify(NO_HOST_PARAMETERS)
727
1019
  ? {}
728
1020
  : { parameters: fn.parameters }),
729
- }))).data).data).data)
1021
+ })))
1022
+ .value('user', app.deployment.embedded.host.user ?? 'claimed', 'claimed').data).data).data)
730
1023
  .text('goal', app.goal)
731
1024
  .list('triggers', app.triggers.map(trigger => new Writer()
732
1025
  .text('type', trigger.type, '\u0000')
@@ -14,6 +14,20 @@ export interface AppCheck {
14
14
  * UI plugins, by the name a surface gives it.
15
15
  */
16
16
  export declare const componentNamed: (name: string) => ComponentSpec | undefined;
17
+ /**
18
+ * What its code declares that agentspecs refuses (LOOP P-06): its tools,
19
+ * its checks and the tests it decides, each by a Python name, each name
20
+ * once; a tool says what it does, by class, each class once.
21
+ */
22
+ export declare function ownCodeProblems(app: AppSpec): string[];
23
+ /**
24
+ * What agentspecs refuses of a widget's page written in its code (LOOP
25
+ * P-05): a page that is not a widget's, inputs that are not a form, outputs
26
+ * of one name twice, drawn with another component, saying in their `props`
27
+ * what their value fills or lacking what their component needs, and an input
28
+ * named as a setting — both are on the page at `/inputs/<name>`.
29
+ */
30
+ export declare function pageProblems(app: AppSpec): string[];
17
31
  /** How an organization's own context is named: never a catalogue id (agentspecs' `ORGANIZATION_FRAME_PREFIX`). */
18
32
  /**
19
33
  * What stops a Form block from asking (LOOP C-16), in agentspecs'