@datalayer/agent-runtimes 1.3.84 → 1.3.86

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 (162) 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 +14 -11
  17. package/lib/chat/display/EmptyState.js +3 -1
  18. package/lib/chat/index.d.ts +2 -0
  19. package/lib/chat/index.js +3 -0
  20. package/lib/chat/messages/ChatMessages.js +3 -1
  21. package/lib/chat/messages/TurnFooter.js +3 -1
  22. package/lib/chat/prompt/InputPrompt.js +3 -1
  23. package/lib/chat/prompt/InputPromptLexical.js +3 -1
  24. package/lib/chat/prompt/footer/InPromptFooter.js +3 -1
  25. package/lib/chat/tools/ToolApprovalBanner.js +5 -3
  26. package/lib/chat/tools/ToolApprovalDialog.js +5 -3
  27. package/lib/chat/tools/ToolCallDisplay.js +29 -25
  28. package/lib/chat/words.d.ts +102 -0
  29. package/lib/chat/words.js +460 -0
  30. package/lib/components/a2ui/custom/index.d.ts +39 -0
  31. package/lib/components/a2ui/custom/index.js +95 -0
  32. package/lib/components/a2ui/custom/sandbox.d.ts +73 -0
  33. package/lib/components/a2ui/custom/sandbox.js +188 -0
  34. package/lib/components/a2ui/datalayer/index.d.ts +1 -1
  35. package/lib/components/a2ui/datalayer/index.js +1 -1
  36. package/lib/components/a2ui/datalayer/schema.d.ts +10 -0
  37. package/lib/components/a2ui/datalayer/schema.js +12 -1
  38. package/lib/components/a2ui/index.d.ts +1 -0
  39. package/lib/components/a2ui/index.js +1 -0
  40. package/lib/components/a2ui/visibleWhen.d.ts +12 -1
  41. package/lib/components/a2ui/visibleWhen.js +24 -3
  42. package/lib/components/inspector/AgentInspector.d.ts +25 -44
  43. package/lib/components/inspector/AgentInspector.js +27 -215
  44. package/lib/components/inspector/AgentInspectorDialog.d.ts +6 -6
  45. package/lib/components/inspector/AgentInspectorDialog.js +2 -2
  46. package/lib/components/inspector/a2aSpans.d.ts +51 -0
  47. package/lib/components/inspector/a2aSpans.js +373 -0
  48. package/lib/components/inspector/agentSpans.d.ts +120 -0
  49. package/lib/components/inspector/agentSpans.js +253 -0
  50. package/lib/components/inspector/chatSpans.d.ts +36 -0
  51. package/lib/components/inspector/chatSpans.js +50 -0
  52. package/lib/components/inspector/classify.d.ts +11 -8
  53. package/lib/components/inspector/classify.js +23 -24
  54. package/lib/components/inspector/index.d.ts +7 -7
  55. package/lib/components/inspector/index.js +7 -7
  56. package/lib/components/teams/A2ATeamGraph.d.ts +117 -22
  57. package/lib/components/teams/A2ATeamGraph.js +217 -97
  58. package/lib/components/teams/SceneTranscript.d.ts +29 -0
  59. package/lib/components/teams/SceneTranscript.js +54 -0
  60. package/lib/components/teams/SceneView.d.ts +41 -0
  61. package/lib/components/teams/SceneView.js +28 -0
  62. package/lib/components/teams/index.d.ts +4 -0
  63. package/lib/components/teams/index.js +4 -0
  64. package/lib/components/teams/sceneStage.d.ts +120 -0
  65. package/lib/components/teams/sceneStage.js +170 -0
  66. package/lib/components/teams/sceneTranscript.d.ts +74 -0
  67. package/lib/components/teams/sceneTranscript.js +294 -0
  68. package/lib/components/teams/teamConnections.d.ts +2 -2
  69. package/lib/components/teams/teamConnections.js +3 -3
  70. package/lib/components/teams/useA2ATeam.d.ts +94 -22
  71. package/lib/components/teams/useA2ATeam.js +356 -140
  72. package/lib/examples/AgentA2ATeamExample.d.ts +10 -9
  73. package/lib/examples/AgentA2ATeamExample.js +70 -60
  74. package/lib/examples/AssistantExample.js +2 -2
  75. package/lib/examples/ScenesExample.d.ts +26 -0
  76. package/lib/examples/ScenesExample.js +241 -0
  77. package/lib/examples/example-selector.js +1 -0
  78. package/lib/examples/exampleGroups.js +3 -0
  79. package/lib/examples/index.d.ts +1 -0
  80. package/lib/examples/index.js +1 -0
  81. package/lib/examples/main.js +4 -0
  82. package/lib/loop/apps/AppComposer.d.ts +2 -1
  83. package/lib/loop/apps/AppComposer.js +6 -3
  84. package/lib/loop/apps/AppRenderer.d.ts +2 -1
  85. package/lib/loop/apps/AppRenderer.js +34 -25
  86. package/lib/loop/apps/appspec.d.ts +11 -1
  87. package/lib/loop/apps/appspec.js +115 -1
  88. package/lib/loop/apps/checks.d.ts +8 -0
  89. package/lib/loop/apps/checks.js +106 -4
  90. package/lib/loop/apps/customComponents.d.ts +41 -0
  91. package/lib/loop/apps/customComponents.js +260 -0
  92. package/lib/loop/apps/hostTools.d.ts +8 -1
  93. package/lib/loop/apps/hostTools.js +9 -0
  94. package/lib/loop/apps/index.d.ts +1 -0
  95. package/lib/loop/apps/index.js +1 -0
  96. package/lib/loop/core/a2uiComponents.d.ts +10 -0
  97. package/lib/loop/core/a2uiComponents.js +15 -0
  98. package/lib/loop/core/index.d.ts +1 -0
  99. package/lib/loop/core/index.js +3 -0
  100. package/lib/loop/embed/AppEmbed.d.ts +11 -3
  101. package/lib/loop/embed/AppEmbed.js +42 -16
  102. package/lib/loop/embed/element.d.ts +5 -0
  103. package/lib/loop/embed/element.js +61 -5
  104. package/lib/loop/embed/embedConfig.d.ts +8 -0
  105. package/lib/loop/embed/embedConfig.js +18 -0
  106. package/lib/loop/embed/hostBridge.d.ts +28 -2
  107. package/lib/loop/embed/hostBridge.js +22 -2
  108. package/lib/loop/index.d.ts +4 -3
  109. package/lib/loop/index.js +4 -3
  110. package/lib/loop/plugins/a2ui-surface/InlineSurface.d.ts +7 -1
  111. package/lib/loop/plugins/a2ui-surface/InlineSurface.js +7 -2
  112. package/lib/loop/plugins/a2ui-surface/SurfaceToolResult.js +4 -1
  113. package/lib/loop/plugins/a2ui-surface/ownComponents.d.ts +6 -0
  114. package/lib/loop/plugins/a2ui-surface/ownComponents.js +30 -0
  115. package/lib/loop/plugins/app-components/index.d.ts +26 -0
  116. package/lib/loop/plugins/app-components/index.js +56 -0
  117. package/lib/loop/plugins/app-elements/AppElements.js +4 -1
  118. package/lib/loop/plugins/app-page/AppPage.d.ts +9 -0
  119. package/lib/loop/plugins/app-page/AppPage.js +106 -8
  120. package/lib/loop/plugins/app-page/appPageModel.d.ts +17 -1
  121. package/lib/loop/plugins/app-page/appPageModel.js +120 -4
  122. package/lib/loop/plugins/app-page/index.d.ts +1 -0
  123. package/lib/loop/plugins/app-page/index.js +1 -0
  124. package/lib/loop/plugins/app-page/pageRun.d.ts +46 -0
  125. package/lib/loop/plugins/app-page/pageRun.js +111 -0
  126. package/lib/loop/plugins/assistant/index.js +8 -6
  127. package/lib/loop/plugins/chat/ChatView.js +6 -4
  128. package/lib/loop/plugins/chat/index.d.ts +1 -1
  129. package/lib/loop/plugins/chat/index.js +2 -1
  130. package/lib/models/PageTag.d.ts +1 -1
  131. package/lib/runtimes/browser/a2aPeer.d.ts +2 -1
  132. package/lib/specs/actions.js +78 -1
  133. package/lib/specs/apps.d.ts +5 -0
  134. package/lib/specs/apps.js +1384 -231
  135. package/lib/specs/appspecSchema.js +191 -1
  136. package/lib/specs/index.d.ts +1 -0
  137. package/lib/specs/index.js +1 -0
  138. package/lib/specs/mcpServers.js +8 -2
  139. package/lib/specs/scenes.d.ts +21 -0
  140. package/lib/specs/scenes.js +1191 -0
  141. package/lib/specs/teams/teams.d.ts +3 -0
  142. package/lib/specs/teams/teams.js +249 -0
  143. package/lib/types/agentspecs.d.ts +70 -0
  144. package/lib/types/chat.d.ts +6 -5
  145. package/lib/types/index.d.ts +1 -0
  146. package/lib/types/index.js +1 -0
  147. package/lib/types/scenes.d.ts +207 -0
  148. package/lib/types/scenes.js +5 -0
  149. package/lib/types/teams.d.ts +13 -2
  150. package/package.json +4 -4
  151. package/scripts/codegen/generate_agents.py +1 -0
  152. package/scripts/codegen/generate_apps.py +4 -2
  153. package/scripts/codegen/generate_scenes.py +301 -0
  154. package/scripts/codegen/generate_teams.py +15 -2
  155. package/lib/components/inspector/JsonTree.d.ts +0 -24
  156. package/lib/components/inspector/JsonTree.js +0 -89
  157. package/lib/components/inspector/a2aInspect.d.ts +0 -56
  158. package/lib/components/inspector/a2aInspect.js +0 -478
  159. package/lib/components/inspector/agentInspector.d.ts +0 -179
  160. package/lib/components/inspector/agentInspector.js +0 -267
  161. package/lib/components/inspector/chatInspect.d.ts +0 -38
  162. package/lib/components/inspector/chatInspect.js +0 -65
