@agent-link/server 0.1.169 → 0.1.171

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-link/server",
3
- "version": "0.1.169",
3
+ "version": "0.1.171",
4
4
  "description": "AgentLink relay server",
5
5
  "license": "MIT",
6
6
  "repository": {
package/web/app.js CHANGED
@@ -27,6 +27,7 @@ import { createI18n } from './modules/i18n.js';
27
27
 
28
28
  // ── Slash commands ──────────────────────────────────────────────────────────
29
29
  const SLASH_COMMANDS = [
30
+ { command: '/btw', descKey: 'slash.btw', isPrefix: true },
30
31
  { command: '/cost', descKey: 'slash.cost' },
31
32
  { command: '/context', descKey: 'slash.context' },
32
33
  { command: '/compact', descKey: 'slash.compact' },
@@ -69,6 +70,10 @@ const App = {
69
70
  const slashMenuIndex = ref(0);
70
71
  const slashMenuOpen = ref(false);
71
72
 
73
+ // Side question (/btw) state
74
+ const btwState = ref(null);
75
+ const btwPending = ref(false);
76
+
72
77
  // Sidebar state
73
78
  const sidebarOpen = ref(window.innerWidth > 768);
74
79
  const historySessions = ref([]);
@@ -357,6 +362,8 @@ const App = {
357
362
  switchConversation,
358
363
  // Memory management
359
364
  memoryFiles, memoryDir, memoryLoading, memoryEditing, memoryEditContent, memorySaving, memoryPanelOpen,
365
+ // Side question (/btw)
366
+ btwState, btwPending,
360
367
  // i18n
361
368
  t,
362
369
  });
@@ -448,9 +455,23 @@ const App = {
448
455
 
449
456
  // ── Send message ──
450
457
  function sendMessage() {
458
+ const text = inputText.value.trim();
459
+
460
+ // Side question — /btw <question> (allowed even during compaction)
461
+ if (text === '/btw' || text.startsWith('/btw ')) {
462
+ if (status.value !== 'Connected') return;
463
+ const question = text.startsWith('/btw ') ? text.slice(5).trim() : '';
464
+ if (!question) return;
465
+ btwState.value = { question, answer: '', done: false, error: null };
466
+ btwPending.value = true;
467
+ inputText.value = '';
468
+ if (inputRef.value) inputRef.value.style.height = 'auto';
469
+ wsSend({ type: 'btw_question', question, conversationId: currentConversationId.value, claudeSessionId: currentClaudeSessionId.value });
470
+ return;
471
+ }
472
+
451
473
  if (!canSend.value) return;
452
474
 
453
- const text = inputText.value.trim();
454
475
  const files = attachments.value.slice();
455
476
  inputText.value = '';
456
477
  if (inputRef.value) inputRef.value.style.height = 'auto';
@@ -504,6 +525,11 @@ const App = {
504
525
  wsSend(cancelPayload);
505
526
  }
506
527
 
528
+ function dismissBtw() {
529
+ btwState.value = null;
530
+ btwPending.value = false;
531
+ }
532
+
507
533
  function dequeueNext() {
508
534
  if (queuedMessages.value.length === 0) return;
509
535
  const queued = queuedMessages.value.shift();
@@ -528,8 +554,13 @@ const App = {
528
554
 
529
555
  function selectSlashCommand(cmd) {
530
556
  slashMenuOpen.value = false;
531
- inputText.value = cmd.command;
532
- sendMessage();
557
+ if (cmd.isPrefix) {
558
+ inputText.value = cmd.command + ' ';
559
+ nextTick(() => inputRef.value?.focus());
560
+ } else {
561
+ inputText.value = cmd.command;
562
+ sendMessage();
563
+ }
533
564
  }
534
565
 
535
566
  function openSlashMenu() {
@@ -545,7 +576,7 @@ const App = {
545
576
  document.addEventListener('click', _slashMenuClickOutside);
546
577
 
547
578
  function handleKeydown(e) {
548
- // Slash menu key handling
579
+ // Slash menu key handling (must come before btw overlay so Escape closes menu first)
549
580
  if (slashMenuVisible.value && filteredSlashCommands.value.length > 0 && !e.isComposing) {
550
581
  const len = filteredSlashCommands.value.length;
551
582
  if (e.key === 'ArrowDown') {
@@ -575,6 +606,12 @@ const App = {
575
606
  return;
576
607
  }
577
608
  }
609
+ // Btw overlay dismiss (after slash menu so menu Escape takes priority)
610
+ if (e.key === 'Escape' && btwState.value) {
611
+ dismissBtw();
612
+ e.preventDefault();
613
+ return;
614
+ }
578
615
 
579
616
  if (e.key === 'Enter' && !e.shiftKey && !e.isComposing) {
580
617
  e.preventDefault();
@@ -645,6 +682,8 @@ const App = {
645
682
  inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages, usageStats,
646
683
  slashMenuVisible, filteredSlashCommands, slashMenuIndex, slashMenuOpen, selectSlashCommand, openSlashMenu,
647
684
  sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
685
+ // Side question (/btw)
686
+ btwState, btwPending, dismissBtw, renderMarkdown,
648
687
  getRenderedContent, copyMessage, toggleTool,
649
688
  isPrevAssistant: _isPrevAssistant,
650
689
  toggleContextSummary, formatTimestamp, formatUsage: (u) => formatUsage(u, t),
@@ -2440,6 +2479,32 @@ const App = {
2440
2479
  </div>
2441
2480
  </template>
2442
2481
 
2482
+ <!-- ══ Side question overlay ══ -->
2483
+ <Transition name="fade">
2484
+ <div v-if="btwState" class="btw-overlay" @click.self="dismissBtw">
2485
+ <div class="btw-panel">
2486
+ <div class="btw-header">
2487
+ <span class="btw-title">{{ t('btw.title') }}</span>
2488
+ <button class="btw-close" @click="dismissBtw" :aria-label="t('btw.dismiss')">&#10005;</button>
2489
+ </div>
2490
+ <div class="btw-body">
2491
+ <div class="btw-question">{{ btwState.question }}</div>
2492
+ <div v-if="btwState.error" class="btw-error">{{ btwState.error }}</div>
2493
+ <template v-else>
2494
+ <div v-if="btwState.answer" class="btw-answer markdown-body" v-html="renderMarkdown(btwState.answer)"></div>
2495
+ <div v-if="!btwState.done" class="btw-loading">
2496
+ <span class="btw-loading-dots"><span></span><span></span><span></span></span>
2497
+ <span v-if="!btwState.answer" class="btw-loading-text">{{ t('btw.thinking') }}</span>
2498
+ </div>
2499
+ </template>
2500
+ </div>
2501
+ <div v-if="btwState.done && !btwState.error" class="btw-hint">
2502
+ {{ isMobile ? t('btw.tapDismiss') : t('btw.escDismiss') }}
2503
+ </div>
2504
+ </div>
2505
+ </div>
2506
+ </Transition>
2507
+
2443
2508
  <!-- Input area (shown in both chat and team create mode) -->
2444
2509
  <div class="input-area" v-if="viewMode === 'chat'">
2445
2510
  <input
@@ -2483,7 +2548,7 @@ const App = {
2483
2548
  @keydown="handleKeydown"
2484
2549
  @input="autoResize"
2485
2550
  @paste="handlePaste"
2486
- :disabled="status !== 'Connected' || isCompacting"
2551
+ :disabled="status !== 'Connected'"
2487
2552
  :placeholder="isCompacting ? t('input.compacting') : t('input.placeholder')"
2488
2553
  rows="1"
2489
2554
  ></textarea>
@@ -2505,7 +2570,7 @@ const App = {
2505
2570
  <button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" :title="t('input.attachFiles')">
2506
2571
  <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5a2.5 2.5 0 0 1 5 0v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5a2.5 2.5 0 0 0 5 0V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z"/></svg>
2507
2572
  </button>
2508
- <button class="slash-btn" @click="openSlashMenu" :disabled="status !== 'Connected' || isCompacting" :title="t('input.slashCommands')">
2573
+ <button class="slash-btn" @click="openSlashMenu" :disabled="status !== 'Connected'" :title="t('input.slashCommands')">
2509
2574
  <svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 21 11 3h2L9 21H7Z"/></svg>
2510
2575
  </button>
2511
2576
  </div>
@@ -0,0 +1,148 @@
1
+ /* ── Side question (/btw) overlay ─────────────────────────────────────────── */
2
+
3
+ .btw-overlay {
4
+ position: absolute;
5
+ inset: 0;
6
+ display: flex;
7
+ align-items: center;
8
+ justify-content: center;
9
+ background: var(--overlay-bg, rgba(0, 0, 0, 0.3));
10
+ z-index: 500;
11
+ }
12
+
13
+ .btw-panel {
14
+ background: var(--bg-primary);
15
+ border: 1px solid var(--border);
16
+ border-radius: 12px;
17
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.2);
18
+ width: 90%;
19
+ max-width: 560px;
20
+ max-height: 60vh;
21
+ display: flex;
22
+ flex-direction: column;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .btw-header {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ padding: 12px 16px;
31
+ border-bottom: 1px solid var(--border);
32
+ flex-shrink: 0;
33
+ }
34
+
35
+ .btw-title {
36
+ font-weight: 600;
37
+ font-size: 14px;
38
+ color: var(--text-primary);
39
+ }
40
+
41
+ .btw-close {
42
+ background: none;
43
+ border: none;
44
+ font-size: 16px;
45
+ cursor: pointer;
46
+ color: var(--text-secondary);
47
+ padding: 4px 8px;
48
+ border-radius: 4px;
49
+ }
50
+
51
+ .btw-close:hover {
52
+ background: var(--bg-hover);
53
+ color: var(--text-primary);
54
+ }
55
+
56
+ .btw-body {
57
+ padding: 16px;
58
+ overflow-y: auto;
59
+ flex: 1;
60
+ min-height: 0;
61
+ }
62
+
63
+ .btw-question {
64
+ font-size: 13px;
65
+ color: var(--text-secondary);
66
+ margin-bottom: 12px;
67
+ padding-bottom: 12px;
68
+ border-bottom: 1px solid var(--border);
69
+ font-style: italic;
70
+ }
71
+
72
+ .btw-answer {
73
+ font-size: 14px;
74
+ color: var(--text-primary);
75
+ line-height: 1.6;
76
+ }
77
+
78
+ .btw-error {
79
+ font-size: 13px;
80
+ color: var(--danger, #e53e3e);
81
+ background: var(--danger-bg, rgba(229, 62, 62, 0.08));
82
+ padding: 10px 12px;
83
+ border-radius: 6px;
84
+ }
85
+
86
+ .btw-loading {
87
+ display: flex;
88
+ flex-direction: column;
89
+ align-items: center;
90
+ justify-content: center;
91
+ padding: 24px 0;
92
+ gap: 12px;
93
+ }
94
+
95
+ .btw-loading-dots {
96
+ display: flex;
97
+ align-items: center;
98
+ gap: 6px;
99
+ }
100
+
101
+ .btw-loading-dots span {
102
+ width: 8px;
103
+ height: 8px;
104
+ border-radius: 50%;
105
+ background: var(--accent, #6366f1);
106
+ animation: btw-bounce 1.4s infinite ease-in-out both;
107
+ }
108
+
109
+ .btw-loading-dots span:nth-child(1) { animation-delay: -0.32s; }
110
+ .btw-loading-dots span:nth-child(2) { animation-delay: -0.16s; }
111
+ .btw-loading-dots span:nth-child(3) { animation-delay: 0s; }
112
+
113
+ @keyframes btw-bounce {
114
+ 0%, 80%, 100% { transform: scale(0.4); opacity: 0.4; }
115
+ 40% { transform: scale(1); opacity: 1; }
116
+ }
117
+
118
+ .btw-loading-text {
119
+ font-size: 13px;
120
+ color: var(--text-secondary);
121
+ }
122
+
123
+ .btw-hint {
124
+ text-align: center;
125
+ font-size: 12px;
126
+ color: var(--text-tertiary);
127
+ padding: 8px 16px 12px;
128
+ flex-shrink: 0;
129
+ }
130
+
131
+ /* ── Mobile (< 768px) ──────────────────────────────────────────────────── */
132
+
133
+ @media (max-width: 768px) {
134
+ .btw-panel {
135
+ width: calc(100% - 24px);
136
+ max-width: none;
137
+ max-height: 50vh;
138
+ margin: 0 12px;
139
+ }
140
+
141
+ .btw-header {
142
+ padding: 10px 14px;
143
+ }
144
+
145
+ .btw-body {
146
+ padding: 14px;
147
+ }
148
+ }
package/web/css/chat.css CHANGED
@@ -4,6 +4,7 @@
4
4
  display: flex;
5
5
  flex-direction: column;
6
6
  min-height: 0;
7
+ position: relative;
7
8
  }
8
9
 
9
10
  /* ── Message list ── */
package/web/index.html CHANGED
@@ -16,6 +16,7 @@
16
16
  <link rel="stylesheet" href="/css/team.css">
17
17
  <link rel="stylesheet" href="/css/responsive.css">
18
18
  <link rel="stylesheet" href="/css/loop.css">
19
+ <link rel="stylesheet" href="/css/btw.css">
19
20
  <link id="hljs-theme" rel="stylesheet" href="/vendor/github.min.css">
20
21
  <script>
21
22
  // Apply saved theme immediately to prevent flash
@@ -171,7 +171,7 @@
171
171
  "chat.customResponse": "Or type a custom response...",
172
172
 
173
173
  "input.placeholder": "Send a message · Enter to send",
174
- "input.compacting": "Context compacting in progress...",
174
+ "input.compacting": "Compacting context... (type /btw to ask a side question)",
175
175
  "input.removeFromQueue": "Remove from queue",
176
176
  "input.attachFiles": "Attach files",
177
177
  "input.stopGeneration": "Stop generation",
@@ -264,5 +264,12 @@
264
264
 
265
265
  "slash.cost": "Show token usage and cost",
266
266
  "slash.context": "Show context usage",
267
- "slash.compact": "Compact context"
267
+ "slash.compact": "Compact context",
268
+
269
+ "slash.btw": "Ask a side question (won't affect conversation)",
270
+ "btw.title": "Side Question",
271
+ "btw.dismiss": "Dismiss",
272
+ "btw.thinking": "Thinking...",
273
+ "btw.escDismiss": "Press Esc to dismiss",
274
+ "btw.tapDismiss": "Tap to dismiss"
268
275
  }
@@ -171,7 +171,7 @@
171
171
  "chat.customResponse": "或输入自定义回复...",
172
172
 
173
173
  "input.placeholder": "发送消息 · 按 Enter 发送",
174
- "input.compacting": "上下文压缩中...",
174
+ "input.compacting": "上下文压缩中... (输入 /btw 可提旁问)",
175
175
  "input.removeFromQueue": "从队列中移除",
176
176
  "input.attachFiles": "附加文件",
177
177
  "input.stopGeneration": "停止生成",
@@ -264,5 +264,12 @@
264
264
 
265
265
  "slash.cost": "显示 Token 用量和费用",
266
266
  "slash.context": "显示上下文用量",
267
- "slash.compact": "压缩上下文"
267
+ "slash.compact": "压缩上下文",
268
+
269
+ "slash.btw": "快速旁问(不影响对话)",
270
+ "btw.title": "旁问",
271
+ "btw.dismiss": "关闭",
272
+ "btw.thinking": "思考中...",
273
+ "btw.escDismiss": "按 Esc 关闭",
274
+ "btw.tapDismiss": "点击关闭"
268
275
  }
@@ -27,6 +27,8 @@ export function createConnection(deps) {
27
27
  switchConversation,
28
28
  // Memory management
29
29
  memoryFiles, memoryDir, memoryLoading, memoryEditing, memoryEditContent, memorySaving, memoryPanelOpen,
30
+ // Side question (/btw)
31
+ btwState, btwPending,
30
32
  // i18n
31
33
  t,
32
34
  } = deps;
@@ -360,6 +362,7 @@ export function createConnection(deps) {
360
362
  }
361
363
 
362
364
  // Clear foreground
365
+ const wasForegroundProcessing = isProcessing.value;
363
366
  if (!activeSet.has(currentConversationId && currentConversationId.value)) {
364
367
  isProcessing.value = false;
365
368
  isCompacting.value = false;
@@ -406,7 +409,18 @@ export function createConnection(deps) {
406
409
  team.handleActiveTeamRestore(msg.activeTeam, workDir.value);
407
410
  }
408
411
  resetIdleCheck();
412
+ // If foreground was processing but no longer is, dequeue pending messages
413
+ if (wasForegroundProcessing && !isProcessing.value) _dequeueNext();
409
414
  } else if (msg.type === 'error') {
415
+ // Route btw-related errors to the overlay instead of the message list
416
+ if (btwPending && btwPending.value && msg.message && msg.message.includes('btw_question')) {
417
+ btwPending.value = false;
418
+ if (btwState && btwState.value) {
419
+ btwState.value.error = msg.message;
420
+ btwState.value.done = true;
421
+ }
422
+ return;
423
+ }
410
424
  streaming.flushReveal();
411
425
  finalizeStreamingMsg(scheduleHighlight);
412
426
  messages.value.push({
@@ -582,6 +596,14 @@ export function createConnection(deps) {
582
596
  // Close preview if open (might be showing the deleted file)
583
597
  if (filePreview) filePreview.closePreview();
584
598
  }
599
+ } else if (msg.type === 'btw_answer') {
600
+ if (btwPending) btwPending.value = false;
601
+ if (btwState && btwState.value) {
602
+ btwState.value.answer += msg.delta;
603
+ if (msg.done) {
604
+ btwState.value.done = true;
605
+ }
606
+ }
585
607
  } else if (msg.type === 'workdir_changed') {
586
608
  workdirSwitching.value = false;
587
609
  workDir.value = msg.workDir;