claudeck 1.4.0 → 1.4.2
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/README.md +6 -8
- package/package.json +1 -1
- package/plugins/claude-editor/manifest.json +10 -0
- package/plugins/repos/manifest.json +10 -0
- package/public/css/core/theme.css +6 -21
- package/public/css/core/variables.css +2 -0
- package/public/css/features/message-queue.css +348 -0
- package/public/css/ui/commands.css +4 -4
- package/public/css/ui/messages.css +310 -78
- package/public/css/ui/right-panel.css +207 -0
- package/public/css/ui/sessions.css +173 -0
- package/public/css/ui/settings.css +75 -0
- package/public/index.html +10 -2
- package/public/js/components/add-project-modal.js +14 -0
- package/public/js/components/jump-to-latest.js +42 -0
- package/public/js/components/queue-stop-modal.js +23 -0
- package/public/js/components/settings-modal.js +65 -0
- package/public/js/core/api.js +15 -43
- package/public/js/core/dom.js +17 -0
- package/public/js/core/events.js +11 -0
- package/public/js/core/plugin-loader.js +96 -11
- package/public/js/core/store.js +11 -0
- package/public/js/core/utils.js +38 -2
- package/public/js/features/chat.js +49 -1
- package/public/js/features/message-queue.js +423 -0
- package/public/js/features/projects.js +185 -3
- package/public/js/main.js +4 -1
- package/public/js/panels/assistant-bot.js +16 -0
- package/public/js/panels/dev-docs.js +2 -2
- package/public/js/panels/memory.js +1 -0
- package/public/js/ui/context-gauge.js +10 -1
- package/public/js/ui/formatting.js +65 -11
- package/public/js/ui/header-dropdowns.js +30 -0
- package/public/js/ui/input-meta.js +13 -6
- package/public/js/ui/max-turns.js +6 -3
- package/public/js/ui/messages.js +97 -1
- package/public/js/ui/model-selector.js +1 -0
- package/public/js/ui/parallel.js +32 -2
- package/public/js/ui/permissions.js +1 -0
- package/public/js/ui/right-panel.js +0 -8
- package/public/js/ui/tab-sdk.js +395 -176
- package/public/style.css +2 -0
- package/server/memory-optimizer.js +17 -13
- package/server/routes/marketplace.js +316 -0
- package/server/routes/projects.js +0 -0
- package/server/ws-handler.js +22 -15
- package/server.js +18 -0
- package/plugins/event-stream/client.css +0 -207
- package/plugins/event-stream/client.js +0 -271
- package/plugins/linear/client.css +0 -345
- package/plugins/linear/client.js +0 -380
- package/plugins/linear/config.json +0 -5
- package/plugins/linear/server.js +0 -312
- package/plugins/sudoku/client.css +0 -196
- package/plugins/sudoku/client.js +0 -329
- package/plugins/tasks/client.css +0 -414
- package/plugins/tasks/client.js +0 -394
- package/plugins/tasks/server.js +0 -116
- package/plugins/tic-tac-toe/client.css +0 -167
- package/plugins/tic-tac-toe/client.js +0 -241
- package/public/js/components/linear-create-modal.js +0 -43
package/public/js/core/api.js
CHANGED
|
@@ -310,6 +310,21 @@ export async function addProject(name, path) {
|
|
|
310
310
|
return res.json();
|
|
311
311
|
}
|
|
312
312
|
|
|
313
|
+
export async function createFolder(parent, name) {
|
|
314
|
+
const res = await fetch("/api/projects/create-folder", {
|
|
315
|
+
method: "POST",
|
|
316
|
+
headers: { "Content-Type": "application/json" },
|
|
317
|
+
body: JSON.stringify({ parent, name }),
|
|
318
|
+
});
|
|
319
|
+
const body = await res.json().catch(() => ({}));
|
|
320
|
+
if (!res.ok) {
|
|
321
|
+
const err = new Error(body.error || "Failed to create folder");
|
|
322
|
+
err.status = res.status;
|
|
323
|
+
throw err;
|
|
324
|
+
}
|
|
325
|
+
return body;
|
|
326
|
+
}
|
|
327
|
+
|
|
313
328
|
export async function deleteProject(path) {
|
|
314
329
|
const res = await fetch("/api/projects", {
|
|
315
330
|
method: "DELETE",
|
|
@@ -474,49 +489,6 @@ export async function execCommand(command, cwd) {
|
|
|
474
489
|
return res.json();
|
|
475
490
|
}
|
|
476
491
|
|
|
477
|
-
export async function fetchLinearIssues() {
|
|
478
|
-
const res = await fetch("/api/plugins/linear/issues");
|
|
479
|
-
return res.json();
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export async function fetchLinearTeams() {
|
|
483
|
-
const res = await fetch("/api/plugins/linear/teams");
|
|
484
|
-
return res.json();
|
|
485
|
-
}
|
|
486
|
-
|
|
487
|
-
export async function fetchLinearTeamStates(teamId) {
|
|
488
|
-
const res = await fetch(`/api/plugins/linear/teams/${encodeURIComponent(teamId)}/states`);
|
|
489
|
-
return res.json();
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
export async function createLinearIssue({ title, description, teamId, stateId }) {
|
|
493
|
-
const res = await fetch("/api/plugins/linear/issues", {
|
|
494
|
-
method: "POST",
|
|
495
|
-
headers: { "Content-Type": "application/json" },
|
|
496
|
-
body: JSON.stringify({ title, description, teamId, stateId }),
|
|
497
|
-
});
|
|
498
|
-
if (!res.ok) throw new Error("Failed to create issue");
|
|
499
|
-
return res.json();
|
|
500
|
-
}
|
|
501
|
-
|
|
502
|
-
export async function fetchLinearConfig() {
|
|
503
|
-
const res = await fetch("/api/plugins/linear/config");
|
|
504
|
-
return res.json();
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
export async function saveLinearConfig(config) {
|
|
508
|
-
const res = await fetch("/api/plugins/linear/config", {
|
|
509
|
-
method: "PUT",
|
|
510
|
-
headers: { "Content-Type": "application/json" },
|
|
511
|
-
body: JSON.stringify(config),
|
|
512
|
-
});
|
|
513
|
-
return res.json();
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
export async function testLinearConnection() {
|
|
517
|
-
const res = await fetch("/api/plugins/linear/test", { method: "POST" });
|
|
518
|
-
return res.json();
|
|
519
|
-
}
|
|
520
492
|
|
|
521
493
|
// Tips
|
|
522
494
|
export async function fetchTips() {
|
package/public/js/core/dom.js
CHANGED
|
@@ -165,6 +165,13 @@ export const $ = {
|
|
|
165
165
|
bgSessionIndicator: document.getElementById("bg-session-indicator"),
|
|
166
166
|
bgSessionBadge: document.getElementById("bg-session-badge"),
|
|
167
167
|
|
|
168
|
+
// Message queue
|
|
169
|
+
queueStopModal: document.getElementById("queue-stop-modal"),
|
|
170
|
+
queueStopAll: document.getElementById("queue-stop-all"),
|
|
171
|
+
queueStopSkip: document.getElementById("queue-stop-skip"),
|
|
172
|
+
queueStopPause: document.getElementById("queue-stop-pause"),
|
|
173
|
+
queueStopPreview: document.getElementById("queue-stop-preview"),
|
|
174
|
+
|
|
168
175
|
// Telegram
|
|
169
176
|
telegramBtn: document.getElementById("telegram-settings-btn"),
|
|
170
177
|
telegramModal: document.getElementById("telegram-modal"),
|
|
@@ -293,5 +300,15 @@ export const $ = {
|
|
|
293
300
|
addProjectConfirm: document.getElementById("add-project-confirm"),
|
|
294
301
|
folderBreadcrumb: document.getElementById("folder-breadcrumb"),
|
|
295
302
|
folderList: document.getElementById("folder-list"),
|
|
303
|
+
folderPathInput: document.getElementById("folder-path-input"),
|
|
304
|
+
folderPathGo: document.getElementById("folder-path-go"),
|
|
305
|
+
folderRecents: document.getElementById("folder-recents"),
|
|
306
|
+
folderNewToggle: document.getElementById("folder-new-toggle"),
|
|
307
|
+
folderNewToggleRow: document.getElementById("folder-new-toggle-row"),
|
|
308
|
+
folderNewRow: document.getElementById("folder-new-row"),
|
|
309
|
+
folderNewName: document.getElementById("folder-new-name"),
|
|
310
|
+
folderNewCreate: document.getElementById("folder-new-create"),
|
|
311
|
+
folderNewCancel: document.getElementById("folder-new-cancel"),
|
|
312
|
+
addProjectMessage: document.getElementById("add-project-message"),
|
|
296
313
|
|
|
297
314
|
};
|
package/public/js/core/events.js
CHANGED
|
@@ -5,6 +5,17 @@ export function emit(event, data) {
|
|
|
5
5
|
(bus[event] || []).forEach((fn) => fn(data));
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
/** Subscribe to an event. Returns an unsubscribe function. */
|
|
8
9
|
export function on(event, fn) {
|
|
9
10
|
(bus[event] ||= []).push(fn);
|
|
11
|
+
return () => {
|
|
12
|
+
const arr = bus[event];
|
|
13
|
+
if (arr) bus[event] = arr.filter(f => f !== fn);
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** Remove a specific listener for an event. */
|
|
18
|
+
export function off(event, fn) {
|
|
19
|
+
const arr = bus[event];
|
|
20
|
+
if (arr) bus[event] = arr.filter(f => f !== fn);
|
|
10
21
|
}
|
|
@@ -12,21 +12,13 @@
|
|
|
12
12
|
const STORAGE_KEY = 'claudeck-enabled-plugins';
|
|
13
13
|
const ORDER_KEY = 'claudeck-plugin-order';
|
|
14
14
|
let availablePlugins = [];
|
|
15
|
+
let marketplaceRegistry = null;
|
|
15
16
|
const loadedPlugins = new Set();
|
|
16
17
|
|
|
17
18
|
/** Maps plugin file name → tab ID registered by that plugin */
|
|
18
19
|
const pluginTabIds = new Map();
|
|
19
20
|
|
|
20
|
-
/**
|
|
21
|
-
const pluginMeta = {
|
|
22
|
-
'claude-editor': { description: 'Edit CLAUDE.md project instructions directly in the UI', icon: '📝', order: 5 },
|
|
23
|
-
'event-stream': { description: 'Real-time WebSocket event viewer with filtering and search', icon: '⚡', order: 10 },
|
|
24
|
-
'repos': { description: 'Git repository and group management with tree view', icon: '📁', order: 20 },
|
|
25
|
-
'linear': { description: 'Linear issue tracking with settings and team management', icon: '📋', order: 25 },
|
|
26
|
-
'tasks': { description: 'Todo list with priority levels and brag tracking', icon: '✅', order: 30 },
|
|
27
|
-
'tic-tac-toe': { description: 'Classic tic-tac-toe game', icon: '🎮', order: 90 },
|
|
28
|
-
'sudoku': { description: 'Sudoku puzzle game', icon: '🧩', order: 91 },
|
|
29
|
-
};
|
|
21
|
+
/** Fallback meta for plugins without manifest.json */
|
|
30
22
|
const defaultMeta = { description: 'A tab-sdk plugin', icon: '🧩', order: 100 };
|
|
31
23
|
|
|
32
24
|
export function getAvailablePlugins() {
|
|
@@ -45,7 +37,15 @@ export function setEnabledPluginNames(names) {
|
|
|
45
37
|
}
|
|
46
38
|
|
|
47
39
|
export function getPluginMeta(name) {
|
|
48
|
-
|
|
40
|
+
const plugin = availablePlugins.find(p => p.name === name);
|
|
41
|
+
if (plugin?.manifest) {
|
|
42
|
+
return {
|
|
43
|
+
description: plugin.manifest.description || defaultMeta.description,
|
|
44
|
+
icon: plugin.manifest.icon || defaultMeta.icon,
|
|
45
|
+
order: defaultMeta.order,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return defaultMeta;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
export function getPluginOrder() {
|
|
@@ -151,3 +151,88 @@ export async function loadPlugins() {
|
|
|
151
151
|
console.error('Plugin loader error:', err);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
|
|
155
|
+
// ── Marketplace ─────────────────────────────────────────
|
|
156
|
+
|
|
157
|
+
export function getMarketplaceRegistry() {
|
|
158
|
+
return marketplaceRegistry;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Fetch the community plugin registry from the server (which proxies GitHub) */
|
|
162
|
+
export async function fetchMarketplace(refresh = false) {
|
|
163
|
+
try {
|
|
164
|
+
const url = refresh ? '/api/marketplace?refresh=true' : '/api/marketplace';
|
|
165
|
+
const res = await fetch(url);
|
|
166
|
+
if (!res.ok) { console.warn('Marketplace fetch failed:', res.status); return null; }
|
|
167
|
+
marketplaceRegistry = await res.json();
|
|
168
|
+
return marketplaceRegistry;
|
|
169
|
+
} catch (err) {
|
|
170
|
+
console.error('Marketplace error:', err);
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Install a community plugin and auto-enable it */
|
|
176
|
+
export async function installMarketplacePlugin(plugin) {
|
|
177
|
+
const res = await fetch('/api/marketplace/install', {
|
|
178
|
+
method: 'POST',
|
|
179
|
+
headers: { 'Content-Type': 'application/json' },
|
|
180
|
+
body: JSON.stringify({ id: plugin.id, repo: plugin.repo, source: plugin.source }),
|
|
181
|
+
});
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
const err = await res.json().catch(() => ({ error: 'Install failed' }));
|
|
184
|
+
throw new Error(err.error);
|
|
185
|
+
}
|
|
186
|
+
const result = await res.json();
|
|
187
|
+
|
|
188
|
+
// Refresh local plugins list to include the newly installed plugin
|
|
189
|
+
const pluginsRes = await fetch('/api/plugins');
|
|
190
|
+
if (pluginsRes.ok) {
|
|
191
|
+
availablePlugins = await pluginsRes.json();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Auto-enable the newly installed plugin
|
|
195
|
+
const enabled = getEnabledPluginNames();
|
|
196
|
+
if (!enabled.includes(plugin.id)) {
|
|
197
|
+
enabled.push(plugin.id);
|
|
198
|
+
setEnabledPluginNames(enabled);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Load the plugin immediately
|
|
202
|
+
await loadPluginByName(plugin.id);
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** Uninstall a community plugin */
|
|
208
|
+
export async function uninstallMarketplacePlugin(id) {
|
|
209
|
+
const res = await fetch('/api/marketplace/uninstall', {
|
|
210
|
+
method: 'POST',
|
|
211
|
+
headers: { 'Content-Type': 'application/json' },
|
|
212
|
+
body: JSON.stringify({ id }),
|
|
213
|
+
});
|
|
214
|
+
if (!res.ok) {
|
|
215
|
+
const err = await res.json().catch(() => ({ error: 'Uninstall failed' }));
|
|
216
|
+
throw new Error(err.error);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Remove from enabled list
|
|
220
|
+
const enabled = getEnabledPluginNames().filter(n => n !== id);
|
|
221
|
+
setEnabledPluginNames(enabled);
|
|
222
|
+
|
|
223
|
+
// Clean up CSS link from DOM
|
|
224
|
+
const cssLink = document.head.querySelector(`link[data-plugin="${id}"]`);
|
|
225
|
+
if (cssLink) cssLink.remove();
|
|
226
|
+
|
|
227
|
+
// Clear loaded/tab tracking state
|
|
228
|
+
loadedPlugins.delete(id);
|
|
229
|
+
pluginTabIds.delete(id);
|
|
230
|
+
|
|
231
|
+
// Refresh local plugins list
|
|
232
|
+
const pluginsRes = await fetch('/api/plugins');
|
|
233
|
+
if (pluginsRes.ok) {
|
|
234
|
+
availablePlugins = await pluginsRes.json();
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return await res.json();
|
|
238
|
+
}
|
package/public/js/core/store.js
CHANGED
|
@@ -30,8 +30,19 @@ export function setState(key, val) {
|
|
|
30
30
|
emit(key, val);
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
/** Subscribe to state changes for a key. Returns an unsubscribe function. */
|
|
33
34
|
export function on(key, fn) {
|
|
34
35
|
(listeners[key] ||= []).push(fn);
|
|
36
|
+
return () => {
|
|
37
|
+
const arr = listeners[key];
|
|
38
|
+
if (arr) listeners[key] = arr.filter(f => f !== fn);
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Remove a specific listener for a key. */
|
|
43
|
+
export function off(key, fn) {
|
|
44
|
+
const arr = listeners[key];
|
|
45
|
+
if (arr) listeners[key] = arr.filter(f => f !== fn);
|
|
35
46
|
}
|
|
36
47
|
|
|
37
48
|
function emit(key, val) {
|
package/public/js/core/utils.js
CHANGED
|
@@ -20,6 +20,42 @@ export function getToolDetail(name, input) {
|
|
|
20
20
|
return "";
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
// Distance from bottom (px) within which we still consider the user "at bottom".
|
|
24
|
+
// Absorbs sub-pixel rounding, momentum scrolling, and small layout shifts.
|
|
25
|
+
export const NEAR_BOTTOM_THRESHOLD = 100;
|
|
26
|
+
|
|
27
|
+
export function isNearBottom(el) {
|
|
28
|
+
if (!el) return true;
|
|
29
|
+
return el.scrollHeight - el.scrollTop - el.clientHeight < NEAR_BOTTOM_THRESHOLD;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Stick-to-bottom: only scrolls if the user is already near the bottom, or
|
|
33
|
+
// if `force` is set (e.g. user sent a message). When new content arrives
|
|
34
|
+
// while the user has scrolled up, we set `pane.hasNewBelow = true` so the
|
|
35
|
+
// "Jump to latest" pill can surface, and we leave their scroll position alone.
|
|
36
|
+
export function scrollToBottom(pane, opts = {}) {
|
|
37
|
+
if (!pane || !pane.messagesDiv) return;
|
|
38
|
+
const el = pane.messagesDiv;
|
|
39
|
+
if (opts.force) {
|
|
40
|
+
el.scrollTop = el.scrollHeight;
|
|
41
|
+
pane.followBottom = true;
|
|
42
|
+
pane.hasNewBelow = false;
|
|
43
|
+
notifyPillUpdate(pane);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (pane.followBottom !== false && isNearBottom(el)) {
|
|
47
|
+
el.scrollTop = el.scrollHeight;
|
|
48
|
+
pane.followBottom = true;
|
|
49
|
+
pane.hasNewBelow = false;
|
|
50
|
+
} else {
|
|
51
|
+
pane.hasNewBelow = true;
|
|
52
|
+
}
|
|
53
|
+
notifyPillUpdate(pane);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function notifyPillUpdate(pane) {
|
|
57
|
+
if (typeof window === "undefined") return;
|
|
58
|
+
window.dispatchEvent(new CustomEvent("claudeck:scroll-state", {
|
|
59
|
+
detail: { chatId: pane.chatId, hasNewBelow: !!pane.hasNewBelow },
|
|
60
|
+
}));
|
|
25
61
|
}
|
|
@@ -4,7 +4,8 @@ import { getState, setState } from '../core/store.js';
|
|
|
4
4
|
import { CHAT_IDS, BOT_CHAT_ID } from '../core/constants.js';
|
|
5
5
|
import { on } from '../core/events.js';
|
|
6
6
|
import { commandRegistry, dismissAutocomplete, handleAutocompleteKeydown, handleSlashAutocomplete, registerCommand } from '../ui/commands.js';
|
|
7
|
-
import { addUserMessage, appendAssistantText, appendToolIndicator, appendToolResult, showThinking, removeThinking, addResultSummary, addStatus, showWhalyPlaceholder, addSkillUsedMessage } from '../ui/messages.js';
|
|
7
|
+
import { addUserMessage, appendAssistantText, appendToolIndicator, appendToolResult, showThinking, removeThinking, addResultSummary, addStatus, showWhalyPlaceholder, addSkillUsedMessage, exitWelcomeState, isWelcomeStateActive } from '../ui/messages.js';
|
|
8
|
+
import { enqueueMessage, pauseQueue, resumeQueue, fireNextQueued, handleStopWithQueue } from './message-queue.js';
|
|
8
9
|
import { getPane, panes, _setChatFns, _setInputHistoryGetter } from '../ui/parallel.js';
|
|
9
10
|
import { loadSessions } from './sessions.js';
|
|
10
11
|
import { loadStats, loadAccountInfo } from './cost-dashboard.js';
|
|
@@ -100,6 +101,15 @@ export function sendMessage(pane) {
|
|
|
100
101
|
const text = pane.messageInput.value.trim();
|
|
101
102
|
const cwd = $.projectSelect.value;
|
|
102
103
|
|
|
104
|
+
// Queue message if currently streaming (don't queue slash commands)
|
|
105
|
+
if (pane.isStreaming && text && !text.startsWith('/')) {
|
|
106
|
+
enqueueMessage(text, pane);
|
|
107
|
+
pane.messageInput.value = "";
|
|
108
|
+
pane.messageInput.style.height = "auto";
|
|
109
|
+
dismissAutocomplete(pane);
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
103
113
|
if (!text || !cwd) {
|
|
104
114
|
if (text && text.startsWith("/")) {
|
|
105
115
|
const match = text.match(/^\/(\S+)\s*(.*)/s);
|
|
@@ -155,6 +165,24 @@ export function sendMessage(pane) {
|
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
|
|
168
|
+
// Resume queue if responding to a question
|
|
169
|
+
if (pane._queuePaused && pane._queuePauseReason === 'question') {
|
|
170
|
+
resumeQueue(pane);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Animate out of welcome state if active
|
|
174
|
+
if (isWelcomeStateActive()) {
|
|
175
|
+
exitWelcomeState().then(() => {
|
|
176
|
+
_doSend(text, pane);
|
|
177
|
+
});
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
_doSend(text, pane);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
export function _doSend(text, pane) {
|
|
184
|
+
const cwd = $.projectSelect.value;
|
|
185
|
+
const ws = getState("ws");
|
|
158
186
|
// Prepend attached files
|
|
159
187
|
let fullMessage = text;
|
|
160
188
|
const attachedFiles = getState("attachedFiles");
|
|
@@ -235,6 +263,11 @@ export function sendMessage(pane) {
|
|
|
235
263
|
|
|
236
264
|
export function stopGeneration(pane) {
|
|
237
265
|
pane = pane || getPane(null);
|
|
266
|
+
// Show 3-option dialog if queue has items
|
|
267
|
+
if (pane._messageQueue?.length > 0) {
|
|
268
|
+
handleStopWithQueue(pane);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
238
271
|
const ws = getState("ws");
|
|
239
272
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
240
273
|
const payload = { type: "abort" };
|
|
@@ -276,6 +309,13 @@ export function finishStreamingHandler(pane) {
|
|
|
276
309
|
if (sid) {
|
|
277
310
|
import('./sessions.js').then(({ loadMessages }) => loadMessages(sid));
|
|
278
311
|
}
|
|
312
|
+
|
|
313
|
+
// Auto-fire next queued message (deferred to let state settle)
|
|
314
|
+
queueMicrotask(() => {
|
|
315
|
+
if (pane._messageQueue?.length > 0 && !pane._queuePaused) {
|
|
316
|
+
fireNextQueued(pane);
|
|
317
|
+
}
|
|
318
|
+
});
|
|
279
319
|
}
|
|
280
320
|
|
|
281
321
|
// Register the chat functions with parallel.js to break circular dependency
|
|
@@ -443,6 +483,10 @@ function handleServerMessage(msg) {
|
|
|
443
483
|
finishStreamingHandler(pane);
|
|
444
484
|
if (isQuestionText(rawText)) {
|
|
445
485
|
showWaitingForInput(pane);
|
|
486
|
+
// Pause queue when Claude asks a question
|
|
487
|
+
if (pane._messageQueue?.length > 0) {
|
|
488
|
+
pauseQueue(pane, 'question');
|
|
489
|
+
}
|
|
446
490
|
}
|
|
447
491
|
break;
|
|
448
492
|
}
|
|
@@ -455,6 +499,10 @@ function handleServerMessage(msg) {
|
|
|
455
499
|
case "error":
|
|
456
500
|
finishStreamingHandler(pane);
|
|
457
501
|
addStatus("Error: " + msg.error, true, pane);
|
|
502
|
+
// Pause queue on error
|
|
503
|
+
if (pane._messageQueue?.length > 0) {
|
|
504
|
+
pauseQueue(pane, 'error');
|
|
505
|
+
}
|
|
458
506
|
break;
|
|
459
507
|
|
|
460
508
|
case "workflow_started":
|