@bill10/agent-007 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +222 -0
  3. package/VERSION +1 -0
  4. package/bin/adduser.js +69 -0
  5. package/bin/agent-007.js +88 -0
  6. package/lib/cron.js +189 -0
  7. package/lib/helpers.js +541 -0
  8. package/lib/jobs.js +965 -0
  9. package/package.json +63 -0
  10. package/public/app.js +650 -0
  11. package/public/assets/characters/LICENSE +21 -0
  12. package/public/assets/characters/char_0.png +0 -0
  13. package/public/assets/characters/char_1.png +0 -0
  14. package/public/assets/characters/char_2.png +0 -0
  15. package/public/assets/characters/char_3.png +0 -0
  16. package/public/assets/characters/char_4.png +0 -0
  17. package/public/assets/characters/char_5.png +0 -0
  18. package/public/assets/furniture/bookshelf.png +0 -0
  19. package/public/assets/furniture/cactus.png +0 -0
  20. package/public/assets/furniture/chair_back.png +0 -0
  21. package/public/assets/furniture/chair_front.png +0 -0
  22. package/public/assets/furniture/chair_side.png +0 -0
  23. package/public/assets/furniture/coffee.png +0 -0
  24. package/public/assets/furniture/coffee_table.png +0 -0
  25. package/public/assets/furniture/desk.png +0 -0
  26. package/public/assets/furniture/desk2.png +0 -0
  27. package/public/assets/furniture/plant_2.png +0 -0
  28. package/public/assets/furniture/sofa_front.png +0 -0
  29. package/public/assets/furniture/sofa_side.png +0 -0
  30. package/public/assets/furniture/table_front.png +0 -0
  31. package/public/index.html +245 -0
  32. package/public/modules/auth.js +83 -0
  33. package/public/modules/explorer.js +760 -0
  34. package/public/modules/jobs.js +971 -0
  35. package/public/modules/office.js +2154 -0
  36. package/public/modules/paths.js +20 -0
  37. package/public/modules/shortcuts.js +54 -0
  38. package/public/modules/state.js +75 -0
  39. package/public/modules/terminal.js +651 -0
  40. package/public/modules/voice.js +393 -0
  41. package/public/modules/ws.js +56 -0
  42. package/public/style.css +1843 -0
  43. package/server/agent-mcp-bridge.js +45 -0
  44. package/server/agent-mcp.js +184 -0
  45. package/server/agent-transcripts.js +195 -0
  46. package/server/approvals.js +155 -0
  47. package/server/auth.js +162 -0
  48. package/server/billion.js +176 -0
  49. package/server/claude-trust.js +66 -0
  50. package/server/command-path.js +102 -0
  51. package/server/config.js +184 -0
  52. package/server/direct-run.js +33 -0
  53. package/server/git.js +630 -0
  54. package/server/http.js +276 -0
  55. package/server/jobs.js +2044 -0
  56. package/server/mcp.js +596 -0
  57. package/server/messages.js +319 -0
  58. package/server/permission-hook.js +47 -0
  59. package/server/pty.js +360 -0
  60. package/server/state.js +104 -0
  61. package/server/ws.js +583 -0
  62. package/server.js +306 -0
  63. package/templates/billion/COMPANY.md +14 -0
  64. package/templates/billion/STATE.md +17 -0
  65. package/templates/billion/charter.md +232 -0
  66. package/templates/billion/owner.md +11 -0
