cubefarm 0.1.0 → 0.3.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.
@@ -0,0 +1,234 @@
1
+ // server/ptyHost.ts
2
+ import fs from "node:fs";
3
+ import http from "node:http";
4
+ import net from "node:net";
5
+ import path from "node:path";
6
+ import { spawn } from "node:child_process";
7
+ import * as nodePty from "@lydell/node-pty";
8
+
9
+ // server/ptyProtocol.ts
10
+ var PTY_PROTOCOL = 1;
11
+ var encode = (m) => `${JSON.stringify(m)}
12
+ `;
13
+ function readMessages(sock, onMessage) {
14
+ let buf = "";
15
+ sock.setEncoding("utf8");
16
+ sock.on("data", (chunk) => {
17
+ buf += chunk;
18
+ let nl;
19
+ while ((nl = buf.indexOf("\n")) >= 0) {
20
+ const line = buf.slice(0, nl);
21
+ buf = buf.slice(nl + 1);
22
+ if (!line) continue;
23
+ let m;
24
+ try {
25
+ m = JSON.parse(line);
26
+ } catch {
27
+ continue;
28
+ }
29
+ onMessage(m);
30
+ }
31
+ });
32
+ }
33
+
34
+ // server/ptyHost.ts
35
+ var BACKLOG_BYTES = 2 * 1024 * 1024;
36
+ var HOOK_WAIT_MS = 11e4;
37
+ var ORPHAN_MS = 10 * 6e4;
38
+ var IDLE_EXIT_MS = 6e4;
39
+ var [mode, socketPath, secretFile] = process.argv.slice(2);
40
+ if (mode === "launch") {
41
+ spawn(process.execPath, [...process.execArgv, process.argv[1], "serve", socketPath, secretFile], { detached: true, stdio: "ignore", windowsHide: true }).unref();
42
+ process.exit(0);
43
+ }
44
+ var logFile = path.join(path.dirname(secretFile), "pty-host.log");
45
+ var note = (text) => fs.appendFile(logFile, `${(/* @__PURE__ */ new Date()).toISOString()} ${text}
46
+ `, () => void 0);
47
+ process.on("uncaughtException", (err) => note(`uncaught: ${err.stack ?? err}`));
48
+ for (const sig of ["SIGINT", "SIGHUP", "SIGBREAK"]) process.on(sig, () => void 0);
49
+ var secret = fs.readFileSync(secretFile, "utf8").trim();
50
+ var held = /* @__PURE__ */ new Map();
51
+ var office = null;
52
+ var officeLeft = Date.now();
53
+ var hooksOpen = false;
54
+ var send = (m) => {
55
+ if (office && !office.destroyed) office.write(encode(m));
56
+ };
57
+ function killTree(pid) {
58
+ if (process.platform === "win32") {
59
+ spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { windowsHide: true, stdio: "ignore" }).once("error", () => void 0);
60
+ return;
61
+ }
62
+ try {
63
+ process.kill(-pid, "SIGTERM");
64
+ } catch {
65
+ try {
66
+ process.kill(pid, "SIGTERM");
67
+ } catch {
68
+ }
69
+ }
70
+ }
71
+ function start(m) {
72
+ let proc;
73
+ try {
74
+ proc = nodePty.spawn(m.file, m.args, { name: "xterm-256color", cols: m.cols, rows: m.rows, cwd: m.cwd, env: m.env });
75
+ } catch (err) {
76
+ send({ op: "failed", id: m.id, error: err.message });
77
+ return;
78
+ }
79
+ const h = { proc, meta: m.meta, attached: true, backlog: [], bytes: 0, exit: null };
80
+ held.set(m.id, h);
81
+ send({ op: "spawned", id: m.id, pid: proc.pid });
82
+ proc.onData((data) => {
83
+ if (h.attached && office) return send({ op: "data", id: m.id, data });
84
+ h.backlog.push(data);
85
+ h.bytes += data.length;
86
+ while (h.bytes > BACKLOG_BYTES && h.backlog.length > 1) h.bytes -= h.backlog.shift().length;
87
+ });
88
+ proc.onExit(({ exitCode }) => {
89
+ h.exit = exitCode;
90
+ if (h.attached && office) {
91
+ send({ op: "exit", id: m.id, code: exitCode });
92
+ held.delete(m.id);
93
+ }
94
+ });
95
+ }
96
+ function attach(id) {
97
+ const h = held.get(id);
98
+ if (!h) return send({ op: "exit", id, code: -1 });
99
+ h.attached = true;
100
+ if (h.backlog.length) send({ op: "data", id, data: h.backlog.join("") });
101
+ h.backlog = [];
102
+ h.bytes = 0;
103
+ if (h.exit !== null) {
104
+ send({ op: "exit", id, code: h.exit });
105
+ held.delete(id);
106
+ }
107
+ }
108
+ function stopAll() {
109
+ for (const h of held.values()) if (h.exit === null) killTree(h.proc.pid);
110
+ }
111
+ var waiting = /* @__PURE__ */ new Map();
112
+ var nextRid = 1;
113
+ function answer(rid, reply) {
114
+ const w = waiting.get(rid);
115
+ if (!w) return;
116
+ waiting.delete(rid);
117
+ clearTimeout(w.timer);
118
+ if (!w.res.writableEnded) w.res.writeHead(200, { "content-type": "application/json" }).end(JSON.stringify(reply ?? {}));
119
+ }
120
+ function passHooks() {
121
+ if (!office || !hooksOpen) return;
122
+ for (const [rid, w] of waiting) {
123
+ if (w.sent) continue;
124
+ w.sent = true;
125
+ send({ op: "hook", rid, token: w.token, body: w.body });
126
+ }
127
+ }
128
+ var hooks = http.createServer((req, res) => {
129
+ const token = req.method === "POST" ? req.url?.match(/^\/api\/hooks\/([\w-]+)$/)?.[1] : void 0;
130
+ if (!token) return void res.writeHead(404).end();
131
+ const chunks = [];
132
+ let size = 0;
133
+ req.on("data", (c) => {
134
+ size += c.length;
135
+ if (size <= 64 * 1024 * 1024) chunks.push(c);
136
+ });
137
+ req.on("end", () => {
138
+ let body = {};
139
+ try {
140
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
141
+ } catch {
142
+ }
143
+ const rid = nextRid++;
144
+ waiting.set(rid, { token, body, res, timer: setTimeout(() => answer(rid, {}), HOOK_WAIT_MS), sent: false });
145
+ passHooks();
146
+ });
147
+ });
148
+ function connected(sock) {
149
+ let helloed = false;
150
+ readMessages(sock, (m) => {
151
+ if (!helloed) {
152
+ if (m.op !== "hello" || m.secret !== secret) return void sock.destroy();
153
+ helloed = true;
154
+ if (office && office !== sock) office.destroy();
155
+ office = sock;
156
+ hooksOpen = false;
157
+ const ptys = [...held].map(([id, h2]) => ({ id, pid: h2.proc.pid, meta: h2.meta, exit: h2.exit }));
158
+ send({ op: "ready", version: PTY_PROTOCOL, hookPort: hooks.address().port, ptys, pid: process.pid });
159
+ return;
160
+ }
161
+ const h = "id" in m ? held.get(m.id) : void 0;
162
+ switch (m.op) {
163
+ case "spawn":
164
+ return start(m);
165
+ case "attach":
166
+ return attach(m.id);
167
+ case "write":
168
+ return void (h && h.exit === null && quietly(() => h.proc.write(m.data)));
169
+ case "resize":
170
+ return void (h && h.exit === null && quietly(() => h.proc.resize(m.cols, m.rows)));
171
+ case "meta":
172
+ if (h) h.meta = m.meta;
173
+ return;
174
+ case "kill":
175
+ if (h?.exit === null) killTree(h.proc.pid);
176
+ else if (h) held.delete(m.id);
177
+ return;
178
+ case "hooksReady":
179
+ hooksOpen = true;
180
+ return passHooks();
181
+ case "hookReply":
182
+ return answer(m.rid, m.answer);
183
+ case "shutdown":
184
+ stopAll();
185
+ setTimeout(() => process.exit(0), 3e3);
186
+ return;
187
+ }
188
+ });
189
+ sock.on("error", () => void 0);
190
+ sock.on("close", () => {
191
+ if (office !== sock) return;
192
+ office = null;
193
+ officeLeft = Date.now();
194
+ for (const h of held.values()) h.attached = false;
195
+ for (const w of waiting.values()) w.sent = false;
196
+ });
197
+ }
198
+ function quietly(fn) {
199
+ try {
200
+ fn();
201
+ } catch {
202
+ }
203
+ }
204
+ var server = net.createServer(connected);
205
+ server.on("error", (err) => {
206
+ if (err.code !== "EADDRINUSE" || process.platform === "win32") {
207
+ note(`cannot listen on ${socketPath}: ${err.message}`);
208
+ process.exit(1);
209
+ }
210
+ const probe = net.connect(socketPath, () => {
211
+ probe.destroy();
212
+ process.exit(0);
213
+ });
214
+ probe.on("error", () => {
215
+ fs.rmSync(socketPath, { force: true });
216
+ server.listen(socketPath);
217
+ });
218
+ });
219
+ hooks.listen(0, "127.0.0.1", () => server.listen(socketPath));
220
+ var orphaned = false;
221
+ setInterval(() => {
222
+ if (office) return void (orphaned = false);
223
+ const away = Date.now() - officeLeft;
224
+ if (away > ORPHAN_MS) {
225
+ for (const [id, h] of held) if (h.exit !== null) held.delete(id);
226
+ if (held.size && !orphaned) {
227
+ orphaned = true;
228
+ note(`no office for ${Math.round(away / 6e4)} min: stopping ${held.size} terminal(s)`);
229
+ stopAll();
230
+ }
231
+ }
232
+ if (held.size === 0 && away > IDLE_EXIT_MS) process.exit(0);
233
+ }, 1e4).unref();
234
+ //# sourceMappingURL=ptyHost.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../server/ptyHost.ts", "../server/ptyProtocol.ts"],
4
+ "sourcesContent": ["import fs from 'node:fs';\nimport http from 'node:http';\nimport net from 'node:net';\nimport path from 'node:path';\nimport { spawn } from 'node:child_process';\nimport * as nodePty from '@lydell/node-pty';\nimport { encode, PTY_PROTOCOL, readMessages, type FromHost, type HeldPty, type ToHost } from './ptyProtocol.ts';\n\n// The office's terminal keeper: a small process of its own that runs the agents' CLIs in pseudo-terminals, so they\n// keep working while the office restarts (an update, a crash, a code change in development). The office connects over\n// a local socket (one office at a time), starts and drives CLIs through it and, when it comes back, picks up the ones\n// still running. The CLIs' hooks come in here too and wait while the office is away, so none is lost and none fails.\n//\n// node ptyHost.js launch <socket> <secret file> starts a keeper outside the caller's process tree, then exits\n// node ptyHost.js serve <socket> <secret file> is the keeper\n\n/** Output kept per terminal while no office is listening. */\nconst BACKLOG_BYTES = 2 * 1024 * 1024;\n/** A hook waits this long for an office (the CLIs are told 120 s). */\nconst HOOK_WAIT_MS = 110_000;\n/** With no office for this long, nobody is coming back: the terminals are stopped. */\nconst ORPHAN_MS = 10 * 60_000;\n/** Nothing left to keep and no office: the keeper goes. */\nconst IDLE_EXIT_MS = 60_000;\n\nconst [mode, socketPath, secretFile] = process.argv.slice(2);\n\nif (mode === 'launch') {\n // A detached child whose parent exits at once: the office's process tree (which the launcher kills on a restart)\n // doesn't include it, and on Windows nothing ties it to a console window.\n spawn(process.execPath, [...process.execArgv, process.argv[1], 'serve', socketPath, secretFile], { detached: true, stdio: 'ignore', windowsHide: true }).unref();\n process.exit(0);\n}\n\nconst logFile = path.join(path.dirname(secretFile), 'pty-host.log');\nconst note = (text: string) => fs.appendFile(logFile, `${new Date().toISOString()} ${text}\\n`, () => undefined);\nprocess.on('uncaughtException', (err) => note(`uncaught: ${err.stack ?? err}`));\nfor (const sig of ['SIGINT', 'SIGHUP', 'SIGBREAK'] as const) process.on(sig, () => undefined); // the office's console isn't ours\n\nconst secret = fs.readFileSync(secretFile, 'utf8').trim();\n\ninterface Held {\n proc: nodePty.IPty;\n meta: unknown;\n /** An office is taking this terminal's output right now. */\n attached: boolean;\n backlog: string[];\n bytes: number;\n exit: number | null;\n}\nconst held = new Map<string, Held>();\nlet office: net.Socket | null = null;\nlet officeLeft = Date.now();\nlet hooksOpen = false; // the connected office has picked up its terminals and takes hooks\n\nconst send = (m: FromHost) => {\n if (office && !office.destroyed) office.write(encode(m));\n};\n\nfunction killTree(pid: number) {\n if (process.platform === 'win32') {\n spawn('taskkill', ['/PID', String(pid), '/T', '/F'], { windowsHide: true, stdio: 'ignore' }).once('error', () => undefined);\n return;\n }\n try {\n process.kill(-pid, 'SIGTERM'); // the CLI leads its own session: take its MCP servers and tools with it\n } catch {\n try {\n process.kill(pid, 'SIGTERM');\n } catch {\n // already gone\n }\n }\n}\n\n// ---------- terminals ----------\n\nfunction start(m: Extract<ToHost, { op: 'spawn' }>) {\n let proc: nodePty.IPty;\n try {\n proc = nodePty.spawn(m.file, m.args, { name: 'xterm-256color', cols: m.cols, rows: m.rows, cwd: m.cwd, env: m.env });\n } catch (err) {\n send({ op: 'failed', id: m.id, error: (err as Error).message });\n return;\n }\n const h: Held = { proc, meta: m.meta, attached: true, backlog: [], bytes: 0, exit: null };\n held.set(m.id, h);\n send({ op: 'spawned', id: m.id, pid: proc.pid });\n proc.onData((data) => {\n if (h.attached && office) return send({ op: 'data', id: m.id, data });\n h.backlog.push(data);\n h.bytes += data.length;\n while (h.bytes > BACKLOG_BYTES && h.backlog.length > 1) h.bytes -= h.backlog.shift()!.length;\n });\n proc.onExit(({ exitCode }) => {\n h.exit = exitCode;\n if (h.attached && office) {\n send({ op: 'exit', id: m.id, code: exitCode });\n held.delete(m.id);\n }\n });\n}\n\nfunction attach(id: string) {\n const h = held.get(id);\n if (!h) return send({ op: 'exit', id, code: -1 });\n h.attached = true;\n if (h.backlog.length) send({ op: 'data', id, data: h.backlog.join('') });\n h.backlog = [];\n h.bytes = 0;\n if (h.exit !== null) {\n send({ op: 'exit', id, code: h.exit });\n held.delete(id);\n }\n}\n\nfunction stopAll() {\n for (const h of held.values()) if (h.exit === null) killTree(h.proc.pid);\n}\n\n// ---------- hooks: CLIs call here, the office answers ----------\n\ninterface Waiting {\n token: string;\n body: unknown;\n res: http.ServerResponse;\n timer: NodeJS.Timeout;\n sent: boolean;\n}\nconst waiting = new Map<number, Waiting>();\nlet nextRid = 1;\n\nfunction answer(rid: number, reply: unknown) {\n const w = waiting.get(rid);\n if (!w) return;\n waiting.delete(rid);\n clearTimeout(w.timer);\n if (!w.res.writableEnded) w.res.writeHead(200, { 'content-type': 'application/json' }).end(JSON.stringify(reply ?? {}));\n}\n\n/** Hooks that waited for an office go to the one that's ready (again, if the office before it went without answering). */\nfunction passHooks() {\n if (!office || !hooksOpen) return;\n for (const [rid, w] of waiting) {\n if (w.sent) continue;\n w.sent = true;\n send({ op: 'hook', rid, token: w.token, body: w.body });\n }\n}\n\nconst hooks = http.createServer((req, res) => {\n const token = req.method === 'POST' ? req.url?.match(/^\\/api\\/hooks\\/([\\w-]+)$/)?.[1] : undefined;\n if (!token) return void res.writeHead(404).end();\n const chunks: Buffer[] = [];\n let size = 0;\n req.on('data', (c: Buffer) => {\n size += c.length;\n if (size <= 64 * 1024 * 1024) chunks.push(c);\n });\n req.on('end', () => {\n let body: unknown = {};\n try {\n body = JSON.parse(Buffer.concat(chunks).toString('utf8') || '{}');\n } catch {\n // not JSON: an empty hook\n }\n const rid = nextRid++;\n waiting.set(rid, { token, body, res, timer: setTimeout(() => answer(rid, {}), HOOK_WAIT_MS), sent: false });\n passHooks();\n });\n});\n\n// ---------- the office ----------\n\nfunction connected(sock: net.Socket) {\n let helloed = false;\n readMessages<ToHost>(sock, (m) => {\n if (!helloed) {\n if (m.op !== 'hello' || m.secret !== secret) return void sock.destroy();\n helloed = true;\n if (office && office !== sock) office.destroy();\n office = sock;\n hooksOpen = false;\n const ptys: HeldPty[] = [...held].map(([id, h]) => ({ id, pid: h.proc.pid, meta: h.meta, exit: h.exit }));\n send({ op: 'ready', version: PTY_PROTOCOL, hookPort: (hooks.address() as net.AddressInfo).port, ptys, pid: process.pid });\n return;\n }\n const h = 'id' in m ? held.get(m.id) : undefined;\n switch (m.op) {\n case 'spawn':\n return start(m);\n case 'attach':\n return attach(m.id);\n case 'write':\n return void (h && h.exit === null && quietly(() => h.proc.write(m.data)));\n case 'resize':\n return void (h && h.exit === null && quietly(() => h.proc.resize(m.cols, m.rows)));\n case 'meta':\n if (h) h.meta = m.meta;\n return;\n case 'kill':\n if (h?.exit === null) killTree(h.proc.pid);\n else if (h) held.delete(m.id);\n return;\n case 'hooksReady':\n hooksOpen = true;\n return passHooks();\n case 'hookReply':\n return answer(m.rid, m.answer);\n case 'shutdown':\n stopAll();\n setTimeout(() => process.exit(0), 3000);\n return;\n }\n });\n sock.on('error', () => undefined);\n sock.on('close', () => {\n if (office !== sock) return;\n office = null;\n officeLeft = Date.now();\n for (const h of held.values()) h.attached = false;\n for (const w of waiting.values()) w.sent = false; // unanswered: the next office gets them\n });\n}\n\n/** A pseudo-terminal whose CLI just died throws on writes and resizes before it reports the exit. */\nfunction quietly(fn: () => void) {\n try {\n fn();\n } catch {\n // its exit is on the way\n }\n}\n\nconst server = net.createServer(connected);\nserver.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code !== 'EADDRINUSE' || process.platform === 'win32') {\n note(`cannot listen on ${socketPath}: ${err.message}`);\n process.exit(1);\n }\n // A Unix socket file left behind: take it over, unless a keeper still answers on it.\n const probe = net.connect(socketPath, () => {\n probe.destroy();\n process.exit(0);\n });\n probe.on('error', () => {\n fs.rmSync(socketPath, { force: true });\n server.listen(socketPath);\n });\n});\n\nhooks.listen(0, '127.0.0.1', () => server.listen(socketPath));\n\nlet orphaned = false;\nsetInterval(() => {\n if (office) return void (orphaned = false);\n const away = Date.now() - officeLeft;\n if (away > ORPHAN_MS) {\n for (const [id, h] of held) if (h.exit !== null) held.delete(id);\n if (held.size && !orphaned) {\n orphaned = true;\n note(`no office for ${Math.round(away / 60_000)} min: stopping ${held.size} terminal(s)`);\n stopAll();\n }\n }\n if (held.size === 0 && away > IDLE_EXIT_MS) process.exit(0);\n}, 10_000).unref();\n", "import crypto from 'node:crypto';\nimport path from 'node:path';\nimport type { Socket } from 'node:net';\n\n// What the office and its terminal keeper (ptyHost.ts) say to each other over their local socket: one JSON message\n// per line. Kept free of the rest of the server so the keeper stays small and starts fast.\n\n/** Bumped when the messages change: an office that finds a keeper on another version replaces it. */\nexport const PTY_PROTOCOL = 1;\n\n/** A terminal the keeper holds, as it reports it to an office that (re)connects. */\nexport interface HeldPty {\n id: string;\n pid: number;\n /** What the office stored with it (who it belongs to, its session). */\n meta: unknown;\n /** The CLI has exited (while no office was listening): its exit code. */\n exit: number | null;\n}\n\nexport type ToHost =\n | { op: 'hello'; secret: string; version: number }\n | { op: 'hooksReady' } // the office has picked up its terminals: hooks that waited for it can come in now\n | { op: 'spawn'; id: string; file: string; args: string[]; cwd: string; env: Record<string, string>; cols: number; rows: number; meta: unknown }\n | { op: 'attach'; id: string } // an office picking up a terminal it found: what it missed, then everything live\n | { op: 'write'; id: string; data: string }\n | { op: 'resize'; id: string; cols: number; rows: number }\n | { op: 'meta'; id: string; meta: unknown }\n | { op: 'kill'; id: string }\n | { op: 'hookReply'; rid: number; answer: unknown }\n | { op: 'shutdown' }; // stop every terminal and exit (a keeper on another version)\n\nexport type FromHost =\n | { op: 'ready'; version: number; hookPort: number; ptys: HeldPty[]; pid?: number }\n | { op: 'spawned'; id: string; pid: number }\n | { op: 'failed'; id: string; error: string }\n | { op: 'data'; id: string; data: string }\n | { op: 'exit'; id: string; code: number }\n | { op: 'hook'; rid: number; token: string; body: unknown };\n\n/** The keeper's socket for an office home: a named pipe on Windows, a Unix socket in the home elsewhere. */\nexport function hostSocket(home: string, platform = process.platform): string {\n if (platform !== 'win32') return path.join(home, 'pty.sock');\n const tag = crypto.createHash('sha1').update(path.resolve(home).toLowerCase()).digest('hex').slice(0, 12);\n return `\\\\\\\\.\\\\pipe\\\\cubefarm-pty-${tag}`;\n}\n\nexport const encode = (m: ToHost | FromHost) => `${JSON.stringify(m)}\\n`;\n\n/** Calls back with each message a socket sends (newline-delimited JSON); a line that isn't JSON is skipped. */\nexport function readMessages<T>(sock: Socket, onMessage: (m: T) => void) {\n let buf = '';\n sock.setEncoding('utf8');\n sock.on('data', (chunk: string) => {\n buf += chunk;\n let nl: number;\n while ((nl = buf.indexOf('\\n')) >= 0) {\n const line = buf.slice(0, nl);\n buf = buf.slice(nl + 1);\n if (!line) continue;\n let m: T;\n try {\n m = JSON.parse(line) as T;\n } catch {\n continue;\n }\n onMessage(m);\n }\n });\n}\n"],
5
+ "mappings": ";AAAA,OAAO,QAAQ;AACf,OAAO,UAAU;AACjB,OAAO,SAAS;AAChB,OAAO,UAAU;AACjB,SAAS,aAAa;AACtB,YAAY,aAAa;;;ACGlB,IAAM,eAAe;AAuCrB,IAAM,SAAS,CAAC,MAAyB,GAAG,KAAK,UAAU,CAAC,CAAC;AAAA;AAG7D,SAAS,aAAgB,MAAc,WAA2B;AACvE,MAAI,MAAM;AACV,OAAK,YAAY,MAAM;AACvB,OAAK,GAAG,QAAQ,CAAC,UAAkB;AACjC,WAAO;AACP,QAAI;AACJ,YAAQ,KAAK,IAAI,QAAQ,IAAI,MAAM,GAAG;AACpC,YAAM,OAAO,IAAI,MAAM,GAAG,EAAE;AAC5B,YAAM,IAAI,MAAM,KAAK,CAAC;AACtB,UAAI,CAAC,KAAM;AACX,UAAI;AACJ,UAAI;AACF,YAAI,KAAK,MAAM,IAAI;AAAA,MACrB,QAAQ;AACN;AAAA,MACF;AACA,gBAAU,CAAC;AAAA,IACb;AAAA,EACF,CAAC;AACH;;;ADpDA,IAAM,gBAAgB,IAAI,OAAO;AAEjC,IAAM,eAAe;AAErB,IAAM,YAAY,KAAK;AAEvB,IAAM,eAAe;AAErB,IAAM,CAAC,MAAM,YAAY,UAAU,IAAI,QAAQ,KAAK,MAAM,CAAC;AAE3D,IAAI,SAAS,UAAU;AAGrB,QAAM,QAAQ,UAAU,CAAC,GAAG,QAAQ,UAAU,QAAQ,KAAK,CAAC,GAAG,SAAS,YAAY,UAAU,GAAG,EAAE,UAAU,MAAM,OAAO,UAAU,aAAa,KAAK,CAAC,EAAE,MAAM;AAC/J,UAAQ,KAAK,CAAC;AAChB;AAEA,IAAM,UAAU,KAAK,KAAK,KAAK,QAAQ,UAAU,GAAG,cAAc;AAClE,IAAM,OAAO,CAAC,SAAiB,GAAG,WAAW,SAAS,IAAG,oBAAI,KAAK,GAAE,YAAY,CAAC,IAAI,IAAI;AAAA,GAAM,MAAM,MAAS;AAC9G,QAAQ,GAAG,qBAAqB,CAAC,QAAQ,KAAK,aAAa,IAAI,SAAS,GAAG,EAAE,CAAC;AAC9E,WAAW,OAAO,CAAC,UAAU,UAAU,UAAU,EAAY,SAAQ,GAAG,KAAK,MAAM,MAAS;AAE5F,IAAM,SAAS,GAAG,aAAa,YAAY,MAAM,EAAE,KAAK;AAWxD,IAAM,OAAO,oBAAI,IAAkB;AACnC,IAAI,SAA4B;AAChC,IAAI,aAAa,KAAK,IAAI;AAC1B,IAAI,YAAY;AAEhB,IAAM,OAAO,CAAC,MAAgB;AAC5B,MAAI,UAAU,CAAC,OAAO,UAAW,QAAO,MAAM,OAAO,CAAC,CAAC;AACzD;AAEA,SAAS,SAAS,KAAa;AAC7B,MAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,YAAY,CAAC,QAAQ,OAAO,GAAG,GAAG,MAAM,IAAI,GAAG,EAAE,aAAa,MAAM,OAAO,SAAS,CAAC,EAAE,KAAK,SAAS,MAAM,MAAS;AAC1H;AAAA,EACF;AACA,MAAI;AACF,YAAQ,KAAK,CAAC,KAAK,SAAS;AAAA,EAC9B,QAAQ;AACN,QAAI;AACF,cAAQ,KAAK,KAAK,SAAS;AAAA,IAC7B,QAAQ;AAAA,IAER;AAAA,EACF;AACF;AAIA,SAAS,MAAM,GAAqC;AAClD,MAAI;AACJ,MAAI;AACF,WAAe,cAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,kBAAkB,MAAM,EAAE,MAAM,MAAM,EAAE,MAAM,KAAK,EAAE,KAAK,KAAK,EAAE,IAAI,CAAC;AAAA,EACrH,SAAS,KAAK;AACZ,SAAK,EAAE,IAAI,UAAU,IAAI,EAAE,IAAI,OAAQ,IAAc,QAAQ,CAAC;AAC9D;AAAA,EACF;AACA,QAAM,IAAU,EAAE,MAAM,MAAM,EAAE,MAAM,UAAU,MAAM,SAAS,CAAC,GAAG,OAAO,GAAG,MAAM,KAAK;AACxF,OAAK,IAAI,EAAE,IAAI,CAAC;AAChB,OAAK,EAAE,IAAI,WAAW,IAAI,EAAE,IAAI,KAAK,KAAK,IAAI,CAAC;AAC/C,OAAK,OAAO,CAAC,SAAS;AACpB,QAAI,EAAE,YAAY,OAAQ,QAAO,KAAK,EAAE,IAAI,QAAQ,IAAI,EAAE,IAAI,KAAK,CAAC;AACpE,MAAE,QAAQ,KAAK,IAAI;AACnB,MAAE,SAAS,KAAK;AAChB,WAAO,EAAE,QAAQ,iBAAiB,EAAE,QAAQ,SAAS,EAAG,GAAE,SAAS,EAAE,QAAQ,MAAM,EAAG;AAAA,EACxF,CAAC;AACD,OAAK,OAAO,CAAC,EAAE,SAAS,MAAM;AAC5B,MAAE,OAAO;AACT,QAAI,EAAE,YAAY,QAAQ;AACxB,WAAK,EAAE,IAAI,QAAQ,IAAI,EAAE,IAAI,MAAM,SAAS,CAAC;AAC7C,WAAK,OAAO,EAAE,EAAE;AAAA,IAClB;AAAA,EACF,CAAC;AACH;AAEA,SAAS,OAAO,IAAY;AAC1B,QAAM,IAAI,KAAK,IAAI,EAAE;AACrB,MAAI,CAAC,EAAG,QAAO,KAAK,EAAE,IAAI,QAAQ,IAAI,MAAM,GAAG,CAAC;AAChD,IAAE,WAAW;AACb,MAAI,EAAE,QAAQ,OAAQ,MAAK,EAAE,IAAI,QAAQ,IAAI,MAAM,EAAE,QAAQ,KAAK,EAAE,EAAE,CAAC;AACvE,IAAE,UAAU,CAAC;AACb,IAAE,QAAQ;AACV,MAAI,EAAE,SAAS,MAAM;AACnB,SAAK,EAAE,IAAI,QAAQ,IAAI,MAAM,EAAE,KAAK,CAAC;AACrC,SAAK,OAAO,EAAE;AAAA,EAChB;AACF;AAEA,SAAS,UAAU;AACjB,aAAW,KAAK,KAAK,OAAO,EAAG,KAAI,EAAE,SAAS,KAAM,UAAS,EAAE,KAAK,GAAG;AACzE;AAWA,IAAM,UAAU,oBAAI,IAAqB;AACzC,IAAI,UAAU;AAEd,SAAS,OAAO,KAAa,OAAgB;AAC3C,QAAM,IAAI,QAAQ,IAAI,GAAG;AACzB,MAAI,CAAC,EAAG;AACR,UAAQ,OAAO,GAAG;AAClB,eAAa,EAAE,KAAK;AACpB,MAAI,CAAC,EAAE,IAAI,cAAe,GAAE,IAAI,UAAU,KAAK,EAAE,gBAAgB,mBAAmB,CAAC,EAAE,IAAI,KAAK,UAAU,SAAS,CAAC,CAAC,CAAC;AACxH;AAGA,SAAS,YAAY;AACnB,MAAI,CAAC,UAAU,CAAC,UAAW;AAC3B,aAAW,CAAC,KAAK,CAAC,KAAK,SAAS;AAC9B,QAAI,EAAE,KAAM;AACZ,MAAE,OAAO;AACT,SAAK,EAAE,IAAI,QAAQ,KAAK,OAAO,EAAE,OAAO,MAAM,EAAE,KAAK,CAAC;AAAA,EACxD;AACF;AAEA,IAAM,QAAQ,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC5C,QAAM,QAAQ,IAAI,WAAW,SAAS,IAAI,KAAK,MAAM,0BAA0B,IAAI,CAAC,IAAI;AACxF,MAAI,CAAC,MAAO,QAAO,KAAK,IAAI,UAAU,GAAG,EAAE,IAAI;AAC/C,QAAM,SAAmB,CAAC;AAC1B,MAAI,OAAO;AACX,MAAI,GAAG,QAAQ,CAAC,MAAc;AAC5B,YAAQ,EAAE;AACV,QAAI,QAAQ,KAAK,OAAO,KAAM,QAAO,KAAK,CAAC;AAAA,EAC7C,CAAC;AACD,MAAI,GAAG,OAAO,MAAM;AAClB,QAAI,OAAgB,CAAC;AACrB,QAAI;AACF,aAAO,KAAK,MAAM,OAAO,OAAO,MAAM,EAAE,SAAS,MAAM,KAAK,IAAI;AAAA,IAClE,QAAQ;AAAA,IAER;AACA,UAAM,MAAM;AACZ,YAAQ,IAAI,KAAK,EAAE,OAAO,MAAM,KAAK,OAAO,WAAW,MAAM,OAAO,KAAK,CAAC,CAAC,GAAG,YAAY,GAAG,MAAM,MAAM,CAAC;AAC1G,cAAU;AAAA,EACZ,CAAC;AACH,CAAC;AAID,SAAS,UAAU,MAAkB;AACnC,MAAI,UAAU;AACd,eAAqB,MAAM,CAAC,MAAM;AAChC,QAAI,CAAC,SAAS;AACZ,UAAI,EAAE,OAAO,WAAW,EAAE,WAAW,OAAQ,QAAO,KAAK,KAAK,QAAQ;AACtE,gBAAU;AACV,UAAI,UAAU,WAAW,KAAM,QAAO,QAAQ;AAC9C,eAAS;AACT,kBAAY;AACZ,YAAM,OAAkB,CAAC,GAAG,IAAI,EAAE,IAAI,CAAC,CAAC,IAAIA,EAAC,OAAO,EAAE,IAAI,KAAKA,GAAE,KAAK,KAAK,MAAMA,GAAE,MAAM,MAAMA,GAAE,KAAK,EAAE;AACxG,WAAK,EAAE,IAAI,SAAS,SAAS,cAAc,UAAW,MAAM,QAAQ,EAAsB,MAAM,MAAM,KAAK,QAAQ,IAAI,CAAC;AACxH;AAAA,IACF;AACA,UAAM,IAAI,QAAQ,IAAI,KAAK,IAAI,EAAE,EAAE,IAAI;AACvC,YAAQ,EAAE,IAAI;AAAA,MACZ,KAAK;AACH,eAAO,MAAM,CAAC;AAAA,MAChB,KAAK;AACH,eAAO,OAAO,EAAE,EAAE;AAAA,MACpB,KAAK;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,QAAQ,QAAQ,MAAM,EAAE,KAAK,MAAM,EAAE,IAAI,CAAC;AAAA,MACzE,KAAK;AACH,eAAO,MAAM,KAAK,EAAE,SAAS,QAAQ,QAAQ,MAAM,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC;AAAA,MAClF,KAAK;AACH,YAAI,EAAG,GAAE,OAAO,EAAE;AAClB;AAAA,MACF,KAAK;AACH,YAAI,GAAG,SAAS,KAAM,UAAS,EAAE,KAAK,GAAG;AAAA,iBAChC,EAAG,MAAK,OAAO,EAAE,EAAE;AAC5B;AAAA,MACF,KAAK;AACH,oBAAY;AACZ,eAAO,UAAU;AAAA,MACnB,KAAK;AACH,eAAO,OAAO,EAAE,KAAK,EAAE,MAAM;AAAA,MAC/B,KAAK;AACH,gBAAQ;AACR,mBAAW,MAAM,QAAQ,KAAK,CAAC,GAAG,GAAI;AACtC;AAAA,IACJ;AAAA,EACF,CAAC;AACD,OAAK,GAAG,SAAS,MAAM,MAAS;AAChC,OAAK,GAAG,SAAS,MAAM;AACrB,QAAI,WAAW,KAAM;AACrB,aAAS;AACT,iBAAa,KAAK,IAAI;AACtB,eAAW,KAAK,KAAK,OAAO,EAAG,GAAE,WAAW;AAC5C,eAAW,KAAK,QAAQ,OAAO,EAAG,GAAE,OAAO;AAAA,EAC7C,CAAC;AACH;AAGA,SAAS,QAAQ,IAAgB;AAC/B,MAAI;AACF,OAAG;AAAA,EACL,QAAQ;AAAA,EAER;AACF;AAEA,IAAM,SAAS,IAAI,aAAa,SAAS;AACzC,OAAO,GAAG,SAAS,CAAC,QAA+B;AACjD,MAAI,IAAI,SAAS,gBAAgB,QAAQ,aAAa,SAAS;AAC7D,SAAK,oBAAoB,UAAU,KAAK,IAAI,OAAO,EAAE;AACrD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,QAAQ,IAAI,QAAQ,YAAY,MAAM;AAC1C,UAAM,QAAQ;AACd,YAAQ,KAAK,CAAC;AAAA,EAChB,CAAC;AACD,QAAM,GAAG,SAAS,MAAM;AACtB,OAAG,OAAO,YAAY,EAAE,OAAO,KAAK,CAAC;AACrC,WAAO,OAAO,UAAU;AAAA,EAC1B,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,aAAa,MAAM,OAAO,OAAO,UAAU,CAAC;AAE5D,IAAI,WAAW;AACf,YAAY,MAAM;AAChB,MAAI,OAAQ,QAAO,MAAM,WAAW;AACpC,QAAM,OAAO,KAAK,IAAI,IAAI;AAC1B,MAAI,OAAO,WAAW;AACpB,eAAW,CAAC,IAAI,CAAC,KAAK,KAAM,KAAI,EAAE,SAAS,KAAM,MAAK,OAAO,EAAE;AAC/D,QAAI,KAAK,QAAQ,CAAC,UAAU;AAC1B,iBAAW;AACX,WAAK,iBAAiB,KAAK,MAAM,OAAO,GAAM,CAAC,kBAAkB,KAAK,IAAI,cAAc;AACxF,cAAQ;AAAA,IACV;AAAA,EACF;AACA,MAAI,KAAK,SAAS,KAAK,OAAO,aAAc,SAAQ,KAAK,CAAC;AAC5D,GAAG,GAAM,EAAE,MAAM;",
6
+ "names": ["h"]
7
+ }
package/package.json CHANGED
@@ -1,9 +1,11 @@
1
1
  {
2
2
  "name": "cubefarm",
3
- "version": "0.1.0",
4
- "description": "A cartoon 3D office where a team of Claude Code agents works through your GitHub issues.",
3
+ "version": "0.3.0",
4
+ "description": "A cartoon 3D office where a team of AI coding agents works through your GitHub issues.",
5
5
  "keywords": [
6
6
  "claude-code",
7
+ "codex",
8
+ "opencode",
7
9
  "claude-agent-sdk",
8
10
  "ai-agents",
9
11
  "coding-agents",
@@ -32,26 +34,29 @@
32
34
  "node": ">=22"
33
35
  },
34
36
  "scripts": {
35
- "dev": "concurrently -k -n server,client -c magenta,cyan \"npm:dev:server\" \"npm:dev:client\"",
36
- "demo": "concurrently -k -n server,client -c magenta,cyan \"npm:demo:server\" \"npm:dev:client\"",
37
- "dev:server": "node scripts/dev-server.mjs",
38
- "demo:server": "node scripts/dev-server.mjs --demo",
39
- "dev:client": "vite",
37
+ "dev": "node scripts/office.mjs --dev",
38
+ "demo": "node scripts/office.mjs --dev --demo",
40
39
  "build": "tsc --noEmit -p tsconfig.json && vite build && node scripts/build-server.mjs",
41
- "start": "node bin/cubefarm.js",
40
+ "start": "node scripts/office.mjs",
42
41
  "typecheck": "tsc --noEmit -p tsconfig.json",
43
42
  "test": "vitest run",
44
43
  "test:watch": "vitest",
44
+ "test:e2e": "playwright test",
45
45
  "prepublishOnly": "npm test && npm run build"
46
46
  },
47
47
  "dependencies": {
48
48
  "@anthropic-ai/claude-agent-sdk": "^0.3.283",
49
+ "@lydell/node-pty": "1.2.0-beta.15",
50
+ "@modelcontextprotocol/sdk": "^1.30.1",
51
+ "@xterm/addon-serialize": "^0.14.0",
52
+ "@xterm/headless": "^6.0.0",
49
53
  "express": "^5.2.1",
50
54
  "ws": "^8.22.0",
51
55
  "zod": "^4.0.0"
52
56
  },
53
57
  "devDependencies": {
54
58
  "@dimforge/rapier3d-compat": "0.19.2",
59
+ "@playwright/test": "1.56.1",
55
60
  "@react-three/drei": "^10.7.9",
56
61
  "@react-three/fiber": "^9.8.1",
57
62
  "@react-three/rapier": "^2.2.0",
@@ -62,6 +67,9 @@
62
67
  "@types/three": "^0.186.0",
63
68
  "@types/ws": "^8.18.1",
64
69
  "@vitejs/plugin-react": "^6.1.1",
70
+ "@xterm/addon-fit": "^0.11.0",
71
+ "@xterm/addon-web-links": "^0.12.0",
72
+ "@xterm/xterm": "^6.0.0",
65
73
  "concurrently": "^10.0.5",
66
74
  "esbuild": "^0.28.2",
67
75
  "react": "^19.3.0",
@@ -1,2 +0,0 @@
1
- const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["assets/rapier-DO_XuNdK.js","assets/index-uo74me0U.js","assets/index-8tdBZH9c.css"])))=>i.map(i=>d[i]);
2
- import{A as e,C as t,D as n,E as r,N as i,O as a,S as o,T as s,_ as c,a as l,b as u,c as d,d as f,f as p,g as m,h,i as g,j as _,k as v,l as y,m as b,n as x,o as S,p as C,r as w,s as T,t as E,u as D,v as O,w as k,x as A,y as ee}from"./index-uo74me0U.js";import{a as te,i as j,n as ne,r as re,t as M}from"./rapier-DO_XuNdK.js";var N=i(_());function ie(e,t){if(typeof e!=`object`||!e)return e;var n=e[Symbol.toPrimitive];if(n!==void 0){var r=n.call(e,t||`default`);if(typeof r!=`object`)return r;throw TypeError(`@@toPrimitive must return a primitive value.`)}return(t===`string`?String:Number)(e)}function P(e){var t=ie(e,`string`);return typeof t==`symbol`?t:t+``}function F(e,t,n){return(t=P(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function ae(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),n.push.apply(n,r)}return n}function I(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]==null?{}:arguments[t];t%2?ae(Object(n),!0).forEach(function(t){F(e,t,n[t])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):ae(Object(n)).forEach(function(t){Object.defineProperty(e,t,Object.getOwnPropertyDescriptor(n,t))})}return e}var oe=new n;new o;var se=new e;new r;var L=new k,R=new e,z=new n,B=new e,ce=t=>{let[n,r,i]=t;return new e(n,r,i)},le=({x:e,y:t,z:n,w:r})=>oe.set(e,t,n,r),ue=e=>{if(Array.isArray(e))return new j(e[0],e[1],e[2]);if(typeof e==`number`)return new j(e,e,e);{let t=e;return new j(t.x,t.y,t.z)}},de={fixed:1,dynamic:0,kinematicPosition:2,kinematicVelocity:3},fe=e=>de[e],pe=(e,t)=>{let n=Array.from(e);for(let r=0;r<e.length/3;r++)n[r*3]*=t.x,n[r*3+1]*=t.y,n[r*3+2]*=t.z;return n},me=t=>t?t instanceof n?[t.x,t.y,t.z,t.w]:t instanceof e||t instanceof o?[t.x,t.y,t.z]:Array.isArray(t)?t:[t]:[0];function V(e){let t=(0,N.useRef)(void 0);return t.current===void 0&&(t.current={value:typeof e==`function`?e():e}),t.current.value}var he=e=>{let t=(0,N.useRef)(e),n=(0,N.useRef)(0),r=(0,N.useRef)(0);(0,N.useEffect)(()=>{t.current=e},[e]),(0,N.useEffect)(()=>{let e=()=>{let i=performance.now(),a=i-r.current;n.current=requestAnimationFrame(e),t.current(a/1e3),r.current=i};return n.current=requestAnimationFrame(e),()=>cancelAnimationFrame(n.current)},[])},ge=({onStep:e,updatePriority:t})=>(b((t,n)=>{e(n)},t),null),_e=({onStep:e})=>(he(t=>{e(t)}),null),ve=(0,N.memo)(({onStep:e,type:t,updatePriority:n})=>t===`independent`?N.createElement(_e,{onStep:e}):N.createElement(ge,{onStep:e,updatePriority:n}));function ye(e,t){if(e==null)return{};var n={};for(var r in e)if({}.hasOwnProperty.call(e,r)){if(t.indexOf(r)!==-1)continue;n[r]=e[r]}return n}function be(e,t){if(e==null)return{};var n,r,i=ye(e,t);if(Object.getOwnPropertySymbols){var a=Object.getOwnPropertySymbols(e);for(r=0;r<a.length;r++)n=a[r],t.indexOf(n)===-1&&{}.propertyIsEnumerable.call(e,n)&&(i[n]=e[n])}return i}var xe=[`mass`,`linearDamping`,`angularDamping`,`type`,`onCollisionEnter`,`onCollisionExit`,`onIntersectionEnter`,`onIntersectionExit`,`onContactForce`,`children`,`canSleep`,`ccd`,`gravityScale`,`softCcdPrediction`,`ref`],Se=(e,t,n)=>{let r=t.slice();if(e===`heightfield`){let e=r[3];return e.x*=n.x,e.x*=n.y,e.x*=n.z,r}if(e===`trimesh`||e===`convexHull`)return r[0]=pe(r[0],n),r;let i=[n.x,n.y,n.z,n.x,n.x];return r.map((e,t)=>i[t]*e)},Ce=(e,t,n,r)=>{let i=Se(e.shape,e.args,n),a=re[e.shape](...i);return t.createCollider(a,r?.())},we=[`shape`,`args`],Te="Please pick ONLY ONE of the `density`, `mass` and `massProperties` options.",Ee=(e,t)=>{if(t.density!==void 0){if(t.mass!==void 0||t.massProperties!==void 0)throw Error(Te);e.setDensity(t.density);return}if(t.mass!==void 0){if(t.massProperties!==void 0)throw Error(Te);e.setMass(t.mass);return}t.massProperties!==void 0&&e.setMassProperties(t.massProperties.mass,t.massProperties.centerOfMass,t.massProperties.principalAngularInertia,t.massProperties.angularInertiaLocalFrame)},De={sensor:(e,t)=>{e.setSensor(t)},collisionGroups:(e,t)=>{e.setCollisionGroups(t)},solverGroups:(e,t)=>{e.setSolverGroups(t)},friction:(e,t)=>{e.setFriction(t)},frictionCombineRule:(e,t)=>{e.setFrictionCombineRule(t)},restitution:(e,t)=>{e.setRestitution(t)},restitutionCombineRule:(e,t)=>{e.setRestitutionCombineRule(t)},activeCollisionTypes:(e,t)=>{e.setActiveCollisionTypes(t)},contactSkin:(e,t)=>{e.setContactSkin(t)},quaternion:()=>{},position:()=>{},rotation:()=>{},scale:()=>{}},Oe=Object.keys(De),ke=(e,t,n)=>{let r=n.get(e.handle);if(r){let n=r.object.parent.getWorldScale(se),i=r.worldParent?.matrixWorld.clone().invert();r.object.updateWorldMatrix(!0,!1),L.copy(r.object.matrixWorld),i&&L.premultiply(i),L.decompose(R,z,B),e.parent()?(e.setTranslationWrtParent({x:R.x*n.x,y:R.y*n.y,z:R.z*n.z}),e.setRotationWrtParent(z)):(e.setTranslation({x:R.x*n.x,y:R.y*n.y,z:R.z*n.z}),e.setRotation(z)),Oe.forEach(n=>{if(n in t){let r=t[n];De[n](e,r,t)}}),Ee(e,t)}},Ae=(e,t,n)=>{let r=(0,N.useMemo)(()=>Oe.flatMap(e=>me(t[e])),[t]);(0,N.useEffect)(()=>{ke(e(),t,n)},[...r,e])},je=e=>{let t=!1;return e.traverseAncestors(e=>{e.userData.r3RapierType===`MeshCollider`&&(t=!0)}),t},Me=(e,t,n)=>({collider:e,worldParent:n||void 0,object:t}),Ne={cuboid:`cuboid`,ball:`ball`,hull:`convexHull`,trimesh:`trimesh`},Pe=({object:e,ignoreMeshColliders:t=!0,options:n})=>{let r=[];e.updateWorldMatrix(!0,!1);let i=e.matrixWorld.clone().invert(),a=e=>{if(`isMesh`in e){if(t&&je(e))return;let a=e.getWorldScale(B),s=Ne[n.colliders||`cuboid`];e.updateWorldMatrix(!0,!1),L.copy(e.matrixWorld).premultiply(i).decompose(R,z,B);let c=new o().setFromQuaternion(z,`XYZ`),{geometry:l}=e,{args:u,offset:d}=Fe(l,n.colliders||`cuboid`),f=I(I({},Re(n)),{},{args:u,shape:s,rotation:[c.x,c.y,c.z],position:[R.x+d.x*a.x,R.y+d.y*a.y,R.z+d.z*a.z],scale:[a.x,a.y,a.z]});r.push(f)}};return n.includeInvisible?e.traverse(a):e.traverseVisible(a),r},Fe=(t,n)=>{switch(n){case`cuboid`:{t.computeBoundingBox();let{boundingBox:n}=t,r=n.getSize(new e);return{args:[r.x/2,r.y/2,r.z/2],offset:n.getCenter(new e)}}case`ball`:{t.computeBoundingSphere();let{boundingSphere:e}=t;return{args:[e.radius],offset:e.center}}case`trimesh`:{let n=t.index?t.clone():C(t);return{args:[n.attributes.position.array,n.index?.array],offset:new e}}case`hull`:return{args:[t.clone().attributes.position.array],offset:new e}}return{args:[],offset:new e}},Ie=e=>({collision:!!(e!=null&&e.onCollisionEnter||e!=null&&e.onCollisionExit||e!=null&&e.onIntersectionEnter||e!=null&&e.onIntersectionExit),contactForce:!!(e!=null&&e.onContactForce)}),Le=(e,t,n,r={})=>{let{onCollisionEnter:i,onCollisionExit:a,onIntersectionEnter:o,onIntersectionExit:s,onContactForce:c}=t;(0,N.useEffect)(()=>{let l=e();if(l){let{collision:e,contactForce:u}=Ie(t),d=e||r.collision,f=u||r.contactForce;d&&f?l.setActiveEvents(M.COLLISION_EVENTS|M.CONTACT_FORCE_EVENTS):d?l.setActiveEvents(M.COLLISION_EVENTS):f&&l.setActiveEvents(M.CONTACT_FORCE_EVENTS),n.set(l.handle,{onCollisionEnter:i,onCollisionExit:a,onIntersectionEnter:o,onIntersectionExit:s,onContactForce:c})}return()=>{l&&n.delete(l.handle)}},[i,a,o,s,c,r])},Re=(e={})=>be(e,xe),ze=e=>{let t=(0,N.useRef)(e);return(0,N.useEffect)(()=>{t.current=e},[e]),t},H=()=>{let e=(0,N.useContext)(Ge);if(!e)throw Error(`react-three-rapier: useRapier must be used within <Physics />!`);return e},Be=e=>{let{beforeStepCallbacks:t}=H(),n=ze(e);(0,N.useEffect)(()=>(t.add(n),()=>{t.delete(n)}),[])},Ve=e=>{let{afterStepCallbacks:t}=H(),n=ze(e);(0,N.useEffect)(()=>(t.add(n),()=>{t.delete(n)}),[])},He=(e,t,n=!0)=>{let[r,i]=(0,N.useState)([]);return(0,N.useEffect)(()=>{e.current&&t.colliders!==!1&&i(Pe({object:e.current,options:t,ignoreMeshColliders:n}))},[t.colliders]),r},Ue=(0,N.memo)(()=>{let{world:e}=H(),t=(0,N.useRef)(null);return b(()=>{let n=t.current;if(!n)return;let r=e.debugRender(),i=new ee;i.setAttribute(`position`,new O(r.vertices,3)),i.setAttribute(`color`,new O(r.colors,4)),n.geometry.dispose(),n.geometry=i}),N.createElement(`group`,null,N.createElement(`lineSegments`,{ref:t,frustumCulled:!1},N.createElement(`lineBasicMaterial`,{color:16777215,vertexColors:!0}),N.createElement(`bufferGeometry`,null)))}),We=e=>{let t;return{proxy:new Proxy({},{get(n,r){return t||=e(),Reflect.get(t,r)},set(n,r,i){return t||=e(),Reflect.set(t,r,i)}}),reset:()=>{t=void 0},set:e=>{t=e}}},Ge=(0,N.createContext)(void 0),Ke=(e,t)=>({target:{rigidBody:e.rigidBody.object,collider:e.collider.object,colliderObject:e.collider.state?.object,rigidBodyObject:e.rigidBody.state?.object},other:{rigidBody:t.rigidBody.object,collider:t.collider.object,colliderObject:t.collider.state?.object,rigidBodyObject:t.rigidBody.state?.object},rigidBody:t.rigidBody.object,collider:t.collider.object,colliderObject:t.collider.state?.object,rigidBodyObject:t.rigidBody.state?.object}),qe=async()=>{let e=await f(()=>import(`./rapier-DO_XuNdK.js`).then(e=>e.o),__vite__mapDeps([0,1,2]));return await e.init(),e},Je=e=>{let{colliders:n=`cuboid`,children:r,timeStep:i=1/60,paused:a=!1,interpolate:o=!0,updatePriority:s,updateLoop:c=`follow`,debug:l=!1,gravity:u=[0,-9.81,0],allowedLinearError:d=.001,predictionDistance:f=.002,numSolverIterations:p=4,numInternalPgsIterations:g=1,minIslandSize:_=128,maxCcdSubsteps:v=1,contactNaturalFrequency:y=30,lengthUnit:b=1}=e,x=m(qe,[`@react-thee/rapier`,qe]),{invalidate:S}=h(),C=V(()=>new Map),w=V(()=>new Map),T=V(()=>new Map),E=V(()=>new Map),D=V(()=>new ne(!1)),O=V(()=>new Set),k=V(()=>new Set),A=V(()=>({filterContactPair:(...e)=>{for(let t of O){let n=t.current(...e);if(n!==null)return n}return null},filterIntersectionPair:(...e)=>{for(let t of k)if(t.current(...e)===!1)return!1;return!0}})),ee=V(()=>new Set),te=V(()=>new Set),{proxy:j,reset:re,set:M}=V(()=>We(()=>new x.World(ce(u))));(0,N.useEffect)(()=>()=>{j.free(),re()},[]),(0,N.useEffect)(()=>{j.gravity=ue(u),j.integrationParameters.numSolverIterations=p,j.integrationParameters.numInternalPgsIterations=g,j.integrationParameters.normalizedAllowedLinearError=d,j.integrationParameters.minIslandSize=_,j.integrationParameters.maxCcdSubsteps=v,j.integrationParameters.normalizedPredictionDistance=f,j.lengthUnit=b,j.integrationParameters.contact_natural_frequency=y},[j,...u,p,g,d,_,v,f,b,y]);let ie=(0,N.useCallback)(e=>{let t=j.getCollider(e),n=E.get(e),r=w.get(e),i=t?.parent()?.handle,a=i===void 0?void 0:j.getRigidBody(i),o=a&&i!==void 0?T.get(i):void 0,s=i===void 0?void 0:C.get(i);return{collider:{object:t,events:n,state:r},rigidBody:{object:a,events:o,state:s}}},[]),[P]=(0,N.useState)({previousState:{},accumulator:0}),F=(0,N.useCallback)(e=>{let n=j,r=i===`vary`,s=t.clamp(e,0,.5),c=e=>{ee.forEach(e=>{e.current(n)}),n.timestep=e;let t=O.size>0||k.size>0;n.step(D,t?A:void 0),te.forEach(e=>{e.current(n)})};if(r)c(s);else for(P.accumulator+=s;P.accumulator>=i;)o&&(P.previousState={},n.forEachRigidBody(e=>{P.previousState[e.handle]={position:e.translation(),rotation:e.rotation()}})),c(i),P.accumulator-=i;let l=r||!o||a?1:P.accumulator/i;C.forEach((e,t)=>{let r=n.getRigidBody(t),i=T.get(t);if(i!=null&&i.onSleep||i!=null&&i.onWake){if(r.isSleeping()&&!e.isSleeping){var a;i==null||(a=i.onSleep)==null||a.call(i)}if(!r.isSleeping()&&e.isSleeping){var o;i==null||(o=i.onWake)==null||o.call(i)}e.isSleeping=r.isSleeping()}if(!r||r.isSleeping()&&!(`isInstancedMesh`in e.object)||!e.setMatrix)return;let s=r.translation(),c=r.rotation(),u=P.previousState[t];u&&(L.compose(u.position,le(u.rotation),e.scale).premultiply(e.invertedWorldMatrix).decompose(R,z,B),e.meshType==`mesh`&&(e.object.position.copy(R),e.object.quaternion.copy(z))),L.compose(s,le(c),e.scale).premultiply(e.invertedWorldMatrix).decompose(R,z,B),e.meshType==`instancedMesh`?e.setMatrix(L):(e.object.position.lerp(R,l),e.object.quaternion.slerp(z,l))}),D.drainCollisionEvents((e,t,r)=>{let i=ie(e),a=ie(t);if(!(i!=null&&i.collider.object)||!(a!=null&&a.collider.object))return;let o=Ke(i,a),s=Ke(a,i);if(r)n.contactPair(i.collider.object,a.collider.object,(e,t)=>{var n,r,c,l,u,d,f,p;(n=i.rigidBody.events)==null||(r=n.onCollisionEnter)==null||r.call(n,I(I({},o),{},{manifold:e,flipped:t})),(c=a.rigidBody.events)==null||(l=c.onCollisionEnter)==null||l.call(c,I(I({},s),{},{manifold:e,flipped:t})),(u=i.collider.events)==null||(d=u.onCollisionEnter)==null||d.call(u,I(I({},o),{},{manifold:e,flipped:t})),(f=a.collider.events)==null||(p=f.onCollisionEnter)==null||p.call(f,I(I({},s),{},{manifold:e,flipped:t}))});else{var c,l,u,d,f,p,m,h;(c=i.rigidBody.events)==null||(l=c.onCollisionExit)==null||l.call(c,o),(u=a.rigidBody.events)==null||(d=u.onCollisionExit)==null||d.call(u,s),(f=i.collider.events)==null||(p=f.onCollisionExit)==null||p.call(f,o),(m=a.collider.events)==null||(h=m.onCollisionExit)==null||h.call(m,s)}if(r){if(n.intersectionPair(i.collider.object,a.collider.object)){var g,_,v,y,b,x,S,C;(g=i.rigidBody.events)==null||(_=g.onIntersectionEnter)==null||_.call(g,o),(v=a.rigidBody.events)==null||(y=v.onIntersectionEnter)==null||y.call(v,s),(b=i.collider.events)==null||(x=b.onIntersectionEnter)==null||x.call(b,o),(S=a.collider.events)==null||(C=S.onIntersectionEnter)==null||C.call(S,s)}}else{var w,T,E,D,O,k,A,ee;(w=i.rigidBody.events)==null||(T=w.onIntersectionExit)==null||T.call(w,o),(E=a.rigidBody.events)==null||(D=E.onIntersectionExit)==null||D.call(E,s),(O=i.collider.events)==null||(k=O.onIntersectionExit)==null||k.call(O,o),(A=a.collider.events)==null||(ee=A.onIntersectionExit)==null||ee.call(A,s)}}),D.drainContactForceEvents(e=>{var t,n,r,i,a,o,s,c;let l=ie(e.collider1()),u=ie(e.collider2());if(!(l!=null&&l.collider.object)||!(u!=null&&u.collider.object))return;let d=Ke(l,u),f=Ke(u,l);(t=l.rigidBody.events)==null||(n=t.onContactForce)==null||n.call(t,I(I({},d),{},{totalForce:e.totalForce(),totalForceMagnitude:e.totalForceMagnitude(),maxForceDirection:e.maxForceDirection(),maxForceMagnitude:e.maxForceMagnitude()})),(r=u.rigidBody.events)==null||(i=r.onContactForce)==null||i.call(r,I(I({},f),{},{totalForce:e.totalForce(),totalForceMagnitude:e.totalForceMagnitude(),maxForceDirection:e.maxForceDirection(),maxForceMagnitude:e.maxForceMagnitude()})),(a=l.collider.events)==null||(o=a.onContactForce)==null||o.call(a,I(I({},d),{},{totalForce:e.totalForce(),totalForceMagnitude:e.totalForceMagnitude(),maxForceDirection:e.maxForceDirection(),maxForceMagnitude:e.maxForceMagnitude()})),(s=u.collider.events)==null||(c=s.onContactForce)==null||c.call(s,I(I({},f),{},{totalForce:e.totalForce(),totalForceMagnitude:e.totalForceMagnitude(),maxForceDirection:e.maxForceDirection(),maxForceMagnitude:e.maxForceMagnitude()}))}),n.forEachActiveRigidBody(()=>{S()})},[a,i,o,j]),ae=(0,N.useMemo)(()=>({rapier:x,world:j,setWorld:e=>{M(e)},physicsOptions:{colliders:n,gravity:u},rigidBodyStates:C,colliderStates:w,rigidBodyEvents:T,colliderEvents:E,beforeStepCallbacks:ee,afterStepCallbacks:te,isPaused:a,isDebug:l,step:F,filterContactPairHooks:O,filterIntersectionPairHooks:k}),[a,F,l,n,u]),oe=(0,N.useCallback)(e=>{a||F(e)},[a,F]);return N.createElement(Ge.Provider,{value:ae},N.createElement(ve,{onStep:oe,type:c,updatePriority:s}),l&&N.createElement(Ue,null),r)};function U(){return U=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)({}).hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},U.apply(null,arguments)}var Ye=(e,t=null)=>{let n=(0,N.useRef)(t);return e&&typeof e!=`function`?(e.current||=n.current,e):n},Xe=(e,t,n)=>{let r=(0,N.useRef)(void 0),i=(0,N.useCallback)(()=>(r.current||=e(),r.current),n);return(0,N.useEffect)(()=>{let e=i(),n=()=>t(e);return()=>{n(),r.current=void 0}},[i]),i},Ze=e=>{let t=fe(e?.type||`dynamic`),n=new te(t);return n.canSleep=e?.canSleep??!0,n},Qe=({rigidBody:e,object:t,setMatrix:n,getMatrix:r,worldScale:i,meshType:a=`mesh`})=>(t.updateWorldMatrix(!0,!1),{object:t,rigidBody:e,invertedWorldMatrix:t.parent.matrixWorld.clone().invert(),setMatrix:n||(e=>{t.matrix.copy(e)}),getMatrix:r||(e=>e.copy(t.matrix)),scale:i||t.getWorldScale(B).clone(),isSleeping:!1,meshType:a}),$e=[`args`,`colliders`,`canSleep`],et={gravityScale:(e,t)=>{e.setGravityScale(t,!0)},additionalSolverIterations(e,t){e.setAdditionalSolverIterations(t)},linearDamping:(e,t)=>{e.setLinearDamping(t)},angularDamping:(e,t)=>{e.setAngularDamping(t)},dominanceGroup:(e,t)=>{e.setDominanceGroup(t)},enabledRotations:(e,[t,n,r])=>{e.setEnabledRotations(t,n,r,!0)},enabledTranslations:(e,[t,n,r])=>{e.setEnabledTranslations(t,n,r,!0)},lockRotations:(e,t)=>{e.lockRotations(t,!0)},lockTranslations:(e,t)=>{e.lockTranslations(t,!0)},angularVelocity:(e,[t,n,r])=>{e.setAngvel({x:t,y:n,z:r},!0)},linearVelocity:(e,[t,n,r])=>{e.setLinvel({x:t,y:n,z:r},!0)},ccd:(e,t)=>{e.enableCcd(t)},softCcdPrediction:(e,t)=>{e.setSoftCcdPrediction(t)},userData:(e,t)=>{e.userData=t},type(e,t){e.setBodyType(fe(t),!0)},position:()=>{},rotation:()=>{},quaternion:()=>{},scale:()=>{}},tt=Object.keys(et),nt=(e,t,n,r=!0)=>{if(!e)return;let i=n.get(e.handle);i&&(r&&(i.object.updateWorldMatrix(!0,!1),L.copy(i.object.matrixWorld).decompose(R,z,B),e.setTranslation(R,!1),e.setRotation(z,!1)),tt.forEach(n=>{n in t&&et[n](e,t[n])}))},rt=(e,t,n,r=!0)=>{let i=(0,N.useMemo)(()=>tt.flatMap(e=>me(t[e])),[t]);(0,N.useEffect)(()=>{nt(e(),t,n,r)},i)},it=(e,t,n)=>{let{onWake:r,onSleep:i,onCollisionEnter:a,onCollisionExit:o,onIntersectionEnter:s,onIntersectionExit:c,onContactForce:l}=t,u={onWake:r,onSleep:i,onCollisionEnter:a,onCollisionExit:o,onIntersectionEnter:s,onIntersectionExit:c,onContactForce:l};(0,N.useEffect)(()=>{let t=e();return n.set(t.handle,u),()=>{n.delete(t.handle)}},[r,i,a,o,s,c,l])},at=({x:t,y:n,z:r}={x:0,y:0,z:0})=>new e(t,n,r),W=(0,N.memo)(e=>{let{children:t,position:n,rotation:r,quaternion:i,scale:a,name:o}=e,{world:s,colliderEvents:c,colliderStates:l}=H(),u=_t(),d=Ye(e.ref),f=(0,N.useRef)(null),p=Xe(()=>{let t=f.current.getWorldScale(at()),n=Ce(e,s,t,u?.getRigidBody);return typeof e.ref==`function`&&e.ref(n),d.current=n,n},e=>{s.getCollider(e.handle)&&s.removeCollider(e,!0)},[...we.flatMap(t=>Array.isArray(e[t])?e[t]:[e[t]]),u]);(0,N.useEffect)(()=>{let e=p();return l.set(e.handle,Me(e,f.current,u?.ref.current)),()=>{l.delete(e.handle)}},[p]);let m=(0,N.useMemo)(()=>I(I({},Re(u?.options)),e),[e,u?.options]);return Ae(p,m,l),Le(p,m,c,Ie(u?.options)),N.createElement(`object3D`,{position:n,rotation:r,quaternion:i,scale:a,ref:f,name:o},t)}),G=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`cuboid`,ref:t})));G.displayName=`CuboidCollider`;var ot=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`roundCuboid`,ref:t})));ot.displayName=`RoundCuboidCollider`;var st=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`ball`,ref:t})));st.displayName=`BallCollider`;var ct=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`capsule`,ref:t})));ct.displayName=`CapsuleCollider`;var lt=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`heightfield`,ref:t})));lt.displayName=`HeightfieldCollider`;var ut=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`trimesh`,ref:t})));ut.displayName=`TrimeshCollider`;var dt=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`cone`,ref:t})));dt.displayName=`ConeCollider`;var ft=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`roundCone`,ref:t})));ft.displayName=`RoundConeCollider`;var pt=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`cylinder`,ref:t})));pt.displayName=`CylinderCollider`,pt.displayName=`RoundCylinderCollider`;var mt=N.forwardRef((e,t)=>N.createElement(W,U({},e,{shape:`convexHull`,ref:t})));mt.displayName=`ConvexHullCollider`;var ht=[`ref`,`children`,`type`,`position`,`rotation`,`scale`,`quaternion`,`transformState`],gt=(0,N.createContext)(void 0),_t=()=>(0,N.useContext)(gt),K=(0,N.memo)(e=>{let{ref:t,children:n,type:r,position:i,rotation:a,scale:o,quaternion:s,transformState:c}=e,l=be(e,ht),u=(0,N.useRef)(null),d=Ye(t),{world:f,rigidBodyStates:p,physicsOptions:m,rigidBodyEvents:h}=H(),g=(0,N.useMemo)(()=>I(I(I({},m),e),{},{children:void 0}),[m,e]),_=$e.flatMap(e=>Array.isArray(g[e])?[...g[e]]:g[e]),v=He(u,g),y=Xe(()=>{let e=Ze(g),n=f.createRigidBody(e);return typeof t==`function`&&t(n),d.current=n,n},e=>{f.getRigidBody(e.handle)&&f.removeRigidBody(e)},_);(0,N.useEffect)(()=>{let t=y(),n=Qe({rigidBody:t,object:u.current});return p.set(t.handle,e.transformState?e.transformState(n):n),()=>{p.delete(t.handle)}},[y]),rt(y,g,p),it(y,g,h);let b=(0,N.useMemo)(()=>({ref:u,getRigidBody:y,options:g}),[y]);return N.createElement(gt.Provider,{value:b},N.createElement(`object3D`,U({ref:u},l,{position:i,rotation:a,quaternion:s,scale:o}),n,v.map((e,t)=>N.createElement(W,U({key:t},e)))))});K.displayName=`RigidBody`;var vt=(0,N.memo)(e=>{let{children:t,type:n}=e,{physicsOptions:r}=H(),i=(0,N.useRef)(null),{options:a}=_t(),o=He(i,(0,N.useMemo)(()=>I(I(I({},r),a),{},{children:void 0,colliders:n}),[r,a]),!1);return N.createElement(`object3D`,{ref:i,userData:{r3RapierType:`MeshCollider`}},t,o.map((e,t)=>N.createElement(W,U({key:t},e))))});vt.displayName=`MeshCollider`;var yt=[`ref`],bt=[`children`,`instances`,`colliderNodes`,`position`,`rotation`,`quaternion`,`scale`],xt=(0,N.memo)(e=>{let{ref:t}=e,n=be(e,yt),r=Ye(t,[]),i=(0,N.useRef)(null),a=(0,N.useRef)(null),{children:o,instances:s,colliderNodes:c=[],position:l,rotation:u,quaternion:d,scale:f}=n,p=be(n,bt),m=He(i,I(I({},n),{},{children:void 0})),h=()=>{let e=a.current.children[0];if(e&&`isInstancedMesh`in e)return e};(0,N.useEffect)(()=>{let e=h();e?e.instanceMatrix.setUsage(A):console.warn(`InstancedRigidBodies expects exactly one child, which must be an InstancedMesh`)},[]);let g=(e,t)=>{let n=h();return n?I(I({},e),{},{getMatrix:e=>(n.getMatrixAt(t,e),e),setMatrix:e=>{n.setMatrixAt(t,e),n.instanceMatrix.needsUpdate=!0},meshType:`instancedMesh`}):e};return N.createElement(`object3D`,U({ref:i},p,{position:l,rotation:u,quaternion:d,scale:f}),N.createElement(`object3D`,{ref:a},o),s?.map((e,t)=>N.createElement(K,U({},p,e,{ref:e=>{r.current[t]=e},transformState:e=>g(e,t)}),N.createElement(N.Fragment,null,c.map((e,t)=>N.createElement(N.Fragment,{key:t},e)),m.map((e,t)=>N.createElement(W,U({key:t},e)))))))});xt.displayName=`InstancedRigidBodies`;var St=(e,t)=>(Ct(e)<<16)+(t===void 0?65535:Ct(t)),Ct=e=>[e].flat().reduce((e,t)=>e|1<<t,0),q=c(),wt=`#1f1d2b`,Tt={beach:{kind:`beach`,r:.4,restitution:.8,density:1.2,damping:.9},exercise:{kind:`exercise`,r:.45,restitution:.7,density:1.6,damping:.9},yarn:{kind:`yarn`,r:.33,restitution:.35,density:2.5,damping:1.3}},J=(e,t,n,r)=>({id:e,...Tt[t],start:{x:n,y:Tt[t].r+.02,z:r}}),Et={office:[J(`beach-ball`,`beach`,6.6,6.8),J(`exercise-ball`,`exercise`,9.8,8.6),J(`yarn-ball`,`yarn`,12.2,6.2)],lobby:[J(`beach-ball`,`beach`,-11,5.6),J(`exercise-ball`,`exercise`,-7.6,8.2),J(`yarn-ball`,`yarn`,-5.4,4.4)]},Dt=e=>Math.abs(e.x)>16||Math.abs(e.z)>12||e.y<-.5||e.y>6,Y=[`#ef476f`,`#ffffff`,`#ffd166`,`#ffffff`,`#118ab2`,`#ffffff`],X=null;function Ot(){if(X)return X;let e=new Uint8Array(Y.length*10*4);for(let t=0;t<10;t++)for(let n=0;n<Y.length;n++){let r=parseInt((t===0||t===9?`#ffffff`:Y[n]).slice(1),16),i=(t*Y.length+n)*4;e[i]=r>>16&255,e[i+1]=r>>8&255,e[i+2]=r&255,e[i+3]=255}return X=new u(e,Y.length,10,a),X.colorSpace=v,X.minFilter=s,X.magFilter=s,X.generateMipmaps=!1,X.needsUpdate=!0,X}var kt=`#8338ec`,At=`#ff70a6`,jt=[[0,0,0],[Math.PI/2,0,0],[.6,0,1.1],[-.7,.3,-1],[1.2,.9,.4]];function Mt({def:e}){let{r:t}=e;return e.kind===`beach`?(0,q.jsxs)(`mesh`,{castShadow:!0,material:g(`beach-ball`,Ot()),children:[(0,q.jsx)(`sphereGeometry`,{args:[t,24,16]}),(0,q.jsx)(p,{thickness:.02,color:wt})]}):e.kind===`exercise`?(0,q.jsxs)(`group`,{children:[(0,q.jsxs)(`mesh`,{castShadow:!0,material:w(kt),children:[(0,q.jsx)(`sphereGeometry`,{args:[t,24,16]}),(0,q.jsx)(p,{thickness:.02,color:wt})]}),(0,q.jsx)(`mesh`,{rotation:[Math.PI/2,0,0],material:w(x(kt,-.14)),children:(0,q.jsx)(`torusGeometry`,{args:[t*1.002,.018,6,40]})}),(0,q.jsx)(`mesh`,{position:[0,0,t],rotation:[Math.PI/2,0,0],material:w(`#f8f9fa`),children:(0,q.jsx)(`cylinderGeometry`,{args:[.045,.05,.04,12]})})]}):(0,q.jsxs)(`group`,{children:[(0,q.jsxs)(`mesh`,{castShadow:!0,material:w(At),children:[(0,q.jsx)(`sphereGeometry`,{args:[t,20,14]}),(0,q.jsx)(p,{thickness:.02,color:wt})]}),jt.map((e,n)=>(0,q.jsx)(`mesh`,{rotation:e,material:w(x(At,n%2?-.12:.08)),children:(0,q.jsx)(`torusGeometry`,{args:[t*1.005,.022,6,36]})},n)),(0,q.jsx)(`mesh`,{position:[t*.55,-t*.62,t*.45],rotation:[.4,0,.9],material:w(x(At,-.12)),children:(0,q.jsx)(`capsuleGeometry`,{args:[.022,.3,3,6]})})]})}var Nt=1/60,Z={building:0,doorway:1,pusher:2,toys:3},Pt=St(Z.doorway,[Z.toys]),Ft=St(Z.pusher,[Z.building,Z.toys]),It=St(Z.toys,[Z.building,Z.doorway,Z.pusher,Z.toys]),Lt=St(Z.building,[Z.pusher,Z.toys]),Q=T();function Rt({floor:e}){let t=(0,N.useMemo)(()=>e===`office`?y():d(),[e]);return(0,q.jsxs)(K,{type:`fixed`,colliders:!1,children:[(0,q.jsx)(G,{args:[17,.5,16],position:[0,-.5,2],friction:.8,restitution:.5,collisionGroups:Lt}),(0,q.jsx)(G,{args:[17,.5,16],position:[0,S+.5,2],collisionGroups:Lt}),(0,q.jsx)(G,{args:[(Q.maxX-Q.minX)/2,S/2,(Q.maxZ-Q.minZ)/2],position:[(Q.minX+Q.maxX)/2,S/2,(Q.minZ+Q.maxZ)/2],collisionGroups:Pt}),t.map((e,t)=>{let n=e.h??3.6;return(0,q.jsx)(G,{args:[(e.maxX-e.minX)/2,n/2,(e.maxZ-e.minZ)/2],position:[(e.minX+e.maxX)/2,n/2,(e.minZ+e.maxZ)/2],friction:.6,restitution:.5,collisionGroups:Lt},t)})]})}var zt={x:0,y:0,z:0},$={half:.62,y:.95,mass:4,maxSpeed:12,maxImpulse:600*Nt,teleport:1.5};function Bt(){let e=h(e=>e.camera),t=(0,N.useRef)(null),n=(0,N.useMemo)(()=>({x:0,y:$.y,z:0}),[]),r=(0,N.useMemo)(()=>({x:0,y:0,z:0}),[]),i=(0,N.useRef)({x:NaN,z:NaN});return Be(()=>{let a=t.current;if(!a)return;let{x:o,z:s}=e.position,c=i.current,l=o===c.x&&s===c.z;if(c.x=o,c.z=s,l&&a.isSleeping())return;let u=a.translation(),d=o-u.x,f=s-u.z,p=Math.hypot(d,f);if(p>$.teleport){n.x=o,n.z=s,a.setTranslation(n,!0),a.setLinvel(zt,!0);return}if(l&&p<.002){a.setLinvel(zt,!1);return}let m=$.maxImpulse*Math.min(1,Math.max(.1,(.7-p)/.4)),h=p>0?Math.min(1/Nt,$.maxSpeed/p):0,g=a.linvel();r.x=$.mass*(d*h-g.x),r.z=$.mass*(f*h-g.z);let _=Math.hypot(r.x,r.z);_>m&&(r.x*=m/_,r.z*=m/_),a.applyImpulse(r,!0)}),(0,q.jsx)(K,{ref:t,colliders:!1,position:[e.position.x,$.y,e.position.z],gravityScale:0,enabledTranslations:[!0,!1,!0],lockRotations:!0,children:(0,q.jsx)(ct,{args:[$.half,l],mass:$.mass,restitution:0,friction:.2,collisionGroups:Ft})})}var Vt={x:0,y:0,z:0,w:1};function Ht(e,t){e.setTranslation(t.start,!0),e.setRotation(Vt,!0),e.setLinvel(zt,!0),e.setAngvel(zt,!0)}function Ut({floor:e}){let t=Et[e],{world:n}=H(),r=(0,N.useRef)([]),i=(0,N.useMemo)(()=>t.map((e,t)=>e=>void(r.current[t]=e)),[t]);(0,N.useEffect)(()=>(E(()=>({bodies:n.bodies.len(),balls:t.map((e,t)=>{let n=r.current[t],i=n?n.translation():e.start;return{id:e.id,x:i.x,y:i.y,z:i.z,sleeping:!n||n.isSleeping()}})})),()=>E(null)),[n,t]);let a=(0,N.useRef)(0);return Ve((0,N.useCallback)(()=>{if(!(++a.current%15))for(let e=0;e<t.length;e++){let n=r.current[e];n&&!n.isSleeping()&&Dt(n.translation())&&Ht(n,t[e])}},[t])),t.map((e,t)=>(0,q.jsxs)(K,{ref:i[t],colliders:!1,position:[e.start.x,e.start.y,e.start.z],linearDamping:e.damping,angularDamping:e.damping,ccd:!0,userData:{toy:e.id},children:[(0,q.jsx)(st,{args:[e.r],restitution:e.restitution,friction:.7,density:e.density,collisionGroups:It}),(0,q.jsx)(Mt,{def:e})]},e.id))}function Wt({floor:e}){let t=D(e=>e.travel!==null);return(0,q.jsxs)(Je,{timeStep:Nt,paused:t,numSolverIterations:8,children:[(0,q.jsx)(Rt,{floor:e}),(0,q.jsx)(Bt,{}),(0,q.jsx)(Ut,{floor:e})]})}var Gt=(0,N.memo)(Wt);export{Gt as default};
@@ -1 +0,0 @@
1
- :root{--ink:#1f1d2b;--paper:#fffdf6;--muted:#6c7086;--good:#2dc653;--bad:#ef476f;--warn:#ffb703;--accent:#ff8a5b;--font:Fredoka, "Segoe UI", system-ui, sans-serif;--mono:"JetBrains Mono", Consolas, monospace;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}*{box-sizing:border-box}html,body,#root{height:100%;font-family:var(--font);color:var(--ink);background:#bfe3ff;margin:0;overflow:hidden}canvas{display:block}a{color:#3a56d4}kbd{border:2px solid var(--ink);min-width:1.6em;font-family:var(--font);text-align:center;background:#fff;border-bottom-width:4px;border-radius:6px;margin:0 .1em;padding:0 .4em;font-size:.85em;font-weight:600;line-height:1.5;display:inline-block}code{font-family:var(--mono);background:#0000000f;border-radius:5px;padding:1px 5px;font-size:.85em}.muted{color:var(--muted)}.small{font-size:.85rem}.spacer{flex:1}.grow{flex:1;min-width:0}.nowrap{white-space:nowrap}.btn{font-family:var(--font);border:2.5px solid var(--ink);color:var(--ink);box-shadow:0 3px 0 var(--ink);cursor:pointer;white-space:nowrap;background:#fff;border-radius:12px;padding:8px 14px;font-size:.95rem;font-weight:600;transition:transform 60ms,box-shadow 60ms}.btn:hover:not(:disabled){box-shadow:0 4px 0 var(--ink);transform:translateY(-1px)}.btn:active:not(:disabled){box-shadow:0 1px 0 var(--ink);transform:translateY(2px)}.btn:disabled{opacity:.45;cursor:not-allowed}.btn-good{background:#80ed99}.btn-bad{background:#ff8fa3}.btn-ghost{box-shadow:none;background:0 0;border-color:#1f1d2b59}.btn-small{box-shadow:0 2px 0 var(--ink);border-width:2px;border-radius:9px;padding:4px 10px;font-size:.82rem}.btn-ghost.btn-small{box-shadow:none}.btn-big{background:var(--accent);color:#fff;text-shadow:0 2px #0003;box-shadow:0 5px 0 var(--ink);border-radius:18px;padding:14px 32px;font-size:1.3rem}a.btn{text-decoration:none;display:inline-block}input,textarea,select{font-family:var(--font);color:var(--ink);background:#fff;border:2px solid #1f1d2b40;border-radius:10px;outline:none;width:100%;padding:8px 10px;font-size:.95rem}input:focus,textarea:focus,select:focus{border-color:var(--accent)}input[type=checkbox],input[type=radio]{width:auto;accent-color:var(--accent)}input[type=color]{width:42px;height:34px;padding:2px}textarea{resize:vertical}input.inline{background:0 0;border-color:#0000;padding:4px 6px}input.inline:hover,input.inline:focus{background:#fff;border-color:#1f1d2b40}.row{align-items:center;gap:8px;margin:6px 0;display:flex}.row.wrap{flex-wrap:wrap}.toggle{cursor:pointer;align-items:center;gap:6px;font-size:.92rem;display:inline-flex}.toggle.block{align-items:flex-start;margin:8px 0;display:flex}.pill,.chip{border:2px solid var(--ink);background:#fff;border-radius:999px;align-items:center;gap:4px;padding:3px 10px;font-size:.8rem;font-weight:600;display:inline-flex}.chip{color:var(--ink);border-width:1.5px;padding:1px 8px;font-size:.72rem;text-decoration:none}.chip-good,.pill-ok{background:#b9fbc0}.pill-bad{background:#ffc2d1}.pill-demo{background:#ffd166}.dot{border:2px solid var(--ink);border-radius:50%;width:12px;height:12px;display:inline-block}.agent-chip{align-items:center;gap:5px;font-size:.85rem;font-weight:600;display:inline-flex}.status{border:2px solid var(--ink);text-transform:uppercase;letter-spacing:.03em;border-radius:999px;padding:1px 9px;font-size:.75rem;font-weight:600;display:inline-block}.status-idle{background:#e9ecef}.status-preparing{background:#ffe066}.status-working{background:#74c0fc}.status-done{background:#8ce99a}.status-error{background:#ffa8a8}.status-stopped{background:#dee2e6}.status-installing,.status-starting{background:#ffe066}.status-running{background:#8ce99a}.status-unconfigured{background:#e9ecef}.hud{pointer-events:none;z-index:10;position:fixed;inset:0}.hud>*{pointer-events:auto}.hud-floor{background:var(--paper);border:3px solid var(--ink);box-shadow:0 4px 0 var(--ink);border-radius:18px;align-items:center;gap:12px;max-width:min(520px,100vw - 32px);padding:8px 16px 8px 8px;display:flex;position:absolute;top:16px;left:16px}.floor-num{background:var(--accent);border:3px solid var(--ink);color:#fff;text-shadow:0 2px #00000040;border-radius:12px;flex:none;place-items:center;width:46px;height:46px;font-size:1.5rem;font-weight:700;display:grid}.floor-name{text-overflow:ellipsis;white-space:nowrap;font-size:1.1rem;font-weight:700;overflow:hidden}.floor-sub{color:var(--muted);font-size:.85rem}.hud-status{flex-wrap:wrap;justify-content:flex-end;gap:6px;max-width:50vw;display:flex;position:absolute;top:16px;right:16px}.hud-banner{border:3px solid var(--ink);background:#ffe066;border-radius:14px;max-width:90vw;padding:8px 16px;font-weight:600;position:absolute;top:84px;left:50%;transform:translate(-50%)}.crosshair{border:2px solid var(--ink);pointer-events:none;background:#fff;border-radius:50%;width:8px;height:8px;margin:-4px 0 0 -4px;transition:transform .15s;position:absolute;top:50%;left:50%}.crosshair-hot{background:var(--accent);transform:scale(1.8)}.hud-hint{background:var(--paper);border:3px solid var(--ink);box-shadow:0 4px 0 var(--ink);white-space:nowrap;pointer-events:none;border-radius:14px;padding:8px 16px;font-size:1.05rem;font-weight:600;position:absolute;bottom:22%;left:50%;transform:translate(-50%)}.hud-resume{color:#fff;text-shadow:0 2px 0 var(--ink), 0 0 6px var(--ink);pointer-events:none;font-weight:600;position:absolute;top:calc(50% + 26px);left:50%;transform:translate(-50%)}.workers{background:var(--paper);border:3px solid var(--ink);width:min(360px,100vw - 32px);max-height:calc(100vh - 180px);box-shadow:0 4px 0 var(--ink);border-radius:18px;flex-direction:column;display:flex;position:absolute;top:60px;right:16px;overflow:hidden}.workers-closed{width:auto}.wk-head{font-family:var(--font);letter-spacing:.08em;text-transform:uppercase;color:var(--ink);cursor:pointer;text-align:left;background:0 0;border:0;justify-content:space-between;align-items:center;gap:12px;padding:8px 14px;font-size:.8rem;font-weight:600;display:flex}.wk-head b{color:#1e9e45}.wk-chevron{color:var(--muted);letter-spacing:0;text-transform:none}.wk-list{padding:0 6px 6px;overflow-y:auto}.wk-empty{color:var(--muted);padding:4px 10px 8px;font-size:.85rem}.wk-row{width:100%;font-family:var(--font);color:var(--ink);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:12px;grid-template-columns:auto minmax(0,1fr);align-items:center;gap:10px;padding:5px 8px 5px 26px;display:grid}.wk-row:hover{background:#1f1d2b0f}.wk-dot{border:2px solid var(--ink);border-radius:50%;width:12px;height:12px}.wk-main{flex-direction:column;min-width:0;display:flex}.wk-name{white-space:nowrap;text-overflow:ellipsis;font-size:.95rem;font-weight:600;overflow:hidden}.wk-task{color:var(--muted);margin-left:6px;font-size:.78rem;font-weight:500}.wk-line{gap:6px;min-width:0;display:flex}.wk-last{min-width:0;font-family:var(--mono);color:var(--muted);white-space:nowrap;text-overflow:ellipsis;flex:auto;font-size:.72rem;overflow:hidden}.wk-last-text{font-family:var(--font);color:var(--ink);font-size:.8rem}.wk-last-thinking{font-style:italic}.wk-last-error{color:var(--bad)}.wk-ago{color:var(--muted);flex:none;font-size:.72rem}.wk-group+.wk-group{margin-top:4px}.wk-group-head{width:100%;font-family:var(--font);letter-spacing:.05em;text-transform:uppercase;color:var(--muted);text-align:left;cursor:pointer;background:0 0;border:0;border-radius:10px;align-items:center;gap:8px;padding:6px 8px 2px;font-size:.75rem;font-weight:600;display:flex}.wk-group-head:hover{color:var(--ink)}.wk-group-arrow{width:10px}.wk-swatch{border:2px solid var(--ink);border-radius:4px;flex:none;width:12px;height:12px}.wk-group-name{text-overflow:ellipsis;white-space:nowrap;min-width:0;color:var(--ink);overflow:hidden}.wk-group-count{color:var(--ink);background:#1f1d2b14;border-radius:999px;margin-left:auto;padding:0 7px}@media (width<=700px){.workers{display:none}}.hud-help{pointer-events:none;background:#fffdf6d9;border-radius:12px;padding:6px 10px;font-size:.85rem;position:absolute;bottom:14px;left:16px}.stats{z-index:60;font:10px/1.2 var(--mono);color:#7cffb2;white-space:nowrap;pointer-events:none;background:#15151fcc;border-bottom-right-radius:8px;padding:1px 8px;position:fixed;top:0;left:0}.fade{opacity:0;pointer-events:none;background:#15151f;place-items:center;transition:opacity .6s;display:grid;position:absolute;inset:0}.fade-in{opacity:1}.fade-label{color:#7cffb2;font-size:3rem;font-weight:700}.toasts{z-index:40;flex-direction:column;align-items:flex-end;gap:8px;max-width:min(420px,100vw - 32px);display:flex;position:absolute;bottom:16px;right:16px}.toast{background:var(--paper);border:3px solid var(--ink);box-shadow:0 4px 0 var(--ink);cursor:pointer;border-radius:14px;padding:10px 14px;font-weight:500;animation:.25s ease-out pop}.toast-success{background:#d8f9df}.toast-error{background:#ffd6de}@keyframes pop{0%{opacity:0;transform:translateY(12px)scale(.95)}}.start{z-index:50;-webkit-backdrop-filter:blur(6px);backdrop-filter:blur(6px);background:radial-gradient(circle at 30% 20%,#ffd6a5bf,#74c0fccc);place-items:center;padding:16px;display:grid;position:fixed;inset:0}.start-card{background:var(--paper);border:4px solid var(--ink);width:min(640px,100%);box-shadow:0 8px 0 var(--ink);text-align:center;border-radius:28px;max-height:calc(100vh - 32px);padding:28px 32px;overflow:auto}.start-logo{color:var(--accent);font-size:4rem;line-height:1;animation:12s linear infinite spin;display:inline-block}@keyframes spin{to{transform:rotate(360deg)}}.start h1{margin:6px 0 4px;font-size:2.6rem}.start-tag{color:var(--muted);margin:0 0 16px;font-size:1.1rem}.start-list{text-align:left;gap:8px;margin:0 0 22px;padding:0;list-style:none;display:grid}.start-meta{color:var(--muted);flex-wrap:wrap;justify-content:center;align-items:center;gap:10px;margin-top:16px;display:flex}.overlay{z-index:30;background:#15151f59;place-items:center;padding:16px;animation:.15s ease-out fadein;display:grid;position:fixed;inset:0}@keyframes fadein{0%{opacity:0}}.panel{background:var(--paper);border:4px solid var(--ink);width:min(640px,100%);max-height:calc(100vh - 32px);box-shadow:0 8px 0 var(--ink);border-radius:24px;flex-direction:column;animation:.2s ease-out pop;display:flex;overflow:hidden}.panel-wide{width:min(1320px,100%);height:calc(100vh - 32px)}.panel-head{background:var(--accent);border-bottom:4px solid var(--ink);align-items:center;gap:10px;padding:12px 16px;display:flex}.panel-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-size:1.25rem;font-weight:700;overflow:hidden}.panel-title a{color:var(--ink)}.panel-x{border:3px solid var(--ink);cursor:pointer;background:#fff;border-radius:12px;flex:none;width:36px;height:36px;font-weight:700}.panel-body{flex-direction:column;flex:1;min-height:0;padding:16px;display:flex;overflow:auto}.help h3{margin:14px 0 4px}.help p{margin:0;line-height:1.55}.mouse-settings{border:2px solid var(--ink);background:#fff8ef;border-radius:12px;flex-direction:column;gap:8px;margin-top:10px;padding:10px 12px;display:flex}.mouse-sens{align-items:center;gap:10px;font-size:.92rem;display:flex}.mouse-sens input[type=range]{max-width:260px;accent-color:var(--accent,#ff8a5b);flex:1}.mouse-sens b{font-variant-numeric:tabular-nums;min-width:3.4em}.sound{gap:10px 18px}.sound-volume{flex:200px;align-items:center;gap:8px;max-width:340px;display:flex}.sound-volume input[type=range]{accent-color:var(--accent);background:0 0;border:0;flex:1;padding:0}.sound-pct{text-align:right;font-variant-numeric:tabular-nums;min-width:3.2em}.elevator{flex-direction:column;gap:10px;display:flex}.floor-btn{text-align:left;border:3px solid var(--ink);box-shadow:0 4px 0 var(--ink);cursor:pointer;font-family:var(--font);background:#fff;border-radius:16px;grid-template-rows:auto auto;grid-template-columns:54px 1fr;align-items:center;column-gap:12px;padding:10px;display:grid}.floor-btn:hover{transform:translateY(-1px)}.floor-btn-here{outline:3px dashed var(--accent);outline-offset:3px;background:#fff4e6}.floor-btn-num{border:3px solid var(--ink);background:var(--accent);color:#fff;text-shadow:0 2px #00000040;border-radius:50%;grid-row:span 2;place-items:center;width:54px;height:54px;font-size:1.5rem;font-weight:700;display:grid}.floor-btn-name{text-overflow:ellipsis;white-space:nowrap;font-size:1.1rem;font-weight:700;overflow:hidden}.floor-btn-meta{color:var(--muted);font-size:.85rem}.term-title{align-items:center;gap:10px;display:flex}.avatar{border:3px solid var(--ink);color:#fff;text-shadow:0 1px #0000004d;border-radius:50%;flex:none;place-items:center;width:34px;height:34px;font-weight:700;display:grid}.term-meta{flex-wrap:wrap;align-items:center;gap:6px 14px;margin-bottom:10px;font-size:.92rem;display:flex}.term-error{word-break:break-word;background:#ffe3e3;border:2px solid #ff8787;border-radius:10px;margin-bottom:8px;padding:6px 10px;font-size:.88rem}.term-split{flex:1;grid-template-columns:1fr;gap:12px;min-height:0;display:grid}.term-split-2{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr)}.term{color:#e8e8f2;border:3px solid var(--ink);font-family:var(--mono);background:#1b1b29;border-radius:14px;min-height:200px;padding:12px 14px;font-size:13px;line-height:1.5;overflow:auto}.term-line{white-space:pre-wrap;word-break:break-word}.term-tool{color:#8be9fd}.term-result{color:#8d8da8}.term-system{color:#bd93f9}.term-error.term-line,.term-line.term-error{color:#ff6b6b;font-size:inherit;background:0 0;border:none;margin:0;padding:0}.term-manager{color:#ffb86c;font-weight:600}.term-done{color:#50fa7b;font-weight:600}.term-thinking{color:#f1fa8c;font-style:italic}.term-spin{color:#ff9e64;animation:1.4s ease-in-out infinite blink}@keyframes blink{50%{opacity:.55}}.browser{flex-direction:column;gap:6px;min-height:0;display:flex}.browser-bar{border:3px solid var(--ink);text-overflow:ellipsis;white-space:nowrap;background:#e9ecf2;border-bottom:none;border-radius:14px 14px 0 0;padding:6px 12px;font-size:.85rem;overflow:hidden}.browser-view{border:3px solid var(--ink);background:#fff;border-radius:0 0 14px 14px;flex:1;min-height:0;margin-top:-6px;overflow:auto}.browser-view img{width:100%;display:block}.term-input{gap:8px;margin-top:12px;display:flex}.term-actions{flex-wrap:wrap;align-items:center;gap:8px;margin-top:10px;display:flex}.term-actions select{width:auto;max-width:420px}.kanban-toolbar{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:12px;display:flex}.issue-form{background:#fff;border:3px dashed #1f1d2b4d;border-radius:16px;flex-direction:column;gap:8px;margin-bottom:12px;padding:12px;display:flex}.issue-form .row select{flex:1}.kanban{flex:1;grid-template-columns:repeat(4,minmax(220px,1fr));gap:12px;min-height:0;display:grid;overflow-x:auto}.kcol{border:3px solid var(--ink);background:#f4f1ea;border-radius:18px;flex-direction:column;min-height:0;display:flex;overflow:hidden}.kcol-head{border-bottom:3px solid var(--ink);padding:10px 12px;font-weight:700}.kcol-backlog .kcol-head{background:#ffd166}.kcol-progress .kcol-head{background:#4cc9f0}.kcol-review .kcol-head{background:#80ed99}.kcol-qa .kcol-head{background:#ff9f68}.kanban-5{grid-template-columns:repeat(5,minmax(210px,1fr))}.kcol-merged .kcol-head{background:#c77dff}.count{border:2px solid var(--ink);text-align:center;background:#fff;border-radius:999px;min-width:24px;padding:0 6px;font-size:.8rem;display:inline-block}.kcol-body{flex-direction:column;gap:8px;padding:10px;display:flex;overflow:auto}.kempty{padding:8px}.kcard{border:2.5px solid var(--ink);background:#fff;border-radius:12px;padding:8px 10px;box-shadow:0 3px #1f1d2bcc}.kcard-warn{background:#fff3d6}.kcard-bad{background:#ffe0e6}.kcard-good{background:#e3fbe7}.kcard-title{font-size:.92rem;font-weight:500;line-height:1.35}.kcard-title a{font-weight:700}.kcard-foot{align-items:center;gap:6px;margin-top:6px;display:flex}.kcard-foot select{width:auto;margin-left:auto;padding:3px 6px;font-size:.82rem}.kcard-foot-wrap{flex-wrap:wrap}.panel-app{width:100%;max-width:1600px}.panel-app .panel-body{padding:12px 16px}.app-title{align-items:center;gap:10px;min-width:0;display:flex}.app-title-name,.app-title-ref{text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.app-title-name{flex:0 auto}.app-title-ref{color:var(--ink);opacity:.75;flex:0 2 auto;font-weight:500}.app-title .status{flex:none}.app-toolbar{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:10px;display:flex}.app-ref{flex:0 auto;width:260px;min-width:0;padding:5px 8px;font-size:.88rem}.seg{border:2px solid var(--ink);box-shadow:0 2px 0 var(--ink);border-radius:9px;display:inline-flex;overflow:hidden}.seg-btn{font-family:var(--font);color:var(--ink);cursor:pointer;white-space:nowrap;background:#fff;border:0;padding:4px 10px;font-size:.82rem;font-weight:600}.seg-btn+.seg-btn{border-left:2px solid var(--ink)}.seg-btn.seg-on{background:var(--accent)}.seg-btn:focus-visible{outline-offset:-3px;outline:3px solid #3a56d4}.app-stage{border:3px solid var(--ink);background:repeating-linear-gradient(45deg,#efeadf 0 12px,#f6f2e9 12px 24px);border-radius:14px;flex:1;justify-content:center;min-height:280px;display:flex;position:relative;overflow:hidden}.app-frame{background:#fff;border:0;width:100%;height:100%;display:block}.app-stage-phone .app-frame{border-left:3px solid var(--ink);border-right:3px solid var(--ink);width:396px;max-width:100%}.app-frame-hint{border:2.5px solid var(--ink);width:min(560px,100% - 24px);box-shadow:0 3px 0 var(--ink);background:#fff3d6;border-radius:12px;padding:10px 14px;font-size:.9rem;line-height:1.4;position:absolute;bottom:16px;left:50%;transform:translate(-50%)}.app-foot{margin:8px 0 0;line-height:1.4}.app-state{text-align:center;background:#fff;border:3px dashed #1f1d2b4d;border-radius:14px;flex-direction:column;flex:1;align-items:center;gap:10px;min-height:0;padding:24px 16px;display:flex;overflow:auto}.app-state h3{margin:0}.app-state p{max-width:560px;margin:0}.app-state-left{text-align:left;align-items:stretch}.app-state-left p{max-width:none}.app-state-icon{font-size:2.6rem;line-height:1}.app-spin{animation:2.4s linear infinite spin}.app-steps{text-align:left;color:var(--muted);margin:0;padding:0;font-weight:500;list-style:none}.app-steps li{padding:2px 0}.app-step-done{color:#1b873a}.app-step-now{color:var(--ink);font-weight:700}.app-log{text-align:left;white-space:pre-wrap;word-break:break-word;width:100%;max-width:900px;min-height:0;max-height:320px;margin:0;font-size:12px}.app-state-left .app-log{flex:0 auto;max-width:none}.app-settings{width:min(640px,100%)}.preview-form{flex-direction:column;gap:2px;display:flex}.preview-form input,.preview-form textarea{font-family:var(--mono);font-size:.85rem}.preview-form p{margin:2px 0 4px}.preview-details summary{cursor:pointer;font-weight:600}.preview-details summary code{word-break:break-all;font-weight:400}.preview-details[open] summary{margin-bottom:6px}.preview-details .status{font-size:.68rem}@media (width<=520px){.panel-app .panel-body{padding:10px}.app-ref{flex:100%;max-width:none}.app-toolbar .spacer,.app-title-ref{display:none}}.tabs{flex-wrap:wrap;gap:6px;margin-bottom:12px;display:flex}.tab{font-family:var(--font);border:2.5px solid var(--ink);cursor:pointer;background:#fff;border-radius:12px;padding:8px 14px;font-size:.95rem;font-weight:600}.tab-on{background:var(--accent);color:#fff;text-shadow:0 1px #00000040;box-shadow:0 3px 0 var(--ink)}.tab-body{flex:1;min-height:0;overflow:auto}.tab-grid{grid-template-columns:minmax(0,1.2fr) minmax(0,1fr);align-items:start;gap:16px;display:grid}@media (width<=900px){.tab-grid{grid-template-columns:1fr}.kanban,.kanban-5{grid-template-columns:repeat(5,250px)}.term-split-2{grid-template-rows:minmax(200px,1fr) auto;grid-template-columns:1fr}}.section{margin:0 0 10px}.card{border:3px solid var(--ink);background:#fff;border-radius:18px;flex-direction:column;gap:6px;margin-bottom:12px;padding:12px 14px;display:flex}.card h3{margin:0 0 4px}.floor-card{border-left:12px solid var(--accent)}.floor-badge{background:var(--accent);border:2.5px solid var(--ink);color:#fff;text-shadow:0 1px #00000040;border-radius:10px;flex:none;place-items:center;width:36px;height:36px;font-weight:700;display:grid}.repo-list{border:2px solid #1f1d2b1f;border-radius:12px;flex-direction:column;gap:4px;max-height:260px;padding:6px;display:flex;overflow:auto}.repo-list.tall{max-height:520px}.repo-row{border-radius:10px;justify-content:space-between;align-items:center;gap:10px;padding:6px 8px;display:flex}.repo-row:hover{background:#fff4e6}.repo-row select{width:auto}.links{font-size:.85rem}.team{border-collapse:collapse;width:100%}.team td{vertical-align:middle;border-top:1.5px solid #1f1d2b14;padding:4px 6px}.team select{width:auto;padding:4px 6px}.field{flex-direction:column;gap:4px;margin:6px 0;font-size:.9rem;font-weight:600;display:flex}.wizard{text-align:left;width:min(680px,100%)}.wizard h1,.wizard .start-logo,.wizard .start-tag,.wizard h2,.wizard-icon{text-align:center}.wizard h2{margin:4px 0 6px;font-size:1.9rem}.wizard-icon{font-size:3rem;line-height:1.1}.wizard .start-logo{width:fit-content;margin:0 auto;display:block}.wizard .btn-big{margin:0 auto;display:block}.wizard-steps{justify-content:center;gap:8px;margin-bottom:10px;display:flex}.wizard-dot{border:2px solid var(--ink);background:#fff;border-radius:50%;width:12px;height:12px}.wizard-dot-done{background:#80ed99}.wizard-dot-on{background:var(--accent);transform:scale(1.25)}.wizard-nav{align-items:center;gap:10px;margin-top:18px;display:flex}.wizard-done{text-align:center;padding:10px 0}.wizard-done p{margin:6px 0}.ceo-setup{align-items:center;gap:18px;display:flex}.ceo-portrait{flex:none;width:120px;height:120px}.swatch{border:2.5px solid var(--ink);cursor:pointer;border-radius:50%;width:24px;height:24px;padding:0}.swatch-on{box-shadow:0 0 0 3px #fff, 0 0 0 5px var(--ink)}.picker{flex-direction:column;gap:8px;display:flex}.picker-tabs{flex-wrap:wrap;gap:6px;display:flex}.picker-tab{font-family:var(--font);border:2.5px solid var(--ink);cursor:pointer;white-space:nowrap;background:#fff;border-radius:12px;flex:1;padding:8px 10px;font-size:.88rem;font-weight:600}.picker-tab-on{background:var(--accent);color:#fff;text-shadow:0 1px #00000040;box-shadow:0 3px 0 var(--ink)}.picker-mode{flex-direction:column;gap:6px;display:flex}.picker-mode .row{margin:0}.folder-list{max-height:240px}.chip-warn{background:#fff3bf}.tour{z-index:45;background:var(--paper);border:3px solid var(--ink);border-left:10px solid var(--accent);width:min(390px,100vw - 32px);box-shadow:0 5px 0 var(--ink);border-radius:18px;padding:10px 14px;animation:.25s ease-out pop;position:fixed;bottom:16px;left:16px}.tour-cheer{border-left-color:var(--good);background:#eafbee}.tour-head{align-items:center;display:flex}.tour-count{color:var(--muted);font-size:.78rem;font-weight:700}.tour-title{margin:2px 0 4px;font-size:1.1rem;font-weight:700}.tour-body{font-size:.9rem;line-height:1.5}.tour-nav{align-items:center;margin-top:8px;display:flex}.ceo-term{flex:none;height:340px}.job-brief{margin:0 0 8px}.job-brief summary{cursor:pointer;font-weight:600}.job-brief .md{margin-top:4px;line-height:1.45}.confirm-overlay{z-index:60;background:#15151f73;place-items:center;padding:16px;animation:.12s ease-out fadein;display:grid;position:fixed;inset:0}.confirm{background:var(--paper);border:4px solid var(--ink);width:min(440px,100%);box-shadow:0 8px 0 var(--ink);text-align:center;border-radius:24px;padding:20px 22px 14px;animation:.18s ease-out pop}.confirm-danger{border-top:12px solid var(--bad)}.confirm-warn{border-top:12px solid var(--warn)}.confirm-good{border-top:12px solid var(--good)}.confirm-icon{font-size:2.4rem;line-height:1}.confirm h3{margin:8px 0 6px;font-size:1.25rem}.confirm-body{color:#4a4e69;margin-bottom:4px;line-height:1.5}.confirm-actions{justify-content:center;gap:10px;margin-top:14px;display:flex}.confirm-keys{margin-top:10px}.btn-warn{background:#ffd166}.badge{background:var(--bad);color:#fff;border:2px solid var(--ink);border-radius:999px;place-items:center;min-width:20px;height:20px;padding:0 6px;font-size:.72rem;font-weight:700;line-height:1;display:inline-grid}.toasts{bottom:88px}.phone-btn{font-family:var(--font);color:var(--ink);background:var(--paper);border:3px solid var(--ink);box-shadow:0 4px 0 var(--ink);cursor:pointer;border-radius:18px;align-items:center;gap:8px;padding:6px 14px 6px 8px;font-size:.9rem;font-weight:600;display:flex;position:absolute;bottom:16px;right:16px}.phone-btn:hover{transform:translateY(-1px)}.phone-btn-icon{font-size:1.7rem;line-height:1;display:inline-block}.phone-btn-ring .phone-btn-icon{animation:2.4s ease-in-out infinite ring}@keyframes ring{0%,72%,to{transform:rotate(0)}76%{transform:rotate(-16deg)}80%{transform:rotate(14deg)}84%{transform:rotate(-10deg)}88%{transform:rotate(8deg)}}.phone-btn-badge{position:absolute;top:-8px;left:26px}.phone-overlay{background:#15151f47;place-items:end;padding:16px 24px}.phone{border:4px solid var(--ink);width:min(400px,100%);height:min(760px,100vh - 32px);box-shadow:0 10px 0 var(--ink), inset 0 0 0 3px #3b3f5c;background:#23263a;border-radius:38px;flex-direction:column;padding:10px 10px 8px;animation:.22s ease-out slideup;display:flex}@keyframes slideup{0%{opacity:0;transform:translateY(40px)}}.phone-status{color:#e9e9f5;justify-content:space-between;align-items:center;padding:2px 16px 8px;font-size:.8rem;font-weight:600;display:flex}.phone-notch{background:#15151f;border-radius:999px;width:90px;height:18px}.phone-screen{background:var(--paper);border-radius:26px 26px 12px 12px;flex-direction:column;flex:1;min-height:0;display:flex;overflow:hidden}.phone-tabs{gap:4px;padding:8px 4px 2px;display:flex}.phone-tab{color:#b8bbd6;font-family:var(--font);cursor:pointer;background:0 0;border:none;border-radius:14px;flex-direction:column;flex:1;align-items:center;gap:2px;padding:6px 4px;font-size:.78rem;font-weight:600;display:flex}.phone-tab-on{color:#fff;background:#3b3f5c}.phone-tab-icon{font-size:1.3rem;position:relative}.badge-dot{min-width:18px;height:18px;font-size:.65rem;position:absolute;top:-6px;right:-14px}.phone-hint{text-align:center;color:#8e92b3;padding-top:4px;font-size:.72rem}.phone-hint kbd{font-size:.7rem}.phone-scroll{flex:1;padding:12px;overflow:auto}.phone-h{align-items:center;gap:6px;margin:10px 2px 8px;font-size:1rem;display:flex}.phone-h:first-child{margin-top:0}.phone-empty{padding:10px 6px;line-height:1.5}.linkish{color:#3a56d4;font-family:var(--font);cursor:pointer;text-align:left;background:0 0;border:none;padding:0;font-weight:600}.phone-chat{flex-direction:column;flex:1;min-height:0;display:flex}.chat-head{background:#fff;border-bottom:2px solid #1f1d2b14;align-items:center;gap:10px;padding:10px 12px;display:flex}.presence-busy{color:#d9480f;font-weight:600}.chat-log{background:#f4f1ea;flex-direction:column;flex:1;gap:8px;padding:10px;display:flex;overflow:auto}.bubble-row{display:flex}.bubble-row-me{justify-content:flex-end}.bubble{border:2px solid var(--ink);border-radius:16px;max-width:86%;padding:8px 11px 5px;font-size:.9rem;line-height:1.4}.bubble-them{background:#fff;border-bottom-left-radius:4px}.bubble-me{background:#ffd6a5;border-bottom-right-radius:4px}.bubble-from{color:var(--muted);font-size:.72rem;font-weight:700}.bubble-text{white-space:pre-wrap;overflow-wrap:anywhere}.bubble:has(.md pre,.md-table){width:86%}.md{overflow-wrap:anywhere;min-width:0}.md>:first-child{margin-top:0}.md>:last-child{margin-bottom:0}.md p,.md ul,.md ol,.md pre,.md blockquote,.md .md-table{margin:0 0 .55em}.md ul,.md ol{padding-left:1.3em}.md li>ul,.md li>ol{margin:.15em 0 .2em}.md li+li{margin-top:.15em}.md li>p{margin:0}.md h1,.md h2,.md h3,.md h4,.md h5,.md h6{margin:.7em 0 .3em;font-size:1em;line-height:1.25}.md h1{font-size:1.15em}.md h2{font-size:1.08em}.md h5,.md h6{color:var(--muted)}.md a{color:#3a56d4;overflow-wrap:anywhere;font-weight:600}.md a:focus-visible{outline:2px solid var(--accent);outline-offset:1px;border-radius:3px}.md-deadlink{-webkit-text-decoration:underline dotted;text-decoration:underline dotted}.md code{font-family:var(--mono);background:#1f1d2b14;border-radius:5px;padding:.1em .3em;font-size:.86em}.md pre,.md-table{contain:inline-size;overflow-x:auto}.md pre{background:#1f1d2b14;border-radius:8px;padding:7px 9px;line-height:1.35}.md pre code{white-space:pre;overflow-wrap:normal;background:0 0;padding:0}.md blockquote{color:#4a4e69;border-left:3px solid #1f1d2b40;padding:0 0 0 9px}.md hr{border:none;border-top:1.5px solid #1f1d2b26;margin:.6em 0}.md table{border-collapse:collapse;font-size:.9em}.md th,.md td{text-align:left;overflow-wrap:normal;border:1px solid #1f1d2b33;padding:3px 7px}.md th{background:#1f1d2b0f}.md td code{white-space:nowrap}.md input[type=checkbox]{vertical-align:middle;margin:0 4px 0 0}.md li:has(>input[type=checkbox]){margin-left:-1.1em;list-style:none}.bubble-time{text-align:right;color:var(--muted);margin-top:2px;font-size:.65rem}.bubble-office{text-align:center;color:#4a4e69;background:#1f1d2b12;border-radius:999px;align-self:center;max-width:92%;padding:3px 10px;font-size:.76rem}.bubble-typing{gap:4px;padding:10px 12px;display:flex}.bubble-typing span{background:var(--muted);border-radius:50%;width:7px;height:7px;animation:1.2s infinite typing}.bubble-typing span:nth-child(2){animation-delay:.15s}.bubble-typing span:nth-child(3){animation-delay:.3s}@keyframes typing{0%,60%,to{opacity:.5;transform:translateY(0)}30%{opacity:1;transform:translateY(-4px)}}.quick{background:#fff;gap:6px;padding:6px 10px 0;display:flex;overflow-x:auto}.quick-chip{font-family:var(--font);cursor:pointer;background:#fff;border:1.5px solid #1f1d2b4d;border-radius:999px;flex:none;padding:4px 10px;font-size:.75rem;font-weight:600}.quick-chip:hover{border-color:var(--accent)}.chat-input{background:#fff;gap:6px;padding:8px 10px 10px;display:flex}.resume{border:3px solid var(--ink);color:var(--ink);background:#fff;border-radius:16px;flex-direction:column;gap:6px;margin-bottom:10px;padding:10px 12px;display:flex}.bubble .resume{border-width:2px;margin:6px 0 2px}.resume-letgo{background:#fff4f2}.resume-hot{box-shadow:0 0 0 4px var(--accent)}.resume-done{opacity:.75}.resume-head{align-items:center;gap:10px;display:flex}.resume-name{font-size:1.05rem;font-weight:700}.resume-title{color:var(--muted);font-size:.85rem;font-weight:600}.resume-meta{flex-wrap:wrap;align-items:center;gap:6px;display:flex}.resume-reason{border-left:3px solid #1f1d2b33;padding-left:9px;font-size:.88rem;line-height:1.45}.resume-brief{background:#f6f3ec;border-radius:10px;margin:0;padding:8px;font-size:.82rem;line-height:1.45}.tiles{grid-template-columns:repeat(3,1fr);gap:8px;display:grid}.tile{border:2.5px solid var(--ink);text-align:center;background:#fff;border-radius:14px;padding:8px}.tile-value{white-space:nowrap;font-size:1.15rem;font-weight:700}.tile-label{color:var(--muted);font-size:.7rem;font-weight:600}.report{flex-direction:column;gap:6px;margin:0;padding:0;list-style:none;display:flex}.report li{background:#fff;border:2px solid #1f1d2b1f;border-radius:12px;align-items:flex-start;gap:8px;padding:7px 10px;font-size:.86rem;line-height:1.4;display:flex}.report li.report-good{background:#e7fbe9;border-color:#80ed99}.report li.report-warn{background:#fff4d6;border-color:#ffd166}.proj{border:2.5px solid var(--ink);border-left:10px solid var(--accent);background:#fff;border-radius:14px;margin-bottom:8px;padding:6px 10px 8px}.proj .row{margin:2px 0}.proj .floor-badge{width:30px;height:30px}.proj-name{text-overflow:ellipsis;white-space:nowrap;display:block;overflow:hidden}.proj-sum{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.proj-stats{flex-wrap:wrap;gap:10px;font-weight:600;display:flex}.proj-ready{color:#1b873a}@media (width<=520px){.phone-overlay{place-items:center;padding:8px}.phone-btn-label{display:none}}