@agent-link/server 0.1.166 → 0.1.168
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 +1 -1
- package/web/app.js +27 -4
- package/web/css/input.css +31 -0
- package/web/locales/en.json +1 -0
- package/web/locales/zh.json +1 -0
- package/web/modules/connection.js +23 -0
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -67,6 +67,7 @@ const App = {
|
|
|
67
67
|
const usageStats = ref(null);
|
|
68
68
|
const inputRef = ref(null);
|
|
69
69
|
const slashMenuIndex = ref(0);
|
|
70
|
+
const slashMenuOpen = ref(false);
|
|
70
71
|
|
|
71
72
|
// Sidebar state
|
|
72
73
|
const sidebarOpen = ref(window.innerWidth > 768);
|
|
@@ -425,10 +426,12 @@ const App = {
|
|
|
425
426
|
|
|
426
427
|
// ── Slash command menu ──
|
|
427
428
|
const slashMenuVisible = computed(() => {
|
|
429
|
+
if (slashMenuOpen.value) return true;
|
|
428
430
|
const txt = inputText.value;
|
|
429
431
|
return txt.startsWith('/') && !/\s/.test(txt.slice(1));
|
|
430
432
|
});
|
|
431
433
|
const filteredSlashCommands = computed(() => {
|
|
434
|
+
if (slashMenuOpen.value && !inputText.value.startsWith('/')) return SLASH_COMMANDS;
|
|
432
435
|
const txt = inputText.value.toLowerCase();
|
|
433
436
|
return SLASH_COMMANDS.filter(c => c.command.startsWith(txt));
|
|
434
437
|
});
|
|
@@ -524,10 +527,23 @@ const App = {
|
|
|
524
527
|
}
|
|
525
528
|
|
|
526
529
|
function selectSlashCommand(cmd) {
|
|
530
|
+
slashMenuOpen.value = false;
|
|
527
531
|
inputText.value = cmd.command;
|
|
528
532
|
sendMessage();
|
|
529
533
|
}
|
|
530
534
|
|
|
535
|
+
function openSlashMenu() {
|
|
536
|
+
slashMenuOpen.value = !slashMenuOpen.value;
|
|
537
|
+
slashMenuIndex.value = 0;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function _slashMenuClickOutside(e) {
|
|
541
|
+
if (slashMenuOpen.value && !e.target.closest('.slash-btn') && !e.target.closest('.slash-menu')) {
|
|
542
|
+
slashMenuOpen.value = false;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
document.addEventListener('click', _slashMenuClickOutside);
|
|
546
|
+
|
|
531
547
|
function handleKeydown(e) {
|
|
532
548
|
// Slash menu key handling
|
|
533
549
|
if (slashMenuVisible.value && filteredSlashCommands.value.length > 0 && !e.isComposing) {
|
|
@@ -554,6 +570,7 @@ const App = {
|
|
|
554
570
|
}
|
|
555
571
|
if (e.key === 'Escape') {
|
|
556
572
|
e.preventDefault();
|
|
573
|
+
slashMenuOpen.value = false;
|
|
557
574
|
inputText.value = '';
|
|
558
575
|
return;
|
|
559
576
|
}
|
|
@@ -617,6 +634,7 @@ const App = {
|
|
|
617
634
|
closeWs(); streaming.cleanup(); cleanupScroll(); cleanupHighlight();
|
|
618
635
|
window.removeEventListener('resize', _resizeHandler);
|
|
619
636
|
document.removeEventListener('click', _workdirMenuClickHandler);
|
|
637
|
+
document.removeEventListener('click', _slashMenuClickOutside);
|
|
620
638
|
document.removeEventListener('keydown', _workdirMenuKeyHandler);
|
|
621
639
|
});
|
|
622
640
|
|
|
@@ -625,7 +643,7 @@ const App = {
|
|
|
625
643
|
serverVersion, agentVersion, latency,
|
|
626
644
|
messages, visibleMessages, hasMoreMessages, loadMoreMessages,
|
|
627
645
|
inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages, usageStats,
|
|
628
|
-
slashMenuVisible, filteredSlashCommands, slashMenuIndex, selectSlashCommand,
|
|
646
|
+
slashMenuVisible, filteredSlashCommands, slashMenuIndex, slashMenuOpen, selectSlashCommand, openSlashMenu,
|
|
629
647
|
sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
|
|
630
648
|
getRenderedContent, copyMessage, toggleTool,
|
|
631
649
|
isPrevAssistant: _isPrevAssistant,
|
|
@@ -2483,9 +2501,14 @@ const App = {
|
|
|
2483
2501
|
</div>
|
|
2484
2502
|
</div>
|
|
2485
2503
|
<div class="input-bottom-row">
|
|
2486
|
-
<
|
|
2487
|
-
<
|
|
2488
|
-
|
|
2504
|
+
<div class="input-bottom-left">
|
|
2505
|
+
<button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" :title="t('input.attachFiles')">
|
|
2506
|
+
<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
|
+
</button>
|
|
2508
|
+
<button class="slash-btn" @click="openSlashMenu" :disabled="status !== 'Connected' || isCompacting" :title="t('input.slashCommands')">
|
|
2509
|
+
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 21 11 3h2L9 21H7Z"/></svg>
|
|
2510
|
+
</button>
|
|
2511
|
+
</div>
|
|
2489
2512
|
<button v-if="isProcessing && !hasInput" @click="cancelExecution" class="send-btn stop-btn" :title="t('input.stopGeneration')">
|
|
2490
2513
|
<svg viewBox="0 0 24 24" width="14" height="14"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor"/></svg>
|
|
2491
2514
|
</button>
|
package/web/css/input.css
CHANGED
|
@@ -84,6 +84,12 @@
|
|
|
84
84
|
padding: 0.1rem 0.25rem 0;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
+
.input-bottom-left {
|
|
88
|
+
display: flex;
|
|
89
|
+
align-items: center;
|
|
90
|
+
gap: 2px;
|
|
91
|
+
}
|
|
92
|
+
|
|
87
93
|
.send-btn {
|
|
88
94
|
background: var(--accent);
|
|
89
95
|
color: #fff;
|
|
@@ -548,6 +554,31 @@
|
|
|
548
554
|
cursor: not-allowed;
|
|
549
555
|
}
|
|
550
556
|
|
|
557
|
+
/* ── Slash command button ── */
|
|
558
|
+
.slash-btn {
|
|
559
|
+
background: none;
|
|
560
|
+
border: none;
|
|
561
|
+
color: var(--text-secondary);
|
|
562
|
+
cursor: pointer;
|
|
563
|
+
display: flex;
|
|
564
|
+
align-items: center;
|
|
565
|
+
justify-content: center;
|
|
566
|
+
width: 32px;
|
|
567
|
+
height: 32px;
|
|
568
|
+
border-radius: 8px;
|
|
569
|
+
transition: color 0.15s, background 0.15s;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
.slash-btn:hover {
|
|
573
|
+
color: var(--text-primary);
|
|
574
|
+
background: var(--bg-tertiary);
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
.slash-btn:disabled {
|
|
578
|
+
opacity: 0.3;
|
|
579
|
+
cursor: not-allowed;
|
|
580
|
+
}
|
|
581
|
+
|
|
551
582
|
/* ── Drag-over highlight on input card ── */
|
|
552
583
|
.input-card.drag-over {
|
|
553
584
|
border-color: var(--accent);
|
package/web/locales/en.json
CHANGED
|
@@ -177,6 +177,7 @@
|
|
|
177
177
|
"input.stopGeneration": "Stop generation",
|
|
178
178
|
"input.send": "Send",
|
|
179
179
|
"input.remove": "Remove",
|
|
180
|
+
"input.slashCommands": "Slash commands",
|
|
180
181
|
|
|
181
182
|
"folderPicker.title": "Select Working Directory",
|
|
182
183
|
"folderPicker.pathPlaceholder": "Enter path...",
|
package/web/locales/zh.json
CHANGED
|
@@ -56,6 +56,7 @@ export function createConnection(deps) {
|
|
|
56
56
|
let reconnectAttempts = 0;
|
|
57
57
|
let reconnectTimer = null;
|
|
58
58
|
let pingTimer = null;
|
|
59
|
+
let idleCheckTimer = null;
|
|
59
60
|
const toolMsgMap = new Map(); // toolId -> message (for fast tool_result lookup)
|
|
60
61
|
|
|
61
62
|
// ── toolMsgMap save/restore for conversation switching ──
|
|
@@ -90,6 +91,22 @@ export function createConnection(deps) {
|
|
|
90
91
|
latency.value = null;
|
|
91
92
|
}
|
|
92
93
|
|
|
94
|
+
// Idle-check: if isProcessing stays true with no claude_output for 15s,
|
|
95
|
+
// poll the agent to reconcile stale state (guards against lost turn_completed).
|
|
96
|
+
const IDLE_CHECK_MS = 15000;
|
|
97
|
+
function resetIdleCheck() {
|
|
98
|
+
if (idleCheckTimer) { clearTimeout(idleCheckTimer); idleCheckTimer = null; }
|
|
99
|
+
if (isProcessing.value) {
|
|
100
|
+
idleCheckTimer = setTimeout(() => {
|
|
101
|
+
idleCheckTimer = null;
|
|
102
|
+
if (isProcessing.value) wsSend({ type: 'query_active_conversations' });
|
|
103
|
+
}, IDLE_CHECK_MS);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function clearIdleCheck() {
|
|
107
|
+
if (idleCheckTimer) { clearTimeout(idleCheckTimer); idleCheckTimer = null; }
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
function getSessionId() {
|
|
94
111
|
const match = window.location.pathname.match(/^\/s\/([^/]+)/);
|
|
95
112
|
return match ? match[1] : null;
|
|
@@ -118,6 +135,7 @@ export function createConnection(deps) {
|
|
|
118
135
|
// (e.g. after reconnect before active_conversations response), self-correct
|
|
119
136
|
if (!isProcessing.value) {
|
|
120
137
|
isProcessing.value = true;
|
|
138
|
+
resetIdleCheck();
|
|
121
139
|
if (currentConversationId && currentConversationId.value) {
|
|
122
140
|
processingConversations.value[currentConversationId.value] = true;
|
|
123
141
|
}
|
|
@@ -387,6 +405,7 @@ export function createConnection(deps) {
|
|
|
387
405
|
if (team && msg.activeTeam) {
|
|
388
406
|
team.handleActiveTeamRestore(msg.activeTeam);
|
|
389
407
|
}
|
|
408
|
+
resetIdleCheck();
|
|
390
409
|
} else if (msg.type === 'error') {
|
|
391
410
|
streaming.flushReveal();
|
|
392
411
|
finalizeStreamingMsg(scheduleHighlight);
|
|
@@ -399,6 +418,7 @@ export function createConnection(deps) {
|
|
|
399
418
|
isProcessing.value = false;
|
|
400
419
|
isCompacting.value = false;
|
|
401
420
|
loadingSessions.value = false;
|
|
421
|
+
clearIdleCheck();
|
|
402
422
|
if (currentConversationId && currentConversationId.value) {
|
|
403
423
|
processingConversations.value[currentConversationId.value] = false;
|
|
404
424
|
}
|
|
@@ -409,6 +429,7 @@ export function createConnection(deps) {
|
|
|
409
429
|
_dequeueNext();
|
|
410
430
|
} else if (msg.type === 'claude_output') {
|
|
411
431
|
handleClaudeOutput(msg, scheduleHighlight);
|
|
432
|
+
resetIdleCheck();
|
|
412
433
|
} else if (msg.type === 'session_started') {
|
|
413
434
|
// Claude session ID captured — update and refresh sidebar
|
|
414
435
|
currentClaudeSessionId.value = msg.claudeSessionId;
|
|
@@ -446,6 +467,7 @@ export function createConnection(deps) {
|
|
|
446
467
|
finalizeStreamingMsg(scheduleHighlight);
|
|
447
468
|
isProcessing.value = false;
|
|
448
469
|
isCompacting.value = false;
|
|
470
|
+
clearIdleCheck();
|
|
449
471
|
toolMsgMap.clear();
|
|
450
472
|
if (msg.usage) usageStats.value = msg.usage;
|
|
451
473
|
if (currentConversationId && currentConversationId.value) {
|
|
@@ -607,6 +629,7 @@ export function createConnection(deps) {
|
|
|
607
629
|
ws.onclose = () => {
|
|
608
630
|
sessionKey = null;
|
|
609
631
|
stopPing();
|
|
632
|
+
clearIdleCheck();
|
|
610
633
|
const wasConnected = status.value === 'Connected' || status.value === 'Connecting...';
|
|
611
634
|
isProcessing.value = false;
|
|
612
635
|
isCompacting.value = false;
|