@agent-link/server 0.1.27 → 0.1.28

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.27",
3
+ "version": "0.1.28",
4
4
  "description": "AgentLink relay server",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/web/app.js CHANGED
@@ -356,7 +356,16 @@ const App = {
356
356
  }
357
357
 
358
358
  let _scrollTimer = null;
359
- function scrollToBottom() {
359
+ let _userScrolledUp = false;
360
+
361
+ function onMessageListScroll(e) {
362
+ const el = e.target;
363
+ // Consider "at bottom" if within 80px of the bottom
364
+ _userScrolledUp = (el.scrollHeight - el.scrollTop - el.clientHeight) > 80;
365
+ }
366
+
367
+ function scrollToBottom(force) {
368
+ if (_userScrolledUp && !force) return;
360
369
  if (_scrollTimer) return;
361
370
  _scrollTimer = setTimeout(() => {
362
371
  _scrollTimer = null;
@@ -394,7 +403,7 @@ const App = {
394
403
  timestamp: new Date(),
395
404
  });
396
405
  isProcessing.value = true;
397
- scrollToBottom();
406
+ scrollToBottom(true);
398
407
 
399
408
  // Build payload
400
409
  const payload = { type: 'chat', prompt: text || '(see attached files)' };
@@ -428,6 +437,12 @@ const App = {
428
437
  }
429
438
 
430
439
  // ── Rendered markdown for assistant messages ──
440
+ function formatTimestamp(ts) {
441
+ if (!ts) return '';
442
+ const d = ts instanceof Date ? ts : new Date(ts);
443
+ return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) + ' · ' + d.toLocaleDateString();
444
+ }
445
+
431
446
  function getRenderedContent(msg) {
432
447
  if (msg.role !== 'assistant' && !msg.isCommandOutput) return msg.content;
433
448
  return renderMarkdown(msg.content);
@@ -1144,12 +1159,17 @@ const App = {
1144
1159
  onMounted(() => { connect(); });
1145
1160
  onUnmounted(() => { if (reconnectTimer) clearTimeout(reconnectTimer); if (ws) ws.close(); });
1146
1161
 
1162
+ // Dynamic page title
1163
+ watch(agentName, (name) => {
1164
+ document.title = name ? `${name} — AgentLink` : 'AgentLink';
1165
+ });
1166
+
1147
1167
  return {
1148
1168
  status, agentName, hostname, workDir, sessionId, error,
1149
1169
  messages, visibleMessages, hasMoreMessages, loadMoreMessages,
1150
1170
  inputText, isProcessing, isCompacting, canSend, inputRef,
1151
- sendMessage, handleKeydown, cancelExecution,
1152
- getRenderedContent, copyMessage, toggleTool, isPrevAssistant, toggleContextSummary,
1171
+ sendMessage, handleKeydown, cancelExecution, onMessageListScroll,
1172
+ getRenderedContent, copyMessage, toggleTool, isPrevAssistant, toggleContextSummary, formatTimestamp,
1153
1173
  getToolIcon, getToolSummary, isEditTool, getEditDiffHtml, getFormattedToolInput, autoResize,
1154
1174
  // AskUserQuestion
1155
1175
  selectQuestionOption, submitQuestionAnswer, hasQuestionAnswer, getQuestionResponseSummary,
@@ -1261,7 +1281,7 @@ const App = {
1261
1281
 
1262
1282
  <!-- Chat area -->
1263
1283
  <div class="chat-area">
1264
- <div class="message-list">
1284
+ <div class="message-list" @scroll="onMessageListScroll">
1265
1285
  <div class="message-list-inner">
1266
1286
  <div v-if="messages.length === 0 && status === 'Connected' && !loadingHistory" class="empty-state">
1267
1287
  <div class="empty-state-icon">
@@ -1286,7 +1306,7 @@ const App = {
1286
1306
  <!-- User message -->
1287
1307
  <template v-if="msg.role === 'user'">
1288
1308
  <div class="message-role-label user-label">You</div>
1289
- <div class="message-bubble user-bubble">
1309
+ <div class="message-bubble user-bubble" :title="formatTimestamp(msg.timestamp)">
1290
1310
  <div v-if="msg.isCommandOutput" class="message-content markdown-body" v-html="getRenderedContent(msg)"></div>
1291
1311
  <div v-else class="message-content">{{ msg.content }}</div>
1292
1312
  <div v-if="msg.attachments && msg.attachments.length" class="message-attachments">
@@ -1304,7 +1324,7 @@ const App = {
1304
1324
  <!-- Assistant message (markdown) -->
1305
1325
  <template v-else-if="msg.role === 'assistant'">
1306
1326
  <div v-if="!isPrevAssistant(msgIdx)" class="message-role-label assistant-label">Claude</div>
1307
- <div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]">
1327
+ <div :class="['message-bubble', 'assistant-bubble', { streaming: msg.isStreaming }]" :title="formatTimestamp(msg.timestamp)">
1308
1328
  <div class="message-actions">
1309
1329
  <button class="icon-btn" @click="copyMessage(msg)" :title="msg.copied ? 'Copied!' : 'Copy'">
1310
1330
  <svg v-if="!msg.copied" viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z"/></svg>
@@ -1327,7 +1347,7 @@ const App = {
1327
1347
  </span>
1328
1348
  <span class="tool-toggle">{{ msg.expanded ? '\u{25B2}' : '\u{25BC}' }}</span>
1329
1349
  </div>
1330
- <div v-if="msg.expanded" class="tool-expand">
1350
+ <div v-show="msg.expanded" class="tool-expand">
1331
1351
  <div v-if="isEditTool(msg) && getEditDiffHtml(msg)" class="tool-diff" v-html="getEditDiffHtml(msg)"></div>
1332
1352
  <div v-else-if="getFormattedToolInput(msg)" class="tool-input-formatted" v-html="getFormattedToolInput(msg)"></div>
1333
1353
  <pre v-else-if="msg.toolInput" class="tool-block">{{ msg.toolInput }}</pre>
@@ -1428,7 +1448,7 @@ const App = {
1428
1448
  @input="autoResize"
1429
1449
  @paste="handlePaste"
1430
1450
  :disabled="status !== 'Connected' || isCompacting"
1431
- :placeholder="isCompacting ? 'Context compacting in progress...' : 'Send a message...'"
1451
+ :placeholder="isCompacting ? 'Context compacting in progress...' : 'Send a message · Enter to send'"
1432
1452
  rows="1"
1433
1453
  ></textarea>
1434
1454
  <div v-if="attachments.length > 0" class="attachment-bar">
package/web/style.css CHANGED
@@ -6,6 +6,16 @@
6
6
  padding: 0;
7
7
  }
8
8
 
9
+ /* Keyboard focus outlines (visible only for keyboard navigation) */
10
+ :focus-visible {
11
+ outline: 2px solid var(--accent);
12
+ outline-offset: 2px;
13
+ }
14
+
15
+ :focus:not(:focus-visible) {
16
+ outline: none;
17
+ }
18
+
9
19
  :root {
10
20
  --bg-primary: #0f172a;
11
21
  --bg-secondary: #1e293b;
@@ -29,7 +39,7 @@
29
39
  --bg-secondary: #f8f9fa;
30
40
  --bg-tertiary: #e9ecef;
31
41
  --text-primary: #1a1a1a;
32
- --text-secondary: #6b7280;
42
+ --text-secondary: #4b5563;
33
43
  --accent: #2563eb;
34
44
  --accent-hover: #1d4ed8;
35
45
  --success: #16a34a;
@@ -753,6 +763,13 @@ body {
753
763
  margin-left: 20px;
754
764
  border-left: 1px solid var(--border);
755
765
  padding-left: 8px;
766
+ overflow: hidden;
767
+ animation: toolExpand 0.15s ease-out;
768
+ }
769
+
770
+ @keyframes toolExpand {
771
+ from { opacity: 0; max-height: 0; }
772
+ to { opacity: 1; max-height: 500px; }
756
773
  }
757
774
 
758
775
  .tool-block {