openzoo 0.42.0 → 0.43.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.
Files changed (2) hide show
  1. package/lib/podagent.mjs +111 -1
  2. package/package.json +1 -1
package/lib/podagent.mjs CHANGED
@@ -30,6 +30,109 @@ const PROXY = process.env.OZ_PROXY || 'http://127.0.0.1:8402/v1';
30
30
  const MODEL = process.env.OZ_BRAIN_MODEL || 'x-ai/grok-4.6';
31
31
  const MAX_STEPS = Number(process.env.OZ_MAX_STEPS || 10);
32
32
 
33
+ // Matches the Grok Bot chat surface itself (dark canvas, right-aligned grey
34
+ // user pills, pink-avatar left-aligned replies, pill input bar with + / mic) —
35
+ // this renders INSIDE Grok Bot's own sandbox panel (its sidebar/titlebar are
36
+ // the app's own chrome, not ours), so only the message canvas needs to match.
37
+ const VNC_CHAT_HTML = `<!doctype html>
38
+ <html><head><meta charset="utf-8"><title>openzoo box</title>
39
+ <meta name="viewport" content="width=device-width,initial-scale=1">
40
+ <style>
41
+ :root { color-scheme: dark; }
42
+ * { box-sizing: border-box; }
43
+ html, body { margin: 0; height: 100%; background: #000; }
44
+ body { color: #ececec; font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
45
+ display: flex; flex-direction: column; }
46
+ #log { flex: 1; overflow-y: auto; padding: 28px 24px 12px; display: flex; flex-direction: column; gap: 18px; }
47
+ .row { display: flex; align-items: flex-start; gap: 10px; max-width: 78%; }
48
+ .row.user { align-self: flex-end; flex-direction: row-reverse; }
49
+ .row.bot { align-self: flex-start; }
50
+ .avatar { width: 30px; height: 30px; border-radius: 8px; flex: 0 0 30px; background: #e91e8c;
51
+ display: flex; align-items: center; justify-content: center; }
52
+ .avatar svg { width: 16px; height: 16px; }
53
+ .bubble { padding: 11px 15px; border-radius: 18px; white-space: pre-wrap; word-break: break-word; }
54
+ .row.user .bubble { background: #3a3a3c; border-bottom-right-radius: 5px; }
55
+ .row.bot .bubble { background: transparent; padding: 6px 0; color: #ececec; }
56
+ .row.bot.pending .bubble { color: #8e8e93; }
57
+ .dots span { display: inline-block; width: 5px; height: 5px; margin-right: 3px; border-radius: 50%;
58
+ background: #8e8e93; animation: blink 1.2s infinite ease-in-out; }
59
+ .dots span:nth-child(2) { animation-delay: .2s; } .dots span:nth-child(3) { animation-delay: .4s; }
60
+ @keyframes blink { 0%, 80%, 100% { opacity: .25; } 40% { opacity: 1; } }
61
+ #bar { padding: 10px 16px 18px; }
62
+ #pill { display: flex; align-items: center; gap: 10px; background: #2c2c2e; border-radius: 26px;
63
+ padding: 8px 10px 8px 14px; }
64
+ .icon-btn { width: 32px; height: 32px; border-radius: 50%; border: none; background: transparent;
65
+ color: #ececec; display: flex; align-items: center; justify-content: center; cursor: pointer;
66
+ flex: 0 0 32px; }
67
+ .icon-btn:hover { background: #3a3a3c; }
68
+ .icon-btn svg { width: 18px; height: 18px; }
69
+ #inp { flex: 1; background: transparent; border: none; color: #ececec; font: inherit;
70
+ padding: 6px 0; min-width: 0; }
71
+ #inp::placeholder { color: #8e8e93; }
72
+ #inp:focus { outline: none; }
73
+ </style></head>
74
+ <body>
75
+ <div id="log"></div>
76
+ <div id="bar">
77
+ <div id="pill">
78
+ <button class="icon-btn" tabindex="-1">
79
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
80
+ </button>
81
+ <input id="inp" placeholder="Message pods" autofocus>
82
+ <button class="icon-btn" id="send">
83
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path d="M12 19V5"/><path d="M6 11l6-6 6 6"/></svg>
84
+ </button>
85
+ </div>
86
+ </div>
87
+ <script>
88
+ const log = document.getElementById('log');
89
+ const inp = document.getElementById('inp');
90
+ const send = document.getElementById('send');
91
+ const AVATAR = '<svg viewBox="0 0 24 24" fill="none"><path d="M6 10c0-1 1-2 2-1l2 2 2-2c1-1 2 0 2 1v2c0 2-2 3-4 3s-4-1-4-3v-2z" fill="#fff"/></svg>';
92
+ function addRow(who, text) {
93
+ const row = document.createElement('div');
94
+ row.className = 'row ' + who + (text === null ? ' pending' : '');
95
+ if (who === 'bot') {
96
+ const av = document.createElement('div');
97
+ av.className = 'avatar';
98
+ av.innerHTML = AVATAR;
99
+ row.appendChild(av);
100
+ }
101
+ const bubble = document.createElement('div');
102
+ bubble.className = 'bubble';
103
+ bubble.innerHTML = text === null ? '<span class="dots"><span></span><span></span><span></span></span>' : '';
104
+ if (text !== null) bubble.textContent = text;
105
+ row.appendChild(bubble);
106
+ log.appendChild(row);
107
+ log.scrollTop = log.scrollHeight;
108
+ return { row, bubble };
109
+ }
110
+ async function submit() {
111
+ const task = inp.value.trim();
112
+ if (!task) return;
113
+ inp.value = '';
114
+ inp.disabled = true; send.disabled = true;
115
+ addRow('user', task);
116
+ const pending = addRow('bot', null);
117
+ try {
118
+ const r = await fetch('/drive', {
119
+ method: 'POST', headers: { 'content-type': 'application/json' },
120
+ body: JSON.stringify({ task }),
121
+ });
122
+ const j = await r.json();
123
+ pending.row.classList.remove('pending');
124
+ pending.bubble.textContent = j.text || '(no response)';
125
+ } catch (e) {
126
+ pending.row.classList.remove('pending');
127
+ pending.bubble.textContent = 'error: ' + e.message;
128
+ }
129
+ inp.disabled = false; send.disabled = false; inp.focus();
130
+ }
131
+ send.addEventListener('click', submit);
132
+ inp.addEventListener('keydown', (e) => { if (e.key === 'Enter') submit(); });
133
+ </script>
134
+ </body></html>`;
135
+
33
136
  function record(entry) {
34
137
  try { appendFileSync(LOG, JSON.stringify(entry) + '\n'); } catch { /* best effort */ }
35
138
  console.log(`[agent:${entry.port ?? '-'}] ${entry.method || entry.ev} ${entry.path || ''} ${entry.bodyBytes ?? ''}${entry.frames ? ' frames=' + entry.frames : ''}`);
@@ -205,9 +308,16 @@ for (const port of PORTS) {
205
308
  }
206
309
  }
