let-them-talk 3.6.0 → 3.6.2
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 +22 -0
- package/cli.js +1 -1
- package/dashboard.html +10 -0
- package/office/agents.js +89 -15
- package/office/animation.js +34 -0
- package/office/campus-env.js +1461 -0
- package/office/environment.js +10 -0
- package/office/index.js +52 -16
- package/office/navigation.js +263 -0
- package/office/scene.js +3 -3
- package/office/spectator-camera.js +1 -1
- package/office/state.js +1 -1
- package/package.json +1 -1
- package/server.js +63 -10
package/office/environment.js
CHANGED
|
@@ -2,6 +2,7 @@ import * as THREE from 'three';
|
|
|
2
2
|
import { CSS2DObject } from 'three/addons/renderers/CSS2DRenderer.js';
|
|
3
3
|
import { S } from './state.js';
|
|
4
4
|
import { FLOOR_W, FLOOR_D, DESK_POSITIONS, RECEPTION_POS, ENVS, DRESSING_ROOM_POS, REST_AREA_POS } from './constants.js';
|
|
5
|
+
import { buildCampusEnvironment, getCampusDeskPositions } from './campus-env.js';
|
|
5
6
|
|
|
6
7
|
export function buildEnvironment() {
|
|
7
8
|
if (S.furnitureGroup) {
|
|
@@ -27,6 +28,15 @@ export function buildEnvironment() {
|
|
|
27
28
|
}
|
|
28
29
|
S.furnitureGroup = new THREE.Group();
|
|
29
30
|
S.deskMeshes = [];
|
|
31
|
+
|
|
32
|
+
if (S.currentEnv === 'campus') {
|
|
33
|
+
buildCampusEnvironment();
|
|
34
|
+
// Store campus desk positions for agent assignment
|
|
35
|
+
S._campusDeskPositions = getCampusDeskPositions();
|
|
36
|
+
S.scene.add(S.furnitureGroup);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
30
40
|
var env = ENVS[S.currentEnv] || ENVS.modern;
|
|
31
41
|
|
|
32
42
|
buildFloor(env);
|
package/office/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { DESK_POSITIONS, DRESSING_ROOM_POS, DRESSING_ROOM_ENTRANCE, REST_AREA_PO
|
|
|
5
5
|
import { initScene } from './scene.js';
|
|
6
6
|
import { buildEnvironment, updateTVScreen } from './environment.js';
|
|
7
7
|
import { updateAgent } from './animation.js';
|
|
8
|
-
import { syncAgents, processMessages, walkTo, showBubble } from './agents.js';
|
|
8
|
+
import { syncAgents, processMessages, walkTo, navigateTo, showBubble } from './agents.js';
|
|
9
9
|
// Side-effect: registers window.officeGetAppearance
|
|
10
10
|
import './appearance.js';
|
|
11
11
|
|
|
@@ -141,8 +141,8 @@ function executeCommand(agentName, action) {
|
|
|
141
141
|
agent.location = 'walking';
|
|
142
142
|
agent.isSitting = false;
|
|
143
143
|
showBubble(agent, 'Going to change!');
|
|
144
|
-
|
|
145
|
-
|
|
144
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
145
|
+
navigateTo(agent, DRESSING_ROOM_POS.x, DRESSING_ROOM_POS.z, function() {
|
|
146
146
|
agent.location = 'dressing_room';
|
|
147
147
|
agent.isSitting = false;
|
|
148
148
|
showBubble(agent, 'Time for a new look!');
|
|
@@ -161,8 +161,8 @@ function executeCommand(agentName, action) {
|
|
|
161
161
|
agent.location = 'walking';
|
|
162
162
|
agent.isSitting = false;
|
|
163
163
|
showBubble(agent, 'Need a break...');
|
|
164
|
-
|
|
165
|
-
|
|
164
|
+
navigateTo(agent, REST_AREA_ENTRANCE.x, REST_AREA_ENTRANCE.z, function() {
|
|
165
|
+
navigateTo(agent, REST_AREA_POS.x, REST_AREA_POS.z, function() {
|
|
166
166
|
agent.location = 'rest';
|
|
167
167
|
agent.state = 'sleeping';
|
|
168
168
|
agent.isSitting = false;
|
|
@@ -176,7 +176,7 @@ function executeCommand(agentName, action) {
|
|
|
176
176
|
agent.state = 'active';
|
|
177
177
|
agent.isSitting = false;
|
|
178
178
|
showBubble(agent, 'Back to work!');
|
|
179
|
-
|
|
179
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
180
180
|
agent.location = 'desk';
|
|
181
181
|
agent.registered = true;
|
|
182
182
|
});
|
|
@@ -201,8 +201,8 @@ function waitForEditorClose(agent) {
|
|
|
201
201
|
if (agent.location === 'dressing_room') {
|
|
202
202
|
agent.location = 'walking';
|
|
203
203
|
showBubble(agent, 'Looking good!');
|
|
204
|
-
|
|
205
|
-
|
|
204
|
+
navigateTo(agent, DRESSING_ROOM_ENTRANCE.x, DRESSING_ROOM_ENTRANCE.z, function() {
|
|
205
|
+
navigateTo(agent, agent.deskPos.x, agent.deskPos.z + 0.7, function() {
|
|
206
206
|
agent.location = 'desk';
|
|
207
207
|
agent.registered = true;
|
|
208
208
|
});
|
|
@@ -226,6 +226,29 @@ function animate() {
|
|
|
226
226
|
updateAgent(S.agents3d[name], dt, time);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
+
// Hide roof when camera is above ceiling height
|
|
230
|
+
if (S._roofGroup) {
|
|
231
|
+
S._roofGroup.visible = S.camera.position.y < 6.5;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Manager office door animation — opens when agent is near the door
|
|
235
|
+
if (S._managerDoor && S._managerOfficePos) {
|
|
236
|
+
var doorX = S._managerOfficePos.x;
|
|
237
|
+
var doorZ = S._managerOfficePos.z - 3.5; // front of office
|
|
238
|
+
var shouldOpen = false;
|
|
239
|
+
for (var an in S.agents3d) {
|
|
240
|
+
var ag = S.agents3d[an];
|
|
241
|
+
if (ag.target || ag.location === 'walking') {
|
|
242
|
+
var adx = ag.pos.x - doorX;
|
|
243
|
+
var adz = ag.pos.z - doorZ;
|
|
244
|
+
if (Math.sqrt(adx * adx + adz * adz) < 3) { shouldOpen = true; break; }
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
S._managerDoorOpen = shouldOpen ? 1 : 0;
|
|
248
|
+
S._managerDoorLerp += (S._managerDoorOpen - S._managerDoorLerp) * Math.min(1, dt * 4);
|
|
249
|
+
S._managerDoor.position.x = S._managerDoorLerp * 1.3; // slide open to the right
|
|
250
|
+
}
|
|
251
|
+
|
|
229
252
|
// Update TV screen every ~0.5s for smooth ticker
|
|
230
253
|
if (!S._tvTimer) S._tvTimer = 0;
|
|
231
254
|
S._tvTimer += dt;
|
|
@@ -309,17 +332,24 @@ window.office3dSetEnvironment = function(env) {
|
|
|
309
332
|
if (env === S.currentEnv) return;
|
|
310
333
|
S.currentEnv = env;
|
|
311
334
|
if (S.scene) {
|
|
312
|
-
|
|
313
|
-
var i = 0;
|
|
335
|
+
// Remove all existing agents so they get recreated with proper desk assignments
|
|
314
336
|
for (var name in S.agents3d) {
|
|
315
337
|
var agent = S.agents3d[name];
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
338
|
+
S.scene.remove(agent.parts.group);
|
|
339
|
+
agent.parts.group.traverse(function(child) {
|
|
340
|
+
if (child.geometry) child.geometry.dispose();
|
|
341
|
+
if (child.material) {
|
|
342
|
+
if (child.material.map) child.material.map.dispose();
|
|
343
|
+
child.material.dispose();
|
|
344
|
+
}
|
|
345
|
+
});
|
|
322
346
|
}
|
|
347
|
+
S.agents3d = {};
|
|
348
|
+
S.lastProcessedMsg = 0;
|
|
349
|
+
buildEnvironment();
|
|
350
|
+
// syncAgents will recreate all agents with correct desk assignments
|
|
351
|
+
syncAgents();
|
|
352
|
+
processMessages();
|
|
323
353
|
}
|
|
324
354
|
};
|
|
325
355
|
|
|
@@ -335,3 +365,9 @@ document.addEventListener('visibilitychange', function() {
|
|
|
335
365
|
window.office3dStart();
|
|
336
366
|
}
|
|
337
367
|
});
|
|
368
|
+
|
|
369
|
+
// Auto-start if 3D Hub is already the active view when this module finishes loading
|
|
370
|
+
// (module loads async, so switchView('office') may have already fired before we defined office3dStart)
|
|
371
|
+
if (window.activeView === 'office') {
|
|
372
|
+
window.office3dStart();
|
|
373
|
+
}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import { S } from './state.js';
|
|
2
|
+
|
|
3
|
+
// ============================================================
|
|
4
|
+
// NAVIGATION SYSTEM — Waypoint graph pathfinding
|
|
5
|
+
// Agents walk along connected waypoints to avoid walls/objects
|
|
6
|
+
// ============================================================
|
|
7
|
+
|
|
8
|
+
// Manager office geometry reference:
|
|
9
|
+
// Office group at (12, 5), size 8x7, walls at:
|
|
10
|
+
// Front (door): z = 5 - 3.5 = 1.5 (door at center x=12)
|
|
11
|
+
// Back: z = 5 + 3.5 = 8.5
|
|
12
|
+
// Left: x = 12 - 4 = 8
|
|
13
|
+
// Right: x = 12 + 4 = 16
|
|
14
|
+
// Glass partition at z = -7 (between workspace and rec)
|
|
15
|
+
// Glass partition at x = -8 (between designer and main)
|
|
16
|
+
|
|
17
|
+
var CAMPUS_WAYPOINTS = [
|
|
18
|
+
// === LOBBY / ENTRANCE ===
|
|
19
|
+
{ id: 'spawn', x: 0, z: 14 },
|
|
20
|
+
{ id: 'lobby', x: 0, z: 10 },
|
|
21
|
+
{ id: 'lobby_left', x: -6, z: 10 },
|
|
22
|
+
{ id: 'lobby_right', x: 6, z: 10 },
|
|
23
|
+
|
|
24
|
+
// === MAIN CORRIDOR (runs along z=6, above workspace) ===
|
|
25
|
+
{ id: 'corr_L', x: -8, z: 7 },
|
|
26
|
+
{ id: 'corr_CL', x: -3, z: 7 },
|
|
27
|
+
{ id: 'corr_C', x: 0, z: 7 },
|
|
28
|
+
{ id: 'corr_CR', x: 3, z: 7 },
|
|
29
|
+
{ id: 'corr_R', x: 7, z: 7 },
|
|
30
|
+
|
|
31
|
+
// === WORKSPACE ZONE (center area, between glass partitions) ===
|
|
32
|
+
{ id: 'work_N', x: 0, z: 4 }, // north end
|
|
33
|
+
{ id: 'work_NW', x: -5, z: 4 },
|
|
34
|
+
{ id: 'work_NE', x: 5, z: 4 },
|
|
35
|
+
{ id: 'work_W', x: -5, z: 0 },
|
|
36
|
+
{ id: 'work_C', x: 0, z: 0 },
|
|
37
|
+
{ id: 'work_E', x: 5, z: 0 },
|
|
38
|
+
{ id: 'work_SW', x: -5, z: -3 },
|
|
39
|
+
{ id: 'work_S', x: 0, z: -5 },
|
|
40
|
+
{ id: 'work_SE', x: 5, z: -3 },
|
|
41
|
+
|
|
42
|
+
// === DESIGNER WING (left of glass partition x=-8) ===
|
|
43
|
+
{ id: 'design_gate', x: -8, z: 3 }, // gap in partition
|
|
44
|
+
{ id: 'design_N', x: -12, z: 3 },
|
|
45
|
+
{ id: 'design_C', x: -12.5, z: 0 },
|
|
46
|
+
{ id: 'design_S', x: -12, z: -3 },
|
|
47
|
+
|
|
48
|
+
// === MANAGER OFFICE (right side, enclosed glass room) ===
|
|
49
|
+
// Office walls: left x=8, right x=16, front z=1.5, back z=8.5
|
|
50
|
+
// Door at front wall center (x=12, z=1.5)
|
|
51
|
+
// Path must go AROUND the left-front corner, then to door from outside
|
|
52
|
+
{ id: 'mgr_hallway', x: 7, z: 3 }, // south of corridor, OUTSIDE left wall (x<8)
|
|
53
|
+
{ id: 'mgr_corner', x: 7, z: 0 }, // past the front-left corner (x<8, z<1.5)
|
|
54
|
+
{ id: 'mgr_outside', x: 12, z: 0 }, // in front of door, OUTSIDE front wall (z<1.5)
|
|
55
|
+
{ id: 'mgr_doorstep', x: 12, z: 1.5 }, // at the door threshold (triggers door open)
|
|
56
|
+
{ id: 'mgr_entry', x: 12, z: 3 }, // just inside the door
|
|
57
|
+
{ id: 'mgr_center', x: 12, z: 5 }, // middle of office
|
|
58
|
+
{ id: 'mgr_desk', x: 12, z: 7 }, // at the desk/chair
|
|
59
|
+
|
|
60
|
+
// === BACK ZONE CORRIDOR (runs along z=-7 to z=-8, south of glass partition) ===
|
|
61
|
+
{ id: 'back_gate', x: 0, z: -6.5 }, // gap in glass partition
|
|
62
|
+
{ id: 'back_L', x: -8, z: -8 },
|
|
63
|
+
{ id: 'back_C', x: 0, z: -8 },
|
|
64
|
+
{ id: 'back_R', x: 8, z: -8 },
|
|
65
|
+
|
|
66
|
+
// === BAR (back left) ===
|
|
67
|
+
{ id: 'bar_entry', x: -10, z: -10 },
|
|
68
|
+
{ id: 'bar_center', x: -14, z: -12 },
|
|
69
|
+
|
|
70
|
+
// === REC CENTER (back center) ===
|
|
71
|
+
{ id: 'rec_entry', x: 0, z: -10 },
|
|
72
|
+
{ id: 'rec_center', x: 0, z: -12 },
|
|
73
|
+
|
|
74
|
+
// === GYM (back right) ===
|
|
75
|
+
{ id: 'gym_entry', x: 10, z: -10 },
|
|
76
|
+
{ id: 'gym_center', x: 14, z: -12 },
|
|
77
|
+
|
|
78
|
+
// === MEZZANINE / STAIRS ===
|
|
79
|
+
{ id: 'stairs_bot', x: 20, z: -5 },
|
|
80
|
+
{ id: 'stairs_top', x: 20, z: -8 },
|
|
81
|
+
{ id: 'mezz_C', x: 0, z: -13 },
|
|
82
|
+
|
|
83
|
+
// === REST / DRESSING (right wing, for old office compat) ===
|
|
84
|
+
{ id: 'rest_entry', x: 7.5, z: -5.5 },
|
|
85
|
+
{ id: 'dress_entry', x: 7.5, z: -1.5 },
|
|
86
|
+
];
|
|
87
|
+
|
|
88
|
+
var CAMPUS_CONNECTIONS = [
|
|
89
|
+
// Lobby connections
|
|
90
|
+
['spawn', 'lobby'],
|
|
91
|
+
['lobby', 'lobby_left'],
|
|
92
|
+
['lobby', 'lobby_right'],
|
|
93
|
+
['lobby', 'corr_C'],
|
|
94
|
+
['lobby_left', 'corr_L'],
|
|
95
|
+
['lobby_right', 'corr_R'],
|
|
96
|
+
|
|
97
|
+
// Main corridor (horizontal)
|
|
98
|
+
['corr_L', 'corr_CL'],
|
|
99
|
+
['corr_CL', 'corr_C'],
|
|
100
|
+
['corr_C', 'corr_CR'],
|
|
101
|
+
['corr_CR', 'corr_R'],
|
|
102
|
+
|
|
103
|
+
// Corridor → workspace
|
|
104
|
+
['corr_C', 'work_N'],
|
|
105
|
+
['corr_CL', 'work_NW'],
|
|
106
|
+
['corr_CR', 'work_NE'],
|
|
107
|
+
|
|
108
|
+
// Workspace grid
|
|
109
|
+
['work_N', 'work_NW'],
|
|
110
|
+
['work_N', 'work_NE'],
|
|
111
|
+
['work_N', 'work_C'],
|
|
112
|
+
['work_NW', 'work_W'],
|
|
113
|
+
['work_NE', 'work_E'],
|
|
114
|
+
['work_W', 'work_C'],
|
|
115
|
+
['work_C', 'work_E'],
|
|
116
|
+
['work_W', 'work_SW'],
|
|
117
|
+
['work_C', 'work_S'],
|
|
118
|
+
['work_E', 'work_SE'],
|
|
119
|
+
['work_SW', 'work_S'],
|
|
120
|
+
['work_S', 'work_SE'],
|
|
121
|
+
|
|
122
|
+
// Designer wing (through gap in glass partition)
|
|
123
|
+
['work_NW', 'design_gate'],
|
|
124
|
+
['corr_L', 'design_gate'],
|
|
125
|
+
['design_gate', 'design_N'],
|
|
126
|
+
['design_N', 'design_C'],
|
|
127
|
+
['design_C', 'design_S'],
|
|
128
|
+
|
|
129
|
+
// Manager office — path goes AROUND the corner then through door
|
|
130
|
+
// corr_R(7,7) → mgr_hallway(7,3) → mgr_corner(7,0) → mgr_outside(12,0) → door → inside
|
|
131
|
+
['corr_R', 'mgr_hallway'], // walk south, outside left wall (x=7 < wall x=8)
|
|
132
|
+
['mgr_hallway', 'mgr_corner'], // walk further south past front-left corner (z=0 < wall z=1.5)
|
|
133
|
+
['mgr_corner', 'mgr_outside'], // walk east to front of door (z=0, safely below front wall z=1.5)
|
|
134
|
+
['mgr_outside', 'mgr_doorstep'], // step to door threshold (triggers open)
|
|
135
|
+
['mgr_doorstep', 'mgr_entry'], // walk through open door into office
|
|
136
|
+
['mgr_entry', 'mgr_center'], // walk deeper inside
|
|
137
|
+
['mgr_center', 'mgr_desk'], // walk to desk
|
|
138
|
+
|
|
139
|
+
// Workspace → back zone (through gap in glass partition at z=-7)
|
|
140
|
+
['work_S', 'back_gate'],
|
|
141
|
+
['back_gate', 'back_C'],
|
|
142
|
+
|
|
143
|
+
// Back corridor
|
|
144
|
+
['back_L', 'back_C'],
|
|
145
|
+
['back_C', 'back_R'],
|
|
146
|
+
['work_SW', 'back_L'],
|
|
147
|
+
['work_SE', 'back_R'],
|
|
148
|
+
|
|
149
|
+
// Back zones
|
|
150
|
+
['back_L', 'bar_entry'],
|
|
151
|
+
['bar_entry', 'bar_center'],
|
|
152
|
+
['back_C', 'rec_entry'],
|
|
153
|
+
['rec_entry', 'rec_center'],
|
|
154
|
+
['back_R', 'gym_entry'],
|
|
155
|
+
['gym_entry', 'gym_center'],
|
|
156
|
+
|
|
157
|
+
// Stairs / mezzanine
|
|
158
|
+
['back_R', 'stairs_bot'],
|
|
159
|
+
['stairs_bot', 'stairs_top'],
|
|
160
|
+
['stairs_top', 'mezz_C'],
|
|
161
|
+
|
|
162
|
+
// Rest/dressing (legacy)
|
|
163
|
+
['work_SE', 'rest_entry'],
|
|
164
|
+
['work_E', 'dress_entry'],
|
|
165
|
+
];
|
|
166
|
+
|
|
167
|
+
// === BUILD GRAPH ===
|
|
168
|
+
var adjacency = {};
|
|
169
|
+
var waypointMap = {};
|
|
170
|
+
|
|
171
|
+
function buildGraph() {
|
|
172
|
+
adjacency = {};
|
|
173
|
+
waypointMap = {};
|
|
174
|
+
CAMPUS_WAYPOINTS.forEach(function(wp) {
|
|
175
|
+
adjacency[wp.id] = [];
|
|
176
|
+
waypointMap[wp.id] = wp;
|
|
177
|
+
});
|
|
178
|
+
CAMPUS_CONNECTIONS.forEach(function(conn) {
|
|
179
|
+
if (adjacency[conn[0]] && adjacency[conn[1]]) {
|
|
180
|
+
adjacency[conn[0]].push(conn[1]);
|
|
181
|
+
adjacency[conn[1]].push(conn[0]);
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
buildGraph();
|
|
186
|
+
|
|
187
|
+
// Find nearest waypoint to a world position
|
|
188
|
+
function nearestWaypoint(x, z) {
|
|
189
|
+
var best = null, bestDist = Infinity;
|
|
190
|
+
CAMPUS_WAYPOINTS.forEach(function(wp) {
|
|
191
|
+
var dx = wp.x - x, dz = wp.z - z;
|
|
192
|
+
var d = dx * dx + dz * dz;
|
|
193
|
+
if (d < bestDist) { bestDist = d; best = wp.id; }
|
|
194
|
+
});
|
|
195
|
+
return best;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// BFS shortest path
|
|
199
|
+
function findPath(startId, endId) {
|
|
200
|
+
if (startId === endId) return [startId];
|
|
201
|
+
var visited = {};
|
|
202
|
+
var queue = [[startId]];
|
|
203
|
+
visited[startId] = true;
|
|
204
|
+
while (queue.length > 0) {
|
|
205
|
+
var path = queue.shift();
|
|
206
|
+
var current = path[path.length - 1];
|
|
207
|
+
var neighbors = adjacency[current] || [];
|
|
208
|
+
for (var i = 0; i < neighbors.length; i++) {
|
|
209
|
+
var next = neighbors[i];
|
|
210
|
+
if (visited[next]) continue;
|
|
211
|
+
var newPath = path.concat([next]);
|
|
212
|
+
if (next === endId) return newPath;
|
|
213
|
+
visited[next] = true;
|
|
214
|
+
queue.push(newPath);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// ==================== PUBLIC API ====================
|
|
221
|
+
|
|
222
|
+
export function getNavigationPath(fromX, fromZ, toX, toZ) {
|
|
223
|
+
if (S.currentEnv !== 'campus') {
|
|
224
|
+
return [{ x: toX, z: toZ }];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
var startWP = nearestWaypoint(fromX, fromZ);
|
|
228
|
+
var endWP = nearestWaypoint(toX, toZ);
|
|
229
|
+
|
|
230
|
+
if (!startWP || !endWP || startWP === endWP) {
|
|
231
|
+
return [{ x: toX, z: toZ }];
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
var wpPath = findPath(startWP, endWP);
|
|
235
|
+
if (!wpPath || wpPath.length === 0) {
|
|
236
|
+
return [{ x: toX, z: toZ }];
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
var result = [];
|
|
240
|
+
// Skip first waypoint if very close to current position
|
|
241
|
+
var firstWP = waypointMap[wpPath[0]];
|
|
242
|
+
var dx0 = firstWP.x - fromX, dz0 = firstWP.z - fromZ;
|
|
243
|
+
var startIdx = (dx0 * dx0 + dz0 * dz0 < 4) ? 1 : 0;
|
|
244
|
+
|
|
245
|
+
for (var i = startIdx; i < wpPath.length; i++) {
|
|
246
|
+
var wp = waypointMap[wpPath[i]];
|
|
247
|
+
var point = { x: wp.x, z: wp.z };
|
|
248
|
+
// Flag door waypoint
|
|
249
|
+
if (wpPath[i] === 'mgr_doorstep') {
|
|
250
|
+
point.triggerDoor = 'open';
|
|
251
|
+
}
|
|
252
|
+
result.push(point);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Add final walk to exact destination if far from last waypoint
|
|
256
|
+
var lastWP = waypointMap[wpPath[wpPath.length - 1]];
|
|
257
|
+
var dxEnd = lastWP.x - toX, dzEnd = lastWP.z - toZ;
|
|
258
|
+
if (dxEnd * dxEnd + dzEnd * dzEnd > 1) {
|
|
259
|
+
result.push({ x: toX, z: toZ });
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result;
|
|
263
|
+
}
|
package/office/scene.js
CHANGED
|
@@ -9,10 +9,10 @@ export function initScene() {
|
|
|
9
9
|
|
|
10
10
|
S.scene = new THREE.Scene();
|
|
11
11
|
S.scene.background = new THREE.Color(0x0d1117);
|
|
12
|
-
S.scene.fog = new THREE.Fog(0x0d1117,
|
|
12
|
+
S.scene.fog = new THREE.Fog(0x0d1117, 30, 80);
|
|
13
13
|
|
|
14
|
-
S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1,
|
|
15
|
-
S.camera.position.set(0,
|
|
14
|
+
S.camera = new THREE.PerspectiveCamera(50, S.container.clientWidth / S.container.clientHeight, 0.1, 300);
|
|
15
|
+
S.camera.position.set(0, 15, 22);
|
|
16
16
|
S.camera.lookAt(0, 0, 0);
|
|
17
17
|
|
|
18
18
|
S.renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
@@ -131,7 +131,7 @@ export function SpectatorCamera(camera, domElement) {
|
|
|
131
131
|
if (keys['KeyA'] || keys['ArrowLeft']) moveDir.x -= 1;
|
|
132
132
|
if (keys['KeyD'] || keys['ArrowRight']) moveDir.x += 1;
|
|
133
133
|
if (keys['KeyE'] || keys['Space']) moveDir.y += 1;
|
|
134
|
-
if (keys['KeyQ']
|
|
134
|
+
if (keys['KeyQ']) moveDir.y -= 1;
|
|
135
135
|
|
|
136
136
|
if (moveDir.lengthSq() > 0) {
|
|
137
137
|
moveDir.normalize();
|
package/office/state.js
CHANGED
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -871,6 +871,15 @@ async function toolSendMessage(content, to = null, reply_to = null) {
|
|
|
871
871
|
if (currentBranch !== 'main') result.branch = currentBranch;
|
|
872
872
|
if (!recipientAlive) {
|
|
873
873
|
result.warning = `Agent "${to}" appears offline (PID not running). Message queued but may not be received until they reconnect.`;
|
|
874
|
+
} else if (agents[to] && !agents[to].listening_since) {
|
|
875
|
+
result.note = `Agent "${to}" is currently working (not in listen mode). Message queued — they'll see it when they finish their current task and call listen_group().`;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// Nudge: check if THIS agent has unread messages waiting
|
|
879
|
+
const myPending = getUnconsumedMessages(registeredName);
|
|
880
|
+
if (myPending.length > 0) {
|
|
881
|
+
result.you_have_messages = myPending.length;
|
|
882
|
+
result.urgent = `You have ${myPending.length} unread message(s) waiting. Call listen_group() after this to read them.`;
|
|
874
883
|
}
|
|
875
884
|
return result;
|
|
876
885
|
}
|
|
@@ -925,6 +934,19 @@ function toolBroadcast(content) {
|
|
|
925
934
|
|
|
926
935
|
const result = { success: true, sent_to: ids, recipient_count: ids.length };
|
|
927
936
|
if (skipped.length > 0) result.skipped = skipped;
|
|
937
|
+
// Show which recipients are busy vs listening
|
|
938
|
+
const agentsNow = getAgents();
|
|
939
|
+
const busy = ids.filter(function(i) { return agentsNow[i.to] && !agentsNow[i.to].listening_since; }).map(function(i) { return i.to; });
|
|
940
|
+
if (busy.length > 0) {
|
|
941
|
+
result.busy_agents = busy;
|
|
942
|
+
result.note = busy.join(', ') + (busy.length === 1 ? ' is' : ' are') + ' currently working (not listening). Messages queued.';
|
|
943
|
+
}
|
|
944
|
+
// Nudge for own unread messages
|
|
945
|
+
const myPending = getUnconsumedMessages(registeredName);
|
|
946
|
+
if (myPending.length > 0) {
|
|
947
|
+
result.you_have_messages = myPending.length;
|
|
948
|
+
result.urgent = `You have ${myPending.length} unread message(s). Call listen_group() soon.`;
|
|
949
|
+
}
|
|
928
950
|
return result;
|
|
929
951
|
}
|
|
930
952
|
|
|
@@ -1370,17 +1392,28 @@ async function toolListenGroup(timeout_seconds = 300) {
|
|
|
1370
1392
|
const recentSpeakers = new Set(history.slice(-10).map(m => m.from));
|
|
1371
1393
|
const silent = agentNames.filter(n => !recentSpeakers.has(n) && n !== registeredName);
|
|
1372
1394
|
|
|
1395
|
+
const now = Date.now();
|
|
1373
1396
|
const result = {
|
|
1374
|
-
messages: batch.map(m =>
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1397
|
+
messages: batch.map(m => {
|
|
1398
|
+
const ageMs = now - new Date(m.timestamp).getTime();
|
|
1399
|
+
const ageSec = Math.round(ageMs / 1000);
|
|
1400
|
+
return {
|
|
1401
|
+
id: m.id, from: m.from, to: m.to, content: m.content,
|
|
1402
|
+
timestamp: m.timestamp,
|
|
1403
|
+
age_seconds: ageSec,
|
|
1404
|
+
...(ageSec > 30 && { delayed: true }),
|
|
1405
|
+
...(m.reply_to && { reply_to: m.reply_to }),
|
|
1406
|
+
...(m.thread_id && { thread_id: m.thread_id }),
|
|
1407
|
+
};
|
|
1408
|
+
}),
|
|
1380
1409
|
message_count: batch.length,
|
|
1381
1410
|
context: recentHistory,
|
|
1382
1411
|
agents_online: agentNames.length,
|
|
1383
1412
|
agents_silent: silent,
|
|
1413
|
+
agents_status: agentNames.reduce(function(acc, n) {
|
|
1414
|
+
acc[n] = agents[n].listening_since ? 'listening' : 'working';
|
|
1415
|
+
return acc;
|
|
1416
|
+
}, {}),
|
|
1384
1417
|
hint: silent.length > 0
|
|
1385
1418
|
? `${silent.join(', ')} haven't spoken recently. Consider addressing them.`
|
|
1386
1419
|
: 'All agents are active in the conversation.',
|
|
@@ -1416,6 +1449,7 @@ async function toolListenGroup(timeout_seconds = 300) {
|
|
|
1416
1449
|
}
|
|
1417
1450
|
}
|
|
1418
1451
|
|
|
1452
|
+
result.next_action = 'After processing these messages and sending your response, call listen_group() again immediately. Never stop listening.';
|
|
1419
1453
|
return result;
|
|
1420
1454
|
}
|
|
1421
1455
|
|
|
@@ -1423,7 +1457,13 @@ async function toolListenGroup(timeout_seconds = 300) {
|
|
|
1423
1457
|
}
|
|
1424
1458
|
|
|
1425
1459
|
setListening(false);
|
|
1426
|
-
return {
|
|
1460
|
+
return {
|
|
1461
|
+
timeout: true,
|
|
1462
|
+
retry: true,
|
|
1463
|
+
message: 'No messages yet. Call listen_group() again immediately to keep listening. Do NOT stop — you must stay in the conversation.',
|
|
1464
|
+
messages: [],
|
|
1465
|
+
message_count: 0,
|
|
1466
|
+
};
|
|
1427
1467
|
}
|
|
1428
1468
|
|
|
1429
1469
|
function toolGetHistory(limit = 50, thread_id = null) {
|
|
@@ -2094,7 +2134,7 @@ function toolListBranches() {
|
|
|
2094
2134
|
// --- MCP Server setup ---
|
|
2095
2135
|
|
|
2096
2136
|
const server = new Server(
|
|
2097
|
-
{ name: 'agent-bridge', version: '3.6.
|
|
2137
|
+
{ name: 'agent-bridge', version: '3.6.2' },
|
|
2098
2138
|
{ capabilities: { tools: {} } }
|
|
2099
2139
|
);
|
|
2100
2140
|
|
|
@@ -2505,7 +2545,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
2505
2545
|
},
|
|
2506
2546
|
{
|
|
2507
2547
|
name: 'listen_group',
|
|
2508
|
-
description: 'Listen for messages in group or managed conversation mode. Returns ALL unconsumed messages as a batch
|
|
2548
|
+
description: 'Listen for messages in group or managed conversation mode. Returns ALL unconsumed messages as a batch, plus conversation context and hints. IMPORTANT: After processing messages and responding, you MUST call listen_group() again immediately. If it times out with retry:true, call it again. Never stop listening — this is how you stay in the conversation.',
|
|
2509
2549
|
inputSchema: {
|
|
2510
2550
|
type: 'object',
|
|
2511
2551
|
properties: {
|
|
@@ -2666,6 +2706,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2666
2706
|
};
|
|
2667
2707
|
}
|
|
2668
2708
|
|
|
2709
|
+
// Global hook: on non-listen tools, check for pending messages and nudge the agent
|
|
2710
|
+
// This catches agents who are mid-work and have messages piling up
|
|
2711
|
+
const listenTools = ['listen', 'listen_group', 'listen_codex', 'wait_for_reply', 'check_messages'];
|
|
2712
|
+
if (registeredName && !listenTools.includes(name) && (isGroupMode() || isManagedMode())) {
|
|
2713
|
+
try {
|
|
2714
|
+
const pending = getUnconsumedMessages(registeredName);
|
|
2715
|
+
if (pending.length > 0 && !result.you_have_messages) {
|
|
2716
|
+
result._pending_messages = pending.length;
|
|
2717
|
+
result._nudge = `You have ${pending.length} unread message(s) from the team. Finish your current task quickly, then call listen_group() to read them.`;
|
|
2718
|
+
}
|
|
2719
|
+
} catch {}
|
|
2720
|
+
}
|
|
2721
|
+
|
|
2669
2722
|
return {
|
|
2670
2723
|
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
2671
2724
|
};
|
|
@@ -2698,7 +2751,7 @@ async function main() {
|
|
|
2698
2751
|
ensureDataDir();
|
|
2699
2752
|
const transport = new StdioServerTransport();
|
|
2700
2753
|
await server.connect(transport);
|
|
2701
|
-
console.error('Agent Bridge MCP server v3.6.
|
|
2754
|
+
console.error('Agent Bridge MCP server v3.6.2 running (32 tools)');
|
|
2702
2755
|
}
|
|
2703
2756
|
|
|
2704
2757
|
main().catch(console.error);
|