loadtoagent 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -390,6 +390,160 @@
390
390
  background: linear-gradient(90deg,#52d7aa,var(--provider));
391
391
  }
392
392
 
393
+ .tmux-subagents {
394
+ border-top: 1px solid #1c2731;
395
+ background: rgba(8,14,20,.74);
396
+ }
397
+
398
+ .tmux-subagents-toggle {
399
+ display: flex;
400
+ align-items: center;
401
+ justify-content: space-between;
402
+ gap: 12px;
403
+ width: 100%;
404
+ padding: 10px 14px;
405
+ border: 0;
406
+ background: transparent;
407
+ color: #cbd7e1;
408
+ text-align: left;
409
+ cursor: pointer;
410
+ }
411
+
412
+ .tmux-subagents-toggle:hover,
413
+ .tmux-subagents-toggle:focus-visible {
414
+ background: rgba(83,218,177,.055);
415
+ color: #edf8f4;
416
+ }
417
+
418
+ .tmux-subagents-toggle span,
419
+ .tmux-subagents-toggle b,
420
+ .tmux-subagents-toggle small {
421
+ display: block;
422
+ }
423
+
424
+ .tmux-subagents-toggle b {
425
+ font-size: 11px;
426
+ }
427
+
428
+ .tmux-subagents-toggle small {
429
+ margin-top: 3px;
430
+ color: #718395;
431
+ font-size: 9px;
432
+ }
433
+
434
+ .tmux-subagents-toggle>i {
435
+ color: #72deb8;
436
+ font-style: normal;
437
+ transition: transform var(--motion-fast) ease;
438
+ }
439
+
440
+ .tmux-subagents.expanded .tmux-subagents-toggle>i {
441
+ transform: rotate(180deg);
442
+ }
443
+
444
+ .tmux-subagent-list {
445
+ display: grid;
446
+ gap: 6px;
447
+ padding: 0 10px 10px;
448
+ }
449
+
450
+ .tmux-subagent-row {
451
+ display: grid;
452
+ grid-template-columns: 28px minmax(0,1fr) auto;
453
+ align-items: center;
454
+ gap: 8px;
455
+ margin-left: calc(var(--tmux-subagent-depth,0) * 12px);
456
+ padding: 8px;
457
+ border: 1px solid color-mix(in srgb,var(--provider) 18%,#26323d);
458
+ border-radius: 9px;
459
+ background: color-mix(in srgb,var(--provider) 4%,#0b1219);
460
+ }
461
+
462
+ .tmux-subagent-row.work-working {
463
+ border-color: color-mix(in srgb,var(--provider) 38%,#315148);
464
+ }
465
+
466
+ .tmux-subagent-row.work-attention {
467
+ border-color: rgba(235,119,119,.42);
468
+ }
469
+
470
+ .tmux-subagent-row>.provider-mark {
471
+ width: 27px;
472
+ height: 27px;
473
+ font-size: 9px;
474
+ }
475
+
476
+ .tmux-subagent-copy,
477
+ .tmux-subagent-copy>span {
478
+ min-width: 0;
479
+ }
480
+
481
+ .tmux-subagent-copy>span {
482
+ display: flex;
483
+ align-items: center;
484
+ gap: 6px;
485
+ }
486
+
487
+ .tmux-subagent-copy b,
488
+ .tmux-subagent-copy strong,
489
+ .tmux-subagent-copy em {
490
+ overflow: hidden;
491
+ text-overflow: ellipsis;
492
+ white-space: nowrap;
493
+ }
494
+
495
+ .tmux-subagent-copy b {
496
+ color: #dce6ee;
497
+ font-size: 10px;
498
+ }
499
+
500
+ .tmux-subagent-copy i {
501
+ padding: 2px 5px;
502
+ border-radius: 999px;
503
+ background: #17262a;
504
+ color: #77dcb9;
505
+ font-size: 8px;
506
+ font-style: normal;
507
+ }
508
+
509
+ .tmux-subagent-copy small {
510
+ color: #647588;
511
+ font-size: 8px;
512
+ }
513
+
514
+ .tmux-subagent-copy strong,
515
+ .tmux-subagent-copy em {
516
+ display: block;
517
+ margin-top: 3px;
518
+ }
519
+
520
+ .tmux-subagent-copy strong {
521
+ color: #b9c7d3;
522
+ font-size: 10px;
523
+ }
524
+
525
+ .tmux-subagent-copy em {
526
+ color: #718294;
527
+ font-size: 9px;
528
+ font-style: normal;
529
+ }
530
+
531
+ .tmux-subagent-row>button {
532
+ min-height: 27px;
533
+ padding: 0 7px;
534
+ border: 1px solid #294139;
535
+ border-radius: 7px;
536
+ background: #10211f;
537
+ color: #72deb8;
538
+ font-size: 9px;
539
+ cursor: pointer;
540
+ }
541
+
542
+ .tmux-subagent-row>button:hover {
543
+ border-color: #3e6b5d;
544
+ background: #142a26;
545
+ }
546
+
393
547
  .tmux-pane-node footer button {
394
548
  height: 27px;
395
549
  padding: 0 8px;
@@ -455,7 +455,17 @@ window.LoadToAgentTerminalWorkbench = function createModule(context) {
455
455
  if (!operation) return;
456
456
  const result = await guarded(operation, message);
457
457
  if (result) {
458
- if (action.startsWith('kill-')) state.selectedTmux = null;
458
+ if (action.startsWith('kill-')) {
459
+ stopCapture();
460
+ state.captureGeneration += 1;
461
+ state.selectedTmux = null;
462
+ state.remoteCapture = '';
463
+ state.remoteViewportAnchor = null;
464
+ state.remoteViewportAtBottom = false;
465
+ if (state.remoteTerminal) state.remoteTerminal.terminal.reset();
466
+ renderAll();
467
+ await showSelection();
468
+ }
459
469
  setTimeout(refreshSnapshot, 300);
460
470
  }
461
471
  }
@@ -406,15 +406,21 @@
406
406
  }
407
407
 
408
408
  async function activate(snapshot, workspaces, mode = 'general') {
409
+ const nextMode = mode === 'tmux' ? 'tmux' : 'general';
410
+ const enteringMode = !state.active || state.mode !== nextMode;
409
411
  state.active = true;
410
- state.mode = mode === 'tmux' ? 'tmux' : 'general';
412
+ state.mode = nextMode;
411
413
  moveWorkbench(state.mode);
412
414
  if (state.mode === 'general') state.selectedTmux = null;
413
415
  if (state.selectedId && !modeSessions().some(item => item.id === state.selectedId)) state.selectedId = null;
414
416
  updateSnapshot(snapshot, workspaces);
415
- if (!state.initialized) return;
417
+ if (!state.initialized) {
418
+ state.active = false;
419
+ return;
420
+ }
416
421
  await refreshSessions();
417
- if (!state.selectedId && !state.selectedTmux) {
422
+ if (!state.active || state.mode !== nextMode) return;
423
+ if (enteringMode && !state.selectedId && !state.selectedTmux) {
418
424
  const visible = modeSessions();
419
425
  if (visible.length) state.selectedId = visible[0].id;
420
426
  else if (state.mode === 'tmux') state.selectedTmux = tmuxRows()[0] || null;
@@ -0,0 +1,67 @@
1
+ 'use strict';
2
+
3
+ class AttentionNotifier {
4
+ constructor(options = {}) {
5
+ this.Notification = options.Notification;
6
+ this.isSupported = options.isSupported || (() => Boolean(this.Notification));
7
+ this.copy = options.copy || (() => ({ title: '내 확인이 필요합니다', body: '응답이나 선택을 기다리는 AI 세션이 있습니다.' }));
8
+ this.onOpen = options.onOpen || (() => {});
9
+ this.onFallback = options.onFallback || (() => {});
10
+ this.waitingIds = null;
11
+ this.notifications = new Set();
12
+ }
13
+
14
+ sync(snapshot) {
15
+ const waiting = (snapshot && Array.isArray(snapshot.sessions) ? snapshot.sessions : [])
16
+ .filter(session => session && session.id && session.status === 'waiting');
17
+ const nextIds = new Set(waiting.map(session => String(session.id)));
18
+
19
+ if (this.waitingIds === null) {
20
+ this.waitingIds = nextIds;
21
+ return [];
22
+ }
23
+
24
+ const newlyWaiting = waiting.filter(session => !this.waitingIds.has(String(session.id)));
25
+ this.waitingIds = nextIds;
26
+ newlyWaiting.forEach(session => this.notify(session));
27
+ return newlyWaiting.map(session => String(session.id));
28
+ }
29
+
30
+ notify(session) {
31
+ let supported = false;
32
+ try {
33
+ supported = Boolean(this.Notification && this.isSupported());
34
+ } catch (_supportProbeFailure) {
35
+ supported = false;
36
+ }
37
+ if (!supported) {
38
+ this.onFallback(session);
39
+ return null;
40
+ }
41
+ try {
42
+ const copy = this.copy(session) || {};
43
+ const notification = new this.Notification({
44
+ title: String(copy.title || '내 확인이 필요합니다'),
45
+ body: String(copy.body || session.title || 'AI 세션이 응답을 기다리고 있습니다.'),
46
+ silent: false,
47
+ });
48
+ this.notifications.add(notification);
49
+ notification.once('click', () => this.onOpen(session));
50
+ notification.once('close', () => this.notifications.delete(notification));
51
+ notification.show();
52
+ return notification;
53
+ } catch (_notificationFailure) {
54
+ this.onFallback(session);
55
+ return null;
56
+ }
57
+ }
58
+
59
+ dispose() {
60
+ for (const notification of this.notifications) {
61
+ try { notification.close(); } catch {}
62
+ }
63
+ this.notifications.clear();
64
+ }
65
+ }
66
+
67
+ module.exports = { AttentionNotifier };
@@ -298,7 +298,7 @@ function applyRuntimePresence(agentSessions, tmuxSnapshot, processSnapshot, now
298
298
  for (const pane of window.panes || []) {
299
299
  const agent = pane.agent;
300
300
  const linked = agent && agent.linkedSessionId && byId.get(agent.linkedSessionId);
301
- if (!linked) continue;
301
+ if (!linked || pane.dead) continue;
302
302
  usedSessionIds.add(linked.id);
303
303
  markRuntime(linked, {
304
304
  id: `tmux:${distro.name}:${pane.nativeId}`,
@@ -271,8 +271,8 @@ function linkAgentSessions(snapshot, agentSessions, now = Date.now()) {
271
271
  for (const pane of window.panes || []) {
272
272
  paneCount += 1;
273
273
  if (!pane.agentProcess) continue;
274
- aiPaneCount += 1;
275
- const ranked = (agentSessions || [])
274
+ if (!pane.dead) aiPaneCount += 1;
275
+ const ranked = pane.dead ? [] : (agentSessions || [])
276
276
  .filter(session => !usedSessionIds.has(session.id))
277
277
  .map(session => ({ session, score: linkScore(session, pane, distro, pane.agentProcess, now) }))
278
278
  .filter(item => item.score >= 1_000)