@theprototype/agent 1.53.1 → 1.54.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 CHANGED
@@ -538,7 +538,7 @@
538
538
  {
539
539
  "name": "create_flow_nodes",
540
540
  "title": "Add behaviour (flow) nodes",
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.",
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] + elapsed * speed (base has pos, rot, scale as arrays; elapsed = seconds since Play started, time = the shared clock, seconds of the day, a large number). Particle gravity is the acceleration along world Y: negative falls, positive rises. 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.",
542
542
  "inputSchema": {
543
543
  "type": "object",
544
544
  "properties": {
@@ -613,7 +613,7 @@
613
613
  {
614
614
  "name": "update_flow_nodes",
615
615
  "title": "Change or remove flow nodes",
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.",
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] + elapsed * speed, not += (elapsed = seconds since Play started, time = the shared clock, seconds of the day).\nTake a screenshot after each change to check your work.",
617
617
  "inputSchema": {
618
618
  "type": "object",
619
619
  "properties": {
@@ -1330,7 +1330,7 @@
1330
1330
  {
1331
1331
  "name": "screenshot",
1332
1332
  "title": "Take a picture of the scene",
1333
- "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).",
1333
+ "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 or uuids); from = an angle or {yaw, pitch}; camera = a free {position, target}. 4:3 unless aspect says otherwise (current follows the person's window). Answers with the camera it used.",
1334
1334
  "inputSchema": {
1335
1335
  "type": "object",
1336
1336
  "properties": {
@@ -1346,7 +1346,93 @@
1346
1346
  },
1347
1347
  "uuid": {
1348
1348
  "type": "string",
1349
- "description": "object to frame (view focus)"
1349
+ "description": "object to frame"
1350
+ },
1351
+ "uuids": {
1352
+ "type": "array",
1353
+ "items": {
1354
+ "type": "string"
1355
+ },
1356
+ "description": "objects to frame together"
1357
+ },
1358
+ "from": {
1359
+ "description": "the angle around the subject, instead of view: front, back, left, right, top, below, iso, or {yaw, pitch} in degrees (yaw 0 = front, 90 = right; pitch 90 = from above)",
1360
+ "anyOf": [
1361
+ {
1362
+ "type": "string",
1363
+ "enum": [
1364
+ "front",
1365
+ "back",
1366
+ "left",
1367
+ "right",
1368
+ "top",
1369
+ "below",
1370
+ "iso"
1371
+ ]
1372
+ },
1373
+ {
1374
+ "type": "object",
1375
+ "properties": {
1376
+ "yaw": {
1377
+ "type": "number"
1378
+ },
1379
+ "pitch": {
1380
+ "type": "number"
1381
+ }
1382
+ },
1383
+ "required": [
1384
+ "yaw",
1385
+ "pitch"
1386
+ ]
1387
+ }
1388
+ ]
1389
+ },
1390
+ "camera": {
1391
+ "type": "object",
1392
+ "description": "a free camera, instead of view and from",
1393
+ "properties": {
1394
+ "position": {
1395
+ "type": "array",
1396
+ "items": {
1397
+ "type": "number"
1398
+ },
1399
+ "minItems": 3,
1400
+ "maxItems": 3,
1401
+ "description": "[x,y,z]"
1402
+ },
1403
+ "target": {
1404
+ "type": "array",
1405
+ "items": {
1406
+ "type": "number"
1407
+ },
1408
+ "minItems": 3,
1409
+ "maxItems": 3,
1410
+ "description": "[x,y,z]"
1411
+ }
1412
+ },
1413
+ "required": [
1414
+ "position",
1415
+ "target"
1416
+ ]
1417
+ },
1418
+ "aspect": {
1419
+ "description": "width to height: \"4:3\" (default), \"1:1\", \"16:9\" or a number. view current defaults to the person's window",
1420
+ "anyOf": [
1421
+ {
1422
+ "type": "string"
1423
+ },
1424
+ {
1425
+ "type": "number"
1426
+ }
1427
+ ]
1428
+ },
1429
+ "zoom": {
1430
+ "type": "number",
1431
+ "description": "above 1 is closer, 0.1 to 10"
1432
+ },
1433
+ "margin": {
1434
+ "type": "number",
1435
+ "description": "room around the subject: 1 touches the frame, default 1.15"
1350
1436
  },
1351
1437
  "size": {
1352
1438
  "type": "number",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@theprototype/agent",
3
- "version": "1.53.1",
3
+ "version": "1.54.0",
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",
@@ -357,13 +357,14 @@ export class BrowserEngine extends EventEmitter {
357
357
  fr.readAsDataURL(/** @type {Blob} */ (r));
358
358
  });
359
359
  }
360
- if (r && typeof r === 'object' && typeof r.data === 'string') return { data: r.data, mimeType: r.mimeType || 'image/png' };
361
- return typeof r === 'string' ? { data: r, mimeType: 'image/png' } : null;
360
+ const camera = typeof api.lastShot === 'function' ? api.lastShot() : null;
361
+ if (r && typeof r === 'object' && typeof r.data === 'string') return { data: r.data, mimeType: r.mimeType || 'image/png', camera };
362
+ return typeof r === 'string' ? { data: r, mimeType: 'image/png', camera } : null;
362
363
  }, opts)
363
364
  .catch((/** @type {any} */ e) => ({ error: firstLine(e) }));
364
365
  if (viaApi && typeof viaApi.data === 'string') {
365
366
  const m = /^data:([^;,]+)(;base64)?,/.exec(viaApi.data);
366
- return m ? { data: viaApi.data.slice(m[0].length), mimeType: m[1] } : viaApi;
367
+ return m ? { data: viaApi.data.slice(m[0].length), mimeType: m[1], camera: viaApi.camera } : viaApi;
367
368
  }
368
369
  const canvas = await this.page.evaluateHandle(() => {
369
370
  const all = [...document.querySelectorAll('canvas')].filter((c) => c.width > 0 && c.height > 0 && c.offsetParent !== null);
package/src/mcpServer.js CHANGED
@@ -222,10 +222,12 @@ export function createBridgeServer(opts) {
222
222
  if (!engine) return textResult({ error: 'not connected — join first', note: 'call connect with the session link first' }, true);
223
223
  if (name === 'screenshot' && reserved.includes('screenshot')) {
224
224
  if (!engineReady()) return textResult({ error: 'the agent page is not up yet (' + engine.state + ')' }, true);
225
- const shot = await engine.screenshot({ view: a.view, uuid: a.uuid, size: a.size });
225
+ const { view, uuid, uuids, from, camera, aspect, zoom, margin, size } = a;
226
+ const shot = await engine.screenshot({ view, uuid, uuids, from, camera, aspect, zoom, margin, size });
226
227
  /** @type {any[]} */
227
228
  const content = [{ type: 'image', data: shot.data, mimeType: shot.mimeType || 'image/png' }];
228
229
  if (shot.note) content.push({ type: 'text', text: shot.note });
230
+ if (shot.camera) content.push({ type: 'text', text: JSON.stringify({ camera: shot.camera }) });
229
231
  return { content };
230
232
  }
231
233
  if (name === 'wait_for_edit' && reserved.includes('wait_for_edit')) {
package/src/mcpTools.js CHANGED
@@ -86,12 +86,32 @@ export function bridgeTools(engine = 'browser') {
86
86
  {
87
87
  name: 'screenshot',
88
88
  description:
89
- '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).',
89
+ '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 or uuids); from = an angle or {yaw, pitch}; camera = a free {position, target}. 4:3 unless aspect says otherwise (current follows the person\'s window). Answers with the camera it used.',
90
90
  inputSchema: {
91
91
  type: 'object',
92
92
  properties: {
93
93
  view: { type: 'string', enum: ['current', 'top', 'front', 'iso', 'focus'] },
94
- uuid: { type: 'string', description: 'object to frame (view focus)' },
94
+ uuid: { type: 'string', description: 'object to frame' },
95
+ uuids: { type: 'array', items: { type: 'string' }, description: 'objects to frame together' },
96
+ from: {
97
+ description: 'the angle around the subject, instead of view: front, back, left, right, top, below, iso, or {yaw, pitch} in degrees (yaw 0 = front, 90 = right; pitch 90 = from above)',
98
+ anyOf: [
99
+ { type: 'string', enum: ['front', 'back', 'left', 'right', 'top', 'below', 'iso'] },
100
+ { type: 'object', properties: { yaw: { type: 'number' }, pitch: { type: 'number' } }, required: ['yaw', 'pitch'] }
101
+ ]
102
+ },
103
+ camera: {
104
+ type: 'object',
105
+ description: 'a free camera, instead of view and from',
106
+ properties: {
107
+ position: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: '[x,y,z]' },
108
+ target: { type: 'array', items: { type: 'number' }, minItems: 3, maxItems: 3, description: '[x,y,z]' }
109
+ },
110
+ required: ['position', 'target']
111
+ },
112
+ aspect: { description: 'width to height: "4:3" (default), "1:1", "16:9" or a number. view current defaults to the person\'s window', anyOf: [{ type: 'string' }, { type: 'number' }] },
113
+ zoom: { type: 'number', description: 'above 1 is closer, 0.1 to 10' },
114
+ margin: { type: 'number', description: 'room around the subject: 1 touches the frame, default 1.15' },
95
115
  size: { type: 'number', description: 'longest side in pixels' }
96
116
  }
97
117
  }