let-them-talk 5.3.0 → 5.4.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 +3 -1
- package/README.md +158 -592
- package/SECURITY.md +3 -3
- package/USAGE.md +151 -0
- package/agent-contracts.js +447 -0
- package/api-agents.js +760 -0
- package/autonomy/decision-v2.js +380 -0
- package/autonomy/watchdog-policy.js +572 -0
- package/cli.js +454 -298
- package/conversation-templates/autonomous-feature.json +83 -22
- package/conversation-templates/code-review.json +69 -21
- package/conversation-templates/debug-squad.json +69 -21
- package/conversation-templates/feature-build.json +69 -21
- package/conversation-templates/research-write.json +69 -21
- package/dashboard.html +3148 -174
- package/dashboard.js +823 -786
- package/data-dir.js +58 -0
- package/docs/architecture/branch-semantics.md +157 -0
- package/docs/architecture/canonical-event-schema.md +88 -0
- package/docs/architecture/markdown-workspace.md +183 -0
- package/docs/architecture/runtime-contract.md +459 -0
- package/docs/architecture/runtime-migration-hardening.md +64 -0
- package/events/hooks.js +154 -0
- package/events/log.js +457 -0
- package/events/replay.js +33 -0
- package/events/schema.js +432 -0
- package/managed-team-integration.js +261 -0
- package/office/agents.js +704 -597
- package/office/animation.js +1 -1
- package/office/assets/arcade-cabinet.js +141 -0
- package/office/assets/archway.js +77 -0
- package/office/assets/bar-counter.js +91 -0
- package/office/assets/bar-stool.js +71 -0
- package/office/assets/beanbag.js +64 -0
- package/office/assets/bench.js +99 -0
- package/office/assets/bollard.js +87 -0
- package/office/assets/cactus.js +100 -0
- package/office/assets/carpet-tile.js +46 -0
- package/office/assets/chair.js +123 -0
- package/office/assets/chandelier.js +107 -0
- package/office/assets/coffee-machine.js +95 -0
- package/office/assets/coffee-table.js +81 -0
- package/office/assets/column.js +95 -0
- package/office/assets/desk-lamp.js +102 -0
- package/office/assets/desk.js +76 -0
- package/office/assets/dining-table.js +105 -0
- package/office/assets/door.js +70 -0
- package/office/assets/dual-monitor.js +72 -0
- package/office/assets/fence.js +76 -0
- package/office/assets/filing-cabinet.js +111 -0
- package/office/assets/floor-lamp.js +69 -0
- package/office/assets/floor-tile.js +54 -0
- package/office/assets/flower-pot.js +76 -0
- package/office/assets/foosball.js +95 -0
- package/office/assets/fridge.js +99 -0
- package/office/assets/gaming-chair.js +154 -0
- package/office/assets/gaming-desk.js +105 -0
- package/office/assets/glass-door.js +72 -0
- package/office/assets/glass-wall.js +64 -0
- package/office/assets/half-wall.js +49 -0
- package/office/assets/hanging-plant.js +112 -0
- package/office/assets/index.js +151 -0
- package/office/assets/indoor-tree.js +90 -0
- package/office/assets/l-sofa.js +153 -0
- package/office/assets/marble-floor.js +64 -0
- package/office/assets/materials.js +40 -0
- package/office/assets/meeting-table.js +88 -0
- package/office/assets/microwave.js +94 -0
- package/office/assets/monitor.js +67 -0
- package/office/assets/neon-strip.js +73 -0
- package/office/assets/painting.js +84 -0
- package/office/assets/palm-tree.js +108 -0
- package/office/assets/pc-tower.js +91 -0
- package/office/assets/pendant-light.js +67 -0
- package/office/assets/ping-pong.js +114 -0
- package/office/assets/plant.js +72 -0
- package/office/assets/planter-box.js +95 -0
- package/office/assets/pool-table.js +94 -0
- package/office/assets/printer.js +113 -0
- package/office/assets/reception-desk.js +133 -0
- package/office/assets/rug.js +78 -0
- package/office/assets/sculpture.js +85 -0
- package/office/assets/server-rack.js +98 -0
- package/office/assets/sink.js +109 -0
- package/office/assets/sofa.js +106 -0
- package/office/assets/speaker.js +83 -0
- package/office/assets/spotlight.js +83 -0
- package/office/assets/street-lamp.js +97 -0
- package/office/assets/trash-can.js +83 -0
- package/office/assets/treadmill.js +126 -0
- package/office/assets/trophy.js +89 -0
- package/office/assets/tv-screen.js +79 -0
- package/office/assets/vase.js +84 -0
- package/office/assets/wall-clock.js +84 -0
- package/office/assets/wall.js +53 -0
- package/office/assets/water-cooler.js +146 -0
- package/office/assets/whiteboard.js +115 -0
- package/office/assets.js +3 -431
- package/office/builder.js +791 -355
- package/office/campus-env.js +1012 -1119
- package/office/environment.js +2 -0
- package/office/gallery.js +997 -0
- package/office/index.js +165 -61
- package/office/navigation.js +173 -152
- package/office/player.js +178 -68
- package/office/robot-character.js +272 -0
- package/office/spectator-camera.js +33 -10
- package/office/state.js +2 -0
- package/office/world-save.js +35 -4
- package/package.json +57 -3
- package/providers/comfyui.js +383 -0
- package/providers/dalle.js +79 -0
- package/providers/gemini.js +181 -0
- package/providers/ollama.js +184 -0
- package/providers/replicate.js +115 -0
- package/providers/zai.js +183 -0
- package/runtime-descriptor.js +270 -0
- package/scripts/check-agent-contract-advisory.js +132 -0
- package/scripts/check-api-agent-parity.js +277 -0
- package/scripts/check-autonomy-v2-decision.js +207 -0
- package/scripts/check-autonomy-v2-execution.js +588 -0
- package/scripts/check-autonomy-v2-watchdog.js +224 -0
- package/scripts/check-branch-fork-snapshot.js +337 -0
- package/scripts/check-branch-isolation.js +787 -0
- package/scripts/check-branch-semantics.js +139 -0
- package/scripts/check-dashboard-control-plane.js +1304 -0
- package/scripts/check-docs-onboarding.js +490 -0
- package/scripts/check-event-schema.js +276 -0
- package/scripts/check-evidence-completion.js +239 -0
- package/scripts/check-invariants.js +992 -0
- package/scripts/check-lifecycle-hooks.js +525 -0
- package/scripts/check-managed-team-integration.js +166 -0
- package/scripts/check-markdown-workspace-export.js +548 -0
- package/scripts/check-markdown-workspace-safety.js +347 -0
- package/scripts/check-markdown-workspace.js +136 -0
- package/scripts/check-message-replay.js +429 -0
- package/scripts/check-migration-hardening.js +300 -0
- package/scripts/check-performance-indexing.js +272 -0
- package/scripts/check-provider-capabilities.js +316 -0
- package/scripts/check-runtime-contract.js +109 -0
- package/scripts/check-session-aware-context.js +172 -0
- package/scripts/check-session-lifecycle.js +210 -0
- package/scripts/export-markdown-workspace.js +84 -0
- package/scripts/fixtures/message-replay/clean.jsonl +2 -0
- package/scripts/fixtures/message-replay/corrupt-correction-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-jsonl.jsonl +1 -0
- package/scripts/fixtures/message-replay/corrupt-payload.jsonl +1 -0
- package/scripts/fixtures/message-replay/out-of-order.jsonl +2 -0
- package/scripts/migrate-legacy-to-canonical.js +201 -0
- package/scripts/run-verification-suite.js +242 -0
- package/scripts/sync-packaged-docs.js +69 -0
- package/server.js +9546 -7216
- package/state/agents.js +161 -0
- package/state/canonical.js +3068 -0
- package/state/dashboard-queries.js +441 -0
- package/state/evidence.js +56 -0
- package/state/io.js +69 -0
- package/state/markdown-workspace.js +951 -0
- package/state/messages.js +669 -0
- package/state/sessions.js +683 -0
- package/state/tasks-workflows.js +92 -0
- package/templates/debate.json +2 -2
- package/templates/managed.json +4 -4
- package/templates/pair.json +2 -2
- package/templates/review.json +2 -2
- package/templates/team.json +3 -3
package/office/agents.js
CHANGED
|
@@ -1,597 +1,704 @@
|
|
|
1
|
-
import { S } from './state.js';
|
|
2
|
-
import { DESK_POSITIONS, SPAWN_POS, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
|
|
3
|
-
import { createCharacter } from './character.js';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
var
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
agent.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
var
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
if (
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
showBubble(a, '
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1
|
+
import { S } from './state.js';
|
|
2
|
+
import { DESK_POSITIONS, SPAWN_POS, REST_AREA_POS, REST_AREA_ENTRANCE } from './constants.js';
|
|
3
|
+
import { createCharacter } from './character.js';
|
|
4
|
+
import { createRobotCharacter, updateRobotAnimation } from './robot-character.js';
|
|
5
|
+
import { resolveAppearance } from './appearance.js';
|
|
6
|
+
import { buildHair } from './hair.js';
|
|
7
|
+
import { buildFaceSprite, setEmotion } from './face.js';
|
|
8
|
+
import { buildOutfit, removeOutfit } from './outfits.js';
|
|
9
|
+
import { getNavigationPath } from './navigation.js';
|
|
10
|
+
import { GALLERY_SEATS } from './gallery.js';
|
|
11
|
+
|
|
12
|
+
// Track which gallery seats are occupied (prevents overlapping bots)
|
|
13
|
+
var _gallerySeatsOccupied = { image: null, video: null, texture: null };
|
|
14
|
+
|
|
15
|
+
// Map bot capability to gallery seat
|
|
16
|
+
// image_gen → image seat (left monitor), video_gen → video seat (center), texture_gen → texture seat (right)
|
|
17
|
+
// vision/chat bots → null (go to regular desk)
|
|
18
|
+
function _getGallerySeat(capability) {
|
|
19
|
+
var seatMap = { image_gen: 'image', video_gen: 'video', texture_gen: 'texture' };
|
|
20
|
+
var seatKey = seatMap[capability];
|
|
21
|
+
if (!seatKey || !GALLERY_SEATS || !GALLERY_SEATS[seatKey]) return null;
|
|
22
|
+
if (_gallerySeatsOccupied[seatKey]) return null; // seat taken
|
|
23
|
+
return GALLERY_SEATS[seatKey];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function _claimGallerySeat(capability, agentName) {
|
|
27
|
+
var seatMap = { image_gen: 'image', video_gen: 'video', texture_gen: 'texture' };
|
|
28
|
+
var seatKey = seatMap[capability];
|
|
29
|
+
if (seatKey) _gallerySeatsOccupied[seatKey] = agentName;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function _releaseGallerySeat(agentName) {
|
|
33
|
+
for (var key in _gallerySeatsOccupied) {
|
|
34
|
+
if (_gallerySeatsOccupied[key] === agentName) _gallerySeatsOccupied[key] = null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get the Z position an agent should walk to when going to their desk
|
|
39
|
+
function _agentDeskZ(agent) {
|
|
40
|
+
return agent.isGallerySeat ? agent.deskPos.z : agent.deskPos.z + 0.7;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Navigate agent using waypoint pathfinding (campus) or direct walk (other envs)
|
|
44
|
+
export function navigateTo(agent, tx, tz, callback) {
|
|
45
|
+
var path = getNavigationPath(agent.pos.x, agent.pos.z, tx, tz);
|
|
46
|
+
if (!path || path.length === 0) {
|
|
47
|
+
walkTo(agent, tx, tz, callback);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
// Queue all waypoints, put callback on the last one
|
|
51
|
+
agent.walkQueue = [];
|
|
52
|
+
for (var i = 1; i < path.length; i++) {
|
|
53
|
+
agent.walkQueue.push({ x: path[i].x, z: path[i].z, cb: null, triggerDoor: path[i].triggerDoor });
|
|
54
|
+
}
|
|
55
|
+
// Attach callback to last queued point (or first walk if only 1 point)
|
|
56
|
+
if (agent.walkQueue.length > 0) {
|
|
57
|
+
agent.walkQueue[agent.walkQueue.length - 1].cb = callback;
|
|
58
|
+
}
|
|
59
|
+
// Start walking to first point
|
|
60
|
+
var first = path[0];
|
|
61
|
+
walkTo(agent, first.x, first.z, first.triggerDoor ? function() { triggerManagerDoor(true); } : (path.length === 1 ? callback : null));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function triggerManagerDoor(open) {
|
|
65
|
+
if (S._managerDoor) {
|
|
66
|
+
S._managerDoorOpen = open ? 1 : 0;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function walkTo(agent, tx, tz, callback) {
|
|
71
|
+
var dx = tx - agent.pos.x;
|
|
72
|
+
var dz = tz - agent.pos.z;
|
|
73
|
+
var dist = Math.sqrt(dx * dx + dz * dz);
|
|
74
|
+
agent.walkStart = { x: agent.pos.x, z: agent.pos.z };
|
|
75
|
+
agent.target = { x: tx, z: tz, cb: callback || null };
|
|
76
|
+
agent.walkProgress = 0;
|
|
77
|
+
agent.walkDuration = Math.max(dist * 0.4, 0.3);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function showBubble(agent, text) {
|
|
81
|
+
var display = text.length > 80 ? text.substring(0, 77) + '...' : text;
|
|
82
|
+
agent.parts.bubbleDiv.textContent = display;
|
|
83
|
+
agent.parts.bubbleDiv.style.display = 'block';
|
|
84
|
+
agent.parts.bubbleDiv.style.opacity = '1';
|
|
85
|
+
agent.bubbleTimer = 4;
|
|
86
|
+
agent.bubbleText = display;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function getDeskPositions() {
|
|
90
|
+
return S._campusDeskPositions || DESK_POSITIONS;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function assignDesk(agentName) {
|
|
94
|
+
var desks = getDeskPositions();
|
|
95
|
+
var used = {};
|
|
96
|
+
for (var n in S.agents3d) used[S.agents3d[n].deskIdx] = true;
|
|
97
|
+
|
|
98
|
+
// If campus mode, check if agent has "Manager" role or name — assign last desk (manager office)
|
|
99
|
+
if (S.currentEnv === 'campus' && desks.length > 0) {
|
|
100
|
+
var info = (window.cachedAgents || {})[agentName] || {};
|
|
101
|
+
var role = (info.role || '').toLowerCase();
|
|
102
|
+
var dname = (info.display_name || agentName).toLowerCase();
|
|
103
|
+
var regName = agentName.toLowerCase();
|
|
104
|
+
var isManager = role === 'manager' || role === 'project lead' || role === 'ceo' || role === 'director' ||
|
|
105
|
+
role.indexOf('project manager') >= 0 || role.indexOf('team lead') >= 0 ||
|
|
106
|
+
dname === 'manager' || regName === 'manager';
|
|
107
|
+
var managerIdx = desks.length - 1; // last desk is manager office
|
|
108
|
+
if (isManager && !used[managerIdx]) {
|
|
109
|
+
return managerIdx;
|
|
110
|
+
}
|
|
111
|
+
// Non-manager agents skip the manager desk
|
|
112
|
+
for (var i = 0; i < desks.length - 1; i++) {
|
|
113
|
+
if (!used[i]) return i;
|
|
114
|
+
}
|
|
115
|
+
return Object.keys(S.agents3d).length % (desks.length - 1);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
for (var j = 0; j < desks.length; j++) {
|
|
119
|
+
if (!used[j]) return j;
|
|
120
|
+
}
|
|
121
|
+
return Object.keys(S.agents3d).length % desks.length;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function fetchTasks() {
|
|
125
|
+
var base = typeof window.scopedApiUrl === 'function'
|
|
126
|
+
? window.scopedApiUrl('/api/tasks')
|
|
127
|
+
: (window.currentProjectPath ? '/api/tasks?project=' + encodeURIComponent(window.currentProjectPath) : '/api/tasks');
|
|
128
|
+
fetch(base).then(function(r) { return r.json(); }).then(function(data) {
|
|
129
|
+
S.cachedTasks = Array.isArray(data) ? data : (data.tasks || []);
|
|
130
|
+
}).catch(function() {});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getAgentTask(agentName) {
|
|
134
|
+
for (var i = 0; i < S.cachedTasks.length; i++) {
|
|
135
|
+
var t = S.cachedTasks[i];
|
|
136
|
+
if (t.assignee === agentName || t.assigned_to === agentName) return t;
|
|
137
|
+
}
|
|
138
|
+
return null;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function updateConversationVelocity() {
|
|
142
|
+
var history = window.cachedHistory;
|
|
143
|
+
if (!history || history.length === 0) { S.conversationVelocity = 0; return; }
|
|
144
|
+
var now = Date.now();
|
|
145
|
+
var cutoff30s = now - 30000;
|
|
146
|
+
var cutoff2m = now - 120000;
|
|
147
|
+
var recent30 = 0, recent2m = 0;
|
|
148
|
+
for (var i = history.length - 1; i >= 0; i--) {
|
|
149
|
+
var ts = new Date(history[i].timestamp).getTime();
|
|
150
|
+
if (ts > cutoff30s) recent30++;
|
|
151
|
+
if (ts > cutoff2m) recent2m++;
|
|
152
|
+
if (ts <= cutoff2m) break;
|
|
153
|
+
}
|
|
154
|
+
S.conversationVelocity = recent30 >= 3 ? 1 : (recent2m === 0 ? -1 : 0);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function updateLabel(agent) {
|
|
158
|
+
var nameEl = agent.parts.labelDiv.querySelector('.office3d-label-name');
|
|
159
|
+
var dotEl = agent.parts.labelDiv.querySelector('.office3d-label-dot');
|
|
160
|
+
if (nameEl) nameEl.textContent = agent.displayName;
|
|
161
|
+
if (dotEl) {
|
|
162
|
+
var colors = { active: '#4ade80', sleeping: '#facc15', dead: '#f87171' };
|
|
163
|
+
dotEl.style.background = colors[agent.state] || '#f87171';
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function updateDeskScreen(deskIdx, status, isListening) {
|
|
168
|
+
var desk = S.deskMeshes[deskIdx];
|
|
169
|
+
if (!desk) return;
|
|
170
|
+
if (status === 'active' && isListening) {
|
|
171
|
+
// Listening — green screen
|
|
172
|
+
desk.screenMat.emissive.setHex(0x22c55e);
|
|
173
|
+
desk.screenMat.emissiveIntensity = 0.5;
|
|
174
|
+
desk.screenMat.color.setHex(0x22c55e);
|
|
175
|
+
} else if (status === 'active' && !isListening) {
|
|
176
|
+
// Active but NOT listening — red screen
|
|
177
|
+
desk.screenMat.emissive.setHex(0xef4444);
|
|
178
|
+
desk.screenMat.emissiveIntensity = 0.6;
|
|
179
|
+
desk.screenMat.color.setHex(0xef4444);
|
|
180
|
+
} else if (status === 'sleeping') {
|
|
181
|
+
desk.screenMat.emissive.setHex(0x1a2744);
|
|
182
|
+
desk.screenMat.emissiveIntensity = 0.15;
|
|
183
|
+
desk.screenMat.color.setHex(0x1a2744);
|
|
184
|
+
} else {
|
|
185
|
+
desk.screenMat.emissive.setHex(0x333333);
|
|
186
|
+
desk.screenMat.emissiveIntensity = 0.1;
|
|
187
|
+
desk.screenMat.color.setHex(0x333333);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function shouldRenderAgent(info) {
|
|
192
|
+
var isApiAgent = !!(info && (info.is_api_agent || (info.role && info.role === 'api-agent')));
|
|
193
|
+
return !(isApiAgent && info.status !== 'active');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function retireAgent(agentName, agent, status) {
|
|
197
|
+
if (!agent) return;
|
|
198
|
+
agent.prevState = agent.state;
|
|
199
|
+
agent.state = status || 'dead';
|
|
200
|
+
agent.isListening = false;
|
|
201
|
+
agent.target = null;
|
|
202
|
+
agent.walkQueue = [];
|
|
203
|
+
agent.walkProgress = 0;
|
|
204
|
+
agent.walkDuration = 0;
|
|
205
|
+
agent.isSitting = false;
|
|
206
|
+
agent.location = 'desk';
|
|
207
|
+
if (agent.deskIdx >= 0) updateDeskScreen(agent.deskIdx, agent.state, false);
|
|
208
|
+
updateLabel(agent);
|
|
209
|
+
agent.registered = false;
|
|
210
|
+
if (!agent.dying) {
|
|
211
|
+
agent.dying = true;
|
|
212
|
+
agent.deathOpacity = 1;
|
|
213
|
+
}
|
|
214
|
+
_releaseGallerySeat(agentName);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function projectBotCapability(capabilities, legacyBotCapability) {
|
|
218
|
+
if (Array.isArray(capabilities)) {
|
|
219
|
+
if (capabilities.indexOf('video_generation') !== -1) return 'video_gen';
|
|
220
|
+
if (capabilities.indexOf('texture_generation') !== -1) return 'texture_gen';
|
|
221
|
+
if (capabilities.indexOf('image_generation') !== -1) return 'image_gen';
|
|
222
|
+
if (capabilities.indexOf('vision') !== -1) return 'vision';
|
|
223
|
+
if (capabilities.indexOf('chat') !== -1) return 'chat';
|
|
224
|
+
}
|
|
225
|
+
return legacyBotCapability || '';
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function flashDeskScreen(deskIdx) {
|
|
229
|
+
var desk = S.deskMeshes[deskIdx];
|
|
230
|
+
if (!desk) return;
|
|
231
|
+
// Flash white briefly — the next syncAgents call (every 2s) will set the correct persistent color via updateDeskScreen
|
|
232
|
+
desk.screenMat.emissive.setHex(0xffffff);
|
|
233
|
+
desk.screenMat.emissiveIntensity = 1.5;
|
|
234
|
+
setTimeout(function() {
|
|
235
|
+
// Force immediate red until next sync corrects it
|
|
236
|
+
desk.screenMat.emissive.setHex(0xef4444);
|
|
237
|
+
desk.screenMat.emissiveIntensity = 0.6;
|
|
238
|
+
desk.screenMat.color.setHex(0xef4444);
|
|
239
|
+
}, 300);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function rebuildCharacterAppearance(agent) {
|
|
243
|
+
var a = resolveAppearance(agent.displayName, agent.appearance);
|
|
244
|
+
agent.parts.bodyMat.color.setHex(a.shirt_hex);
|
|
245
|
+
agent.parts.armMat.color.setHex(a.shirt_hex);
|
|
246
|
+
agent.parts.legMat.color.setHex(a.pants_hex);
|
|
247
|
+
agent.parts.headMat.color.setHex(a.head_hex);
|
|
248
|
+
agent.parts.handMat.color.setHex(a.head_hex);
|
|
249
|
+
agent.parts.shoeMat.color.setHex(a.shoe_hex);
|
|
250
|
+
|
|
251
|
+
// Rebuild hair
|
|
252
|
+
var oldHair = agent.parts.hairGroup;
|
|
253
|
+
agent.parts.group.remove(oldHair);
|
|
254
|
+
oldHair.traverse(function(c) { if (c.geometry) c.geometry.dispose(); if (c.material) c.material.dispose(); });
|
|
255
|
+
var newHair = buildHair(a.hair_style, a.hair_hex);
|
|
256
|
+
newHair.position.y = 1.05;
|
|
257
|
+
agent.parts.group.add(newHair);
|
|
258
|
+
agent.parts.hairGroup = newHair;
|
|
259
|
+
|
|
260
|
+
// Rebuild face
|
|
261
|
+
var oldFace = agent.parts.faceSprite;
|
|
262
|
+
agent.parts.head.remove(oldFace);
|
|
263
|
+
if (oldFace.material.map) oldFace.material.map.dispose();
|
|
264
|
+
oldFace.material.dispose();
|
|
265
|
+
var newFace = buildFaceSprite(a.eye_style, a.mouth_style, agent.state === 'sleeping');
|
|
266
|
+
newFace.position.set(0, 0, 0.251);
|
|
267
|
+
agent.parts.head.add(newFace);
|
|
268
|
+
agent.parts.faceSprite = newFace;
|
|
269
|
+
|
|
270
|
+
// Rebuild outfit
|
|
271
|
+
removeOutfit(agent.parts.group);
|
|
272
|
+
if (a.outfit) {
|
|
273
|
+
agent.parts.outfitGroup = buildOutfit(a.outfit, { shirt_color: a.shirt_color, pants_color: a.pants_color }, agent.parts.group);
|
|
274
|
+
} else {
|
|
275
|
+
agent.parts.outfitGroup = null;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
export function syncAgents() {
|
|
280
|
+
if (!window.cachedAgents) return;
|
|
281
|
+
|
|
282
|
+
// Reset gallery seats if environment was switched
|
|
283
|
+
if (window._gallerySeatsReset) {
|
|
284
|
+
_gallerySeatsOccupied = { image: null, video: null, texture: null };
|
|
285
|
+
window._gallerySeatsReset = false;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
fetchTasks();
|
|
289
|
+
updateConversationVelocity();
|
|
290
|
+
|
|
291
|
+
for (var name in window.cachedAgents) {
|
|
292
|
+
var info = window.cachedAgents[name];
|
|
293
|
+
// Skip API agents that aren't actively running (user hasn't clicked Start)
|
|
294
|
+
var isApiAgent = !!(info.is_api_agent || (info.role && info.role === 'api-agent'));
|
|
295
|
+
if (!shouldRenderAgent(info)) {
|
|
296
|
+
if (S.agents3d[name]) retireAgent(name, S.agents3d[name], info.status || 'dead');
|
|
297
|
+
continue;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (!S.agents3d[name]) {
|
|
301
|
+
var providerColor = info.provider_color || '#0ea5e9';
|
|
302
|
+
var botCap = projectBotCapability(info.capabilities, info.bot_capability);
|
|
303
|
+
var deskIdx, deskPos;
|
|
304
|
+
|
|
305
|
+
if (isApiAgent && _getGallerySeat(botCap)) {
|
|
306
|
+
// Creative bots (image/video/texture generators) sit at gallery desk
|
|
307
|
+
deskIdx = -1;
|
|
308
|
+
deskPos = _getGallerySeat(botCap);
|
|
309
|
+
} else if (isApiAgent && !_getGallerySeat(botCap)) {
|
|
310
|
+
// Non-creative bots (vision, chat) get a regular workspace desk
|
|
311
|
+
deskIdx = assignDesk(name);
|
|
312
|
+
var allDesks2 = getDeskPositions();
|
|
313
|
+
deskPos = allDesks2[deskIdx] || allDesks2[0];
|
|
314
|
+
} else {
|
|
315
|
+
deskIdx = assignDesk(name);
|
|
316
|
+
var allDesks = getDeskPositions();
|
|
317
|
+
deskPos = allDesks[deskIdx] || allDesks[0];
|
|
318
|
+
}
|
|
319
|
+
// Gallery bots (image/video/texture) get robot character, chat/vision bots get chibi
|
|
320
|
+
var isGalleryBot = isApiAgent && deskIdx === -1;
|
|
321
|
+
var parts = isGalleryBot
|
|
322
|
+
? createRobotCharacter(info.display_name || name, providerColor)
|
|
323
|
+
: createCharacter(info.display_name || name, info.appearance || {});
|
|
324
|
+
var agent = {
|
|
325
|
+
name: name,
|
|
326
|
+
displayName: info.display_name || name,
|
|
327
|
+
appearance: info.appearance || {},
|
|
328
|
+
parts: parts,
|
|
329
|
+
deskIdx: deskIdx,
|
|
330
|
+
deskPos: { x: deskPos.x, z: deskPos.z },
|
|
331
|
+
pos: { x: SPAWN_POS.x, z: SPAWN_POS.z },
|
|
332
|
+
target: null,
|
|
333
|
+
walkQueue: [],
|
|
334
|
+
walkProgress: 0,
|
|
335
|
+
walkDuration: 0,
|
|
336
|
+
walkStart: null,
|
|
337
|
+
state: info.status || 'active',
|
|
338
|
+
prevState: null,
|
|
339
|
+
registered: false,
|
|
340
|
+
bubbleTimer: 0,
|
|
341
|
+
bubbleText: '',
|
|
342
|
+
isSitting: false,
|
|
343
|
+
sittingLerp: 0,
|
|
344
|
+
facingTarget: 0,
|
|
345
|
+
zzzActive: false,
|
|
346
|
+
sleepTransition: 0,
|
|
347
|
+
spawnOpacity: 1,
|
|
348
|
+
deathOpacity: 1,
|
|
349
|
+
dying: false,
|
|
350
|
+
currentTask: null,
|
|
351
|
+
taskCelebration: 0,
|
|
352
|
+
isListening: !!(info.is_listening),
|
|
353
|
+
handRaiseTimer: 0,
|
|
354
|
+
waveTimer: 0,
|
|
355
|
+
thinkTimer: 0,
|
|
356
|
+
pointTimer: 0,
|
|
357
|
+
celebrateTimer: 0,
|
|
358
|
+
stretchTimer: 0,
|
|
359
|
+
idleGestureTimer: 5 + Math.random() * 10,
|
|
360
|
+
listenLostTimer: 0,
|
|
361
|
+
lastMessageTime: 0,
|
|
362
|
+
monitorTimer: 0,
|
|
363
|
+
location: 'desk', // 'desk', 'dressing_room', 'rest', 'walking'
|
|
364
|
+
isApiAgent: isApiAgent,
|
|
365
|
+
botCapability: botCap,
|
|
366
|
+
isGallerySeat: !!(isApiAgent && deskIdx === -1),
|
|
367
|
+
_processing: false,
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// Claim gallery seat if applicable
|
|
371
|
+
if (isApiAgent && deskIdx === -1) _claimGallerySeat(botCap, name);
|
|
372
|
+
|
|
373
|
+
parts.group.position.set(SPAWN_POS.x, 0, SPAWN_POS.z);
|
|
374
|
+
S.scene.add(parts.group);
|
|
375
|
+
updateLabel(agent);
|
|
376
|
+
S.agents3d[name] = agent;
|
|
377
|
+
|
|
378
|
+
// Gallery bots walk directly to seat pos, others walk to desk + 0.7 (chair offset)
|
|
379
|
+
var walkTargetZ = agent.isGallerySeat ? deskPos.z : deskPos.z + 0.7;
|
|
380
|
+
showBubble(agent, 'Checking in...');
|
|
381
|
+
(function(a, wz) {
|
|
382
|
+
setTimeout(function() {
|
|
383
|
+
navigateTo(a, a.deskPos.x, wz, function() {
|
|
384
|
+
a.registered = true;
|
|
385
|
+
showBubble(a, a.isApiAgent ? 'Systems online.' : 'Ready to work!');
|
|
386
|
+
if (a.deskIdx >= 0) updateDeskScreen(a.deskIdx, a.state, a.isListening);
|
|
387
|
+
});
|
|
388
|
+
}, 800);
|
|
389
|
+
})(agent, walkTargetZ);
|
|
390
|
+
} else {
|
|
391
|
+
var existing = S.agents3d[name];
|
|
392
|
+
var newState = info.status || 'active';
|
|
393
|
+
var oldState = existing.state;
|
|
394
|
+
|
|
395
|
+
// Don't override local state changes (rest area sleeping, dressing room)
|
|
396
|
+
var isLocalOverride = existing.location === 'rest' || existing.location === 'dressing_room' || existing.location === 'walking';
|
|
397
|
+
if (newState !== oldState && !isLocalOverride) {
|
|
398
|
+
existing.prevState = oldState;
|
|
399
|
+
existing.state = newState;
|
|
400
|
+
if (newState === 'dead' && !existing.dying) {
|
|
401
|
+
existing.dying = true;
|
|
402
|
+
existing.deathOpacity = 1;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// --- Autonomous behaviors: sleeping → rest area, waking → back to desk ---
|
|
407
|
+
if (newState === 'sleeping' && oldState === 'active' && existing.location === 'desk' && existing.registered && !existing.dying) {
|
|
408
|
+
// Agent fell asleep — walk to rest area after a short delay
|
|
409
|
+
existing.location = 'walking';
|
|
410
|
+
(function(a) {
|
|
411
|
+
setTimeout(function() {
|
|
412
|
+
showBubble(a, 'Need a break...');
|
|
413
|
+
a.isSitting = false;
|
|
414
|
+
navigateTo(a, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
|
|
415
|
+
navigateTo(a, REST_AREA_POS.x, REST_AREA_POS.z, function() {
|
|
416
|
+
a.location = 'rest';
|
|
417
|
+
a.state = 'sleeping';
|
|
418
|
+
showBubble(a, 'zzz...');
|
|
419
|
+
});
|
|
420
|
+
});
|
|
421
|
+
}, 1000 + Math.random() * 2000);
|
|
422
|
+
})(existing);
|
|
423
|
+
}
|
|
424
|
+
if (newState === 'active' && (oldState === 'sleeping' || existing.location === 'rest') && existing.location !== 'desk' && existing.registered && !existing.dying) {
|
|
425
|
+
// Agent woke up — walk back to desk
|
|
426
|
+
existing.location = 'walking';
|
|
427
|
+
existing.state = 'active';
|
|
428
|
+
(function(a) {
|
|
429
|
+
showBubble(a, 'Back to work!');
|
|
430
|
+
navigateTo(a, a.deskPos.x, _agentDeskZ(a), function() {
|
|
431
|
+
a.location = 'desk';
|
|
432
|
+
});
|
|
433
|
+
})(existing);
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
existing.displayName = info.display_name || name;
|
|
437
|
+
var wasListening = existing.isListening;
|
|
438
|
+
existing.isListening = !!(info.is_listening);
|
|
439
|
+
|
|
440
|
+
// Detect listen mode change — update screen color persistently
|
|
441
|
+
if (wasListening && !existing.isListening) {
|
|
442
|
+
// Left listen mode — flash then stay red until next sync sets updateDeskScreen
|
|
443
|
+
existing.listenLostTimer = 3;
|
|
444
|
+
flashDeskScreen(existing.deskIdx);
|
|
445
|
+
}
|
|
446
|
+
if (!wasListening && existing.isListening) {
|
|
447
|
+
// Entered listen mode — next updateDeskScreen will set green
|
|
448
|
+
existing.listenLostTimer = 0;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
var task = getAgentTask(name);
|
|
452
|
+
if (task) {
|
|
453
|
+
var prevTask = existing.currentTask;
|
|
454
|
+
existing.currentTask = task;
|
|
455
|
+
if (prevTask && prevTask.status !== 'done' && task.status === 'done') {
|
|
456
|
+
existing.taskCelebration = 2;
|
|
457
|
+
existing.celebrateTimer = 1.5;
|
|
458
|
+
setEmotion(existing, 'happy', 6);
|
|
459
|
+
}
|
|
460
|
+
// Blocked task → frustrated face
|
|
461
|
+
if (task.status === 'blocked' && (!prevTask || prevTask.status !== 'blocked')) {
|
|
462
|
+
setEmotion(existing, 'frustrated', 8);
|
|
463
|
+
}
|
|
464
|
+
} else {
|
|
465
|
+
existing.currentTask = null;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Listening agents look focused
|
|
469
|
+
if (existing.isListening && !wasListening) {
|
|
470
|
+
setEmotion(existing, 'focused', 10);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
var newApp = info.appearance || {};
|
|
474
|
+
if (JSON.stringify(newApp) !== JSON.stringify(existing.appearance)) {
|
|
475
|
+
existing.appearance = newApp;
|
|
476
|
+
rebuildCharacterAppearance(existing);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
updateLabel(existing);
|
|
480
|
+
if (existing.registered && existing.deskIdx >= 0) updateDeskScreen(existing.deskIdx, existing.state, existing.isListening);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// --- Random social behavior: idle agents occasionally stretch or look around ---
|
|
485
|
+
// Limit concurrent social walks to prevent traffic jams (max 2 walking at once)
|
|
486
|
+
var walkingCount = 0;
|
|
487
|
+
for (var wn in S.agents3d) { if (S.agents3d[wn].location === 'walking') walkingCount++; }
|
|
488
|
+
|
|
489
|
+
for (var sn in S.agents3d) {
|
|
490
|
+
var sa = S.agents3d[sn];
|
|
491
|
+
if (!sa.registered || sa.state !== 'active' || sa.location !== 'desk' || sa.target) continue;
|
|
492
|
+
if (!sa._socialTimer) sa._socialTimer = 30 + Math.random() * 60;
|
|
493
|
+
sa._socialTimer -= 2; // syncAgents runs every ~2s
|
|
494
|
+
if (sa._socialTimer <= 0) {
|
|
495
|
+
sa._socialTimer = 40 + Math.random() * 80; // next social event in 40-120s
|
|
496
|
+
// Pick a random behavior: stretch, look around, or visit another agent
|
|
497
|
+
var roll = Math.random();
|
|
498
|
+
if (roll < 0.4) {
|
|
499
|
+
// Stretch at desk
|
|
500
|
+
sa.stretchTimer = 2;
|
|
501
|
+
} else if (roll < 0.7) {
|
|
502
|
+
// Look around curiously
|
|
503
|
+
sa.thinkTimer = 1.5;
|
|
504
|
+
} else if (walkingCount < 2) {
|
|
505
|
+
// Walk to a random nearby agent's desk to "chat" then return (max 2 concurrent)
|
|
506
|
+
var others = [];
|
|
507
|
+
for (var on in S.agents3d) {
|
|
508
|
+
if (on !== sn && S.agents3d[on].registered && S.agents3d[on].state === 'active' && S.agents3d[on].location === 'desk') {
|
|
509
|
+
others.push(S.agents3d[on]);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (others.length > 0) {
|
|
513
|
+
var buddy = others[Math.floor(Math.random() * others.length)];
|
|
514
|
+
(function(a, b) {
|
|
515
|
+
a.location = 'walking';
|
|
516
|
+
a.isSitting = false;
|
|
517
|
+
showBubble(a, 'Hey ' + b.displayName + '!');
|
|
518
|
+
setEmotion(a, 'playful', 6);
|
|
519
|
+
var stopX = b.deskPos.x + 1.5;
|
|
520
|
+
var stopZ = _agentDeskZ(b);
|
|
521
|
+
navigateTo(a, stopX, stopZ, function() {
|
|
522
|
+
// Face buddy
|
|
523
|
+
var dx = b.pos.x - a.pos.x;
|
|
524
|
+
var dz = b.pos.z - a.pos.z;
|
|
525
|
+
a.facingTarget = Math.atan2(dx, dz);
|
|
526
|
+
a.waveTimer = 0.8;
|
|
527
|
+
// Buddy turns toward visitor
|
|
528
|
+
b.facingTarget = Math.atan2(-dx, -dz);
|
|
529
|
+
setTimeout(function() {
|
|
530
|
+
showBubble(a, 'Back to it!');
|
|
531
|
+
navigateTo(a, a.deskPos.x, _agentDeskZ(a), function() {
|
|
532
|
+
a.location = 'desk';
|
|
533
|
+
});
|
|
534
|
+
// Buddy turns back to desk
|
|
535
|
+
setTimeout(function() { b.facingTarget = Math.PI; }, 1500);
|
|
536
|
+
}, 3000 + Math.random() * 2000);
|
|
537
|
+
});
|
|
538
|
+
})(sa, buddy);
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
for (var n in S.agents3d) {
|
|
545
|
+
if (!window.cachedAgents[n]) {
|
|
546
|
+
retireAgent(n, S.agents3d[n], 'dead');
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
export function processMessages() {
|
|
552
|
+
var history = window.cachedHistory;
|
|
553
|
+
if (!history || history.length === 0) return;
|
|
554
|
+
|
|
555
|
+
// Use window-level counter so it persists across 3D stop/start cycles (tab switches)
|
|
556
|
+
// This prevents message replay when user switches from Messages tab back to 3D Hub
|
|
557
|
+
if (typeof window._lastProcessedMsg === 'undefined') window._lastProcessedMsg = 0;
|
|
558
|
+
var newMsgs = history.slice(window._lastProcessedMsg);
|
|
559
|
+
window._lastProcessedMsg = history.length;
|
|
560
|
+
|
|
561
|
+
for (var i = 0; i < newMsgs.length; i++) {
|
|
562
|
+
var msg = newMsgs[i];
|
|
563
|
+
var from = S.agents3d[msg.from];
|
|
564
|
+
if (!from || !from.registered) continue;
|
|
565
|
+
var text = msg.content || msg.message || '';
|
|
566
|
+
|
|
567
|
+
from.lastMessageTime = Date.now();
|
|
568
|
+
flashDeskScreen(from.deskIdx);
|
|
569
|
+
|
|
570
|
+
// Instant preview bubble — show short text immediately before walk animation
|
|
571
|
+
// Gives users instant visual feedback that the agent is about to speak
|
|
572
|
+
var preview = text.length > 30 ? text.substring(0, 27) + '...' : text;
|
|
573
|
+
showBubble(from, preview);
|
|
574
|
+
|
|
575
|
+
// Auto-celebrate on task completion events
|
|
576
|
+
if (text.indexOf('[EVENT] Task') >= 0 && text.indexOf('completed') >= 0) {
|
|
577
|
+
from.celebrateTimer = 1.5;
|
|
578
|
+
from.taskCelebration = 2;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
// Emotion detection from message content
|
|
582
|
+
var textLower = text.toLowerCase();
|
|
583
|
+
if (textLower.indexOf('done') >= 0 || textLower.indexOf('pass') >= 0 || textLower.indexOf('success') >= 0 || textLower.indexOf('great') >= 0 || textLower.indexOf('shipped') >= 0) {
|
|
584
|
+
setEmotion(from, 'happy', 5);
|
|
585
|
+
} else if (textLower.indexOf('error') >= 0 || textLower.indexOf('fail') >= 0 || textLower.indexOf('bug') >= 0 || textLower.indexOf('broken') >= 0) {
|
|
586
|
+
setEmotion(from, 'frustrated', 5);
|
|
587
|
+
} else if (textLower.indexOf('?') >= 0 && (textLower.indexOf('how') >= 0 || textLower.indexOf('why') >= 0 || textLower.indexOf('what if') >= 0)) {
|
|
588
|
+
setEmotion(from, 'thinking', 4);
|
|
589
|
+
} else if (textLower.indexOf('!') >= 0 && (textLower.indexOf('wow') >= 0 || textLower.indexOf('amazing') >= 0 || textLower.indexOf('awesome') >= 0)) {
|
|
590
|
+
setEmotion(from, 'excited', 4);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// Target agent gets surprised when directly addressed
|
|
594
|
+
if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
|
|
595
|
+
var targetAgent = S.agents3d[msg.to];
|
|
596
|
+
if (targetAgent.registered && targetAgent.isSitting) {
|
|
597
|
+
setEmotion(targetAgent, 'surprised', 2);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// Contextual gesture based on message type
|
|
602
|
+
var isBC = !msg.to || msg.to === 'all';
|
|
603
|
+
if (isBC) {
|
|
604
|
+
from.waveTimer = 0.8;
|
|
605
|
+
} else {
|
|
606
|
+
from.pointTimer = 0.6;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Glance reaction — nearby sitting agents glance toward the speaker
|
|
610
|
+
for (var gn in S.agents3d) {
|
|
611
|
+
var ga = S.agents3d[gn];
|
|
612
|
+
if (gn === msg.from || gn === msg.to || !ga.registered || ga.state !== 'active' || !ga.isSitting) continue;
|
|
613
|
+
var gdx = from.pos.x - ga.pos.x;
|
|
614
|
+
ga._glanceTarget = from.name;
|
|
615
|
+
ga._glanceDirection = gdx > 0 ? 1 : -1; // left or right glance
|
|
616
|
+
ga._glanceTimer = 0;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (msg.to && msg.to !== 'all' && S.agents3d[msg.to]) {
|
|
620
|
+
var target = S.agents3d[msg.to];
|
|
621
|
+
(function(f, t, txt) {
|
|
622
|
+
setTimeout(function() {
|
|
623
|
+
f.walkQueue = [];
|
|
624
|
+
// Calculate a stop point ~1.8m away from the target, facing them
|
|
625
|
+
var tx = t.pos.x, tz = t.pos.z;
|
|
626
|
+
var fx = f.pos.x, fz = f.pos.z;
|
|
627
|
+
var adx = tx - fx, adz = tz - fz;
|
|
628
|
+
var dist = Math.sqrt(adx * adx + adz * adz);
|
|
629
|
+
var stopDist = 1.8;
|
|
630
|
+
var stopX, stopZ;
|
|
631
|
+
if (dist > stopDist + 0.5) {
|
|
632
|
+
// Approach from sender's direction, stop 1.8m away
|
|
633
|
+
stopX = tx - (adx / dist) * stopDist;
|
|
634
|
+
stopZ = tz - (adz / dist) * stopDist;
|
|
635
|
+
} else {
|
|
636
|
+
// Already close — just step to the side of target's desk
|
|
637
|
+
stopX = tx + 1.5;
|
|
638
|
+
stopZ = tz;
|
|
639
|
+
}
|
|
640
|
+
navigateTo(f, stopX, stopZ, function() {
|
|
641
|
+
// Sender faces target
|
|
642
|
+
var dx2 = t.pos.x - f.pos.x;
|
|
643
|
+
var dz2 = t.pos.z - f.pos.z;
|
|
644
|
+
f.facingTarget = Math.atan2(dx2, dz2);
|
|
645
|
+
showBubble(f, txt);
|
|
646
|
+
|
|
647
|
+
// Target turns toward sender (listener reaction)
|
|
648
|
+
var rdx = f.pos.x - t.pos.x;
|
|
649
|
+
var rdz = f.pos.z - t.pos.z;
|
|
650
|
+
t.facingTarget = Math.atan2(rdx, rdz);
|
|
651
|
+
t.isListening = true;
|
|
652
|
+
t._listeningTo = f.name;
|
|
653
|
+
|
|
654
|
+
setTimeout(function() {
|
|
655
|
+
// Sender walks back to desk
|
|
656
|
+
navigateTo(f, f.deskPos.x, _agentDeskZ(f));
|
|
657
|
+
// Target turns back to desk after a short delay
|
|
658
|
+
setTimeout(function() {
|
|
659
|
+
if (t._listeningTo === f.name) {
|
|
660
|
+
t.isListening = false;
|
|
661
|
+
t._listeningTo = null;
|
|
662
|
+
t.facingTarget = Math.PI; // face desk
|
|
663
|
+
}
|
|
664
|
+
}, 1500);
|
|
665
|
+
}, 4200);
|
|
666
|
+
});
|
|
667
|
+
}, 400);
|
|
668
|
+
})(from, target, text);
|
|
669
|
+
} else {
|
|
670
|
+
(function(f, txt) {
|
|
671
|
+
setTimeout(function() {
|
|
672
|
+
f.walkQueue = [];
|
|
673
|
+
navigateTo(f, 0, 0, function() {
|
|
674
|
+
showBubble(f, txt);
|
|
675
|
+
// All nearby agents turn toward the broadcaster
|
|
676
|
+
for (var an in S.agents3d) {
|
|
677
|
+
var a = S.agents3d[an];
|
|
678
|
+
if (a.name === f.name || !a.registered || a.state !== 'active') continue;
|
|
679
|
+
var bdx = f.pos.x - a.pos.x;
|
|
680
|
+
var bdz = f.pos.z - a.pos.z;
|
|
681
|
+
a.facingTarget = Math.atan2(bdx, bdz);
|
|
682
|
+
a.isListening = true;
|
|
683
|
+
a._listeningTo = f.name;
|
|
684
|
+
}
|
|
685
|
+
setTimeout(function() {
|
|
686
|
+
navigateTo(f, f.deskPos.x, _agentDeskZ(f));
|
|
687
|
+
// All listeners turn back
|
|
688
|
+
setTimeout(function() {
|
|
689
|
+
for (var an2 in S.agents3d) {
|
|
690
|
+
var a2 = S.agents3d[an2];
|
|
691
|
+
if (a2._listeningTo === f.name) {
|
|
692
|
+
a2.isListening = false;
|
|
693
|
+
a2._listeningTo = null;
|
|
694
|
+
a2.facingTarget = Math.PI;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}, 1500);
|
|
698
|
+
}, 4200);
|
|
699
|
+
});
|
|
700
|
+
}, 400);
|
|
701
|
+
})(from, text);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|