@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@datalayer/agent-runtimes",
3
- "version": "1.3.84",
3
+ "version": "1.3.86",
4
4
  "type": "module",
5
5
  "workspaces": [
6
6
  ".",
@@ -69,7 +69,7 @@
69
69
  "clean:dist": "rimraf dist",
70
70
  "clean:lib": "rimraf lib tsconfig.tsbuildinfo",
71
71
  "create:patches": "bash scripts/create-patches.sh",
72
- "examples": "run-p server:start server:start:codemode server:start:accounting server:accounting jupyter:start examples:vite",
72
+ "examples": "run-p server:start server:start:codemode server:start:accounting server:scenes jupyter:start examples:vite",
73
73
  "examples2": "run-p server:start server:start:codemode jupyter:start examples2:vite",
74
74
  "example-local": "make example-local",
75
75
  "examples:fresh": "npm run clean:cache && npm run examples",
@@ -99,7 +99,7 @@
99
99
  "server:start:agent-node": "AGENT_NODE_JUPYTER_URL=${AGENT_NODE_JUPYTER_URL:-http://127.0.0.1:8888/api/jupyter-server} AGENT_NODE_JUPYTER_TOKEN=${AGENT_NODE_JUPYTER_TOKEN:-60c1661cc408f978c309d04157af55c9588ff9557c9380e4fb50785750703da6} PYTHONIOENCODING=utf-8 AGENT_RUNTIMES_DEBUG=false AGENT_RUNTIMES_LOG_LEVEL=info DATALAYER_EVALS_MODE=interactive DATALAYER_EVALS_EMIT_LIVE_EVENTS=true AGENT_RUNTIMES_INFERENCE_PROVIDER_OVERRIDE=${AGENT_RUNTIMES_INFERENCE_PROVIDER_OVERRIDE:-datalayer} python -m agent_runtimes serve --node --port 8765 --log-level info",
100
100
  "server:start:codemode": "PYTHONIOENCODING=utf-8 AGENT_RUNTIMES_DEBUG=false AGENT_RUNTIMES_LOG_LEVEL=info DATALAYER_EVALS_MODE=interactive DATALAYER_EVALS_EMIT_LIVE_EVENTS=true python -m agent_runtimes serve --port 8766 --log-level info",
101
101
  "server:start:accounting": "PYTHONIOENCODING=utf-8 AGENT_RUNTIMES_DEBUG=false AGENT_RUNTIMES_LOG_LEVEL=info python -m agent_runtimes serve --port 8767 --log-level info",
102
- "server:accounting": "python examples/sales-accounting-a2a/serve_accounting.py --url http://127.0.0.1:8767 --wait 300 || echo 'Accounting was not served on 8767: the Agent A2A Team example will say it is not reachable.'",
102
+ "server:scenes": "python examples/sales-accounting-a2a/serve_scenes.py --url http://127.0.0.1:8767 --wait 300 || echo 'The scenes were not served: the Agent A2A Team and Scenes examples will say which member is not reachable.'",
103
103
  "examples:codemode": "run-p server:start server:start:codemode examples:vite",
104
104
  "start": "vite",
105
105
  "start:acp": "run-p server:start start:acp:vite",
@@ -147,7 +147,7 @@
147
147
  "@ai-sdk/react": "4.0.93",
148
148
  "@anthropic-ai/sdk": "^0.52.0",
149
149
  "@datalayer/agent-teams": "^1.0.0",
150
- "@datalayer/core": "^1.2.14",
150
+ "@datalayer/core": "^1.2.18",
151
151
  "@datalayer/decks": "^1.0.4",
152
152
  "@datalayer/icons-react": "^1.0.16",
153
153
  "@datalayer/jupyter-lexical": "^2.0.18",
@@ -1647,6 +1647,7 @@ export * from './gates';
1647
1647
  export * from './tracks';
1648
1648
  export * from './ops';
1649
1649
  export * from './apps';
1650
+ export * from './scenes';
1650
1651
  export * from './actions';
1651
1652
  export * from './appspecSchema';
1652
1653
  """
@@ -55,6 +55,7 @@ CAMEL = {
55
55
  "backend_tools": "backendTools",
56
56
  "character_alone": "characterAlone",
57
57
  "settings_ui": "settingsUi",
58
+ "custom_components": "customComponents",
58
59
  }
59
60
 
60
61
  #: The keys whose value is carried as it is written: a component tree, weights by name.
@@ -126,6 +127,9 @@ def load_specs(specs_dir: Path) -> tuple[list[dict[str, Any]], dict[str, Any]]:
126
127
  for case in spec["tests"]["cases"]:
127
128
  if not case.get("code"):
128
129
  case.pop("code", None)
130
+ # Components of its own: said only when it has some.
131
+ if not spec["interface"].get("custom_components"):
132
+ spec["interface"].pop("custom_components", None)
129
133
  for key in ("description", "instructions", "goal"):
130
134
  spec[key] = _flat(spec.get(key))
131
135
  spec["interface"]["welcome"] = _flat(spec["interface"].get("welcome"))
@@ -492,8 +496,6 @@ def generate_schema_typescript_code() -> str:
492
496
  " * @module specs/appspecSchema",
493
497
  " */",
494
498
  "",
495
- "/* eslint-disable */",
496
- "",
497
499
  "export type JsonSchema = {",
498
500
  " [key: string]: unknown;",
499
501
  " type?: string;",
@@ -0,0 +1,301 @@
1
+ #!/usr/bin/env python3
2
+ # Copyright (c) 2025-2026 Datalayer, Inc.
3
+ # Distributed under the terms of the Modified BSD License.
4
+
5
+ """
6
+ Generate Python and TypeScript code from YAML scene specifications.
7
+
8
+ A scene (agentspecs >= 0.0.61, LOOP A-11) stages a team: the team says who
9
+ is on stage, the scene says what happens there — the setting, the script,
10
+ the stage directions, the audience, the rehearsal and where it plays. One
11
+ YAML per scene under ``agentspecs/scenes``.
12
+
13
+ Each scene is validated here, by ``agentspecs`` itself (``scene_problems``),
14
+ and arrives with its cast resolved — every member of the team, its persona
15
+ filled from its application (``cast_of``) — its ``entry`` said, what each
16
+ beat ``shows`` said, and what it names that is not enabled (``setup``).
17
+ Empty optional lists are dropped, as ``generate_apps.py`` drops them.
18
+
19
+ Usage:
20
+ python generate_scenes.py \\
21
+ --specs-dir agentspecs/agentspecs/scenes \\
22
+ --python-output agent_runtimes/specs/scenes.py \\
23
+ --typescript-output src/specs/scenes.ts
24
+ """
25
+
26
+ import argparse
27
+ import json
28
+ import sys
29
+ from pathlib import Path
30
+ from typing import Any
31
+
32
+ from agentspecs_clone import import_from_clone
33
+ from versioning import ensure_spec_version, version_suffix
34
+
35
+ #: The keys of a scene spec, as TypeScript spells them.
36
+ CAMEL = {
37
+ "runs_in": "runsIn",
38
+ "talks_to": "talksTo",
39
+ "opens_first": "opensFirst",
40
+ "rests_after": "restsAfter",
41
+ "ceiling_per_ask": "ceilingPerAsk",
42
+ "asks_a_day": "asksADay",
43
+ "must_say": "mustSay",
44
+ "must_not_say": "mustNotSay",
45
+ }
46
+
47
+ #: The lists written only when they hold something: TypeScript reads them as optional.
48
+ OPTIONAL_LISTS = (
49
+ "talks_to",
50
+ "frames",
51
+ "contents",
52
+ "branch",
53
+ "withhold",
54
+ "must_say",
55
+ "must_not_say",
56
+ )
57
+
58
+
59
+ def _flat(text: Any) -> str:
60
+ """A text on one line."""
61
+ return " ".join(str(text or "").split())
62
+
63
+
64
+ def _trim(value: Any, key: str = "") -> Any:
65
+ """A value without the empty optional lists and the keys that hold nothing."""
66
+ if isinstance(value, dict):
67
+ trimmed = {}
68
+ for name, item in value.items():
69
+ if item is None or (name in OPTIONAL_LISTS and item == []):
70
+ continue
71
+ trimmed[name] = _trim(item, name)
72
+ return trimmed
73
+ if isinstance(value, list):
74
+ return [_trim(item) for item in value]
75
+ return value
76
+
77
+
78
+ def load_specs(specs_dir: Path) -> list[dict[str, Any]]:
79
+ """Every scene, validated, as plain data: its cast resolved, its entry said."""
80
+ scenes_module = import_from_clone(specs_dir, "scenes")
81
+ scenes = scenes_module.load_scenes(specs_dir)
82
+ specs: list[dict[str, Any]] = []
83
+ for identity in sorted(scenes):
84
+ scene = scenes[identity]
85
+ spec = scene.model_dump(mode="json", by_alias=True)
86
+ spec["entry"] = scene.entry_of()
87
+ spec["cast"] = [
88
+ member.model_dump(mode="json", by_alias=True) for member in scene.cast_of()
89
+ ]
90
+ for beat, data in zip(scene.script, spec["script"]):
91
+ data["shows"] = [kind.value for kind in beat.shown]
92
+ spec["setup"] = scenes_module.scene_setup(scene)
93
+ for key in ("description",):
94
+ spec[key] = _flat(spec.get(key))
95
+ spec["setting"]["assumes"] = _flat(spec["setting"].get("assumes"))
96
+ for member in spec["cast"]:
97
+ member["brief"] = _flat(member.get("brief"))
98
+ for beat in spec["script"]:
99
+ beat["expect"] = _flat(beat.get("expect"))
100
+ for branch in beat.get("branch") or []:
101
+ branch["expect"] = _flat(branch.get("expect"))
102
+ ensure_spec_version(spec)
103
+ specs.append(_trim(spec))
104
+ return specs
105
+
106
+
107
+ def _const_name(spec: dict[str, Any]) -> str:
108
+ """`sales-and-accounting` 0.0.1 → `SALES_AND_ACCOUNTING_SCENE_0_0_1`."""
109
+ base = spec["id"].upper().replace("-", "_").replace(".", "_")
110
+ return f"{base}_SCENE" + version_suffix(spec["version"])
111
+
112
+
113
+ def _typescript_value(value: Any) -> Any:
114
+ """A scene as the TypeScript interfaces read it."""
115
+ if isinstance(value, dict):
116
+ return {
117
+ CAMEL.get(key, key): _typescript_value(item) for key, item in value.items()
118
+ }
119
+ if isinstance(value, list):
120
+ return [_typescript_value(item) for item in value]
121
+ return value
122
+
123
+
124
+ HEADER = [
125
+ "This file is AUTO-GENERATED from YAML specifications.",
126
+ "DO NOT EDIT MANUALLY - run 'make specs' to regenerate.",
127
+ ]
128
+
129
+ ABOUT = [
130
+ "Scene Catalog.",
131
+ "",
132
+ "A team, staged: the setting, the script, the stage directions, the",
133
+ "audience, the rehearsal and where it plays. The cast is resolved.",
134
+ ]
135
+
136
+
137
+ def generate_python_code(specs: list[dict[str, Any]]) -> str:
138
+ """Generate the Python scene catalogue."""
139
+ lines = [
140
+ "# Copyright (c) 2025-2026 Datalayer, Inc.",
141
+ "# Distributed under the terms of the Modified BSD License.",
142
+ '"""',
143
+ *ABOUT,
144
+ "",
145
+ *HEADER,
146
+ '"""',
147
+ "",
148
+ "from typing import Dict",
149
+ "",
150
+ "from agent_runtimes.types import SceneSpec",
151
+ "",
152
+ "# " + "=" * 76,
153
+ "# Scene Definitions",
154
+ "# " + "=" * 76,
155
+ "",
156
+ ]
157
+ for spec in specs:
158
+ lines.extend(
159
+ [
160
+ f"{_const_name(spec)} = SceneSpec.model_validate(",
161
+ f" {spec!r}",
162
+ ")",
163
+ "",
164
+ ]
165
+ )
166
+ lines.extend(
167
+ [
168
+ "",
169
+ "# " + "=" * 76,
170
+ "# Scene Catalog",
171
+ "# " + "=" * 76,
172
+ "",
173
+ "SCENE_CATALOGUE: Dict[str, SceneSpec] = {",
174
+ ]
175
+ )
176
+ for spec in specs:
177
+ lines.append(f" {spec['id']!r}: {_const_name(spec)},")
178
+ lines.extend(
179
+ [
180
+ "}",
181
+ "",
182
+ "",
183
+ "def get_scene_spec(scene_id: str) -> SceneSpec | None:",
184
+ ' """A scene, by `id` or `id:version`, or None."""',
185
+ " found = SCENE_CATALOGUE.get(scene_id)",
186
+ " if found is not None:",
187
+ " return found",
188
+ ' base, _, version = scene_id.rpartition(":")',
189
+ ' return SCENE_CATALOGUE.get(base) if base and "." in version else None',
190
+ "",
191
+ "",
192
+ "def list_scene_specs(tag: str | None = None) -> list[SceneSpec]:",
193
+ ' """Every scene of the catalogue, or those carrying a tag."""',
194
+ " return [",
195
+ " scene for scene in SCENE_CATALOGUE.values() if tag is None or tag in scene.tags",
196
+ " ]",
197
+ "",
198
+ "",
199
+ "def scenes_staging(team_id: str) -> list[SceneSpec]:",
200
+ ' """Every scene that stages a team, by its id with or without a version."""',
201
+ ' wanted = team_id.split(":")[0]',
202
+ " return [",
203
+ " scene",
204
+ " for scene in SCENE_CATALOGUE.values()",
205
+ ' if scene.team and scene.team.split(":")[0] == wanted',
206
+ " ]",
207
+ "",
208
+ ]
209
+ )
210
+ return "\n".join(lines)
211
+
212
+
213
+ def generate_typescript_code(specs: list[dict[str, Any]]) -> str:
214
+ """Generate the TypeScript scene catalogue."""
215
+ lines = [
216
+ "/*",
217
+ " * Copyright (c) 2025-2026 Datalayer, Inc.",
218
+ " * Distributed under the terms of the Modified BSD License.",
219
+ " */",
220
+ "",
221
+ "/**",
222
+ *[f" * {line}".rstrip() for line in ABOUT],
223
+ " *",
224
+ *[f" * {line}" for line in HEADER],
225
+ " */",
226
+ "",
227
+ "import type { SceneSpec } from '../types/scenes';",
228
+ "",
229
+ ]
230
+ for spec in specs:
231
+ body = json.dumps(_typescript_value(spec), indent=2, ensure_ascii=False)
232
+ lines.extend([f"export const {_const_name(spec)}: SceneSpec = {body};", ""])
233
+ lines.append("export const SCENE_CATALOGUE: Record<string, SceneSpec> = {")
234
+ for spec in specs:
235
+ lines.append(f" {json.dumps(spec['id'])}: {_const_name(spec)},")
236
+ lines.extend(
237
+ [
238
+ "};",
239
+ "",
240
+ "/** A scene, by `id` or `id:version`, or undefined. */",
241
+ "export function getSceneSpec(ref: string): SceneSpec | undefined {",
242
+ " // Own entries only: `constructor` and `toString` are not scenes.",
243
+ " const own = (id: string): SceneSpec | undefined =>",
244
+ " Object.prototype.hasOwnProperty.call(SCENE_CATALOGUE, id)",
245
+ " ? SCENE_CATALOGUE[id]",
246
+ " : undefined;",
247
+ " const at = ref.lastIndexOf(':');",
248
+ " return (",
249
+ " own(ref) ??",
250
+ " (at > 0 && ref.slice(at + 1).includes('.')",
251
+ " ? own(ref.slice(0, at))",
252
+ " : undefined)",
253
+ " );",
254
+ "}",
255
+ "",
256
+ "/** Every scene of the catalogue, in the catalogue's order, or those carrying a tag. */",
257
+ "export function listSceneSpecs(tag?: string): SceneSpec[] {",
258
+ " return Object.values(SCENE_CATALOGUE).filter(",
259
+ " scene => tag === undefined || scene.tags.includes(tag),",
260
+ " );",
261
+ "}",
262
+ "",
263
+ "/** Every scene that stages a team, by its id with or without a version. */",
264
+ "export function scenesStaging(teamId: string): SceneSpec[] {",
265
+ " const wanted = teamId.split(':')[0];",
266
+ " return Object.values(SCENE_CATALOGUE).filter(",
267
+ " scene => scene.team !== '' && scene.team.split(':')[0] === wanted,",
268
+ " );",
269
+ "}",
270
+ "",
271
+ ]
272
+ )
273
+ return "\n".join(lines)
274
+
275
+
276
+ def main() -> None:
277
+ """Generate the scene catalogue."""
278
+ parser = argparse.ArgumentParser(
279
+ description="Generate Python and TypeScript code from YAML scene specifications"
280
+ )
281
+ parser.add_argument("--specs-dir", type=Path, required=True)
282
+ parser.add_argument("--python-output", type=Path, required=True)
283
+ parser.add_argument("--typescript-output", type=Path, required=True)
284
+ args = parser.parse_args()
285
+
286
+ if not args.specs_dir.exists():
287
+ print(f"Error: Specs directory not found: {args.specs_dir}", file=sys.stderr)
288
+ sys.exit(1)
289
+ specs = load_specs(args.specs_dir)
290
+ for path, text in (
291
+ (args.python_output, generate_python_code(specs)),
292
+ (args.typescript_output, generate_typescript_code(specs)),
293
+ ):
294
+ path.parent.mkdir(parents=True, exist_ok=True)
295
+ path.write_text(text)
296
+ print(f"Generated: {path}")
297
+ print(f"\n✓ Successfully generated code from {len(specs)} scene specs")
298
+
299
+
300
+ if __name__ == "__main__":
301
+ main()
@@ -151,6 +151,8 @@ def _generate_team_agent_py(agent: Dict[str, Any]) -> str:
151
151
  # emitted only when said, as a team of agents says none of them.
