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.
- package/CHANGELOG.md +640 -540
- package/README.md +592 -415
- package/cli.js +1089 -589
- package/conversation-templates/autonomous-feature.json +22 -0
- package/conversation-templates/code-review.json +21 -11
- package/conversation-templates/debug-squad.json +21 -11
- package/conversation-templates/feature-build.json +21 -11
- package/conversation-templates/research-write.json +21 -11
- package/dashboard.html +9250 -7771
- package/dashboard.js +1232 -29
- package/office/agents.js +148 -4
- package/office/animation.js +68 -0
- package/office/assets.js +431 -0
- package/office/builder.js +355 -0
- package/office/building-interior.js +261 -0
- package/office/campus-env.js +119 -23
- package/office/car-hud.js +368 -0
- package/office/daynight.js +221 -0
- package/office/economy-hud.js +432 -0
- package/office/economy-ui.js +238 -0
- package/office/environment.js +818 -808
- package/office/face.js +65 -0
- package/office/fast-travel.js +215 -0
- package/office/hq-building.js +295 -0
- package/office/index.js +1095 -423
- package/office/instancing.js +160 -0
- package/office/lod-manager.js +165 -0
- package/office/multiplayer-hud.js +428 -0
- package/office/net-client.js +299 -0
- package/office/particles.js +172 -0
- package/office/player.js +658 -436
- package/office/post-processing.js +82 -0
- package/office/sky.js +319 -0
- package/office/street-furniture.js +308 -0
- package/office/vehicle.js +455 -0
- package/office/world-save.js +91 -0
- package/package.json +59 -59
- package/server.js +7190 -4685
- package/conversation-templates/managed-team.json +0 -12
package/office/agents.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { S } from './state.js';
|
|
2
|
-
import { DESK_POSITIONS, SPAWN_POS } from './constants.js';
|
|
2
|
+
import { DESK_POSITIONS, SPAWN_POS, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
|
|
3
3
|
import { createCharacter } from './character.js';
|
|
4
4
|
import { resolveAppearance } from './appearance.js';
|
|
5
5
|
import { buildHair } from './hair.js';
|
|
6
|
-
import { buildFaceSprite } from './face.js';
|
|
6
|
+
import { buildFaceSprite, setEmotion } from './face.js';
|
|
7
7
|
import { buildOutfit, removeOutfit } from './outfits.js';
|
|
8
8
|
import { getNavigationPath } from './navigation.js';
|
|
9
9
|
|
|
@@ -291,6 +291,36 @@ export function syncAgents() {
|
|
|
291
291
|
}
|
|
292
292
|
}
|
|
293
293
|
|
|
294
|
+
// --- Autonomous behaviors: sleeping → rest area, waking → back to desk ---
|
|
295
|
+
if (newState === 'sleeping' && oldState === 'active' && existing.location === 'desk' && existing.registered && !existing.dying) {
|
|
296
|
+
// Agent fell asleep — walk to rest area after a short delay
|
|
297
|
+
existing.location = 'walking';
|
|
298
|
+
(function(a) {
|
|
299
|
+
setTimeout(function() {
|
|
300
|
+
showBubble(a, 'Need a break...');
|
|
301
|
+
a.isSitting = false;
|
|
302
|
+
navigateTo(a, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
|
|
303
|
+
navigateTo(a, REST_AREA_POS.x, REST_AREA_POS.z, function() {
|
|
304
|
+
a.location = 'rest';
|
|
305
|
+
a.state = 'sleeping';
|
|
306
|
+
showBubble(a, 'zzz...');
|
|
307
|
+
});
|
|
308
|
+
});
|
|
309
|
+
}, 1000 + Math.random() * 2000);
|
|
310
|
+
})(existing);
|
|
311
|
+
}
|
|
312
|
+
if (newState === 'active' && (oldState === 'sleeping' || existing.location === 'rest') && existing.location !== 'desk' && existing.registered && !existing.dying) {
|
|
313
|
+
// Agent woke up — walk back to desk
|
|
314
|
+
existing.location = 'walking';
|
|
315
|
+
existing.state = 'active';
|
|
316
|
+
(function(a) {
|
|
317
|
+
showBubble(a, 'Back to work!');
|
|
318
|
+
navigateTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
|
|
319
|
+
a.location = 'desk';
|
|
320
|
+
});
|
|
321
|
+
})(existing);
|
|
322
|
+
}
|
|
323
|
+
|
|
294
324
|
existing.displayName = info.display_name || name;
|
|
295
325
|
var wasListening = existing.isListening;
|
|
296
326
|
existing.isListening = !!(info.is_listening);
|
|
@@ -313,11 +343,21 @@ export function syncAgents() {
|
|
|
313
343
|
if (prevTask && prevTask.status !== 'done' && task.status === 'done') {
|
|
314
344
|
existing.taskCelebration = 2;
|
|
315
345
|
existing.celebrateTimer = 1.5;
|
|
346
|
+
setEmotion(existing, 'happy', 6);
|
|
347
|
+
}
|
|
348
|
+
// Blocked task → frustrated face
|
|
349
|
+
if (task.status === 'blocked' && (!prevTask || prevTask.status !== 'blocked')) {
|
|
350
|
+
setEmotion(existing, 'frustrated', 8);
|
|
316
351
|
}
|
|
317
352
|
} else {
|
|
318
353
|
existing.currentTask = null;
|
|
319
354
|
}
|
|
320
355
|
|
|
356
|
+
// Listening agents look focused
|
|
357
|
+
if (existing.isListening && !wasListening) {
|
|
358
|
+
setEmotion(existing, 'focused', 10);
|
|
359
|
+
}
|
|
360
|
+
|
|
321
361
|
var newApp = info.appearance || {};
|
|
322
362
|
if (JSON.stringify(newApp) !== JSON.stringify(existing.appearance)) {
|
|
323
363
|
existing.appearance = newApp;
|
|
@@ -329,6 +369,66 @@ export function syncAgents() {
|
|
|
329
369
|
}
|
|
330
370
|
}
|
|
331
371
|
|
|
372
|
+
// --- Random social behavior: idle agents occasionally stretch or look around ---
|
|
373
|
+
// Limit concurrent social walks to prevent traffic jams (max 2 walking at once)
|
|
374
|
+
var walkingCount = 0;
|
|
375
|
+
for (var wn in S.agents3d) { if (S.agents3d[wn].location === 'walking') walkingCount++; }
|
|
376
|
+
|
|
377
|
+
for (var sn in S.agents3d) {
|
|
378
|
+
var sa = S.agents3d[sn];
|
|
379
|
+
if (!sa.registered || sa.state !== 'active' || sa.location !== 'desk' || sa.target) continue;
|
|
380
|
+
if (!sa._socialTimer) sa._socialTimer = 30 + Math.random() * 60;
|
|
381
|
+
sa._socialTimer -= 2; // syncAgents runs every ~2s
|
|
382
|
+
if (sa._socialTimer <= 0) {
|
|
383
|
+
sa._socialTimer = 40 + Math.random() * 80; // next social event in 40-120s
|
|
384
|
+
// Pick a random behavior: stretch, look around, or visit another agent
|
|
385
|
+
var roll = Math.random();
|
|
386
|
+
if (roll < 0.4) {
|
|
387
|
+
// Stretch at desk
|
|
388
|
+
sa.stretchTimer = 2;
|
|
389
|
+
} else if (roll < 0.7) {
|
|
390
|
+
// Look around curiously
|
|
391
|
+
sa.thinkTimer = 1.5;
|
|
392
|
+
} else if (walkingCount < 2) {
|
|
393
|
+
// Walk to a random nearby agent's desk to "chat" then return (max 2 concurrent)
|
|
394
|
+
var others = [];
|
|
395
|
+
for (var on in S.agents3d) {
|
|
396
|
+
if (on !== sn && S.agents3d[on].registered && S.agents3d[on].state === 'active' && S.agents3d[on].location === 'desk') {
|
|
397
|
+
others.push(S.agents3d[on]);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
if (others.length > 0) {
|
|
401
|
+
var buddy = others[Math.floor(Math.random() * others.length)];
|
|
402
|
+
(function(a, b) {
|
|
403
|
+
a.location = 'walking';
|
|
404
|
+
a.isSitting = false;
|
|
405
|
+
showBubble(a, 'Hey ' + b.displayName + '!');
|
|
406
|
+
setEmotion(a, 'playful', 6);
|
|
407
|
+
var stopX = b.deskPos.x + 1.5;
|
|
408
|
+
var stopZ = b.deskPos.z + 0.7;
|
|
409
|
+
navigateTo(a, stopX, stopZ, function() {
|
|
410
|
+
// Face buddy
|
|
411
|
+
var dx = b.pos.x - a.pos.x;
|
|
412
|
+
var dz = b.pos.z - a.pos.z;
|
|
413
|
+
a.facingTarget = Math.atan2(dx, dz);
|
|
414
|
+
a.waveTimer = 0.8;
|
|
415
|
+
// Buddy turns toward visitor
|
|
416
|
+
b.facingTarget = Math.atan2(-dx, -dz);
|
|
417
|
+
setTimeout(function() {
|
|
418
|
+
showBubble(a, 'Back to it!');
|
|
419
|
+
navigateTo(a, a.deskPos.x, a.deskPos.z + 0.7, function() {
|
|
420
|
+
a.location = 'desk';
|
|
421
|
+
});
|
|
422
|
+
// Buddy turns back to desk
|
|
423
|
+
setTimeout(function() { b.facingTarget = Math.PI; }, 1500);
|
|
424
|
+
}, 3000 + Math.random() * 2000);
|
|
425
|
+
});
|
|
426
|
+
})(sa, buddy);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
332
432
|
for (var n in S.agents3d) {
|
|
333
433
|
if (!window.cachedAgents[n]) {
|
|
334
434
|
var deadAgent = S.agents3d[n];
|
|
@@ -345,8 +445,11 @@ export function processMessages() {
|
|
|
345
445
|
var history = window.cachedHistory;
|
|
346
446
|
if (!history || history.length === 0) return;
|
|
347
447
|
|
|
348
|
-
|
|
349
|
-
|
|
448
|
+
// Use window-level counter so it persists across 3D stop/start cycles (tab switches)
|
|
449
|
+
// This prevents message replay when user switches from Messages tab back to 3D Hub
|
|
450
|
+
if (typeof window._lastProcessedMsg === 'undefined') window._lastProcessedMsg = 0;
|
|
451
|
+
var newMsgs = history.slice(window._lastProcessedMsg);
|
|
452
|
+
window._lastProcessedMsg = history.length;
|
|
350
453
|
|
|
351
454
|
for (var i = 0; i < newMsgs.length; i++) {
|
|
352
455
|
var msg = newMsgs[i];
|
|
@@ -357,6 +460,37 @@ export function processMessages() {
|
|
|
357
460
|
from.lastMessageTime = Date.now();
|
|
358
461
|
flashDeskScreen(from.deskIdx);
|
|
359
462
|
|
|
463
|
+
// Instant preview bubble — show short text immediately before walk animation
|
|
464
|
+
// Gives users instant visual feedback that the agent is about to speak
|
|
465
|
+
var preview = text.length > 30 ? text.substring(0, 27) + '...' : text;
|
|
466
|
+
showBubble(from, preview);
|
|
467
|
+
|
|
468
|
+
// Auto-celebrate on task completion events
|
|
469
|
+
if (text.indexOf('[EVENT] Task') >= 0 && text.indexOf('completed') >= 0) {
|
|
470
|
+
from.celebrateTimer = 1.5;
|
|
471
|
+
from.taskCelebration = 2;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
// Emotion detection from message content
|
|
475
|
+
var textLower = text.toLowerCase();
|
|
476
|
+
if (textLower.indexOf('done') >= 0 || textLower.indexOf('pass') >= 0 || textLower.indexOf('success') >= 0 || textLower.indexOf('great') >= 0 || textLower.indexOf('shipped') >= 0) {
|
|
477
|
+
setEmotion(from, 'happy', 5);
|
|
478
|
+
} else if (textLower.indexOf('error') >= 0 || textLower.indexOf('fail') >= 0 || textLower.indexOf('bug') >= 0 || textLower.indexOf('broken') >= 0) {
|
|
479
|
+
setEmotion(from, 'frustrated', 5);
|
|
480
|
+
} else if (textLower.indexOf('?') >= 0 && (textLower.indexOf('how') >= 0 || textLower.indexOf('why') >= 0 || textLower.indexOf('what if') >= 0)) {
|
|
481
|
+
setEmotion(from, 'thinking', 4);
|
|
482
|
+
} else if (textLower.indexOf('!') >= 0 && (textLower.indexOf('wow') >= 0 || textLower.indexOf('amazing') >= 0 || textLower.indexOf('awesome') >= 0)) {
|
|
483
|
+
setEmotion(from, 'excited', 4);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// Target agent gets surprised when directly addressed
|
|
487
|
+
if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
|
|
488
|
+
var targetAgent = S.agents3d[msg.to];
|
|
489
|
+
if (targetAgent.registered && targetAgent.isSitting) {
|
|
490
|
+
setEmotion(targetAgent, 'surprised', 2);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
360
494
|
// Contextual gesture based on message type
|
|
361
495
|
var isBC = !msg.to || msg.to === 'all';
|
|
362
496
|
if (isBC) {
|
|
@@ -365,6 +499,16 @@ export function processMessages() {
|
|
|
365
499
|
from.pointTimer = 0.6;
|
|
366
500
|
}
|
|
367
501
|
|
|
502
|
+
// Glance reaction — nearby sitting agents glance toward the speaker
|
|
503
|
+
for (var gn in S.agents3d) {
|
|
504
|
+
var ga = S.agents3d[gn];
|
|
505
|
+
if (gn === msg.from || gn === msg.to || !ga.registered || ga.state !== 'active' || !ga.isSitting) continue;
|
|
506
|
+
var gdx = from.pos.x - ga.pos.x;
|
|
507
|
+
ga._glanceTarget = from.name;
|
|
508
|
+
ga._glanceDirection = gdx > 0 ? 1 : -1; // left or right glance
|
|
509
|
+
ga._glanceTimer = 0;
|
|
510
|
+
}
|
|
511
|
+
|
|
368
512
|
if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
|
|
369
513
|
var target = S.agents3d[msg.to];
|
|
370
514
|
(function(f, t, txt) {
|
package/office/animation.js
CHANGED
|
@@ -120,6 +120,56 @@ export function updateAgent(agent, dt, time) {
|
|
|
120
120
|
agent.parts.head.rotation.x = -stPhase * 0.2;
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
// Active typing animation — subtle hand/finger movement when working at desk
|
|
124
|
+
if (agent.isSitting && agent.state === 'active' && !agent.isListening && !isWalking && !isSleeping) {
|
|
125
|
+
var typeSpeed = 8 + Math.sin(time * 0.7 + agent.name.length) * 2; // vary per agent
|
|
126
|
+
var typeL = Math.sin(time * typeSpeed) * 0.06;
|
|
127
|
+
var typeR = Math.sin(time * typeSpeed + 1.5) * 0.06; // offset for alternating hands
|
|
128
|
+
agent.parts.leftForearm.rotation.x += typeL;
|
|
129
|
+
agent.parts.rightForearm.rotation.x += typeR;
|
|
130
|
+
// Subtle head bob while typing (looking at screen)
|
|
131
|
+
agent.parts.head.rotation.x += Math.sin(time * 1.2) * 0.015;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Frustrated gesture — when task is blocked, head in hands periodically
|
|
135
|
+
if (agent.currentTask && agent.currentTask.status === 'blocked' && agent.isSitting && !isWalking) {
|
|
136
|
+
if (!agent.frustratedTimer) agent.frustratedTimer = 3 + Math.random() * 5;
|
|
137
|
+
agent.frustratedTimer -= dt;
|
|
138
|
+
if (agent.frustratedTimer <= 0 && agent.frustratedTimer > -2.5) {
|
|
139
|
+
// Head drops, arms come up to cradle head
|
|
140
|
+
var fT = Math.min(1, (-agent.frustratedTimer) / 0.5);
|
|
141
|
+
agent.parts.head.rotation.x += fT * 0.3;
|
|
142
|
+
agent.parts.leftArm.rotation.x = -fT * 1.2;
|
|
143
|
+
agent.parts.rightArm.rotation.x = -fT * 1.2;
|
|
144
|
+
agent.parts.leftForearm.rotation.x = -fT * 1.0;
|
|
145
|
+
agent.parts.rightForearm.rotation.x = -fT * 1.0;
|
|
146
|
+
}
|
|
147
|
+
if (agent.frustratedTimer < -2.5) {
|
|
148
|
+
agent.frustratedTimer = 8 + Math.random() * 10; // reset
|
|
149
|
+
}
|
|
150
|
+
} else {
|
|
151
|
+
agent.frustratedTimer = 0;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Listening lean-forward — body leans toward screen when in listen mode
|
|
155
|
+
if (agent.isListening && agent.isSitting && !isWalking && !isSleeping) {
|
|
156
|
+
agent.parts.body.rotation.x += -0.08; // slight forward lean
|
|
157
|
+
agent.parts.head.rotation.x += -0.05; // looking up at screen
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Head nod when being talked to (another agent is visiting)
|
|
161
|
+
if (agent._listeningTo && !isWalking && !isSleeping) {
|
|
162
|
+
if (!agent._nodTimer) agent._nodTimer = 0;
|
|
163
|
+
agent._nodTimer += dt;
|
|
164
|
+
// Periodic nod: quick down-up every ~2s
|
|
165
|
+
var nodCycle = agent._nodTimer % 2.2;
|
|
166
|
+
if (nodCycle < 0.3) {
|
|
167
|
+
agent.parts.head.rotation.x += Math.sin(nodCycle / 0.3 * Math.PI) * 0.12;
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
agent._nodTimer = 0;
|
|
171
|
+
}
|
|
172
|
+
|
|
123
173
|
// Idle gesture system — random gestures when sitting and idle
|
|
124
174
|
if (!agent.idleGestureTimer) agent.idleGestureTimer = 5 + Math.random() * 10;
|
|
125
175
|
if (agent.isSitting && agent.state === 'active' && !isWalking && !agent.isListening) {
|
|
@@ -205,6 +255,24 @@ export function updateAgent(agent, dt, time) {
|
|
|
205
255
|
agent.parts.rightForearm.rotation.x *= 0.9;
|
|
206
256
|
}
|
|
207
257
|
|
|
258
|
+
// Glance at nearby speaking agent — head turns slightly toward speaker
|
|
259
|
+
if (agent._glanceTarget && agent.isSitting && !isWalking && !isSleeping) {
|
|
260
|
+
if (!agent._glanceTimer) agent._glanceTimer = 0;
|
|
261
|
+
agent._glanceTimer += dt;
|
|
262
|
+
if (agent._glanceTimer < 2.5) {
|
|
263
|
+
var glanceT = Math.min(1, agent._glanceTimer / 0.4);
|
|
264
|
+
agent.parts.head.rotation.y = agent._glanceDirection * 0.25 * glanceT;
|
|
265
|
+
} else {
|
|
266
|
+
// Fade back
|
|
267
|
+
agent.parts.head.rotation.y *= 0.9;
|
|
268
|
+
if (Math.abs(agent.parts.head.rotation.y) < 0.01) {
|
|
269
|
+
agent._glanceTarget = null;
|
|
270
|
+
agent._glanceTimer = 0;
|
|
271
|
+
agent.parts.head.rotation.y = 0;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
208
276
|
// Idle breathing
|
|
209
277
|
if (!isWalking && !isSleeping) {
|
|
210
278
|
var breatheSpeed = S.conversationVelocity === -1 ? 1.2 : 2;
|