@agent-link/server 0.1.126 → 0.1.127

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.126",
3
+ "version": "0.1.127",
4
4
  "description": "AgentLink relay server",
5
5
  "license": "MIT",
6
6
  "repository": {
package/web/app.js CHANGED
@@ -80,6 +80,9 @@ const App = {
80
80
  // Working directory history
81
81
  const workdirHistory = ref([]);
82
82
 
83
+ // Working directory switching loading state
84
+ const workdirSwitching = ref(false);
85
+
83
86
  // Authentication state
84
87
  const authRequired = ref(false);
85
88
  const authPassword = ref('');
@@ -112,6 +115,7 @@ const App = {
112
115
  const previewPanelWidth = ref(parseInt(localStorage.getItem('agentlink-preview-panel-width'), 10) || 400);
113
116
  const previewFile = ref(null);
114
117
  const previewLoading = ref(false);
118
+ const previewMarkdownRendered = ref(false);
115
119
 
116
120
  // ── switchConversation: save current → load target ──
117
121
  // Defined here and used by sidebar.newConversation, sidebar.resumeSession, workdir_changed
@@ -249,7 +253,7 @@ const App = {
249
253
  folderPickerLoading, folderPickerSelected, streaming,
250
254
  deleteConfirmOpen, deleteConfirmTitle,
251
255
  renamingSessionId, renameText,
252
- hostname, workdirHistory,
256
+ hostname, workdirHistory, workdirSwitching,
253
257
  // Multi-session parallel
254
258
  currentConversationId, conversationCache, processingConversations,
255
259
  switchConversation,
@@ -263,6 +267,7 @@ const App = {
263
267
  folderPickerLoading, folderPickerEntries, folderPickerPath,
264
268
  authRequired, authPassword, authError, authAttempts, authLocked,
265
269
  streaming, sidebar, scrollToBottom,
270
+ workdirSwitching,
266
271
  // Multi-session parallel
267
272
  currentConversationId, processingConversations, conversationCache,
268
273
  switchConversation,
@@ -287,7 +292,7 @@ const App = {
287
292
  // File preview module
288
293
  const filePreview = createFilePreview({
289
294
  wsSend, previewPanelOpen, previewPanelWidth, previewFile, previewLoading,
290
- sidebarView, sidebarOpen, isMobile,
295
+ previewMarkdownRendered, sidebarView, sidebarOpen, isMobile, renderMarkdown,
291
296
  });
292
297
  setFilePreview(filePreview);
293
298
 
@@ -498,6 +503,7 @@ const App = {
498
503
  filteredWorkdirHistory: sidebar.filteredWorkdirHistory,
499
504
  switchToWorkdir: sidebar.switchToWorkdir,
500
505
  removeFromWorkdirHistory: sidebar.removeFromWorkdirHistory,
506
+ workdirSwitching,
501
507
  // Authentication
502
508
  authRequired, authPassword, authError, authAttempts, authLocked,
503
509
  submitPassword,
@@ -516,7 +522,7 @@ const App = {
516
522
  sidebarView, isMobile, fileBrowser,
517
523
  flattenedTree: fileBrowser.flattenedTree,
518
524
  // File preview
519
- previewPanelOpen, previewPanelWidth, previewFile, previewLoading, filePreview,
525
+ previewPanelOpen, previewPanelWidth, previewFile, previewLoading, previewMarkdownRendered, filePreview,
520
526
  workdirMenuOpen,
521
527
  toggleWorkdirMenu() { workdirMenuOpen.value = !workdirMenuOpen.value; },
522
528
  workdirMenuBrowse() {
@@ -617,9 +623,17 @@ const App = {
617
623
  <svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z"/></svg>
618
624
  Files
619
625
  </button>
620
- <span v-if="previewFile" class="file-preview-mobile-size">
621
- {{ filePreview.formatFileSize(previewFile.totalSize) }}
622
- </span>
626
+ <div class="preview-header-actions">
627
+ <button v-if="previewFile?.content && filePreview.isMarkdownFile(previewFile.fileName)"
628
+ class="preview-md-toggle" :class="{ active: previewMarkdownRendered }"
629
+ @click="previewMarkdownRendered = !previewMarkdownRendered"
630
+ :title="previewMarkdownRendered ? 'Show source' : 'Render markdown'">
631
+ <svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"/></svg>
632
+ </button>
633
+ <span v-if="previewFile" class="file-preview-mobile-size">
634
+ {{ filePreview.formatFileSize(previewFile.totalSize) }}
635
+ </span>
636
+ </div>
623
637
  </div>
624
638
  <div class="file-preview-mobile-filename" :title="previewFile?.filePath">
625
639
  {{ previewFile?.fileName || 'Preview' }}
@@ -634,6 +648,9 @@ const App = {
634
648
  <img :src="'data:' + previewFile.mimeType + ';base64,' + previewFile.content"
635
649
  :alt="previewFile.fileName" class="preview-image" />
636
650
  </div>
651
+ <div v-else-if="previewFile?.content && previewMarkdownRendered && filePreview.isMarkdownFile(previewFile.fileName)"
652
+ class="preview-markdown-rendered markdown-body" v-html="filePreview.renderedMarkdownHtml(previewFile.content)">
653
+ </div>
637
654
  <div v-else-if="previewFile?.content" class="preview-text-container">
638
655
  <pre class="preview-code"><code v-html="filePreview.highlightCode(previewFile.content, previewFile.fileName)"></code></pre>
639
656
  <div v-if="previewFile.truncated" class="preview-truncated-notice">
@@ -1036,6 +1053,12 @@ const App = {
1036
1053
  <span class="preview-panel-filename" :title="previewFile?.filePath">
1037
1054
  {{ previewFile?.fileName || 'Preview' }}
1038
1055
  </span>
1056
+ <button v-if="previewFile?.content && filePreview.isMarkdownFile(previewFile.fileName)"
1057
+ class="preview-md-toggle" :class="{ active: previewMarkdownRendered }"
1058
+ @click="previewMarkdownRendered = !previewMarkdownRendered"
1059
+ :title="previewMarkdownRendered ? 'Show source' : 'Render markdown'">
1060
+ <svg viewBox="0 0 16 16" width="14" height="14"><path fill="currentColor" d="M14.85 3H1.15C.52 3 0 3.52 0 4.15v7.69C0 12.48.52 13 1.15 13h13.69c.64 0 1.15-.52 1.15-1.15v-7.7C16 3.52 15.48 3 14.85 3zM9 11H7V8L5.5 9.92 4 8v3H2V5h2l1.5 2L7 5h2v6zm2.99.5L9.5 8H11V5h2v3h1.5l-2.51 3.5z"/></svg>
1061
+ </button>
1039
1062
  <span v-if="previewFile" class="preview-panel-size">
1040
1063
  {{ filePreview.formatFileSize(previewFile.totalSize) }}
1041
1064
  </span>
@@ -1051,6 +1074,9 @@ const App = {
1051
1074
  <img :src="'data:' + previewFile.mimeType + ';base64,' + previewFile.content"
1052
1075
  :alt="previewFile.fileName" class="preview-image" />
1053
1076
  </div>
1077
+ <div v-else-if="previewFile?.content && previewMarkdownRendered && filePreview.isMarkdownFile(previewFile.fileName)"
1078
+ class="preview-markdown-rendered markdown-body" v-html="filePreview.renderedMarkdownHtml(previewFile.content)">
1079
+ </div>
1054
1080
  <div v-else-if="previewFile?.content" class="preview-text-container">
1055
1081
  <pre class="preview-code"><code v-html="filePreview.highlightCode(previewFile.content, previewFile.fileName)"></code></pre>
1056
1082
  <div v-if="previewFile.truncated" class="preview-truncated-notice">
@@ -1165,6 +1191,14 @@ const App = {
1165
1191
  </div>
1166
1192
  </div>
1167
1193
 
1194
+ <!-- Workdir switching overlay -->
1195
+ <Transition name="fade">
1196
+ <div v-if="workdirSwitching" class="workdir-switching-overlay">
1197
+ <div class="workdir-switching-spinner"></div>
1198
+ <div class="workdir-switching-text">Switching directory...</div>
1199
+ </div>
1200
+ </Transition>
1201
+
1168
1202
  <!-- File context menu -->
1169
1203
  <div
1170
1204
  v-if="fileContextMenu"
@@ -20,6 +20,7 @@ export function createConnection(deps) {
20
20
  authRequired, authPassword, authError, authAttempts, authLocked,
21
21
  streaming, sidebar,
22
22
  scrollToBottom,
23
+ workdirSwitching,
23
24
  // Multi-session parallel
24
25
  currentConversationId, processingConversations, conversationCache,
25
26
  switchConversation,
@@ -504,6 +505,8 @@ export function createConnection(deps) {
504
505
  sidebar.addToWorkdirHistory(msg.agent.workDir);
505
506
  const savedDir = localStorage.getItem(`agentlink-workdir-${sessionId.value}`);
506
507
  if (savedDir && savedDir !== msg.agent.workDir) {
508
+ workdirSwitching.value = true;
509
+ setTimeout(() => { workdirSwitching.value = false; }, 10000);
507
510
  wsSend({ type: 'change_workdir', workDir: savedDir });
508
511
  }
509
512
  sidebar.requestSessionList();
@@ -797,6 +800,7 @@ export function createConnection(deps) {
797
800
  } else if (msg.type === 'file_content') {
798
801
  if (filePreview) filePreview.handleFileContent(msg);
799
802
  } else if (msg.type === 'workdir_changed') {
803
+ workdirSwitching.value = false;
800
804
  workDir.value = msg.workDir;
801
805
  localStorage.setItem(`agentlink-workdir-${sessionId.value}`, msg.workDir);
802
806
  sidebar.addToWorkdirHistory(msg.workDir);
@@ -11,9 +11,11 @@ export function createFilePreview(deps) {
11
11
  previewPanelWidth,
12
12
  previewFile,
13
13
  previewLoading,
14
+ previewMarkdownRendered,
14
15
  sidebarView,
15
16
  sidebarOpen,
16
17
  isMobile,
18
+ renderMarkdown,
17
19
  } = deps;
18
20
 
19
21
  // ── Open / Close ──
@@ -52,6 +54,7 @@ export function createFilePreview(deps) {
52
54
 
53
55
  function handleFileContent(msg) {
54
56
  previewLoading.value = false;
57
+ previewMarkdownRendered.value = false;
55
58
  previewFile.value = {
56
59
  filePath: msg.filePath,
57
60
  fileName: msg.fileName,
@@ -174,6 +177,17 @@ export function createFilePreview(deps) {
174
177
  localStorage.setItem('agentlink-preview-panel-width', String(previewPanelWidth.value));
175
178
  }
176
179
 
180
+ // ── Markdown preview ──
181
+
182
+ function isMarkdownFile(fileName) {
183
+ const ext = (fileName || '').split('.').pop()?.toLowerCase();
184
+ return ext === 'md' || ext === 'mdx';
185
+ }
186
+
187
+ function renderedMarkdownHtml(content) {
188
+ return renderMarkdown(content || '');
189
+ }
190
+
177
191
  return {
178
192
  openPreview,
179
193
  closePreview,
@@ -183,5 +197,7 @@ export function createFilePreview(deps) {
183
197
  highlightCode,
184
198
  formatFileSize,
185
199
  onResizeStart,
200
+ isMarkdownFile,
201
+ renderedMarkdownHtml,
186
202
  };
187
203
  }
@@ -31,12 +31,20 @@ export function createSidebar(deps) {
31
31
  loadingSessions, loadingHistory, workDir, visibleLimit,
32
32
  folderPickerOpen, folderPickerPath, folderPickerEntries,
33
33
  folderPickerLoading, folderPickerSelected, streaming,
34
- hostname, workdirHistory,
34
+ hostname, workdirHistory, workdirSwitching,
35
35
  // Multi-session parallel
36
36
  currentConversationId, conversationCache, processingConversations,
37
37
  switchConversation,
38
38
  } = deps;
39
39
 
40
+ // ── Workdir switching timeout ──
41
+ let _workdirSwitchTimer = null;
42
+ function setWorkdirSwitching() {
43
+ workdirSwitching.value = true;
44
+ clearTimeout(_workdirSwitchTimer);
45
+ _workdirSwitchTimer = setTimeout(() => { workdirSwitching.value = false; }, 10000);
46
+ }
47
+
40
48
  // ── Session management ──
41
49
 
42
50
  let _sessionListTimer = null;
@@ -278,6 +286,7 @@ export function createSidebar(deps) {
278
286
  path = path.replace(/[/\\]$/, '') + sep + folderPickerSelected.value;
279
287
  }
280
288
  folderPickerOpen.value = false;
289
+ setWorkdirSwitching();
281
290
  wsSend({ type: 'change_workdir', workDir: path });
282
291
  }
283
292
 
@@ -316,6 +325,7 @@ export function createSidebar(deps) {
316
325
  }
317
326
 
318
327
  function switchToWorkdir(path) {
328
+ setWorkdirSwitching();
319
329
  wsSend({ type: 'change_workdir', workDir: path });
320
330
  }
321
331
 
package/web/style.css CHANGED
@@ -1939,6 +1939,42 @@ body {
1939
1939
  color: var(--text-secondary);
1940
1940
  }
1941
1941
 
1942
+ /* ── Workdir switching overlay ── */
1943
+ .workdir-switching-overlay {
1944
+ position: fixed;
1945
+ top: 0; left: 0; right: 0; bottom: 0;
1946
+ background: rgba(0, 0, 0, 0.45);
1947
+ z-index: 1100;
1948
+ display: flex;
1949
+ flex-direction: column;
1950
+ align-items: center;
1951
+ justify-content: center;
1952
+ gap: 16px;
1953
+ backdrop-filter: blur(2px);
1954
+ }
1955
+ .workdir-switching-spinner {
1956
+ width: 36px;
1957
+ height: 36px;
1958
+ border: 3px solid rgba(255, 255, 255, 0.2);
1959
+ border-top-color: rgba(255, 255, 255, 0.8);
1960
+ border-radius: 50%;
1961
+ animation: workdir-spin 0.7s linear infinite;
1962
+ }
1963
+ @keyframes workdir-spin {
1964
+ to { transform: rotate(360deg); }
1965
+ }
1966
+ .workdir-switching-text {
1967
+ color: rgba(255, 255, 255, 0.9);
1968
+ font-size: 0.9rem;
1969
+ font-weight: 500;
1970
+ }
1971
+ .fade-enter-active, .fade-leave-active {
1972
+ transition: opacity 0.2s ease;
1973
+ }
1974
+ .fade-enter-from, .fade-leave-to {
1975
+ opacity: 0;
1976
+ }
1977
+
1942
1978
  /* ── Folder Picker Modal ── */
1943
1979
  .folder-picker-overlay {
1944
1980
  position: fixed;
@@ -2618,6 +2654,40 @@ body {
2618
2654
  color: var(--text);
2619
2655
  }
2620
2656
 
2657
+ .preview-md-toggle {
2658
+ background: none;
2659
+ border: 1px solid var(--border);
2660
+ border-radius: 4px;
2661
+ cursor: pointer;
2662
+ color: var(--text-secondary);
2663
+ padding: 2px 6px;
2664
+ line-height: 1;
2665
+ display: flex;
2666
+ align-items: center;
2667
+ flex-shrink: 0;
2668
+ }
2669
+
2670
+ .preview-md-toggle:hover {
2671
+ color: var(--text);
2672
+ border-color: var(--text-secondary);
2673
+ }
2674
+
2675
+ .preview-md-toggle.active {
2676
+ color: var(--accent, #6366f1);
2677
+ border-color: var(--accent, #6366f1);
2678
+ background: color-mix(in srgb, var(--accent, #6366f1) 10%, transparent);
2679
+ }
2680
+
2681
+ .preview-header-actions {
2682
+ display: flex;
2683
+ align-items: center;
2684
+ gap: 0.5rem;
2685
+ }
2686
+
2687
+ .preview-markdown-rendered {
2688
+ padding: 1rem;
2689
+ }
2690
+
2621
2691
  .preview-panel-body {
2622
2692
  flex: 1;
2623
2693
  overflow: auto;