207
310
 
311
+ // THE REAL TRIGGER. Grok Bot's own chat (StreamUnifiedChat) never reaches
312
+ // us — inference happens server-side inside whatever pod EnsureSandBox
313
+ // named, and since we hijacked that to a pod that doesn't exist on
314
+ // Cursor's side, the UI's chat box just sits silent forever. But the app
315
+ // ALSO opens this vnc.html URL — OUR box — inside its own sandbox panel.
316
+ // Serve a real chat page here instead of a stub: the user types inside
317
+ // Grok Bot's own window, it POSTs straight to /drive on this box.
208
318
  if (req.url && req.url.includes('/vnc')) {
209
319
  res.writeHead(200, { 'content-type': 'text/html' });
210
- res.end('<!doctype html><title>openzoo box</title>');
320
+ res.end(VNC_CHAT_HTML);
211
321
  } else {
212
322
  res.writeHead(200, { 'content-type': 'application/json' });
213
323
  res.end('{"ok":true}');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openzoo",
3
- "version": "0.42.0",
3
+ "version": "0.43.0",
4
4
  "description": "Local x402-paying proxy + MCP server for openzoo.fun — point any OpenAI-compatible harness (Cursor, Claude Code, aider, SDKs) at localhost and it pays per call from a local burner wallet. Solana and Base rails live; Robinhood experimental.",
5
5
  "license": "MIT",
6
6
  "type": "module",