@theprototype/agent 1.52.0 → 1.53.1

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 CHANGED
@@ -3,15 +3,24 @@
3
3
  "format": 2,
4
4
  "generator": {
5
5
  "source": "tpAgent.tools() via core scripts/agent-tool-catalog.cjs, mapped by tools/agent/src/catalog.js",
6
- "hostTools": 21,
6
+ "hostTools": 23,
7
7
  "dropped": []
8
8
  },
9
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).\nYou act as this person in their own tab: status \"you\" is them (their peer id, name and role); the other users are everyone else.\nCall list_scene before editing so you use real uuids; batch many objects into one call.\nFor \"in front of me\", \"here\" or \"this\", call where_are_users and build at the spots it gives, facing the person.\nMotion and interaction are flow nodes (create_flow_nodes); call describe_node_types before wiring edges.\nTake a screenshot after each change to check your work.\nWhen asked what you can do here, call what_can_you_do and say its \"say\" line.",
10
+ "connectorExcluded": [
11
+ "accept_offer",
12
+ "connect",
13
+ "import_file",
14
+ "import_model",
15
+ "install_modules",
16
+ "leave",
17
+ "pending_offers"
18
+ ],
10
19
  "tools": [
11
20
  {
12
21
  "name": "list_scene",
13
22
  "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.\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 list_scene before editing so you use real uuids; batch many objects into one call.",
23
+ "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}. The transform is the AUTHORED pose: an object an effect is moving also has animated: true, and live {position, rotation, scale, visible} when this frame differs; update_objects writes the authored pose, so never copy live back into it. 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.\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 list_scene before editing so you use real uuids; batch many objects into one call.",
15
24
  "inputSchema": {
16
25
  "type": "object",
17
26
  "properties": {
@@ -236,7 +245,7 @@
236
245
  {
237
246
  "name": "update_objects",
238
247
  "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.\nCall list_scene before editing so you use real uuids; batch many objects into one call.\nTake a screenshot after each change to check your work.",
248
+ "description": "Modify existing objects by uuid: move/rotate/scale, recolor, change material, rename, show/hide, or reparent. Batch many in one call. On an object an effect is moving, a transform sets its authored pose (the one list_scene reports) and the effect carries on from there.\nCall list_scene before editing so you use real uuids; batch many objects into one call.\nTake a screenshot after each change to check your work.",
240
249
  "inputSchema": {
241
250
  "type": "object",
242
251
  "properties": {
@@ -529,7 +538,7 @@
529
538
  {
530
539
  "name": "create_flow_nodes",
531
540
  "title": "Add behaviour (flow) nodes",
532
- "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.\nMotion and interaction are flow nodes (create_flow_nodes); call describe_node_types before wiring edges.\nTake a screenshot after each change to check your work.",
541
+ "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. A script node runs every frame FROM THE BASE POSE, so object.rotation.y += 0.02 lands at base + 0.02 and stays: write object.rotation.y = base.rot[1] + time * speed (base has pos, rot, scale as arrays). 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.\nMotion and interaction are flow nodes (create_flow_nodes); call describe_node_types before wiring edges.\nTake a screenshot after each change to check your work.",
533
542
  "inputSchema": {
534
543
  "type": "object",
535
544
  "properties": {
@@ -604,7 +613,7 @@
604
613
  {
605
614
  "name": "update_flow_nodes",
606
615
  "title": "Change or remove flow nodes",
607
- "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\").\nTake a screenshot after each change to check your work.",
616
+ "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\"). Script code runs every frame from the base pose: use object.rotation.y = base.rot[1] + time * speed, not +=.\nTake a screenshot after each change to check your work.",
608
617
  "inputSchema": {
609
618
  "type": "object",
610
619
  "properties": {
@@ -949,7 +958,7 @@
949
958
  },
950
959
  {
951
960
  "name": "import_model",
952
- "title": "Import model",
961
+ "title": "Import a 3D model",
953
962
  "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.\nTake a screenshot after each change to check your work.",
954
963
  "inputSchema": {
955
964
  "type": "object",
@@ -1005,15 +1014,15 @@
1005
1014
  }
1006
1015
  },
1007
1016
  "annotations": {
1008
- "title": "Import model",
1017
+ "title": "Import a 3D model",
1009
1018
  "readOnlyHint": false,
1010
- "destructiveHint": true,
1019
+ "destructiveHint": false,
1011
1020
  "openWorldHint": false
1012
1021
  }
1013
1022
  },
1014
1023
  {
1015
1024
  "name": "import_file",
1016
- "title": "Import file",
1025
+ "title": "Import a file",
1017
1026
  "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`).\nTake a screenshot after each change to check your work.",
1018
1027
  "inputSchema": {
1019
1028
  "type": "object",
@@ -1141,9 +1150,9 @@
1141
1150
  }
1142
1151
  },
1143
1152
  "annotations": {
1144
- "title": "Import file",
1153
+ "title": "Import a file",
1145
1154
  "readOnlyHint": false,
1146
- "destructiveHint": true,
1155
+ "destructiveHint": false,
1147
1156
  "openWorldHint": false
1148
1157
  }
1149
1158
  },
@@ -1223,6 +1232,63 @@
1223
1232
  "openWorldHint": false
1224
1233
  }
1225
1234
  },
1235
+ {
1236
+ "name": "save_scene",
1237
+ "title": "Save the scene",
1238
+ "description": "Save the open scene as a new version in the person's Library, the same save as Ctrl+S; Version history can restore older ones. In someone else's session it saves on the host. Viewers cannot save, and a private scene cannot be saved this way.\nTake a screenshot after each change to check your work.",
1239
+ "inputSchema": {
1240
+ "type": "object",
1241
+ "properties": {
1242
+ "name": {
1243
+ "type": "string",
1244
+ "description": "scene name; omit to keep the open scene's name. Ignored when saving on a host."
1245
+ },
1246
+ "folder": {
1247
+ "type": "string",
1248
+ "description": "Library folder name or id; omit for the Library root. Ignored when saving on a host."
1249
+ }
1250
+ }
1251
+ },
1252
+ "annotations": {
1253
+ "title": "Save the scene",
1254
+ "readOnlyHint": false,
1255
+ "destructiveHint": false,
1256
+ "openWorldHint": false
1257
+ }
1258
+ },
1259
+ {
1260
+ "name": "generate_mesh",
1261
+ "title": "Generate a 3D model",
1262
+ "description": "Generate a NEW custom 3D mesh from a text description when no primitive fits (an organic prop, a detailed object, a character). SLOW (~1-3 min) and async — it returns immediately and the mesh appears in the scene when ready. Use sparingly; prefer primitives for simple/blocky shapes.\nTake a screenshot after each change to check your work.",
1263
+ "inputSchema": {
1264
+ "type": "object",
1265
+ "properties": {
1266
+ "prompt": {
1267
+ "type": "string",
1268
+ "description": "what to generate, e.g. \"a weathered wooden treasure chest\""
1269
+ },
1270
+ "name": {
1271
+ "type": "string"
1272
+ },
1273
+ "position": {
1274
+ "type": "array",
1275
+ "items": {
1276
+ "type": "number"
1277
+ },
1278
+ "description": "[x,y,z]"
1279
+ }
1280
+ },
1281
+ "required": [
1282
+ "prompt"
1283
+ ]
1284
+ },
1285
+ "annotations": {
1286
+ "title": "Generate a 3D model",
1287
+ "readOnlyHint": false,
1288
+ "destructiveHint": false,
1289
+ "openWorldHint": true
1290
+ }
1291
+ },
1226
1292
  {
1227
1293
  "name": "connect",
1228
1294
  "title": "Join a session",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theprototype/agent",
3
- "version": "1.52.0",
3
+ "version": "1.53.1",
4
4
  "description": "Let an AI agent (Claude Code, Claude Desktop, any MCP host) join a theprototype.app session from its link and build the 3D scene with you.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/catalog.js CHANGED
@@ -33,6 +33,9 @@ export const TOOL_ANNOTATIONS = /** @type {Record<string, {title: string, read?:
33
33
  create_joints: { title: 'Add physics joints', destructive: false },
34
34
  group_objects: { title: 'Group objects', destructive: false },
35
35
  generate_mesh: { title: 'Generate a 3D model', destructive: false, openWorld: true },
36
+ save_scene: { title: 'Save the scene', destructive: false },
37
+ import_model: { title: 'Import a 3D model', destructive: false },
38
+ import_file: { title: 'Import a file', destructive: false },
36
39
  update_objects: { title: 'Change objects', destructive: true },
37
40
  update_flow_nodes: { title: 'Change or remove flow nodes', destructive: true },
38
41
  edit_behaviour: { title: 'Edit a code behaviour', destructive: true },
@@ -157,7 +160,7 @@ export function guidanceGaps(catalog) {
157
160
  /**
158
161
  * Host tools (tpAgent.tools()'s shape) → the static catalog.
159
162
  * @param {any[]} hostTools
160
- * @param {{provenance?: Record<string, any>}} [opts]
163
+ * @param {{provenance?: Record<string, any>, connectorExcluded?: string[]}} [opts]
161
164
  * @returns {{format: number, generator: Record<string, any>, instructions: string, tools: {name: string, title: string, description: string, inputSchema: any, annotations: Record<string, any>}[]}}
162
165
  */
163
166
  export function buildCatalog(hostTools, opts = {}) {
@@ -174,6 +177,26 @@ export function buildCatalog(hostTools, opts = {}) {
174
177
  ...(opts.provenance ?? {})
175
178
  },
176
179
  instructions: connectorInstructions(tools.map((t) => t.name)),
180
+ // mcp-fix: what the connector leaves out, on purpose (core's CONNECTOR_BLOCKED_TOOLS); the Worker checks against it
181
+ ...(opts.connectorExcluded ? { connectorExcluded: [...opts.connectorExcluded].sort() } : {}),
177
182
  tools
178
183
  };
179
184
  }
185
+
186
+ /**
187
+ * mcp-fix: the tab's list and the catalog must agree. Fails on a tool a connector tab answers that the catalog neither
188
+ * lists nor leaves out on purpose, and on a listed tool no connector tab can answer. `tabTools` comes from a real
189
+ * createConnectorAgent(); `conditional` = tools the tab offers only sometimes (status.missingTools says why).
190
+ * @param {{tabTools: string[], own: string[], conditional?: string[], catalogTools: string[], excluded: string[]}} facts
191
+ * @returns {string[]} the problems, in words
192
+ */
193
+ export function connectorToolGaps(facts) {
194
+ const { tabTools, own, conditional = [], catalogTools, excluded } = facts;
195
+ const out = new Set(excluded);
196
+ const listed = new Set(catalogTools.filter((n) => !out.has(n)));
197
+ const answered = new Set([...tabTools, ...own, ...conditional].filter((n) => !out.has(n)));
198
+ return [
199
+ ...[...answered].filter((n) => !listed.has(n)).map((n) => n + ': a connector tab answers it, but it is neither in the catalog nor in CONNECTOR_BLOCKED_TOOLS'),
200
+ ...[...listed].filter((n) => !answered.has(n)).map((n) => n + ': in the catalog, but no connector tab answers it')
201
+ ];
202
+ }