@@ -58,6 +58,7 @@ import { LoopEmbed } from '../embed/LoopEmbed';
58
58
  import { dumpAppspec } from './appspec';
59
59
  import { defineAppActivityPlugin } from '../plugins/app-activity';
60
60
  import { AppElementsPlugin } from '../plugins/app-elements';
61
+ import { defineAppComponentsPlugin, hasCustomComponents, } from '../plugins/app-components';
61
62
  import { defineAppComputerPlugin } from '../plugins/app-computer';
62
63
  import { defineAppRulesPlugin } from '../plugins/app-rules';
63
64
  import { defineAppComposerPlugin, needsAppComposer, startersAsOpeners, } from './AppComposer';
@@ -67,6 +68,7 @@ import { defineAppFeedbackPlugin } from './AppFeedback';
67
68
  import { defineAppKeptPlugin } from './AppKept';
68
69
  import { keepsFeedback } from './feedback';
69
70
  import { keptBeforeFirstMessage } from './kept';
71
+ import { ChatLanguage } from '../../chat/ChatLanguage';
70
72
  /** The id of an agent or a Cog, without its version. */
71
73
  export const agentIdOf = (app) => {
72
74
  const reference = app.agent || app.team;
@@ -167,7 +169,8 @@ export function appLayoutOptions(app) {
167
169
  * whose contributions are the blocks it may draw (R-01b); a thumb and a
168
170
  * comment on each answer when its record keeps feedback (V-18); its
169
171
  * commands in the composer's `/` menu and its modes beside it (P-19); the
170
- * side panel and the pages its code opens (`app-elements`, P-18); what it
172
+ * side panel and the pages its code opens (`app-elements`, P-18); the
173
+ * components its developer wrote (`app-components`, P-17); what it
171
174
  * keeps and for how long, under its prompt before the first message, when
172
175
  * `kept` says it (R-31); with
173
176
  * `sidebar`, its rules and approvals card, its activity feed and its
@@ -203,6 +206,9 @@ export function appPreset(app, options = {}) {
203
206
  plugins: [
204
207
  defineAppPlugin(app, shown),
205
208
  ...(withPage ? [defineAppPagePlugin(shown), ...blocks] : []),
209
+ // The components its developer wrote (LOOP P-17): blocks of its page
210
+ // and renderers of its page, its elements and its answers — its own.
211
+ ...(hasCustomComponents(app) ? [defineAppComponentsPlugin(app)] : []),
206
212
  // Its commands in the composer's `/` menu, its modes and its profiles
207
213
  // beside the composer (LOOP P-19, P-20) and what a person may attach
208
214
  // there (P-21): only for an application that has some.
@@ -385,31 +391,34 @@ export function AppRenderer({ app, plugins = NO_PLUGINS, instance, onPresence, o
385
391
  if ('problem' in preset) {
386
392
  return (_jsx("div", { role: "status", style: { padding: 16 }, children: preset.problem }));
387
393
  }
388
- return (_jsx(LoopEmbed
389
- // The application's own agent: created under its id, so that its
390
- // spec is applied to an agent of its own, never to the one it extends.
391
- , {
394
+ return (
395
+ // The chat's own words in the person's language too (P-26): the host's,
396
+ // else the browser's.
397
+ _jsx(ChatLanguage, { language: language, children: _jsx(LoopEmbed
392
398
  // The application's own agent: created under its id, so that its
393
399
  // spec is applied to an agent of its own, never to the one it extends.
394
- agentId: app.id, ...preset.workspace, ...(frame
395
- ? {
396
- frameTitle: [app.emoji, shown.name].filter(Boolean).join(' '),
397
- }
398
- : {}),
399
- // Its rules, activity and computer on a rail, one at a time (T-07).
400
- sidebarRail: sidebar || computer, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
401
- // Where an application runs is decided by its host — the Studio's
402
- // Preview, the hosted page — not offered to its user.
403
- targetFixed: true, ...(named ? { themeVariant: named.variant } : {}), ...(namedMode ? { colorMode: namedMode } : {}), themeOverrides: themeOverrides,
404
- // Its own face, name and welcome in the chat (T-08); no counters: a
405
- // person using an application is not asking about tokens.
406
- presence: {
407
- name: shown.name,
408
- face: app.emoji,
409
- welcome: shown.interface.welcome || shown.description,
410
- paused,
411
- onPresence,
412
- onSaying,
413
- }, showTokenUsage: false, datalayerCreatePayload: datalayerCreatePayload, ...embed, plugins: allPlugins }));
400
+ , {
401
+ // The application's own agent: created under its id, so that its
402
+ // spec is applied to an agent of its own, never to the one it extends.
403
+ agentId: app.id, ...preset.workspace, ...(frame
404
+ ? {
405
+ frameTitle: [app.emoji, shown.name].filter(Boolean).join(' '),
406
+ }
407
+ : {}),
408
+ // Its rules, activity and computer on a rail, one at a time (T-07).
409
+ sidebarRail: sidebar || computer, teamPicker: false, showAgentVariants: false, graph: false, pluginsPanel: false, datalayerAgentSpecId: DATALAYER_BOOTSTRAP_AGENTSPEC,
410
+ // Where an application runs is decided by its host — the Studio's
411
+ // Preview, the hosted page — not offered to its user.
412
+ targetFixed: true, ...(named ? { themeVariant: named.variant } : {}), ...(namedMode ? { colorMode: namedMode } : {}), themeOverrides: themeOverrides,
413
+ // Its own face, name and welcome in the chat (T-08); no counters: a
414
+ // person using an application is not asking about tokens.
415
+ presence: {
416
+ name: shown.name,
417
+ face: app.emoji,
418
+ welcome: shown.interface.welcome || shown.description,
419
+ paused,
420
+ onPresence,
421
+ onSaying,
422
+ }, showTokenUsage: false, datalayerCreatePayload: datalayerCreatePayload, ...embed, plugins: allPlugins }) }));
414
423
  }
415
424
  export default AppRenderer;
@@ -24,7 +24,7 @@
24
24
  *
25
25
  * @module loop/apps/appspec
26
26
  */
27
- import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, AppVoiceSpec, AppKind, AppLayout, AppSpec } from '../../types/agentspecs';
27
+ import type { AppAccent, AppThemeMode, AppThemeVariant, AppAssistantCharacter, AppEmbedMode, AppCustomComponentSpec, AppVoiceSpec, AppKind, AppLayout, AppPageOutputComponent, AppSpec } from '../../types/agentspecs';
28
28
  /** The version of the spec itself. */
29
29
  export declare const APP_SCHEMA = "loop.app/v1";
30
30
  /** The language an application's own words are in, unless it says (LOOP P-26). */
@@ -62,6 +62,16 @@ export declare function retentionDays(keepFor: string): number;
62
62
  export declare function emptyAppspec(kind?: AppKind): AppSpec;
63
63
  /** An application's voice (VOICE.md VO-41): off unless said. */
64
64
  export declare const DEFAULT_VOICE: AppVoiceSpec;
65
+ /** What a page's output may be drawn with (LOOP P-05), and the property of each that shows its value. */
66
+ export declare const PAGE_OUTPUT_SHOWS: Record<AppPageOutputComponent, string>;
67
+ /** The height of a component its developer wrote (P-17), unless it says. */
68
+ export declare const DEFAULT_CUSTOM_HEIGHT = 240;
69
+ /**
70
+ * The property a page's output fills with its value (P-05): a catalog
71
+ * component's (`PAGE_OUTPUT_SHOWS`), or what a component of the
72
+ * application's own shows first (P-17). Undefined for any other.
73
+ */
74
+ export declare function pageOutputShows(component: string, own?: readonly AppCustomComponentSpec[]): string | undefined;
65
75
  /** What reading a document gives: the application, and what the document got wrong. */
66
76
  export interface ParsedAppspec {
67
77
  app: AppSpec;
@@ -363,6 +363,103 @@ function parseUploads(data) {
363
363
  maxFiles: numberOf(data.max_files, DEFAULT_MAX_FILES),
364
364
  };
365
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
+ }
366
463
  function parseInterface(data, kind) {
367
464
  const parsed = {
368
465
  layout: oneOf(data.layout, ['chat', 'page', 'split'], DEFAULT_LAYOUTS[kind]),
@@ -392,9 +489,16 @@ function parseInterface(data, kind) {
392
489
  if (isData(data.uploads)) {
393
490
  parsed.uploads = parseUploads(data.uploads);
394
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
+ }
395
496
  if (isData(data.surface)) {
396
497
  parsed.surface = parseSurface(data.surface);
397
498
  }
499
+ if (isData(data.page)) {
500
+ parsed.page = parsePage(data.page);
501
+ }
398
502
  if (isAssistantCharacterId(data.assistant)) {
399
503
  parsed.assistant = data.assistant;
400
504
  }
@@ -452,6 +556,11 @@ function parseDeployment(data) {
452
556
  ? {
453
557
  host: {
454
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
+ : {}),
455
564
  functions: records(data.embedded.host.functions).map(fn => ({
456
565
  name: text(fn.name),
457
566
  description: text(fn.description),
@@ -752,9 +861,13 @@ function dumpInterface(spec, kind) {
752
861
  .value('max_files', spec.uploads.maxFiles, DEFAULT_MAX_FILES).data;
753
862
  }
754
863
  writer.list('components', spec.components);
864
+ writer.list('custom_components', (spec.customComponents ?? []).map(dumpCustomComponent));
755
865
  if (spec.surface) {
756
866
  writer.data.surface = dumpSurface(spec.surface);
757
867
  }
868
+ if (spec.page) {
869
+ writer.data.page = dumpPage(spec.page);
870
+ }
758
871
  if (spec.assistant) {
759
872
  writer.data.assistant = spec.assistant;
760
873
  }
@@ -905,7 +1018,8 @@ export function dumpAppspec(app) {
905
1018
  JSON.stringify(NO_HOST_PARAMETERS)
906
1019
  ? {}
907
1020
  : { parameters: fn.parameters }),
908
- }))).data).data).data)
1021
+ })))
1022
+ .value('user', app.deployment.embedded.host.user ?? 'claimed', 'claimed').data).data).data)
909
1023
  .text('goal', app.goal)
