let-them-talk 4.2.0 → 5.2.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.
Files changed (39) hide show
  1. package/CHANGELOG.md +640 -540
  2. package/README.md +592 -415
  3. package/cli.js +1089 -589
  4. package/conversation-templates/autonomous-feature.json +22 -0
  5. package/conversation-templates/code-review.json +21 -11
  6. package/conversation-templates/debug-squad.json +21 -11
  7. package/conversation-templates/feature-build.json +21 -11
  8. package/conversation-templates/research-write.json +21 -11
  9. package/dashboard.html +9250 -7771
  10. package/dashboard.js +1232 -29
  11. package/office/agents.js +148 -4
  12. package/office/animation.js +68 -0
  13. package/office/assets.js +431 -0
  14. package/office/builder.js +355 -0
  15. package/office/building-interior.js +261 -0
  16. package/office/campus-env.js +119 -23
  17. package/office/car-hud.js +368 -0
  18. package/office/daynight.js +221 -0
  19. package/office/economy-hud.js +432 -0
  20. package/office/economy-ui.js +238 -0
  21. package/office/environment.js +818 -808
  22. package/office/face.js +65 -0
  23. package/office/fast-travel.js +215 -0
  24. package/office/hq-building.js +295 -0
  25. package/office/index.js +1095 -423
  26. package/office/instancing.js +160 -0
  27. package/office/lod-manager.js +165 -0
  28. package/office/multiplayer-hud.js +428 -0
  29. package/office/net-client.js +299 -0
  30. package/office/particles.js +172 -0
  31. package/office/player.js +658 -436
  32. package/office/post-processing.js +82 -0
  33. package/office/sky.js +319 -0
  34. package/office/street-furniture.js +308 -0
  35. package/office/vehicle.js +455 -0
  36. package/office/world-save.js +91 -0
  37. package/package.json +59 -59
  38. package/server.js +7190 -4685
  39. package/conversation-templates/managed-team.json +0 -12
