opencroc 1.8.0 → 1.8.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.
Files changed (71) hide show
  1. package/dist/cli/index.js +1107 -49
  2. package/dist/cli/index.js.map +1 -1
  3. package/dist/index.d.ts +128 -1
  4. package/dist/index.js +548 -0
  5. package/dist/index.js.map +1 -1
  6. package/dist/web/dist/assets/main-Ccg3eDNK.js +1 -0
  7. package/dist/web/dist/assets/office-runtime-B3iNctxE.css +1 -0
  8. package/dist/web/dist/assets/office-runtime-BsCh82Pj.js +183 -0
  9. package/dist/web/dist/assets/pixel-page-3BYGm7dH.js +470 -0
  10. package/dist/web/dist/assets/react-vendor-C8RhVn0h.js +49 -0
  11. package/dist/web/dist/assets/studio-page-BInoyoV2.css +1 -0
  12. package/dist/web/dist/assets/studio-page-o3SCvE_v.js +351 -0
  13. package/dist/web/dist/assets/three-addons-BdrPp04O.js +470 -0
  14. package/dist/web/dist/assets/three-core-CsxM1PCY.js +4057 -0
  15. package/dist/web/dist/index.html +15 -0
  16. package/dist/web/index.html +11 -572
  17. package/dist/web/public/botreview/char_0.png +0 -0
  18. package/dist/web/public/botreview/char_1.png +0 -0
  19. package/dist/web/public/botreview/char_2.png +0 -0
  20. package/dist/web/public/botreview/coffee-machine.gif +0 -0
  21. package/dist/web/public/botreview/server.gif +0 -0
  22. package/dist/web/public/botreview/walls.png +0 -0
  23. package/dist/web/public/star/desk-v3.webp +0 -0
  24. package/dist/web/public/star/office_bg_small.webp +0 -0
  25. package/dist/web/public/star/star-idle-v5.png +0 -0
  26. package/dist/web/public/star/star-working-spritesheet-grid.webp +0 -0
  27. package/dist/web/src/app/AppLayout.tsx +34 -0
  28. package/dist/web/src/app/AppRouter.tsx +46 -0
  29. package/dist/web/src/app/bootstrap.tsx +22 -0
  30. package/dist/web/src/app/routes.tsx +52 -0
  31. package/dist/web/src/features/office/runtime/index.ts +1 -0
  32. package/dist/web/src/features/office/runtime/mount.ts +809 -0
  33. package/dist/web/src/features/pixel/runtime/index.ts +1 -0
  34. package/dist/web/src/features/pixel/runtime/mount.ts +728 -0
  35. package/dist/web/src/features/studio/runtime/index.ts +1 -0
  36. package/dist/web/src/features/studio/runtime/mount.ts +664 -0
  37. package/dist/web/src/features/three/engine/index.ts +1 -0
  38. package/dist/web/src/main.tsx +7 -0
  39. package/dist/web/src/pages/office/index.ts +1 -0
  40. package/dist/web/src/pages/office/page.tsx +283 -0
  41. package/dist/web/src/pages/pixel/index.ts +1 -0
  42. package/dist/web/src/pages/pixel/page.tsx +564 -0
  43. package/dist/web/src/pages/studio/index.ts +1 -0
  44. package/dist/web/src/pages/studio/page.tsx +446 -0
  45. package/dist/web/{js/agents.js → src/runtime/agents.ts} +304 -31
  46. package/dist/web/{js/camera.js → src/runtime/camera.ts} +12 -5
  47. package/dist/web/{js/dataviz.js → src/runtime/dataviz.ts} +38 -14
  48. package/dist/web/{js/effects.js → src/runtime/effects.ts} +139 -2
  49. package/dist/web/{js/engine.js → src/runtime/engine.ts} +45 -6
  50. package/dist/web/{js/office.js → src/runtime/office.ts} +136 -20
  51. package/dist/web/{js/ui.js → src/runtime/ui.ts} +11 -7
  52. package/dist/web/src/shared/assets.ts +4 -0
  53. package/dist/web/src/shared/navigation.ts +47 -0
  54. package/dist/web/src/styles/app-layout.css +19 -0
  55. package/dist/web/src/styles/office.css +268 -0
  56. package/dist/web/tsconfig.json +28 -0
  57. package/dist/web/vite.config.ts +93 -0
  58. package/package.json +11 -2
  59. package/dist/web/index-studio.html +0 -804
  60. package/dist/web/index-v2-pixel.html +0 -1571
  61. /package/dist/web/{assets → dist}/botreview/char_0.png +0 -0
  62. /package/dist/web/{assets → dist}/botreview/char_1.png +0 -0
  63. /package/dist/web/{assets → dist}/botreview/char_2.png +0 -0
  64. /package/dist/web/{assets → dist}/botreview/coffee-machine.gif +0 -0
  65. /package/dist/web/{assets → dist}/botreview/server.gif +0 -0
  66. /package/dist/web/{assets → dist}/botreview/walls.png +0 -0
  67. /package/dist/web/{assets → dist}/star/desk-v3.webp +0 -0
  68. /package/dist/web/{assets → dist}/star/office_bg_small.webp +0 -0
  69. /package/dist/web/{assets → dist}/star/star-idle-v5.png +0 -0
  70. /package/dist/web/{assets → dist}/star/star-working-spritesheet-grid.webp +0 -0
  71. /package/dist/web/{js/state.js → src/runtime/state.ts} +0 -0