152
152
  if agent.get("app"):
153
153
  lines.append(f' app="{agent["app"]}",')
154
+ if agent.get("server"):
155
+ lines.append(f' server="{agent["server"]}",')
154
156
  if agent.get("runs_in"):
155
157
  lines.append(f' runs_in="{agent["runs_in"]}",')
156
158
  links = agent.get("talks_to") or []
@@ -220,6 +222,7 @@ def _generate_team_agent_ts(agent: Dict[str, Any]) -> str:
220
222
  links = agent.get("talks_to") or []
221
223
  application = (
222
224
  (f"\n app: '{agent['app']}'," if agent.get("app") else "")
225
+ + (f"\n server: '{agent['server']}'," if agent.get("server") else "")
223
226
  + (f"\n runsIn: '{agent['runs_in']}'," if agent.get("runs_in") else "")
224
227
  + (
225
228
  "\n talksTo: ["
@@ -484,10 +487,14 @@ from agent_runtimes.types import (
484
487
  # `shared` by default: routing only makes sense if the member
485
488
  # receiving the work can see what was already said.
486
489
  context = spec.get("context") or {}
490
+ # The Frames of its shared context (LOOP A-04): written only when
491
+ # it names some, as the parser reads it.
492
+ frames = context.get("frames") or []
487
493
  code += (
488
494
  " context=TeamContextSpec("
489
495
  f'sharing="{context.get("sharing", "shared")}"'
490
- "),\n"
496
+ + (f", frames={_fmt_list(frames)}" if frames else "")
497
+ + "),\n"
491
498
  )
492
499
  code += f" validation={validation_code},\n"
493
500
  code += f" agents={agents_code},\n"
@@ -778,7 +785,13 @@ import type {{ TeamSpec }} from '{types_import_path}';
778
785
  " },\n"
779
786
  )
780
787
  context = spec.get("context") or {}
781
- code += f" context: {{ sharing: '{context.get('sharing', 'shared')}' }},\n"
788
+ frames = context.get("frames") or []
789
+ frames_ts = (
790
+ ", frames: [" + ", ".join(f"'{frame}'" for frame in frames) + "]"
791
+ if frames
792
+ else ""
793
+ )
794
+ code += f" context: {{ sharing: '{context.get('sharing', 'shared')}'{frames_ts} }},\n"
782
795
  code += f" validation: {validation_ts},\n"
783
796
  code += f" agents: {agents_ts},\n"
784
797
  if rr_ts is not None:
@@ -1,24 +0,0 @@
1
- /**
2
- * A JSON value as a tree: objects and arrays fold and unfold, each value
3
- * coloured by its type (theme colours, light and dark), long strings cut
4
- * until asked for whole, and the whole value copied as JSON.
5
- *
6
- * @module components/inspector/JsonTree
7
- */
8
- import type { JSX } from 'react';
9
- export type JsonTreeProps = {
10
- value: unknown;
11
- /** The root's name, shown as its key. */
12
- name?: string;
13
- /** How many levels open at first. */
14
- expandDepth?: number;
15
- /** Offer *Copy JSON* above the tree. */
16
- copyable?: boolean;
17
- /** Longer strings are cut until clicked. */
18
- stringLimit?: number;
19
- };
20
- /** A value's type, as the tree colours it. */
21
- export declare function jsonType(value: unknown): 'string' | 'number' | 'boolean' | 'null' | 'array' | 'object' | 'undefined';
22
- /** A JSON value, as a tree. */
23
- export declare function JsonTree({ value, name, expandDepth, copyable, stringLimit, }: JsonTreeProps): JSX.Element;
24
- export default JsonTree;
@@ -1,89 +0,0 @@
1
- import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
- import { useState } from 'react';
3
- import { Button, Text } from '@primer/react';
4
- import { Box } from '@datalayer/primer-addons';
5
- import { CopyIcon, CheckIcon } from '@primer/octicons-react';
6
- const MONO = 'ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace';
7
- /** A value's type, as the tree colours it. */
8
- export function jsonType(value) {
9
- if (value === null) {
10
- return 'null';
11
- }
12
- if (Array.isArray(value)) {
13
- return 'array';
14
- }
15
- const type = typeof value;
16
- if (type === 'string' || type === 'number' || type === 'boolean') {
17
- return type;
18
- }
19
- if (type === 'bigint') {
20
- return 'number';
21
- }
22
- return type === 'undefined' ? 'undefined' : 'object';
23
- }
24
- const COLOURS = {
25
- string: 'success.fg',
26
- number: 'accent.fg',
27
- boolean: 'done.fg',
28
- null: 'fg.muted',
29
- undefined: 'fg.muted',
30
- };
31
- function Leaf({ value, stringLimit, }) {
32
- const type = jsonType(value);
33
- const [whole, setWhole] = useState(false);
34
- if (type === 'string') {
35
- const text = value;
36
- const cut = !whole && text.length > stringLimit;
37
- return (_jsxs(Text, { "data-json-type": "string", sx: {
38
- color: COLOURS.string,
39
- whiteSpace: 'pre-wrap',
40
- wordBreak: 'break-word',
41
- }, children: [JSON.stringify(cut ? text.slice(0, stringLimit) : text), cut && (_jsxs(Box, { as: "button", type: "button", onClick: () => setWhole(true), sx: {
42
- ml: 1,
43
- p: 0,
44
- border: 0,
45
- bg: 'transparent',
46
- color: 'accent.fg',
47
- cursor: 'pointer',
48
- font: 'inherit',
49
- }, children: ["\u2026 ", text.length - stringLimit, " more"] }))] }));
50
- }
51
- return (_jsx(Text, { "data-json-type": type, sx: { color: COLOURS[type] ?? 'fg.default' }, children: type === 'undefined' ? 'undefined' : String(value) }));
52
- }
53
- function Node({ name, value, depth, expandDepth, stringLimit, }) {
54
- const type = jsonType(value);
55
- const branch = type === 'array' || type === 'object';
56
- const [open, setOpen] = useState(depth < expandDepth);
57
- const key = name !== undefined ? (_jsxs(Text, { sx: { color: 'fg.default', fontWeight: 600 }, children: [name, ": "] })) : null;
58
- if (!branch) {
59
- return (_jsxs(Box, { role: "treeitem", "data-json-node": "", sx: { pl: 3 }, children: [key, _jsx(Leaf, { value: value, stringLimit: stringLimit })] }));
60
- }
61
- const items = type === 'array'
62
- ? value.map((item, index) => [String(index), item])
63
- : Object.entries(value);
64
- const [openMark, closeMark] = type === 'array' ? ['[', ']'] : ['{', '}'];
65
- return (_jsxs(Box, { role: "treeitem", "aria-expanded": open, "data-json-node": "", sx: { pl: depth ? 3 : 0 }, children: [_jsxs(Box, { as: "button", type: "button", onClick: () => setOpen(!open), "aria-label": `${open ? 'Fold' : 'Unfold'} ${name ?? 'the value'}`, "data-json-toggle": "", sx: {
66
- p: 0,
67
- border: 0,
68
- bg: 'transparent',
69
- color: 'inherit',
70
- font: 'inherit',
71
- cursor: 'pointer',
72
- textAlign: 'left',
73
- }, children: [_jsx(Text, { sx: { color: 'fg.muted', display: 'inline-block', width: '1em' }, children: open ? '▾' : '▸' }), key, _jsx(Text, { sx: { color: 'fg.muted' }, children: open
74
- ? openMark
75
- : `${openMark} ${items.length} ${type === 'array' ? (items.length === 1 ? 'item' : 'items') : items.length === 1 ? 'key' : 'keys'} ${closeMark}` })] }), open && (_jsxs(Box, { role: "group", children: [items.map(([itemName, item]) => (_jsx(Node, { name: itemName, value: item, depth: depth + 1, expandDepth: expandDepth, stringLimit: stringLimit }, itemName))), _jsx(Text, { sx: { color: 'fg.muted', pl: 3 }, children: closeMark })] }))] }));
76
- }
77
- /** A JSON value, as a tree. */
78
- export function JsonTree({ value, name, expandDepth = 1, copyable = true, stringLimit = 240, }) {
79
- const [copied, setCopied] = useState(false);
80
- const copy = () => {
81
- const text = JSON.stringify(value, null, 2) ?? 'undefined';
82
- void navigator.clipboard?.writeText(text).then(() => {
83
- setCopied(true);
84
- setTimeout(() => setCopied(false), 1500);
85
- }, () => undefined);
86
- };
87
- return (_jsxs(Box, { "data-json-tree": "", sx: { fontFamily: MONO, fontSize: 0, lineHeight: 1.6, minWidth: 0 }, children: [copyable && (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: copied ? CheckIcon : CopyIcon, onClick: copy, "data-json-copy": "", sx: { float: 'right' }, children: copied ? 'Copied' : 'Copy JSON' })), _jsx(Box, { role: "tree", "aria-label": name ?? 'JSON', children: _jsx(Node, { name: name, value: value, depth: 0, expandDepth: expandDepth, stringLimit: stringLimit }) })] }));
88
- }
89
- export default JsonTree;
@@ -1,56 +0,0 @@
1
- /**
2
- * The A2A traffic between two agents, as the Agent Inspector records it: a
3
- * fetch that hands every request on and records what went (the JSON-RPC
4
- * request: its method, its message) and what came back — a JSON-RPC
5
- * response, or each server-sent event of a stream (the task, its status
6
- * updates, its artifact updates, a message) — with its task, context and
7
- * state, whole.
8
- *
9
- * It sits under the A2A SDK (`ClientFactory`'s `JsonRpcTransportFactory`
10
- * takes a `fetchImpl`): the SDK reads the response it is handed, and the
11
- * inspector reads a copy of its body. Nothing it records changes what the
12
- * SDK sees, and a key sent as a header is never recorded.
13
- *
14
- * A peer served by agent-runtimes tells each tool call of its agent on a
15
- * `working` status (`{tool_call}`, then `{tool_result}`): each call is
16
- * recorded besides as one entry of its own, the peer's, from its start to
17
- * its end, linked to the two statuses — an MCP call, a skill, or a tool its
18
- * runtime runs, by who claims its name. The text chunks of one artifact are
19
- * one entry, their count kept.
20
- *
21
- * {@link linkA2APeerEvent} attaches what the asker made of an event (an
22
- * `A2APeerEvent`) to the entry it came from.
23
- *
24
- * @module components/inspector/a2aInspect
25
- */
26
- import type { A2APeerEvent } from '../../runtimes/browser/a2aPeer';
27
- import { type AgentInspectorEntry, type AgentInspectorSink, type ToolClass } from './agentInspector';
28
- export type A2AInspectOptions = {
29
- sink: AgentInspectorSink;
30
- /** The agent that asks: `Sales`. */
31
- asker: string;
32
- /** The agent asked: `Accounting`. */
33
- peer: string;
34
- /**
35
- * Where a peer's tool call comes from — an MCP server's, a skill, … — and
36
- * its mark (`classifyToolCall`, or a team's `teamToolClassifier`).
37
- * Without it, a tool the peer's runtime runs (`backend-tool`).
38
- */
39
- classifyTool?: (name: string, args?: Record<string, unknown>) => ToolClass;
40
- };
41
- /** The text of an A2A 1.0 message's or artifact's parts, as JSON carries them. */
42
- export declare function partsText(parts: unknown): string;
43
- /** `TASK_STATE_WORKING` is `working`. */
44
- export declare function stateWord(state: unknown): string;
45
- /**
46
- * A fetch that records the A2A traffic it carries into `options.sink`, and
47
- * hands the SDK what the network gave.
48
- */
49
- export declare function inspectA2AFetch(base: typeof globalThis.fetch, options: A2AInspectOptions): typeof globalThis.fetch;
50
- /**
51
- * The linker the A2A capture adds to its sink: an `A2APeerEvent` noted
52
- * (`sink.note`) is attached to the entry it came from.
53
- */
54
- export declare function a2aEventLinker(event: unknown): ((entry: AgentInspectorEntry) => boolean) | undefined;
55
- /** Attach what the asker made of an event to the A2A entry it came from. */
56
- export declare function linkA2APeerEvent(sink: AgentInspectorSink, event: A2APeerEvent): void;