@@ -0,0 +1,1843 @@
1
+ * { margin: 0; padding: 0; box-sizing: border-box; }
2
+
3
+ /* Default theme: Cool Steel */
4
+ :root {
5
+ color-scheme: dark;
6
+ --bg-dark: #090a0c;
7
+ --bg-panel: #0f1114;
8
+ --bg-office: #131519;
9
+ --bg-terminal: #07080a;
10
+ --bg-tabs: #0d0e11;
11
+ --border: #1f2228;
12
+ --text: #d8dce4;
13
+ --text-muted: #9ca3af;
14
+ --text-dim: #6b7280;
15
+ --accent: #d4a847;
16
+ --accent-contrast: #111;
17
+ --hover: rgba(255,255,255,0.05);
18
+ --state-working: #d4a847;
19
+ --state-waiting: #7fbc6a;
20
+ --state-message: #e0853a;
21
+ --state-idle: #4a4e58;
22
+ --state-disconnected: #c44040;
23
+ --state-recording: #c44040;
24
+ }
25
+
26
+ /* Theme: Light — warm low-glare paper (Flexoki-style neutrals).
27
+ Surfaces sit in the cream band (~L*90) instead of near-white so long
28
+ terminal sessions don't glare; ink is warm near-black, not pure gray. */
29
+ [data-theme="light"] {
30
+ color-scheme: light;
31
+ --bg-dark: #e6e4d9;
32
+ --bg-panel: #f2f0e5;
33
+ --bg-office: #dad8ce;
34
+ --bg-terminal: #f2f0e5;
35
+ --bg-tabs: #e6e4d9;
36
+ --border: #c6c3b6;
37
+ --text: #1c1b1a;
38
+ --text-muted: #62615c;
39
+ --text-dim: #6b6a64;
40
+ --accent: #7d611f;
41
+ --accent-contrast: #fff;
42
+ --hover: rgba(0,0,0,0.05);
43
+ --state-working: #8c6200;
44
+ --state-waiting: #1a7f37;
45
+ --state-message: #aa540f;
46
+ --state-idle: #7b7973;
47
+ --state-disconnected: #c8222e;
48
+ --state-recording: #c8222e;
49
+ }
50
+
51
+ /* Focus visibility — keyboard users get a visible ring */
52
+ :focus-visible {
53
+ outline: 2px solid var(--accent);
54
+ outline-offset: 2px;
55
+ }
56
+
57
+ body {
58
+ font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', 'Consolas', monospace;
59
+ background: var(--bg-dark);
60
+ color: var(--text);
61
+ height: 100vh;
62
+ height: 100dvh; /* mobile Safari: 100vh includes the hidden address bar */
63
+ overflow: hidden;
64
+ display: flex;
65
+ flex-direction: column;
66
+ }
67
+
68
+ /* Top bar */
69
+ /* Panel headers (continuous top bar via per-panel headers) */
70
+ .panel-header {
71
+ height: 36px;
72
+ display: flex;
73
+ align-items: center;
74
+ padding: 0 12px;
75
+ gap: 8px;
76
+ flex-shrink: 0;
77
+ border-bottom: 1px solid var(--border);
78
+ background: var(--bg-dark);
79
+ }
80
+ .office-header {
81
+ justify-content: center;
82
+ }
83
+ .terminal-header {
84
+ justify-content: space-between;
85
+ }
86
+ .app-logo {
87
+ vertical-align: -2px;
88
+ margin: 0 2px;
89
+ }
90
+ .topbar-title {
91
+ color: var(--accent);
92
+ font-weight: bold;
93
+ font-size: 14px;
94
+ letter-spacing: 1px;
95
+ line-height: 1;
96
+ flex-shrink: 0;
97
+ }
98
+ .topbar-btn {
99
+ background: none;
100
+ border: none;
101
+ color: var(--text);
102
+ padding: 6px 14px;
103
+ font-family: inherit;
104
+ font-size: 12px;
105
+ font-weight: 600;
106
+ cursor: pointer;
107
+ /* Two buttons now share this header, and it can be dragged down to 160px.
108
+ Without nowrap the labels wrap and get clipped by the 36px header. */
109
+ white-space: nowrap;
110
+ transition: color 0.15s;
111
+ }
112
+ .topbar-btn:hover { color: var(--accent); }
113
+ /* Primary action — give it button chrome so it reads as a CTA, not a label. */
114
+ #btn-new-agent, #btn-new-job-shortcut {
115
+ border: 1px solid var(--border);
116
+ border-radius: 4px;
117
+ color: var(--text);
118
+ transition: color 0.15s, border-color 0.15s, background 0.15s;
119
+ }
120
+ #btn-new-agent:hover, #btn-new-job-shortcut:hover {
121
+ color: var(--accent);
122
+ border-color: var(--accent);
123
+ background: color-mix(in srgb, var(--accent) 8%, transparent);
124
+ }
125
+ .topbar-agent-info {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ gap: 6px;
129
+ font-size: 13px;
130
+ }
131
+ .topbar-repo-label {
132
+ color: var(--text-muted);
133
+ font-size: 12px;
134
+ }
135
+ .topbar-repo-name {
136
+ color: var(--text);
137
+ font-size: 12px;
138
+ font-weight: 600;
139
+ }
140
+ .topbar-branch-icon {
141
+ color: var(--text-dim);
142
+ flex-shrink: 0;
143
+ }
144
+ .topbar-branch-name {
145
+ color: var(--text-muted);
146
+ font-size: 12px;
147
+ }
148
+ .topbar-status {
149
+ margin-left: auto;
150
+ color: var(--text-dim);
151
+ font-size: 12px;
152
+ }
153
+
154
+ /* Round icon buttons (theme toggle, voice input) */
155
+ .icon-btn {
156
+ background: none;
157
+ border: 1px solid var(--border);
158
+ color: var(--text-dim);
159
+ font-size: 14px;
160
+ cursor: pointer;
161
+ width: 26px;
162
+ height: 26px;
163
+ border-radius: 50%;
164
+ display: inline-flex;
165
+ align-items: center;
166
+ justify-content: center;
167
+ transition: color 0.15s, border-color 0.15s;
168
+ padding: 0;
169
+ }
170
+ .icon-btn:hover { color: var(--text); border-color: var(--text-dim); }
171
+
172
+ /* Voice input */
173
+ /* Fab geometry is the single source for the pill's placement below AND the
174
+ presence pill's stacking (presence is a body-level fixed element, so these
175
+ live on :root to reach it). right clears xterm's scrollbar on
176
+ classic-scrollbar platforms too (4px container padding + ~16px track). */
177
+ :root {
178
+ --voice-fab-right: 20px;
179
+ --voice-fab-bottom: 12px;
180
+ --voice-fab-size: 34px;
181
+ /* fab top edge + pill row (~26px) + 2 gaps of 8px */
182
+ --voice-column-top: calc(var(--voice-fab-bottom) + var(--voice-fab-size) + 42px);
183
+ }
184
+ /* Floating mic, anchored next to the prompt line at the terminal's bottom.
185
+ Opaque bg so it reads as a control, not terminal text. */
186
+ .voice-fab {
187
+ position: absolute;
188
+ right: var(--voice-fab-right);
189
+ bottom: var(--voice-fab-bottom);
190
+ width: var(--voice-fab-size);
191
+ height: var(--voice-fab-size);
192
+ background: var(--bg-panel);
193
+ z-index: 10;
194
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.35);
195
+ }
196
+ #btn-voice.listening {
197
+ color: var(--state-recording);
198
+ border-color: var(--state-recording);
199
+ animation: mic-pulse 1.6s ease-in-out infinite;
200
+ }
201
+ @keyframes mic-pulse {
202
+ 0%, 100% { opacity: 1; }
203
+ 50% { opacity: 0.45; }
204
+ }
205
+ /* Anchored to the mic so the reaction appears where the user clicked, but
206
+ stacked one row ABOVE the fab: the terminal's last row is where dictated
207
+ keystrokes echo — the pill must never hide that feedback. Grows leftward. */
208
+ .voice-indicator {
209
+ position: absolute;
210
+ right: var(--voice-fab-right);
211
+ bottom: calc(var(--voice-fab-bottom) + var(--voice-fab-size) + 8px);
212
+ max-width: calc(100% - 2 * var(--voice-fab-right));
213
+ padding: 5px 14px;
214
+ border-radius: 14px;
215
+ background: var(--bg-panel);
216
+ border: 1px solid var(--border);
217
+ color: var(--text);
218
+ font-size: 12px;
219
+ z-index: 10;
220
+ align-items: center;
221
+ gap: 7px;
222
+ pointer-events: none;
223
+ animation: pill-in 0.15s ease-out;
224
+ }
225
+ @keyframes pill-in {
226
+ from { opacity: 0; transform: translateY(8px); }
227
+ to { opacity: 1; transform: none; }
228
+ }
229
+ @media (prefers-reduced-motion: reduce) {
230
+ .voice-indicator,
231
+ .voice-indicator-dot,
232
+ #btn-voice.listening { animation: none; }
233
+ }
234
+ .voice-indicator-dot {
235
+ width: 7px;
236
+ height: 7px;
237
+ border-radius: 50%;
238
+ background: var(--state-recording);
239
+ animation: mic-pulse 1.6s ease-in-out infinite;
240
+ flex-shrink: 0;
241
+ }
242
+ .voice-indicator-text {
243
+ white-space: nowrap;
244
+ overflow: hidden;
245
+ text-overflow: ellipsis;
246
+ }
247
+ .voice-indicator.error {
248
+ color: var(--state-disconnected);
249
+ border-color: var(--state-disconnected);
250
+ }
251
+ /* The dot means "recording" — never show it on post-stop notices or errors */
252
+ .voice-indicator.error .voice-indicator-dot,
253
+ .voice-indicator.notice .voice-indicator-dot { display: none; }
254
+
255
+ /* Visually-hidden live region for screen-reader announcements */
256
+ .sr-only {
257
+ position: absolute;
258
+ width: 1px;
259
+ height: 1px;
260
+ overflow: hidden;
261
+ clip: rect(0 0 0 0);
262
+ white-space: nowrap;
263
+ }
264
+
265
+ /* Spawn form */
266
+ .spawn-form {
267
+ position: absolute;
268
+ top: 40px;
269
+ left: 0;
270
+ right: 0;
271
+ z-index: 100;
272
+ background: rgba(0,0,0,0.6);
273
+ display: flex;
274
+ justify-content: center;
275
+ padding: 16px;
276
+ }
277
+ .spawn-form-inner {
278
+ background: var(--bg-panel);
279
+ border: 1px solid var(--border);
280
+ border-radius: 8px;
281
+ padding: 16px 20px;
282
+ display: flex;
283
+ flex-direction: column;
284
+ gap: 12px;
285
+ }
286
+ .spawn-form-main {
287
+ display: flex;
288
+ gap: 12px;
289
+ align-items: flex-end;
290
+ }
291
+ .spawn-form-inner label {
292
+ display: flex;
293
+ flex-direction: column;
294
+ gap: 4px;
295
+ font-size: 11px;
296
+ color: var(--text-dim);
297
+ text-transform: uppercase;
298
+ letter-spacing: 1px;
299
+ }
300
+ .spawn-form-inner input {
301
+ background: var(--bg-dark);
302
+ border: 1px solid var(--border);
303
+ color: var(--text);
304
+ padding: 6px 10px;
305
+ font-family: inherit;
306
+ font-size: 13px;
307
+ border-radius: 4px;
308
+ width: 160px;
309
+ }
310
+ .spawn-form-inner input:focus { border-color: var(--accent); }
311
+ .spawn-repo-label { position: relative; }
312
+ .spawn-repo-wrapper { position: relative; }
313
+ .spawn-repo-wrapper { display: flex; }
314
+ .spawn-repo-wrapper input { width: 240px; border-radius: 4px 0 0 4px; }
315
+ .spawn-browse-btn {
316
+ background: var(--bg-dark);
317
+ border: 1px solid var(--border);
318
+ border-left: none;
319
+ border-radius: 0 4px 4px 0;
320
+ color: var(--text-dim);
321
+ font-size: 14px;
322
+ cursor: pointer;
323
+ padding: 0 8px;
324
+ line-height: 1;
325
+ transition: border-color 0.15s, color 0.15s;
326
+ }
327
+ .spawn-browse-btn:hover { border-color: var(--accent); color: var(--accent); }
328
+ .repo-dropdown {
329
+ position: absolute;
330
+ top: 100%;
331
+ left: 0;
332
+ right: 0;
333
+ background: var(--bg-dark);
334
+ border: 1px solid var(--border);
335
+ border-radius: 0 0 4px 4px;
336
+ max-height: 150px;
337
+ overflow-y: auto;
338
+ z-index: 110;
339
+ }
340
+ .repo-dropdown-item {
341
+ padding: 6px 10px;
342
+ cursor: pointer;
343
+ display: flex;
344
+ flex-direction: column;
345
+ gap: 2px;
346
+ }
347
+ .repo-dropdown-item:hover { background: var(--hover); }
348
+ .repo-dropdown-slug {
349
+ color: var(--text);
350
+ font-size: 12px;
351
+ }
352
+ .repo-dropdown-path {
353
+ color: var(--text-dim);
354
+ font-size: 10px;
355
+ }
356
+ .spawn-presets { display: flex; gap: 8px; }
357
+ .spawn-preset-btn {
358
+ padding: 6px 14px;
359
+ font-family: inherit;
360
+ font-size: 12px;
361
+ cursor: pointer;
362
+ border-radius: 4px;
363
+ border: 1px solid var(--border);
364
+ background: var(--bg-dark);
365
+ color: var(--text-dim);
366
+ transition: border-color 0.15s, color 0.15s;
367
+ }
368
+ .spawn-preset-btn:hover { border-color: var(--accent); color: var(--text); }
369
+ .spawn-preset-btn.selected { border-color: var(--accent); color: var(--accent); }
370
+ .spawn-advanced-toggle {
371
+ font-size: 11px;
372
+ color: var(--text-dim);
373
+ cursor: pointer;
374
+ user-select: none;
375
+ align-self: flex-end;
376
+ }
377
+ .spawn-advanced-toggle:hover { color: var(--text); }
378
+ .spawn-advanced {
379
+ display: flex;
380
+ flex-direction: column;
381
+ gap: 8px;
382
+ padding: 8px 0 0;
383
+ border-top: 1px solid var(--border);
384
+ }
385
+ .spawn-form-error {
386
+ font-size: 12px;
387
+ color: var(--state-disconnected);
388
+ background: rgba(255, 80, 80, 0.08);
389
+ border: 1px solid var(--state-disconnected);
390
+ border-radius: 4px;
391
+ padding: 6px 10px;
392
+ word-break: break-word;
393
+ }
394
+ .spawn-form-actions { display: flex; gap: 8px; }
395
+ .spawn-form-actions button {
396
+ padding: 6px 16px;
397
+ font-family: inherit;
398
+ font-size: 12px;
399
+ cursor: pointer;
400
+ border-radius: 4px;
401
+ border: 1px solid var(--border);
402
+ }
403
+ .spawn-form-actions button:first-child {
404
+ background: var(--accent);
405
+ color: var(--accent-contrast);
406
+ border-color: var(--accent);
407
+ font-weight: bold;
408
+ }
409
+ .spawn-form-actions button.secondary {
410
+ background: transparent;
411
+ color: var(--text-dim);
412
+ }
413
+
414
+ /* Directory browser modal */
415
+ .dir-browser-modal {
416
+ position: fixed;
417
+ inset: 0;
418
+ z-index: 150;
419
+ background: rgba(0,0,0,0.7);
420
+ display: flex;
421
+ align-items: center;
422
+ justify-content: center;
423
+ }
424
+ .dir-browser-inner {
425
+ background: var(--bg-panel);
426
+ border: 1px solid var(--border);
427
+ border-radius: 8px;
428
+ width: 520px;
429
+ max-height: 70vh;
430
+ display: flex;
431
+ flex-direction: column;
432
+ overflow: hidden;
433
+ }
434
+ .dir-browser-header {
435
+ display: flex;
436
+ align-items: center;
437
+ justify-content: space-between;
438
+ padding: 12px 16px;
439
+ border-bottom: 1px solid var(--border);
440
+ }
441
+ .dir-browser-title {
442
+ font-size: 14px;
443
+ font-weight: bold;
444
+ color: var(--accent);
445
+ }
446
+ .dir-browser-close {
447
+ background: none;
448
+ border: none;
449
+ color: var(--text-dim);
450
+ font-size: 18px;
451
+ cursor: pointer;
452
+ padding: 0 4px;
453
+ line-height: 1;
454
+ }
455
+ .dir-browser-close:hover { color: var(--text); }
456
+ .dir-browser-path {
457
+ padding: 8px 16px;
458
+ font-size: 12px;
459
+ color: var(--text-dim);
460
+ background: var(--bg-dark);
461
+ border-bottom: 1px solid var(--border);
462
+ overflow: hidden;
463
+ text-overflow: ellipsis;
464
+ white-space: nowrap;
465
+ font-family: inherit;
466
+ }
467
+ .dir-browser-list {
468
+ flex: 1;
469
+ overflow-y: auto;
470
+ min-height: 200px;
471
+ max-height: 50vh;
472
+ }
473
+ .dir-browser-list::-webkit-scrollbar { width: 4px; }
474
+ .dir-browser-list::-webkit-scrollbar-track { background: transparent; }
475
+ .dir-browser-list::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
476
+ .dir-browser-entry {
477
+ padding: 6px 16px;
478
+ cursor: pointer;
479
+ display: flex;
480
+ align-items: center;
481
+ gap: 8px;
482
+ font-size: 13px;
483
+ color: var(--text);
484
+ transition: background 0.1s;
485
+ }
486
+ .dir-browser-entry:hover { background: var(--hover); }
487
+ .dir-browser-entry.is-git { color: var(--accent); }
488
+ .dir-browser-entry-icon { font-size: 14px; flex-shrink: 0; width: 18px; text-align: center; }
489
+ .dir-browser-entry-name { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
490
+ .dir-browser-git-badge {
491
+ font-size: 9px;
492
+ background: var(--accent);
493
+ color: var(--bg-dark);
494
+ padding: 1px 5px;
495
+ border-radius: 8px;
496
+ font-weight: bold;
497
+ flex-shrink: 0;
498
+ }
499
+ .dir-browser-empty {
500
+ padding: 20px 16px;
501
+ text-align: center;
502
+ color: var(--text-dim);
503
+ font-size: 12px;
504
+ }
505
+ .dir-browser-error {
506
+ padding: 12px 16px;
507
+ color: var(--state-disconnected);
508
+ font-size: 12px;
509
+ }
510
+ .dir-browser-actions {
511
+ padding: 12px 16px;
512
+ display: flex;
513
+ gap: 8px;
514
+ justify-content: flex-end;
515
+ border-top: 1px solid var(--border);
516
+ }
517
+ .dir-browser-actions button {
518
+ padding: 6px 16px;
519
+ font-family: inherit;
520
+ font-size: 12px;
521
+ cursor: pointer;
522
+ border-radius: 4px;
523
+ border: 1px solid var(--border);
524
+ }
525
+ .dir-browser-actions button:first-child {
526
+ background: var(--accent);
527
+ color: var(--accent-contrast);
528
+ border-color: var(--accent);
529
+ font-weight: bold;
530
+ }
531
+ .dir-browser-actions button:first-child:disabled {
532
+ opacity: 0.4;
533
+ cursor: default;
534
+ }
535
+ .dir-browser-actions button.secondary {
536
+ background: transparent;
537
+ color: var(--text-dim);
538
+ }
539
+
540
+ /* Main layout */
541
+ .main {
542
+ display: flex;
543
+ flex: 1;
544
+ overflow: hidden;
545
+ }
546
+
547
+ /* Explorer panel */
548
+ .explorer {
549
+ width: 220px;
550
+ min-width: 180px;
551
+ background: var(--bg-panel);
552
+ display: flex;
553
+ flex-direction: column;
554
+ flex-shrink: 0;
555
+ transition: width 200ms ease-out;
556
+ overflow: hidden;
557
+ }
558
+ .explorer.collapsed {
559
+ width: 0 !important;
560
+ min-width: 0;
561
+ border-right: none;
562
+ overflow: hidden;
563
+ }
564
+ .explorer-header {
565
+ height: 28px;
566
+ display: flex;
567
+ align-items: center;
568
+ justify-content: space-between;
569
+ padding: 0 10px;
570
+ background: var(--bg-dark);
571
+ border-bottom: 1px solid var(--border);
572
+ flex-shrink: 0;
573
+ }
574
+ .explorer-label {
575
+ font-size: 9px;
576
+ color: var(--text-dim);
577
+ text-transform: uppercase;
578
+ letter-spacing: 2px;
579
+ }
580
+ .add-repo-bar {
581
+ display: flex;
582
+ align-items: center;
583
+ gap: 4px;
584
+ padding: 6px 8px;
585
+ border-bottom: 1px solid var(--border);
586
+ background: var(--bg-dark);
587
+ }
588
+ .add-repo-bar input {
589
+ flex: 1;
590
+ min-width: 0;
591
+ background: var(--bg-panel);
592
+ border: 1px solid var(--border);
593
+ color: var(--text);
594
+ padding: 4px 8px;
595
+ font-family: inherit;
596
+ font-size: 11px;
597
+ border-radius: 3px;
598
+ }
599
+ .add-repo-bar input:focus {
600
+ border-color: var(--accent);
601
+ outline: none;
602
+ }
603
+ .add-repo-browse {
604
+ background: none;
605
+ border: 1px solid var(--border);
606
+ color: var(--text-dim);
607
+ cursor: pointer;
608
+ padding: 4px 6px;
609
+ border-radius: 3px;
610
+ display: flex;
611
+ align-items: center;
612
+ }
613
+ .add-repo-browse:hover {
614
+ color: var(--accent);
615
+ border-color: var(--accent);
616
+ }
617
+ .explorer-header-actions {
618
+ display: flex;
619
+ align-items: center;
620
+ gap: 2px;
621
+ }
622
+ .explorer-reopen-btn {
623
+ background: var(--bg-panel);
624
+ border: none;
625
+ border-right: 1px solid var(--border);
626
+ color: var(--text-dim);
627
+ font-size: 10px;
628
+ cursor: pointer;
629
+ padding: 0 4px;
630
+ width: 18px;
631
+ flex-shrink: 0;
632
+ transition: color 0.15s, background 0.15s;
633
+ }
634
+ .explorer-reopen-btn:hover { color: var(--accent); background: var(--bg-dark); }
635
+ .explorer-content {
636
+ flex: 1;
637
+ overflow-y: auto;
638
+ overflow-x: hidden;
639
+ }
640
+ .explorer-content::-webkit-scrollbar { width: 4px; }
641
+ .explorer-content::-webkit-scrollbar-track { background: transparent; }
642
+ .explorer-content::-webkit-scrollbar-thumb { background: var(--border); border-radius: 2px; }
643
+
644
+ /* Explorer: repo sections */
645
+ .explorer-repo {
646
+ border-bottom: 1px solid var(--border);
647
+ }
648
+ .explorer-repo + .explorer-repo {
649
+ margin-top: 8px;
650
+ }
651
+ .explorer-repo-header {
652
+ display: flex;
653
+ align-items: center;
654
+ gap: 6px;
655
+ padding: 6px 10px;
656
+ background: var(--bg-dark);
657
+ font-size: 13px;
658
+ font-weight: bold;
659
+ position: sticky;
660
+ top: 0;
661
+ z-index: 1;
662
+ cursor: pointer;
663
+ }
664
+ .explorer-repo-header:hover { background: var(--bg-panel); }
665
+ .explorer-repo-name {
666
+ color: var(--text);
667
+ flex: 1;
668
+ overflow: hidden;
669
+ text-overflow: ellipsis;
670
+ white-space: nowrap;
671
+ }
672
+ .explorer-collapsed-badge {
673
+ display: inline-flex;
674
+ align-items: center;
675
+ gap: 4px;
676
+ font-size: 11px;
677
+ font-weight: normal;
678
+ color: var(--text-dim);
679
+ }
680
+ .explorer-repo-actions {
681
+ display: flex;
682
+ gap: 4px;
683
+ opacity: 0;
684
+ transition: opacity 0.15s;
685
+ }
686
+ .explorer-repo-header:hover .explorer-repo-actions { opacity: 1; }
687
+ .explorer-icon-btn {
688
+ background: none;
689
+ border: none;
690
+ color: var(--text-dim);
691
+ font-size: 16px;
692
+ cursor: pointer;
693
+ padding: 0;
694
+ width: 22px;
695
+ height: 22px;
696
+ line-height: 22px;
697
+ text-align: center;
698
+ display: inline-flex;
699
+ align-items: center;
700
+ justify-content: center;
701
+ flex-shrink: 0;
702
+ }
703
+ .explorer-icon-btn:hover { color: var(--accent); }
704
+ .explorer-remove-btn:hover { color: var(--state-disconnected); }
705
+ .explorer-error-badge {
706
+ font-size: 9px;
707
+ color: var(--state-disconnected);
708
+ margin-left: 6px;
709
+ }
710
+
711
+ /* Explorer: branches */
712
+ .explorer-no-agents {
713
+ padding: 6px 16px;
714
+ color: var(--text-dim);
715
+ font-size: 11px;
716
+ font-style: italic;
717
+ }
718
+ .explorer-branch-wrapper {
719
+ /* wrapper for branch + its file tree */
720
+ }
721
+ .explorer-branch {
722
+ display: flex;
723
+ align-items: center;
724
+ gap: 6px;
725
+ padding: 4px 10px;
726
+ font-size: 11px;
727
+ cursor: pointer;
728
+ min-height: 28px;
729
+ border-left: 2px solid transparent;
730
+ transition: background 0.1s;
731
+ }
732
+ .explorer-branch:hover { background: var(--hover); }
733
+ .explorer-branch.active {
734
+ background: var(--bg-terminal);
735
+ border-left-color: var(--accent);
736
+ }
737
+ .explorer-toggle-arrow {
738
+ font-size: 9px;
739
+ color: var(--text-dim);
740
+ width: 10px;
741
+ cursor: pointer;
742
+ flex-shrink: 0;
743
+ }
744
+ .explorer-dot {
745
+ width: 6px;
746
+ height: 6px;
747
+ border-radius: 50%;
748
+ flex-shrink: 0;
749
+ }
750
+ /* Billion's row, pinned above the repos */
751
+ .explorer-billion-name { color: var(--text); font-weight: 600; }
752
+ /* The one way back for a stopped Billion, so it reads as the row's action. */
753
+ .explorer-billion-start { margin-left: auto; color: var(--accent); }
754
+
755
+ .explorer-branch-name {
756
+ overflow: hidden;
757
+ text-overflow: ellipsis;
758
+ white-space: nowrap;
759
+ color: var(--text-muted);
760
+ }
761
+ .explorer-branch-label {
762
+ color: var(--text-dim);
763
+ font-size: 10px;
764
+ white-space: nowrap;
765
+ overflow: hidden;
766
+ text-overflow: ellipsis;
767
+ margin-left: 6px;
768
+ flex-shrink: 1;
769
+ min-width: 0;
770
+ }
771
+
772
+ /* Explorer: orphan entries */
773
+ .explorer-orphan {
774
+ opacity: 0.7;
775
+ border-left: 2px solid var(--state-message);
776
+ }
777
+ .orphan-badge {
778
+ color: var(--state-message);
779
+ font-size: 10px;
780
+ margin-left: 6px;
781
+ }
782
+ .explorer-orphan-actions {
783
+ display: flex;
784
+ gap: 4px;
785
+ padding: 2px 10px 4px 28px;
786
+ }
787
+ .explorer-orphan-actions button {
788
+ font-size: 10px;
789
+ font-family: inherit;
790
+ padding: 2px 8px;
791
+ cursor: pointer;
792
+ border-radius: 3px;
793
+ border: 1px solid var(--border);
794
+ background: transparent;
795
+ color: var(--text-dim);
796
+ transition: border-color 0.15s, color 0.15s;
797
+ }
798
+ .explorer-orphan-actions button:hover {
799
+ border-color: var(--accent);
800
+ color: var(--text);
801
+ }
802
+ .explorer-orphan-actions .orphan-delete-btn:hover {
803
+ border-color: var(--state-disconnected);
804
+ color: var(--state-disconnected);
805
+ }
806
+
807
+ /* Explorer: tree mode toggle switch */
808
+ /* Dedicated row so the changes/all filter doesn't overlap the first file row. */
809
+ .explorer-tree-toggle-row {
810
+ display: flex;
811
+ justify-content: flex-end;
812
+ padding: 4px 8px 4px 0;
813
+ }
814
+ .explorer-tree-toggle {
815
+ display: inline-flex;
816
+ align-items: center;
817
+ font-size: 9px;
818
+ flex-shrink: 0;
819
+ gap: 0;
820
+ background: rgba(255, 255, 255, 0.04);
821
+ border-radius: 3px;
822
+ overflow: hidden;
823
+ border: 1px solid var(--border);
824
+ }
825
+ .explorer-tree-toggle-opt {
826
+ padding: 1px 5px;
827
+ color: var(--text-dim);
828
+ cursor: pointer;
829
+ user-select: none;
830
+ transition: color 0.12s, background 0.12s;
831
+ }
832
+ .explorer-tree-toggle-opt:hover {
833
+ color: var(--text);
834
+ }
835
+ .explorer-tree-toggle-opt.active {
836
+ color: var(--accent);
837
+ background: rgba(255, 255, 255, 0.08);
838
+ }
839
+
840
+ /* Explorer: file tree */
841
+ .explorer-file-tree {
842
+ max-height: 400px;
843
+ overflow-y: auto;
844
+ transition: max-height 150ms ease-out;
845
+ }
846
+ .explorer-dir {
847
+ padding: 2px 10px;
848
+ font-size: 11px;
849
+ color: var(--text-dim);
850
+ cursor: pointer;
851
+ }
852
+ .explorer-dir:hover { color: var(--text); }
853
+ .explorer-file {
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 6px;
857
+ padding: 2px 10px;
858
+ font-size: 11px;
859
+ cursor: pointer;
860
+ min-height: 24px;
861
+ }
862
+ .explorer-file:hover { background: var(--hover); }
863
+ .explorer-status-dot {
864
+ width: 6px;
865
+ height: 6px;
866
+ border-radius: 50%;
867
+ flex-shrink: 0;
868
+ }
869
+ .explorer-filename {
870
+ overflow: hidden;
871
+ text-overflow: ellipsis;
872
+ white-space: nowrap;
873
+ color: var(--text);
874
+ }
875
+ .explorer-conflict {
876
+ color: var(--state-working);
877
+ font-size: 12px;
878
+ cursor: pointer;
879
+ flex-shrink: 0;
880
+ }
881
+
882
+ /* Explorer: diff viewer */
883
+ .explorer-diff-loading {
884
+ padding: 6px 10px;
885
+ font-size: 10px;
886
+ color: var(--text-dim);
887
+ animation: pulse 1.5s infinite;
888
+ }
889
+ @keyframes pulse {
890
+ 0%, 100% { opacity: 0.4; }
891
+ 50% { opacity: 1; }
892
+ }
893
+ .explorer-diff {
894
+ background: var(--bg-terminal);
895
+ border: 1px solid var(--border);
896
+ border-radius: 4px;
897
+ padding: 6px 8px;
898
+ font-size: 10px;
899
+ line-height: 1.5;
900
+ max-height: 300px;
901
+ overflow: auto;
902
+ margin: 4px 10px 6px;
903
+ white-space: pre;
904
+ }
905
+ .explorer-diff .diff-add { color: #98c379; background: rgba(26, 58, 26, 0.5); }
906
+ .explorer-diff .diff-del { color: #e06c75; background: rgba(58, 26, 26, 0.5); }
907
+ /* The dark add/del pair composites to mud on the cream light theme */
908
+ [data-theme="light"] .explorer-diff .diff-add,
909
+ [data-theme="light"] .diff-viewer-content .diff-add { color: #116329; background: rgba(26, 127, 55, 0.12); }
910
+ [data-theme="light"] .explorer-diff .diff-del,
911
+ [data-theme="light"] .diff-viewer-content .diff-del { color: #a40e26; background: rgba(200, 34, 46, 0.10); }
912
+ .explorer-diff .diff-hunk { color: var(--accent); }
913
+ .explorer-diff .diff-empty { color: var(--text-dim); font-style: italic; }
914
+
915
+ /* Diff viewer (replaces office canvas in center panel) */
916
+ .diff-viewer {
917
+ position: absolute;
918
+ inset: 0;
919
+ display: flex;
920
+ flex-direction: column;
921
+ background: var(--bg-panel);
922
+ z-index: 2;
923
+ }
924
+ .diff-viewer-header {
925
+ display: flex;
926
+ align-items: center;
927
+ justify-content: space-between;
928
+ padding: 6px 12px;
929
+ border-bottom: 1px solid var(--border);
930
+ background: var(--bg-dark);
931
+ flex-shrink: 0;
932
+ }
933
+ .diff-viewer-path {
934
+ font-size: 12px;
935
+ color: var(--text);
936
+ overflow: hidden;
937
+ text-overflow: ellipsis;
938
+ white-space: nowrap;
939
+ }
940
+ .diff-viewer-close {
941
+ background: none;
942
+ border: none;
943
+ color: var(--text-dim);
944
+ font-size: 16px;
945
+ cursor: pointer;
946
+ padding: 2px 6px;
947
+ line-height: 1;
948
+ flex-shrink: 0;
949
+ }
950
+ .diff-viewer-close:hover { color: var(--text); }
951
+ .diff-viewer-content {
952
+ flex: 1;
953
+ overflow: auto;
954
+ padding: 10px 14px;
955
+ font-size: 12px;
956
+ font-family: inherit;
957
+ line-height: 1.6;
958
+ color: var(--text-dim);
959
+ margin: 0;
960
+ white-space: pre;
961
+ tab-size: 4;
962
+ }
963
+ .diff-viewer-content .diff-add { color: #98c379; background: rgba(26, 58, 26, 0.5); }
964
+ .diff-viewer-content .diff-del { color: #e06c75; background: rgba(58, 26, 26, 0.5); }
965
+ .diff-viewer-content .diff-hunk { color: var(--accent); }
966
+ .diff-viewer-content .diff-empty { color: var(--text-dim); font-style: italic; }
967
+
968
+ /* Explorer: add repo */
969
+ .explorer-empty {
970
+ padding: 20px 10px;
971
+ text-align: center;
972
+ color: var(--text-dim);
973
+ font-size: 11px;
974
+ }
975
+
976
+ /* Office panel */
977
+ .office {
978
+ width: 440px;
979
+ min-width: 160px;
980
+ max-width: 80vw;
981
+ background: var(--bg-office);
982
+ position: relative;
983
+ overflow: hidden;
984
+ flex-shrink: 0;
985
+ display: flex;
986
+ flex-direction: column;
987
+ }
988
+ .office-label {
989
+ position: absolute;
990
+ top: 8px;
991
+ left: 10px;
992
+ font-size: 9px;
993
+ color: rgba(255,255,255,0.15);
994
+ text-transform: uppercase;
995
+ letter-spacing: 3px;
996
+ pointer-events: none;
997
+ }
998
+ /* White watermark is invisible on the light office canvas */
999
+ [data-theme="light"] .office-label { color: rgba(0,0,0,0.14); }
1000
+ #office-canvas {
1001
+ width: 100%;
1002
+ flex: 1;
1003
+ min-height: 0; /* else the h*dpr height attribute becomes the flex minimum and the canvas grows every frame on HiDPI */
1004
+ display: block;
1005
+ image-rendering: pixelated;
1006
+ }
1007
+ .office-empty {
1008
+ position: absolute;
1009
+ inset: 0;
1010
+ display: flex;
1011
+ flex-direction: column;
1012
+ align-items: center;
1013
+ justify-content: center;
1014
+ gap: 8px;
1015
+ color: var(--text-dim);
1016
+ font-size: 14px;
1017
+ /* Passive text overlay — must not intercept clicks on the header's
1018
+ "+ New Agent" button, which sits beneath it when the office is empty. */
1019
+ pointer-events: none;
1020
+ }
1021
+ .office-empty .dim { font-size: 12px; color: var(--text-dim); }
1022
+
1023
+ /* Divider */
1024
+ .divider {
1025
+ width: 5px;
1026
+ background: linear-gradient(to bottom, var(--bg-dark) 36px, var(--border) 36px);
1027
+ cursor: col-resize;
1028
+ flex-shrink: 0;
1029
+ transition: background 0.15s;
1030
+ }
1031
+ .divider:hover, .divider.dragging { background: var(--accent) !important; }
1032
+
1033
+ /* Terminal panel */
1034
+ .terminal-panel {
1035
+ flex: 1;
1036
+ display: flex;
1037
+ flex-direction: column;
1038
+ background: var(--bg-terminal);
1039
+ min-width: 300px;
1040
+ overflow: hidden;
1041
+ }
1042
+
1043
+ /* Terminal tabs */
1044
+ .terminal-tabs {
1045
+ display: flex;
1046
+ background: var(--bg-tabs);
1047
+ border-bottom: 1px solid var(--border);
1048
+ height: 34px;
1049
+ flex-shrink: 0;
1050
+ overflow-x: auto;
1051
+ }
1052
+ .terminal-tabs::-webkit-scrollbar { display: none; }
1053
+ .terminal-tab {
1054
+ padding: 0 16px;
1055
+ font-size: 12px;
1056
+ font-family: inherit;
1057
+ color: var(--text-dim);
1058
+ border-right: 1px solid var(--border);
1059
+ cursor: pointer;
1060
+ display: flex;
1061
+ align-items: center;
1062
+ gap: 8px;
1063
+ white-space: nowrap;
1064
+ transition: background 0.15s;
1065
+ position: relative;
1066
+ }
1067
+ .terminal-tab:hover { background: var(--hover); }
1068
+ .terminal-tab.active {
1069
+ color: var(--text);
1070
+ background: var(--bg-terminal);
1071
+ }
1072
+ .terminal-tab.active::after {
1073
+ content: '';
1074
+ position: absolute;
1075
+ bottom: 0;
1076
+ left: 0;
1077
+ right: 0;
1078
+ height: 2px;
1079
+ background: var(--accent);
1080
+ }
1081
+ .terminal-tab.dragging {
1082
+ opacity: 0.4;
1083
+ }
1084
+ .terminal-tab .dot {
1085
+ width: 7px;
1086
+ height: 7px;
1087
+ border-radius: 50%;
1088
+ flex-shrink: 0;
1089
+ }
1090
+ .terminal-tab .close-btn {
1091
+ margin-left: 4px;
1092
+ opacity: 0;
1093
+ color: var(--text-dim);
1094
+ font-size: 14px;
1095
+ line-height: 1;
1096
+ cursor: pointer;
1097
+ transition: opacity 0.15s;
1098
+ }
1099
+ .terminal-tab:hover .close-btn { opacity: 0.7; }
1100
+ .terminal-tab .close-btn:hover { opacity: 1; color: var(--state-disconnected); }
1101
+
1102
+ /* Drop overlay */
1103
+ .upload-drop-overlay {
1104
+ position: absolute;
1105
+ inset: 0;
1106
+ z-index: 50;
1107
+ background: rgba(0, 0, 0, 0.7);
1108
+ border: 2px dashed var(--accent);
1109
+ border-radius: 4px;
1110
+ color: var(--accent);
1111
+ font-size: 16px;
1112
+ font-family: inherit;
1113
+ display: flex;
1114
+ align-items: center;
1115
+ justify-content: center;
1116
+ pointer-events: none;
1117
+ }
1118
+
1119
+ /* Terminal viewport */
1120
+ .terminal-viewport {
1121
+ flex: 1;
1122
+ position: relative;
1123
+ overflow: hidden;
1124
+ }
1125
+ .terminal-container {
1126
+ position: absolute;
1127
+ inset: 0;
1128
+ padding: 4px;
1129
+ }
1130
+ .terminal-container .xterm { height: 100%; }
1131
+ .terminal-empty {
1132
+ position: absolute;
1133
+ inset: 0;
1134
+ display: flex;
1135
+ align-items: center;
1136
+ justify-content: center;
1137
+ color: var(--text-dim);
1138
+ font-size: 14px;
1139
+ }
1140
+
1141
+ /* Reconnecting banner */
1142
+ .reconnecting {
1143
+ position: fixed;
1144
+ top: 40px;
1145
+ left: 50%;
1146
+ transform: translateX(-50%);
1147
+ background: var(--state-message);
1148
+ color: var(--accent-contrast);
1149
+ padding: 6px 20px;
1150
+ border-radius: 0 0 6px 6px;
1151
+ font-size: 12px;
1152
+ font-weight: bold;
1153
+ z-index: 200;
1154
+ }
1155
+
1156
+ /* Reduced motion */
1157
+ @media (prefers-reduced-motion: reduce) {
1158
+ *, *::before, *::after {
1159
+ animation-duration: 0.01ms !important;
1160
+ animation-iteration-count: 1 !important;
1161
+ transition-duration: 0.01ms !important;
1162
+ }
1163
+ }
1164
+
1165
+ /* Responsive */
1166
+ @media (max-width: 900px) {
1167
+ .explorer { width: 0 !important; min-width: 0; border-right: none; overflow: hidden; }
1168
+ #divider-explorer { display: none; }
1169
+ .explorer-reopen-btn { display: none !important; }
1170
+ }
1171
+
1172
+ /* Phones: one panel at a time, picked by body[data-view] from the bottom
1173
+ nav. The panels' desktop widths (and the explorer's collapsed state) are
1174
+ overridden by id so this wins over .explorer.collapsed and inline widths. */
1175
+ .mobile-nav { display: none; }
1176
+ @media (max-width: 700px) {
1177
+ .divider, .explorer-reopen-btn, #btn-toggle-explorer { display: none !important; }
1178
+ .explorer-billion-start { min-width: 44px; min-height: 44px; }
1179
+ #explorer-panel, #office-panel, #terminal-panel {
1180
+ display: none; width: 100% !important; min-width: 0; max-width: none; flex: 1;
1181
+ }
1182
+ body[data-view="files"] #explorer-panel,
1183
+ body[data-view="office"] #office-panel,
1184
+ body[data-view="terminal"] #terminal-panel { display: flex; }
1185
+ .mobile-nav {
1186
+ display: flex;
1187
+ flex-shrink: 0;
1188
+ border-top: 1px solid var(--border);
1189
+ background: var(--bg-dark);
1190
+ }
1191
+ .mobile-nav button {
1192
+ flex: 1;
1193
+ min-height: 44px; /* touch target */
1194
+ background: none;
1195
+ border: none;
1196
+ color: var(--text-dim);
1197
+ font-family: inherit;
1198
+ font-size: 11px;
1199
+ letter-spacing: 1px;
1200
+ text-transform: uppercase;
1201
+ cursor: pointer;
1202
+ }
1203
+ .mobile-nav button:hover, .mobile-nav button[aria-current="true"] { color: var(--accent); }
1204
+ /* Overlays sized for desktop */
1205
+ .spawn-form-inner, .dir-browser-inner { width: auto; max-width: 100%; }
1206
+ .spawn-form-main, .spawn-presets { flex-wrap: wrap; }
1207
+ .spawn-form-inner input, .spawn-repo-wrapper input { width: 100%; }
1208
+ .spawn-repo-label { flex: 1 1 100%; }
1209
+ /* iOS zooms into any field smaller than 16px on focus, and never zooms
1210
+ back out. Every form above sets its own size, hence the override. */
1211
+ input, select, textarea { font-size: 16px !important; }
1212
+ }
1213
+
1214
+ /* --- Login overlay (phase 1) --- */
1215
+ .login-overlay {
1216
+ position: fixed; inset: 0; z-index: 1000;
1217
+ display: flex; align-items: center; justify-content: center;
1218
+ background: var(--bg-office);
1219
+ }
1220
+ .login-box {
1221
+ display: flex; flex-direction: column; gap: 12px;
1222
+ background: var(--bg-panel); border: 1px solid var(--border);
1223
+ padding: 32px 28px; border-radius: 8px; width: 320px; max-width: 90vw;
1224
+ box-shadow: 0 10px 40px rgba(0, 0, 0, 0.4);
1225
+ }
1226
+ .login-title { font-size: 18px; font-weight: bold; letter-spacing: 1px; color: var(--accent); }
1227
+ .login-subtitle { font-size: 12px; color: var(--text-dim); margin-bottom: 4px; }
1228
+ .login-input {
1229
+ background: var(--bg-terminal); border: 1px solid var(--border); color: var(--text);
1230
+ padding: 10px 12px; border-radius: 4px; font-family: inherit; font-size: 13px;
1231
+ }
1232
+ .login-input:focus { outline: none; border-color: var(--accent); }
1233
+ .login-submit {
1234
+ background: var(--accent); color: var(--bg-dark); border: none;
1235
+ padding: 10px; border-radius: 4px; font-weight: bold; cursor: pointer; font-family: inherit;
1236
+ }
1237
+ .login-submit:hover { opacity: 0.9; }
1238
+ .login-message { font-size: 11px; color: var(--state-message); min-height: 14px; }
1239
+
1240
+ /* --- Presence pill (phase 1) --- */
1241
+ .presence {
1242
+ /* Stacked above the voice pill/fab column (geometry derived from the
1243
+ --voice-fab-* props on :root) — at bottom-right it sat directly on the
1244
+ mic (z 500 > 10) and ate its clicks. */
1245
+ position: fixed;
1246
+ bottom: var(--voice-column-top);
1247
+ right: var(--voice-fab-right);
1248
+ z-index: 500;
1249
+ display: flex; gap: 4px;
1250
+ }
1251
+ .presence-dot {
1252
+ width: 22px; height: 22px; border-radius: 50%;
1253
+ display: flex; align-items: center; justify-content: center;
1254
+ font-size: 9px; font-weight: bold; color: #08090b;
1255
+ border: 1px solid rgba(0, 0, 0, 0.3);
1256
+ }
1257
+ .presence-self { box-shadow: 0 0 0 2px var(--accent); }
1258
+
1259
+ /* --- Read-only / ownership (phase 3) --- */
1260
+ .topbar-readonly {
1261
+ display: inline-flex; align-items: center; gap: 4px;
1262
+ font-size: 11px; font-weight: 600;
1263
+ padding: 1px 7px; margin-right: 8px;
1264
+ border: 1px solid currentColor; border-radius: 10px;
1265
+ opacity: 0.9;
1266
+ }
1267
+ /* Subtle cue that the active terminal is view-only. */
1268
+ .terminal-panel.readonly .terminal-viewport { opacity: 0.92; }
1269
+
1270
+ /* ============================================================
1271
+ Job Board
1272
+ Shares the terminal viewport with the xterm containers; the
1273
+ pinned "Jobs" tab swaps between them.
1274
+ ============================================================ */
1275
+
1276
+ .terminal-tab.board-tab {
1277
+ gap: 6px;
1278
+ color: var(--text-muted);
1279
+ /* Visually pinned: a hairline separates it from the agent tabs, which are
1280
+ draggable and closable while this one is neither. */
1281
+ border-right: 1px solid var(--border);
1282
+ }
1283
+ .terminal-tab.board-tab .board-tab-icon { opacity: 0.75; flex-shrink: 0; }
1284
+ .terminal-tab.board-tab.active { color: var(--accent); }
1285
+ .terminal-tab.board-tab.active .board-tab-icon { opacity: 1; }
1286
+
1287
+ .board-tab-badge {
1288
+ min-width: 15px;
1289
+ padding: 0 4px;
1290
+ border-radius: 7px;
1291
+ background: var(--state-message);
1292
+ color: #0b0c0e;
1293
+ font-size: 9px;
1294
+ font-weight: 700;
1295
+ line-height: 15px;
1296
+ text-align: center;
1297
+ }
1298
+
1299
+ /* An agent the board created, not you. Subtle — it is still an ordinary agent
1300
+ you can type into; the marker only explains where it came from. */
1301
+ .terminal-tab.board-spawned .dot {
1302
+ box-shadow: 0 0 0 1px var(--bg-tabs), 0 0 0 2px var(--text-dim);
1303
+ }
1304
+
1305
+ .job-board {
1306
+ position: absolute;
1307
+ inset: 0;
1308
+ display: flex;
1309
+ flex-direction: column;
1310
+ background: var(--bg-panel);
1311
+ overflow: hidden;
1312
+ z-index: 5;
1313
+ }
1314
+
1315
+ /* --- Toolbar --- */
1316
+
1317
+ .job-board-toolbar {
1318
+ display: flex;
1319
+ align-items: center;
1320
+ gap: 12px;
1321
+ flex-wrap: wrap;
1322
+ padding: 8px 12px;
1323
+ background: var(--bg-dark);
1324
+ border-bottom: 1px solid var(--border);
1325
+ flex-shrink: 0;
1326
+ }
1327
+
1328
+ .job-new-btn {
1329
+ padding: 4px 10px;
1330
+ font-size: 11px;
1331
+ font-family: inherit;
1332
+ color: var(--text);
1333
+ background: transparent;
1334
+ border: 1px solid var(--border);
1335
+ border-radius: 3px;
1336
+ cursor: pointer;
1337
+ }
1338
+ .job-new-btn:hover { border-color: var(--accent); color: var(--accent); }
1339
+
1340
+ .job-dispatcher { display: flex; align-items: center; gap: 8px; }
1341
+
1342
+ /* Same shape as the dispatcher controls — it is a view switch, not an action,
1343
+ so it stays quiet until it is the view you are in. */
1344
+ .job-finished-btn {
1345
+ padding: 4px 10px;
1346
+ font-size: 11px;
1347
+ font-family: inherit;
1348
+ color: var(--text-muted);
1349
+ background: transparent;
1350
+ border: 1px solid var(--border);
1351
+ border-radius: 3px;
1352
+ cursor: pointer;
1353
+ }
1354
+ .job-finished-btn:hover { border-color: var(--accent); color: var(--accent); }
1355
+ .job-finished-btn.showing { color: var(--accent); border-color: var(--accent); }
1356
+
1357
+ .job-run-toggle,
1358
+ .job-run-now {
1359
+ padding: 4px 10px;
1360
+ font-size: 11px;
1361
+ font-family: inherit;
1362
+ color: var(--text-muted);
1363
+ background: transparent;
1364
+ border: 1px solid var(--border);
1365
+ border-radius: 3px;
1366
+ cursor: pointer;
1367
+ }
1368
+ .job-run-toggle:hover,
1369
+ .job-run-now:hover { border-color: var(--accent); color: var(--accent); }
1370
+ .job-run-toggle.running { color: var(--state-waiting); border-color: var(--state-waiting); }
1371
+
1372
+ .job-dispatcher-status { font-size: 10px; color: var(--text-dim); }
1373
+ .job-dispatcher-status.running { color: var(--state-waiting); }
1374
+
1375
+ .job-cap-label {
1376
+ display: flex;
1377
+ align-items: center;
1378
+ gap: 5px;
1379
+ margin-left: auto;
1380
+ font-size: 10px;
1381
+ color: var(--text-dim);
1382
+ }
1383
+ .job-cap-label input,
1384
+ .job-cap-label select {
1385
+ width: 44px;
1386
+ padding: 3px 5px;
1387
+ font-size: 11px;
1388
+ font-family: inherit;
1389
+ color: var(--text);
1390
+ background: var(--bg-panel);
1391
+ border: 1px solid var(--border);
1392
+ border-radius: 3px;
1393
+ }
1394
+ /* Only the first control in the group takes the slack; a second `margin-left:
1395
+ auto` would split it and strand the two labels at opposite ends. */
1396
+ .job-perm-label { margin-left: 0; }
1397
+ .job-cap-label select { width: auto; max-width: 200px; }
1398
+ /* bypassPermissions is the one mode with no check on the agent at all, and it
1399
+ reads as just another entry in a list of six. The danger token says so on the
1400
+ closed control, where it stays visible after the dropdown shuts — styling the
1401
+ <option> itself would not, since Safari ignores option colours. */
1402
+ select.job-mode-danger {
1403
+ color: var(--state-disconnected);
1404
+ border-color: var(--state-disconnected);
1405
+ }
1406
+
1407
+ /* --- New / edit form --- */
1408
+
1409
+ .job-form {
1410
+ display: flex;
1411
+ flex-direction: column;
1412
+ gap: 8px;
1413
+ padding: 12px;
1414
+ background: var(--bg-dark);
1415
+ border-bottom: 1px solid var(--border);
1416
+ flex-shrink: 0;
1417
+ }
1418
+ .job-form label {
1419
+ display: flex;
1420
+ flex-direction: column;
1421
+ gap: 4px;
1422
+ font-size: 10px;
1423
+ color: var(--text-dim);
1424
+ text-transform: uppercase;
1425
+ letter-spacing: 1px;
1426
+ }
1427
+ .job-form input[type="text"],
1428
+ .job-form select,
1429
+ .job-form textarea {
1430
+ padding: 6px 8px;
1431
+ font-size: 12px;
1432
+ font-family: inherit;
1433
+ color: var(--text);
1434
+ background: var(--bg-panel);
1435
+ border: 1px solid var(--border);
1436
+ border-radius: 3px;
1437
+ text-transform: none;
1438
+ letter-spacing: 0;
1439
+ }
1440
+ .job-form textarea { resize: vertical; line-height: 1.5; }
1441
+ .job-form input:focus,
1442
+ .job-form select:focus,
1443
+ .job-form textarea:focus { outline: none; border-color: var(--accent); }
1444
+
1445
+ .job-form-actions { display: flex; gap: 8px; }
1446
+ .job-form-actions button,
1447
+ .job-form-attach button {
1448
+ padding: 5px 12px;
1449
+ font-size: 11px;
1450
+ font-family: inherit;
1451
+ color: var(--bg-dark);
1452
+ background: var(--accent);
1453
+ border: none;
1454
+ border-radius: 3px;
1455
+ cursor: pointer;
1456
+ }
1457
+ .job-form button.secondary {
1458
+ color: var(--text-muted);
1459
+ background: transparent;
1460
+ border: 1px solid var(--border);
1461
+ }
1462
+ /* Type and schedule sit on one line: they are a single decision, and a cron
1463
+ box stacked under its own label would take a third of the form. */
1464
+ /* Wraps: with Type, Agent, Permissions and Schedule in one row, a narrow
1465
+ panel would otherwise clip the right-hand fields under the board's
1466
+ overflow: hidden rather than drop them to a second line. */
1467
+ .job-form-row { display: flex; flex-wrap: wrap; gap: 8px; align-items: flex-start; }
1468
+ /* Capped like the toolbar's: uncapped, the select sizes to its longest option
1469
+ and squeezes the flex:1 cron field beside it on a narrow panel. */
1470
+ #job-permission-mode-field { max-width: 200px; }
1471
+ .job-form-row > label { flex: 0 0 auto; }
1472
+ .job-form-schedule { flex: 1 1 200px; min-width: 0; }
1473
+ .job-form-hint {
1474
+ font-size: 9px;
1475
+ color: var(--text-dim);
1476
+ text-transform: none;
1477
+ letter-spacing: 0;
1478
+ }
1479
+ .job-form-error { font-size: 11px; color: var(--state-disconnected); }
1480
+
1481
+ .job-form-attach { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
1482
+ .job-form-attachments { display: flex; flex-wrap: wrap; gap: 6px; flex-basis: 100%; }
1483
+ .job-form-attachments:empty { display: none; }
1484
+ .job-attachment {
1485
+ display: inline-flex;
1486
+ align-items: center;
1487
+ gap: 4px;
1488
+ padding: 2px 6px;
1489
+ font-size: 11px;
1490
+ color: var(--text);
1491
+ background: var(--bg-panel);
1492
+ border: 1px solid var(--border);
1493
+ border-radius: 3px;
1494
+ }
1495
+ .job-attachment-remove {
1496
+ padding: 2px 6px;
1497
+ margin: -2px -6px -2px 0;
1498
+ font-size: 12px;
1499
+ font-family: inherit;
1500
+ line-height: 1;
1501
+ color: var(--text-dim);
1502
+ background: none;
1503
+ border: none;
1504
+ cursor: pointer;
1505
+ }
1506
+ .job-attachment-remove:hover { color: var(--state-disconnected); }
1507
+
1508
+ /* --- Columns --- */
1509
+
1510
+ .job-columns {
1511
+ display: flex;
1512
+ gap: 1px;
1513
+ flex: 1;
1514
+ min-height: 0;
1515
+ background: var(--border);
1516
+ }
1517
+
1518
+ .job-column {
1519
+ display: flex;
1520
+ flex-direction: column;
1521
+ flex: 1;
1522
+ min-width: 0;
1523
+ background: var(--bg-panel);
1524
+ }
1525
+
1526
+ .job-column-header {
1527
+ display: flex;
1528
+ align-items: center;
1529
+ justify-content: space-between;
1530
+ padding: 8px 10px;
1531
+ background: var(--bg-tabs);
1532
+ border-bottom: 1px solid var(--border);
1533
+ flex-shrink: 0;
1534
+ }
1535
+ .job-column-label {
1536
+ font-size: 9px;
1537
+ font-weight: 700;
1538
+ letter-spacing: 2px;
1539
+ text-transform: uppercase;
1540
+ color: var(--text-dim);
1541
+ }
1542
+ .job-column-count { font-size: 10px; color: var(--text-dim); }
1543
+
1544
+ .job-cards {
1545
+ flex: 1;
1546
+ overflow-y: auto;
1547
+ padding: 8px;
1548
+ display: flex;
1549
+ flex-direction: column;
1550
+ gap: 8px;
1551
+ }
1552
+ .job-column-empty {
1553
+ padding: 12px 4px;
1554
+ font-size: 10px;
1555
+ color: var(--text-dim);
1556
+ text-align: center;
1557
+ }
1558
+
1559
+ /* --- Finished jobs --- */
1560
+
1561
+ /* Takes the columns' place rather than sitting beside them, so an archive that
1562
+ only grows can never squeeze the live work. */
1563
+ .job-finished {
1564
+ display: flex;
1565
+ flex-direction: column;
1566
+ flex: 1;
1567
+ min-height: 0;
1568
+ background: var(--bg-panel);
1569
+ }
1570
+ .job-finished-header {
1571
+ display: flex;
1572
+ align-items: center;
1573
+ justify-content: space-between;
1574
+ padding: 8px 10px;
1575
+ background: var(--bg-tabs);
1576
+ border-bottom: 1px solid var(--border);
1577
+ flex-shrink: 0;
1578
+ }
1579
+ .job-finished-label {
1580
+ font-size: 9px;
1581
+ font-weight: 700;
1582
+ letter-spacing: 2px;
1583
+ text-transform: uppercase;
1584
+ color: var(--text-dim);
1585
+ }
1586
+ .job-finished-count { font-size: 10px; color: var(--text-dim); }
1587
+
1588
+ /* --- Cards --- */
1589
+
1590
+ .job-card {
1591
+ display: flex;
1592
+ flex-direction: column;
1593
+ gap: 5px;
1594
+ padding: 9px 10px;
1595
+ background: var(--bg-dark);
1596
+ border: 1px solid var(--border);
1597
+ border-left: 2px solid var(--border);
1598
+ border-radius: 3px;
1599
+ }
1600
+ /* An in-progress card whose agent is still running opens that agent's tab.
1601
+ Only the non-left borders light up on hover — the left edge is the status
1602
+ stripe and has to keep its own colour. */
1603
+ .job-card-live { cursor: pointer; }
1604
+ .job-card-live:hover {
1605
+ border-top-color: var(--accent);
1606
+ border-right-color: var(--accent);
1607
+ border-bottom-color: var(--accent);
1608
+ }
1609
+ .job-card.status-running { border-left-color: var(--state-working); }
1610
+ .job-card.status-needs-input { border-left-color: var(--state-message); }
1611
+ .job-card.status-stalled { border-left-color: var(--state-idle); }
1612
+ .job-card.status-gone { border-left-color: var(--state-disconnected); }
1613
+
1614
+ .job-card-title {
1615
+ font-size: 12px;
1616
+ color: var(--text);
1617
+ line-height: 1.35;
1618
+ word-break: break-word;
1619
+ }
1620
+
1621
+ .job-card-meta {
1622
+ display: flex;
1623
+ gap: 6px;
1624
+ flex-wrap: wrap;
1625
+ font-size: 9px;
1626
+ color: var(--text-dim);
1627
+ }
1628
+ .job-card-repo { color: var(--text-muted); }
1629
+ /* Posted by an agent rather than typed into the form. Accent-tinted so a board
1630
+ full of cards still shows at a glance which ones a machine queued. */
1631
+ .job-card-via { color: var(--accent); opacity: 0.85; }
1632
+
1633
+ /* Scheduled cards. The chip rides in the title line and the schedule gets its
1634
+ own row, so the cron text never competes with the title for the same space. */
1635
+ .job-card-type {
1636
+ margin-left: 6px;
1637
+ padding: 1px 5px;
1638
+ font-size: 9px;
1639
+ letter-spacing: 0.5px;
1640
+ text-transform: uppercase;
1641
+ color: var(--accent);
1642
+ border: 1px solid var(--accent);
1643
+ border-radius: 8px;
1644
+ opacity: 0.75;
1645
+ white-space: nowrap;
1646
+ vertical-align: 1px;
1647
+ }
1648
+ /* Same reason as select.job-mode-danger: a card that will dispatch with no
1649
+ permission check must not wear the same gold pill as the routine
1650
+ "scheduled" chip beside it. */
1651
+ .job-card-type.job-mode-danger {
1652
+ color: var(--state-disconnected);
1653
+ border-color: var(--state-disconnected);
1654
+ }
1655
+ .job-card-schedule {
1656
+ display: flex;
1657
+ gap: 6px;
1658
+ flex-wrap: wrap;
1659
+ align-items: baseline;
1660
+ font-size: 9px;
1661
+ color: var(--text-dim);
1662
+ }
1663
+ /* The page is already set in a mono stack, so cron text needs no font of its
1664
+ own — just enough contrast to read as the machine-readable bit. */
1665
+ .job-card-cron { color: var(--text-muted); }
1666
+ .job-card-next { color: var(--accent); opacity: 0.85; }
1667
+ /* A paused schedule is not a countdown: drop the accent so the card reads as
1668
+ held rather than as the next one about to go. */
1669
+ .job-card-paused { color: var(--text-muted); }
1670
+ .job-card-runs { opacity: 0.8; }
1671
+
1672
+ .job-card-detail {
1673
+ font-size: 10px;
1674
+ color: var(--text-dim);
1675
+ line-height: 1.45;
1676
+ /* Cards stay scannable: the full text lives in the agent's prompt, this is
1677
+ only a reminder of what the job was. */
1678
+ display: -webkit-box;
1679
+ -webkit-line-clamp: 3;
1680
+ -webkit-box-orient: vertical;
1681
+ overflow: hidden;
1682
+ white-space: pre-wrap;
1683
+ word-break: break-word;
1684
+ }
1685
+
1686
+ /* What the agent reported through finish_job. Scrolls rather than clamps: on
1687
+ a card with no PR this is the result, so none of it may be cut off. */
1688
+ .job-card-result {
1689
+ font-size: 10px;
1690
+ color: var(--text);
1691
+ line-height: 1.45;
1692
+ max-height: 12em;
1693
+ overflow-y: auto;
1694
+ white-space: pre-wrap;
1695
+ word-break: break-word;
1696
+ border-left: 2px solid var(--border);
1697
+ padding-left: 6px;
1698
+ }
1699
+ .job-card-result:focus-visible { outline: 1px solid var(--accent); outline-offset: 2px; }
1700
+
1701
+ .job-card-agent {
1702
+ display: flex;
1703
+ gap: 6px;
1704
+ flex-wrap: wrap;
1705
+ align-items: baseline;
1706
+ font-size: 9px;
1707
+ color: var(--text-dim);
1708
+ }
1709
+ .job-card-agent-name { color: var(--text-muted); font-weight: 600; }
1710
+ .job-card-branch { color: var(--text-dim); opacity: 0.85; }
1711
+
1712
+ .job-card-status {
1713
+ display: inline-flex;
1714
+ align-items: center;
1715
+ gap: 5px;
1716
+ align-self: flex-start;
1717
+ padding: 2px 7px;
1718
+ font-size: 9px;
1719
+ font-family: inherit;
1720
+ color: var(--text-muted);
1721
+ background: transparent;
1722
+ border: 1px solid var(--border);
1723
+ border-radius: 10px;
1724
+ cursor: pointer;
1725
+ }
1726
+ .job-card-status:hover:not(:disabled) { border-color: var(--accent); color: var(--accent); }
1727
+ .job-card-status:disabled { cursor: default; opacity: 0.65; }
1728
+ .job-status-dot {
1729
+ width: 5px;
1730
+ height: 5px;
1731
+ border-radius: 50%;
1732
+ background: currentColor;
1733
+ flex-shrink: 0;
1734
+ }
1735
+ .job-status-running { color: var(--state-working); border-color: var(--state-working); }
1736
+ .job-status-needs-input { color: var(--state-message); border-color: var(--state-message); }
1737
+ .job-status-stalled { color: var(--text-dim); }
1738
+ .job-status-gone { color: var(--state-disconnected); border-color: var(--state-disconnected); }
1739
+
1740
+ /* Only the "needs you" badge pulses. A card that is merely quiet should not
1741
+ compete for attention with one that is actually blocking on the user. */
1742
+ .job-status-needs-input .job-status-dot { animation: job-pulse 1.8s ease-in-out infinite; }
1743
+ @keyframes job-pulse {
1744
+ 0%, 100% { opacity: 1; }
1745
+ 50% { opacity: 0.3; }
1746
+ }
1747
+
1748
+ .job-card-attachments { display: flex; flex-wrap: wrap; gap: 4px 8px; }
1749
+ .job-card-attachment {
1750
+ font-size: 10px;
1751
+ color: var(--accent);
1752
+ text-decoration: none;
1753
+ overflow-wrap: anywhere;
1754
+ }
1755
+ .job-card-attachment::before { content: '\1F4CE\00a0'; }
1756
+ .job-card-attachment:hover { text-decoration: underline; }
1757
+
1758
+ .job-card-pr {
1759
+ align-self: flex-start;
1760
+ font-size: 10px;
1761
+ color: var(--accent);
1762
+ text-decoration: none;
1763
+ }
1764
+ .job-card-pr:hover { text-decoration: underline; }
1765
+
1766
+ .job-card-error {
1767
+ font-size: 9px;
1768
+ line-height: 1.4;
1769
+ color: var(--state-disconnected);
1770
+ word-break: break-word;
1771
+ }
1772
+
1773
+ .job-card-actions {
1774
+ display: flex;
1775
+ gap: 4px;
1776
+ flex-wrap: wrap;
1777
+ margin-top: 2px;
1778
+ opacity: 0;
1779
+ transition: opacity 0.12s;
1780
+ }
1781
+ .job-card:hover .job-card-actions,
1782
+ .job-card:focus-within .job-card-actions { opacity: 1; }
1783
+
1784
+ .job-card-btn {
1785
+ padding: 2px 6px;
1786
+ font-size: 9px;
1787
+ font-family: inherit;
1788
+ color: var(--text-dim);
1789
+ background: transparent;
1790
+ border: 1px solid var(--border);
1791
+ border-radius: 3px;
1792
+ cursor: pointer;
1793
+ }
1794
+ .job-card-btn:hover { color: var(--accent); border-color: var(--accent); }
1795
+ .job-card-btn.danger:hover { color: var(--state-disconnected); border-color: var(--state-disconnected); }
1796
+
1797
+ /* Narrow panels: stack the columns instead of crushing them to unreadable
1798
+ widths (the terminal panel is resizable down to 300px). */
1799
+ @media (max-width: 700px) {
1800
+ .job-columns { flex-direction: column; overflow-y: auto; }
1801
+ .job-column { min-height: 140px; }
1802
+ }
1803
+
1804
+ @media (prefers-reduced-motion: reduce) {
1805
+ .job-status-needs-input .job-status-dot { animation: none; }
1806
+ .job-card-actions { transition: none; }
1807
+ }
1808
+
1809
+ .job-card-retired {
1810
+ font-size: 9px;
1811
+ color: var(--text-dim);
1812
+ opacity: 0.8;
1813
+ }
1814
+
1815
+ /* Why a schedule held off a firing. Above retired metadata: it often says
1816
+ what to do ("waiting on PR #12" — go merge it). */
1817
+ .job-card-held {
1818
+ font-size: 9px;
1819
+ color: var(--text-dim);
1820
+ }
1821
+
1822
+ /* The schedule's link to its latest run. */
1823
+ .job-card-lastrun {
1824
+ font-size: 9px;
1825
+ color: var(--accent);
1826
+ background: none;
1827
+ border: none;
1828
+ padding: 0;
1829
+ cursor: pointer;
1830
+ text-align: left;
1831
+ font-family: inherit;
1832
+ }
1833
+ .job-card-lastrun:hover { text-decoration: underline; }
1834
+
1835
+
1836
+ /* Every card in the archive carries this line, so a colour here would be pure
1837
+ decoration — it differentiates nothing. Matches .job-card-retired exactly:
1838
+ both are 9px card metadata that has stopped being actionable. */
1839
+ .job-card-finished {
1840
+ font-size: 9px;
1841
+ color: var(--text-dim);
1842
+ opacity: 0.8;
1843
+ }