@@ -15,11 +15,21 @@ import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
15
15
  /* ─── Module-level singletons ──────────────────────────────────────────────── */
16
16
  let renderer = null;
17
17
  let scene = null;
18
- let camera = null;
19
- let composer = null;
20
- let clock = null;
21
- let bloomPass = null;
22
- let fxaaPass = null;
18
+ let camera = null;
19
+ let composer = null;
20
+ let clock = null;
21
+ let bloomPass = null;
22
+ let fxaaPass = null;
23
+
24
+ function disposeMaterial(material) {
25
+ if (!material) return;
26
+ for (const value of Object.values(material)) {
27
+ if (value && typeof value === 'object' && value.isTexture) {
28
+ value.dispose();
29
+ }
30
+ }
31
+ material.dispose?.();
32
+ }
23
33
 
24
34
  /* ═══════════════════════════════════════════════════════════════════════════════
25
35
  1. createEngine — Initialize the full Three.js rendering pipeline
@@ -482,7 +492,36 @@ export function updateEngineTheme(theme) {
482
492
  /* ═══════════════════════════════════════════════════════════════════════════════
483
493
  10. Getters
484
494
  ═══════════════════════════════════════════════════════════════════════════════ */
485
- export function getRenderer() { return renderer; }
495
+ export function disposeEngine() {
496
+ if (scene) {
497
+ scene.traverse((child) => {
498
+ child.geometry?.dispose?.();
499
+ if (Array.isArray(child.material)) {
500
+ child.material.forEach(disposeMaterial);
501
+ } else {
502
+ disposeMaterial(child.material);
503
+ }
504
+ });
505
+
506
+ while (scene.children.length > 0) {
507
+ scene.remove(scene.children[0]);
508
+ }
509
+ }
510
+
511
+ composer?.passes?.forEach?.((pass) => pass.dispose?.());
512
+ composer?.dispose?.();
513
+ renderer?.dispose?.();
514
+
515
+ renderer = null;
516
+ scene = null;
517
+ camera = null;
518
+ composer = null;
519
+ clock = null;
520
+ bloomPass = null;
521
+ fxaaPass = null;
522
+ }
523
+
524
+ export function getRenderer() { return renderer; }
486
525
  export function getScene() { return scene; }
487
526
  export function getCamera() { return camera; }
488
527
  export function getComposer() { return composer; }
