@agent-link/server 0.1.89 → 0.1.91
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 +56 -16
- package/web/modules/connection.js +11 -2
- package/web/style.css +66 -0
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -47,6 +47,7 @@ const App = {
|
|
|
47
47
|
const inputText = ref('');
|
|
48
48
|
const isProcessing = ref(false);
|
|
49
49
|
const isCompacting = ref(false);
|
|
50
|
+
const queuedMessages = ref([]);
|
|
50
51
|
const inputRef = ref(null);
|
|
51
52
|
|
|
52
53
|
// Sidebar state
|
|
@@ -154,10 +155,10 @@ const App = {
|
|
|
154
155
|
hostname, workdirHistory,
|
|
155
156
|
});
|
|
156
157
|
|
|
157
|
-
const { connect, wsSend, closeWs, submitPassword } = createConnection({
|
|
158
|
+
const { connect, wsSend, closeWs, submitPassword, setDequeueNext } = createConnection({
|
|
158
159
|
status, agentName, hostname, workDir, sessionId, error,
|
|
159
160
|
serverVersion, agentVersion,
|
|
160
|
-
messages, isProcessing, isCompacting, visibleLimit,
|
|
161
|
+
messages, isProcessing, isCompacting, visibleLimit, queuedMessages,
|
|
161
162
|
historySessions, currentClaudeSessionId, loadingSessions, loadingHistory,
|
|
162
163
|
folderPickerLoading, folderPickerEntries, folderPickerPath,
|
|
163
164
|
authRequired, authPassword, authError, authAttempts, authLocked,
|
|
@@ -166,10 +167,12 @@ const App = {
|
|
|
166
167
|
|
|
167
168
|
// Now wire up the forwarding function
|
|
168
169
|
_wsSend = wsSend;
|
|
170
|
+
setDequeueNext(dequeueNext);
|
|
169
171
|
|
|
170
172
|
// ── Computed ──
|
|
173
|
+
const hasInput = computed(() => !!(inputText.value.trim() || attachments.value.length > 0));
|
|
171
174
|
const canSend = computed(() =>
|
|
172
|
-
status.value === 'Connected' &&
|
|
175
|
+
status.value === 'Connected' && hasInput.value && !isCompacting.value
|
|
173
176
|
&& !messages.value.some(m => m.role === 'ask-question' && !m.answered)
|
|
174
177
|
);
|
|
175
178
|
|
|
@@ -195,15 +198,6 @@ const App = {
|
|
|
195
198
|
name: f.name, size: f.size, isImage: f.isImage, thumbUrl: f.thumbUrl,
|
|
196
199
|
}));
|
|
197
200
|
|
|
198
|
-
messages.value.push({
|
|
199
|
-
id: streaming.nextId(), role: 'user',
|
|
200
|
-
content: text || (files.length > 0 ? `[${files.length} file${files.length > 1 ? 's' : ''} attached]` : ''),
|
|
201
|
-
attachments: msgAttachments.length > 0 ? msgAttachments : undefined,
|
|
202
|
-
timestamp: new Date(),
|
|
203
|
-
});
|
|
204
|
-
isProcessing.value = true;
|
|
205
|
-
scrollToBottom(true);
|
|
206
|
-
|
|
207
201
|
const payload = { type: 'chat', prompt: text || '(see attached files)' };
|
|
208
202
|
if (needsResume.value && currentClaudeSessionId.value) {
|
|
209
203
|
payload.resumeSessionId = currentClaudeSessionId.value;
|
|
@@ -214,7 +208,23 @@ const App = {
|
|
|
214
208
|
name: f.name, mimeType: f.mimeType, data: f.data,
|
|
215
209
|
}));
|
|
216
210
|
}
|
|
217
|
-
|
|
211
|
+
|
|
212
|
+
const userMsg = {
|
|
213
|
+
id: streaming.nextId(), role: 'user',
|
|
214
|
+
content: text || (files.length > 0 ? `[${files.length} file${files.length > 1 ? 's' : ''} attached]` : ''),
|
|
215
|
+
attachments: msgAttachments.length > 0 ? msgAttachments : undefined,
|
|
216
|
+
timestamp: new Date(),
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
if (isProcessing.value) {
|
|
220
|
+
queuedMessages.value.push({ id: streaming.nextId(), content: userMsg.content, attachments: userMsg.attachments, payload });
|
|
221
|
+
} else {
|
|
222
|
+
userMsg.status = 'sent';
|
|
223
|
+
messages.value.push(userMsg);
|
|
224
|
+
isProcessing.value = true;
|
|
225
|
+
wsSend(payload);
|
|
226
|
+
}
|
|
227
|
+
scrollToBottom(true);
|
|
218
228
|
attachments.value = [];
|
|
219
229
|
}
|
|
220
230
|
|
|
@@ -223,6 +233,25 @@ const App = {
|
|
|
223
233
|
wsSend({ type: 'cancel_execution' });
|
|
224
234
|
}
|
|
225
235
|
|
|
236
|
+
function dequeueNext() {
|
|
237
|
+
if (queuedMessages.value.length === 0) return;
|
|
238
|
+
const queued = queuedMessages.value.shift();
|
|
239
|
+
const userMsg = {
|
|
240
|
+
id: queued.id, role: 'user', status: 'sent',
|
|
241
|
+
content: queued.content, attachments: queued.attachments,
|
|
242
|
+
timestamp: new Date(),
|
|
243
|
+
};
|
|
244
|
+
messages.value.push(userMsg);
|
|
245
|
+
isProcessing.value = true;
|
|
246
|
+
wsSend(queued.payload);
|
|
247
|
+
scrollToBottom(true);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
function removeQueuedMessage(msgId) {
|
|
251
|
+
const idx = queuedMessages.value.findIndex(m => m.id === msgId);
|
|
252
|
+
if (idx !== -1) queuedMessages.value.splice(idx, 1);
|
|
253
|
+
}
|
|
254
|
+
|
|
226
255
|
function handleKeydown(e) {
|
|
227
256
|
if (e.key === 'Enter' && !e.shiftKey) {
|
|
228
257
|
e.preventDefault();
|
|
@@ -256,8 +285,8 @@ const App = {
|
|
|
256
285
|
status, agentName, hostname, workDir, sessionId, error,
|
|
257
286
|
serverVersion, agentVersion,
|
|
258
287
|
messages, visibleMessages, hasMoreMessages, loadMoreMessages,
|
|
259
|
-
inputText, isProcessing, isCompacting, canSend, inputRef,
|
|
260
|
-
sendMessage, handleKeydown, cancelExecution, onMessageListScroll,
|
|
288
|
+
inputText, isProcessing, isCompacting, canSend, hasInput, inputRef, queuedMessages,
|
|
289
|
+
sendMessage, handleKeydown, cancelExecution, removeQueuedMessage, onMessageListScroll,
|
|
261
290
|
getRenderedContent, copyMessage, toggleTool,
|
|
262
291
|
isPrevAssistant: _isPrevAssistant,
|
|
263
292
|
toggleContextSummary, formatTimestamp,
|
|
@@ -587,6 +616,17 @@ const App = {
|
|
|
587
616
|
@change="handleFileSelect"
|
|
588
617
|
accept="image/*,text/*,.pdf,.json,.md,.py,.js,.ts,.tsx,.jsx,.css,.html,.xml,.yaml,.yml,.toml,.sh,.sql,.csv"
|
|
589
618
|
/>
|
|
619
|
+
<div v-if="queuedMessages.length > 0" class="queue-bar">
|
|
620
|
+
<div v-for="(qm, qi) in queuedMessages" :key="qm.id" class="queue-item">
|
|
621
|
+
<span class="queue-item-num">{{ qi + 1 }}.</span>
|
|
622
|
+
<span class="queue-item-text">{{ qm.content }}</span>
|
|
623
|
+
<span v-if="qm.attachments && qm.attachments.length" class="queue-item-attach" :title="qm.attachments.map(a => a.name).join(', ')">
|
|
624
|
+
<svg viewBox="0 0 24 24" width="11" height="11"><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>
|
|
625
|
+
{{ qm.attachments.length }}
|
|
626
|
+
</span>
|
|
627
|
+
<button class="queue-item-remove" @click="removeQueuedMessage(qm.id)" title="Remove from queue">×</button>
|
|
628
|
+
</div>
|
|
629
|
+
</div>
|
|
590
630
|
<div
|
|
591
631
|
:class="['input-card', { 'drag-over': dragOver }]"
|
|
592
632
|
@dragover="handleDragOver"
|
|
@@ -620,7 +660,7 @@ const App = {
|
|
|
620
660
|
<button class="attach-btn" @click="triggerFileInput" :disabled="status !== 'Connected' || isCompacting || attachments.length >= 5" title="Attach files">
|
|
621
661
|
<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>
|
|
622
662
|
</button>
|
|
623
|
-
<button v-if="isProcessing" @click="cancelExecution" class="send-btn stop-btn" title="Stop generation">
|
|
663
|
+
<button v-if="isProcessing && !hasInput" @click="cancelExecution" class="send-btn stop-btn" title="Stop generation">
|
|
624
664
|
<svg viewBox="0 0 24 24" width="14" height="14"><rect x="6" y="6" width="12" height="12" rx="2" fill="currentColor"/></svg>
|
|
625
665
|
</button>
|
|
626
666
|
<button v-else @click="sendMessage" :disabled="!canSend" class="send-btn" title="Send">
|
|
@@ -14,7 +14,7 @@ export function createConnection(deps) {
|
|
|
14
14
|
const {
|
|
15
15
|
status, agentName, hostname, workDir, sessionId, error,
|
|
16
16
|
serverVersion, agentVersion,
|
|
17
|
-
messages, isProcessing, isCompacting, visibleLimit,
|
|
17
|
+
messages, isProcessing, isCompacting, visibleLimit, queuedMessages,
|
|
18
18
|
historySessions, currentClaudeSessionId, loadingSessions, loadingHistory,
|
|
19
19
|
folderPickerLoading, folderPickerEntries, folderPickerPath,
|
|
20
20
|
authRequired, authPassword, authError, authAttempts, authLocked,
|
|
@@ -22,6 +22,10 @@ export function createConnection(deps) {
|
|
|
22
22
|
scrollToBottom,
|
|
23
23
|
} = deps;
|
|
24
24
|
|
|
25
|
+
// Dequeue callback — set after creation to resolve circular dependency
|
|
26
|
+
let _dequeueNext = () => {};
|
|
27
|
+
function setDequeueNext(fn) { _dequeueNext = fn; }
|
|
28
|
+
|
|
25
29
|
let ws = null;
|
|
26
30
|
let sessionKey = null;
|
|
27
31
|
let reconnectAttempts = 0;
|
|
@@ -203,6 +207,7 @@ export function createConnection(deps) {
|
|
|
203
207
|
error.value = 'Agent disconnected. Waiting for reconnect...';
|
|
204
208
|
isProcessing.value = false;
|
|
205
209
|
isCompacting.value = false;
|
|
210
|
+
queuedMessages.value = [];
|
|
206
211
|
} else if (msg.type === 'agent_reconnected') {
|
|
207
212
|
status.value = 'Connected';
|
|
208
213
|
error.value = '';
|
|
@@ -226,6 +231,7 @@ export function createConnection(deps) {
|
|
|
226
231
|
scrollToBottom();
|
|
227
232
|
isProcessing.value = false;
|
|
228
233
|
isCompacting.value = false;
|
|
234
|
+
_dequeueNext();
|
|
229
235
|
} else if (msg.type === 'claude_output') {
|
|
230
236
|
handleClaudeOutput(msg, scheduleHighlight);
|
|
231
237
|
} else if (msg.type === 'command_output') {
|
|
@@ -269,6 +275,7 @@ export function createConnection(deps) {
|
|
|
269
275
|
});
|
|
270
276
|
scrollToBottom();
|
|
271
277
|
}
|
|
278
|
+
_dequeueNext();
|
|
272
279
|
} else if (msg.type === 'ask_user_question') {
|
|
273
280
|
streaming.flushReveal();
|
|
274
281
|
finalizeStreamingMsg(scheduleHighlight);
|
|
@@ -387,6 +394,7 @@ export function createConnection(deps) {
|
|
|
387
394
|
localStorage.setItem(`agentlink-workdir-${sessionId.value}`, msg.workDir);
|
|
388
395
|
sidebar.addToWorkdirHistory(msg.workDir);
|
|
389
396
|
messages.value = [];
|
|
397
|
+
queuedMessages.value = [];
|
|
390
398
|
toolMsgMap.clear();
|
|
391
399
|
visibleLimit.value = 50;
|
|
392
400
|
streaming.setMessageIdCounter(0);
|
|
@@ -408,6 +416,7 @@ export function createConnection(deps) {
|
|
|
408
416
|
const wasConnected = status.value === 'Connected' || status.value === 'Connecting...';
|
|
409
417
|
isProcessing.value = false;
|
|
410
418
|
isCompacting.value = false;
|
|
419
|
+
queuedMessages.value = [];
|
|
411
420
|
|
|
412
421
|
// Don't auto-reconnect if auth-locked or still in auth prompt
|
|
413
422
|
if (authLocked.value || authRequired.value) return;
|
|
@@ -446,5 +455,5 @@ export function createConnection(deps) {
|
|
|
446
455
|
ws.send(JSON.stringify({ type: 'authenticate', password: pwd }));
|
|
447
456
|
}
|
|
448
457
|
|
|
449
|
-
return { connect, wsSend, closeWs, submitPassword };
|
|
458
|
+
return { connect, wsSend, closeWs, submitPassword, setDequeueNext };
|
|
450
459
|
}
|
package/web/style.css
CHANGED
|
@@ -787,6 +787,72 @@ body {
|
|
|
787
787
|
color: var(--text-primary);
|
|
788
788
|
}
|
|
789
789
|
|
|
790
|
+
/* ── Queue bar (pending messages above input) ── */
|
|
791
|
+
.queue-bar {
|
|
792
|
+
max-width: 768px;
|
|
793
|
+
margin: 0 auto 6px;
|
|
794
|
+
display: flex;
|
|
795
|
+
flex-direction: column;
|
|
796
|
+
gap: 3px;
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
.queue-item {
|
|
800
|
+
display: flex;
|
|
801
|
+
align-items: center;
|
|
802
|
+
gap: 6px;
|
|
803
|
+
background: var(--bg-secondary);
|
|
804
|
+
border: 1px dashed var(--border);
|
|
805
|
+
border-radius: 8px;
|
|
806
|
+
padding: 4px 8px;
|
|
807
|
+
font-size: 0.8rem;
|
|
808
|
+
line-height: 1.3;
|
|
809
|
+
color: var(--text-secondary);
|
|
810
|
+
opacity: 0.85;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
.queue-item-num {
|
|
814
|
+
flex-shrink: 0;
|
|
815
|
+
font-weight: 600;
|
|
816
|
+
color: var(--text-secondary);
|
|
817
|
+
font-size: 0.7rem;
|
|
818
|
+
opacity: 0.6;
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
.queue-item-text {
|
|
822
|
+
flex: 1;
|
|
823
|
+
min-width: 0;
|
|
824
|
+
overflow: hidden;
|
|
825
|
+
text-overflow: ellipsis;
|
|
826
|
+
white-space: nowrap;
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
.queue-item-attach {
|
|
830
|
+
flex-shrink: 0;
|
|
831
|
+
display: flex;
|
|
832
|
+
align-items: center;
|
|
833
|
+
gap: 2px;
|
|
834
|
+
font-size: 0.7rem;
|
|
835
|
+
opacity: 0.6;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
.queue-item-remove {
|
|
839
|
+
flex-shrink: 0;
|
|
840
|
+
background: none;
|
|
841
|
+
border: none;
|
|
842
|
+
color: var(--text-secondary);
|
|
843
|
+
font-size: 1rem;
|
|
844
|
+
line-height: 1;
|
|
845
|
+
cursor: pointer;
|
|
846
|
+
padding: 0 2px;
|
|
847
|
+
opacity: 0.5;
|
|
848
|
+
transition: color 0.15s, opacity 0.15s;
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
.queue-item-remove:hover {
|
|
852
|
+
opacity: 1;
|
|
853
|
+
color: var(--error);
|
|
854
|
+
}
|
|
855
|
+
|
|
790
856
|
.assistant-bubble {
|
|
791
857
|
background: transparent;
|
|
792
858
|
padding: 0.2rem 0;
|