910
1024
  .list('triggers', app.triggers.map(trigger => new Writer()
911
1025
  .text('type', trigger.type, '\u0000')
@@ -20,6 +20,14 @@ export declare const componentNamed: (name: string) => ComponentSpec | undefined
20
20
  * once; a tool says what it does, by class, each class once.
21
21
  */
22
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[];
23
31
  /** How an organization's own context is named: never a catalogue id (agentspecs' `ORGANIZATION_FRAME_PREFIX`). */
24
32
  /**
25
33
  * What stops a Form block from asking (LOOP C-16), in agentspecs'
@@ -39,12 +39,13 @@ import { getTeamSpec } from '../../specs/teams';
39
39
  import { getBackendToolSpec } from '../../specs/backendTools';
40
40
  import { getTrack } from '../../specs/tracks';
41
41
  import { pluginsOffSetupNotes } from '../plugins/canvas-blocks';
42
- import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, parseAppspec, } from './appspec';
42
+ import { APP_THEME_MODES, APP_THEME_VARIANTS, isAssistantCharacterId, PAGE_OUTPUT_SHOWS, parseAppspec, } from './appspec';
43
43
  import { classesOf, splitRef, toolBehaviours } from './rules';
44
44
  import { COMMAND_INPUT, COMMAND_NAME, MODE_ID } from './composer';
45
- import { HOST_NAME, hostToolsOf } from './hostTools';
45
+ import { HOST_NAME, HOST_USERS, hostToolsOf } from './hostTools';
46
46
  import { MAX_UPLOAD_MB, UPLOAD_KIND } from './uploads';
47
47
  import { formUiProblems } from './settingsInputs';
48
+ import { customComponentOf, customComponentsProblems, customNodeRefused, customPropsRefused, } from './customComponents';
48
49
  import { translationProblems } from './language';
49
50
  export const NOT_READY = 'Not ready';
50
51
  export const NEEDS_ATTENTION = 'Needs attention';
@@ -129,6 +130,82 @@ export function ownCodeProblems(app) {
129
130
  if (new Set(decided).size !== decided.length) {
130
131
  problems.push('Two tests are decided by the same function of its code.');
131
132
  }
133
+ problems.push(...pageProblems(app));
134
+ // The components its developer wrote, reviewed as the catalog's (P-17).
135
+ problems.push(...customComponentsProblems(app));
136
+ return problems;
137
+ }
138
+ /**
139
+ * What agentspecs refuses of a widget's page written in its code (LOOP
140
+ * P-05): a page that is not a widget's, inputs that are not a form, outputs
141
+ * of one name twice, drawn with another component, saying in their `props`
142
+ * what their value fills or lacking what their component needs, and an input
143
+ * named as a setting — both are on the page at `/inputs/<name>`.
144
+ */
145
+ export function pageProblems(app) {
146
+ const page = app.interface.page;
147
+ if (!page) {
148
+ return [];
149
+ }
150
+ const problems = [];
151
+ if (app.kind !== 'widget') {
152
+ problems.push(`A page of inputs and outputs is a widget's: a ${app.kind} application has none. Remove \`interface.page\`, or make it a widget.`);
153
+ }
154
+ if (!CODE_NAME.test(page.function)) {
155
+ problems.push(`Cannot use “${page.function}” as the function of its page: letters, digits and \`_\`, as in Python.`);
156
+ }
157
+ problems.push(...formProblems({
158
+ id: 'page inputs',
159
+ schema: page.inputs,
160
+ ui: page.inputsUi,
161
+ }));
162
+ if (page.outputs.length === 0) {
163
+ problems.push('Its page shows one output at least.');
164
+ }
165
+ const names = page.outputs.map(output => output.name);
166
+ if (new Set(names).size !== names.length) {
167
+ problems.push('Two outputs of its page have the same name.');
168
+ }
169
+ for (const output of page.outputs) {
170
+ if (!CODE_NAME.test(output.name)) {
171
+ problems.push(`Cannot use “${output.name}” as an output's name: letters, digits and \`_\`, as in Python.`);
172
+ }
173
+ // A component its developer wrote (P-17): its value what it shows first.
174
+ const custom = output.component in PAGE_OUTPUT_SHOWS
175
+ ? undefined
176
+ : customComponentOf(app, output.component);
177
+ if (custom) {
178
+ if (custom.shows.length === 0) {
179
+ problems.push(`The output “${output.name}” is drawn with ${output.component}, which shows nothing: its value is what it shows first, under \`shows\`.`);
180
+ continue;
181
+ }
182
+ const refused = customPropsRefused(custom, output.props);
183
+ if (refused.length > 0) {
184
+ problems.push(`The output “${output.name}” is a ${output.component} its schema refuses: ${refused.join('; ')}.`);
185
+ }
186
+ continue;
187
+ }
188
+ const shows = PAGE_OUTPUT_SHOWS[output.component];
189
+ if (!shows) {
190
+ problems.push(`The output “${output.name}” is drawn with ${output.component}: an output is one of ${Object.keys(PAGE_OUTPUT_SHOWS).join(', ')} or a component of its own (\`custom_components\`).`);
191
+ continue;
192
+ }
193
+ const said = [shows, 'id', 'component'].filter(key => key in output.props);
194
+ if (said.length > 0) {
195
+ problems.push(`The output “${output.name}” says ${said.join(', ')} in its \`props\`: its id is its name, and what it shows is its value.`);
196
+ }
197
+ const schema = getComponent(output.component)?.properties;
198
+ const missing = (schema?.required ?? []).filter(key => key !== shows && !(key in output.props));
199
+ if (missing.length > 0) {
200
+ problems.push(`The output “${output.name}” is a ${output.component} without ${missing.join(', ')}: say it in its \`props\`.`);
201
+ }
202
+ }
203
+ const settings = Object.keys(app.interface.settings?.properties ?? {});
204
+ const inputs = Object.keys(page.inputs?.properties ?? {});
205
+ const shared = settings.filter(name => inputs.includes(name));
206
+ if (shared.length > 0) {
207
+ problems.push(`${shared.map(name => `“${name}”`).join(', ')} is both a setting and an input of its page: name one otherwise.`);
208
+ }
132
209
  return problems;
133
210
  }
134
211
  /** What the spec says of itself that the tolerant reader lets through. */
@@ -228,6 +305,10 @@ function shapeProblems(app) {
228
305
  if (new Set(names).size !== names.length) {
229
306
  problems.push('The host’s functions are named once each.');
230
307
  }
308
+ // Who its user is (D-21): what the page says, or what its server signed.
309
+ if (host.user !== undefined && !HOST_USERS.includes(host.user)) {
310
+ problems.push(`“${host.user}” is not how the host’s user is taken: claimed or signed.`);
311
+ }
231
312
  const named = new Set(app.rules.flatMap(rule => rule.appliesTo));
232
313
  for (const tool of hostToolsOf(host)) {
233
314
  if (!named.has(tool)) {
@@ -380,13 +461,21 @@ function referenceProblems(app, organizationFrames) {
380
461
  }
381
462
  }
382
463
  // The components it may use, and those its surface uses, are the catalog's (C-13).
464
+ // Those its developer wrote are of the catalog for it alone (P-17).
383
465
  for (const name of app.interface.components ?? []) {
384
- if (!componentNamed(name)) {
466
+ if (!componentNamed(name) && !customComponentOf(app, name)) {
385
467
  problems.push(`There is no component named “${name}” in the catalog.`);
386
468
  }
387
469
  }
388
470
  for (const node of app.interface.surface?.components ?? []) {
389
- if (!componentNamed(String(node.component))) {
471
+ const custom = customComponentOf(app, String(node.component));
472
+ if (custom) {
473
+ const refused = customNodeRefused(custom, node);
474
+ if (refused.length > 0) {
475
+ problems.push(`The surface's “${String(node.id)}” is a ${custom.name} its schema refuses: ${refused.join('; ')}.`);
476
+ }
477
+ }
478
+ else if (!componentNamed(String(node.component))) {
390
479
  problems.push(`The surface's “${String(node.id)}” is a “${String(node.component)}”, which the catalog does not have.`);
391
480
  }
392
481
  else if (node.component === 'Form') {
@@ -912,6 +1001,19 @@ export function documentShapeProblems(document) {
912
1001
  required(item, 'id', where);
913
1002
  required(item, 'component', where);
914
1003
  });
1004
+ }); // A widget's page written in its code (LOOP P-05), as agentspecs reads it.
1005
+ mapping(ui.page, 'interface.page', page => {
1006
+ unknownKeys(page, ['function', 'inputs', 'inputs_ui', 'outputs', 'live'], 'interface.page', 'a page');
1007
+ required(page, 'function', 'interface.page');
1008
+ required(page, 'inputs', 'interface.page');
1009
+ required(page, 'outputs', 'interface.page');
1010
+ if (page.live !== undefined && typeof page.live !== 'boolean') {
1011
+ at('interface.page.live', 'is true or false');
1012
+ }
1013
+ records(page.outputs, 'interface.page.outputs', (output, where) => {
1014
+ required(output, 'name', where);
1015
+ unknownKeys(output, ['name', 'title', 'component', 'props'], where, 'an output');
1016
+ });
915
1017
  });
916
1018
  });
917
1019
  mapping(d.tests, 'tests', tests => {
@@ -0,0 +1,41 @@
1
+ import type { AppCustomComponentSpec, AppSpec, ComponentSpec } from '../../types/agentspecs';
2
+ /** Its name: a word starting with a capital letter, as the catalog's are named. */
3
+ export declare const CUSTOM_COMPONENT_NAME: RegExp;
4
+ /** What one of its properties may be typed: what the renderer tells apart. */
5
+ export declare const CUSTOM_PROPERTY_TYPES: readonly ["string", "integer", "number", "boolean", "array", "object"];
6
+ /** What every component of a surface already has, and a custom one cannot declare. */
7
+ export declare const CUSTOM_RESERVED_NAMES: readonly ["id", "component", "action", "weight", "visible_when", "children", "child"];
8
+ /**
9
+ * Where its module is loaded from: an address served over HTTPS, or this
10
+ * machine while it is written. A file of the application's folder waits for
11
+ * its packaging (P-29).
12
+ */
13
+ export declare const CUSTOM_SOURCE: RegExp;
14
+ /** A Subresource Integrity hash: the module as it was reviewed. */
15
+ export declare const INTEGRITY: RegExp;
16
+ /**
17
+ * What its schema refuses of the properties given, in the words agentspecs
18
+ * says them: one it does not have, a value of another type, one it needs.
19
+ * `bound` are given elsewhere — bound to what the application publishes.
20
+ */
21
+ export declare function customPropsRefused(component: AppCustomComponentSpec, props: Record<string, unknown>, bound?: readonly string[]): string[];
22
+ /** What stops one component its developer wrote from being reviewed, in sentences. */
23
+ export declare function customComponentProblems(component: AppCustomComponentSpec): string[];
24
+ /** What stops the components an application's developer wrote, in sentences. */
25
+ export declare function customComponentsProblems(app: AppSpec): string[];
26
+ /** A component its developer wrote, by name, or undefined. */
27
+ export declare function customComponentOf(app: Pick<AppSpec, 'interface'>, name: string): AppCustomComponentSpec | undefined;
28
+ /**
29
+ * It as the catalog lists a component (C-13): what the palette, the
30
+ * components page and the checks read — under the application's version, its
31
+ * category `custom`.
32
+ */
33
+ export declare function customComponentEntry(component: AppCustomComponentSpec, version: string): ComponentSpec;
34
+ /**
35
+ * The components an application may place: the catalog's own, given, and
36
+ * those its developer wrote, for it alone. A name of its own that the
37
+ * catalog has is not listed twice: the checks refuse it.
38
+ */
39
+ export declare function appComponents(app: Pick<AppSpec, 'interface' | 'version'>, catalog: readonly ComponentSpec[]): ComponentSpec[];
40
+ /** What its schema refuses of a node of a surface drawn by it, in sentences; empty when nothing. */
41
+ export declare function customNodeRefused(component: AppCustomComponentSpec, node: Record<string, unknown>): string[];