@theprototype/agent 1.48.0 → 1.50.0

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.
package/catalog.json ADDED
@@ -0,0 +1,1224 @@
1
+ {
2
+ "$comment": "GENERATED by core scripts/agent-tool-catalog.cjs (npm run catalog:agent) - never edit by hand.",
3
+ "format": 2,
4
+ "generator": {
5
+ "source": "tpAgent.tools() via core scripts/agent-tool-catalog.cjs, mapped by tools/agent/src/catalog.js",
6
+ "hostTools": 20,
7
+ "dropped": []
8
+ },
9
+ "instructions": "You are building in the person's own open theprototype.app tab: a live, shared 3D scene. Every change shows for everyone in the session and can be undone.\nThis may be a VOICE conversation: answer in one or two short spoken sentences and say what changed in plain words (\"Added three trees by the fire\"). Never read out uuids, lists of numbers or JSON.\nIf a tool says the tab is closed or not connected, tell the person to open theprototype.app and press Connect Claude, then wait; do not retry in a loop.\nCall status first: it says whether you may edit (a viewer can only look).\nCall list_scene before editing so you use real uuids; batch many objects into one call.\nMotion and interaction are flow nodes (create_flow_nodes); call describe_node_types before wiring edges.\nUse screenshot to check a result when it matters, not after every step.\nWhen asked what you can do here, call what_can_you_do and say its \"say\" line.",
10
+ "tools": [
11
+ {
12
+ "name": "list_scene",
13
+ "title": "Read the scene",
14
+ "description": "List every object currently in the scene with its uuid, name, type, parent, transform, material, color and (for a primitive) its shape — geometry {type, sizes such as the thickness of a wall}. Call this before editing so you use real uuids. On a big scene narrow it: brief (uuids + names only), name (a glob), type, near {position, radius}. A big flow graph shows its first page and says how to read the rest: pass {flow: \"scene\" | object uuid, page} to read one page of one graph.",
15
+ "inputSchema": {
16
+ "type": "object",
17
+ "properties": {
18
+ "flow": {
19
+ "type": "string",
20
+ "description": "optional: read one flow graph (\"scene\" or an object uuid) page by page instead of the scene summary"
21
+ },
22
+ "page": {
23
+ "type": "number",
24
+ "description": "with flow: which page (1-based)"
25
+ },
26
+ "brief": {
27
+ "type": "boolean",
28
+ "description": "optional: only uuid, name, type, parentUuid per object (cheap on a big scene)"
29
+ },
30
+ "name": {
31
+ "type": "string",
32
+ "description": "optional: only objects whose name matches — a glob (\"Wall*\", \"*door*\"), or plain text = contains; case-insensitive"
33
+ },
34
+ "type": {
35
+ "type": "string",
36
+ "description": "optional: only objects of this type — Mesh, Group, PointLight… or a primitive shape (Box, Wall, Sphere…)"
37
+ },
38
+ "near": {
39
+ "type": "object",
40
+ "description": "optional: only objects within radius of a WORLD position",
41
+ "properties": {
42
+ "position": {
43
+ "type": "array",
44
+ "items": {
45
+ "type": "number"
46
+ },
47
+ "description": "[x, y, z]"
48
+ },
49
+ "radius": {
50
+ "type": "number"
51
+ }
52
+ },
53
+ "required": [
54
+ "position",
55
+ "radius"
56
+ ]
57
+ }
58
+ }
59
+ },
60
+ "annotations": {
61
+ "title": "Read the scene",
62
+ "readOnlyHint": true,
63
+ "openWorldHint": false
64
+ }
65
+ },
66
+ {
67
+ "name": "create_objects",
68
+ "title": "Add objects",
69
+ "description": "Create one or more primitives, lights or empty groups in a single call. Ground is the y=0 plane; place objects so they rest ON it (a box of height h sits at y=h/2). Returns the new uuids.",
70
+ "inputSchema": {
71
+ "type": "object",
72
+ "properties": {
73
+ "objects": {
74
+ "type": "array",
75
+ "items": {
76
+ "type": "object",
77
+ "properties": {
78
+ "kind": {
79
+ "type": "string",
80
+ "enum": [
81
+ "primitive",
82
+ "light",
83
+ "group"
84
+ ]
85
+ },
86
+ "primitive": {
87
+ "type": "string",
88
+ "enum": [
89
+ "Box",
90
+ "Sphere",
91
+ "Cylinder",
92
+ "Cone",
93
+ "Capsule",
94
+ "Torus",
95
+ "TorusKnot",
96
+ "Ring",
97
+ "Circle",
98
+ "Plane",
99
+ "Dodecahedron",
100
+ "Icosahedron",
101
+ "Octahedron",
102
+ "Tetrahedron",
103
+ "Tube",
104
+ "Lathe",
105
+ "Wedge",
106
+ "Stairs",
107
+ "Arch",
108
+ "Corner"
109
+ ],
110
+ "description": "required when kind=primitive"
111
+ },
112
+ "light": {
113
+ "type": "string",
114
+ "enum": [
115
+ "ambient",
116
+ "directional",
117
+ "hemisphere",
118
+ "point",
119
+ "spot",
120
+ "rectarea"
121
+ ],
122
+ "description": "required when kind=light"
123
+ },
124
+ "params": {
125
+ "type": "array",
126
+ "items": {
127
+ "type": "number"
128
+ },
129
+ "description": "up to 4 constructor args. Box=[w,h,d], Sphere=[radius], Cylinder=[rTop,rBottom,height], Cone=[radius,height], Plane=[w,h], Torus=[radius,tube]. Omit for sensible defaults."
130
+ },
131
+ "name": {
132
+ "type": "string"
133
+ },
134
+ "position": {
135
+ "type": "array",
136
+ "items": {
137
+ "type": "number"
138
+ },
139
+ "description": "[x,y,z]"
140
+ },
141
+ "rotation": {
142
+ "type": "array",
143
+ "items": {
144
+ "type": "number"
145
+ },
146
+ "description": "Euler radians [x,y,z]"
147
+ },
148
+ "scale": {
149
+ "type": "array",
150
+ "items": {
151
+ "type": "number"
152
+ },
153
+ "description": "[x,y,z]"
154
+ },
155
+ "color": {
156
+ "type": "string",
157
+ "description": "hex like #ff8800 (a mesh, or a light's colour)"
158
+ },
159
+ "intensity": {
160
+ "type": "number",
161
+ "description": "lights: brightness (point/spot also fall off with distance)"
162
+ },
163
+ "distance": {
164
+ "type": "number",
165
+ "description": "point/spot: range in metres (0 = unlimited)"
166
+ },
167
+ "decay": {
168
+ "type": "number",
169
+ "description": "point/spot: falloff (2 = physical)"
170
+ },
171
+ "angle": {
172
+ "type": "number",
173
+ "description": "spot: cone half-angle in radians (0.02-1.57)"
174
+ },
175
+ "penumbra": {
176
+ "type": "number",
177
+ "description": "spot: soft edge 0-1"
178
+ },
179
+ "width": {
180
+ "type": "number",
181
+ "description": "rectarea: width in metres"
182
+ },
183
+ "height": {
184
+ "type": "number",
185
+ "description": "rectarea: height in metres"
186
+ },
187
+ "groundColor": {
188
+ "type": "string",
189
+ "description": "hemisphere: the ground colour, hex"
190
+ },
191
+ "castShadow": {
192
+ "type": "boolean",
193
+ "description": "directional/spot/point: cast shadows"
194
+ },
195
+ "aim": {
196
+ "type": "array",
197
+ "items": {
198
+ "type": "number"
199
+ },
200
+ "description": "directional/spot: a world point [x,y,z] to point at"
201
+ },
202
+ "parentUuid": {
203
+ "type": "string",
204
+ "description": "uuid of an existing group to nest under — position/rotation/scale are then LOCAL to that group"
205
+ },
206
+ "physics": {
207
+ "type": "string",
208
+ "enum": [
209
+ "static",
210
+ "dynamic"
211
+ ],
212
+ "description": "default static (it stays put in Play). dynamic = it falls, collides and can be thrown — only when asked for"
213
+ },
214
+ "mass": {
215
+ "type": "number",
216
+ "description": "kg; implies physics dynamic"
217
+ }
218
+ },
219
+ "required": [
220
+ "kind"
221
+ ]
222
+ }
223
+ }
224
+ },
225
+ "required": [
226
+ "objects"
227
+ ]
228
+ },
229
+ "annotations": {
230
+ "title": "Add objects",
231
+ "readOnlyHint": false,
232
+ "destructiveHint": false,
233
+ "openWorldHint": false
234
+ }
235
+ },
236
+ {
237
+ "name": "update_objects",
238
+ "title": "Change objects",
239
+ "description": "Modify existing objects by uuid: move/rotate/scale, recolor, change material, rename, show/hide, or reparent. Batch many in one call.",
240
+ "inputSchema": {
241
+ "type": "object",
242
+ "properties": {
243
+ "updates": {
244
+ "type": "array",
245
+ "items": {
246
+ "type": "object",
247
+ "properties": {
248
+ "uuid": {
249
+ "type": "string"
250
+ },
251
+ "name": {
252
+ "type": "string"
253
+ },
254
+ "position": {
255
+ "type": "array",
256
+ "items": {
257
+ "type": "number"
258
+ }
259
+ },
260
+ "rotation": {
261
+ "type": "array",
262
+ "items": {
263
+ "type": "number"
264
+ }
265
+ },
266
+ "scale": {
267
+ "type": "array",
268
+ "items": {
269
+ "type": "number"
270
+ }
271
+ },
272
+ "color": {
273
+ "type": "string",
274
+ "description": "hex #rrggbb"
275
+ },
276
+ "materialType": {
277
+ "type": "string",
278
+ "enum": [
279
+ "MeshBasicMaterial",
280
+ "MeshStandardMaterial",
281
+ "MeshPhysicalMaterial",
282
+ "MeshPhongMaterial",
283
+ "MeshLambertMaterial",
284
+ "MeshToonMaterial"
285
+ ],
286
+ "description": "swap the material class"
287
+ },
288
+ "materialParams": {
289
+ "type": "object",
290
+ "description": "meshes: numbers (roughness, metalness, opacity, emissiveIntensity, clearcoat, transmission, ior, shininess…, clamped), true/false (wireframe, transparent, flatShading), hex colours (emissive, specular), side (front|back|double), and map = a library image hash (null clears). Each bad key comes back in errors."
291
+ },
292
+ "intensity": {
293
+ "type": "number",
294
+ "description": "lights: brightness (point/spot also fall off with distance)"
295
+ },
296
+ "distance": {
297
+ "type": "number",
298
+ "description": "point/spot: range in metres (0 = unlimited)"
299
+ },
300
+ "decay": {
301
+ "type": "number",
302
+ "description": "point/spot: falloff (2 = physical)"
303
+ },
304
+ "angle": {
305
+ "type": "number",
306
+ "description": "spot: cone half-angle in radians (0.02-1.57)"
307
+ },
308
+ "penumbra": {
309
+ "type": "number",
310
+ "description": "spot: soft edge 0-1"
311
+ },
312
+ "width": {
313
+ "type": "number",
314
+ "description": "rectarea: width in metres"
315
+ },
316
+ "height": {
317
+ "type": "number",
318
+ "description": "rectarea: height in metres"
319
+ },
320
+ "groundColor": {
321
+ "type": "string",
322
+ "description": "hemisphere: the ground colour, hex"
323
+ },
324
+ "castShadow": {
325
+ "type": "boolean",
326
+ "description": "directional/spot/point: cast shadows"
327
+ },
328
+ "aim": {
329
+ "type": "array",
330
+ "items": {
331
+ "type": "number"
332
+ },
333
+ "description": "directional/spot: a world point [x,y,z] to point at"
334
+ },
335
+ "visible": {
336
+ "type": "boolean"
337
+ },
338
+ "parentUuid": {
339
+ "type": "string",
340
+ "description": "group uuid to nest under, or \"root\" to un-nest. The object keeps its world pose; a position given in the same update is LOCAL to the new parent"
341
+ }
342
+ },
343
+ "required": [
344
+ "uuid"
345
+ ]
346
+ }
347
+ }
348
+ },
349
+ "required": [
350
+ "updates"
351
+ ]
352
+ },
353
+ "annotations": {
354
+ "title": "Change objects",
355
+ "readOnlyHint": false,
356
+ "destructiveHint": true,
357
+ "openWorldHint": false
358
+ }
359
+ },
360
+ {
361
+ "name": "delete_objects",
362
+ "title": "Delete objects",
363
+ "description": "Delete objects by uuid.",
364
+ "inputSchema": {
365
+ "type": "object",
366
+ "properties": {
367
+ "uuids": {
368
+ "type": "array",
369
+ "items": {
370
+ "type": "string"
371
+ }
372
+ }
373
+ },
374
+ "required": [
375
+ "uuids"
376
+ ]
377
+ },
378
+ "annotations": {
379
+ "title": "Delete objects",
380
+ "readOnlyHint": false,
381
+ "destructiveHint": true,
382
+ "openWorldHint": false
383
+ }
384
+ },
385
+ {
386
+ "name": "group_objects",
387
+ "title": "Group objects",
388
+ "description": "Create a new group and move the given member objects into it. Returns the group uuid.",
389
+ "inputSchema": {
390
+ "type": "object",
391
+ "properties": {
392
+ "name": {
393
+ "type": "string"
394
+ },
395
+ "memberUuids": {
396
+ "type": "array",
397
+ "items": {
398
+ "type": "string"
399
+ }
400
+ }
401
+ },
402
+ "required": [
403
+ "name",
404
+ "memberUuids"
405
+ ]
406
+ },
407
+ "annotations": {
408
+ "title": "Group objects",
409
+ "readOnlyHint": false,
410
+ "destructiveHint": false,
411
+ "openWorldHint": false
412
+ }
413
+ },
414
+ {
415
+ "name": "clear_scene",
416
+ "title": "Clear the scene",
417
+ "description": "Delete ALL objects in the scene. Requires confirm:true. Use only when the user clearly asks to start over.",
418
+ "inputSchema": {
419
+ "type": "object",
420
+ "properties": {
421
+ "confirm": {
422
+ "type": "boolean"
423
+ }
424
+ },
425
+ "required": [
426
+ "confirm"
427
+ ]
428
+ },
429
+ "annotations": {
430
+ "title": "Clear the scene",
431
+ "readOnlyHint": false,
432
+ "destructiveHint": true,
433
+ "openWorldHint": false
434
+ }
435
+ },
436
+ {
437
+ "name": "create_flow_nodes",
438
+ "title": "Add behaviour (flow) nodes",
439
+ "description": "Add behavior (flow) nodes to a node graph — this is how objects get MOTION and interactivity: spin, bounce, patrol a path, pulse, blink, react to clicks. graph is \"scene\" or an object uuid (ONE graph per call). KEY RULE: a behavior node placed in an OBJECT's graph with no edges automatically drives that object — most behaviors are one node, zero edges, so usually OMIT edges. Describe nodes EITHER as text (graph lines, see the system prompt) OR as nodes/edges arrays. A call with some bad nodes still creates the good ones and returns partial:true + errors[] — resend only what errors[] names.",
440
+ "inputSchema": {
441
+ "type": "object",
442
+ "properties": {
443
+ "graph": {
444
+ "type": "string",
445
+ "description": "\"scene\" or an existing object uuid (nodes in an object's graph drive that object)"
446
+ },
447
+ "text": {
448
+ "type": "string",
449
+ "description": "graph lines: `ref = type \"label\" {params}` per new node (omitted params = defaults), `ref.out -> other.in` per wire (refs or existing node ids). e.g. \"c = onclick\\nl = latch {initial: false}\\nc -> l.set\""
450
+ },
451
+ "nodes": {
452
+ "type": "array",
453
+ "items": {
454
+ "type": "object",
455
+ "properties": {
456
+ "ref": {
457
+ "type": "string",
458
+ "description": "local key so edges in THIS call can reference the new node"
459
+ },
460
+ "type": {
461
+ "type": "string",
462
+ "description": "a node type from the system prompt's Node types"
463
+ },
464
+ "data": {
465
+ "type": "object",
466
+ "description": "node params, e.g. spin {axis:\"y\",speed:2}; bounce {amplitude:0.5,speed:2}; pathpatrol {points:[[x,y,z],…] (>=2 world waypoints), speed:1, mode:\"loop\"|\"pingpong\"}; setcolor {color:\"#ff0000\"}; latch {initial:false}; delay {seconds:3}; sequence {delay1:0,delay2:0.5,delay3:0.5,delay4:0.5} (delays are CUMULATIVE). Omit for defaults."
467
+ }
468
+ },
469
+ "required": [
470
+ "type"
471
+ ]
472
+ }
473
+ },
474
+ "edges": {
475
+ "type": "array",
476
+ "description": "optional wires between nodes (by ref or existing node id). Usually OMIT — unwired behavior nodes already drive their graph's object.",
477
+ "items": {
478
+ "type": "object",
479
+ "properties": {
480
+ "from": {
481
+ "type": "string"
482
+ },
483
+ "to": {
484
+ "type": "string"
485
+ },
486
+ "fromHandle": {
487
+ "type": "string"
488
+ },
489
+ "toHandle": {
490
+ "type": "string"
491
+ }
492
+ },
493
+ "required": [
494
+ "from",
495
+ "to"
496
+ ]
497
+ }
498
+ }
499
+ },
500
+ "required": [
501
+ "graph"
502
+ ]
503
+ },
504
+ "annotations": {
505
+ "title": "Add behaviour (flow) nodes",
506
+ "readOnlyHint": false,
507
+ "destructiveHint": false,
508
+ "openWorldHint": false
509
+ }
510
+ },
511
+ {
512
+ "name": "update_flow_nodes",
513
+ "title": "Change or remove flow nodes",
514
+ "description": "Tune or remove existing behavior nodes in one graph. Node ids and current data are in the scene summary under each object's \"flow\" (and \"sceneFlow\").",
515
+ "inputSchema": {
516
+ "type": "object",
517
+ "properties": {
518
+ "graph": {
519
+ "type": "string",
520
+ "description": "\"scene\" or an object uuid"
521
+ },
522
+ "updates": {
523
+ "type": "array",
524
+ "items": {
525
+ "type": "object",
526
+ "properties": {
527
+ "id": {
528
+ "type": "string"
529
+ },
530
+ "data": {
531
+ "type": "object",
532
+ "description": "data keys to change, e.g. {speed: 3}"
533
+ }
534
+ },
535
+ "required": [
536
+ "id",
537
+ "data"
538
+ ]
539
+ }
540
+ },
541
+ "remove": {
542
+ "type": "array",
543
+ "items": {
544
+ "type": "string"
545
+ },
546
+ "description": "node ids to delete"
547
+ }
548
+ },
549
+ "required": [
550
+ "graph"
551
+ ]
552
+ },
553
+ "annotations": {
554
+ "title": "Change or remove flow nodes",
555
+ "readOnlyHint": false,
556
+ "destructiveHint": true,
557
+ "openWorldHint": false
558
+ }
559
+ },
560
+ {
561
+ "name": "describe_node_types",
562
+ "title": "Look up flow node types",
563
+ "description": "Look up flow node types before wiring them: per type its inputs (toHandle names + socket types), its output type and named outputs (fromHandle), its params with types and defaults, and one line on what it does. No arguments = every type name by group; group = that group with one-line docs; types = full details (up to 12). Changes nothing.",
564
+ "inputSchema": {
565
+ "type": "object",
566
+ "properties": {
567
+ "types": {
568
+ "type": "array",
569
+ "items": {
570
+ "type": "string"
571
+ },
572
+ "description": "node types to describe, e.g. [\"latch\",\"spin\",\"onclick\"]"
573
+ },
574
+ "group": {
575
+ "type": "string",
576
+ "description": "a palette group, e.g. \"Logic\" or \"Triggers\""
577
+ }
578
+ }
579
+ },
580
+ "annotations": {
581
+ "title": "Look up flow node types",
582
+ "readOnlyHint": true,
583
+ "openWorldHint": false
584
+ }
585
+ },
586
+ {
587
+ "name": "create_behaviour",
588
+ "title": "Add a code behaviour",
589
+ "description": "Create a code behaviour: game logic (rules, waves, scoring, timers, win/lose) as ONE JavaScript module, run on the session authority and replicated. Prefer it over large node graphs for game rules. Linted before it is applied; problems come back with line numbers. The result names the node id and its sockets — wire them with create_flow_nodes (an On Click / On Enter into an input; an output into HUD Text, Visibility, Announce…).\nBEHAVIOURS (create_behaviour / edit_behaviour): game logic as ONE JavaScript module, run on ONE peer (the session authority); `state` replicates to everyone.\nexport default behaviour({ name, params: {k: literal | {value, min, max, step, unit}}, state: {plain JSON}, inputs: [names], outputs: [names], on: {event(payload) {...}}, ...methods });\ninputs: a trigger wired into input \"x\" runs on.x. outputs: a state field = a value output, any other name = an event fired by this.emit(\"name\").\nA behaviour does NOT move, rotate, recolour or hide objects (no such this.*): it drives effect nodes (spin, visibility, setcolor…) via its outputs. NO per-frame hook (no onTick/update): repeat with this.after(seconds, \"method\").\nthis: params, state, kit (also bare `kit`), after(seconds, \"method\", ...args) (survives the host leaving), cancel(key), rand(), randInt(a,b), pick(list), now(), find(glob) / findAll(glob) -> {uuid, name, pos, tags}, object, log() — always as this.find(…) etc. (only `kit` and the helpers are bare). Helpers: dist(a,b), clamp, lerp.\nevents: load (runs on EVERY peer when the behaviour loads — local, state read-only: register things there), start (once, on the authority); grabRequest({piece, distance, hand, refuse}) (runs on the grabbing peer, read-only: call refuse(reason)); any kit event as \"piece.event\" or roundStart, go, won, lost, paused, resumed, menu, levelSelected, scored, collected, allCollected, spawned, despawned, emptied, damaged, healed, died, revived, stuck, grabRefused. Entity payloads have entity.is(glob), entity.hasTag(t).\nkit calls: kit.round.start/win(reason)/lose(reason), kit.score.add(n), kit.spawner.spawn({kind, template: uuid, at, count, hp, mover: {speed}}), kit.health.damage(id, n), kit.mover.chase(kind, \"nearestPlayer\"), kit.rules.set({reach, jump}), kit.pickups.register({id, score, respawn, radius}) — register pickups in on.load (every peer must know them, or touching one does nothing), never in start.\nForbidden (the load is refused): Math.random, Date / Date.now, performance.now, localStorage, document/window/globalThis, fetch, eval, setTimeout/setInterval, import. Unknown this.* members, unknown events and hook-named methods nothing calls are refused too.\nEXAMPLE (passes the lint; wire a trigger into input gemTouched, output left into a HUD Text, timeUp into a Visibility or Announce):\nexport default behaviour({\n name: 'Countdown',\n params: { seconds: 30 },\n state: { left: 30, found: 0 },\n inputs: ['gemTouched'],\n outputs: ['left', 'found', 'timeUp'],\n on: {\n start() { this.state.left = this.params.seconds; this.after(1, 'tick'); },\n gemTouched() { this.state.found += 1; }\n },\n tick() {\n this.state.left -= 1;\n if (this.state.left > 0) this.after(1, 'tick');\n else this.emit('timeUp');\n }\n});",
590
+ "inputSchema": {
591
+ "type": "object",
592
+ "properties": {
593
+ "name": {
594
+ "type": "string",
595
+ "description": "a short unique name, letters/digits/_/-"
596
+ },
597
+ "target": {
598
+ "type": "string",
599
+ "description": "\"scene\" (default) or an object uuid the behaviour belongs to"
600
+ },
601
+ "source": {
602
+ "type": "string",
603
+ "description": "the whole behaviour module source (plain JavaScript, the format in create_behaviour's description). Must be deterministic: no Math.random (use rand()), no Date.now/new Date (use the session clock), no DOM, no localStorage, no timers (use this.after), no loop without an exit."
604
+ }
605
+ },
606
+ "required": [
607
+ "name",
608
+ "source"
609
+ ]
610
+ },
611
+ "annotations": {
612
+ "title": "Add a code behaviour",
613
+ "readOnlyHint": false,
614
+ "destructiveHint": false,
615
+ "openWorldHint": false
616
+ }
617
+ },
618
+ {
619
+ "name": "edit_behaviour",
620
+ "title": "Edit a code behaviour",
621
+ "description": "Replace an existing behaviour's source (the scene summary lists them; read the current source with the name). Linted before it is applied. Same format as create_behaviour (see its description).",
622
+ "inputSchema": {
623
+ "type": "object",
624
+ "properties": {
625
+ "name": {
626
+ "type": "string"
627
+ },
628
+ "source": {
629
+ "type": "string",
630
+ "description": "the whole behaviour module source (plain JavaScript, the format in create_behaviour's description). Must be deterministic: no Math.random (use rand()), no Date.now/new Date (use the session clock), no DOM, no localStorage, no timers (use this.after), no loop without an exit."
631
+ }
632
+ },
633
+ "required": [
634
+ "name",
635
+ "source"
636
+ ]
637
+ },
638
+ "annotations": {
639
+ "title": "Edit a code behaviour",
640
+ "readOnlyHint": false,
641
+ "destructiveHint": true,
642
+ "openWorldHint": false
643
+ }
644
+ },
645
+ {
646
+ "name": "set_physics",
647
+ "title": "Set physics",
648
+ "description": "Set physics body params on objects (batch). NOTE: objects made with create_objects are STATIC unless created with physics \"dynamic\" or a mass — use this to make existing objects dynamic, to TUNE bodies, or to pin scenery (mode \"static\"). mode \"auto\" reverts to scenery defaults.",
649
+ "inputSchema": {
650
+ "type": "object",
651
+ "properties": {
652
+ "updates": {
653
+ "type": "array",
654
+ "items": {
655
+ "type": "object",
656
+ "properties": {
657
+ "uuid": {
658
+ "type": "string"
659
+ },
660
+ "mode": {
661
+ "type": "string",
662
+ "enum": [
663
+ "auto",
664
+ "static",
665
+ "dynamic"
666
+ ]
667
+ },
668
+ "mass": {
669
+ "type": "number",
670
+ "description": "kg; implies mode dynamic"
671
+ },
672
+ "restitution": {
673
+ "type": "number",
674
+ "description": "bounciness 0..1"
675
+ },
676
+ "friction": {
677
+ "type": "number",
678
+ "description": "0..2"
679
+ },
680
+ "collider": {
681
+ "type": "string",
682
+ "enum": [
683
+ "box",
684
+ "sphere",
685
+ "capsule",
686
+ "cylinder",
687
+ "hull",
688
+ "trimesh"
689
+ ],
690
+ "description": "trimesh = the exact mesh, for static scenery (tunnels, pools, arches); a dynamic body gets its hull"
691
+ },
692
+ "sensor": {
693
+ "type": "boolean",
694
+ "description": "true = a trigger volume: no collision response, fires On Enter/On Exit nodes"
695
+ },
696
+ "group": {
697
+ "type": "string",
698
+ "enum": [
699
+ "default",
700
+ "a",
701
+ "b",
702
+ "c",
703
+ "d",
704
+ "water"
705
+ ],
706
+ "description": "collision group; water = a pass-through trigger volume"
707
+ },
708
+ "collidesWith": {
709
+ "type": "array",
710
+ "items": {
711
+ "type": "string",
712
+ "enum": [
713
+ "default",
714
+ "a",
715
+ "b",
716
+ "c",
717
+ "d",
718
+ "water",
719
+ "player"
720
+ ]
721
+ },
722
+ "description": "groups this object collides with (omit = all); leave out player for a ghost wall the player walks through"
723
+ }
724
+ },
725
+ "required": [
726
+ "uuid"
727
+ ]
728
+ }
729
+ }
730
+ },
731
+ "required": [
732
+ "updates"
733
+ ]
734
+ },
735
+ "annotations": {
736
+ "title": "Set physics",
737
+ "readOnlyHint": false,
738
+ "destructiveHint": true,
739
+ "openWorldHint": false
740
+ }
741
+ },
742
+ {
743
+ "name": "create_joints",
744
+ "title": "Add physics joints",
745
+ "description": "Attach object pairs with physics joints: \"fixed\" welds them rigidly, \"revolute\" hinges around an axis (optional motor spins it). Move parts to their final pose WORLD-ALIGNED (no rotation) before jointing. Without anchor a hinge sits at b's origin (its centre) and a weld midway: pass anchor [x,y,z] (world) for the real pivot, e.g. the top of a pendulum rod or a door's edge. Jointed bodies that touch (a door in its frame, a wheel in its arch) also need contacts:false, or the solver pushes them apart.",
746
+ "inputSchema": {
747
+ "type": "object",
748
+ "properties": {
749
+ "joints": {
750
+ "type": "array",
751
+ "items": {
752
+ "type": "object",
753
+ "properties": {
754
+ "kind": {
755
+ "type": "string",
756
+ "enum": [
757
+ "fixed",
758
+ "revolute"
759
+ ]
760
+ },
761
+ "a": {
762
+ "type": "string",
763
+ "description": "uuid of the base object"
764
+ },
765
+ "b": {
766
+ "type": "string",
767
+ "description": "uuid of the attached object (hinge anchors at its origin)"
768
+ },
769
+ "axis": {
770
+ "type": "string",
771
+ "enum": [
772
+ "x",
773
+ "y",
774
+ "z"
775
+ ],
776
+ "description": "hinge axis (revolute only, default y)"
777
+ },
778
+ "anchor": {
779
+ "type": "array",
780
+ "items": {
781
+ "type": "number"
782
+ },
783
+ "description": "world [x,y,z] of the pivot/weld point (default: b's origin for a hinge, midway for a weld)"
784
+ },
785
+ "contacts": {
786
+ "type": "boolean",
787
+ "description": "false = the two bodies stop colliding with each other (default true)"
788
+ },
789
+ "limits": {
790
+ "type": "array",
791
+ "items": {
792
+ "type": "number"
793
+ },
794
+ "description": "revolute only: [min, max] radians the hinge may turn"
795
+ },
796
+ "motor": {
797
+ "type": "object",
798
+ "description": "revolute only: {vel: rad/s, maxForce}",
799
+ "properties": {
800
+ "vel": {
801
+ "type": "number"
802
+ },
803
+ "maxForce": {
804
+ "type": "number"
805
+ }
806
+ }
807
+ }
808
+ },
809
+ "required": [
810
+ "kind",
811
+ "a",
812
+ "b"
813
+ ]
814
+ }
815
+ }
816
+ },
817
+ "required": [
818
+ "joints"
819
+ ]
820
+ },
821
+ "annotations": {
822
+ "title": "Add physics joints",
823
+ "readOnlyHint": false,
824
+ "destructiveHint": false,
825
+ "openWorldHint": false
826
+ }
827
+ },
828
+ {
829
+ "name": "control_simulation",
830
+ "title": "Run or stop the simulation",
831
+ "description": "Start, stop, pause, resume or reset the physics simulation. After building a physics assembly, start the simulation so the user sees it move.",
832
+ "inputSchema": {
833
+ "type": "object",
834
+ "properties": {
835
+ "action": {
836
+ "type": "string",
837
+ "enum": [
838
+ "start",
839
+ "stop",
840
+ "pause",
841
+ "resume",
842
+ "reset"
843
+ ]
844
+ }
845
+ },
846
+ "required": [
847
+ "action"
848
+ ]
849
+ },
850
+ "annotations": {
851
+ "title": "Run or stop the simulation",
852
+ "readOnlyHint": false,
853
+ "destructiveHint": true,
854
+ "openWorldHint": false
855
+ }
856
+ },
857
+ {
858
+ "name": "import_model",
859
+ "title": "Import model",
860
+ "description": "Import a self-contained GLB (binary glTF 2.0, everything embedded, ≤ 25 MB) into the scene through the app's own import pipeline (auto-optimize, LOD, budget check). Named nodes are kept as a hierarchy and returned with uuids. One undo step; syncs to every peer; provenance is recorded. Use for models built outside the app (e.g. an img2threejs rebuild exported with named pivots). Materials: standard PBR only — custom shaders do not survive glTF.",
861
+ "inputSchema": {
862
+ "type": "object",
863
+ "properties": {
864
+ "path": {
865
+ "type": "string",
866
+ "description": "a local .glb path on the agent's machine (the bridge reads it)"
867
+ },
868
+ "data": {
869
+ "type": "string",
870
+ "description": "the GLB as base64 (alternative to path/url)"
871
+ },
872
+ "url": {
873
+ "type": "string",
874
+ "description": "an http(s) URL of a .glb (the host may block other origins)"
875
+ },
876
+ "name": {
877
+ "type": "string"
878
+ },
879
+ "position": {
880
+ "type": "array",
881
+ "items": {
882
+ "type": "number"
883
+ },
884
+ "description": "[x,y,z] metres (local to parent when given)"
885
+ },
886
+ "rotation": {
887
+ "type": "array",
888
+ "items": {
889
+ "type": "number"
890
+ },
891
+ "description": "[x,y,z] degrees, XYZ order"
892
+ },
893
+ "scale": {
894
+ "description": "a number or [x,y,z]"
895
+ },
896
+ "parent": {
897
+ "type": "string",
898
+ "description": "uuid (or exact name) of the object to parent under"
899
+ },
900
+ "source": {
901
+ "type": "string",
902
+ "description": "where the model came from, e.g. \"img2threejs from photo campfire.jpg\""
903
+ },
904
+ "licence": {
905
+ "type": "string",
906
+ "description": "its licence, e.g. CC0, CC-BY-4.0, \"own work\""
907
+ },
908
+ "prompt": {
909
+ "type": "string",
910
+ "description": "the request it was made for"
911
+ }
912
+ }
913
+ },
914
+ "annotations": {
915
+ "title": "Import model",
916
+ "readOnlyHint": false,
917
+ "destructiveHint": true,
918
+ "openWorldHint": false
919
+ }
920
+ },
921
+ {
922
+ "name": "import_file",
923
+ "title": "Import file",
924
+ "description": "Bring a file made on your machine into the session: PNG / WebP images (≤ 16 MB, metadata stripped), JSON (≤ 2 MB) or a .tpanim clip. The type is read from the bytes. It lands in the Explorer (folder \"Generated\" unless `folder`), shared with every peer. Pass the generator's input as `spec` and it is saved beside the file as <name>.<generator>.json (e.g. knight.png + knight.pixling.json) so it can be regenerated. An image is also placed in the scene as a card (a plane; `place: false` to skip): `filter: \"nearest\"` keeps pixel art crisp (default when a spec is given), `frame: {w, h, col, row}` shows one frame of a sprite sheet, `facing: \"camera-y\"` turns it to face each viewer. One undo step; editors and admins only. Large files: send base64 parts with `chunk: {id, index, total}` (the bridge does this for you with `path`).",
925
+ "inputSchema": {
926
+ "type": "object",
927
+ "properties": {
928
+ "path": {
929
+ "type": "string",
930
+ "description": "a local file on the agent's machine (the bridge reads it)"
931
+ },
932
+ "data": {
933
+ "type": "string",
934
+ "description": "the file as base64 (or this chunk's part of it)"
935
+ },
936
+ "chunk": {
937
+ "type": "object",
938
+ "description": "a chunked upload: every part has the same id; the import runs when the last part arrives",
939
+ "properties": {
940
+ "id": {
941
+ "type": "string"
942
+ },
943
+ "index": {
944
+ "type": "number"
945
+ },
946
+ "total": {
947
+ "type": "number"
948
+ }
949
+ }
950
+ },
951
+ "name": {
952
+ "type": "string",
953
+ "description": "the file name in the Explorer (its extension comes from the bytes)"
954
+ },
955
+ "folder": {
956
+ "type": "string",
957
+ "description": "Explorer folder, \"A/B\" for nested (default \"Generated\")"
958
+ },
959
+ "spec": {
960
+ "description": "the generator input (JSON object or string), saved beside the file"
961
+ },
962
+ "spec_path": {
963
+ "type": "string",
964
+ "description": "the spec as a local .json file (the bridge reads it)"
965
+ },
966
+ "generator": {
967
+ "type": "string",
968
+ "description": "names the spec file, default \"pixling\""
969
+ },
970
+ "place": {
971
+ "type": "boolean",
972
+ "description": "images: place a card in the scene (default true)"
973
+ },
974
+ "position": {
975
+ "type": "array",
976
+ "items": {
977
+ "type": "number"
978
+ },
979
+ "description": "[x,y,z] metres (default: standing on the ground at the origin)"
980
+ },
981
+ "rotation": {
982
+ "type": "array",
983
+ "items": {
984
+ "type": "number"
985
+ },
986
+ "description": "[x,y,z] degrees"
987
+ },
988
+ "height": {
989
+ "type": "number",
990
+ "description": "card height in metres (default 1; width follows the aspect)"
991
+ },
992
+ "width": {
993
+ "type": "number",
994
+ "description": "card width in metres (instead of height)"
995
+ },
996
+ "frame": {
997
+ "type": "object",
998
+ "description": "show one frame of a sheet: w/h in pixels, col/row from 0 (row 0 = top)",
999
+ "properties": {
1000
+ "w": {
1001
+ "type": "number"
1002
+ },
1003
+ "h": {
1004
+ "type": "number"
1005
+ },
1006
+ "col": {
1007
+ "type": "number"
1008
+ },
1009
+ "row": {
1010
+ "type": "number"
1011
+ }
1012
+ }
1013
+ },
1014
+ "filter": {
1015
+ "type": "string",
1016
+ "enum": [
1017
+ "auto",
1018
+ "nearest",
1019
+ "linear"
1020
+ ],
1021
+ "description": "texture filtering (auto = nearest for pixel art)"
1022
+ },
1023
+ "facing": {
1024
+ "type": "string",
1025
+ "enum": [
1026
+ "none",
1027
+ "camera",
1028
+ "camera-y"
1029
+ ],
1030
+ "description": "billboard: face each viewer (camera-y = turn about Y only)"
1031
+ },
1032
+ "lit": {
1033
+ "type": "boolean",
1034
+ "description": "shade the card with the scene lights (default unlit, like a sprite)"
1035
+ },
1036
+ "source": {
1037
+ "type": "string",
1038
+ "description": "where it came from, e.g. \"pixling 0.3.1 build specs/knight.json\""
1039
+ },
1040
+ "licence": {
1041
+ "type": "string",
1042
+ "description": "its licence, e.g. CC0, \"own work\""
1043
+ },
1044
+ "prompt": {
1045
+ "type": "string",
1046
+ "description": "the request it was made for"
1047
+ }
1048
+ }
1049
+ },
1050
+ "annotations": {
1051
+ "title": "Import file",
1052
+ "readOnlyHint": false,
1053
+ "destructiveHint": true,
1054
+ "openWorldHint": false
1055
+ }
1056
+ },
1057
+ {
1058
+ "name": "install_modules",
1059
+ "title": "Install modules",
1060
+ "description": "Install the modules the other peers in this session run and this page lacks (status().missingModules), from the module gallery. Local to this page; omit ids to install every missing one.",
1061
+ "inputSchema": {
1062
+ "type": "object",
1063
+ "properties": {
1064
+ "ids": {
1065
+ "type": "array",
1066
+ "items": {
1067
+ "type": "string"
1068
+ },
1069
+ "description": "module ids; omit for all missing"
1070
+ }
1071
+ }
1072
+ },
1073
+ "annotations": {
1074
+ "title": "Install modules",
1075
+ "readOnlyHint": false,
1076
+ "destructiveHint": false,
1077
+ "openWorldHint": true
1078
+ }
1079
+ },
1080
+ {
1081
+ "name": "pending_offers",
1082
+ "title": "List questions the app is asking",
1083
+ "description": "List the questions the app is asking this page (an admin-role offer, \"share or stash your scene\"…), each with its buttons. Answer one with accept_offer.",
1084
+ "inputSchema": {
1085
+ "type": "object",
1086
+ "properties": {}
1087
+ },
1088
+ "annotations": {
1089
+ "title": "List questions the app is asking",
1090
+ "readOnlyHint": true,
1091
+ "openWorldHint": false
1092
+ }
1093
+ },
1094
+ {
1095
+ "name": "accept_offer",
1096
+ "title": "Answer a question the app asks",
1097
+ "description": "Press a button on a pending offer (see pending_offers). action = the button label; omit for the first button.",
1098
+ "inputSchema": {
1099
+ "type": "object",
1100
+ "properties": {
1101
+ "id": {
1102
+ "type": "string"
1103
+ },
1104
+ "action": {
1105
+ "type": "string"
1106
+ }
1107
+ },
1108
+ "required": [
1109
+ "id"
1110
+ ]
1111
+ },
1112
+ "annotations": {
1113
+ "title": "Answer a question the app asks",
1114
+ "readOnlyHint": false,
1115
+ "destructiveHint": true,
1116
+ "openWorldHint": false
1117
+ }
1118
+ },
1119
+ {
1120
+ "name": "what_can_you_do",
1121
+ "title": "What can you do here",
1122
+ "description": "Answer \"what can you do here?\": a short spoken summary of what you can build in this tab, what you cannot do, and example requests. Changes nothing.",
1123
+ "inputSchema": {
1124
+ "type": "object",
1125
+ "properties": {}
1126
+ },
1127
+ "annotations": {
1128
+ "title": "What can you do here",
1129
+ "readOnlyHint": true,
1130
+ "openWorldHint": false
1131
+ }
1132
+ },
1133
+ {
1134
+ "name": "connect",
1135
+ "title": "Join a session",
1136
+ "description": "Join a theprototype.app session. Pass the link the host shared (https://theprototype.app/#F0FD3, possibly with &invite=…) or the bare session id. An invite link joins at once; a plain link asks the host to Approve. The scene tools appear once the agent page is up; call status to see whether you are in.",
1137
+ "inputSchema": {
1138
+ "type": "object",
1139
+ "properties": {
1140
+ "link": {
1141
+ "type": "string",
1142
+ "description": "session link or id"
1143
+ },
1144
+ "peer_id": {
1145
+ "type": "string",
1146
+ "description": "older name for link"
1147
+ }
1148
+ }
1149
+ },
1150
+ "annotations": {
1151
+ "title": "Join a session",
1152
+ "readOnlyHint": false,
1153
+ "destructiveHint": false,
1154
+ "openWorldHint": true
1155
+ }
1156
+ },
1157
+ {
1158
+ "name": "status",
1159
+ "title": "Session status",
1160
+ "description": "Where the agent is: connection state, whether the host has let it in, its role (viewer edits stay local), the app version, and why any tool is missing.",
1161
+ "inputSchema": {
1162
+ "type": "object",
1163
+ "properties": {}
1164
+ },
1165
+ "annotations": {
1166
+ "title": "Session status",
1167
+ "readOnlyHint": true,
1168
+ "openWorldHint": false
1169
+ }
1170
+ },
1171
+ {
1172
+ "name": "screenshot",
1173
+ "title": "Take a picture of the scene",
1174
+ "description": "See the scene: a PNG of a clean frame (no gizmos) from a temporary camera, never moving anyone's view. view: current | top | front | iso | focus (focus needs uuid).",
1175
+ "inputSchema": {
1176
+ "type": "object",
1177
+ "properties": {
1178
+ "view": {
1179
+ "type": "string",
1180
+ "enum": [
1181
+ "current",
1182
+ "top",
1183
+ "front",
1184
+ "iso",
1185
+ "focus"
1186
+ ]
1187
+ },
1188
+ "uuid": {
1189
+ "type": "string",
1190
+ "description": "object to frame (view focus)"
1191
+ },
1192
+ "size": {
1193
+ "type": "number",
1194
+ "description": "longest side in pixels"
1195
+ }
1196
+ }
1197
+ },
1198
+ "annotations": {
1199
+ "title": "Take a picture of the scene",
1200
+ "readOnlyHint": true,
1201
+ "openWorldHint": false
1202
+ }
1203
+ },
1204
+ {
1205
+ "name": "wait_for_edit",
1206
+ "title": "Wait for someone to edit",
1207
+ "description": "Wait until someone in the session changes the scene (or the timeout passes), so you can react to the humans.",
1208
+ "inputSchema": {
1209
+ "type": "object",
1210
+ "properties": {
1211
+ "timeout_ms": {
1212
+ "type": "number",
1213
+ "description": "default 30000, at most 120000"
1214
+ }
1215
+ }
1216
+ },
1217
+ "annotations": {
1218
+ "title": "Wait for someone to edit",
1219
+ "readOnlyHint": true,
1220
+ "openWorldHint": false
1221
+ }
1222
+ }
1223
+ ]
1224
+ }