@@ -0,0 +1,455 @@
1
+ import * as THREE from 'three';
2
+ import { S } from './state.js';
3
+
4
+ // ============================================================
5
+ // VEHICLE SYSTEM — Drivable cars in AI City
6
+ // Phase 2: Enter/exit, WASD driving, camera follow, collision
7
+ // ============================================================
8
+
9
+ var CAR_SPEED = 0.3;
10
+ var CAR_TURN_SPEED = 0.035;
11
+ var CAR_BRAKE = 0.92; // friction multiplier per frame
12
+ var CAR_ACCEL = 0.012;
13
+ var CAR_MAX_SPEED = 0.6;
14
+ var CAM_DISTANCE = 10;
15
+ var CAM_HEIGHT = 4;
16
+ var CAM_SMOOTH = 0.06;
17
+ var firstPersonMode = false;
18
+
19
+ var vehicles = [];
20
+ var activeVehicle = null; // currently driven vehicle
21
+ var driving = false;
22
+ var keys = { w: false, a: false, s: false, d: false, space: false };
23
+ var velocity = 0;
24
+ var steerAngle = 0;
25
+
26
+ // ============================================================
27
+ // CAR MESH — low-poly stylized car
28
+ // ============================================================
29
+
30
+ function createCarMesh(color) {
31
+ var group = new THREE.Group();
32
+ var paintMat = new THREE.MeshStandardMaterial({ color: color, roughness: 0.25, metalness: 0.5 });
33
+ var chromeMat = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.05, metalness: 0.95 });
34
+ var glassMat = new THREE.MeshStandardMaterial({
35
+ color: 0x99ccff, transparent: true, opacity: 0.18,
36
+ roughness: 0.0, metalness: 0.3, side: THREE.DoubleSide,
37
+ depthWrite: false,
38
+ });
39
+ var darkMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.8 });
40
+ var tireMat = new THREE.MeshStandardMaterial({ color: 0x1a1a1a, roughness: 0.95 });
41
+ var rimMat = new THREE.MeshStandardMaterial({ color: 0xbbbbbb, roughness: 0.15, metalness: 0.8 });
42
+ var interiorMat = new THREE.MeshStandardMaterial({ color: 0x2a2a2a, roughness: 0.7 });
43
+
44
+ // === SEDAN BODY — two boxes for performance ===
45
+ // Lower body
46
+ var bodyGeo = new THREE.BoxGeometry(1.5, 0.45, 3.8);
47
+ var body = new THREE.Mesh(bodyGeo, paintMat);
48
+ body.position.set(0, 0.42, 0);
49
+ group.add(body);
50
+
51
+ // Cabin (narrower, shorter)
52
+ var cabinGeo = new THREE.BoxGeometry(1.3, 0.42, 1.7);
53
+ var cabin = new THREE.Mesh(cabinGeo, paintMat);
54
+ cabin.position.set(0, 0.86, -0.1);
55
+ group.add(cabin);
56
+
57
+ // Interior floor (visible through glass)
58
+ var intFloor = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.05, 1.5), interiorMat);
59
+ intFloor.position.set(0, 0.68, 0.15);
60
+ group.add(intFloor);
61
+
62
+ // Steering wheel
63
+ var steeringGeo = new THREE.TorusGeometry(0.12, 0.015, 8, 16);
64
+ var steering = new THREE.Mesh(steeringGeo, darkMat);
65
+ steering.position.set(-0.3, 0.85, 0.5);
66
+ steering.rotation.x = -0.5;
67
+ group.add(steering);
68
+
69
+ // Seats (2 front)
70
+ [-0.3, 0.3].forEach(function(x) {
71
+ var seatBase = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.08, 0.4), interiorMat);
72
+ seatBase.position.set(x, 0.74, 0.25);
73
+ group.add(seatBase);
74
+ var seatBack = new THREE.Mesh(new THREE.BoxGeometry(0.35, 0.35, 0.08), interiorMat);
75
+ seatBack.position.set(x, 0.9, 0.03);
76
+ seatBack.rotation.x = 0.1;
77
+ group.add(seatBack);
78
+ });
79
+
80
+ // === GLASS PANELS (truly transparent) ===
81
+ // Windshield (angled)
82
+ var wsGeo = new THREE.PlaneGeometry(1.1, 0.5);
83
+ var ws = new THREE.Mesh(wsGeo, glassMat);
84
+ ws.position.set(0, 0.9, 0.78);
85
+ ws.rotation.x = -0.45;
86
+ group.add(ws);
87
+
88
+ // Rear window
89
+ var rwGeo = new THREE.PlaneGeometry(1.0, 0.4);
90
+ var rw = new THREE.Mesh(rwGeo, glassMat);
91
+ rw.position.set(0, 0.9, -0.82);
92
+ rw.rotation.x = 0.4;
93
+ rw.rotation.y = Math.PI;
94
+ group.add(rw);
95
+
96
+ // Side windows
97
+ [1, -1].forEach(function(side) {
98
+ var swGeo = new THREE.PlaneGeometry(1.4, 0.32);
99
+ var sw = new THREE.Mesh(swGeo, glassMat);
100
+ sw.position.set(side * 0.59, 0.88, 0.0);
101
+ sw.rotation.y = side * Math.PI / 2;
102
+ group.add(sw);
103
+ });
104
+
105
+ // === WHEELS (realistic proportions) ===
106
+ var wheelPositions = [
107
+ { x: -0.68, y: 0.24, z: 1.1 },
108
+ { x: 0.68, y: 0.24, z: 1.1 },
109
+ { x: -0.68, y: 0.24, z: -1.1 },
110
+ { x: 0.68, y: 0.24, z: -1.1 },
111
+ ];
112
+ var wheelMeshes = [];
113
+ wheelPositions.forEach(function(wp) {
114
+ var wheelGroup = new THREE.Group();
115
+ // Tire (simple cylinder for performance)
116
+ var tireGeo = new THREE.CylinderGeometry(0.24, 0.24, 0.16, 8);
117
+ var tire = new THREE.Mesh(tireGeo, tireMat);
118
+ tire.rotation.z = Math.PI / 2;
119
+ wheelGroup.add(tire);
120
+ // Rim
121
+ var rimGeo = new THREE.CylinderGeometry(0.14, 0.14, 0.17, 6);
122
+ var rim = new THREE.Mesh(rimGeo, rimMat);
123
+ rim.rotation.z = Math.PI / 2;
124
+ wheelGroup.add(rim);
125
+
126
+ wheelGroup.position.set(wp.x, wp.y, wp.z);
127
+ group.add(wheelGroup);
128
+ wheelMeshes.push(wheelGroup);
129
+ });
130
+
131
+ // === FRONT GRILLE ===
132
+ var grilleGeo = new THREE.BoxGeometry(0.9, 0.2, 0.05);
133
+ var grilleMat = new THREE.MeshStandardMaterial({ color: 0x111111, roughness: 0.3, metalness: 0.6 });
134
+ var grille = new THREE.Mesh(grilleGeo, grilleMat);
135
+ grille.position.set(0, 0.42, 1.93);
136
+ group.add(grille);
137
+
138
+ // === BUMPERS (body-colored, integrated) ===
139
+ var fBumper = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.12, 0.12), paintMat);
140
+ fBumper.position.set(0, 0.28, 1.94);
141
+ group.add(fBumper);
142
+ var rBumper = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.12, 0.12), paintMat);
143
+ rBumper.position.set(0, 0.28, -1.94);
144
+ group.add(rBumper);
145
+
146
+ // === HEADLIGHTS (modern LED style) ===
147
+ var hlMat = new THREE.MeshStandardMaterial({ color: 0xffffff, emissive: 0xffffee, emissiveIntensity: 1.5 });
148
+ [-0.5, 0.5].forEach(function(x) {
149
+ var hl = new THREE.Mesh(new THREE.BoxGeometry(0.22, 0.08, 0.05), hlMat);
150
+ hl.position.set(x, 0.48, 1.94);
151
+ group.add(hl);
152
+ });
153
+
154
+ // === TAILLIGHTS (LED strip) ===
155
+ var tlMat = new THREE.MeshStandardMaterial({ color: 0xff1111, emissive: 0xff0000, emissiveIntensity: 1.0 });
156
+ [-0.5, 0.5].forEach(function(x) {
157
+ var tl = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.06, 0.05), tlMat);
158
+ tl.position.set(x, 0.48, -1.94);
159
+ group.add(tl);
160
+ });
161
+
162
+ // === SIDE MIRRORS ===
163
+ [-1, 1].forEach(function(side) {
164
+ var arm = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.03, 0.04), darkMat);
165
+ arm.position.set(side * 0.78, 0.78, 0.55);
166
+ group.add(arm);
167
+ var head = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.08, 0.1), darkMat);
168
+ head.position.set(side * 0.86, 0.78, 0.55);
169
+ group.add(head);
170
+ });
171
+
172
+ // === EXHAUST ===
173
+ var exhaust = new THREE.Mesh(new THREE.CylinderGeometry(0.035, 0.04, 0.15, 8), chromeMat);
174
+ exhaust.position.set(0.4, 0.2, -1.98);
175
+ exhaust.rotation.x = Math.PI / 2;
176
+ group.add(exhaust);
177
+
178
+ // === LICENSE PLATES ===
179
+ var plateMat = new THREE.MeshStandardMaterial({ color: 0xeeeeee, roughness: 0.4 });
180
+ var plateGeo = new THREE.BoxGeometry(0.4, 0.1, 0.015);
181
+ var fPlate = new THREE.Mesh(plateGeo, plateMat);
182
+ fPlate.position.set(0, 0.32, 1.95);
183
+ group.add(fPlate);
184
+ var rPlate = new THREE.Mesh(plateGeo, plateMat);
185
+ rPlate.position.set(0, 0.32, -1.95);
186
+ group.add(rPlate);
187
+
188
+ group.userData.isVehicle = true;
189
+ group.userData.velocity = 0;
190
+ group.userData.wheels = wheelMeshes;
191
+
192
+ return group;
193
+ }
194
+
195
+ // ============================================================
196
+ // SPAWN VEHICLES — place cars at parking spots
197
+ // ============================================================
198
+
199
+ export function spawnVehicles(parkingSpots) {
200
+ var colors = [0x3366cc, 0xcc3333, 0x33cc66, 0xcccc33, 0x9933cc, 0xcc6633];
201
+
202
+ // Default parking spots if none provided (along main roads)
203
+ if (!parkingSpots || parkingSpots.length === 0) {
204
+ parkingSpots = [
205
+ { x: 26, z: 2, rot: 0 },
206
+ { x: 50, z: 2, rot: 0 },
207
+ { x: 74, z: 2, rot: 0 },
208
+ { x: 98, z: 2, rot: 0 },
209
+ { x: 122, z: 2, rot: 0 },
210
+ { x: 146, z: 2, rot: 0 },
211
+ { x: 2, z: 50, rot: Math.PI / 2 },
212
+ { x: 2, z: 98, rot: Math.PI / 2 },
213
+ ];
214
+ }
215
+
216
+ parkingSpots.forEach(function(spot, i) {
217
+ var color = colors[i % colors.length];
218
+ var car = createCarMesh(color);
219
+ car.position.set(spot.x, 0, spot.z);
220
+ car.rotation.y = spot.rot || 0;
221
+ car.userData.parkingSpot = spot;
222
+ car.userData.carIndex = i;
223
+ S.scene.add(car);
224
+ vehicles.push(car);
225
+ });
226
+
227
+ return vehicles;
228
+ }
229
+
230
+ // ============================================================
231
+ // ENTER / EXIT — E key to toggle
232
+ // ============================================================
233
+
234
+ export function getNearestVehicle(playerPos, maxDist) {
235
+ maxDist = maxDist || 5;
236
+ var nearest = null;
237
+ var nearestDist = maxDist;
238
+
239
+ vehicles.forEach(function(car) {
240
+ var dx = car.position.x - playerPos.x;
241
+ var dz = car.position.z - playerPos.z;
242
+ var dist = Math.sqrt(dx * dx + dz * dz);
243
+ if (dist < nearestDist) {
244
+ nearestDist = dist;
245
+ nearest = car;
246
+ }
247
+ });
248
+
249
+ return nearest;
250
+ }
251
+
252
+ export function enterVehicle(car) {
253
+ if (!car || driving) return false;
254
+ activeVehicle = car;
255
+ driving = true;
256
+ velocity = 0;
257
+ steerAngle = 0;
258
+ firstPersonMode = false;
259
+
260
+ // Disable spectator/player controls
261
+ if (S.controls && S.controls.enabled !== undefined) {
262
+ S.controls.enabled = false;
263
+ }
264
+
265
+ // Hide player character while in vehicle
266
+ if (S._player && S._player.parts && S._player.parts.group) {
267
+ S._player.parts.group.visible = false;
268
+ }
269
+
270
+ // Set up key listeners
271
+ window.addEventListener('keydown', onDriveKeyDown);
272
+ window.addEventListener('keyup', onDriveKeyUp);
273
+
274
+ return true;
275
+ }
276
+
277
+ export function exitVehicle() {
278
+ if (!driving || !activeVehicle) return false;
279
+ driving = false;
280
+ velocity = 0;
281
+ firstPersonMode = false;
282
+
283
+ // Re-enable spectator/player controls
284
+ if (S.controls && S.controls.enabled !== undefined) {
285
+ S.controls.enabled = true;
286
+ }
287
+
288
+ // Show player character again
289
+ if (S._player && S._player.parts && S._player.parts.group) {
290
+ S._player.parts.group.visible = true;
291
+ // Move player to car exit position
292
+ S._player.pos.x = activeVehicle.position.x + 3;
293
+ S._player.pos.z = activeVehicle.position.z;
294
+ }
295
+
296
+ // Remove key listeners
297
+ window.removeEventListener('keydown', onDriveKeyDown);
298
+ window.removeEventListener('keyup', onDriveKeyUp);
299
+
300
+ // Reset keys
301
+ keys.w = keys.a = keys.s = keys.d = keys.space = false;
302
+
303
+ var exitPos = activeVehicle.position.clone();
304
+ exitPos.x += 3;
305
+ activeVehicle = null;
306
+
307
+ return exitPos;
308
+ }
309
+
310
+ function onDriveKeyDown(e) {
311
+ var k = e.key.toLowerCase();
312
+ if (k === 'w') keys.w = true;
313
+ if (k === 'a') keys.a = true;
314
+ if (k === 's') keys.s = true;
315
+ if (k === 'd') keys.d = true;
316
+ if (k === ' ') keys.space = true;
317
+ if (k === 'f') {
318
+ firstPersonMode = !firstPersonMode;
319
+ }
320
+ if (k === 'escape' || k === 'e') {
321
+ var pos = exitVehicle();
322
+ window.dispatchEvent(new CustomEvent('vehicle-exit', { detail: { position: pos } }));
323
+ }
324
+ }
325
+
326
+ function onDriveKeyUp(e) {
327
+ var k = e.key.toLowerCase();
328
+ if (k === 'w') keys.w = false;
329
+ if (k === 'a') keys.a = false;
330
+ if (k === 's') keys.s = false;
331
+ if (k === 'd') keys.d = false;
332
+ if (k === ' ') keys.space = false;
333
+ }
334
+
335
+ // ============================================================
336
+ // DRIVING PHYSICS — simple arcade style
337
+ // ============================================================
338
+
339
+ function checkBuildingCollision(pos) {
340
+ // Simple AABB collision against city buildings
341
+ if (!S._cityBuildings) return false;
342
+ for (var i = 0; i < S._cityBuildings.length; i++) {
343
+ var b = S._cityBuildings[i];
344
+ var halfW = b.width / 2 + 1.5; // car half-width buffer
345
+ var halfD = b.depth / 2 + 1.5;
346
+ if (pos.x > b.x - halfW && pos.x < b.x + halfW &&
347
+ pos.z > b.z - halfD && pos.z < b.z + halfD) {
348
+ return true;
349
+ }
350
+ }
351
+ return false;
352
+ }
353
+
354
+ export function updateVehicle(dt) {
355
+ if (!driving || !activeVehicle) return;
356
+
357
+ // Acceleration / braking
358
+ if (keys.w) velocity = Math.min(velocity + CAR_ACCEL, CAR_MAX_SPEED);
359
+ if (keys.s) velocity = Math.max(velocity - CAR_ACCEL, -CAR_MAX_SPEED * 0.5);
360
+ if (keys.space) velocity *= 0.9; // handbrake
361
+ if (!keys.w && !keys.s) velocity *= CAR_BRAKE; // friction
362
+
363
+ // Steering (only when moving) — drift when space + turn
364
+ var turnMultiplier = keys.space ? 2.2 : 1.0; // drift = sharper turns
365
+ if (Math.abs(velocity) > 0.01) {
366
+ if (keys.a) steerAngle += CAR_TURN_SPEED * turnMultiplier * (velocity > 0 ? 1 : -1);
367
+ if (keys.d) steerAngle -= CAR_TURN_SPEED * turnMultiplier * (velocity > 0 ? 1 : -1);
368
+ }
369
+
370
+ // Apply movement
371
+ var forward = new THREE.Vector3(0, 0, 1);
372
+ forward.applyAxisAngle(new THREE.Vector3(0, 1, 0), steerAngle);
373
+
374
+ var newPos = activeVehicle.position.clone();
375
+ newPos.x += forward.x * velocity;
376
+ newPos.z += forward.z * velocity;
377
+
378
+ // Collision check
379
+ if (!checkBuildingCollision(newPos)) {
380
+ activeVehicle.position.copy(newPos);
381
+ } else {
382
+ velocity *= -0.3; // bounce back slightly
383
+ }
384
+
385
+ activeVehicle.rotation.y = steerAngle;
386
+
387
+ // Spin wheels based on velocity
388
+ if (activeVehicle.userData.wheels) {
389
+ activeVehicle.userData.wheels.forEach(function(wheel) {
390
+ wheel.children.forEach(function(child) {
391
+ if (child.geometry && child.geometry.type === 'TorusGeometry') {
392
+ child.rotation.x += velocity * 3; // spin tires
393
+ }
394
+ });
395
+ });
396
+ }
397
+
398
+ // Camera: first-person or third-person
399
+ if (firstPersonMode) {
400
+ // First person — inside car, looking forward
401
+ var fpPos = new THREE.Vector3();
402
+ fpPos.x = activeVehicle.position.x + forward.x * 0.3;
403
+ fpPos.y = activeVehicle.position.y + 1.1;
404
+ fpPos.z = activeVehicle.position.z + forward.z * 0.3;
405
+ S.camera.position.lerp(fpPos, 0.15);
406
+
407
+ var lookTarget = new THREE.Vector3();
408
+ lookTarget.x = activeVehicle.position.x + forward.x * 10;
409
+ lookTarget.y = activeVehicle.position.y + 0.8;
410
+ lookTarget.z = activeVehicle.position.z + forward.z * 10;
411
+ S.camera.lookAt(lookTarget);
412
+ } else {
413
+ // Third person — behind and above
414
+ var camTarget = new THREE.Vector3();
415
+ camTarget.x = activeVehicle.position.x - forward.x * CAM_DISTANCE;
416
+ camTarget.y = activeVehicle.position.y + CAM_HEIGHT;
417
+ camTarget.z = activeVehicle.position.z - forward.z * CAM_DISTANCE;
418
+
419
+ S.camera.position.lerp(camTarget, CAM_SMOOTH);
420
+ S.camera.lookAt(activeVehicle.position.x, activeVehicle.position.y + 1, activeVehicle.position.z);
421
+ }
422
+ }
423
+
424
+ // ============================================================
425
+ // GETTERS
426
+ // ============================================================
427
+
428
+ export function isDriving() { return driving; }
429
+ export function isFirstPerson() { return firstPersonMode; }
430
+ export function getActiveVehicle() { return activeVehicle; }
431
+ export function getVehicles() { return vehicles; }
432
+ export function getSpeed() { return Math.abs(velocity) * 100; } // km/h-ish
433
+ export function getSteerAngle() { return steerAngle; }
434
+ export function isDrifting() { return keys.space && Math.abs(velocity) > 0.1 && (keys.a || keys.d); }
435
+
436
+ // ============================================================
437
+ // CLEANUP
438
+ // ============================================================
439
+
440
+ export function disposeVehicles() {
441
+ vehicles.forEach(function(car) {
442
+ car.traverse(function(child) {
443
+ if (child.geometry) child.geometry.dispose();
444
+ if (child.material) {
445
+ if (Array.isArray(child.material)) child.material.forEach(function(m) { m.dispose(); });
446
+ else child.material.dispose();
447
+ }
448
+ });
449
+ S.scene.remove(car);
450
+ });
451
+ vehicles = [];
452
+ activeVehicle = null;
453
+ driving = false;
454
+ velocity = 0;
455
+ }
@@ -0,0 +1,91 @@
1
+ // world-save.js — Persistence layer for the World Builder
2
+ // Saves/loads placed objects to .agent-bridge/world-layout.json via dashboard API
3
+
4
+ var _placements = []; // in-memory placement array
5
+ var _saveTimeout = null; // debounce timer
6
+ var _loaded = false;
7
+
8
+ // Generate unique placement ID
9
+ function generatePlacementId() {
10
+ return 'p_' + Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
11
+ }
12
+
13
+ // Get project query string for API calls
14
+ function getProjectParam() {
15
+ return window.activeProject ? '?project=' + encodeURIComponent(window.activeProject) : '';
16
+ }
17
+
18
+ // --- Load world layout from server ---
19
+ export async function loadWorld() {
20
+ try {
21
+ var res = await fetch('/api/world-layout' + getProjectParam());
22
+ if (res.ok) {
23
+ var data = await res.json();
24
+ _placements = Array.isArray(data) ? data : [];
25
+ _loaded = true;
26
+ return _placements;
27
+ }
28
+ } catch (e) {
29
+ console.warn('[world-save] Load failed:', e.message);
30
+ }
31
+ _placements = [];
32
+ _loaded = true;
33
+ return _placements;
34
+ }
35
+
36
+ // --- Save full world layout to server (debounced) ---
37
+ function scheduleSave() {
38
+ if (_saveTimeout) clearTimeout(_saveTimeout);
39
+ _saveTimeout = setTimeout(function() {
40
+ _saveTimeout = null;
41
+ fetch('/api/world-save' + getProjectParam(), {
42
+ method: 'POST',
43
+ headers: { 'Content-Type': 'application/json', 'X-LTT-Request': '1' },
44
+ body: JSON.stringify(_placements)
45
+ }).catch(function(e) {
46
+ console.warn('[world-save] Save failed:', e.message);
47
+ });
48
+ }, 500); // debounce 500ms — batches rapid placements
49
+ }
50
+
51
+ // --- Add a single placement ---
52
+ export function addPlacement(type, x, y, z, rotY, placedBy) {
53
+ var entry = {
54
+ id: generatePlacementId(),
55
+ type: type,
56
+ x: x,
57
+ y: y || 0,
58
+ z: z,
59
+ rotY: rotY || 0,
60
+ placed_by: placedBy || 'user',
61
+ timestamp: new Date().toISOString()
62
+ };
63
+ _placements.push(entry);
64
+ scheduleSave();
65
+ return entry;
66
+ }
67
+
68
+ // --- Remove a placement by ID ---
69
+ export function removePlacement(id) {
70
+ var idx = _placements.findIndex(function(p) { return p.id === id; });
71
+ if (idx === -1) return null;
72
+ var removed = _placements.splice(idx, 1)[0];
73
+ scheduleSave();
74
+ return removed;
75
+ }
76
+
77
+ // --- Get all placements (read-only copy) ---
78
+ export function getPlacements() {
79
+ return _placements.slice();
80
+ }
81
+
82
+ // --- Clear all placements ---
83
+ export function clearWorld() {
84
+ _placements = [];
85
+ scheduleSave();
86
+ }
87
+
88
+ // --- Check if world has been loaded ---
89
+ export function isLoaded() {
90
+ return _loaded;
91
+ }
package/package.json CHANGED
@@ -1,59 +1,59 @@
1
- {
2
- "name": "let-them-talk",
3
- "version": "4.2.0",
4
- "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
- "main": "server.js",
6
- "bin": {
7
- "agent-bridge": "./cli.js",
8
- "let-them-talk": "./cli.js"
9
- },
10
- "scripts": {
11
- "start": "node server.js",
12
- "dashboard": "node dashboard.js",
13
- "test": "echo \"No tests yet\""
14
- },
15
- "engines": {
16
- "node": ">=16.0.0"
17
- },
18
- "files": [
19
- "server.js",
20
- "dashboard.js",
21
- "dashboard.html",
22
- "office/",
23
- "mods/",
24
- "cli.js",
25
- "templates/",
26
- "conversation-templates/",
27
- "logo.png",
28
- "LICENSE",
29
- "SECURITY.md",
30
- "CHANGELOG.md"
31
- ],
32
- "keywords": [
33
- "mcp",
34
- "claude",
35
- "claude-code",
36
- "gemini-cli",
37
- "codex-cli",
38
- "agent",
39
- "multi-agent",
40
- "communication",
41
- "message-broker",
42
- "ai-agents",
43
- "let-them-talk"
44
- ],
45
- "repository": {
46
- "type": "git",
47
- "url": "git+https://github.com/Dekelelz/let-them-talk.git"
48
- },
49
- "homepage": "https://talk.unrealai.studio",
50
- "bugs": {
51
- "url": "https://github.com/Dekelelz/let-them-talk/issues"
52
- },
53
- "author": "Dekelelz <contact@talk.unrealai.studio>",
54
- "license": "SEE LICENSE IN LICENSE",
55
- "dependencies": {
56
- "@modelcontextprotocol/sdk": "1.27.1",
57
- "three": "0.170.0"
58
- }
59
- }
1
+ {
2
+ "name": "let-them-talk",
3
+ "version": "5.2.0",
4
+ "description": "MCP message broker + web dashboard for inter-agent communication. Let AI CLI agents talk to each other.",
5
+ "main": "server.js",
6
+ "bin": {
7
+ "agent-bridge": "./cli.js",
8
+ "let-them-talk": "./cli.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js",
12
+ "dashboard": "node dashboard.js",
13
+ "test": "echo \"No tests configured\""
14
+ },
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "files": [
19
+ "server.js",
20
+ "dashboard.js",
21
+ "dashboard.html",
22
+ "office/",
23
+ "mods/",
24
+ "cli.js",
25
+ "templates/",
26
+ "conversation-templates/",
27
+ "logo.png",
28
+ "LICENSE",
29
+ "SECURITY.md",
30
+ "CHANGELOG.md"
31
+ ],
32
+ "keywords": [
33
+ "mcp",
34
+ "claude",
35
+ "claude-code",
36
+ "gemini-cli",
37
+ "codex-cli",
38
+ "agent",
39
+ "multi-agent",
40
+ "communication",
41
+ "message-broker",
42
+ "ai-agents",
43
+ "let-them-talk"
44
+ ],
45
+ "repository": {
46
+ "type": "git",
47
+ "url": "git+https://github.com/Dekelelz/let-them-talk.git"
48
+ },
49
+ "homepage": "https://talk.unrealai.studio",
50
+ "bugs": {
51
+ "url": "https://github.com/Dekelelz/let-them-talk/issues"
52
+ },
53
+ "author": "Dekelelz <contact@talk.unrealai.studio>",
54
+ "license": "SEE LICENSE IN LICENSE",
55
+ "dependencies": {
56
+ "@modelcontextprotocol/sdk": "1.27.1",
57
+ "three": "0.175.0"
58
+ }
59
+ }