@agent-link/server 0.1.113 → 0.1.115
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 +101 -6
- package/web/iPad.webp +0 -0
- package/web/iPhone.webp +0 -0
- package/web/landing.html +993 -281
- package/web/modules/connection.js +313 -10
- package/web/modules/sidebar.js +88 -7
- package/web/modules/streaming.js +18 -1
- package/web/style.css +18 -0
package/package.json
CHANGED
package/web/app.js
CHANGED
|
@@ -89,6 +89,75 @@ const App = {
|
|
|
89
89
|
const fileInputRef = ref(null);
|
|
90
90
|
const dragOver = ref(false);
|
|
91
91
|
|
|
92
|
+
// Multi-session parallel state
|
|
93
|
+
const conversationCache = ref({}); // conversationId → saved state snapshot
|
|
94
|
+
const currentConversationId = ref(crypto.randomUUID()); // currently visible conversation
|
|
95
|
+
const processingConversations = ref({}); // conversationId → boolean
|
|
96
|
+
|
|
97
|
+
// ── switchConversation: save current → load target ──
|
|
98
|
+
// Defined here and used by sidebar.newConversation, sidebar.resumeSession, workdir_changed
|
|
99
|
+
// Needs access to streaming / connection which are created later, so we use late-binding refs.
|
|
100
|
+
let _getToolMsgMap = () => new Map();
|
|
101
|
+
let _restoreToolMsgMap = () => {};
|
|
102
|
+
let _clearToolMsgMap = () => {};
|
|
103
|
+
|
|
104
|
+
function switchConversation(newConvId) {
|
|
105
|
+
const oldConvId = currentConversationId.value;
|
|
106
|
+
|
|
107
|
+
// Save current state (if there is one)
|
|
108
|
+
if (oldConvId) {
|
|
109
|
+
const streamState = streaming.saveState();
|
|
110
|
+
conversationCache.value[oldConvId] = {
|
|
111
|
+
messages: messages.value,
|
|
112
|
+
isProcessing: isProcessing.value,
|
|
113
|
+
isCompacting: isCompacting.value,
|
|
114
|
+
loadingHistory: loadingHistory.value,
|
|
115
|
+
claudeSessionId: currentClaudeSessionId.value,
|
|
116
|
+
visibleLimit: visibleLimit.value,
|
|
117
|
+
needsResume: needsResume.value,
|
|
118
|
+
streamingState: streamState,
|
|
119
|
+
toolMsgMap: _getToolMsgMap(),
|
|
120
|
+
messageIdCounter: streaming.getMessageIdCounter(),
|
|
121
|
+
queuedMessages: queuedMessages.value,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Load target state
|
|
126
|
+
const cached = conversationCache.value[newConvId];
|
|
127
|
+
if (cached) {
|
|
128
|
+
messages.value = cached.messages;
|
|
129
|
+
isProcessing.value = cached.isProcessing;
|
|
130
|
+
isCompacting.value = cached.isCompacting;
|
|
131
|
+
loadingHistory.value = cached.loadingHistory || false;
|
|
132
|
+
currentClaudeSessionId.value = cached.claudeSessionId;
|
|
133
|
+
visibleLimit.value = cached.visibleLimit;
|
|
134
|
+
needsResume.value = cached.needsResume;
|
|
135
|
+
streaming.restoreState(cached.streamingState || { pendingText: '', streamingMessageId: null, messageIdCounter: cached.messageIdCounter || 0 });
|
|
136
|
+
// Background routing may have incremented messageIdCounter beyond what
|
|
137
|
+
// streamingState recorded at save time — use the authoritative value.
|
|
138
|
+
streaming.setMessageIdCounter(cached.messageIdCounter || 0);
|
|
139
|
+
_restoreToolMsgMap(cached.toolMsgMap || new Map());
|
|
140
|
+
queuedMessages.value = cached.queuedMessages || [];
|
|
141
|
+
} else {
|
|
142
|
+
// New blank conversation
|
|
143
|
+
messages.value = [];
|
|
144
|
+
isProcessing.value = false;
|
|
145
|
+
isCompacting.value = false;
|
|
146
|
+
loadingHistory.value = false;
|
|
147
|
+
currentClaudeSessionId.value = null;
|
|
148
|
+
visibleLimit.value = 50;
|
|
149
|
+
needsResume.value = false;
|
|
150
|
+
streaming.setMessageIdCounter(0);
|
|
151
|
+
streaming.setStreamingMessageId(null);
|
|
152
|
+
streaming.reset();
|
|
153
|
+
_clearToolMsgMap();
|
|
154
|
+
queuedMessages.value = [];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
currentConversationId.value = newConvId;
|
|
158
|
+
scrollToBottom(true);
|
|
159
|
+
}
|
|
160
|
+
|
|
92
161
|
// Theme
|
|
93
162
|
const theme = ref(localStorage.getItem('agentlink-theme') || 'light');
|
|
94
163
|
function applyTheme() {
|
|
@@ -159,9 +228,12 @@ const App = {
|
|
|
159
228
|
deleteConfirmOpen, deleteConfirmTitle,
|
|
160
229
|
renamingSessionId, renameText,
|
|
161
230
|
hostname, workdirHistory,
|
|
231
|
+
// Multi-session parallel
|
|
232
|
+
currentConversationId, conversationCache, processingConversations,
|
|
233
|
+
switchConversation,
|
|
162
234
|
});
|
|
163
235
|
|
|
164
|
-
const { connect, wsSend, closeWs, submitPassword, setDequeueNext } = createConnection({
|
|
236
|
+
const { connect, wsSend, closeWs, submitPassword, setDequeueNext, getToolMsgMap, restoreToolMsgMap, clearToolMsgMap } = createConnection({
|
|
165
237
|
status, agentName, hostname, workDir, sessionId, error,
|
|
166
238
|
serverVersion, agentVersion, latency,
|
|
167
239
|
messages, isProcessing, isCompacting, visibleLimit, queuedMessages,
|
|
@@ -169,11 +241,18 @@ const App = {
|
|
|
169
241
|
folderPickerLoading, folderPickerEntries, folderPickerPath,
|
|
170
242
|
authRequired, authPassword, authError, authAttempts, authLocked,
|
|
171
243
|
streaming, sidebar, scrollToBottom,
|
|
244
|
+
// Multi-session parallel
|
|
245
|
+
currentConversationId, processingConversations, conversationCache,
|
|
246
|
+
switchConversation,
|
|
172
247
|
});
|
|
173
248
|
|
|
174
249
|
// Now wire up the forwarding function
|
|
175
250
|
_wsSend = wsSend;
|
|
176
251
|
setDequeueNext(dequeueNext);
|
|
252
|
+
// Wire up late-binding toolMsgMap functions for switchConversation
|
|
253
|
+
_getToolMsgMap = getToolMsgMap;
|
|
254
|
+
_restoreToolMsgMap = restoreToolMsgMap;
|
|
255
|
+
_clearToolMsgMap = clearToolMsgMap;
|
|
177
256
|
|
|
178
257
|
// ── Computed ──
|
|
179
258
|
const hasInput = computed(() => !!(inputText.value.trim() || attachments.value.length > 0));
|
|
@@ -205,6 +284,9 @@ const App = {
|
|
|
205
284
|
}));
|
|
206
285
|
|
|
207
286
|
const payload = { type: 'chat', prompt: text || '(see attached files)' };
|
|
287
|
+
if (currentConversationId.value) {
|
|
288
|
+
payload.conversationId = currentConversationId.value;
|
|
289
|
+
}
|
|
208
290
|
if (needsResume.value && currentClaudeSessionId.value) {
|
|
209
291
|
payload.resumeSessionId = currentClaudeSessionId.value;
|
|
210
292
|
needsResume.value = false;
|
|
@@ -228,6 +310,9 @@ const App = {
|
|
|
228
310
|
userMsg.status = 'sent';
|
|
229
311
|
messages.value.push(userMsg);
|
|
230
312
|
isProcessing.value = true;
|
|
313
|
+
if (currentConversationId.value) {
|
|
314
|
+
processingConversations.value[currentConversationId.value] = true;
|
|
315
|
+
}
|
|
231
316
|
wsSend(payload);
|
|
232
317
|
}
|
|
233
318
|
scrollToBottom(true);
|
|
@@ -236,7 +321,11 @@ const App = {
|
|
|
236
321
|
|
|
237
322
|
function cancelExecution() {
|
|
238
323
|
if (!isProcessing.value) return;
|
|
239
|
-
|
|
324
|
+
const cancelPayload = { type: 'cancel_execution' };
|
|
325
|
+
if (currentConversationId.value) {
|
|
326
|
+
cancelPayload.conversationId = currentConversationId.value;
|
|
327
|
+
}
|
|
328
|
+
wsSend(cancelPayload);
|
|
240
329
|
}
|
|
241
330
|
|
|
242
331
|
function dequeueNext() {
|
|
@@ -249,6 +338,9 @@ const App = {
|
|
|
249
338
|
};
|
|
250
339
|
messages.value.push(userMsg);
|
|
251
340
|
isProcessing.value = true;
|
|
341
|
+
if (currentConversationId.value) {
|
|
342
|
+
processingConversations.value[currentConversationId.value] = true;
|
|
343
|
+
}
|
|
252
344
|
wsSend(queued.payload);
|
|
253
345
|
scrollToBottom(true);
|
|
254
346
|
}
|
|
@@ -311,6 +403,8 @@ const App = {
|
|
|
311
403
|
requestSessionList: sidebar.requestSessionList,
|
|
312
404
|
formatRelativeTime,
|
|
313
405
|
groupedSessions: sidebar.groupedSessions,
|
|
406
|
+
isSessionProcessing: sidebar.isSessionProcessing,
|
|
407
|
+
processingConversations,
|
|
314
408
|
// Folder picker
|
|
315
409
|
folderPickerOpen, folderPickerPath, folderPickerEntries,
|
|
316
410
|
folderPickerLoading, folderPickerSelected,
|
|
@@ -395,7 +489,7 @@ const App = {
|
|
|
395
489
|
</div>
|
|
396
490
|
<div class="sidebar-workdir-header">
|
|
397
491
|
<div class="sidebar-workdir-label">Working Directory</div>
|
|
398
|
-
<button class="sidebar-change-dir-btn" @click="openFolderPicker" title="Change working directory"
|
|
492
|
+
<button class="sidebar-change-dir-btn" @click="openFolderPicker" title="Change working directory">
|
|
399
493
|
<svg viewBox="0 0 24 24" width="12" height="12"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
|
|
400
494
|
</button>
|
|
401
495
|
</div>
|
|
@@ -427,7 +521,7 @@ const App = {
|
|
|
427
521
|
</button>
|
|
428
522
|
</div>
|
|
429
523
|
|
|
430
|
-
<button class="new-conversation-btn" @click="newConversation"
|
|
524
|
+
<button class="new-conversation-btn" @click="newConversation">
|
|
431
525
|
<svg viewBox="0 0 24 24" width="14" height="14"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
|
|
432
526
|
New conversation
|
|
433
527
|
</button>
|
|
@@ -443,9 +537,10 @@ const App = {
|
|
|
443
537
|
<div class="session-group-label">{{ group.label }}</div>
|
|
444
538
|
<div
|
|
445
539
|
v-for="s in group.sessions" :key="s.sessionId"
|
|
446
|
-
:class="['session-item', { active: currentClaudeSessionId === s.sessionId }]"
|
|
540
|
+
:class="['session-item', { active: currentClaudeSessionId === s.sessionId, processing: isSessionProcessing(s.sessionId) }]"
|
|
447
541
|
@click="renamingSessionId !== s.sessionId && resumeSession(s)"
|
|
448
542
|
:title="s.preview"
|
|
543
|
+
:aria-label="(s.title || s.sessionId.slice(0, 8)) + (isSessionProcessing(s.sessionId) ? ' (processing)' : '')"
|
|
449
544
|
>
|
|
450
545
|
<div v-if="renamingSessionId === s.sessionId" class="session-rename-row">
|
|
451
546
|
<input
|
|
@@ -462,7 +557,7 @@ const App = {
|
|
|
462
557
|
<div v-else class="session-title">{{ s.title }}</div>
|
|
463
558
|
<div class="session-meta">
|
|
464
559
|
<span>{{ formatRelativeTime(s.lastModified) }}</span>
|
|
465
|
-
<span v-if="renamingSessionId !== s.sessionId
|
|
560
|
+
<span v-if="renamingSessionId !== s.sessionId" class="session-actions">
|
|
466
561
|
<button
|
|
467
562
|
class="session-rename-btn"
|
|
468
563
|
@click.stop="startRename(s)"
|
package/web/iPad.webp
ADDED
|
Binary file
|
package/web/iPhone.webp
ADDED
|
Binary file
|