openzoo 0.50.37 → 0.50.39

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.
@@ -0,0 +1,766 @@
1
+ /* Injected into the Grok Bot renderer so it can boot in a normal browser.
2
+ window.desktop matches the Electron preload surface; coordinatorPort is a
3
+ MessagePort lookalike over WebSocket to the local hijack. */
4
+ (function ozWebShim() {
5
+ 'use strict';
6
+ function ozReport(payload) {
7
+ try {
8
+ fetch('/oz-crash', {
9
+ method: 'POST',
10
+ headers: { 'content-type': 'application/json' },
11
+ body: JSON.stringify({ t: Date.now(), ...payload }),
12
+ keepalive: true,
13
+ }).catch(() => {});
14
+ } catch { /* */ }
15
+ try { console.error('[oz-web]', payload); } catch { /* */ }
16
+ }
17
+ window.addEventListener('error', (e) => {
18
+ ozReport({
19
+ type: 'error',
20
+ message: e.message,
21
+ filename: e.filename,
22
+ lineno: e.lineno,
23
+ colno: e.colno,
24
+ stack: e.error && e.error.stack,
25
+ });
26
+ });
27
+ window.addEventListener('unhandledrejection', (e) => {
28
+ const r = e.reason;
29
+ ozReport({
30
+ type: 'unhandledrejection',
31
+ message: r && r.message ? r.message : String(r),
32
+ stack: r && r.stack,
33
+ });
34
+ });
35
+ const _err = console.error.bind(console);
36
+ let ozReporting = false;
37
+ console.error = function ozConsoleError(...args) {
38
+ if (!ozReporting) {
39
+ ozReporting = true;
40
+ try {
41
+ const first = args[0];
42
+ if (!(typeof first === 'string' && first.startsWith('[oz-web]'))) {
43
+ ozReport({
44
+ type: 'console.error',
45
+ message: args.map((a) => {
46
+ if (a instanceof Error) return (a.stack || a.message);
47
+ try { return typeof a === 'string' ? a : JSON.stringify(a); } catch { return String(a); }
48
+ }).join(' '),
49
+ });
50
+ }
51
+ } catch { /* */ }
52
+ ozReporting = false;
53
+ }
54
+ return _err(...args);
55
+ };
56
+ const ozStaged = new Map();
57
+ function ozAsU8(bytes) {
58
+ if (!bytes) return new Uint8Array();
59
+ if (bytes instanceof Uint8Array) return bytes;
60
+ if (ArrayBuffer.isView(bytes)) return new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength);
61
+ if (bytes instanceof ArrayBuffer) return new Uint8Array(bytes);
62
+ if (Array.isArray(bytes)) return Uint8Array.from(bytes);
63
+ if (typeof bytes === 'object' && bytes.length != null) {
64
+ return Uint8Array.from({ length: Number(bytes.length) }, (_, i) => bytes[i] & 255);
65
+ }
66
+ return new Uint8Array();
67
+ }
68
+ function ozU8ToB64(u8) {
69
+ const bytes = ozAsU8(u8);
70
+ let bin = '';
71
+ const step = 0x8000;
72
+ for (let i = 0; i < bytes.length; i += step) {
73
+ bin += String.fromCharCode.apply(null, bytes.subarray(i, i + step));
74
+ }
75
+ return btoa(bin);
76
+ }
77
+ function ozMimeFromBytes(u8, name) {
78
+ const b = ozAsU8(u8);
79
+ const n = String(name || '').toLowerCase();
80
+ if (n.endsWith('.png')) return 'image/png';
81
+ if (n.endsWith('.jpg') || n.endsWith('.jpeg')) return 'image/jpeg';
82
+ if (n.endsWith('.gif')) return 'image/gif';
83
+ if (n.endsWith('.webp')) return 'image/webp';
84
+ if (b.length >= 4 && b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) return 'image/png';
85
+ if (b.length >= 3 && b[0] === 0xff && b[1] === 0xd8 && b[2] === 0xff) return 'image/jpeg';
86
+ if (b.length >= 4 && b[0] === 0x47 && b[1] === 0x49 && b[2] === 0x46 && b[3] === 0x38) return 'image/gif';
87
+ return null;
88
+ }
89
+ const unsub = () => () => {};
90
+ const persist = {
91
+ async read(key) {
92
+ try { return localStorage.getItem('sand.p.' + key); } catch { return null; }
93
+ },
94
+ async write(key, value) {
95
+ try { localStorage.setItem('sand.p.' + key, String(value ?? '')); } catch { /* */ }
96
+ },
97
+ async remove(key) {
98
+ try { localStorage.removeItem('sand.p.' + key); } catch { /* */ }
99
+ },
100
+ async listKeys(prefix) {
101
+ const p = 'sand.p.' + String(prefix || '');
102
+ const out = [];
103
+ try {
104
+ for (let i = 0; i < localStorage.length; i += 1) {
105
+ const k = localStorage.key(i);
106
+ if (k && k.startsWith(p)) out.push(k.slice('sand.p.'.length));
107
+ }
108
+ } catch { /* */ }
109
+ return out;
110
+ },
111
+ async migrateFromLocalStorage(entries) {
112
+ for (const e of entries || []) {
113
+ if (e && e.key != null) await persist.write(String(e.key).replace(/^sand\.p\./, ''), e.value);
114
+ }
115
+ return true;
116
+ },
117
+ };
118
+
119
+ const ACCOUNT = { accountId: 'openzoo', displayName: 'openzoo', email: 'openzoo@local' };
120
+ const AUTH = {
121
+ kind: 'logged-in',
122
+ authId: 'openzoo',
123
+ email: 'openzoo@local',
124
+ displayName: 'openzoo',
125
+ expiresAt: Date.now() + 365 * 86400 * 1000,
126
+ profilePictureUrl: null,
127
+ isAnysphereUser: true,
128
+ accounts: [ACCOUNT],
129
+ };
130
+ const ACCESS = { state: 'granted', reason: 'none' };
131
+ const THEME = { preference: 'dark', resolved: 'dark' };
132
+ const LANGUAGE = { preference: 'system', resolved: 'en' };
133
+ const EGRESS = { state: 'off', relayedStreams: 0, activeStreams: 0 };
134
+ const UPDATE = {
135
+ state: { type: 'disabled', reason: 'not-packaged' },
136
+ currentVersion: '0.30.0',
137
+ currentTrack: 'stable',
138
+ trackOverride: null,
139
+ buildDefaultTrack: null,
140
+ availableTracks: ['stable'],
141
+ isTrackManagedByPolicy: false,
142
+ isBelowMinimumVersion: false,
143
+ autoUpdateWhenIdleOptIn: false,
144
+ autoUpdateWhenIdleGateEnabled: false,
145
+ };
146
+ const DEFAULT_MODEL = { modelId: 'x-ai/grok-4.6', maxMode: true, parameters: [] };
147
+ let defaultModel = DEFAULT_MODEL;
148
+ try {
149
+ const raw = localStorage.getItem('sand.p.default-model');
150
+ if (raw) defaultModel = JSON.parse(raw);
151
+ } catch { /* */ }
152
+
153
+ const plat = /Mac/i.test(navigator.platform || navigator.userAgent) ? 'darwin'
154
+ : /Win/i.test(navigator.platform || navigator.userAgent) ? 'win32'
155
+ : /Linux/i.test(navigator.userAgent) ? 'linux' : 'other';
156
+
157
+ const desktop = {
158
+ platform: plat,
159
+ isDev: false,
160
+ getZoomFactor: () => 1,
161
+ storage: window.localStorage,
162
+ capability: { agent: { clientPersistence: persist } },
163
+ theme: {
164
+ initial: THEME,
165
+ get: async () => THEME,
166
+ set: async (preference) => ({ preference, resolved: preference === 'light' ? 'light' : 'dark' }),
167
+ onChanged: unsub,
168
+ },
169
+ language: {
170
+ initial: LANGUAGE,
171
+ get: async () => LANGUAGE,
172
+ set: async (preference) => ({ preference, resolved: 'en' }),
173
+ onChanged: unsub,
174
+ },
175
+ experiments: {
176
+ initialSnapshot: {},
177
+ getSnapshot: async () => ({}),
178
+ applyFeatureFlagOverride: async () => {},
179
+ refresh: async () => {},
180
+ startRpcTraceWindow: async () => false,
181
+ onChanged: unsub,
182
+ },
183
+ assistiveTech: { initial: false, onChanged: unsub },
184
+ foreverBox: {
185
+ forceRecreate: async () => ({ ok: true }),
186
+ update: async () => ({ ok: true }),
187
+ upgradeSchedule: {
188
+ get: async () => null,
189
+ schedule: async () => ({ ok: true }),
190
+ reschedule: async () => ({ ok: true }),
191
+ cancel: async () => ({ ok: true }),
192
+ },
193
+ egressTunnel: {
194
+ initial: false,
195
+ initialStatus: EGRESS,
196
+ get: async () => false,
197
+ set: async () => false,
198
+ getStatus: async () => EGRESS,
199
+ onChanged: unsub,
200
+ onStatusChanged: unsub,
201
+ },
202
+ webauthnProxy: {
203
+ initial: false,
204
+ get: async () => false,
205
+ set: async () => false,
206
+ onChanged: unsub,
207
+ },
208
+ onUpdateDispatched: unsub,
209
+ onVncUserPresence: unsub,
210
+ onDevBoxPullProgress: unsub,
211
+ },
212
+ windowControls: {
213
+ minimize: async () => {},
214
+ toggleMaximize: async () => {},
215
+ close: async () => {},
216
+ setTitleBarOverlayTone: async () => {},
217
+ resizeWidth: async () => ({ ok: true }),
218
+ },
219
+ onboarding: {
220
+ getSeen: async () => true,
221
+ setSeen: async () => {},
222
+ onSkip: unsub,
223
+ },
224
+ timeZone: {
225
+ get: async () => Intl.DateTimeFormat().resolvedOptions().timeZone || 'UTC',
226
+ setOverride: async () => {},
227
+ },
228
+ hardwareAcceleration: {
229
+ get: async () => ({ enabled: true }),
230
+ setEnabled: async () => ({ enabled: true }),
231
+ relaunch: async () => {},
232
+ },
233
+ notificationPreferences: {
234
+ get: async () => ({ sound: 'ping-1-open-blip', playSound: false }),
235
+ set: async (preferences) => preferences,
236
+ },
237
+ autoReviewInstructions: {
238
+ get: async () => ({ isEnabled: false, allowInstructions: [], blockInstructions: [] }),
239
+ set: async (instructions) => instructions,
240
+ },
241
+ localToolPermission: {
242
+ get: async () => 'always',
243
+ set: async () => 'always',
244
+ ceiling: async () => 'always',
245
+ recordApproval: async () => {},
246
+ clearApprovals: async () => {},
247
+ },
248
+ secrets: {
249
+ list: async () => [],
250
+ reveal: async () => null,
251
+ upsert: async () => ({ ok: true }),
252
+ remove: async () => ({ ok: true }),
253
+ },
254
+ update: {
255
+ getStatus: async () => UPDATE,
256
+ check: async () => UPDATE,
257
+ setTrack: async () => UPDATE,
258
+ quitAndInstall: async () => ({ ok: false }),
259
+ setAutoUpdateWhenIdleOptIn: async () => {},
260
+ onStatusEvent: unsub,
261
+ },
262
+ telemetry: {
263
+ reportTurnClientStart() {},
264
+ reportTurnClientOutcome() {},
265
+ reportAgentLoad() {},
266
+ reportAccessBlocked() {},
267
+ reportAgentsUnreachable() {},
268
+ reportRecoveryAction() {},
269
+ reportRebuildLifecycle() {},
270
+ reportReconciliation() {},
271
+ reportBoxVisibility() {},
272
+ reportSendLatency() {},
273
+ reportHeapMetrics() {},
274
+ reportSendAck() {},
275
+ reportReactionAck() {},
276
+ reportRenderTtfr() {},
277
+ reportRenderStream() {},
278
+ reportVncSession() {},
279
+ reportVncLiveness() {},
280
+ reportOpenComputer() {},
281
+ reportUpdatePrompt() {},
282
+ reportSigninGate() {},
283
+ reportOnboardingStep() {},
284
+ reportOnboardingCompleted() {},
285
+ reportClientFailure() {},
286
+ noteSentryConversation() {},
287
+ },
288
+ agent: {
289
+ getPinnedAgents: async () => [],
290
+ setPinnedAgents: async () => [],
291
+ getSidebarSections: async () => [],
292
+ setSidebarSections: async () => [],
293
+ getDefaultModel: async () => defaultModel,
294
+ setDefaultModel: async (model) => {
295
+ defaultModel = model || defaultModel;
296
+ try { localStorage.setItem('sand.p.default-model', JSON.stringify(defaultModel)); } catch { /* */ }
297
+ return defaultModel;
298
+ },
299
+ getComputerUseModel: async () => null,
300
+ setComputerUseModel: async () => null,
301
+ getAvailableModels: async () => ({
302
+ models: [
303
+ { modelId: 'x-ai/grok-4.6', maxMode: true, parameters: [] },
304
+ { modelId: 'x-ai/grok-4.5', maxMode: true, parameters: [] },
305
+ ],
306
+ }),
307
+ readTranscriptStoreTail: async () => ({ entries: [] }),
308
+ getPublicBotTemplate: async () => null,
309
+ listPublicBotMarketplace: async () => [],
310
+ getGrokBotSlackInstallState: async () => ({ installed: false }),
311
+ startGrokBotSlackConnect: async () => ({ ok: false }),
312
+ installGrokBotSlackApp: async () => ({ ok: false }),
313
+ reinstallGrokBotSlackApp: async () => ({ ok: false }),
314
+ uninstallGrokBotSlackApp: async () => ({ ok: true }),
315
+ listGrokBotTeamAgents: async () => [],
316
+ setGrokBotAgentVisibility: async () => ({ ok: true }),
317
+ setGrokBotAgentSidebarHidden: async () => {},
318
+ clientPersistence: persist,
319
+ },
320
+ mcp: {
321
+ list: async () => ({ servers: [] }),
322
+ effectivePlugins: async () => [],
323
+ catalog: async () => [],
324
+ teamPopularity: async () => ({}),
325
+ pluginLogo: async () => null,
326
+ install: async () => ({ ok: false }),
327
+ updatePluginInstall: async () => ({ ok: false }),
328
+ remove: async () => ({ ok: true }),
329
+ uninstallPlugin: async () => ({ ok: true }),
330
+ authenticate: async () => ({ ok: false }),
331
+ renameAccount: async () => ({ ok: true }),
332
+ removeAccount: async () => ({ ok: true }),
333
+ setCustomInstructions: async () => ({ ok: true }),
334
+ listServerTools: async () => [],
335
+ toggleToolDisabled: async () => [],
336
+ onAuthCompleted: unsub,
337
+ },
338
+ cursorAccount: {
339
+ getStatus: async () => AUTH,
340
+ login: async () => AUTH,
341
+ addAccount: async () => AUTH,
342
+ listAccounts: async () => ({ accounts: [ACCOUNT] }),
343
+ switchAccount: async () => AUTH,
344
+ removeAccount: async () => ({ ok: true }),
345
+ getLoginFlight: async () => ({ kind: 'idle' }),
346
+ cancelLoginFlight: async () => ({ kind: 'idle' }),
347
+ logout: async () => ({ kind: 'logged-out' }),
348
+ updateName: async () => AUTH,
349
+ getAvatar: async () => null,
350
+ getMachines: async () => [],
351
+ updateMachineLabel: async () => ({ ok: true }),
352
+ getWeeklyUsage: async () => ({}),
353
+ getUsageSummary: async () => ({ includedUsageKind: 'weekly', planId: null }),
354
+ getEnterpriseUsage: async () => ({}),
355
+ getPrReviewPreferences: async () => ({}),
356
+ getPrivacyModeEnabled: async () => false,
357
+ getSandAccess: async () => ACCESS,
358
+ getSandAccessFresh: async () => ACCESS,
359
+ mintVoiceCallCredential: async () => ({ ok: false }),
360
+ invokeDashboardAction: async () => ({ ok: false }),
361
+ cancelTrial: async () => ({ ok: true }),
362
+ setSpendLimit: async () => ({ ok: true }),
363
+ getSelectedTeam: async () => ({ selectedTeamId: null, fallback: null }),
364
+ listTeamMemberships: async () => [],
365
+ checkTeamAccess: async () => ({ ok: true }),
366
+ selectTeam: async () => ({ ok: true }),
367
+ ackTeamFallback: async () => {},
368
+ onStatusChanged: unsub,
369
+ onLoginFlightChanged: unsub,
370
+ onSelectedTeamChanged: unsub,
371
+ },
372
+ async openExternal(url) {
373
+ try { window.open(String(url), '_blank', 'noopener'); } catch { /* */ }
374
+ },
375
+ async submitFeedback() { return { ok: true }; },
376
+ async openCloudAgent() {},
377
+ async getWindowState() {
378
+ return { isMaximized: false, isFullScreen: false, isMinimized: false, isVisible: true, isFocused: document.hasFocus() };
379
+ },
380
+ async getBoxMigrationStatus() {
381
+ return { operationId: null, phase: 'done', detail: '' };
382
+ },
383
+ async deepLinksReady() {},
384
+ async forceGatewayReconnect() {},
385
+ async pickAvatarSource() { return null; },
386
+ async pickAvatarFile() { return null; },
387
+ async generateAgentAvatarImage() { return { dataUrl: null }; },
388
+ async resolveAttachmentMedia({ source } = {}) {
389
+ const rec = ozStaged.get(source);
390
+ if (!rec) return null;
391
+ const mime = ozMimeFromBytes(rec.bytes, rec.filename);
392
+ if (!mime) return null;
393
+ return { kind: 'image', dataUrl: `data:${mime};base64,${ozU8ToB64(rec.bytes)}`, width: null, height: null };
394
+ },
395
+ async readAttachmentText({ path } = {}) {
396
+ const rec = ozStaged.get(path);
397
+ if (!rec) return { text: '' };
398
+ return { text: new TextDecoder().decode(rec.bytes) };
399
+ },
400
+ async readAttachmentBytes({ path, maxBytes } = {}) {
401
+ const rec = ozStaged.get(path);
402
+ if (!rec) return new Uint8Array();
403
+ const n = typeof maxBytes === 'number' && maxBytes > 0 ? maxBytes : rec.bytes.length;
404
+ return rec.bytes.slice(0, n);
405
+ },
406
+ async downloadAttachment() { return { ok: false }; },
407
+ async getLinkMetadata() { return null; },
408
+ async stageAttachmentBytes({ filename, bytes } = {}) {
409
+ const buf = ozAsU8(bytes);
410
+ if (!buf.byteLength) return { ok: false, reason: 'failed' };
411
+ if (buf.byteLength > 20 * 1024 * 1024) return { ok: false, reason: 'too-large' };
412
+ const name = String(filename || 'image.png') || 'image.png';
413
+ const id = (globalThis.crypto && crypto.randomUUID)
414
+ ? crypto.randomUUID()
415
+ : `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;
416
+ const stagedPath = `oz-stage-${id}-${name.replace(/[^\w.\-]+/g, '_')}`;
417
+ ozStaged.set(stagedPath, { filename: name, bytes: buf });
418
+ return { ok: true, path: stagedPath };
419
+ },
420
+ async commitStagedAttachments({ paths, filenames } = {}) {
421
+ const ps = Array.isArray(paths) ? paths : [];
422
+ const ns = Array.isArray(filenames) ? filenames : [];
423
+ const out = [];
424
+ for (let i = 0; i < ps.length; i += 1) {
425
+ const p = ps[i];
426
+ const rec = ozStaged.get(p);
427
+ if (!rec) {
428
+ if (typeof p === 'string' && p.startsWith('/openzoo-uploads/')) {
429
+ out.push(p);
430
+ continue;
431
+ }
432
+ return null;
433
+ }
434
+ try {
435
+ const r = await fetch('/oz-upload', {
436
+ method: 'POST',
437
+ headers: { 'content-type': 'application/json' },
438
+ credentials: 'same-origin',
439
+ body: JSON.stringify({
440
+ filename: ns[i] || rec.filename,
441
+ bytesBase64: ozU8ToB64(rec.bytes),
442
+ }),
443
+ });
444
+ const j = await r.json();
445
+ if (!r.ok || !j || !j.path) return null;
446
+ out.push(j.path);
447
+ ozStaged.delete(p);
448
+ } catch {
449
+ return null;
450
+ }
451
+ }
452
+ return out;
453
+ },
454
+ async discardStagedAttachment({ path } = {}) {
455
+ ozStaged.delete(path);
456
+ },
457
+ async transcribeAudio() { return { text: '' }; },
458
+ onFocusAgent: unsub,
459
+ onDeepLink: unsub,
460
+ onBoxMigration: unsub,
461
+ onDevBoxRebuild: unsub,
462
+ onOpenFeedback: unsub,
463
+ onOpenAbout: unsub,
464
+ onOpenSettings: unsub,
465
+ onWidgetGallery: unsub,
466
+ onForceOnboarding: unsub,
467
+ onWindowStateEvent: unsub,
468
+ onZoomFactorEvent: unsub,
469
+ onNotificationSound: unsub,
470
+ };
471
+
472
+ window.desktop = desktop;
473
+
474
+ const OZ_PALETTE = window.__OZ_WHO_PALETTE__ && typeof window.__OZ_WHO_PALETTE__ === 'object'
475
+ ? window.__OZ_WHO_PALETTE__
476
+ : {};
477
+ if (!window.__OZ_WHO__) {
478
+ try {
479
+ fetch('/oz-who', { credentials: 'same-origin' })
480
+ .then((r) => r.ok ? r.json() : null)
481
+ .then((j) => { if (j && j.shortname) window.__OZ_WHO__ = j; })
482
+ .catch(() => {});
483
+ } catch { /* */ }
484
+ }
485
+ function ozEnsureChipCss() {
486
+ if (document.getElementById('oz-who-chip-css')) return;
487
+ const s = document.createElement('style');
488
+ s.id = 'oz-who-chip-css';
489
+ s.textContent = [
490
+ '.oz-who-chip{position:relative;color:inherit;',
491
+ 'box-shadow:0 0 0 2px var(--oz-who,#3db8e8);border-radius:999px;',
492
+ 'background:color-mix(in srgb,var(--oz-who,#3db8e8) 20%,transparent)}',
493
+ '.oz-who-chip::after{content:"";position:absolute;top:-4px;right:-4px;width:9px;height:9px;border-radius:50%;',
494
+ 'background:var(--oz-who,#3db8e8);box-shadow:0 0 0 2px #141414;pointer-events:none}',
495
+ ].join('');
496
+ (document.head || document.documentElement).appendChild(s);
497
+ }
498
+ function ozNameRe() {
499
+ const keys = Object.keys(OZ_PALETTE).filter((k) => /^[a-z][a-z0-9]{1,15}$/.test(k));
500
+ if (!keys.length) return null;
501
+ return new RegExp('^(' + keys.join('|') + '):\\s');
502
+ }
503
+ function ozLooksLikeSpend(t) {
504
+ return /this call \$|OpenRouter would|proves x402|memo x402|spent \$|::oz-spend::/i.test(String(t || ''));
505
+ }
506
+ function ozInnermostName(el, re) {
507
+ const text = String(el.textContent || '').trim();
508
+ if (text.length > 280 || ozLooksLikeSpend(text)) return null;
509
+ const m = text.match(re);
510
+ if (!m) return null;
511
+ for (let i = 0; i < el.children.length; i += 1) {
512
+ if (ozInnermostName(el.children[i], re)) return null;
513
+ }
514
+ return m[1];
515
+ }
516
+ function ozUserChip(el) {
517
+ let cur = el;
518
+ let best = el;
519
+ for (let i = 0; i < 8 && cur && cur !== document.body; i += 1) {
520
+ const r = cur.getBoundingClientRect();
521
+ if (!r.width || r.width > 480 || r.height > 140) break;
522
+ const t = String(cur.textContent || '').trim();
523
+ if (t.length > 280 || ozLooksLikeSpend(t)) break;
524
+ best = cur;
525
+ cur = cur.parentElement;
526
+ }
527
+ const r = best.getBoundingClientRect();
528
+ if (r.width > 520 || r.height > 160 || r.width < 24) return null;
529
+ if (r.left + r.width / 2 < window.innerWidth * 0.38) return null;
530
+ let up = best;
531
+ for (let i = 0; i < 10 && up; i += 1) {
532
+ if (ozLooksLikeSpend(up.textContent)) return null;
533
+ up = up.parentElement;
534
+ }
535
+ return best;
536
+ }
537
+ function ozRingChip(el, name) {
538
+ const color = OZ_PALETTE[name];
539
+ if (!color) return;
540
+ const chip = ozUserChip(el);
541
+ if (!chip) return;
542
+ if (chip.dataset.ozWho === name) return;
543
+ ozEnsureChipCss();
544
+ chip.classList.add('oz-who-chip');
545
+ chip.style.setProperty('--oz-who', color);
546
+ chip.dataset.ozWho = name;
547
+ }
548
+ function ozScanChips() {
549
+ const re = ozNameRe();
550
+ if (!re || !document.body) return;
551
+ const nodes = document.body.querySelectorAll('div, p, span');
552
+ for (let i = 0; i < nodes.length; i += 1) {
553
+ const name = ozInnermostName(nodes[i], re);
554
+ if (name) ozRingChip(nodes[i], name);
555
+ }
556
+ }
557
+ function ozWatchChips() {
558
+ const run = () => { try { ozScanChips(); } catch { /* */ } };
559
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', run);
560
+ else run();
561
+ try {
562
+ const mo = new MutationObserver(run);
563
+ const start = () => {
564
+ if (document.body) mo.observe(document.body, { childList: true, subtree: true });
565
+ };
566
+ if (document.body) start();
567
+ else document.addEventListener('DOMContentLoaded', start);
568
+ } catch { /* */ }
569
+ setInterval(run, 2500);
570
+ }
571
+ ozWatchChips();
572
+ /* spend chip IIFE is concatenated from lib/ozSpendChip.js */
573
+
574
+ function ozIsPhone() {
575
+ try {
576
+ const w = window.innerWidth || 9999;
577
+ const coarse = window.matchMedia && window.matchMedia('(pointer: coarse)').matches;
578
+ const noHover = window.matchMedia && window.matchMedia('(hover: none)').matches;
579
+ const mq = window.matchMedia && (
580
+ window.matchMedia('(max-width: 900px)').matches
581
+ || window.matchMedia('(max-width: 720px)').matches
582
+ );
583
+ return w <= 900 || !!coarse || !!noHover || !!mq;
584
+ } catch {
585
+ return (window.innerWidth || 0) <= 900;
586
+ }
587
+ }
588
+ try {
589
+ const syncNarrow = () => {
590
+ document.documentElement.classList.toggle('oz-narrow', ozIsPhone());
591
+ try { ozEnsureNewChatFab(); } catch { /* */ }
592
+ };
593
+ syncNarrow();
594
+ window.addEventListener('resize', syncNarrow);
595
+ try {
596
+ const mq = window.matchMedia('(max-width: 900px), (pointer: coarse)');
597
+ if (mq.addEventListener) mq.addEventListener('change', syncNarrow);
598
+ else if (mq.addListener) mq.addListener(syncNarrow);
599
+ } catch { /* */ }
600
+ } catch { /* */ }
601
+
602
+ function ozFindNewChat() {
603
+ const labeled = document.querySelectorAll('button[aria-label="New chat"]');
604
+ for (let i = 0; i < labeled.length; i += 1) {
605
+ if (labeled[i].id !== 'oz-new-chat') return labeled[i];
606
+ }
607
+ const roots = document.querySelectorAll(
608
+ '.sand-agents-sidebar__new-actions, .sand-agents-sidebar__rail-actions, .sand-agents-sidebar__header',
609
+ );
610
+ for (let i = 0; i < roots.length; i += 1) {
611
+ const btns = roots[i].querySelectorAll('button');
612
+ for (let b = 0; b < btns.length; b += 1) {
613
+ if (btns[b].id === 'oz-new-chat') continue;
614
+ const al = String(btns[b].getAttribute('aria-label') || '').toLowerCase();
615
+ if (al === 'new chat') return btns[b];
616
+ }
617
+ }
618
+ return null;
619
+ }
620
+ function ozBtnOnScreen(el) {
621
+ if (!el || el.id === 'oz-new-chat') return false;
622
+ try {
623
+ const r = el.getBoundingClientRect();
624
+ if (r.width < 16 || r.height < 16) return false;
625
+ if (r.bottom < 4 || r.top > (window.innerHeight || 0) - 4) return false;
626
+ if (r.right < 4 || r.left > (window.innerWidth || 0) - 4) return false;
627
+ const st = window.getComputedStyle(el);
628
+ if (!st) return true;
629
+ if (st.display === 'none' || st.visibility === 'hidden' || st.opacity === '0') return false;
630
+ return true;
631
+ } catch {
632
+ return false;
633
+ }
634
+ }
635
+ function ozFireNewChat() {
636
+ const real = ozFindNewChat();
637
+ if (real) {
638
+ try { real.click(); return; } catch { /* */ }
639
+ }
640
+ const init = {
641
+ key: 'n',
642
+ code: 'KeyN',
643
+ keyCode: 78,
644
+ which: 78,
645
+ bubbles: true,
646
+ cancelable: true,
647
+ };
648
+ try { document.dispatchEvent(new KeyboardEvent('keydown', { ...init, metaKey: true })); } catch { /* */ }
649
+ try { document.dispatchEvent(new KeyboardEvent('keydown', { ...init, ctrlKey: true })); } catch { /* */ }
650
+ }
651
+ function ozEnsureNewChatFab() {
652
+ const orig = ozFindNewChat();
653
+ const origVisible = ozBtnOnScreen(orig);
654
+ let fab = document.getElementById('oz-new-chat');
655
+ if (origVisible) {
656
+ if (fab) fab.hidden = true;
657
+ return;
658
+ }
659
+ if (!fab) {
660
+ if (!document.body) return;
661
+ fab = document.createElement('button');
662
+ fab.id = 'oz-new-chat';
663
+ fab.type = 'button';
664
+ fab.setAttribute('aria-label', 'New chat');
665
+ fab.textContent = '+';
666
+ let last = 0;
667
+ const go = (ev) => {
668
+ try { ev.preventDefault(); ev.stopPropagation(); } catch { /* */ }
669
+ const now = Date.now();
670
+ if (now - last < 400) return;
671
+ last = now;
672
+ ozFireNewChat();
673
+ };
674
+ fab.addEventListener('click', go);
675
+ fab.addEventListener('pointerup', go);
676
+ try { document.body.appendChild(fab); } catch { /* */ }
677
+ }
678
+ fab.hidden = false;
679
+ }
680
+ (function ozWatchNewChatFab() {
681
+ const tick = () => { try { ozEnsureNewChatFab(); } catch { /* */ } };
682
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', tick);
683
+ else tick();
684
+ setInterval(tick, 800);
685
+ }());
686
+
687
+ function ozOpenTopConversation() {
688
+ if (window.__ozTopOpened) return true;
689
+ const selected = document.querySelector('[data-agent-id][data-selected="true"], [data-agent-id][data-active="true"]');
690
+ if (selected) {
691
+ window.__ozTopOpened = true;
692
+ return true;
693
+ }
694
+ const first = document.querySelector('[data-agent-id]');
695
+ if (!first) return false;
696
+ window.__ozTopOpened = true;
697
+ try { first.click(); } catch { window.__ozTopOpened = false; return false; }
698
+ return true;
699
+ }
700
+ (function ozWatchTopConv() {
701
+ const tick = () => { try { ozOpenTopConversation(); } catch { /* */ } };
702
+ if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', tick);
703
+ else tick();
704
+ let n = 0;
705
+ const iv = setInterval(() => {
706
+ n += 1;
707
+ if (ozOpenTopConversation() || n > 40) clearInterval(iv);
708
+ }, 250);
709
+ try {
710
+ const mo = new MutationObserver(tick);
711
+ const start = () => { if (document.body) mo.observe(document.body, { childList: true, subtree: true }); };
712
+ if (document.body) start();
713
+ else document.addEventListener('DOMContentLoaded', start);
714
+ } catch { /* */ }
715
+ }());
716
+
717
+ function fakePort(ws) {
718
+ const listeners = { message: new Set(), close: new Set() };
719
+ ws.addEventListener('message', (ev) => {
720
+ let data = ev.data;
721
+ try { if (typeof data === 'string') data = JSON.parse(data); } catch { /* keep */ }
722
+ for (const fn of listeners.message) {
723
+ try { fn({ data }); } catch (err) { console.warn('[oz-web] port message handler', err); }
724
+ }
725
+ });
726
+ ws.addEventListener('close', () => {
727
+ for (const fn of listeners.close) {
728
+ try { fn({}); } catch { /* */ }
729
+ }
730
+ });
731
+ return {
732
+ postMessage(data) {
733
+ if (ws.readyState === 1) ws.send(JSON.stringify(data));
734
+ },
735
+ close() { try { ws.close(); } catch { /* */ } },
736
+ start() {},
737
+ addEventListener(type, fn) {
738
+ if (listeners[type]) listeners[type].add(fn);
739
+ },
740
+ };
741
+ }
742
+
743
+ let claimed = null;
744
+ window.coordinatorPort = {
745
+ claim(handler) {
746
+ if (claimed != null) return null;
747
+ claimed = handler;
748
+ return {
749
+ request() {
750
+ if (claimed !== handler) return;
751
+ const proto = location.protocol === 'https:' ? 'wss' : 'ws';
752
+ const ws = new WebSocket(`${proto}://${location.host}/oz-coord`);
753
+ ws.addEventListener('open', () => {
754
+ try { handler.onPort(fakePort(ws)); } catch (err) {
755
+ console.warn('[oz-web] onPort failed', err);
756
+ }
757
+ });
758
+ ws.addEventListener('error', () => {
759
+ console.warn('[oz-web] coordinator websocket error');
760
+ });
761
+ },
762
+ release() { if (claimed === handler) claimed = null; },
763
+ };
764
+ },
765
+ };
766
+ }());