@@ -5,13 +5,33 @@
5
5
  ═══════════════════════════════════════════════════════════════════════════════ */
6
6
 
7
7
  import * as THREE from 'three';
8
- import { getScene } from './engine.js';
8
+ import { getScene } from './engine';
9
9
 
10
10
  let officeGroup = null;
11
11
  let currentTheme = 'dark';
12
12
 
13
13
  /* ─── Material Cache ───────────────────────────────────────────────────────── */
14
- const MAT = {};
14
+ const MAT = {};
15
+
16
+ function disposeMaterialCache() {
17
+ Object.keys(MAT).forEach((key) => {
18
+ MAT[key]?.dispose?.();
19
+ delete MAT[key];
20
+ });
21
+ }
22
+
23
+ function disposeOfficeGroup(scene) {
24
+ if (!officeGroup) return;
25
+ scene?.remove(officeGroup);
26
+ officeGroup.traverse((child) => {
27
+ child.geometry?.dispose?.();
28
+ });
29
+ officeGroup = null;
30
+ DESK_POSITIONS.length = 0;
31
+ POND_POSITIONS.length = 0;
32
+ DESK_INDICATORS.clear();
33
+ disposeMaterialCache();
34
+ }
15
35
 
16
36
  function initMaterials(theme) {
17
37
  const dk = theme === 'dark';
@@ -41,9 +61,10 @@ function initMaterials(theme) {
41
61
  /* ═══════════════════════════════════════════════════════════════════════════════
42
62
  createOffice — Build the full 3D office environment
43
63
  ═══════════════════════════════════════════════════════════════════════════════ */
44
- export async function createOffice(theme) {
45
- currentTheme = theme;
46
- initMaterials(theme);
64
+ export async function createOffice(theme) {
65
+ currentTheme = theme;
66
+ disposeOfficeGroup(getScene());
67
+ initMaterials(theme);
47
68
 
48
69
  const scene = getScene();
49
70
  officeGroup = new THREE.Group();
@@ -52,6 +73,7 @@ export async function createOffice(theme) {
52
73
  buildFloor();
53
74
  buildWalls();
54
75
  buildGlassPartitions();
76
+ buildPondZone();
55
77
  buildDesks(6);
56
78
  buildServerRack();
57
79
  buildCoffeeMachine();
@@ -164,8 +186,70 @@ function buildGlassPartitions() {
164
186
  Agent Desks — Each agent gets a desk with monitor and chair
165
187
  ═══════════════════════════════════════════════════════════════════════════════ */
166
188
  export const DESK_POSITIONS = [];
189
+ export const POND_POSITIONS = [];
190
+ const DESK_INDICATORS = new Map();
191
+
192
+ function buildPondZone() {
193
+ const pond = new THREE.Group();
194
+ pond.name = 'agent-pond';
195
+
196
+ const centerX = -9;
197
+ const centerZ = 6.2;
198
+
199
+ const rim = new THREE.Mesh(
200
+ new THREE.CylinderGeometry(3.4, 3.5, 0.16, 24),
201
+ MAT.frame,
202
+ );
203
+ rim.position.set(centerX, 0.25, centerZ);
204
+ rim.receiveShadow = true;
205
+ pond.add(rim);
206
+
207
+ const water = new THREE.Mesh(
208
+ new THREE.CylinderGeometry(3.1, 3.2, 0.1, 24),
209
+ new THREE.MeshStandardMaterial({
210
+ color: currentTheme === 'dark' ? 0x0f3a5d : 0x7dd3fc,
211
+ roughness: 0.18,
212
+ metalness: 0.2,
213
+ transparent: true,
214
+ opacity: currentTheme === 'dark' ? 0.72 : 0.52,
215
+ emissive: currentTheme === 'dark' ? 0x0ea5e9 : 0x0369a1,
216
+ emissiveIntensity: currentTheme === 'dark' ? 0.16 : 0.06,
217
+ }),
218
+ );
219
+ water.position.set(centerX, 0.3, centerZ);
220
+ pond.add(water);
221
+
222
+ // Pixel-style stepping stones around the pond edge.
223
+ for (let i = 0; i < 10; i++) {
224
+ const a = (i / 10) * Math.PI * 2;
225
+ const r = 3.7 + (i % 2) * 0.25;
226
+ const stone = new THREE.Mesh(
227
+ new THREE.BoxGeometry(0.32, 0.06, 0.32),
228
+ MAT.deskTop,
229
+ );
230
+ stone.position.set(centerX + Math.cos(a) * r, 0.23, centerZ + Math.sin(a) * r);
231
+ stone.rotation.y = a * 0.7;
232
+ stone.receiveShadow = true;
233
+ pond.add(stone);
234
+ }
235
+
236
+ POND_POSITIONS.length = 0;
237
+ const count = 24;
238
+ for (let i = 0; i < count; i++) {
239
+ const a = (i / count) * Math.PI * 2;
240
+ const r = 1.55 + (i % 3) * 0.34;
241
+ POND_POSITIONS.push({
242
+ x: centerX + Math.cos(a) * r,
243
+ z: centerZ + Math.sin(a) * r,
244
+ });
245
+ }
246
+
247
+ officeGroup.add(pond);
248
+ }
167
249
 
168
250
  function buildDesks(count) {
251
+ DESK_POSITIONS.length = 0;
252
+ DESK_INDICATORS.clear();
169
253
  const rows = 2;
170
254
  const cols = Math.ceil(count / rows);
171
255
  const xStart = -2;
@@ -304,10 +388,42 @@ function buildSingleDesk(x, z, idx) {
304
388
  desk.add(mug);
305
389
  }
306
390
 
391
+ // Occupancy beacon near each desk (idle=dim, occupied=bright).
392
+ const indicator = new THREE.Mesh(
393
+ new THREE.CylinderGeometry(0.06, 0.06, 0.04, 12),
394
+ new THREE.MeshStandardMaterial({
395
+ color: currentTheme === 'dark' ? 0x475569 : 0x94a3b8,
396
+ roughness: 0.25,
397
+ metalness: 0.4,
398
+ emissive: currentTheme === 'dark' ? 0x0f172a : 0x64748b,
399
+ emissiveIntensity: 0.12,
400
+ }),
401
+ );
402
+ indicator.position.set(1.0, 1.08, -0.32);
403
+ desk.add(indicator);
404
+ DESK_INDICATORS.set(idx, indicator);
405
+
307
406
  desk.position.set(x, 0.2, z);
308
407
  officeGroup.add(desk);
309
408
  }
310
409
 
410
+ export function setDeskOccupied(index, occupied) {
411
+ const indicator = DESK_INDICATORS.get(index);
412
+ if (!indicator) return;
413
+ const mat = indicator.material;
414
+ if (!mat) return;
415
+
416
+ if (occupied) {
417
+ mat.color.setHex(currentTheme === 'dark' ? 0x34d399 : 0x059669);
418
+ mat.emissive.setHex(currentTheme === 'dark' ? 0x34d399 : 0x047857);
419
+ mat.emissiveIntensity = currentTheme === 'dark' ? 0.62 : 0.35;
420
+ } else {
421
+ mat.color.setHex(currentTheme === 'dark' ? 0x475569 : 0x94a3b8);
422
+ mat.emissive.setHex(currentTheme === 'dark' ? 0x0f172a : 0x64748b);
423
+ mat.emissiveIntensity = 0.12;
424
+ }
425
+ }
426
+
311
427
  /* ═══════════════════════════════════════════════════════════════════════════════
312
428
  Server Rack
313
429
  ═══════════════════════════════════════════════════════════════════════════════ */
@@ -775,29 +891,29 @@ function buildDecorativeElements() {
775
891
  /* ═══════════════════════════════════════════════════════════════════════════════
776
892
  Floor Y position getter
777
893
  ═══════════════════════════════════════════════════════════════════════════════ */
778
- export function getFloorY() {
779
- return 0.2;
780
- }
894
+ export function getFloorY() {
895
+ return 0.2;
896
+ }
897
+
898
+ export function disposeOffice() {
899
+ disposeOfficeGroup(getScene());
900
+ }
781
901
 
782
902
  /* ═══════════════════════════════════════════════════════════════════════════════
783
903
  Theme Update
784
904
  ═══════════════════════════════════════════════════════════════════════════════ */
785
- export function updateOfficeLighting(theme) {
786
- if (!officeGroup) return;
787
- currentTheme = theme;
788
-
789
- // Reload materials
790
- initMaterials(theme);
791
-
792
- // We rebuild the office to apply new materials
793
- const scene = getScene();
794
- scene.remove(officeGroup);
795
- officeGroup = new THREE.Group();
796
- officeGroup.name = 'office';
905
+ export function updateOfficeLighting(theme) {
906
+ currentTheme = theme;
907
+ const scene = getScene();
908
+ disposeOfficeGroup(scene);
909
+ initMaterials(theme);
910
+ officeGroup = new THREE.Group();
911
+ officeGroup.name = 'office';
797
912
 
798
913
  buildFloor();
799
914
  buildWalls();
800
915
  buildGlassPartitions();
916
+ buildPondZone();
801
917
  buildDesks(6);
802
918
  buildServerRack();
803
919
  buildCoffeeMachine();
@@ -4,15 +4,15 @@
4
4
  ═══════════════════════════════════════════════════════════════════════════════ */
5
5
 
6
6
  const STATUS_LABEL = {
7
- idle: '空闲', scanning: '扫描中', navigating: '导航中',
7
+ idle: '空闲', working: '工作中', scanning: '扫描中', navigating: '导航中',
8
8
  interacting: '交互中', asserting: '断言中', reporting: '报告中',
9
- thinking: '思考中', complete: '完成', error: '错误',
9
+ thinking: '思考中', complete: '完成', done: '完成', error: '错误',
10
10
  };
11
11
 
12
12
  const STATUS_DOT_CLASS = {
13
- idle: 'dot-idle', scanning: 'dot-active', navigating: 'dot-active',
13
+ idle: 'dot-idle', working: 'dot-active', scanning: 'dot-active', navigating: 'dot-active',
14
14
  interacting: 'dot-active', asserting: 'dot-active', reporting: 'dot-active',
15
- thinking: 'dot-active', complete: 'dot-ok', error: 'dot-err',
15
+ thinking: 'dot-active', complete: 'dot-ok', done: 'dot-ok', error: 'dot-err',
16
16
  };
17
17
 
18
18
  /* ═══════════════════════════════════════════════════════════════════════════════
@@ -27,6 +27,7 @@ export class UIManager {
27
27
  this._logCount = 0;
28
28
  this._esc = options?.esc || (s => String(s));
29
29
  this._ROLE_ICONS = options?.ROLE_ICONS || {};
30
+ this._resolveIcon = options?.resolveRoleIcon || (name => this._ROLE_ICONS[name] || '🐊');
30
31
  }
31
32
 
32
33
  /* ── init: store callbacks (event binding done in index.html) ────────── */
@@ -135,11 +136,13 @@ export class UIManager {
135
136
  entries.forEach(agent => {
136
137
  const name = agent.name || agent.role || '';
137
138
  const status = agent.status || 'idle';
139
+ const role = agent.role || name;
138
140
  const item = document.createElement('div');
139
141
  item.className = 'agent-sidebar-item';
142
+ const category = agent.category ? ` <span style="font-size:9px;color:var(--text-subtle);opacity:0.7">${this._esc(agent.category)}</span>` : '';
140
143
  item.innerHTML = `
141
- <span class="agent-icon">${this._ROLE_ICONS[name] || '🤖'}</span>
142
- <span class="agent-name">${this._esc(name)}</span>
144
+ <span class="agent-icon">${this._resolveIcon(role)}</span>
145
+ <span class="agent-name">${this._esc(name)}${category}</span>
143
146
  <span class="agent-status-dot ${STATUS_DOT_CLASS[status] || 'dot-idle'}"></span>
144
147
  <span class="agent-status-text">${STATUS_LABEL[status] || status}</span>
145
148
  `;
@@ -179,8 +182,9 @@ export class UIManager {
179
182
  _renderDeskCard(name, info) {
180
183
  const status = info.status || 'idle';
181
184
  const progress = info.progress || 0;
185
+ const role = info.role || name;
182
186
  return `
183
- <div class="dc-icon">${this._ROLE_ICONS[name] || '🤖'}</div>
187
+ <div class="dc-icon">${this._resolveIcon(role)}</div>
184
188
  <div class="dc-name">${this._esc(name)}</div>
185
189
  <div class="dc-status ${STATUS_DOT_CLASS[status] || ''}">${STATUS_LABEL[status] || status}</div>
186
190
  <div class="dc-bar"><div class="dc-fill" style="width:${progress}%"></div></div>
@@ -0,0 +1,4 @@
1
+ export function publicAsset(path: string): string {
2
+ const normalizedPath = path.replace(/^\/+/, '');
3
+ return `${import.meta.env.BASE_URL}${normalizedPath}`;
4
+ }
@@ -0,0 +1,47 @@
1
+ const ROUTE_CHANGE_EVENT = 'opencroc:route-change';
2
+
3
+ const LEGACY_ROUTE_ALIASES: Record<string, string> = {
4
+ '/index.html': '/',
5
+ '/index-studio.html': '/studio',
6
+ '/index-v2-pixel.html': '/pixel',
7
+ };
8
+
9
+ export function normalizeAppPath(pathname: string): string {
10
+ if (!pathname) {
11
+ return '/';
12
+ }
13
+
14
+ const [pathOnly] = pathname.split('?');
15
+ const withLeadingSlash = pathOnly.startsWith('/') ? pathOnly : `/${pathOnly}`;
16
+ const trimmed = withLeadingSlash.length > 1 && withLeadingSlash.endsWith('/')
17
+ ? withLeadingSlash.slice(0, -1)
18
+ : withLeadingSlash;
19
+
20
+ return LEGACY_ROUTE_ALIASES[trimmed] || trimmed;
21
+ }
22
+
23
+ export function getCurrentAppPath(): string {
24
+ return normalizeAppPath(window.location.pathname);
25
+ }
26
+
27
+ export function navigate(to: string, options?: { replace?: boolean }): void {
28
+ const nextPath = normalizeAppPath(to);
29
+ const currentPath = getCurrentAppPath();
30
+
31
+ if (nextPath !== currentPath) {
32
+ const method = options?.replace ? 'replaceState' : 'pushState';
33
+ window.history[method]({}, '', nextPath);
34
+ }
35
+
36
+ window.dispatchEvent(new Event(ROUTE_CHANGE_EVENT));
37
+ }
38
+
39
+ export function subscribeNavigation(callback: () => void): () => void {
40
+ window.addEventListener('popstate', callback);
41
+ window.addEventListener(ROUTE_CHANGE_EVENT, callback);
42
+
43
+ return () => {
44
+ window.removeEventListener('popstate', callback);
45
+ window.removeEventListener(ROUTE_CHANGE_EVENT, callback);
46
+ };
47
+ }
@@ -0,0 +1,19 @@
1
+ html,
2
+ body,
3
+ #root,
4
+ .app-layout {
5
+ width: 100%;
6
+ height: 100%;
7
+ }
8
+
9
+ body[data-route-variant='office'] {
10
+ background: #000;
11
+ }
12
+
13
+ body[data-route-variant='graph'] {
14
+ background: #08101d;
15
+ }
16
+
17
+ body[data-route-variant='pixel'] {
18
+ background: #0a1220;
19
+ }