@iroaxel/arcena 4.3.157

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js ADDED
@@ -0,0 +1,3504 @@
1
+ #!/usr/bin/env -S node --max-old-space-size=8192 --expose-gc
2
+ import { createRequire as __createRequire } from "node:module"; const require = __createRequire(import.meta.url);
3
+ import {
4
+ ActivityIndicator,
5
+ AnimationProvider,
6
+ AssistantMessage,
7
+ Box_default,
8
+ CompactionDone,
9
+ CompactionSpinner,
10
+ GGBoss,
11
+ InputArea,
12
+ MODELS,
13
+ MessageResponse,
14
+ ModelSelector,
15
+ SelectList,
16
+ Static,
17
+ StreamingArea,
18
+ TerminalSizeProvider,
19
+ Text,
20
+ ThemeContext,
21
+ ToolExecution,
22
+ ToolUseLoader,
23
+ UserMessage,
24
+ bossStore,
25
+ closeLogger,
26
+ getAppPaths,
27
+ getBossState,
28
+ getContextWindow,
29
+ getSplashAudioDurationMs,
30
+ initLogger,
31
+ loadSettings,
32
+ loadTheme,
33
+ log,
34
+ playSplashAudio,
35
+ render_default,
36
+ require_jsx_runtime,
37
+ require_react,
38
+ saveSettings,
39
+ subscribeToBossStore,
40
+ tasksStore,
41
+ useAnimationActive,
42
+ useAnimationTick,
43
+ useBossState,
44
+ useDoublePress,
45
+ useTasksState,
46
+ useTerminalSize,
47
+ useTheme,
48
+ use_app_default,
49
+ use_input_default,
50
+ use_stdout_default
51
+ } from "./chunk-OWJSBNAJ.js";
52
+ import "./chunk-KZJPLEZU.js";
53
+ import {
54
+ source_default
55
+ } from "./chunk-YKKUCYNE.js";
56
+ import {
57
+ __toESM,
58
+ init_esm_shims
59
+ } from "./chunk-YNWFCUMR.js";
60
+
61
+ // src/cli.ts
62
+ init_esm_shims();
63
+ import path5 from "path";
64
+
65
+ // src/links.ts
66
+ init_esm_shims();
67
+ import fs from "fs/promises";
68
+ import path from "path";
69
+ function getLinksPath() {
70
+ return path.join(getAppPaths().agentDir, "boss", "links.json");
71
+ }
72
+ async function loadLinks() {
73
+ try {
74
+ const content = await fs.readFile(getLinksPath(), "utf-8");
75
+ const parsed = JSON.parse(content);
76
+ return { projects: parsed.projects ?? [] };
77
+ } catch {
78
+ return { projects: [] };
79
+ }
80
+ }
81
+ async function saveLinks(links) {
82
+ const p = getLinksPath();
83
+ await fs.mkdir(path.dirname(p), { recursive: true, mode: 448 });
84
+ await fs.writeFile(p, JSON.stringify(links, null, 2), "utf-8");
85
+ }
86
+
87
+ // src/link-command.tsx
88
+ init_esm_shims();
89
+ var import_react2 = __toESM(require_react(), 1);
90
+
91
+ // src/discover.ts
92
+ init_esm_shims();
93
+ import fs2 from "fs/promises";
94
+ import { createReadStream } from "fs";
95
+ import readline from "readline";
96
+ import os from "os";
97
+ import path2 from "path";
98
+ async function discoverProjects() {
99
+ const [gg, cc, cx] = await Promise.all([
100
+ discoverGgcoderProjects(),
101
+ discoverClaudeProjects(),
102
+ discoverCodexProjects()
103
+ ]);
104
+ const byPath = /* @__PURE__ */ new Map();
105
+ for (const p of [...gg, ...cc, ...cx]) {
106
+ const existing = byPath.get(p.path);
107
+ if (!existing) {
108
+ byPath.set(p.path, p);
109
+ continue;
110
+ }
111
+ byPath.set(p.path, {
112
+ name: existing.name,
113
+ path: existing.path,
114
+ lastActiveMs: Math.max(existing.lastActiveMs, p.lastActiveMs),
115
+ lastActiveDisplay: "",
116
+ // recomputed below
117
+ sources: mergeSources(existing.sources, p.sources)
118
+ });
119
+ }
120
+ const merged = Array.from(byPath.values()).map((p) => ({
121
+ ...p,
122
+ lastActiveDisplay: formatRelativeTime(p.lastActiveMs)
123
+ }));
124
+ merged.sort((a, b) => b.lastActiveMs - a.lastActiveMs);
125
+ return merged;
126
+ }
127
+ var SOURCE_ORDER = {
128
+ arcicoder: 0,
129
+ "claude-code": 1,
130
+ codex: 2
131
+ };
132
+ function mergeSources(a, b) {
133
+ const set = /* @__PURE__ */ new Set([...a, ...b]);
134
+ return Array.from(set).sort((x, y) => SOURCE_ORDER[x] - SOURCE_ORDER[y]);
135
+ }
136
+ async function discoverGgcoderProjects() {
137
+ const sessionsDir = getAppPaths().sessionsDir;
138
+ let entries;
139
+ try {
140
+ entries = await fs2.readdir(sessionsDir);
141
+ } catch {
142
+ return [];
143
+ }
144
+ const results = [];
145
+ for (const entry of entries) {
146
+ const dir = path2.join(sessionsDir, entry);
147
+ const mtime = await maxJsonlMtime(dir);
148
+ if (mtime === null) continue;
149
+ const decoded = "/" + entry.replace(/_/g, "/");
150
+ if (!await isDirectory(decoded)) continue;
151
+ results.push({
152
+ name: path2.basename(decoded),
153
+ path: decoded,
154
+ lastActiveMs: mtime,
155
+ lastActiveDisplay: formatRelativeTime(mtime),
156
+ sources: ["arcicoder"]
157
+ });
158
+ }
159
+ return results;
160
+ }
161
+ async function discoverClaudeProjects() {
162
+ const projectsDir = path2.join(os.homedir(), ".claude", "projects");
163
+ let entries;
164
+ try {
165
+ entries = await fs2.readdir(projectsDir);
166
+ } catch {
167
+ return [];
168
+ }
169
+ const results = await Promise.all(
170
+ entries.map(async (entry) => {
171
+ const dir = path2.join(projectsDir, entry);
172
+ const mtime = await maxJsonlMtime(dir);
173
+ if (mtime === null) return null;
174
+ const cwd = await readFirstFromJsonlDir(dir, claudeCwdExtractor) ?? fallbackDashDecode(entry);
175
+ if (!cwd) return null;
176
+ if (!await isDirectory(cwd)) return null;
177
+ return {
178
+ name: path2.basename(cwd),
179
+ path: cwd,
180
+ lastActiveMs: mtime,
181
+ lastActiveDisplay: formatRelativeTime(mtime),
182
+ sources: ["claude-code"]
183
+ };
184
+ })
185
+ );
186
+ return results.filter((p) => p !== null);
187
+ }
188
+ async function discoverCodexProjects() {
189
+ const sessionsDir = path2.join(os.homedir(), ".codex", "sessions");
190
+ if (!await isDirectory(sessionsDir)) return [];
191
+ const files = await collectJsonlFiles(sessionsDir, 4);
192
+ if (files.length === 0) return [];
193
+ files.sort((a, b) => b.mtime - a.mtime);
194
+ const byCwd = /* @__PURE__ */ new Map();
195
+ for (const f of files) {
196
+ const cwd = await readFirstFromFile(f.path, codexCwdExtractor);
197
+ if (!cwd) continue;
198
+ const prev = byCwd.get(cwd);
199
+ if (prev === void 0 || f.mtime > prev) byCwd.set(cwd, f.mtime);
200
+ }
201
+ const results = [];
202
+ for (const [cwd, mtime] of byCwd) {
203
+ if (!await isDirectory(cwd)) continue;
204
+ results.push({
205
+ name: path2.basename(cwd),
206
+ path: cwd,
207
+ lastActiveMs: mtime,
208
+ lastActiveDisplay: formatRelativeTime(mtime),
209
+ sources: ["codex"]
210
+ });
211
+ }
212
+ return results;
213
+ }
214
+ async function isDirectory(p) {
215
+ try {
216
+ const s = await fs2.stat(p);
217
+ return s.isDirectory();
218
+ } catch {
219
+ return false;
220
+ }
221
+ }
222
+ async function maxJsonlMtime(dir) {
223
+ if (!await isDirectory(dir)) return null;
224
+ const files = await collectJsonlFiles(dir, 2);
225
+ if (files.length === 0) return null;
226
+ let max = 0;
227
+ for (const f of files) if (f.mtime > max) max = f.mtime;
228
+ return max > 0 ? max : null;
229
+ }
230
+ async function collectJsonlFiles(dir, maxDepth) {
231
+ const out = [];
232
+ await walk(dir, 0);
233
+ return out;
234
+ async function walk(current, depth) {
235
+ let entries;
236
+ try {
237
+ entries = await fs2.readdir(current, { withFileTypes: true });
238
+ } catch {
239
+ return;
240
+ }
241
+ for (const e of entries) {
242
+ const full = path2.join(current, e.name);
243
+ if (e.isFile() && e.name.endsWith(".jsonl")) {
244
+ try {
245
+ const s = await fs2.stat(full);
246
+ out.push({ path: full, mtime: s.mtimeMs });
247
+ } catch {
248
+ }
249
+ } else if (e.isDirectory() && depth < maxDepth) {
250
+ await walk(full, depth + 1);
251
+ }
252
+ }
253
+ }
254
+ }
255
+ var claudeCwdExtractor = (line) => {
256
+ try {
257
+ const parsed = JSON.parse(line);
258
+ if (typeof parsed.cwd === "string" && parsed.cwd.startsWith("/")) return parsed.cwd;
259
+ } catch {
260
+ }
261
+ return null;
262
+ };
263
+ var CODEX_CWD_RE = /<cwd>([^<]+)<\/cwd>/;
264
+ var codexCwdExtractor = (line) => {
265
+ try {
266
+ const parsed = JSON.parse(line);
267
+ const cwd = parsed.payload?.cwd;
268
+ if (typeof cwd === "string" && cwd.startsWith("/")) return cwd;
269
+ } catch {
270
+ }
271
+ const m = CODEX_CWD_RE.exec(line);
272
+ if (m && m[1] && m[1].startsWith("/")) return m[1];
273
+ return null;
274
+ };
275
+ async function readFirstFromJsonlDir(dir, extractor) {
276
+ const files = await collectJsonlFiles(dir, 2);
277
+ if (files.length === 0) return null;
278
+ files.sort((a, b) => b.mtime - a.mtime);
279
+ for (const f of files) {
280
+ const v = await readFirstFromFile(f.path, extractor);
281
+ if (v) return v;
282
+ }
283
+ return null;
284
+ }
285
+ async function readFirstFromFile(file, extractor) {
286
+ return new Promise((resolve) => {
287
+ const stream = createReadStream(file, { encoding: "utf-8" });
288
+ const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
289
+ let lines = 0;
290
+ let done = false;
291
+ const MAX_LINES = 200;
292
+ const finish = (value) => {
293
+ if (done) return;
294
+ done = true;
295
+ resolve(value);
296
+ rl.close();
297
+ stream.destroy();
298
+ };
299
+ rl.on("line", (line) => {
300
+ if (done) return;
301
+ lines++;
302
+ if (lines > MAX_LINES) {
303
+ finish(null);
304
+ return;
305
+ }
306
+ const v = extractor(line);
307
+ if (v) finish(v);
308
+ });
309
+ rl.on("close", () => finish(null));
310
+ rl.on("error", () => finish(null));
311
+ stream.on("error", () => finish(null));
312
+ });
313
+ }
314
+ function fallbackDashDecode(entry) {
315
+ if (!entry.startsWith("-")) return null;
316
+ return "/" + entry.slice(1).replace(/-/g, "/");
317
+ }
318
+ function formatRelativeTime(ms) {
319
+ if (ms === 0) return "\u2014";
320
+ const diff = Date.now() - ms;
321
+ if (diff < 6e4) return "just now";
322
+ const min = 6e4;
323
+ const hour = 60 * min;
324
+ const day = 24 * hour;
325
+ const week = 7 * day;
326
+ const month = 30 * day;
327
+ if (diff < hour) return `${Math.floor(diff / min)}m ago`;
328
+ if (diff < day) return `${Math.floor(diff / hour)}h ago`;
329
+ if (diff < week) return `${Math.floor(diff / day)}d ago`;
330
+ if (diff < month) return `${Math.floor(diff / week)}w ago`;
331
+ return `${Math.floor(diff / month)}mo ago`;
332
+ }
333
+
334
+ // src/banner.tsx
335
+ init_esm_shims();
336
+ var import_react = __toESM(require_react(), 1);
337
+
338
+ // src/branding.ts
339
+ init_esm_shims();
340
+
341
+ // package.json
342
+ var package_default = {
343
+ name: "@iroaxel/arcena",
344
+ version: "4.3.157",
345
+ type: "module",
346
+ description: "Orchestrator agent that drives multiple arcicoder sessions across projects from a single chat",
347
+ license: "MIT",
348
+ repository: {
349
+ type: "git",
350
+ url: "git+https://github.com/BigBlindGorg/ARCena.git",
351
+ directory: "packages/gg-boss"
352
+ },
353
+ bin: {
354
+ arcena: "./dist/cli.js"
355
+ },
356
+ exports: {
357
+ ".": {
358
+ import: "./dist/index.js",
359
+ types: "./dist/index.d.ts"
360
+ }
361
+ },
362
+ files: [
363
+ "dist"
364
+ ],
365
+ scripts: {
366
+ build: "tsup && cp -R assets/. dist/ && chmod +x dist/cli.js",
367
+ check: "tsc --noEmit",
368
+ test: "vitest run"
369
+ },
370
+ devDependencies: {
371
+ "@iroaxel/gg-agent": "workspace:*",
372
+ "@iroaxel/gg-ai": "workspace:*",
373
+ "@iroaxel/arcicoder": "workspace:*",
374
+ "@types/node": "^25.6.0",
375
+ "@types/react": "^19.2.14",
376
+ chalk: "^5.6.2",
377
+ ink: "^7.0.2",
378
+ react: "^19.2.5",
379
+ tsup: "^8.5.1",
380
+ typescript: "^6.0.3",
381
+ vitest: "^4.1.4",
382
+ zod: "^4.4.3"
383
+ },
384
+ optionalDependencies: {
385
+ "@huggingface/transformers": "^3.6.0",
386
+ "ogg-opus-decoder": "^1.6.13"
387
+ },
388
+ publishConfig: {
389
+ access: "public"
390
+ }
391
+ };
392
+
393
+ // src/branding.ts
394
+ var VERSION = package_default.version;
395
+ var BRAND = "ARCena";
396
+ var AUTHOR = "Axel";
397
+ var LOGO_LINES = [" \u2584\u2580\u2580\u2580 \u2584\u2580\u2580\u2580", " \u2588 \u2580\u2588 \u2588 \u2580\u2588", " \u2580\u2584\u2584\u2580 \u2580\u2584\u2584\u2580"];
398
+ var LOGO_GAP = " ";
399
+ var GRADIENT = [
400
+ "#dc2626",
401
+ // red-600
402
+ "#e11d48",
403
+ // rose-600
404
+ "#be185d",
405
+ // pink-700
406
+ "#a21caf",
407
+ // fuchsia-700
408
+ "#c026d3",
409
+ // fuchsia-600
410
+ "#d946ef",
411
+ // fuchsia-500
412
+ "#c026d3",
413
+ // fuchsia-600 (back)
414
+ "#a21caf",
415
+ // fuchsia-700 (back)
416
+ "#be185d",
417
+ // pink-700 (back)
418
+ "#e11d48",
419
+ // rose-600 (back)
420
+ "#dc2626",
421
+ // red-600 (back)
422
+ "#b91c1c"
423
+ // red-700 (slight darker tail)
424
+ ];
425
+ var PULSE_COLORS = [
426
+ "#dc2626",
427
+ // crimson
428
+ "#e11d48",
429
+ // rose
430
+ "#be185d",
431
+ // wine
432
+ "#a21caf",
433
+ // magenta
434
+ "#c026d3",
435
+ // fuchsia
436
+ "#a21caf",
437
+ // back
438
+ "#be185d",
439
+ // back
440
+ "#e11d48"
441
+ // back
442
+ ];
443
+ var COLORS = {
444
+ primary: "#e11d48",
445
+ // crimson-rose — main brand color
446
+ accent: "#d946ef",
447
+ // fuchsia — secondary
448
+ text: "#e2e8f0",
449
+ textDim: "#6b7280",
450
+ success: "#4ade80",
451
+ warning: "#fbbf24",
452
+ error: "#f87171"
453
+ };
454
+ function clearScreen() {
455
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
456
+ }
457
+
458
+ // src/banner.tsx
459
+ var import_jsx_runtime = __toESM(require_jsx_runtime(), 1);
460
+ function BossBanner({ subtitle, hint, showShortcuts }) {
461
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: [
462
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
463
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[0] }),
464
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
465
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, bold: true, children: BRAND }),
466
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { color: COLORS.textDim, children: [
467
+ " v",
468
+ VERSION
469
+ ] }),
470
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
471
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
472
+ ] }),
473
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
474
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[1] }),
475
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
476
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.accent, children: subtitle })
477
+ ] }),
478
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Box_default, { children: [
479
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(GradientText, { text: LOGO_LINES[2] }),
480
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: LOGO_GAP }),
481
+ showShortcuts ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(Text, { children: [
482
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "^T" }),
483
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " tasks" }),
484
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
485
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "Tab" }),
486
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " scope" }),
487
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
488
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "\u21E7Tab" }),
489
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " thinking" }),
490
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " " }),
491
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.primary, children: "ESC" }),
492
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: " interrupt" })
493
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color: COLORS.textDim, children: hint ?? "" })
494
+ ] })
495
+ ] });
496
+ }
497
+ function GradientText({ text }) {
498
+ const chars = [];
499
+ let colorIdx = 0;
500
+ for (let i = 0; i < text.length; i++) {
501
+ const ch = text[i];
502
+ if (ch === " ") {
503
+ chars.push(ch);
504
+ } else {
505
+ const color = GRADIENT[colorIdx % GRADIENT.length];
506
+ chars.push(
507
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { color, children: ch }, i)
508
+ );
509
+ colorIdx++;
510
+ }
511
+ }
512
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Text, { children: chars });
513
+ }
514
+
515
+ // src/link-command.tsx
516
+ var import_jsx_runtime2 = __toESM(require_jsx_runtime(), 1);
517
+ var VISIBLE_ROWS = 12;
518
+ function sourceBadge(sources) {
519
+ if (sources.length > 1) return { label: "[mix]", color: COLORS.success };
520
+ const only = sources[0];
521
+ if (only === "arcicoder") return { label: "[gg ]", color: COLORS.accent };
522
+ if (only === "claude-code") return { label: "[cc ]", color: COLORS.warning };
523
+ if (only === "codex") return { label: "[cx ]", color: COLORS.primary };
524
+ return { label: "[?? ]", color: COLORS.textDim };
525
+ }
526
+ function LinkScreen({ projects, initialSelected, onDone }) {
527
+ const [cursor, setCursor] = (0, import_react2.useState)(0);
528
+ const [selected, setSelected] = (0, import_react2.useState)(new Set(initialSelected));
529
+ const [scrollOffset, setScrollOffset] = (0, import_react2.useState)(0);
530
+ const visible = projects.slice(scrollOffset, scrollOffset + VISIBLE_ROWS);
531
+ use_input_default((input, key) => {
532
+ if (key.ctrl && input === "c") {
533
+ onDone([], true);
534
+ return;
535
+ }
536
+ if (projects.length === 0) {
537
+ if (key.return || key.escape || input === "q") onDone([], true);
538
+ return;
539
+ }
540
+ if (key.upArrow) {
541
+ const next = Math.max(0, cursor - 1);
542
+ setCursor(next);
543
+ if (next < scrollOffset) setScrollOffset(next);
544
+ } else if (key.downArrow) {
545
+ const next = Math.min(projects.length - 1, cursor + 1);
546
+ setCursor(next);
547
+ if (next >= scrollOffset + VISIBLE_ROWS) setScrollOffset(next - VISIBLE_ROWS + 1);
548
+ } else if (input === " ") {
549
+ const p = projects[cursor];
550
+ if (!p) return;
551
+ const nextSet = new Set(selected);
552
+ if (nextSet.has(p.path)) nextSet.delete(p.path);
553
+ else nextSet.add(p.path);
554
+ setSelected(nextSet);
555
+ } else if (input === "a") {
556
+ const allSelected = projects.every((p) => selected.has(p.path));
557
+ setSelected(allSelected ? /* @__PURE__ */ new Set() : new Set(projects.map((p) => p.path)));
558
+ } else if (key.return) {
559
+ onDone(
560
+ projects.filter((p) => selected.has(p.path)).map((p) => p.path),
561
+ false
562
+ );
563
+ } else if (key.escape || input === "q") {
564
+ onDone([], true);
565
+ }
566
+ });
567
+ const subtitle = projects.length === 0 ? "Link projects" : `Link projects \xB7 ${projects.length} discovered \xB7 ${selected.size} selected`;
568
+ const hint = "\u2191\u2193 navigate \xB7 space toggle \xB7 a all \xB7 enter save \xB7 esc cancel";
569
+ if (projects.length === 0) {
570
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingX: 2, children: [
571
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BossBanner, { subtitle: "Link projects", hint: "No projects yet" }),
572
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginLeft: 2, children: [
573
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: "No arcicoder projects found in ~/.gg/sessions/." }),
574
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Text, { color: COLORS.textDim, children: [
575
+ "Run arcicoder in a project at least once, then re-run",
576
+ " ",
577
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.accent, children: "arcena link" }),
578
+ "."
579
+ ] }),
580
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: "Press any key to exit." }) })
581
+ ] })
582
+ ] });
583
+ }
584
+ const showingTop = scrollOffset > 0;
585
+ const showingBottom = scrollOffset + VISIBLE_ROWS < projects.length;
586
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", paddingX: 2, children: [
587
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BossBanner, { subtitle, hint }),
588
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { flexDirection: "column", marginLeft: 2, children: [
589
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " \u2191 more above" }),
590
+ visible.map((p, i) => {
591
+ const realIndex = scrollOffset + i;
592
+ const isCursor = realIndex === cursor;
593
+ const isSelected = selected.has(p.path);
594
+ const checkbox = isSelected ? "[\u2713]" : "[ ]";
595
+ const arrow = isCursor ? "\u276F" : " ";
596
+ const nameColor = isCursor ? COLORS.primary : isSelected ? COLORS.success : COLORS.text;
597
+ const checkboxColor = isSelected ? COLORS.success : COLORS.textDim;
598
+ const badge = sourceBadge(p.sources);
599
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(Box_default, { children: [
600
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.primary, children: arrow }),
601
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
602
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: checkboxColor, children: checkbox }),
603
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
604
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: badge.color, children: badge.label }),
605
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { children: " " }),
606
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: nameColor, bold: isCursor, children: p.name }),
607
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " " }),
608
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: p.lastActiveDisplay })
609
+ ] }, p.path);
610
+ }),
611
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(Text, { color: COLORS.textDim, children: " \u2193 more below" })
612
+ ] })
613
+ ] });
614
+ }
615
+ function LinkApp({ projects, initialSelected, resolve }) {
616
+ const { exit } = use_app_default();
617
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
618
+ LinkScreen,
619
+ {
620
+ projects,
621
+ initialSelected,
622
+ onDone: (selected, cancelled) => {
623
+ resolve({ selected, cancelled });
624
+ exit();
625
+ }
626
+ }
627
+ );
628
+ }
629
+ async function runLinkCommand() {
630
+ const projects = await discoverProjects();
631
+ const links = await loadLinks();
632
+ const initialSelected = new Set(links.projects.map((p) => p.cwd));
633
+ clearScreen();
634
+ const result = await new Promise((resolve) => {
635
+ const { waitUntilExit } = render_default(
636
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LinkApp, { projects, initialSelected, resolve })
637
+ );
638
+ void waitUntilExit();
639
+ });
640
+ if (result.cancelled) {
641
+ process.stdout.write(source_default.hex(COLORS.textDim)("\nCancelled. No changes saved.\n"));
642
+ return;
643
+ }
644
+ const linked = result.selected.map((path6) => projects.find((p) => p.path === path6)).filter((p) => Boolean(p)).map((p) => ({ name: p.name, cwd: p.path }));
645
+ await saveLinks({ projects: linked });
646
+ process.stdout.write("\n");
647
+ if (linked.length === 0) {
648
+ process.stdout.write(source_default.hex(COLORS.warning)("Cleared linked projects.\n"));
649
+ } else {
650
+ process.stdout.write(
651
+ source_default.hex(COLORS.success)(
652
+ `Linked ${linked.length} project${linked.length === 1 ? "" : "s"}:
653
+ `
654
+ )
655
+ );
656
+ for (const p of linked) {
657
+ process.stdout.write(
658
+ " " + source_default.hex(COLORS.primary)("\xB7") + " " + source_default.hex(COLORS.text)(p.name) + "\n"
659
+ );
660
+ }
661
+ process.stdout.write("\n");
662
+ process.stdout.write(
663
+ source_default.hex(COLORS.textDim)(`Run `) + source_default.hex(COLORS.accent)("arcena") + source_default.hex(COLORS.textDim)(` to start the orchestrator.
664
+ `)
665
+ );
666
+ }
667
+ }
668
+
669
+ // src/serve-mode.ts
670
+ init_esm_shims();
671
+ import path3 from "path";
672
+ import fs3 from "fs/promises";
673
+
674
+ // src/voice-transcriber.ts
675
+ init_esm_shims();
676
+ var TARGET_SAMPLE_RATE = 16e3;
677
+ var MODEL_ID = "Xenova/whisper-tiny.en";
678
+ var transcriber = null;
679
+ var loadPromise = null;
680
+ var onProgress = null;
681
+ function setProgressCallback(cb) {
682
+ onProgress = cb;
683
+ }
684
+ function resample(audio, fromRate, toRate) {
685
+ if (fromRate === toRate) return audio;
686
+ const ratio = fromRate / toRate;
687
+ const newLength = Math.round(audio.length / ratio);
688
+ const result = new Float32Array(newLength);
689
+ for (let i = 0; i < newLength; i++) {
690
+ const srcIndex = i * ratio;
691
+ const low = Math.floor(srcIndex);
692
+ const high = Math.min(low + 1, audio.length - 1);
693
+ const frac = srcIndex - low;
694
+ result[i] = audio[low] * (1 - frac) + audio[high] * frac;
695
+ }
696
+ return result;
697
+ }
698
+ function downmixToMono(channelData) {
699
+ if (channelData.length === 0) return new Float32Array();
700
+ if (channelData.length === 1) return channelData[0];
701
+ const samples = channelData[0].length;
702
+ const out = new Float32Array(samples);
703
+ const scale = 1 / channelData.length;
704
+ for (let i = 0; i < samples; i++) {
705
+ let mixed = 0;
706
+ for (const channel of channelData) mixed += channel[i] ?? 0;
707
+ out[i] = mixed * scale;
708
+ }
709
+ return out;
710
+ }
711
+ async function decodeOggOpus(buffer) {
712
+ const { OggOpusDecoder } = await import("ogg-opus-decoder");
713
+ const decoder = new OggOpusDecoder();
714
+ await decoder.ready;
715
+ try {
716
+ const decoded = await decoder.decodeFile(buffer);
717
+ if (!decoded.channelData?.length || !decoded.channelData[0]?.length) {
718
+ throw new Error("Decoded audio is empty");
719
+ }
720
+ const mono = downmixToMono(decoded.channelData);
721
+ return resample(mono, decoded.sampleRate, TARGET_SAMPLE_RATE);
722
+ } finally {
723
+ decoder.free();
724
+ }
725
+ }
726
+ async function getTranscriber() {
727
+ if (transcriber) return transcriber;
728
+ if (!loadPromise) {
729
+ loadPromise = (async () => {
730
+ const { pipeline } = await import("@huggingface/transformers");
731
+ const instance = await pipeline("automatic-speech-recognition", MODEL_ID, {
732
+ dtype: "fp32",
733
+ progress_callback: onProgress ?? void 0
734
+ });
735
+ transcriber = instance;
736
+ return instance;
737
+ })();
738
+ }
739
+ return loadPromise;
740
+ }
741
+ function isModelLoaded() {
742
+ return transcriber !== null;
743
+ }
744
+ async function transcribeVoice(fileUrl) {
745
+ const response = await fetch(fileUrl);
746
+ if (!response.ok) throw new Error(`Failed to download voice file: ${response.status}`);
747
+ const buffer = new Uint8Array(await response.arrayBuffer());
748
+ const pcm = await decodeOggOpus(buffer);
749
+ const asr = await getTranscriber();
750
+ const result = await asr(pcm);
751
+ const text = Array.isArray(result) ? result[0]?.text : result.text;
752
+ return (text ?? "").trim();
753
+ }
754
+
755
+ // src/telegram.ts
756
+ init_esm_shims();
757
+ var TELEGRAM_API = "https://api.telegram.org";
758
+ var MAX_MESSAGE_LENGTH = 4096;
759
+ var TelegramBot = class {
760
+ token;
761
+ allowedUserId;
762
+ offset = 0;
763
+ running = false;
764
+ onMessage = null;
765
+ onVoiceMessage = null;
766
+ onCallback = null;
767
+ onBotAdded = null;
768
+ onBotRemoved = null;
769
+ constructor(config) {
770
+ this.token = config.botToken;
771
+ this.allowedUserId = config.allowedUserId;
772
+ }
773
+ /** Register handler for incoming text messages. */
774
+ onText(handler) {
775
+ this.onMessage = handler;
776
+ }
777
+ /** Register handler for incoming voice notes. */
778
+ onVoice(handler) {
779
+ this.onVoiceMessage = handler;
780
+ }
781
+ /** Register handler for inline keyboard button presses. */
782
+ onCallbackQuery(handler) {
783
+ this.onCallback = handler;
784
+ }
785
+ /** Register handler for when the bot is added to a group. */
786
+ onAddedToGroup(handler) {
787
+ this.onBotAdded = handler;
788
+ }
789
+ /** Register handler for when the bot is removed from a group. */
790
+ onRemovedFromGroup(handler) {
791
+ this.onBotRemoved = handler;
792
+ }
793
+ /** Start long polling. Blocks until stop() is called. */
794
+ async start() {
795
+ this.running = true;
796
+ const me = await this.apiCall("getMe");
797
+ if (!me.ok) {
798
+ throw new Error(`Invalid bot token: ${JSON.stringify(me)}`);
799
+ }
800
+ while (this.running) {
801
+ try {
802
+ const updates = await this.getUpdates();
803
+ for (const update of updates) {
804
+ await this.handleUpdate(update);
805
+ }
806
+ } catch (err) {
807
+ if (!this.running) break;
808
+ console.error(`[telegram] Poll error: ${err instanceof Error ? err.message : err}`);
809
+ await sleep(3e3);
810
+ }
811
+ }
812
+ }
813
+ /** Stop long polling. */
814
+ stop() {
815
+ this.running = false;
816
+ }
817
+ /** Send a text message to a specific chat. Converts markdown and splits long messages. */
818
+ async send(chatId, text, buttons) {
819
+ const converted = toTelegramMarkdown(text);
820
+ const chunks = splitMessage(converted);
821
+ for (let i = 0; i < chunks.length; i++) {
822
+ const isLast = i === chunks.length - 1;
823
+ const replyMarkup = isLast && buttons ? {
824
+ inline_keyboard: buttons.map(
825
+ (row) => row.map((b) => ({ text: b.text, callback_data: b.callback_data }))
826
+ )
827
+ } : void 0;
828
+ await this.apiCall("sendMessage", {
829
+ chat_id: chatId,
830
+ text: chunks[i],
831
+ parse_mode: "Markdown",
832
+ ...replyMarkup ? { reply_markup: replyMarkup } : {}
833
+ });
834
+ }
835
+ }
836
+ /** Send a plain text message (no markdown parsing) to a specific chat. */
837
+ async sendPlain(chatId, text) {
838
+ const chunks = splitMessage(text);
839
+ for (const chunk of chunks) {
840
+ await this.apiCall("sendMessage", {
841
+ chat_id: chatId,
842
+ text: chunk
843
+ });
844
+ }
845
+ }
846
+ /** Send a typing indicator to a specific chat. */
847
+ async sendTyping(chatId) {
848
+ await this.apiCall("sendChatAction", {
849
+ chat_id: chatId,
850
+ action: "typing"
851
+ });
852
+ }
853
+ /** Get a direct download URL for a Telegram file. */
854
+ async getFileUrl(fileId) {
855
+ const result = await this.apiCall("getFile", { file_id: fileId });
856
+ if (!result.ok) throw new Error(`Failed to get file: ${JSON.stringify(result)}`);
857
+ const filePath = result.result.file_path;
858
+ return `${TELEGRAM_API}/file/bot${this.token}/${filePath}`;
859
+ }
860
+ // ── Private ───────────────────────────────────────────
861
+ async getUpdates() {
862
+ const result = await this.apiCall("getUpdates", {
863
+ offset: this.offset,
864
+ timeout: 30,
865
+ allowed_updates: ["message", "callback_query", "my_chat_member"]
866
+ });
867
+ if (!result.ok || !Array.isArray(result.result)) return [];
868
+ const updates = result.result;
869
+ if (updates.length > 0) {
870
+ this.offset = updates[updates.length - 1].update_id + 1;
871
+ }
872
+ return updates;
873
+ }
874
+ async handleUpdate(update) {
875
+ if (update.message) {
876
+ const msg = update.message;
877
+ if (msg.from.id !== this.allowedUserId) {
878
+ return;
879
+ }
880
+ if (msg.text && this.onMessage) {
881
+ this.onMessage({
882
+ text: msg.text,
883
+ chatId: msg.chat.id,
884
+ chatType: msg.chat.type,
885
+ chatTitle: msg.chat.title
886
+ });
887
+ } else if (msg.voice && this.onVoiceMessage) {
888
+ this.onVoiceMessage({
889
+ fileId: msg.voice.file_id,
890
+ duration: msg.voice.duration,
891
+ chatId: msg.chat.id,
892
+ chatType: msg.chat.type,
893
+ chatTitle: msg.chat.title
894
+ });
895
+ }
896
+ }
897
+ if (update.my_chat_member) {
898
+ const member = update.my_chat_member;
899
+ const status = member.new_chat_member.status;
900
+ if ((status === "member" || status === "administrator") && this.onBotAdded) {
901
+ this.onBotAdded(member.chat.id, member.chat.title);
902
+ } else if ((status === "left" || status === "kicked") && this.onBotRemoved) {
903
+ this.onBotRemoved(member.chat.id);
904
+ }
905
+ }
906
+ if (update.callback_query) {
907
+ const cb = update.callback_query;
908
+ if (cb.from.id !== this.allowedUserId) return;
909
+ await this.apiCall("answerCallbackQuery", { callback_query_id: cb.id });
910
+ if (cb.data && this.onCallback) {
911
+ this.onCallback(cb.data, cb.message.chat.id);
912
+ }
913
+ }
914
+ }
915
+ async apiCall(method, body) {
916
+ const url = `${TELEGRAM_API}/bot${this.token}/${method}`;
917
+ const response = await fetch(url, {
918
+ method: "POST",
919
+ headers: { "Content-Type": "application/json" },
920
+ body: body ? JSON.stringify(body) : void 0
921
+ });
922
+ if (!response.ok) {
923
+ return { ok: false };
924
+ }
925
+ return response.json();
926
+ }
927
+ };
928
+ function toTelegramMarkdown(text) {
929
+ const lines = text.split("\n");
930
+ const result = [];
931
+ let inCodeBlock = false;
932
+ for (const line of lines) {
933
+ if (line.trimStart().startsWith("```")) {
934
+ inCodeBlock = !inCodeBlock;
935
+ result.push(line);
936
+ continue;
937
+ }
938
+ if (inCodeBlock) {
939
+ result.push(line);
940
+ continue;
941
+ }
942
+ let transformed = line;
943
+ const headingMatch = transformed.match(/^(#{1,6})\s+(.+)$/);
944
+ if (headingMatch) {
945
+ transformed = `*${headingMatch[2]}*`;
946
+ result.push(transformed);
947
+ continue;
948
+ }
949
+ if (/^(-{3,}|_{3,}|\*{3,})$/.test(transformed.trim())) {
950
+ result.push("");
951
+ continue;
952
+ }
953
+ transformed = transformed.replace(/\*\*(.+?)\*\*/g, "*$1*");
954
+ result.push(transformed);
955
+ }
956
+ return result.join("\n");
957
+ }
958
+ function splitMessage(text) {
959
+ if (text.length <= MAX_MESSAGE_LENGTH) return [text];
960
+ const chunks = [];
961
+ let remaining = text;
962
+ while (remaining.length > 0) {
963
+ if (remaining.length <= MAX_MESSAGE_LENGTH) {
964
+ chunks.push(remaining);
965
+ break;
966
+ }
967
+ let splitAt = remaining.lastIndexOf("\n", MAX_MESSAGE_LENGTH);
968
+ if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
969
+ splitAt = remaining.lastIndexOf(" ", MAX_MESSAGE_LENGTH);
970
+ }
971
+ if (splitAt === -1 || splitAt < MAX_MESSAGE_LENGTH * 0.5) {
972
+ splitAt = MAX_MESSAGE_LENGTH;
973
+ }
974
+ chunks.push(remaining.slice(0, splitAt));
975
+ remaining = remaining.slice(splitAt).trimStart();
976
+ }
977
+ return chunks;
978
+ }
979
+ function sleep(ms) {
980
+ return new Promise((r) => setTimeout(r, ms));
981
+ }
982
+
983
+ // src/serve-mode.ts
984
+ function getTelegramConfigPath() {
985
+ return path3.join(getAppPaths().agentDir, "boss", "telegram.json");
986
+ }
987
+ async function loadBossTelegramConfig() {
988
+ try {
989
+ const raw = await fs3.readFile(getTelegramConfigPath(), "utf-8");
990
+ const data = JSON.parse(raw);
991
+ if (data.botToken && data.userId) return data;
992
+ return null;
993
+ } catch {
994
+ return null;
995
+ }
996
+ }
997
+ async function saveBossTelegramConfig(config) {
998
+ const file = getTelegramConfigPath();
999
+ await fs3.mkdir(path3.dirname(file), { recursive: true });
1000
+ await fs3.writeFile(file, JSON.stringify(config, null, 2), { encoding: "utf-8", mode: 384 });
1001
+ }
1002
+ function formatItemForTelegram(item) {
1003
+ switch (item.kind) {
1004
+ case "user":
1005
+ case "tool":
1006
+ case "worker_event":
1007
+ return null;
1008
+ case "assistant": {
1009
+ const cleaned = stripScopePrefix(item.text).trim();
1010
+ return cleaned ? truncate(cleaned, 1500) : null;
1011
+ }
1012
+ case "worker_error":
1013
+ return `\u2717 *${item.project}* \u2014 ${truncate(item.message, 300)}`;
1014
+ case "info":
1015
+ if (item.level !== "warning" && item.level !== "error") return null;
1016
+ return `${item.level === "error" ? "\u2717 " : "\u26A0 "}_${truncate(item.text, 300)}_`;
1017
+ case "task_dispatch": {
1018
+ if (item.tasks.length === 0) return null;
1019
+ const projects = [...new Set(item.tasks.map((t) => t.project))];
1020
+ if (item.tasks.length === 1) {
1021
+ const t = item.tasks[0];
1022
+ return `\u2192 *${t.project}*: ${truncate(t.title, 140)}`;
1023
+ }
1024
+ return `\u2192 Dispatched ${item.tasks.length} tasks across ${projects.length} project${projects.length === 1 ? "" : "s"}`;
1025
+ }
1026
+ case "update_notice":
1027
+ return `\u2728 ${item.text}`;
1028
+ }
1029
+ }
1030
+ function stripScopePrefix(text) {
1031
+ return text.replace(/^\s*\[scope:[^\]]+\]\s*/, "");
1032
+ }
1033
+ function truncate(text, max) {
1034
+ if (text.length <= max) return text;
1035
+ return text.slice(0, max - 1).trimEnd() + "\u2026";
1036
+ }
1037
+ function scopePrefix(scope) {
1038
+ if (scope === "all") return "[scope:all] ";
1039
+ return `[scope:${scope}] `;
1040
+ }
1041
+ async function runBossServeMode(options) {
1042
+ initLogger({
1043
+ version: VERSION,
1044
+ bossProvider: options.bossProvider,
1045
+ bossModel: options.bossModel,
1046
+ bossThinking: options.bossThinkingLevel,
1047
+ workerProvider: options.workerProvider,
1048
+ workerModel: options.workerModel,
1049
+ projectCount: 0
1050
+ });
1051
+ const links = await loadLinks();
1052
+ if (links.projects.length === 0) {
1053
+ console.error(
1054
+ source_default.hex(COLORS.error)("No linked projects.\n") + source_default.hex(COLORS.textDim)("Run ") + source_default.hex(COLORS.accent)("arcena link") + source_default.hex(COLORS.textDim)(" first to choose which projects the boss should manage.")
1055
+ );
1056
+ process.exit(1);
1057
+ }
1058
+ const projects = links.projects.map((p) => ({ name: p.name, cwd: p.cwd }));
1059
+ await tasksStore.load();
1060
+ const bot = new TelegramBot({
1061
+ botToken: options.telegram.botToken,
1062
+ allowedUserId: options.telegram.userId
1063
+ });
1064
+ const boss = new GGBoss({
1065
+ bossProvider: options.bossProvider,
1066
+ bossModel: options.bossModel,
1067
+ bossThinkingLevel: options.bossThinkingLevel,
1068
+ workerProvider: options.workerProvider,
1069
+ workerModel: options.workerModel,
1070
+ workerThinkingLevel: options.workerThinkingLevel,
1071
+ projects
1072
+ });
1073
+ await boss.initialize();
1074
+ log("INFO", "serve", "boss initialized", { projects: projects.map((p) => p.name).join(",") });
1075
+ const allowedChatId = options.telegram.userId;
1076
+ const pendingScopeSelections = /* @__PURE__ */ new Map();
1077
+ const pendingModelSelections = /* @__PURE__ */ new Map();
1078
+ let lastHistoryLen = getBossState().history.length;
1079
+ let typingInterval = null;
1080
+ let isStreaming = false;
1081
+ function sendQueued(text) {
1082
+ bot.send(allowedChatId, text).catch((err) => {
1083
+ log("WARN", "telegram", `send failed: ${err instanceof Error ? err.message : String(err)}`);
1084
+ bot.sendPlain(allowedChatId, text.replace(/[*_`]/g, "")).catch(() => {
1085
+ });
1086
+ });
1087
+ }
1088
+ function startTyping() {
1089
+ if (typingInterval) return;
1090
+ bot.sendTyping(allowedChatId).catch(() => {
1091
+ });
1092
+ typingInterval = setInterval(() => {
1093
+ bot.sendTyping(allowedChatId).catch(() => {
1094
+ });
1095
+ }, 4e3);
1096
+ }
1097
+ function stopTyping() {
1098
+ if (typingInterval) {
1099
+ clearInterval(typingInterval);
1100
+ typingInterval = null;
1101
+ }
1102
+ }
1103
+ function flushNewItems(state) {
1104
+ const len = state.history.length;
1105
+ if (len <= lastHistoryLen) return;
1106
+ const fresh = state.history.slice(lastHistoryLen);
1107
+ lastHistoryLen = len;
1108
+ for (const item of fresh) {
1109
+ const formatted = formatItemForTelegram(item);
1110
+ if (formatted) sendQueued(formatted);
1111
+ }
1112
+ }
1113
+ async function applyModelChoice(target, selected) {
1114
+ try {
1115
+ if (target === "boss") {
1116
+ await boss.switchBossModel(selected.provider, selected.id);
1117
+ await saveSettings({ bossProvider: selected.provider, bossModel: selected.id });
1118
+ await bot.send(allowedChatId, `Boss \u2192 *${selected.name}*`);
1119
+ } else {
1120
+ await boss.switchWorkerModel(selected.provider, selected.id);
1121
+ await saveSettings({ workerProvider: selected.provider, workerModel: selected.id });
1122
+ await bot.send(allowedChatId, `Workers \u2192 *${selected.name}*`);
1123
+ }
1124
+ } catch (err) {
1125
+ const message = err instanceof Error ? err.message : String(err);
1126
+ log("WARN", "model_switch", message, { target, model: selected.id });
1127
+ await bot.send(allowedChatId, `Failed to switch ${target}: ${message}`);
1128
+ }
1129
+ }
1130
+ const unsubscribe = subscribeToBossStore(() => {
1131
+ if (getBossState().pendingFlush.length > 0) {
1132
+ bossStore.commitPendingFlush();
1133
+ }
1134
+ const state = getBossState();
1135
+ flushNewItems(state);
1136
+ const streamingNow = state.streaming !== null;
1137
+ if (streamingNow && !isStreaming) {
1138
+ isStreaming = true;
1139
+ startTyping();
1140
+ } else if (!streamingNow && isStreaming) {
1141
+ isStreaming = false;
1142
+ stopTyping();
1143
+ }
1144
+ });
1145
+ bot.onText(async (msg) => {
1146
+ const { text, chatId } = msg;
1147
+ if (chatId !== allowedChatId) return;
1148
+ const pendingScopes = pendingScopeSelections.get(chatId);
1149
+ if (pendingScopes && /^\s*\d+\s*$/.test(text)) {
1150
+ pendingScopeSelections.delete(chatId);
1151
+ const num = parseInt(text.trim(), 10);
1152
+ if (num < 1 || num > pendingScopes.length) {
1153
+ await bot.send(chatId, "Invalid selection. Send /scope to try again.");
1154
+ return;
1155
+ }
1156
+ const chosen = pendingScopes[num - 1];
1157
+ bossStore.setScope(chosen);
1158
+ await bot.send(chatId, `Scope: *${chosen}*`);
1159
+ return;
1160
+ }
1161
+ const pendingModel = pendingModelSelections.get(chatId);
1162
+ if (pendingModel && /^\s*\d+\s*$/.test(text)) {
1163
+ pendingModelSelections.delete(chatId);
1164
+ const num = parseInt(text.trim(), 10);
1165
+ if (num < 1 || num > pendingModel.models.length) {
1166
+ await bot.send(chatId, "Invalid selection. Send /m to try again.");
1167
+ return;
1168
+ }
1169
+ const selected = pendingModel.models[num - 1];
1170
+ await applyModelChoice(pendingModel.target, selected);
1171
+ return;
1172
+ }
1173
+ if (!text.startsWith("/")) {
1174
+ const scoped = scopePrefix(getBossState().scope) + text;
1175
+ boss.enqueueUserMessage(scoped);
1176
+ return;
1177
+ }
1178
+ const parts = text.trim().split(/\s+/);
1179
+ const cmd = parts[0].slice(1).toLowerCase().replace(/@\w+$/, "");
1180
+ if (cmd === "help" || cmd === "start") {
1181
+ await bot.send(chatId, buildTelegramHelpText());
1182
+ return;
1183
+ }
1184
+ if (cmd === "m" || cmd === "model" || cmd === "model-boss" || cmd === "model-workers") {
1185
+ const target = cmd === "model-workers" ? "workers" : "boss";
1186
+ const arg = parts.slice(1).join(" ").trim().toLowerCase();
1187
+ const state = getBossState();
1188
+ const currentId = target === "boss" ? state.bossModel : state.workerModel;
1189
+ if (arg) {
1190
+ const num = parseInt(arg, 10);
1191
+ let match;
1192
+ if (!isNaN(num) && num >= 1 && num <= MODELS.length) {
1193
+ match = MODELS[num - 1];
1194
+ } else {
1195
+ match = MODELS.find(
1196
+ (m) => m.name.toLowerCase().includes(arg) || m.id.toLowerCase().includes(arg)
1197
+ );
1198
+ }
1199
+ if (!match) {
1200
+ await bot.send(chatId, `No model matching "${arg}". Send /${cmd} to see the list.`);
1201
+ return;
1202
+ }
1203
+ await applyModelChoice(target, match);
1204
+ return;
1205
+ }
1206
+ let listText = `*${target === "boss" ? "Boss" : "Worker"} model*
1207
+ `;
1208
+ let lastProvider = "";
1209
+ MODELS.forEach((m, i) => {
1210
+ if (m.provider !== lastProvider) {
1211
+ lastProvider = m.provider;
1212
+ listText += `
1213
+ _${providerLabel(m.provider)}_
1214
+ `;
1215
+ }
1216
+ const active = m.id === currentId ? " \u2190" : "";
1217
+ listText += ` *${i + 1}.* ${m.name}${active}
1218
+ `;
1219
+ });
1220
+ listText += `
1221
+ Send the number, or \`/${cmd} <name>\`.`;
1222
+ pendingModelSelections.set(chatId, { target, models: [...MODELS] });
1223
+ await bot.send(chatId, listText);
1224
+ return;
1225
+ }
1226
+ if (cmd === "scope" || cmd === "s") {
1227
+ const state = getBossState();
1228
+ const arg = parts.slice(1).join(" ").trim().toLowerCase();
1229
+ const names = ["all", ...state.workers.map((w) => w.name)];
1230
+ if (!arg) {
1231
+ const lines = names.map((n, i) => {
1232
+ const active = n === state.scope ? " \u2190" : "";
1233
+ const label = n === "all" ? "*All*" : `*${n}*`;
1234
+ return `*${i + 1}.* ${label}${active}`;
1235
+ });
1236
+ pendingScopeSelections.set(chatId, names);
1237
+ await bot.send(
1238
+ chatId,
1239
+ `*Scope* \u2014 current: *${state.scope}*
1240
+
1241
+ ${lines.join("\n")}
1242
+
1243
+ Send the number, or \`/scope <name>\`.`
1244
+ );
1245
+ return;
1246
+ }
1247
+ const num = parseInt(arg, 10);
1248
+ let chosen;
1249
+ if (!isNaN(num) && num >= 1 && num <= names.length) {
1250
+ chosen = names[num - 1];
1251
+ } else {
1252
+ const exact = names.find((n) => n.toLowerCase() === arg);
1253
+ chosen = exact ?? names.find((n) => n.toLowerCase().includes(arg)) ?? null;
1254
+ }
1255
+ if (!chosen) {
1256
+ await bot.send(chatId, `No scope matching "${arg}". Send /scope to see the list.`);
1257
+ return;
1258
+ }
1259
+ bossStore.setScope(chosen);
1260
+ await bot.send(chatId, `Scope: *${chosen}*`);
1261
+ return;
1262
+ }
1263
+ if (cmd === "status") {
1264
+ const state = getBossState();
1265
+ const lines = [`*${BRAND}* \u2014 ${state.bossModel}`, `Scope *${state.scope}*`, ""];
1266
+ lines.push("*Workers*");
1267
+ for (const w of state.workers) {
1268
+ const dot = w.status === "working" ? "\u25CF" : w.status === "error" ? "\u2717" : "\u25CB";
1269
+ lines.push(` ${dot} *${w.name}* \u2014 _${w.status}_`);
1270
+ }
1271
+ const tasks = tasksStore.list();
1272
+ const open = tasks.filter((t) => t.status === "pending" || t.status === "in_progress").length;
1273
+ lines.push("");
1274
+ lines.push(`Tasks ${open} open \xB7 ${tasks.length} total`);
1275
+ await bot.send(chatId, lines.join("\n"));
1276
+ return;
1277
+ }
1278
+ if (cmd === "cancel") {
1279
+ boss.abort();
1280
+ await bot.send(chatId, "_Aborted current boss turn._");
1281
+ return;
1282
+ }
1283
+ if (cmd === "new" || cmd === "n") {
1284
+ await boss.newSession();
1285
+ await bot.send(chatId, "\u2500\u2500 *New session* \u2500\u2500");
1286
+ return;
1287
+ }
1288
+ if (cmd === "tasks") {
1289
+ const tasks = tasksStore.list();
1290
+ if (tasks.length === 0) {
1291
+ await bot.send(chatId, "_No tasks._");
1292
+ return;
1293
+ }
1294
+ const lines = tasks.slice(0, 30).map((t, i) => {
1295
+ const status = t.status.replace("_", " ");
1296
+ return `*${i + 1}.* [${status}] *${t.project}* \u2014 ${t.description.split("\n")[0]}`;
1297
+ });
1298
+ await bot.send(chatId, `*Tasks*
1299
+
1300
+ ${lines.join("\n")}`);
1301
+ return;
1302
+ }
1303
+ boss.enqueueUserMessage(text);
1304
+ });
1305
+ bot.onVoice(async (msg) => {
1306
+ const { chatId } = msg;
1307
+ if (chatId !== allowedChatId) return;
1308
+ try {
1309
+ if (!isModelLoaded()) {
1310
+ await bot.send(
1311
+ chatId,
1312
+ "Setting up voice transcription \u2014 downloading Whisper model. This only happens once."
1313
+ );
1314
+ setProgressCallback((info) => {
1315
+ if (info.status === "progress" && info.progress !== void 0) {
1316
+ const pct = Math.round(info.progress);
1317
+ if (pct % 25 === 0 && pct > 0) {
1318
+ bot.sendTyping(chatId).catch(() => {
1319
+ });
1320
+ }
1321
+ }
1322
+ });
1323
+ }
1324
+ await bot.sendTyping(chatId);
1325
+ const fileUrl = await bot.getFileUrl(msg.fileId);
1326
+ const transcribed = await transcribeVoice(fileUrl);
1327
+ if (!transcribed) {
1328
+ await bot.send(chatId, "_Could not transcribe voice note._");
1329
+ return;
1330
+ }
1331
+ await bot.send(chatId, `_Voice: "${transcribed}"_`);
1332
+ const scoped = scopePrefix(getBossState().scope) + transcribed;
1333
+ boss.enqueueUserMessage(scoped);
1334
+ } catch (err) {
1335
+ const message = err instanceof Error ? err.message : String(err);
1336
+ log("ERROR", "voice", message);
1337
+ const hint = /Cannot find module|Cannot resolve|MODULE_NOT_FOUND/.test(message) ? "\n\nVoice transcription needs the optional `@huggingface/transformers` and `ogg-opus-decoder` packages. Reinstall with `npm i -g @iroaxel/arcena` and ensure optional deps installed." : "";
1338
+ await bot.send(chatId, `_Voice transcription failed: ${message}_${hint}`);
1339
+ }
1340
+ });
1341
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1342
+ printBanner({
1343
+ bossModel: options.bossModel,
1344
+ workerModel: options.workerModel,
1345
+ userId: options.telegram.userId,
1346
+ projectCount: projects.length
1347
+ });
1348
+ let shuttingDown = false;
1349
+ const shutdown = async () => {
1350
+ if (shuttingDown) return;
1351
+ shuttingDown = true;
1352
+ console.log(source_default.hex(COLORS.textDim)("\nShutting down..."));
1353
+ bot.stop();
1354
+ stopTyping();
1355
+ unsubscribe();
1356
+ await boss.dispose().catch(() => {
1357
+ });
1358
+ closeLogger();
1359
+ process.exit(0);
1360
+ };
1361
+ process.on("SIGINT", () => void shutdown());
1362
+ process.on("SIGTERM", () => void shutdown());
1363
+ const runPromise = boss.run().catch((err) => {
1364
+ log("ERROR", "boss", err instanceof Error ? err.message : String(err));
1365
+ });
1366
+ await bot.start();
1367
+ await runPromise;
1368
+ }
1369
+ function buildTelegramHelpText() {
1370
+ return [
1371
+ "*ARCena* \u2014 orchestrator over Telegram",
1372
+ "",
1373
+ "*Commands*",
1374
+ "/scope (/s) \u2014 switch project focus (All / per-worker)",
1375
+ "/m, /model-boss \u2014 switch the orchestrator's model",
1376
+ "/model-workers \u2014 switch every worker's model",
1377
+ "/status \u2014 workers + open tasks",
1378
+ "/tasks \u2014 list tasks",
1379
+ "/new \u2014 fresh boss session",
1380
+ "/cancel \u2014 abort the current boss turn",
1381
+ "/help \u2014 this message",
1382
+ "",
1383
+ "Voice notes are transcribed locally with Whisper and sent as prompts.",
1384
+ "Send any message to talk to the boss."
1385
+ ].join("\n");
1386
+ }
1387
+ function providerLabel(provider) {
1388
+ switch (provider) {
1389
+ case "anthropic":
1390
+ return "Anthropic";
1391
+ case "openai":
1392
+ return "OpenAI";
1393
+ case "glm":
1394
+ return "Z.AI";
1395
+ case "moonshot":
1396
+ return "Moonshot";
1397
+ case "minimax":
1398
+ return "MiniMax";
1399
+ case "deepseek":
1400
+ return "DeepSeek";
1401
+ case "openrouter":
1402
+ return "OpenRouter";
1403
+ case "xiaomi":
1404
+ return "Xiaomi";
1405
+ default:
1406
+ return provider;
1407
+ }
1408
+ }
1409
+ function gradientText(text) {
1410
+ let i = 0;
1411
+ return text.split("").map((ch) => ch === " " ? ch : source_default.hex(GRADIENT[i++ % GRADIENT.length])(ch)).join("");
1412
+ }
1413
+ function printBanner(opts) {
1414
+ console.log();
1415
+ console.log(
1416
+ ` ${gradientText(LOGO_LINES[0])}${LOGO_GAP}` + source_default.hex(COLORS.primary).bold(BRAND) + source_default.hex(COLORS.textDim)(` v${VERSION}`) + source_default.hex(COLORS.textDim)(" \xB7 By ") + source_default.white.bold(AUTHOR)
1417
+ );
1418
+ console.log(
1419
+ ` ${gradientText(LOGO_LINES[1])}${LOGO_GAP}` + source_default.hex(COLORS.accent)(`Boss: ${opts.bossModel}`)
1420
+ );
1421
+ console.log(
1422
+ ` ${gradientText(LOGO_LINES[2])}${LOGO_GAP}` + source_default.hex(COLORS.textDim)(`Workers: ${opts.workerModel}`)
1423
+ );
1424
+ console.log();
1425
+ console.log(
1426
+ source_default.hex(COLORS.textDim)(" Mode ") + source_default.hex(COLORS.accent)("Telegram") + source_default.hex(COLORS.textDim)(" \xB7 User ") + source_default.white(String(opts.userId)) + source_default.hex(COLORS.textDim)(
1427
+ ` \xB7 ${opts.projectCount} project${opts.projectCount === 1 ? "" : "s"}`
1428
+ )
1429
+ );
1430
+ console.log();
1431
+ console.log(
1432
+ source_default.hex(COLORS.success)(" Ready. ") + source_default.hex(COLORS.textDim)("Open Telegram and message your bot.")
1433
+ );
1434
+ console.log();
1435
+ console.log(
1436
+ source_default.hex(COLORS.textDim)(" /help ") + source_default.hex(COLORS.textDim)("commands") + source_default.hex(COLORS.textDim)(" /status ") + source_default.hex(COLORS.textDim)("workers + tasks") + source_default.hex(COLORS.textDim)(" /cancel ") + source_default.hex(COLORS.textDim)("abort turn")
1437
+ );
1438
+ console.log();
1439
+ }
1440
+
1441
+ // src/telegram-setup.ts
1442
+ init_esm_shims();
1443
+ import readline2 from "readline/promises";
1444
+ async function runBossTelegramSetup() {
1445
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
1446
+ printSetupBanner();
1447
+ const existing = await loadBossTelegramConfig();
1448
+ if (existing) {
1449
+ console.log(
1450
+ source_default.hex(COLORS.textDim)(" Current config:\n") + source_default.hex(COLORS.textDim)(
1451
+ ` Bot token: ${existing.botToken.slice(0, 10)}...${existing.botToken.slice(-4)}
1452
+ `
1453
+ ) + source_default.hex(COLORS.textDim)(` User ID: ${existing.userId}
1454
+ `)
1455
+ );
1456
+ }
1457
+ const rl = readline2.createInterface({ input: process.stdin, output: process.stdout });
1458
+ try {
1459
+ console.log(
1460
+ source_default.hex(COLORS.accent)(" Step 1: Bot Token\n") + source_default.hex(COLORS.textDim)(" 1. Open BotFather: ") + source_default.hex(COLORS.primary).underline("https://t.me/BotFather") + "\n" + source_default.hex(COLORS.textDim)(" 2. Send /newbot and follow the prompts\n") + source_default.hex(COLORS.textDim)(" 3. Copy the bot token\n")
1461
+ );
1462
+ const tokenPrompt = existing ? source_default.hex(COLORS.primary)(" Paste bot token (enter to keep current): ") : source_default.hex(COLORS.primary)(" Paste bot token: ");
1463
+ const tokenInput = await rl.question(tokenPrompt);
1464
+ const botToken = tokenInput.trim() || existing?.botToken;
1465
+ if (!botToken) {
1466
+ console.log(source_default.hex(COLORS.error)("\n No bot token provided. Setup cancelled."));
1467
+ return;
1468
+ }
1469
+ if (!/^\d+:[A-Za-z0-9_-]+$/.test(botToken)) {
1470
+ console.log(
1471
+ source_default.hex(COLORS.error)("\n Invalid token format. Expected: 123456789:ABCdef...")
1472
+ );
1473
+ return;
1474
+ }
1475
+ console.log(
1476
+ source_default.hex(COLORS.accent)("\n Step 2: User ID\n") + source_default.hex(COLORS.textDim)(" 1. Open userinfobot: ") + source_default.hex(COLORS.primary).underline("https://t.me/userinfobot") + "\n" + source_default.hex(COLORS.textDim)(" 2. Send any message \u2014 it replies with your numeric ID\n") + source_default.hex(COLORS.textDim)(" Only this user ID can control the boss.\n")
1477
+ );
1478
+ const userPrompt = existing ? source_default.hex(COLORS.primary)(` Your Telegram user ID (enter to keep ${existing.userId}): `) : source_default.hex(COLORS.primary)(" Your Telegram user ID: ");
1479
+ const userInput = await rl.question(userPrompt);
1480
+ const userId = userInput.trim() ? parseInt(userInput.trim(), 10) : existing?.userId;
1481
+ if (!userId || isNaN(userId)) {
1482
+ console.log(source_default.hex(COLORS.error)("\n Invalid user ID. Must be a number."));
1483
+ return;
1484
+ }
1485
+ console.log(source_default.hex(COLORS.textDim)("\n Verifying bot token..."));
1486
+ const verifyRes = await fetch(`https://api.telegram.org/bot${botToken}/getMe`, {
1487
+ method: "POST"
1488
+ });
1489
+ const verifyData = await verifyRes.json();
1490
+ if (!verifyData.ok || !verifyData.result) {
1491
+ console.log(
1492
+ source_default.hex(COLORS.error)(
1493
+ "\n Invalid bot token \u2014 Telegram rejected it. Check and try again."
1494
+ )
1495
+ );
1496
+ return;
1497
+ }
1498
+ const config = { botToken, userId };
1499
+ await saveBossTelegramConfig(config);
1500
+ console.log(
1501
+ source_default.hex(COLORS.success)(
1502
+ `
1503
+ \u2713 Connected to @${verifyData.result.username} (${verifyData.result.first_name})
1504
+ `
1505
+ ) + source_default.hex(COLORS.success)(` \u2713 Authorized user ID: ${userId}
1506
+
1507
+ `) + source_default.hex(COLORS.primary)(" To start:\n") + source_default.hex(COLORS.textDim)(" arcena serve\n")
1508
+ );
1509
+ } finally {
1510
+ rl.close();
1511
+ }
1512
+ }
1513
+ function gradientText2(text) {
1514
+ let i = 0;
1515
+ return text.split("").map((ch) => ch === " " ? ch : source_default.hex(GRADIENT[i++ % GRADIENT.length])(ch)).join("");
1516
+ }
1517
+ function printSetupBanner() {
1518
+ console.log();
1519
+ console.log(
1520
+ ` ${gradientText2(LOGO_LINES[0])}${LOGO_GAP}` + source_default.hex(COLORS.primary).bold(BRAND) + source_default.hex(COLORS.textDim)(` v${VERSION}`) + source_default.hex(COLORS.textDim)(" \xB7 By ") + source_default.white.bold(AUTHOR)
1521
+ );
1522
+ console.log(
1523
+ ` ${gradientText2(LOGO_LINES[1])}${LOGO_GAP}` + source_default.hex(COLORS.accent)("Telegram Setup")
1524
+ );
1525
+ console.log(
1526
+ ` ${gradientText2(LOGO_LINES[2])}${LOGO_GAP}` + source_default.hex(COLORS.textDim)("Remote Control")
1527
+ );
1528
+ console.log();
1529
+ }
1530
+
1531
+ // src/orchestrator-app.tsx
1532
+ init_esm_shims();
1533
+ var import_react11 = __toESM(require_react(), 1);
1534
+
1535
+ // ../ggcoder/dist/ui/components/index.js
1536
+ init_esm_shims();
1537
+
1538
+ // ../ggcoder/dist/ui/components/DiffView.js
1539
+ init_esm_shims();
1540
+ var import_jsx_runtime3 = __toESM(require_jsx_runtime(), 1);
1541
+ var import_react3 = __toESM(require_react(), 1);
1542
+
1543
+ // ../ggcoder/dist/ui/components/Overlay.js
1544
+ init_esm_shims();
1545
+ var import_jsx_runtime4 = __toESM(require_jsx_runtime(), 1);
1546
+ var import_react4 = __toESM(require_react(), 1);
1547
+
1548
+ // ../ggcoder/dist/ui/components/SessionSelector.js
1549
+ init_esm_shims();
1550
+ var import_jsx_runtime5 = __toESM(require_jsx_runtime(), 1);
1551
+ var import_react5 = __toESM(require_react(), 1);
1552
+
1553
+ // ../ggcoder/dist/ui/components/SettingsSelector.js
1554
+ init_esm_shims();
1555
+ var import_jsx_runtime6 = __toESM(require_jsx_runtime(), 1);
1556
+ var import_react6 = __toESM(require_react(), 1);
1557
+
1558
+ // ../ggcoder/dist/ui/components/ThinkingIndicator.js
1559
+ init_esm_shims();
1560
+ var import_jsx_runtime7 = __toESM(require_jsx_runtime(), 1);
1561
+ var import_react7 = __toESM(require_react(), 1);
1562
+
1563
+ // src/boss-footer.tsx
1564
+ init_esm_shims();
1565
+ var import_react8 = __toESM(require_react(), 1);
1566
+ var import_jsx_runtime8 = __toESM(require_jsx_runtime(), 1);
1567
+ var PARTIAL_BLOCKS = [" ", "\u258F", "\u258E", "\u258D", "\u258C", "\u258B", "\u258A", "\u2589", "\u2588"];
1568
+ var LIGHT_SHADE = "\u2591";
1569
+ var SHORT_MODELS = {
1570
+ "claude-opus-4-7": "Opus",
1571
+ "claude-sonnet-4-6": "Sonnet",
1572
+ "claude-haiku-4-5": "Haiku",
1573
+ "claude-haiku-4-5-20251001": "Haiku",
1574
+ "gpt-5.5": "GPT-5.5",
1575
+ "gpt-5.5-pro": "GPT-5.5 Pro",
1576
+ "gpt-5.4": "GPT-5.4",
1577
+ "gpt-5.4-mini": "GPT-5.4 Mini",
1578
+ "gpt-5.3-codex": "GPT-5.3 Codex"
1579
+ };
1580
+ function shortModel(model) {
1581
+ return SHORT_MODELS[model] ?? model;
1582
+ }
1583
+ function getContextPercent(model, tokensIn) {
1584
+ const limit = getContextWindow(model);
1585
+ if (!limit || tokensIn === 0) return 0;
1586
+ return Math.round(tokensIn / limit * 100);
1587
+ }
1588
+ var SHORT_RADIO = {
1589
+ "somafm-groove-salad": "Groove Salad",
1590
+ "somafm-drone-zone": "Drone Zone",
1591
+ "radio-paradise": "Radio Paradise",
1592
+ "george-fm": "George FM"
1593
+ };
1594
+ function BossFooter({
1595
+ bossModel,
1596
+ workerModel,
1597
+ tokensIn,
1598
+ exitPending,
1599
+ bossThinkingLevel,
1600
+ updatePending,
1601
+ currentRadioStationId
1602
+ }) {
1603
+ const theme = useTheme();
1604
+ const { columns } = useTerminalSize();
1605
+ if (exitPending) {
1606
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.warning, children: "Press Ctrl+C again to exit" }) });
1607
+ }
1608
+ const contextPct = getContextPercent(bossModel, tokensIn);
1609
+ const contextColor = contextPct >= 80 ? theme.error : contextPct >= 50 ? theme.warning : theme.success;
1610
+ const sep = /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.border, children: " \u2502 " });
1611
+ const barWidth = 8;
1612
+ const fillFloat = Math.min(contextPct / 100 * barWidth, barWidth);
1613
+ const barChars = [];
1614
+ for (let i = 0; i < barWidth; i++) {
1615
+ const cellFill = Math.max(0, Math.min(1, fillFloat - i));
1616
+ const eighths = Math.round(cellFill * 8);
1617
+ if (eighths === 8) {
1618
+ barChars.push(
1619
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[8] }, i)
1620
+ );
1621
+ } else if (eighths > 0) {
1622
+ barChars.push(
1623
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: contextColor, children: PARTIAL_BLOCKS[eighths] }, i)
1624
+ );
1625
+ } else {
1626
+ barChars.push(
1627
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: LIGHT_SHADE }, i)
1628
+ );
1629
+ }
1630
+ }
1631
+ const radioName = currentRadioStationId ? SHORT_RADIO[currentRadioStationId] ?? currentRadioStationId : null;
1632
+ const bossM = shortModel(bossModel);
1633
+ const wkrM = shortModel(workerModel);
1634
+ const estFull = 2 + 12 + // bar + " 99%"
1635
+ 3 + 5 + bossM.length + // " │ boss <model>"
1636
+ 3 + 8 + wkrM.length + // " │ workers <model>"
1637
+ 3 + 12 + // " │ Thinking off"
1638
+ (radioName ? 3 + 2 + radioName.length : 0) + // " │ ♪ Name"
1639
+ (updatePending ? 3 + 28 : 0);
1640
+ const dropLabels = estFull > columns;
1641
+ const dropThinking = estFull > columns + 14;
1642
+ const useShortUpdate = updatePending && estFull > columns + 6;
1643
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { paddingX: 1, width: columns, children: [
1644
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Box_default, { flexGrow: 1 }),
1645
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Box_default, { flexShrink: 0, children: [
1646
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { children: barChars }),
1647
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: contextColor, children: [
1648
+ " ",
1649
+ contextPct,
1650
+ "%"
1651
+ ] }),
1652
+ sep,
1653
+ !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "boss " }),
1654
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.primary, bold: true, children: bossM }),
1655
+ sep,
1656
+ !dropLabels && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.textDim, children: "workers " }),
1657
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: COLORS.accent, bold: true, children: wkrM }),
1658
+ !dropThinking && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1659
+ sep,
1660
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: bossThinkingLevel ? theme.accent : theme.textDim, children: bossThinkingLevel ? "Thinking on" : "Thinking off" })
1661
+ ] }),
1662
+ radioName && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1663
+ sep,
1664
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(Text, { color: theme.secondary ?? theme.accent, children: [
1665
+ "\u266A ",
1666
+ radioName
1667
+ ] })
1668
+ ] }),
1669
+ updatePending && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
1670
+ sep,
1671
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Text, { color: theme.success, bold: true, wrap: "truncate", children: useShortUpdate ? "Update ready" : "Update ready. Restart ARCena." })
1672
+ ] })
1673
+ ] })
1674
+ ] });
1675
+ }
1676
+
1677
+ // src/slash-commands.ts
1678
+ init_esm_shims();
1679
+ var BOSS_SLASH_COMMANDS = [
1680
+ { name: "help", aliases: ["?"], description: "Show available commands" },
1681
+ { name: "model-boss", aliases: [], description: "Switch the orchestrator's model" },
1682
+ { name: "model-workers", aliases: [], description: "Switch every worker's model" },
1683
+ { name: "compact", aliases: [], description: "Compact the boss's context now" },
1684
+ { name: "clear", aliases: [], description: "Clear chat history and terminal" },
1685
+ { name: "radio", aliases: [], description: "Stream a free internet radio station" },
1686
+ { name: "quit", aliases: ["q", "exit"], description: "Exit arcena" }
1687
+ ];
1688
+ function isSlashCommand(value) {
1689
+ return value.startsWith("/") && !value.startsWith("//");
1690
+ }
1691
+ function parseSlash(value) {
1692
+ if (!isSlashCommand(value)) return null;
1693
+ const rest = value.slice(1).trim();
1694
+ if (!rest) return null;
1695
+ const space = rest.indexOf(" ");
1696
+ if (space === -1) return { name: rest.toLowerCase(), args: "" };
1697
+ return { name: rest.slice(0, space).toLowerCase(), args: rest.slice(space + 1).trim() };
1698
+ }
1699
+ function canonicalName(name) {
1700
+ for (const cmd of BOSS_SLASH_COMMANDS) {
1701
+ if (cmd.name === name) return cmd.name;
1702
+ if (cmd.aliases.includes(name)) return cmd.name;
1703
+ }
1704
+ return null;
1705
+ }
1706
+ function buildHelpText() {
1707
+ const lines = ["**arcena commands**", ""];
1708
+ for (const cmd of BOSS_SLASH_COMMANDS) {
1709
+ const aliases = cmd.aliases.length > 0 ? ` (${cmd.aliases.map((a) => "/" + a).join(", ")})` : "";
1710
+ lines.push(`- \`/${cmd.name}\`${aliases} \u2014 ${cmd.description}`);
1711
+ }
1712
+ lines.push("");
1713
+ lines.push("**Global keybindings**");
1714
+ lines.push("- `Ctrl+T` \u2014 open the Tasks pane");
1715
+ lines.push("- `Tab` \u2014 switch project scope (All / per-project pill in the input)");
1716
+ lines.push("- `Shift+Tab` \u2014 toggle the boss's extended thinking on/off");
1717
+ lines.push("- `Esc` \u2014 interrupt the boss while it's running");
1718
+ lines.push("- `Ctrl+C` (twice) \u2014 exit");
1719
+ lines.push("");
1720
+ lines.push("**Inside the Tasks pane (Ctrl+T)**");
1721
+ lines.push("- `\u2191` / `\u2193` (or `k` / `j`) \u2014 navigate tasks");
1722
+ lines.push("- `r` \u2014 run all pending and blocked tasks across idle workers");
1723
+ lines.push("- `d` \u2014 delete the selected task");
1724
+ lines.push("- `Esc` \u2014 close the Tasks pane");
1725
+ lines.push("");
1726
+ lines.push("**Inside model pickers (`/model-boss`, `/model-workers`)**");
1727
+ lines.push("- `\u2191` / `\u2193` \u2014 navigate models");
1728
+ lines.push("- `Enter` \u2014 select");
1729
+ lines.push("- `Esc` \u2014 cancel");
1730
+ lines.push("");
1731
+ lines.push("**Radio** (`/radio`)");
1732
+ lines.push("- Pick a station to stream while you work, or select `Off` to stop.");
1733
+ lines.push("- Requires `mpv` (recommended), `ffplay`, `mpg123`, or `vlc/cvlc` installed.");
1734
+ lines.push("");
1735
+ lines.push("**Input area**");
1736
+ lines.push("- `\u2191` / `\u2193` \u2014 recall previous prompts (when input is empty)");
1737
+ lines.push("- `Enter` \u2014 send \xB7 `Shift+Enter` \u2014 newline");
1738
+ lines.push("- `/` \u2014 open the slash-command menu (Tab / arrows to pick, Enter to insert)");
1739
+ return lines.join("\n");
1740
+ }
1741
+
1742
+ // src/tool-formatters.ts
1743
+ init_esm_shims();
1744
+
1745
+ // src/colors.ts
1746
+ init_esm_shims();
1747
+ var PROJECT_COLORS = [
1748
+ "#60a5fa",
1749
+ // blue
1750
+ "#a78bfa",
1751
+ // violet
1752
+ "#4ade80",
1753
+ // green
1754
+ "#fbbf24",
1755
+ // amber
1756
+ "#f472b6",
1757
+ // pink
1758
+ "#22d3ee",
1759
+ // cyan
1760
+ "#fb923c",
1761
+ // orange
1762
+ "#34d399"
1763
+ // emerald
1764
+ ];
1765
+ function stableHash(s) {
1766
+ let h = 0;
1767
+ for (let i = 0; i < s.length; i++) {
1768
+ h = h * 31 + s.charCodeAt(i) | 0;
1769
+ }
1770
+ return Math.abs(h);
1771
+ }
1772
+ function projectColor(name) {
1773
+ return PROJECT_COLORS[stableHash(name) % PROJECT_COLORS.length];
1774
+ }
1775
+
1776
+ // src/tool-formatters.ts
1777
+ function truncate2(s, max) {
1778
+ if (max <= 1) return "\u2026";
1779
+ return s.length > max ? s.slice(0, max - 1) + "\u2026" : s;
1780
+ }
1781
+ function promptWorkerDetailLen(project) {
1782
+ const cols = process.stdout.columns ?? 80;
1783
+ const fixed = 2 + 13 + 1 + project.length + 3 + 1 + 1 + 11 + 6;
1784
+ return Math.max(20, cols - fixed);
1785
+ }
1786
+ var bossToolFormatters = {
1787
+ formatLabel(name) {
1788
+ switch (name) {
1789
+ case "list_workers":
1790
+ return "List Workers";
1791
+ case "get_worker_status":
1792
+ return "Worker Status";
1793
+ case "prompt_worker":
1794
+ return "Prompt Worker";
1795
+ case "get_worker_summary":
1796
+ return "Worker Summary";
1797
+ default:
1798
+ return void 0;
1799
+ }
1800
+ },
1801
+ formatDetail(name, args) {
1802
+ switch (name) {
1803
+ case "list_workers":
1804
+ return "";
1805
+ case "get_worker_status":
1806
+ case "get_worker_summary":
1807
+ return truncate2(String(args.project ?? ""), 40);
1808
+ case "prompt_worker": {
1809
+ const project = String(args.project ?? "");
1810
+ const message = String(args.message ?? "").replace(/\s+/g, " ");
1811
+ const fresh = args.fresh === true;
1812
+ const maxMsg = promptWorkerDetailLen(project) - (fresh ? 8 : 0);
1813
+ const truncMsg = truncate2(message, Math.max(15, maxMsg));
1814
+ const head = fresh ? "fresh \xB7 " : "";
1815
+ return project ? `${head}${project} \xB7 ${truncMsg}` : `${head}${truncMsg}`;
1816
+ }
1817
+ default:
1818
+ return void 0;
1819
+ }
1820
+ },
1821
+ formatInline(name, result, isError) {
1822
+ if (isError) return void 0;
1823
+ switch (name) {
1824
+ case "list_workers": {
1825
+ const lines = result.split("\n").filter((l) => l.startsWith("-"));
1826
+ return `${lines.length} worker${lines.length === 1 ? "" : "s"}`;
1827
+ }
1828
+ case "prompt_worker": {
1829
+ if (result.includes("currently working")) {
1830
+ return { text: "busy \u2014 skipped", color: "#fbbf24" };
1831
+ }
1832
+ if (result.includes("Unknown project")) {
1833
+ return { text: "unknown project", color: "#f87171" };
1834
+ }
1835
+ const project = String(result.match(/"([^"]+)"/)?.[1] ?? "");
1836
+ const color = project ? projectColor(project) : "#e11d48";
1837
+ return { text: "dispatched", color };
1838
+ }
1839
+ case "get_worker_status": {
1840
+ const parts = result.split(":");
1841
+ if (parts.length < 2) return void 0;
1842
+ const status = parts.slice(1).join(":").trim();
1843
+ const project = parts[0].trim();
1844
+ return { text: status, color: projectColor(project) };
1845
+ }
1846
+ case "get_worker_summary": {
1847
+ const turnMatch = result.match(/Turn:\s*(\d+)/);
1848
+ const projectMatch = result.match(/Project:\s*(.+)/);
1849
+ const toolsMatch = result.match(/Tools used:\s*(.+)/);
1850
+ const tools = toolsMatch ? toolsMatch[1] : "";
1851
+ const toolCount = tools && tools !== "(no tools used)" ? tools.split(",").length : 0;
1852
+ const turn = turnMatch ? `turn ${turnMatch[1]}` : void 0;
1853
+ const tCount = toolCount > 0 ? `${toolCount} tool${toolCount === 1 ? "" : "s"}` : void 0;
1854
+ const summary = [turn, tCount].filter(Boolean).join(" \xB7 ");
1855
+ if (!summary) return void 0;
1856
+ const project = projectMatch ? projectMatch[1].trim() : "";
1857
+ return project ? { text: summary, color: projectColor(project) } : { text: summary, color: "#9ca3af" };
1858
+ }
1859
+ default:
1860
+ return void 0;
1861
+ }
1862
+ }
1863
+ };
1864
+
1865
+ // src/boss-phrases.ts
1866
+ init_esm_shims();
1867
+ var BOSS_PHRASES = {
1868
+ // Generic between-states fallback. Probably never shown but keep for safety.
1869
+ idle: ["Standing by", "Waiting for orders", "On call"],
1870
+ // Boss has issued a request, waiting for the LLM to begin streaming.
1871
+ waiting: [
1872
+ "Briefing",
1873
+ "Reviewing the room",
1874
+ "Triaging",
1875
+ "Lining up the brief",
1876
+ "Surveying projects",
1877
+ "Reading the room",
1878
+ "Picking the right hand",
1879
+ "Marshalling thoughts",
1880
+ "Checking the board",
1881
+ "Sizing up the work"
1882
+ ],
1883
+ // LLM is mid-thinking-block (extended reasoning).
1884
+ thinking: [
1885
+ "Strategising",
1886
+ "Plotting next move",
1887
+ "Weighing options",
1888
+ "Reasoning",
1889
+ "Deliberating",
1890
+ "Thinking it through",
1891
+ "Mapping the play",
1892
+ "Considering angles",
1893
+ "Calculating odds",
1894
+ "Drafting the call"
1895
+ ],
1896
+ // LLM is streaming text — boss is forming its dispatch / response.
1897
+ generating: [
1898
+ "Drafting",
1899
+ "Composing dispatch",
1900
+ "Writing the brief",
1901
+ "Penning instructions",
1902
+ "Wording it up",
1903
+ "Putting it on paper",
1904
+ "Phrasing the ask",
1905
+ "Forming the directive",
1906
+ "Scripting the plan"
1907
+ ],
1908
+ // Boss is invoking a tool — most often prompt_worker.
1909
+ tools: [
1910
+ "Coordinating",
1911
+ "Dispatching",
1912
+ "Routing",
1913
+ "Delegating",
1914
+ "Issuing orders",
1915
+ "Handing off",
1916
+ "Aligning workers",
1917
+ "Conducting",
1918
+ "Calling the team",
1919
+ "Steering",
1920
+ "Pulling levers"
1921
+ ],
1922
+ // Provider retry (overloaded / rate-limited / etc.).
1923
+ retrying: [
1924
+ "Reattempting",
1925
+ "Course correcting",
1926
+ "Trying again",
1927
+ "Pushing through",
1928
+ "Holding the line"
1929
+ ]
1930
+ };
1931
+
1932
+ // src/boss-tasks-overlay.tsx
1933
+ init_esm_shims();
1934
+ var import_react9 = __toESM(require_react(), 1);
1935
+ var import_jsx_runtime9 = __toESM(require_jsx_runtime(), 1);
1936
+ function statusGlyph(status) {
1937
+ switch (status) {
1938
+ case "done":
1939
+ return "\u2713";
1940
+ case "in_progress":
1941
+ return "~";
1942
+ case "blocked":
1943
+ return "\u2717";
1944
+ case "skipped":
1945
+ return "\u2014";
1946
+ default:
1947
+ return " ";
1948
+ }
1949
+ }
1950
+ function BossTasksOverlay({
1951
+ boss,
1952
+ workers,
1953
+ onClose
1954
+ }) {
1955
+ const theme = useTheme();
1956
+ const tasksState = useTasksState();
1957
+ const tasks = tasksState.tasks;
1958
+ const [selectedIndex, setSelectedIndex] = (0, import_react9.useState)(0);
1959
+ const [status, setStatusMsg] = (0, import_react9.useState)("");
1960
+ const statusTimer = (0, import_react9.useRef)(null);
1961
+ const showStatus = (0, import_react9.useCallback)((msg) => {
1962
+ setStatusMsg(msg);
1963
+ if (statusTimer.current) clearTimeout(statusTimer.current);
1964
+ statusTimer.current = setTimeout(() => setStatusMsg(""), 2500);
1965
+ }, []);
1966
+ const groupedTasks = workers.map((w) => ({
1967
+ project: w.name,
1968
+ tasks: tasks.filter((t) => t.project === w.name).sort((a, b) => a.createdAt.localeCompare(b.createdAt))
1969
+ }));
1970
+ const flatTasks = groupedTasks.flatMap((g) => g.tasks);
1971
+ (0, import_react9.useEffect)(() => {
1972
+ if (flatTasks.length === 0) {
1973
+ setSelectedIndex(0);
1974
+ } else if (selectedIndex >= flatTasks.length) {
1975
+ setSelectedIndex(flatTasks.length - 1);
1976
+ }
1977
+ }, [flatTasks.length, selectedIndex]);
1978
+ const selected = flatTasks[selectedIndex];
1979
+ const MAX_VISIBLE = 12;
1980
+ const startIdx = Math.max(
1981
+ 0,
1982
+ Math.min(flatTasks.length - MAX_VISIBLE, selectedIndex - MAX_VISIBLE + 1, selectedIndex)
1983
+ );
1984
+ const endIdx = Math.min(flatTasks.length, startIdx + MAX_VISIBLE);
1985
+ const visibleIdSet = new Set(flatTasks.slice(startIdx, endIdx).map((t) => t.id));
1986
+ const showingTop = startIdx > 0;
1987
+ const showingBottom = endIdx < flatTasks.length;
1988
+ use_input_default((input, key) => {
1989
+ if (key.escape) {
1990
+ onClose();
1991
+ return;
1992
+ }
1993
+ if (key.upArrow || input === "k") {
1994
+ setSelectedIndex((i) => Math.max(0, i - 1));
1995
+ return;
1996
+ }
1997
+ if (key.downArrow || input === "j") {
1998
+ setSelectedIndex((i) => Math.min(flatTasks.length - 1, i + 1));
1999
+ return;
2000
+ }
2001
+ if (input === "d" && selected) {
2002
+ void tasksStore.remove(selected.id).then(() => showStatus("Deleted"));
2003
+ return;
2004
+ }
2005
+ if (input === "r") {
2006
+ onClose();
2007
+ void (async () => {
2008
+ const dispatched = [];
2009
+ for (const w of workers) {
2010
+ const next = tasksStore.nextDispatchable(w.name);
2011
+ if (!next) continue;
2012
+ if (next.status === "blocked") {
2013
+ await tasksStore.update(next.id, { status: "pending", notes: void 0 });
2014
+ }
2015
+ const res = await boss.dispatchTaskById(next.id);
2016
+ if (res.ok) dispatched.push({ project: w.name, title: next.title });
2017
+ }
2018
+ if (dispatched.length === 0) {
2019
+ bossStore.appendInfo("No pending or blocked tasks to run.", "info");
2020
+ } else {
2021
+ bossStore.appendTaskDispatch(dispatched);
2022
+ }
2023
+ })();
2024
+ return;
2025
+ }
2026
+ });
2027
+ const doneCount = tasks.filter((t) => t.status === "done").length;
2028
+ const inProgressCount = tasks.filter((t) => t.status === "in_progress").length;
2029
+ const pendingCount = tasks.filter((t) => t.status === "pending").length;
2030
+ const blockedCount = tasks.filter((t) => t.status === "blocked").length;
2031
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, paddingX: 1, children: [
2032
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { children: [
2033
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: COLORS.primary, bold: true, children: "Tasks" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${tasks.length} total \xB7 ` }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
2036
+ CountsRow,
2037
+ {
2038
+ theme,
2039
+ done: doneCount,
2040
+ active: inProgressCount,
2041
+ pending: pendingCount,
2042
+ blocked: blockedCount
2043
+ }
2044
+ )
2045
+ ] }),
2046
+ flatTasks.length === 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
2047
+ " No tasks yet. Ask the boss to plan some \u2014 e.g. ",
2048
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.text, children: '"plan some work"' }),
2049
+ "."
2050
+ ] }) }),
2051
+ showingTop && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2191 ${startIdx} more above` }),
2052
+ groupedTasks.map((group, gIdx) => {
2053
+ const startInFlat = groupedTasks.slice(0, gIdx).reduce((acc, g) => acc + g.tasks.length, 0);
2054
+ const visibleInSection = group.tasks.filter((t) => visibleIdSet.has(t.id));
2055
+ if (visibleInSection.length === 0) return null;
2056
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: projectColor(group.project), bold: true, children: group.project }),
2059
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${group.tasks.length}` })
2060
+ ] }),
2061
+ visibleInSection.map((task) => {
2062
+ const realIdx = startInFlat + group.tasks.indexOf(task);
2063
+ const isSelected = realIdx === selectedIndex;
2064
+ const prefix = isSelected ? "\u276F " : " ";
2065
+ const glyph = statusGlyph(task.status);
2066
+ const color = isSelected ? theme.primary : task.status === "done" ? theme.success : task.status === "in_progress" ? theme.warning : task.status === "blocked" ? theme.error : theme.text;
2067
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color, bold: isSelected, children: [
2068
+ prefix,
2069
+ "[",
2070
+ glyph,
2071
+ "] ",
2072
+ task.title
2073
+ ] }, task.id);
2074
+ })
2075
+ ] }, group.project);
2076
+ }),
2077
+ showingBottom && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: ` \u2193 ${flatTasks.length - endIdx} more below` }),
2078
+ status && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.success, children: " " + status }) }),
2079
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.textDim, children: [
2080
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "\u2191\u2193" }),
2081
+ " move \xB7 (",
2082
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "d" }),
2083
+ ")elete \xB7 (",
2084
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "r" }),
2085
+ ")un pending \xB7 ",
2086
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.primary, children: "ESC" }),
2087
+ " close"
2088
+ ] }) })
2089
+ ] });
2090
+ }
2091
+ function CountsRow({
2092
+ theme,
2093
+ done,
2094
+ active,
2095
+ pending,
2096
+ blocked
2097
+ }) {
2098
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { children: [
2099
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.success, children: [
2100
+ done,
2101
+ " done"
2102
+ ] }),
2103
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2104
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.warning, children: [
2105
+ active,
2106
+ " active"
2107
+ ] }),
2108
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.text, children: [
2110
+ pending,
2111
+ " pending"
2112
+ ] }),
2113
+ blocked > 0 && /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
2114
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
2115
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(Text, { color: theme.error, children: [
2116
+ blocked,
2117
+ " blocked"
2118
+ ] })
2119
+ ] })
2120
+ ] });
2121
+ }
2122
+
2123
+ // src/radio-picker.tsx
2124
+ init_esm_shims();
2125
+ var import_react10 = __toESM(require_react(), 1);
2126
+
2127
+ // src/radio.ts
2128
+ init_esm_shims();
2129
+ import { spawn } from "child_process";
2130
+ import { existsSync } from "fs";
2131
+ var RADIO_STATIONS = [
2132
+ {
2133
+ id: "somafm-groove-salad",
2134
+ name: "SomaFM \xB7 Groove Salad",
2135
+ description: "Chilled downtempo, ambient grooves",
2136
+ url: "http://ice1.somafm.com/groovesalad-128-mp3"
2137
+ },
2138
+ {
2139
+ id: "somafm-drone-zone",
2140
+ name: "SomaFM \xB7 Drone Zone",
2141
+ description: "Atmospheric textures with minimal beats",
2142
+ url: "http://ice1.somafm.com/dronezone-128-mp3"
2143
+ },
2144
+ {
2145
+ id: "radio-paradise",
2146
+ name: "Radio Paradise",
2147
+ description: "Eclectic mix \u2014 rock, electronica, jazz",
2148
+ url: "http://stream.radioparadise.com/mp3-128"
2149
+ },
2150
+ {
2151
+ id: "george-fm",
2152
+ name: "George FM",
2153
+ description: "NZ dance + electronic",
2154
+ url: "https://mediaworks.streamguys1.com/george_net_icy"
2155
+ }
2156
+ ];
2157
+ var PLAYERS = [
2158
+ { cmd: "mpv", args: (u) => ["--really-quiet", "--no-video", "--no-terminal", u] },
2159
+ {
2160
+ cmd: "ffplay",
2161
+ args: (u) => ["-nodisp", "-autoexit", "-loglevel", "quiet", u]
2162
+ },
2163
+ { cmd: "mpg123", args: (u) => ["-q", u] },
2164
+ { cmd: "cvlc", args: (u) => ["--play-and-exit", "--quiet", u] }
2165
+ ];
2166
+ var currentChild = null;
2167
+ var currentStationId = null;
2168
+ function getCurrentStation() {
2169
+ return currentStationId;
2170
+ }
2171
+ function stopRadio() {
2172
+ if (!currentChild) return;
2173
+ try {
2174
+ if (process.platform !== "win32" && currentChild.pid) {
2175
+ try {
2176
+ process.kill(-currentChild.pid, "SIGTERM");
2177
+ } catch {
2178
+ currentChild.kill("SIGTERM");
2179
+ }
2180
+ } else {
2181
+ currentChild.kill("SIGTERM");
2182
+ }
2183
+ } catch {
2184
+ }
2185
+ currentChild = null;
2186
+ currentStationId = null;
2187
+ log("INFO", "radio", "stopped");
2188
+ }
2189
+ function isWsl() {
2190
+ if (process.platform !== "linux") return false;
2191
+ return !!process.env.WSL_DISTRO_NAME || existsSync("/proc/sys/fs/binfmt_misc/WSLInterop");
2192
+ }
2193
+ function tryPlayOnWindowsHost(station) {
2194
+ const allowedUrls = new Set(RADIO_STATIONS.map((s) => s.url));
2195
+ if (!allowedUrls.has(station.url)) return null;
2196
+ if (!/^https?:\/\//i.test(station.url)) return null;
2197
+ const psScript = [
2198
+ "Add-Type -AssemblyName presentationCore;",
2199
+ "Add-Type -AssemblyName WindowsBase;",
2200
+ "$p = New-Object System.Windows.Media.MediaPlayer;",
2201
+ "$p.Open([uri]$env:GGBOSS_RADIO_URL);",
2202
+ "$p.Play();",
2203
+ "[System.Windows.Threading.Dispatcher]::Run();"
2204
+ ].join(" ");
2205
+ try {
2206
+ const child = spawn(
2207
+ "powershell.exe",
2208
+ ["-NoProfile", "-WindowStyle", "Hidden", "-Command", psScript],
2209
+ {
2210
+ detached: true,
2211
+ stdio: "ignore",
2212
+ env: {
2213
+ ...process.env,
2214
+ GGBOSS_RADIO_URL: station.url,
2215
+ WSLENV: (process.env.WSLENV ? process.env.WSLENV + ":" : "") + "GGBOSS_RADIO_URL"
2216
+ }
2217
+ }
2218
+ );
2219
+ return child;
2220
+ } catch {
2221
+ return null;
2222
+ }
2223
+ }
2224
+ function playRadio(stationId) {
2225
+ const station = RADIO_STATIONS.find((s) => s.id === stationId);
2226
+ if (!station) return { ok: false, error: `Unknown station: ${stationId}` };
2227
+ stopRadio();
2228
+ if (isWsl()) {
2229
+ const child = tryPlayOnWindowsHost(station);
2230
+ if (child) {
2231
+ let errored = false;
2232
+ child.once("error", () => {
2233
+ errored = true;
2234
+ });
2235
+ if (child.pid && !errored) {
2236
+ currentChild = child;
2237
+ currentStationId = stationId;
2238
+ log("INFO", "radio", "playing", {
2239
+ station: station.id,
2240
+ player: "powershell.exe (wsl\u2192host)",
2241
+ url: station.url
2242
+ });
2243
+ child.unref();
2244
+ return { ok: true };
2245
+ }
2246
+ }
2247
+ }
2248
+ for (const player of PLAYERS) {
2249
+ try {
2250
+ const child = spawn(player.cmd, player.args(station.url), {
2251
+ detached: process.platform !== "win32",
2252
+ stdio: "ignore"
2253
+ });
2254
+ let errored = false;
2255
+ child.once("error", () => {
2256
+ errored = true;
2257
+ });
2258
+ if (child.pid && !errored) {
2259
+ currentChild = child;
2260
+ currentStationId = stationId;
2261
+ log("INFO", "radio", "playing", {
2262
+ station: station.id,
2263
+ player: player.cmd,
2264
+ url: station.url
2265
+ });
2266
+ child.unref();
2267
+ return { ok: true };
2268
+ }
2269
+ } catch {
2270
+ }
2271
+ }
2272
+ log("WARN", "radio", "no compatible player found", { platform: process.platform });
2273
+ return {
2274
+ ok: false,
2275
+ error: buildInstallHint()
2276
+ };
2277
+ }
2278
+ function buildInstallHint() {
2279
+ const base = "Radio needs a streaming player. Install one of: mpv (recommended), ffplay, mpg123, or vlc.";
2280
+ switch (process.platform) {
2281
+ case "darwin":
2282
+ return `${base} On macOS: \`brew install mpv\` (or \`brew install ffmpeg\` for ffplay).`;
2283
+ case "linux":
2284
+ return `${base} On Linux (Debian/Ubuntu): \`sudo apt install mpv\`. Fedora: \`sudo dnf install mpv\`. Arch: \`sudo pacman -S mpv\`.`;
2285
+ case "win32":
2286
+ return `${base} On Windows: \`winget install mpv.mpv\` (or download from https://mpv.io).`;
2287
+ default:
2288
+ return `${base} See https://mpv.io for platform installation instructions.`;
2289
+ }
2290
+ }
2291
+
2292
+ // src/radio-picker.tsx
2293
+ var import_jsx_runtime10 = __toESM(require_jsx_runtime(), 1);
2294
+ function RadioPicker({
2295
+ currentStationId: currentStationId2,
2296
+ onSelect,
2297
+ onCancel
2298
+ }) {
2299
+ const items = [
2300
+ ...RADIO_STATIONS.map((s) => ({
2301
+ label: `${currentStationId2 === s.id ? "* " : " "}${s.name}`,
2302
+ value: s.id,
2303
+ description: s.description
2304
+ })),
2305
+ {
2306
+ label: `${currentStationId2 === null ? "* " : " "}Off`,
2307
+ value: "off",
2308
+ description: "Stop the radio"
2309
+ }
2310
+ ];
2311
+ const initialIndex = Math.max(
2312
+ 0,
2313
+ items.findIndex((i) => i.value === (currentStationId2 ?? "off"))
2314
+ );
2315
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
2316
+ SelectList,
2317
+ {
2318
+ items,
2319
+ onSelect: (v) => onSelect(v === "off" ? "off" : v),
2320
+ onCancel,
2321
+ initialIndex,
2322
+ windowSize: 6
2323
+ }
2324
+ );
2325
+ }
2326
+
2327
+ // src/auto-update.ts
2328
+ init_esm_shims();
2329
+ import { spawn as spawn2 } from "child_process";
2330
+ import fs4 from "fs";
2331
+ import path4 from "path";
2332
+ import os2 from "os";
2333
+ var PACKAGE_NAME = "@iroaxel/arcena";
2334
+ var REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
2335
+ var CHECK_INTERVAL_MS = 60 * 60 * 1e3;
2336
+ var FETCH_TIMEOUT_MS = 1e4;
2337
+ function getStateFilePath() {
2338
+ return path4.join(os2.homedir(), ".gg", "boss", "update-state.json");
2339
+ }
2340
+ function readState() {
2341
+ try {
2342
+ const raw = fs4.readFileSync(getStateFilePath(), "utf-8");
2343
+ return JSON.parse(raw);
2344
+ } catch {
2345
+ return null;
2346
+ }
2347
+ }
2348
+ function writeState(state) {
2349
+ try {
2350
+ const dir = path4.dirname(getStateFilePath());
2351
+ fs4.mkdirSync(dir, { recursive: true, mode: 448 });
2352
+ fs4.writeFileSync(getStateFilePath(), JSON.stringify(state));
2353
+ } catch {
2354
+ }
2355
+ }
2356
+ function compareVersions(a, b) {
2357
+ const pa = a.split(".").map(Number);
2358
+ const pb = b.split(".").map(Number);
2359
+ for (let i = 0; i < 3; i++) {
2360
+ const diff = (pa[i] ?? 0) - (pb[i] ?? 0);
2361
+ if (diff !== 0) return diff;
2362
+ }
2363
+ return 0;
2364
+ }
2365
+ function detectInstallInfo() {
2366
+ const scriptPath = (process.argv[1] ?? "").replace(/\\/g, "/");
2367
+ if (scriptPath.includes("/_npx/")) {
2368
+ return { packageManager: "unknown" /* UNKNOWN */, updateCommand: null };
2369
+ }
2370
+ if (scriptPath.includes("/.pnpm") || scriptPath.includes("/pnpm/global")) {
2371
+ return {
2372
+ packageManager: "pnpm" /* PNPM */,
2373
+ updateCommand: `pnpm add -g ${PACKAGE_NAME}@latest`
2374
+ };
2375
+ }
2376
+ if (scriptPath.includes("/.yarn/") || scriptPath.includes("/yarn/global")) {
2377
+ return {
2378
+ packageManager: "yarn" /* YARN */,
2379
+ updateCommand: `yarn global add ${PACKAGE_NAME}@latest`
2380
+ };
2381
+ }
2382
+ return {
2383
+ packageManager: "npm" /* NPM */,
2384
+ updateCommand: `npm install -g ${PACKAGE_NAME}@latest`
2385
+ };
2386
+ }
2387
+ async function fetchLatestVersion() {
2388
+ try {
2389
+ const controller = new AbortController();
2390
+ const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
2391
+ const response = await fetch(REGISTRY_URL, { signal: controller.signal });
2392
+ clearTimeout(timeout);
2393
+ const data = await response.json();
2394
+ const version = data.version?.trim();
2395
+ return version && /^\d+\.\d+\.\d+/.test(version) ? version : null;
2396
+ } catch {
2397
+ return null;
2398
+ }
2399
+ }
2400
+ function performUpdateInBackground(command) {
2401
+ try {
2402
+ const parts = command.split(" ");
2403
+ const child = spawn2(parts[0], parts.slice(1), {
2404
+ detached: true,
2405
+ stdio: "ignore",
2406
+ env: { ...process.env, npm_config_loglevel: "silent" }
2407
+ });
2408
+ child.unref();
2409
+ } catch {
2410
+ }
2411
+ }
2412
+ function checkAndAutoUpdate(currentVersion) {
2413
+ try {
2414
+ const state = readState();
2415
+ let message = null;
2416
+ if (state?.updatePending && state.latestVersion) {
2417
+ if (compareVersions(state.latestVersion, currentVersion) > 0) {
2418
+ const info = detectInstallInfo();
2419
+ if (info.updateCommand) {
2420
+ performUpdateInBackground(info.updateCommand);
2421
+ message = `Axel just shipped ${state.latestVersion}! Installing in the background \u2014 takes effect next launch.`;
2422
+ writeState({
2423
+ ...state,
2424
+ lastCheckedAt: Date.now(),
2425
+ updatePending: false,
2426
+ lastUpdateAttempt: Date.now()
2427
+ });
2428
+ }
2429
+ } else {
2430
+ writeState({ ...state, updatePending: false });
2431
+ }
2432
+ }
2433
+ const shouldCheck = !state || Date.now() - state.lastCheckedAt > CHECK_INTERVAL_MS;
2434
+ if (shouldCheck) scheduleBackgroundCheck(currentVersion);
2435
+ return message;
2436
+ } catch {
2437
+ return null;
2438
+ }
2439
+ }
2440
+ function getPendingUpdate(currentVersion) {
2441
+ try {
2442
+ const state = readState();
2443
+ if (!state?.latestVersion) return null;
2444
+ if (compareVersions(state.latestVersion, currentVersion) <= 0) return null;
2445
+ return { latestVersion: state.latestVersion };
2446
+ } catch {
2447
+ return null;
2448
+ }
2449
+ }
2450
+ function scheduleBackgroundCheck(currentVersion) {
2451
+ fetchLatestVersion().then((latestVersion) => {
2452
+ const newState = {
2453
+ lastCheckedAt: Date.now(),
2454
+ latestVersion: latestVersion ?? void 0,
2455
+ updatePending: false
2456
+ };
2457
+ if (latestVersion && compareVersions(latestVersion, currentVersion) > 0) {
2458
+ newState.updatePending = true;
2459
+ }
2460
+ writeState(newState);
2461
+ }).catch(() => {
2462
+ });
2463
+ }
2464
+ var periodicTimer = null;
2465
+ function startPeriodicUpdateCheck(currentVersion, onUpdate) {
2466
+ if (periodicTimer) return;
2467
+ periodicTimer = setInterval(() => {
2468
+ fetchLatestVersion().then((latestVersion) => {
2469
+ if (!latestVersion) return;
2470
+ if (compareVersions(latestVersion, currentVersion) <= 0) return;
2471
+ const info = detectInstallInfo();
2472
+ if (!info.updateCommand) return;
2473
+ writeState({
2474
+ lastCheckedAt: Date.now(),
2475
+ latestVersion,
2476
+ updatePending: true
2477
+ });
2478
+ onUpdate(
2479
+ `Ken just pushed a fresh update \u2014 ${currentVersion} \u2192 ${latestVersion}! Restart arcena to grab it (or run ${info.updateCommand} if you can't wait).`
2480
+ );
2481
+ stopPeriodicUpdateCheck();
2482
+ }).catch(() => {
2483
+ });
2484
+ }, CHECK_INTERVAL_MS);
2485
+ periodicTimer.unref();
2486
+ }
2487
+ function stopPeriodicUpdateCheck() {
2488
+ if (periodicTimer) {
2489
+ clearInterval(periodicTimer);
2490
+ periodicTimer = null;
2491
+ }
2492
+ }
2493
+
2494
+ // src/orchestrator-app.tsx
2495
+ var import_jsx_runtime11 = __toESM(require_jsx_runtime(), 1);
2496
+ function BossApp(props) {
2497
+ const theme = loadTheme("dark");
2498
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TerminalSizeProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ThemeContext.Provider, { value: theme, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AnimationProvider, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossAppInner, { ...props }) }) }) });
2499
+ }
2500
+ function BossAppInner({ boss, resetUI }) {
2501
+ const state = useBossState();
2502
+ const { exit } = use_app_default();
2503
+ const { stdout } = use_stdout_default();
2504
+ const { resizeKey, columns, rows } = useTerminalSize();
2505
+ const runStartRef = (0, import_react11.useRef)(null);
2506
+ runStartRef.current = state.runStartMs;
2507
+ const charCountRef = (0, import_react11.useRef)(0);
2508
+ charCountRef.current = state.streaming?.text.length ?? 0;
2509
+ const realTokensAccumRef = (0, import_react11.useRef)(0);
2510
+ realTokensAccumRef.current = state.bossInputTokens;
2511
+ const [lastUserMessage, setLastUserMessage] = (0, import_react11.useState)("");
2512
+ const overlay = state.overlay;
2513
+ const [currentRadio, setCurrentRadio] = (0, import_react11.useState)(() => getCurrentStation());
2514
+ const [updatePending, setUpdatePending] = (0, import_react11.useState)(
2515
+ () => getPendingUpdate(VERSION) !== null
2516
+ );
2517
+ (0, import_react11.useEffect)(() => {
2518
+ startPeriodicUpdateCheck(VERSION, (msg) => {
2519
+ bossStore.appendUpdateNotice(msg);
2520
+ setUpdatePending(true);
2521
+ });
2522
+ return () => stopPeriodicUpdateCheck();
2523
+ }, []);
2524
+ const workersRunning = state.workers.filter((w) => w.status === "working").length;
2525
+ const titlePrevRef = (0, import_react11.useRef)("");
2526
+ (0, import_react11.useEffect)(() => {
2527
+ if (!stdout) return;
2528
+ let title;
2529
+ if (workersRunning > 0) {
2530
+ const label = `${workersRunning} worker${workersRunning === 1 ? "" : "s"} running`;
2531
+ title = `\u25CF ${label} \xB7 ARCena`;
2532
+ } else if (state.phase === "working") {
2533
+ title = "\u25CF ARCena";
2534
+ } else {
2535
+ title = "ARCena";
2536
+ }
2537
+ if (title !== titlePrevRef.current) {
2538
+ titlePrevRef.current = title;
2539
+ stdout.write(`\x1B]0;${title}\x1B\\`);
2540
+ }
2541
+ }, [stdout, workersRunning, state.phase]);
2542
+ (0, import_react11.useEffect)(() => {
2543
+ return () => {
2544
+ stdout?.write(`\x1B]0;ARCena\x1B\\`);
2545
+ };
2546
+ }, [stdout]);
2547
+ const staticItems = (0, import_react11.useMemo)(
2548
+ () => [{ kind: "banner", id: "banner" }, ...state.history],
2549
+ [state.history]
2550
+ );
2551
+ const openOverlay = (0, import_react11.useCallback)(
2552
+ (next) => {
2553
+ bossStore.setOverlay(next);
2554
+ if (resetUI) resetUI();
2555
+ },
2556
+ [resetUI]
2557
+ );
2558
+ const closeOverlay = (0, import_react11.useCallback)(() => {
2559
+ bossStore.setOverlay(null);
2560
+ if (resetUI) resetUI();
2561
+ }, [resetUI]);
2562
+ void stdout;
2563
+ const handleDoubleExit = useDoublePress(
2564
+ (pending) => bossStore.setExitPending(pending),
2565
+ () => exit()
2566
+ );
2567
+ (0, import_react11.useEffect)(() => {
2568
+ if (state.pendingFlush.length > 0) {
2569
+ bossStore.commitPendingFlush();
2570
+ }
2571
+ }, [state.flushGeneration, state.pendingFlush.length]);
2572
+ use_input_default((input, key) => {
2573
+ if (key.ctrl && input === "t") {
2574
+ if (overlay === "tasks") closeOverlay();
2575
+ else openOverlay("tasks");
2576
+ return;
2577
+ }
2578
+ if (key.escape && state.phase === "working") {
2579
+ boss.abort();
2580
+ }
2581
+ });
2582
+ const handleSlashCommand = async (value) => {
2583
+ const parsed = parseSlash(value);
2584
+ if (!parsed) return false;
2585
+ const name = canonicalName(parsed.name);
2586
+ if (!name) {
2587
+ bossStore.appendInfo(`Unknown command: /${parsed.name}`, "warning");
2588
+ return true;
2589
+ }
2590
+ switch (name) {
2591
+ case "help":
2592
+ bossStore.appendUser(value);
2593
+ bossStore.appendInfo(buildHelpText(), "info");
2594
+ return true;
2595
+ case "clear":
2596
+ bossStore.clearHistory();
2597
+ resetUI?.();
2598
+ await boss.resetConversation();
2599
+ bossStore.appendInfo("Session cleared.", "info");
2600
+ return true;
2601
+ case "model-boss":
2602
+ openOverlay("model-boss");
2603
+ return true;
2604
+ case "model-workers":
2605
+ openOverlay("model-workers");
2606
+ return true;
2607
+ case "compact":
2608
+ bossStore.appendUser(value);
2609
+ await boss.manualCompact();
2610
+ return true;
2611
+ case "radio":
2612
+ openOverlay("radio");
2613
+ return true;
2614
+ case "quit":
2615
+ exit();
2616
+ return true;
2617
+ }
2618
+ return false;
2619
+ };
2620
+ const handleModelSelect = (value) => {
2621
+ const colon = value.indexOf(":");
2622
+ if (colon < 0) {
2623
+ closeOverlay();
2624
+ return;
2625
+ }
2626
+ const provider = value.slice(0, colon);
2627
+ const model = value.slice(colon + 1);
2628
+ if (overlay === "model-boss") {
2629
+ void boss.switchBossModel(provider, model);
2630
+ } else if (overlay === "model-workers") {
2631
+ void boss.switchWorkerModel(provider, model);
2632
+ }
2633
+ closeOverlay();
2634
+ };
2635
+ const handleSubmit = (value) => {
2636
+ const trimmed = value.trim();
2637
+ if (!trimmed) return;
2638
+ if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
2639
+ void handleSlashCommand(trimmed);
2640
+ return;
2641
+ }
2642
+ bossStore.appendUser(trimmed);
2643
+ setLastUserMessage(trimmed);
2644
+ const scoped = scopePrefix2(state.scope) + trimmed;
2645
+ boss.enqueueUserMessage(scoped);
2646
+ };
2647
+ const handleAbort = () => {
2648
+ if (state.phase === "working") {
2649
+ boss.abort();
2650
+ return;
2651
+ }
2652
+ handleDoubleExit();
2653
+ };
2654
+ if (rows < 14) {
2655
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: columns, paddingX: 1, marginTop: 1, children: [
2656
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { bold: true, color: COLORS.accent, children: "Terminal too small" }),
2657
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, children: `Resize to at least 14 rows to use ARCena (currently ${rows}).` })
2658
+ ] });
2659
+ }
2660
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", width: columns, children: [
2661
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Static, { items: staticItems, style: { width: "100%" }, children: (item) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexDirection: "column", paddingRight: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StaticRowView, { row: item }) }, item.id) }, resizeKey),
2662
+ overlay === "tasks" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossTasksOverlay, { boss, workers: state.workers, onClose: closeOverlay }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2663
+ state.streaming && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingTurnView, { turn: state.streaming, isRunning: state.phase === "working" }),
2664
+ state.phase === "working" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2665
+ ActivityIndicator,
2666
+ {
2667
+ phase: state.activityPhase,
2668
+ elapsedMs: state.runStartMs ? Date.now() - state.runStartMs : 0,
2669
+ runStartRef,
2670
+ thinkingMs: state.streaming?.thinkingMs ?? 0,
2671
+ isThinking: state.activityPhase === "thinking",
2672
+ tokenEstimate: state.bossInputTokens,
2673
+ charCountRef,
2674
+ realTokensAccumRef,
2675
+ userMessage: lastUserMessage,
2676
+ activeToolNames: (state.streaming?.tools ?? []).filter((t) => t.status === "running").map((t) => t.name),
2677
+ retryInfo: state.retryInfo,
2678
+ phrases: BOSS_PHRASES,
2679
+ pulseColors: PULSE_COLORS
2680
+ }
2681
+ ) }),
2682
+ state.compaction?.state === "running" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(CompactionSpinner, {}),
2683
+ state.compaction?.state === "done" && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2684
+ CompactionDone,
2685
+ {
2686
+ originalCount: state.compaction.originalCount,
2687
+ newCount: state.compaction.newCount,
2688
+ tokensBefore: state.compaction.tokensBefore,
2689
+ tokensAfter: state.compaction.tokensAfter
2690
+ }
2691
+ ),
2692
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2693
+ InputArea,
2694
+ {
2695
+ onSubmit: handleSubmit,
2696
+ onAbort: handleAbort,
2697
+ disabled: state.phase === "working",
2698
+ isActive: !overlay,
2699
+ cwd: process.cwd(),
2700
+ commands: BOSS_SLASH_COMMANDS,
2701
+ scopeBadge: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ScopePill, { scope: state.scope }),
2702
+ disableMouseTracking: true,
2703
+ onTab: () => bossStore.cycleScope(),
2704
+ onShiftTab: () => {
2705
+ const next = state.bossThinkingLevel ? void 0 : "medium";
2706
+ void boss.setBossThinking(next);
2707
+ }
2708
+ }
2709
+ ),
2710
+ overlay === "model-boss" || overlay === "model-workers" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2711
+ ModelSelector,
2712
+ {
2713
+ onSelect: handleModelSelect,
2714
+ onCancel: closeOverlay,
2715
+ loggedInProviders: state.loggedInProviders,
2716
+ currentModel: overlay === "model-boss" ? state.bossModel : state.workerModel,
2717
+ currentProvider: overlay === "model-boss" ? state.bossProvider : state.workerProvider
2718
+ }
2719
+ ) : overlay === "radio" ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2720
+ RadioPicker,
2721
+ {
2722
+ currentStationId: currentRadio,
2723
+ onCancel: closeOverlay,
2724
+ onSelect: (value) => {
2725
+ if (value === "off") {
2726
+ stopRadio();
2727
+ setCurrentRadio(null);
2728
+ bossStore.appendInfo("Radio off.", "info");
2729
+ } else {
2730
+ const result = playRadio(value);
2731
+ if (result.ok) {
2732
+ setCurrentRadio(value);
2733
+ const station = RADIO_STATIONS.find((s) => s.id === value);
2734
+ bossStore.appendInfo(`Now playing: ${station?.name ?? value}`, "info");
2735
+ } else {
2736
+ bossStore.appendInfo(result.error ?? "Radio failed to start.", "warning");
2737
+ }
2738
+ }
2739
+ closeOverlay();
2740
+ }
2741
+ }
2742
+ ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2743
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2744
+ BossFooter,
2745
+ {
2746
+ bossModel: state.bossModel,
2747
+ workerModel: state.workerModel,
2748
+ tokensIn: state.bossInputTokens,
2749
+ exitPending: state.exitPending,
2750
+ bossThinkingLevel: state.bossThinkingLevel,
2751
+ updatePending,
2752
+ currentRadioStationId: currentRadio
2753
+ }
2754
+ ),
2755
+ !state.exitPending && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2756
+ WorkerStatusBar,
2757
+ {
2758
+ workers: state.workers,
2759
+ pendingMessages: state.pendingUserMessages
2760
+ }
2761
+ )
2762
+ ] })
2763
+ ] })
2764
+ ] });
2765
+ }
2766
+ function ScopePill({ scope }) {
2767
+ const theme = useTheme();
2768
+ const isAll = scope === "all";
2769
+ const bg = isAll ? COLORS.accent : projectColor(scope);
2770
+ const label = isAll ? "All" : scope;
2771
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
2772
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: "Project " }),
2773
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: "black", backgroundColor: bg, bold: true, children: ` ${label} ` }),
2774
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
2775
+ " ",
2776
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.primary, children: "Tab" }),
2777
+ " to switch"
2778
+ ] })
2779
+ ] });
2780
+ }
2781
+ function scopePrefix2(scope) {
2782
+ if (scope === "all") return "[scope:all] ";
2783
+ return `[scope:${scope}] `;
2784
+ }
2785
+ var SHIMMER_WIDTH = 3;
2786
+ function formatElapsed(ms) {
2787
+ const total = Math.floor(ms / 1e3);
2788
+ const m = Math.floor(total / 60);
2789
+ const s = total % 60;
2790
+ return `${m}:${s.toString().padStart(2, "0")}`;
2791
+ }
2792
+ function AnimationActiveSentinel() {
2793
+ useAnimationActive();
2794
+ return null;
2795
+ }
2796
+ function ShimmerName({
2797
+ name,
2798
+ color,
2799
+ tick
2800
+ }) {
2801
+ const cycle = name.length + SHIMMER_WIDTH * 2;
2802
+ const shimmerPos = tick % cycle - SHIMMER_WIDTH;
2803
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { children: name.split("").map((ch, i) => {
2804
+ const isBright = Math.abs(i - shimmerPos) <= SHIMMER_WIDTH;
2805
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color, bold: isBright, dimColor: !isBright, children: ch }, i);
2806
+ }) });
2807
+ }
2808
+ function WorkerStatusBar({
2809
+ workers,
2810
+ pendingMessages
2811
+ }) {
2812
+ const theme = useTheme();
2813
+ const { columns } = useTerminalSize();
2814
+ const working = workers.filter((w) => w.status === "working");
2815
+ const errored = workers.filter((w) => w.status === "error");
2816
+ const idleCount = workers.length - working.length - errored.length;
2817
+ const anyWorking = working.length > 0;
2818
+ const tick = useAnimationTick();
2819
+ const now = Date.now();
2820
+ if (workers.length === 0) return null;
2821
+ const slots = [];
2822
+ for (const w of working) {
2823
+ const projectHue = projectColor(w.name);
2824
+ const elapsed = w.workStartedAt ? formatElapsed(now - w.workStartedAt) : null;
2825
+ slots.push(
2826
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
2827
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ShimmerName, { name: w.name, color: projectHue, tick }),
2828
+ elapsed && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
2829
+ " ",
2830
+ elapsed
2831
+ ] })
2832
+ ] }, `w-${w.name}`)
2833
+ );
2834
+ }
2835
+ for (const w of errored) {
2836
+ slots.push(
2837
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.error, children: [
2838
+ "\u2717 ",
2839
+ w.name
2840
+ ] }) }, `e-${w.name}`)
2841
+ );
2842
+ }
2843
+ if (idleCount > 0) {
2844
+ slots.push(
2845
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react11.default.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.textDim, children: [
2846
+ "\u25CB ",
2847
+ idleCount,
2848
+ " idle"
2849
+ ] }) }, "idle")
2850
+ );
2851
+ }
2852
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { paddingX: 1, width: columns, flexShrink: 1, children: [
2853
+ anyWorking && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AnimationActiveSentinel, {}),
2854
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "truncate", children: [
2855
+ slots.map((slot, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react11.default.Fragment, { children: [
2856
+ i > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.border, children: " \u2502 " }),
2857
+ slot
2858
+ ] }, i)),
2859
+ pendingMessages > 0 && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
2860
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " " }),
2861
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.warning, children: [
2862
+ pendingMessages,
2863
+ " message",
2864
+ pendingMessages === 1 ? "" : "s",
2865
+ " queued"
2866
+ ] })
2867
+ ] })
2868
+ ] })
2869
+ ] });
2870
+ }
2871
+ function StaticRowView({ row }) {
2872
+ if (row.kind === "banner") {
2873
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossBanner, { subtitle: "Orchestrator", showShortcuts: true }) });
2874
+ }
2875
+ if (row.kind === "user") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UserMessage, { text: row.text });
2876
+ if (row.kind === "assistant") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssistantRow, { item: row });
2877
+ if (row.kind === "tool") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolHistoryRow, { item: row });
2878
+ if (row.kind === "worker_event") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(WorkerEventRow, { item: row });
2879
+ if (row.kind === "worker_error") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(WorkerErrorRow, { item: row });
2880
+ if (row.kind === "info") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(InfoRow, { text: row.text, level: row.level ?? "info" });
2881
+ if (row.kind === "task_dispatch") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TaskDispatchRow, { tasks: row.tasks });
2882
+ if (row.kind === "update_notice") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(UpdateNoticeRow, { text: row.text });
2883
+ return null;
2884
+ }
2885
+ function UpdateNoticeRow({ text }) {
2886
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { marginTop: 1, flexShrink: 1, borderStyle: "round", borderColor: COLORS.accent, paddingX: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
2887
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.accent, bold: true, children: "\u2728 " }),
2888
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, bold: true, children: text })
2889
+ ] }) });
2890
+ }
2891
+ function TaskDispatchRow({
2892
+ tasks
2893
+ }) {
2894
+ const theme = useTheme();
2895
+ const count = tasks.length;
2896
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", paddingX: 1, marginTop: 1, children: [
2897
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
2898
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: COLORS.primary, bold: true, children: "\u23FA " }),
2899
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: theme.text, bold: true, children: [
2900
+ "Running ",
2901
+ count,
2902
+ " task",
2903
+ count === 1 ? "" : "s",
2904
+ ":"
2905
+ ] })
2906
+ ] }),
2907
+ tasks.map((t, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { children: [
2908
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " \u2022 " }),
2909
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: projectColor(t.project), bold: true, children: t.project }),
2910
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: ": " }),
2911
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: t.title })
2912
+ ] }, `${t.project}-${i}`))
2913
+ ] });
2914
+ }
2915
+ var SHORTCUT_PATTERNS = [
2916
+ // Modifier+Key combos: Ctrl+T, Shift+Tab, Cmd+K, Ctrl+Shift+P, Ctrl+C
2917
+ /\b(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super)(?:\s*\+\s*(?:Ctrl|Cmd|Alt|Option|Opt|Shift|Meta|Win|Super))*\s*\+\s*(?:Tab|Enter|Esc|Escape|Space|Backspace|Delete|Del|Home|End|PageUp|PageDown|Up|Down|Left|Right|F[1-9]|F1[0-2]|[A-Z0-9]|\/|\?|\.|,|;|=|-)\b/g,
2918
+ // Bare named keys (only when surrounded by clear key context)
2919
+ /\b(?:Ctrl-[A-Z]|F[1-9]|F1[0-2])\b/g
2920
+ ];
2921
+ function highlightShortcuts(text) {
2922
+ if (!text) return text;
2923
+ const SENTINEL = "\uE000";
2924
+ const masks = [];
2925
+ let masked = text.replace(/```[\s\S]*?```|`[^`]+`/g, (m) => {
2926
+ const idx = masks.push(m) - 1;
2927
+ return `${SENTINEL}${idx}${SENTINEL}`;
2928
+ });
2929
+ for (const re of SHORTCUT_PATTERNS) {
2930
+ masked = masked.replace(re, (m) => `\`${m}\``);
2931
+ }
2932
+ return masked.replace(
2933
+ new RegExp(`${SENTINEL}(\\d+)${SENTINEL}`, "g"),
2934
+ (_, i) => masks[Number(i)]
2935
+ );
2936
+ }
2937
+ function AssistantRow({ item }) {
2938
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2939
+ AssistantMessage,
2940
+ {
2941
+ text: highlightShortcuts(item.text),
2942
+ thinking: item.thinking,
2943
+ thinkingMs: item.thinkingMs
2944
+ }
2945
+ );
2946
+ }
2947
+ function ToolHistoryRow({ item }) {
2948
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
2949
+ ToolExecution,
2950
+ {
2951
+ status: "done",
2952
+ name: item.name,
2953
+ args: item.args,
2954
+ result: item.result,
2955
+ isError: item.isError,
2956
+ details: item.details,
2957
+ formatters: bossToolFormatters
2958
+ }
2959
+ );
2960
+ }
2961
+ function parseStatusGrade(text) {
2962
+ const matches = [...text.matchAll(/^\s*Status:\s*(DONE|UNVERIFIED|PARTIAL|BLOCKED|INFO)\b/gim)];
2963
+ const last = matches[matches.length - 1];
2964
+ if (!last) return null;
2965
+ return last[1].toUpperCase();
2966
+ }
2967
+ function parseWorkerTrailer(text) {
2968
+ const out = {};
2969
+ const grab = (label) => {
2970
+ const re = new RegExp(
2971
+ `^\\s*${label}:\\s*([\\s\\S]*?)(?=^\\s*(?:Changed|Skipped|Verified|Notes|Status):|$)`,
2972
+ "im"
2973
+ );
2974
+ const m = re.exec(text);
2975
+ if (!m) return void 0;
2976
+ const v = m[1].replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
2977
+ return v.length > 0 ? v : void 0;
2978
+ };
2979
+ out.changed = grab("Changed");
2980
+ out.skipped = grab("Skipped");
2981
+ out.verified = grab("Verified");
2982
+ out.notes = grab("Notes");
2983
+ return out;
2984
+ }
2985
+ function clip(text, maxLen) {
2986
+ return text.length <= maxLen ? text : text.slice(0, Math.max(1, maxLen - 1)) + "\u2026";
2987
+ }
2988
+ function summarizeFinalText(text, maxLen) {
2989
+ if (!text) return "";
2990
+ const trailer = parseWorkerTrailer(text);
2991
+ const parts = [];
2992
+ if (trailer.changed) parts.push(`Changed: ${trailer.changed}`);
2993
+ if (trailer.verified) parts.push(`Verified: ${trailer.verified}`);
2994
+ if (trailer.skipped) parts.push(`Skipped: ${trailer.skipped}`);
2995
+ if (trailer.notes) parts.push(`Notes: ${trailer.notes}`);
2996
+ if (parts.length > 0) return clip(parts.join(" \xB7 "), maxLen);
2997
+ const beforeSummary = text.split(/^Changed:|^Skipped:|^Verified:|^Notes:|^Status:/im)[0];
2998
+ const stripped = beforeSummary.replace(/```[\s\S]*?```/g, "[code]").replace(/`([^`]+)`/g, "$1").replace(/\*\*([^*]+)\*\*/g, "$1").replace(/\*([^*]+)\*/g, "$1").replace(/^\s*[-*]\s+/gm, "").replace(/^#+\s+/gm, "").replace(/\s+/g, " ").trim();
2999
+ if (!stripped) return "";
3000
+ const firstSentence = stripped.match(/^[^.!?\n]+[.!?]/);
3001
+ return clip(firstSentence ? firstSentence[0] : stripped, maxLen);
3002
+ }
3003
+ function statusGradeColor(grade, theme) {
3004
+ switch (grade) {
3005
+ case "DONE":
3006
+ return theme.success;
3007
+ case "UNVERIFIED":
3008
+ case "PARTIAL":
3009
+ return theme.warning;
3010
+ case "BLOCKED":
3011
+ return theme.error;
3012
+ case "INFO":
3013
+ return theme.textDim;
3014
+ default:
3015
+ return theme.textDim;
3016
+ }
3017
+ }
3018
+ function WorkerEventRow({ item }) {
3019
+ const theme = useTheme();
3020
+ const { columns } = useTerminalSize();
3021
+ const failedCount = item.toolsUsed.filter((t) => !t.ok).length;
3022
+ const total = item.toolsUsed.length;
3023
+ const grade = parseStatusGrade(item.finalText);
3024
+ const loaderStatus = grade === "BLOCKED" || failedCount > 0 ? "error" : grade === "UNVERIFIED" || grade === "PARTIAL" ? "queued" : "done";
3025
+ const headerColor = loaderStatus === "error" ? theme.toolError : projectColor(item.project);
3026
+ const toolSummary = total === 0 ? "no tools" : failedCount > 0 ? `${total} tools (${failedCount} failed)` : `${total} tool${total === 1 ? "" : "s"}`;
3027
+ const fieldMaxLen = Math.max(20, columns - 14);
3028
+ const trailer = parseWorkerTrailer(item.finalText);
3029
+ const hasTrailer = !!(trailer.changed || trailer.skipped || trailer.verified || trailer.notes);
3030
+ const fallbackSummary = hasTrailer ? "" : summarizeFinalText(item.finalText, fieldMaxLen);
3031
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
3032
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "row", children: [
3033
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: loaderStatus }),
3034
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
3035
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: headerColor, bold: true, children: item.project }),
3036
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: ` turn ${item.turnIndex}` }),
3037
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: ` \xB7 ${toolSummary}` }),
3038
+ grade && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3039
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " \xB7 " }),
3040
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: statusGradeColor(grade, theme), bold: true, children: grade })
3041
+ ] })
3042
+ ] }) })
3043
+ ] }),
3044
+ hasTrailer ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
3045
+ trailer.changed && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrailerLine, { label: "Changed", value: trailer.changed, maxLen: fieldMaxLen }),
3046
+ trailer.verified && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3047
+ TrailerLine,
3048
+ {
3049
+ label: "Verified",
3050
+ value: trailer.verified,
3051
+ maxLen: fieldMaxLen,
3052
+ labelColor: theme.success
3053
+ }
3054
+ ),
3055
+ trailer.skipped && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3056
+ TrailerLine,
3057
+ {
3058
+ label: "Skipped",
3059
+ value: trailer.skipped,
3060
+ maxLen: fieldMaxLen,
3061
+ labelColor: theme.warning
3062
+ }
3063
+ ),
3064
+ trailer.notes && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(TrailerLine, { label: "Notes", value: trailer.notes, maxLen: fieldMaxLen })
3065
+ ] }) : fallbackSummary && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, wrap: "truncate", children: fallbackSummary }) })
3066
+ ] });
3067
+ }
3068
+ function TrailerLine({
3069
+ label,
3070
+ value,
3071
+ maxLen,
3072
+ labelColor
3073
+ }) {
3074
+ const theme = useTheme();
3075
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "truncate", children: [
3076
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { color: labelColor ?? theme.textDim, bold: true, children: [
3077
+ label,
3078
+ ":"
3079
+ ] }),
3080
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.text, children: ` ${clip(value, maxLen - label.length - 2)}` })
3081
+ ] }) });
3082
+ }
3083
+ function WorkerErrorRow({ item }) {
3084
+ const theme = useTheme();
3085
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", marginTop: 1, children: [
3086
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "row", children: [
3087
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: "error" }),
3088
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Text, { wrap: "wrap", children: [
3089
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.toolError, bold: true, children: item.project }),
3090
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.textDim, children: " worker error" })
3091
+ ] }) })
3092
+ ] }),
3093
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(MessageResponse, { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color: theme.error, wrap: "wrap", children: item.message }) })
3094
+ ] });
3095
+ }
3096
+ function InfoRow({
3097
+ text,
3098
+ level
3099
+ }) {
3100
+ if (level === "info") return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(AssistantMessage, { text });
3101
+ const theme = useTheme();
3102
+ const color = level === "error" ? theme.error : theme.warning;
3103
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { marginTop: 1, flexDirection: "row", children: [
3104
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(ToolUseLoader, { status: level === "error" ? "error" : "queued" }),
3105
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Box_default, { flexGrow: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Text, { color, wrap: "wrap", children: text }) })
3106
+ ] });
3107
+ }
3108
+ function StreamingTurnView({
3109
+ turn,
3110
+ isRunning
3111
+ }) {
3112
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(Box_default, { flexDirection: "column", children: [
3113
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3114
+ StreamingArea,
3115
+ {
3116
+ isRunning,
3117
+ streamingText: turn.text,
3118
+ streamingThinking: turn.thinking,
3119
+ thinkingMs: turn.thinkingMs
3120
+ }
3121
+ ),
3122
+ turn.tools.map((t) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(StreamingToolRow, { tool: t }, t.toolCallId))
3123
+ ] });
3124
+ }
3125
+ function StreamingToolRow({ tool }) {
3126
+ if (tool.status === "running") {
3127
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3128
+ ToolExecution,
3129
+ {
3130
+ status: "running",
3131
+ name: tool.name,
3132
+ args: tool.args,
3133
+ formatters: bossToolFormatters
3134
+ }
3135
+ );
3136
+ }
3137
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
3138
+ ToolExecution,
3139
+ {
3140
+ status: "done",
3141
+ name: tool.name,
3142
+ args: tool.args,
3143
+ result: tool.result ?? "",
3144
+ isError: tool.status === "error",
3145
+ details: tool.details,
3146
+ formatters: bossToolFormatters
3147
+ }
3148
+ );
3149
+ }
3150
+ function renderBossApp(opts) {
3151
+ const ref = { instance: null };
3152
+ const resetUI = () => {
3153
+ const old = ref.instance;
3154
+ if (!old) return;
3155
+ process.stdout.write("\x1B[2J\x1B[3J\x1B[H");
3156
+ old.unmount();
3157
+ ref.instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossApp, { boss: opts.boss, resetUI }), {
3158
+ exitOnCtrlC: false
3159
+ });
3160
+ };
3161
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime11.jsx)(BossApp, { boss: opts.boss, resetUI }), {
3162
+ // Disable Ink's built-in exit-on-Ctrl+C — we need our own double-press
3163
+ // handler in BossApp to drive the "Press Ctrl+C again to exit" footer
3164
+ // message. With this flag true (the default), Ink kills the process on
3165
+ // the very first Ctrl+C and InputArea's onAbort never runs.
3166
+ exitOnCtrlC: false
3167
+ });
3168
+ ref.instance = instance;
3169
+ let resizeTimer = null;
3170
+ const onTerminalResize = () => {
3171
+ if (resizeTimer) clearTimeout(resizeTimer);
3172
+ resizeTimer = setTimeout(() => {
3173
+ resizeTimer = null;
3174
+ resetUI();
3175
+ }, 250);
3176
+ };
3177
+ process.stdout.on("resize", onTerminalResize);
3178
+ return {
3179
+ // Follow ref.instance through restarts: when /clear nukes the current
3180
+ // instance and creates a new one, this promise re-binds to whichever
3181
+ // Ink instance is alive now. Without the loop, we'd wait on the OLD
3182
+ // instance's waitUntilExit (which already resolved on unmount) and
3183
+ // exit the CLI immediately after every /clear.
3184
+ waitUntilExit: async () => {
3185
+ while (true) {
3186
+ const current = ref.instance;
3187
+ if (!current) {
3188
+ process.stdout.off("resize", onTerminalResize);
3189
+ if (resizeTimer) clearTimeout(resizeTimer);
3190
+ return;
3191
+ }
3192
+ await current.waitUntilExit();
3193
+ if (ref.instance === current) {
3194
+ ref.instance = null;
3195
+ process.stdout.off("resize", onTerminalResize);
3196
+ if (resizeTimer) clearTimeout(resizeTimer);
3197
+ return;
3198
+ }
3199
+ }
3200
+ },
3201
+ unmount: () => {
3202
+ process.stdout.off("resize", onTerminalResize);
3203
+ if (resizeTimer) clearTimeout(resizeTimer);
3204
+ ref.instance?.unmount();
3205
+ }
3206
+ };
3207
+ }
3208
+
3209
+ // src/splash.tsx
3210
+ init_esm_shims();
3211
+ var import_react12 = __toESM(require_react(), 1);
3212
+ var import_jsx_runtime12 = __toESM(require_jsx_runtime(), 1);
3213
+ var SPLASH_LINES = [
3214
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 ",
3215
+ " \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588 ",
3216
+ " \u2588\u2588\u2588 \u2591\u2591\u2591 \u2588\u2588\u2588 \u2591\u2591\u2591 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 ",
3217
+ "\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2591\u2591 \u2588\u2588\u2588\u2591\u2591 ",
3218
+ "\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2591\u2591\u2591\u2591\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2591\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588 ",
3219
+ "\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588\u2591\u2588\u2588\u2588 \u2591\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2588\u2588\u2588 \u2591\u2591\u2591\u2591\u2588\u2588\u2588",
3220
+ " \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588 \u2591\u2591\u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 \u2588\u2588\u2588\u2588\u2588\u2588 ",
3221
+ " \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 \u2591\u2591\u2591\u2591\u2591\u2591 "
3222
+ ];
3223
+ var SPLASH_WIDTH = SPLASH_LINES[0].length;
3224
+ function colorForLine(lineIdx, totalLines, offset) {
3225
+ const t = totalLines <= 1 ? 0 : (lineIdx + offset) % totalLines;
3226
+ const idx = Math.floor(t / totalLines * GRADIENT.length) % GRADIENT.length;
3227
+ return GRADIENT[idx];
3228
+ }
3229
+ function SplashLogo({ offset }) {
3230
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { flexDirection: "column", children: SPLASH_LINES.map((line, i) => {
3231
+ const hue = colorForLine(i, SPLASH_LINES.length, offset);
3232
+ const segments = [];
3233
+ let buf = "";
3234
+ let bufDim = false;
3235
+ for (const ch of line) {
3236
+ const dim = ch === "\u2591";
3237
+ if (segments.length === 0 && buf.length === 0) {
3238
+ buf = ch;
3239
+ bufDim = dim;
3240
+ continue;
3241
+ }
3242
+ if (dim === bufDim) {
3243
+ buf += ch;
3244
+ } else {
3245
+ segments.push({ text: buf, dim: bufDim });
3246
+ buf = ch;
3247
+ bufDim = dim;
3248
+ }
3249
+ }
3250
+ if (buf) segments.push({ text: buf, dim: bufDim });
3251
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { children: segments.map((seg, j) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: hue, dimColor: seg.dim, children: seg.text }, j)) }, i);
3252
+ }) });
3253
+ }
3254
+ function SplashScreen({ caption }) {
3255
+ const [offset, setOffset] = (0, import_react12.useState)(0);
3256
+ (0, import_react12.useEffect)(() => {
3257
+ const timer = setInterval(() => {
3258
+ setOffset((o) => o + 1);
3259
+ }, 120);
3260
+ return () => {
3261
+ clearInterval(timer);
3262
+ };
3263
+ }, []);
3264
+ const [size, setSize] = (0, import_react12.useState)(() => ({
3265
+ columns: process.stdout.columns ?? 80,
3266
+ rows: process.stdout.rows ?? 24
3267
+ }));
3268
+ (0, import_react12.useEffect)(() => {
3269
+ const handler = () => setSize({
3270
+ columns: process.stdout.columns ?? 80,
3271
+ rows: process.stdout.rows ?? 24
3272
+ });
3273
+ process.stdout.on("resize", handler);
3274
+ return () => {
3275
+ process.stdout.off("resize", handler);
3276
+ };
3277
+ }, []);
3278
+ const SPLASH_BLOCK_HEIGHT = SPLASH_LINES.length + 3;
3279
+ const verticalPad = Math.max(0, Math.floor((size.rows - SPLASH_BLOCK_HEIGHT) / 2));
3280
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", width: size.columns, height: size.rows, alignItems: "center", children: [
3281
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { height: verticalPad, flexShrink: 0 }),
3282
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Box_default, { flexDirection: "column", alignItems: "flex-start", flexShrink: 0, children: [
3283
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SplashLogo, { offset }),
3284
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { width: SPLASH_WIDTH, marginTop: 1, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { children: [
3285
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.text, bold: true, children: BRAND }),
3286
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(Text, { color: COLORS.textDim, children: [
3287
+ " v",
3288
+ VERSION
3289
+ ] }),
3290
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.textDim, children: " \xB7 By " }),
3291
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.text, bold: true, children: AUTHOR })
3292
+ ] }) }),
3293
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Box_default, { width: SPLASH_WIDTH, justifyContent: "center", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Text, { color: COLORS.textDim, children: caption ?? "Spinning up the orchestrator\u2026" }) })
3294
+ ] })
3295
+ ] });
3296
+ }
3297
+ function showSplash(opts) {
3298
+ const start = Date.now();
3299
+ void playSplashAudio();
3300
+ const instance = render_default(/* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SplashScreen, { caption: opts.caption }));
3301
+ const audioDurationMs = getSplashAudioDurationMs();
3302
+ const defaultMinMs = audioDurationMs + 200;
3303
+ return {
3304
+ dismiss: async () => {
3305
+ const minMs = opts.minMs ?? defaultMinMs;
3306
+ const elapsed = Date.now() - start;
3307
+ const remaining = Math.max(0, minMs - elapsed);
3308
+ if (remaining > 0) {
3309
+ await new Promise((r) => setTimeout(r, remaining));
3310
+ }
3311
+ instance.unmount();
3312
+ await new Promise((r) => setImmediate(r));
3313
+ }
3314
+ };
3315
+ }
3316
+
3317
+ // src/cli.ts
3318
+ function parseProjectSpec(raw) {
3319
+ const eq = raw.indexOf("=");
3320
+ if (eq > 0) {
3321
+ const name = raw.slice(0, eq);
3322
+ const cwd2 = path5.resolve(raw.slice(eq + 1));
3323
+ return { name, cwd: cwd2 };
3324
+ }
3325
+ const cwd = path5.resolve(raw);
3326
+ return { name: path5.basename(cwd), cwd };
3327
+ }
3328
+ function parseArgs(argv) {
3329
+ const args = {
3330
+ projects: []
3331
+ };
3332
+ for (let i = 0; i < argv.length; i++) {
3333
+ const a = argv[i];
3334
+ if (a === "--project" || a === "-p") {
3335
+ const v = argv[++i];
3336
+ if (!v) throw new Error("--project requires a value");
3337
+ args.projects.push(parseProjectSpec(v));
3338
+ } else if (a === "--boss-model") {
3339
+ const v = argv[++i];
3340
+ if (!v) throw new Error("--boss-model requires a value");
3341
+ args.bossModel = v;
3342
+ } else if (a === "--worker-model") {
3343
+ const v = argv[++i];
3344
+ if (!v) throw new Error("--worker-model requires a value");
3345
+ args.workerModel = v;
3346
+ } else if (a === "--resume") {
3347
+ const v = argv[++i];
3348
+ if (!v) throw new Error("--resume requires a session id");
3349
+ args.resumeSessionId = v;
3350
+ } else if (a === "--help" || a === "-h") {
3351
+ printHelpAndExit();
3352
+ } else {
3353
+ throw new Error(`Unknown argument: ${a}`);
3354
+ }
3355
+ }
3356
+ return args;
3357
+ }
3358
+ function printHelpAndExit() {
3359
+ const c = (color, text) => source_default.hex(color)(text);
3360
+ process.stdout.write(
3361
+ "\n" + c(COLORS.primary, "ARCena") + c(COLORS.textDim, " \u2014 orchestrator that drives multiple arcicoder workers from one chat.\n\n") + c(COLORS.text, "Usage\n") + " " + c(COLORS.accent, "arcena") + c(
3362
+ COLORS.textDim,
3363
+ " start orchestrator using linked projects\n"
3364
+ ) + " " + c(COLORS.accent, "arcena link") + c(COLORS.textDim, " pick which projects to link (interactive)\n") + " " + c(COLORS.accent, "arcena telegram") + c(COLORS.textDim, " configure Telegram bot integration\n") + " " + c(COLORS.accent, "arcena serve") + c(COLORS.textDim, " run the boss over Telegram (no TUI)\n") + " " + c(COLORS.accent, "arcena continue") + c(COLORS.textDim, " resume the most recent boss session\n") + " " + c(COLORS.accent, "arcena --resume <id>") + c(COLORS.textDim, " resume a specific boss session\n") + " " + c(COLORS.accent, "arcena --project <spec> [...]") + c(COLORS.textDim, " override links with explicit project(s)\n\n") + c(COLORS.text, "Options\n") + " " + c(COLORS.primary, "--project, -p <spec>") + c(COLORS.textDim, ' project to manage. spec is "cwd" or "name=cwd". repeatable.\n') + " " + c(COLORS.primary, "--boss-model <id>") + c(COLORS.textDim, " model for the orchestrator (default: claude-opus-4-7)\n") + " " + c(COLORS.primary, "--worker-model <id>") + c(COLORS.textDim, " model for workers (default: claude-sonnet-4-6)\n") + " " + c(COLORS.primary, "--help, -h") + c(COLORS.textDim, " show this help\n\n") + c(COLORS.textDim, "Talk to the boss at the prompt. Press ") + c(COLORS.accent, "Ctrl+C") + c(COLORS.textDim, " twice to exit.\n\n")
3365
+ );
3366
+ process.exit(0);
3367
+ }
3368
+ async function runServeSubcommand(argv) {
3369
+ let cliBotToken;
3370
+ let cliUserId;
3371
+ let cliBossModel;
3372
+ let cliWorkerModel;
3373
+ for (let i = 0; i < argv.length; i++) {
3374
+ const a = argv[i];
3375
+ if (a === "--bot-token") cliBotToken = argv[++i];
3376
+ else if (a === "--user-id") cliUserId = argv[++i];
3377
+ else if (a === "--boss-model") cliBossModel = argv[++i];
3378
+ else if (a === "--worker-model") cliWorkerModel = argv[++i];
3379
+ else if (a === "--help" || a === "-h") {
3380
+ process.stdout.write(
3381
+ "\nggboss serve \u2014 drive the boss from Telegram\n\nOptions\n --bot-token <token> Telegram bot token (or env GG_BOSS_TELEGRAM_BOT_TOKEN)\n --user-id <id> Allowed Telegram user ID (or env GG_BOSS_TELEGRAM_USER_ID)\n --boss-model <id> Override boss model\n --worker-model <id> Override worker model\n\nRun `arcena telegram` first to save credentials interactively.\n\n"
3382
+ );
3383
+ process.exit(0);
3384
+ } else {
3385
+ throw new Error(`Unknown argument: ${a}`);
3386
+ }
3387
+ }
3388
+ const saved = await loadBossTelegramConfig();
3389
+ const botToken = cliBotToken ?? process.env.GG_BOSS_TELEGRAM_BOT_TOKEN ?? saved?.botToken;
3390
+ const userIdStr = cliUserId ?? process.env.GG_BOSS_TELEGRAM_USER_ID;
3391
+ const userId = userIdStr ? parseInt(userIdStr, 10) : saved?.userId;
3392
+ if (!botToken || !userId || isNaN(userId)) {
3393
+ process.stderr.write(
3394
+ source_default.hex(COLORS.error)("Telegram not configured.\n\n") + "Run " + source_default.hex(COLORS.primary).bold("arcena telegram") + " to set up your bot token and user ID.\n\n" + source_default.hex(COLORS.textDim)("Or provide manually:\n") + source_default.hex(COLORS.textDim)(" arcena serve --bot-token TOKEN --user-id ID\n")
3395
+ );
3396
+ process.exit(1);
3397
+ }
3398
+ const settings = await loadSettings();
3399
+ const bossProvider = settings.bossProvider ?? "anthropic";
3400
+ const bossModel = cliBossModel ?? settings.bossModel ?? "claude-opus-4-7";
3401
+ const workerProvider = settings.workerProvider ?? "anthropic";
3402
+ const workerModel = cliWorkerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3403
+ await runBossServeMode({
3404
+ bossProvider,
3405
+ bossModel,
3406
+ bossThinkingLevel: settings.bossThinkingLevel,
3407
+ workerProvider,
3408
+ workerModel,
3409
+ telegram: { botToken, userId }
3410
+ });
3411
+ }
3412
+ async function runOrchestrator(args) {
3413
+ if (args.projects.length === 0) {
3414
+ const links = await loadLinks();
3415
+ if (links.projects.length === 0) {
3416
+ process.stderr.write(
3417
+ "\n" + source_default.hex(COLORS.warning)("No linked projects.") + source_default.hex(COLORS.textDim)(" Run ") + source_default.hex(COLORS.accent)("arcena link") + source_default.hex(COLORS.textDim)(" to choose, or pass ") + source_default.hex(COLORS.accent)("--project") + source_default.hex(COLORS.textDim)(".\n\n")
3418
+ );
3419
+ process.exit(1);
3420
+ }
3421
+ args.projects = links.projects.map((p) => ({ name: p.name, cwd: p.cwd }));
3422
+ }
3423
+ clearScreen();
3424
+ const splash = showSplash({
3425
+ caption: `Spinning up ${args.projects.length} worker${args.projects.length === 1 ? "" : "s"}\u2026`
3426
+ });
3427
+ const settings = await loadSettings();
3428
+ const finalBossProvider = args.bossProvider ?? settings.bossProvider ?? "anthropic";
3429
+ const finalBossModel = args.bossModel ?? settings.bossModel ?? "claude-opus-4-7";
3430
+ const finalWorkerProvider = args.workerProvider ?? settings.workerProvider ?? "anthropic";
3431
+ const finalWorkerModel = args.workerModel ?? settings.workerModel ?? "claude-sonnet-4-6";
3432
+ initLogger({
3433
+ version: VERSION,
3434
+ bossProvider: finalBossProvider,
3435
+ bossModel: finalBossModel,
3436
+ bossThinking: settings.bossThinkingLevel,
3437
+ workerProvider: finalWorkerProvider,
3438
+ workerModel: finalWorkerModel,
3439
+ projectCount: args.projects.length
3440
+ });
3441
+ log("INFO", "cli", "linked projects", {
3442
+ projects: args.projects.map((p) => p.name).join(",")
3443
+ });
3444
+ const updateMessage = checkAndAutoUpdate(VERSION);
3445
+ if (updateMessage) log("INFO", "auto_update", updateMessage);
3446
+ const boss = new GGBoss({
3447
+ bossProvider: finalBossProvider,
3448
+ bossModel: finalBossModel,
3449
+ bossThinkingLevel: settings.bossThinkingLevel,
3450
+ workerProvider: finalWorkerProvider,
3451
+ workerModel: finalWorkerModel,
3452
+ projects: args.projects,
3453
+ continueRecent: args.continueRecent,
3454
+ resumeSessionId: args.resumeSessionId
3455
+ });
3456
+ await boss.initialize();
3457
+ await splash.dismiss();
3458
+ clearScreen();
3459
+ const ink = renderBossApp({ boss });
3460
+ const runPromise = boss.run();
3461
+ await ink.waitUntilExit();
3462
+ await boss.dispose();
3463
+ stopRadio();
3464
+ await runPromise.catch(() => {
3465
+ });
3466
+ process.exit(0);
3467
+ }
3468
+ async function main() {
3469
+ const argv = process.argv.slice(2);
3470
+ if (argv[0] === "link") {
3471
+ await runLinkCommand();
3472
+ process.exit(0);
3473
+ }
3474
+ if (argv[0] === "telegram") {
3475
+ await runBossTelegramSetup();
3476
+ process.exit(0);
3477
+ }
3478
+ if (argv[0] === "serve") {
3479
+ await runServeSubcommand(argv.slice(1));
3480
+ return;
3481
+ }
3482
+ const isContinue = argv[0] === "continue";
3483
+ const args = parseArgs(isContinue ? argv.slice(1) : argv);
3484
+ if (isContinue) args.continueRecent = true;
3485
+ await runOrchestrator(args);
3486
+ }
3487
+ process.on("uncaughtException", (err) => {
3488
+ const message = err instanceof Error ? err.message : String(err);
3489
+ const stack = err instanceof Error ? err.stack : void 0;
3490
+ log("ERROR", "uncaught_exception", message, { stack });
3491
+ });
3492
+ process.on("unhandledRejection", (reason) => {
3493
+ const message = reason instanceof Error ? reason.message : String(reason);
3494
+ const stack = reason instanceof Error ? reason.stack : void 0;
3495
+ log("ERROR", "unhandled_rejection", message, { stack });
3496
+ });
3497
+ main().catch((err) => {
3498
+ const message = err instanceof Error ? err.message : String(err);
3499
+ process.stderr.write(source_default.hex(COLORS.error)(`
3500
+ gg-boss failed: ${message}
3501
+ `));
3502
+ process.exit(1);
3503
+ });
3504
+ //# sourceMappingURL=cli.js.map