labgate 0.5.32 → 0.5.33
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 +2 -2
- package/dist/lib/config.js +1 -4
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/container.js +27 -4
- package/dist/lib/container.js.map +1 -1
- package/dist/lib/ui.html +303 -43
- package/dist/lib/ui.js +13 -9
- package/dist/lib/ui.js.map +1 -1
- package/dist/lib/web-terminal.js +4 -3
- package/dist/lib/web-terminal.js.map +1 -1
- package/dist/mcp-bundles/dataset-mcp.bundle.mjs +0 -8
- package/dist/mcp-bundles/explorer-mcp.bundle.mjs +0 -8
- package/package.json +1 -1
package/dist/lib/ui.html
CHANGED
|
@@ -5213,6 +5213,7 @@
|
|
|
5213
5213
|
border: 1px solid var(--border-color);
|
|
5214
5214
|
background: var(--card-bg);
|
|
5215
5215
|
overflow: hidden;
|
|
5216
|
+
flex-shrink: 0;
|
|
5216
5217
|
}
|
|
5217
5218
|
.terminal-chat-msg.user {
|
|
5218
5219
|
margin-left: auto;
|
|
@@ -5275,15 +5276,18 @@
|
|
|
5275
5276
|
.terminal-chat-msg.pending .terminal-chat-msg-header::after {
|
|
5276
5277
|
content: '';
|
|
5277
5278
|
display: inline-block;
|
|
5278
|
-
width:
|
|
5279
|
-
height:
|
|
5279
|
+
width: 6px;
|
|
5280
|
+
height: 6px;
|
|
5280
5281
|
margin-left: 6px;
|
|
5281
5282
|
border-radius: 50%;
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
animation: terminalHistorySpin 0.75s linear infinite;
|
|
5283
|
+
background: var(--accent);
|
|
5284
|
+
animation: chatPulse 1.4s ease-in-out infinite;
|
|
5285
5285
|
vertical-align: middle;
|
|
5286
5286
|
}
|
|
5287
|
+
@keyframes chatPulse {
|
|
5288
|
+
0%, 100% { opacity: 0.25; transform: scale(0.85); }
|
|
5289
|
+
50% { opacity: 1; transform: scale(1); }
|
|
5290
|
+
}
|
|
5287
5291
|
|
|
5288
5292
|
/* ── Tool use cards (inline in chat transcript) ── */
|
|
5289
5293
|
.terminal-chat-tool {
|
|
@@ -5300,6 +5304,7 @@
|
|
|
5300
5304
|
color: var(--text-secondary);
|
|
5301
5305
|
transition: opacity 0.2s;
|
|
5302
5306
|
border-left: 2px solid var(--border-color);
|
|
5307
|
+
flex-shrink: 0;
|
|
5303
5308
|
}
|
|
5304
5309
|
.terminal-chat-tool + .terminal-chat-tool {
|
|
5305
5310
|
margin-top: -6px;
|
|
@@ -5330,12 +5335,11 @@
|
|
|
5330
5335
|
}
|
|
5331
5336
|
.terminal-chat-tool.pending .tool-icon {
|
|
5332
5337
|
display: inline-block;
|
|
5333
|
-
width:
|
|
5334
|
-
height:
|
|
5338
|
+
width: 6px;
|
|
5339
|
+
height: 6px;
|
|
5335
5340
|
border-radius: 50%;
|
|
5336
|
-
|
|
5337
|
-
|
|
5338
|
-
animation: terminalHistorySpin 0.75s linear infinite;
|
|
5341
|
+
background: var(--accent);
|
|
5342
|
+
animation: chatPulse 1.4s ease-in-out infinite;
|
|
5339
5343
|
font-size: 0;
|
|
5340
5344
|
line-height: 0;
|
|
5341
5345
|
}
|
|
@@ -5352,6 +5356,49 @@
|
|
|
5352
5356
|
color: #dc2626;
|
|
5353
5357
|
}
|
|
5354
5358
|
|
|
5359
|
+
/* ── Collapsible tool groups ── */
|
|
5360
|
+
.terminal-chat-tool-group {
|
|
5361
|
+
display: flex;
|
|
5362
|
+
flex-direction: column;
|
|
5363
|
+
gap: 0;
|
|
5364
|
+
flex-shrink: 0;
|
|
5365
|
+
}
|
|
5366
|
+
.terminal-chat-tool-group .terminal-chat-tool + .terminal-chat-tool {
|
|
5367
|
+
margin-top: -6px;
|
|
5368
|
+
}
|
|
5369
|
+
.terminal-chat-tool-group .tool-group-collapsed {
|
|
5370
|
+
display: none;
|
|
5371
|
+
}
|
|
5372
|
+
.terminal-chat-tool-group.expanded .tool-group-collapsed {
|
|
5373
|
+
display: flex;
|
|
5374
|
+
}
|
|
5375
|
+
.tool-group-toggle {
|
|
5376
|
+
display: flex;
|
|
5377
|
+
align-items: center;
|
|
5378
|
+
gap: 6px;
|
|
5379
|
+
padding: 2px 10px;
|
|
5380
|
+
margin-bottom: 2px;
|
|
5381
|
+
border: none;
|
|
5382
|
+
background: transparent;
|
|
5383
|
+
cursor: pointer;
|
|
5384
|
+
font-family: 'GeistMono', monospace;
|
|
5385
|
+
font-size: 0.625rem;
|
|
5386
|
+
color: var(--text-muted);
|
|
5387
|
+
border-left: 2px solid var(--border-color);
|
|
5388
|
+
transition: color 0.15s;
|
|
5389
|
+
}
|
|
5390
|
+
.tool-group-toggle:hover {
|
|
5391
|
+
color: var(--text-secondary);
|
|
5392
|
+
}
|
|
5393
|
+
.tool-group-toggle .toggle-caret {
|
|
5394
|
+
display: inline-block;
|
|
5395
|
+
transition: transform 0.15s;
|
|
5396
|
+
font-size: 0.5rem;
|
|
5397
|
+
}
|
|
5398
|
+
.terminal-chat-tool-group.expanded .tool-group-toggle .toggle-caret {
|
|
5399
|
+
transform: rotate(90deg);
|
|
5400
|
+
}
|
|
5401
|
+
|
|
5355
5402
|
/* ── Rich content widgets (inline in chat transcript) ── */
|
|
5356
5403
|
.terminal-chat-widget {
|
|
5357
5404
|
max-width: min(820px, 100%);
|
|
@@ -6000,11 +6047,11 @@
|
|
|
6000
6047
|
<div class="terminal-input-shell" id="terminalInputShell">
|
|
6001
6048
|
<div class="terminal-input-toolbar">
|
|
6002
6049
|
<div class="terminal-input-mode-toggle">
|
|
6003
|
-
<button class="terminal-input-mode-btn
|
|
6004
|
-
<button class="terminal-input-mode-btn" id="terminalInputModeRawBtn" type="button" onclick="setTerminalInputMode('raw', { focus: true })">Raw</button>
|
|
6050
|
+
<button class="terminal-input-mode-btn" id="terminalInputModeChatBtn" type="button" onclick="setTerminalInputMode('chat', { focus: true })">Chat</button>
|
|
6051
|
+
<button class="terminal-input-mode-btn active" id="terminalInputModeRawBtn" type="button" onclick="setTerminalInputMode('raw', { focus: true })">Raw</button>
|
|
6005
6052
|
</div>
|
|
6006
6053
|
</div>
|
|
6007
|
-
<textarea id="terminalChatInput" class="terminal-input-chat" placeholder="Type command or prompt. Enter sends, Shift+Enter inserts newline."
|
|
6054
|
+
<textarea id="terminalChatInput" class="terminal-input-chat" placeholder="Type command or prompt. Enter sends, Shift+Enter inserts newline." onkeydown="handleTerminalChatInputKeydown(event)" spellcheck="false"></textarea>
|
|
6008
6055
|
<div class="terminal-input-actions">
|
|
6009
6056
|
<span class="terminal-input-hint" id="terminalInputHint">Enter sends to terminal. Shift+Enter inserts newline.</span>
|
|
6010
6057
|
<span class="terminal-input-spacer"></span>
|
|
@@ -6131,7 +6178,7 @@
|
|
|
6131
6178
|
<div class="explorer-experiment-row">
|
|
6132
6179
|
<select id="explorerAgentModeSelect" onchange="explorerOnAgentModeChanged()">
|
|
6133
6180
|
<option value="stub">Agent: Stub (deterministic)</option>
|
|
6134
|
-
<option value="claude_headless">Agent: Claude headless</option>
|
|
6181
|
+
<option value="claude_headless">Agent: Claude headless (beta)</option>
|
|
6135
6182
|
</select>
|
|
6136
6183
|
</div>
|
|
6137
6184
|
<div class="explorer-experiment-row">
|
|
@@ -6319,6 +6366,16 @@
|
|
|
6319
6366
|
</div>
|
|
6320
6367
|
</div>
|
|
6321
6368
|
</div>
|
|
6369
|
+
<div class="settings-section">
|
|
6370
|
+
<h3>Headless Chat <span style="display:inline-block;padding:1px 6px;border-radius:4px;background:var(--accent);color:#fff;font-size:0.625rem;font-weight:700;vertical-align:middle;margin-left:6px;letter-spacing:0.03em">BETA</span></h3>
|
|
6371
|
+
<p class="card-description">Enable a structured chat interface for Claude sessions running on Apptainer. This replaces the raw terminal view with a chat transcript showing tool calls and responses. <strong>Experimental</strong> — may not work reliably in all configurations.</p>
|
|
6372
|
+
<div class="field">
|
|
6373
|
+
<label class="toggle-row" style="display:flex;align-items:center;gap:10px;cursor:pointer">
|
|
6374
|
+
<input type="checkbox" id="headlessEnabledToggle" onchange="toggleHeadlessEnabled(this.checked)">
|
|
6375
|
+
<span>Enable headless chat mode</span>
|
|
6376
|
+
</label>
|
|
6377
|
+
</div>
|
|
6378
|
+
</div>
|
|
6322
6379
|
</div>
|
|
6323
6380
|
|
|
6324
6381
|
<!-- Network sub-panel -->
|
|
@@ -6675,7 +6732,8 @@ var webTerm = {
|
|
|
6675
6732
|
outOfSyncOverlayDismissed: false,
|
|
6676
6733
|
outOfSyncAutoReconnectTimer: 0,
|
|
6677
6734
|
outOfSyncAutoReconnectCooldownUntil: 0,
|
|
6678
|
-
inputMode: '
|
|
6735
|
+
inputMode: 'raw',
|
|
6736
|
+
headlessEnabled: false,
|
|
6679
6737
|
headlessSocket: null,
|
|
6680
6738
|
headlessRunning: false,
|
|
6681
6739
|
headlessResumeBySession: {},
|
|
@@ -6709,7 +6767,8 @@ var AUTO_COPY_SELECTION_DEDUPE_WINDOW_MS = 1400;
|
|
|
6709
6767
|
var autoCopySelectionState = {
|
|
6710
6768
|
inFlight: false,
|
|
6711
6769
|
lastText: '',
|
|
6712
|
-
lastCopiedAt: 0
|
|
6770
|
+
lastCopiedAt: 0,
|
|
6771
|
+
lastToastAt: 0
|
|
6713
6772
|
};
|
|
6714
6773
|
|
|
6715
6774
|
function getCachedClaudeEmail() {
|
|
@@ -6950,7 +7009,19 @@ function getWebTerminalSessionRuntime(session) {
|
|
|
6950
7009
|
return '';
|
|
6951
7010
|
}
|
|
6952
7011
|
|
|
7012
|
+
function toggleHeadlessEnabled(enabled) {
|
|
7013
|
+
webTerm.headlessEnabled = !!enabled;
|
|
7014
|
+
try { localStorage.setItem('labgate_headless_enabled', enabled ? '1' : '0'); } catch(e) {}
|
|
7015
|
+
if (!enabled && webTerm.inputMode === 'chat') {
|
|
7016
|
+
setTerminalInputMode('raw', { focus: false });
|
|
7017
|
+
}
|
|
7018
|
+
updateTerminalInputModeUi();
|
|
7019
|
+
updateTerminalInputAvailability();
|
|
7020
|
+
updateTerminalPresentationMode();
|
|
7021
|
+
}
|
|
7022
|
+
|
|
6953
7023
|
function isHeadlessClaudeChatSupported() {
|
|
7024
|
+
if (!webTerm.headlessEnabled) return false;
|
|
6954
7025
|
var session = getAttachedWebTerminalSession();
|
|
6955
7026
|
if (!session) return false;
|
|
6956
7027
|
var agent = String(session.agent || '').toLowerCase();
|
|
@@ -7051,26 +7122,69 @@ function renderTerminalChatTranscript() {
|
|
|
7051
7122
|
}
|
|
7052
7123
|
var transcript = getHeadlessTranscript(sessionId);
|
|
7053
7124
|
if (!transcript.length) {
|
|
7054
|
-
el.innerHTML = '<div class="terminal-chat-empty">Claude headless chat is ready. Your prompts and responses will appear here.</div>';
|
|
7125
|
+
el.innerHTML = '<div class="terminal-chat-empty"><strong style="color:var(--accent)">Beta</strong> — Claude headless chat is ready. This feature is experimental and under active development. Your prompts and responses will appear here.</div>';
|
|
7055
7126
|
return;
|
|
7056
7127
|
}
|
|
7057
|
-
|
|
7128
|
+
// Group consecutive tool entries for collapsible rendering
|
|
7129
|
+
var groups = [];
|
|
7130
|
+
var currentToolGroup = [];
|
|
7131
|
+
for (var ti = 0; ti < transcript.length; ti++) {
|
|
7132
|
+
var entry = transcript[ti];
|
|
7058
7133
|
var role = String(entry.role || 'assistant');
|
|
7059
|
-
|
|
7060
|
-
// Render tool entries as compact inline cards
|
|
7061
7134
|
if (role === 'tool') {
|
|
7062
|
-
|
|
7063
|
-
|
|
7064
|
-
|
|
7065
|
-
|
|
7066
|
-
|
|
7067
|
-
|
|
7068
|
-
|
|
7069
|
-
|
|
7070
|
-
|
|
7071
|
-
|
|
7135
|
+
currentToolGroup.push(entry);
|
|
7136
|
+
} else {
|
|
7137
|
+
if (currentToolGroup.length) {
|
|
7138
|
+
groups.push({ type: 'tools', items: currentToolGroup });
|
|
7139
|
+
currentToolGroup = [];
|
|
7140
|
+
}
|
|
7141
|
+
groups.push({ type: 'entry', entry: entry });
|
|
7142
|
+
}
|
|
7143
|
+
}
|
|
7144
|
+
if (currentToolGroup.length) {
|
|
7145
|
+
groups.push({ type: 'tools', items: currentToolGroup });
|
|
7146
|
+
}
|
|
7147
|
+
|
|
7148
|
+
var html = groups.map(function(group, gi) {
|
|
7149
|
+
if (group.type === 'tools') {
|
|
7150
|
+
var items = group.items;
|
|
7151
|
+
var VISIBLE_COUNT = 2;
|
|
7152
|
+
var needsCollapse = items.length > VISIBLE_COUNT + 1;
|
|
7153
|
+
var renderToolDiv = function(e) {
|
|
7154
|
+
var toolClasses = ['terminal-chat-tool'];
|
|
7155
|
+
if (e.toolDone) toolClasses.push('done');
|
|
7156
|
+
else if (e.toolError) toolClasses.push('error');
|
|
7157
|
+
else toolClasses.push('pending');
|
|
7158
|
+
var icon = e.toolDone ? '\u2713' : e.toolError ? '\u2717' : '\u26a1';
|
|
7159
|
+
return '<div class="' + toolClasses.join(' ') + '">'
|
|
7160
|
+
+ '<span class="tool-icon">' + icon + '</span>'
|
|
7161
|
+
+ '<span class="tool-name">' + escapeHtml(String(e.toolName || 'tool')) + '</span>'
|
|
7162
|
+
+ (e.toolDetail ? '<span class="tool-detail">' + escapeHtml(String(e.toolDetail)) + '</span>' : '')
|
|
7163
|
+
+ '</div>';
|
|
7164
|
+
};
|
|
7165
|
+
if (!needsCollapse) {
|
|
7166
|
+
return items.map(renderToolDiv).join('');
|
|
7167
|
+
}
|
|
7168
|
+
var hiddenCount = items.length - VISIBLE_COUNT;
|
|
7169
|
+
var groupId = 'tool-group-' + gi;
|
|
7170
|
+
var out = '<div class="terminal-chat-tool-group" id="' + groupId + '">';
|
|
7171
|
+
out += '<button type="button" class="tool-group-toggle" onclick="var g=document.getElementById(\'' + groupId + '\');g.classList.toggle(\'expanded\');this.querySelector(\'.toggle-label\').textContent=g.classList.contains(\'expanded\')?\'\u25BE Hide '+hiddenCount+' tool calls\':\'\u25B8 '+hiddenCount+' more tool calls\';this.querySelector(\'.toggle-caret\').textContent=g.classList.contains(\'expanded\')?\'\u25BE\':\'\u25B8\'">'
|
|
7172
|
+
+ '<span class="toggle-caret">\u25B8</span>'
|
|
7173
|
+
+ '<span class="toggle-label">' + hiddenCount + ' more tool calls</span>'
|
|
7174
|
+
+ '</button>';
|
|
7175
|
+
for (var hi = 0; hi < items.length - VISIBLE_COUNT; hi++) {
|
|
7176
|
+
out += renderToolDiv(items[hi]).replace('class="terminal-chat-tool', 'class="terminal-chat-tool tool-group-collapsed');
|
|
7177
|
+
}
|
|
7178
|
+
for (var vi = items.length - VISIBLE_COUNT; vi < items.length; vi++) {
|
|
7179
|
+
out += renderToolDiv(items[vi]);
|
|
7180
|
+
}
|
|
7181
|
+
out += '</div>';
|
|
7182
|
+
return out;
|
|
7072
7183
|
}
|
|
7073
7184
|
|
|
7185
|
+
var entry = group.entry;
|
|
7186
|
+
var role = String(entry.role || 'assistant');
|
|
7187
|
+
|
|
7074
7188
|
// Render widget entries as rich content cards
|
|
7075
7189
|
if (role === 'widget') {
|
|
7076
7190
|
var widgetId = 'widget-' + entry.id;
|
|
@@ -8183,10 +8297,13 @@ function updateTerminalInputModeUi() {
|
|
|
8183
8297
|
}
|
|
8184
8298
|
var headless = isHeadlessClaudeChatAvailable();
|
|
8185
8299
|
|
|
8300
|
+
var modeToggle = inputShell ? inputShell.querySelector('.terminal-input-mode-toggle') : null;
|
|
8186
8301
|
if (inputShell) {
|
|
8187
8302
|
inputShell.classList.toggle('raw-mode', isRaw);
|
|
8188
8303
|
inputShell.classList.toggle('out-of-sync', !!webTerm.outOfSync);
|
|
8189
8304
|
}
|
|
8305
|
+
// Hide the Chat/Raw toggle entirely when headless is not enabled
|
|
8306
|
+
if (modeToggle) modeToggle.style.display = webTerm.headlessEnabled ? '' : 'none';
|
|
8190
8307
|
if (chatBtn) chatBtn.classList.toggle('active', !isRaw && headlessCapable);
|
|
8191
8308
|
if (rawBtn) rawBtn.classList.toggle('active', isRaw);
|
|
8192
8309
|
if (hint) {
|
|
@@ -8992,6 +9109,33 @@ function ensureWebTerminalReady() {
|
|
|
8992
9109
|
if (!clean) return;
|
|
8993
9110
|
sendTerminalInputData(clean, { silent: true });
|
|
8994
9111
|
});
|
|
9112
|
+
if (typeof webTerm.terminal.onSelectionChange === 'function') {
|
|
9113
|
+
webTerm.terminal.onSelectionChange(function() {
|
|
9114
|
+
runAutoCopySelection({
|
|
9115
|
+
getText: getTerminalSelectionText,
|
|
9116
|
+
allowFallback: true,
|
|
9117
|
+
allowTextareaFallback: false
|
|
9118
|
+
});
|
|
9119
|
+
});
|
|
9120
|
+
}
|
|
9121
|
+
if (typeof webTerm.terminal.attachCustomKeyEventHandler === 'function') {
|
|
9122
|
+
webTerm.terminal.attachCustomKeyEventHandler(function(event) {
|
|
9123
|
+
if (!isTerminalCopyShortcut(event)) return true;
|
|
9124
|
+
var selectedText = getTerminalSelectionText();
|
|
9125
|
+
if (!selectedText) return true;
|
|
9126
|
+
event.preventDefault();
|
|
9127
|
+
runAutoCopySelection({
|
|
9128
|
+
text: selectedText,
|
|
9129
|
+
allowEditable: true,
|
|
9130
|
+
force: true,
|
|
9131
|
+
allowFallback: true,
|
|
9132
|
+
fallbackRestoreFocus: false,
|
|
9133
|
+
notifyCopied: true,
|
|
9134
|
+
copiedToastMessage: 'Copied'
|
|
9135
|
+
});
|
|
9136
|
+
return false;
|
|
9137
|
+
});
|
|
9138
|
+
}
|
|
8995
9139
|
webTerm.terminal.onResize(function(size) {
|
|
8996
9140
|
if (!webTerm.socket || webTerm.socket.readyState !== WebSocket.OPEN) return;
|
|
8997
9141
|
webTerm.socket.send(JSON.stringify({ type: 'resize', cols: size.cols, rows: size.rows }));
|
|
@@ -9694,6 +9838,11 @@ try {
|
|
|
9694
9838
|
}
|
|
9695
9839
|
} catch(e) {}
|
|
9696
9840
|
|
|
9841
|
+
// Restore headless chat preference
|
|
9842
|
+
try {
|
|
9843
|
+
webTerm.headlessEnabled = localStorage.getItem('labgate_headless_enabled') === '1';
|
|
9844
|
+
} catch(e) {}
|
|
9845
|
+
|
|
9697
9846
|
applyExplorerDevModeVisibility();
|
|
9698
9847
|
|
|
9699
9848
|
function saveSidebarState() {
|
|
@@ -10560,9 +10709,57 @@ function isEditableSelectionNode(node) {
|
|
|
10560
10709
|
return false;
|
|
10561
10710
|
}
|
|
10562
10711
|
|
|
10563
|
-
function
|
|
10564
|
-
if (!
|
|
10712
|
+
function getTerminalSelectionText() {
|
|
10713
|
+
if (!webTerm || !webTerm.terminal) return '';
|
|
10714
|
+
try {
|
|
10715
|
+
if (typeof webTerm.terminal.hasSelection === 'function' && webTerm.terminal.hasSelection()) {
|
|
10716
|
+
return webTerm.terminal.getSelection() || '';
|
|
10717
|
+
}
|
|
10718
|
+
} catch (_errSelection) {}
|
|
10719
|
+
return '';
|
|
10720
|
+
}
|
|
10721
|
+
|
|
10722
|
+
function isTerminalCopyShortcut(event) {
|
|
10723
|
+
if (!event || event.type !== 'keydown') return false;
|
|
10724
|
+
var key = String(event.key || '').toLowerCase();
|
|
10725
|
+
if ((event.metaKey || event.ctrlKey) && !event.altKey && !event.shiftKey && key === 'c') return true;
|
|
10726
|
+
if (event.ctrlKey && event.shiftKey && !event.altKey && key === 'c') return true;
|
|
10727
|
+
if (event.ctrlKey && !event.altKey && !event.metaKey && key === 'insert') return true;
|
|
10728
|
+
return false;
|
|
10729
|
+
}
|
|
10730
|
+
|
|
10731
|
+
function isNodeInsideTerminalView(node) {
|
|
10732
|
+
var el = node && node.nodeType === 1 ? node : (node && node.parentElement ? node.parentElement : null);
|
|
10733
|
+
while (el) {
|
|
10734
|
+
if (el.id === 'terminalView') return true;
|
|
10735
|
+
el = el.parentElement;
|
|
10736
|
+
}
|
|
10737
|
+
return false;
|
|
10738
|
+
}
|
|
10739
|
+
|
|
10740
|
+
function isTerminalSelectionContext() {
|
|
10741
|
+
var terminalSelection = getTerminalSelectionText();
|
|
10742
|
+
if (terminalSelection && String(terminalSelection).trim()) return true;
|
|
10565
10743
|
var selection = window.getSelection();
|
|
10744
|
+
if (!selection || selection.isCollapsed) return false;
|
|
10745
|
+
return isNodeInsideTerminalView(selection.anchorNode) || isNodeInsideTerminalView(selection.focusNode);
|
|
10746
|
+
}
|
|
10747
|
+
|
|
10748
|
+
function maybeShowCopiedToast(opts) {
|
|
10749
|
+
var options = opts || {};
|
|
10750
|
+
var now = Date.now();
|
|
10751
|
+
var minIntervalMs = typeof options.minIntervalMs === 'number' ? options.minIntervalMs : 800;
|
|
10752
|
+
if ((now - autoCopySelectionState.lastToastAt) < minIntervalMs) return;
|
|
10753
|
+
autoCopySelectionState.lastToastAt = now;
|
|
10754
|
+
showToast(options.message || 'Copied', options.type || 'success');
|
|
10755
|
+
}
|
|
10756
|
+
|
|
10757
|
+
function fallbackCopyTextToClipboard(text, opts) {
|
|
10758
|
+
if (!text) return false;
|
|
10759
|
+
var options = opts || {};
|
|
10760
|
+
var preserveSelection = options.preserveSelection !== false;
|
|
10761
|
+
var restoreFocus = options.restoreFocus !== false;
|
|
10762
|
+
var selection = preserveSelection ? window.getSelection() : null;
|
|
10566
10763
|
var preservedRanges = [];
|
|
10567
10764
|
if (selection && typeof selection.rangeCount === 'number') {
|
|
10568
10765
|
for (var i = 0; i < selection.rangeCount; i++) {
|
|
@@ -10595,15 +10792,40 @@ function fallbackCopyTextToClipboard(text) {
|
|
|
10595
10792
|
try { selection.addRange(range); } catch (_errAdd) {}
|
|
10596
10793
|
});
|
|
10597
10794
|
}
|
|
10598
|
-
if (activeElement && typeof activeElement.focus === 'function') {
|
|
10795
|
+
if (restoreFocus && activeElement && typeof activeElement.focus === 'function') {
|
|
10599
10796
|
try { activeElement.focus(); } catch (_errFocus) {}
|
|
10600
10797
|
}
|
|
10601
10798
|
return ok;
|
|
10602
10799
|
}
|
|
10603
10800
|
|
|
10801
|
+
function execCommandCopyTextToClipboard(text) {
|
|
10802
|
+
var rawText = typeof text === 'string' ? text : '';
|
|
10803
|
+
if (!rawText || typeof document.execCommand !== 'function') return false;
|
|
10804
|
+
var copied = false;
|
|
10805
|
+
function onCopy(event) {
|
|
10806
|
+
if (!event || !event.clipboardData || typeof event.clipboardData.setData !== 'function') return;
|
|
10807
|
+
try {
|
|
10808
|
+
event.clipboardData.setData('text/plain', rawText);
|
|
10809
|
+
event.preventDefault();
|
|
10810
|
+
copied = true;
|
|
10811
|
+
} catch (_errSetData) {}
|
|
10812
|
+
}
|
|
10813
|
+
document.addEventListener('copy', onCopy);
|
|
10814
|
+
try {
|
|
10815
|
+
copied = document.execCommand('copy') || copied;
|
|
10816
|
+
} catch (_errExec) {
|
|
10817
|
+
copied = copied || false;
|
|
10818
|
+
}
|
|
10819
|
+
document.removeEventListener('copy', onCopy);
|
|
10820
|
+
return copied;
|
|
10821
|
+
}
|
|
10822
|
+
|
|
10604
10823
|
function writeTextToClipboard(text, opts) {
|
|
10605
10824
|
var options = opts || {};
|
|
10606
10825
|
var allowFallback = options.allowFallback !== false;
|
|
10826
|
+
var allowTextareaFallback = options.allowTextareaFallback !== false;
|
|
10827
|
+
var fallbackRestoreFocus = options.fallbackRestoreFocus !== false;
|
|
10828
|
+
var fallbackPreserveSelection = options.fallbackPreserveSelection !== false;
|
|
10607
10829
|
var rawText = typeof text === 'string' ? text : '';
|
|
10608
10830
|
if (!rawText) return Promise.resolve(false);
|
|
10609
10831
|
if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
|
|
@@ -10611,11 +10833,21 @@ function writeTextToClipboard(text, opts) {
|
|
|
10611
10833
|
return true;
|
|
10612
10834
|
}).catch(function() {
|
|
10613
10835
|
if (!allowFallback) return false;
|
|
10614
|
-
|
|
10836
|
+
if (execCommandCopyTextToClipboard(rawText)) return true;
|
|
10837
|
+
if (!allowTextareaFallback) return false;
|
|
10838
|
+
return fallbackCopyTextToClipboard(rawText, {
|
|
10839
|
+
restoreFocus: fallbackRestoreFocus,
|
|
10840
|
+
preserveSelection: fallbackPreserveSelection
|
|
10841
|
+
});
|
|
10615
10842
|
});
|
|
10616
10843
|
}
|
|
10617
10844
|
if (!allowFallback) return Promise.resolve(false);
|
|
10618
|
-
return Promise.resolve(
|
|
10845
|
+
if (execCommandCopyTextToClipboard(rawText)) return Promise.resolve(true);
|
|
10846
|
+
if (!allowTextareaFallback) return Promise.resolve(false);
|
|
10847
|
+
return Promise.resolve(fallbackCopyTextToClipboard(rawText, {
|
|
10848
|
+
restoreFocus: fallbackRestoreFocus,
|
|
10849
|
+
preserveSelection: fallbackPreserveSelection
|
|
10850
|
+
}));
|
|
10619
10851
|
}
|
|
10620
10852
|
|
|
10621
10853
|
function runAutoCopySelection(opts) {
|
|
@@ -10632,11 +10864,14 @@ function runAutoCopySelection(opts) {
|
|
|
10632
10864
|
}
|
|
10633
10865
|
if (!text) {
|
|
10634
10866
|
var selection = window.getSelection();
|
|
10635
|
-
if (
|
|
10636
|
-
|
|
10637
|
-
|
|
10867
|
+
if (selection && !selection.isCollapsed) {
|
|
10868
|
+
if (!options.allowEditable && (isEditableSelectionNode(selection.anchorNode) || isEditableSelectionNode(selection.focusNode))) {
|
|
10869
|
+
return;
|
|
10870
|
+
}
|
|
10871
|
+
text = selection.toString();
|
|
10872
|
+
} else {
|
|
10873
|
+
text = getTerminalSelectionText();
|
|
10638
10874
|
}
|
|
10639
|
-
text = selection.toString();
|
|
10640
10875
|
}
|
|
10641
10876
|
if (!text) return;
|
|
10642
10877
|
var normalized = String(text).replace(/\s+/g, ' ').trim();
|
|
@@ -10644,17 +10879,30 @@ function runAutoCopySelection(opts) {
|
|
|
10644
10879
|
|
|
10645
10880
|
var now = Date.now();
|
|
10646
10881
|
if (
|
|
10882
|
+
!options.force
|
|
10883
|
+
&&
|
|
10647
10884
|
normalized === autoCopySelectionState.lastText
|
|
10648
10885
|
&& (now - autoCopySelectionState.lastCopiedAt) < AUTO_COPY_SELECTION_DEDUPE_WINDOW_MS
|
|
10649
10886
|
) {
|
|
10887
|
+
if (options.notifyCopied) {
|
|
10888
|
+
maybeShowCopiedToast({ message: options.copiedToastMessage || 'Copied' });
|
|
10889
|
+
}
|
|
10650
10890
|
return;
|
|
10651
10891
|
}
|
|
10652
10892
|
if (autoCopySelectionState.inFlight) return;
|
|
10653
10893
|
autoCopySelectionState.inFlight = true;
|
|
10654
|
-
writeTextToClipboard(text, {
|
|
10894
|
+
writeTextToClipboard(text, {
|
|
10895
|
+
allowFallback: options.allowFallback !== false,
|
|
10896
|
+
allowTextareaFallback: options.allowTextareaFallback !== false,
|
|
10897
|
+
fallbackRestoreFocus: options.fallbackRestoreFocus !== false,
|
|
10898
|
+
fallbackPreserveSelection: options.fallbackPreserveSelection !== false
|
|
10899
|
+
}).then(function(copied) {
|
|
10655
10900
|
if (copied) {
|
|
10656
10901
|
autoCopySelectionState.lastText = normalized;
|
|
10657
10902
|
autoCopySelectionState.lastCopiedAt = Date.now();
|
|
10903
|
+
if (options.notifyCopied) {
|
|
10904
|
+
maybeShowCopiedToast({ message: options.copiedToastMessage || 'Copied' });
|
|
10905
|
+
}
|
|
10658
10906
|
}
|
|
10659
10907
|
autoCopySelectionState.inFlight = false;
|
|
10660
10908
|
}).catch(function() {
|
|
@@ -11025,6 +11273,9 @@ function syncHeroStartForm() {
|
|
|
11025
11273
|
function openSettingsModal() {
|
|
11026
11274
|
var modal = document.getElementById('settingsModal');
|
|
11027
11275
|
modal.classList.add('visible');
|
|
11276
|
+
// Sync headless toggle
|
|
11277
|
+
var headlessToggle = document.getElementById('headlessEnabledToggle');
|
|
11278
|
+
if (headlessToggle) headlessToggle.checked = !!webTerm.headlessEnabled;
|
|
11028
11279
|
// Load data for active sub-tab
|
|
11029
11280
|
var activeSubTab = document.querySelector('.sub-tab.active');
|
|
11030
11281
|
if (activeSubTab && activeSubTab.dataset.subtab === 'mcp') { setMcpTabActive(true); loadMcpServers(); }
|
|
@@ -14409,7 +14660,6 @@ function removeWidget(widgetId) {
|
|
|
14409
14660
|
}
|
|
14410
14661
|
|
|
14411
14662
|
function clearWidgets() {
|
|
14412
|
-
if (widgetsCache.length === 0) return;
|
|
14413
14663
|
fetch('/api/widgets/clear', {
|
|
14414
14664
|
method: 'POST',
|
|
14415
14665
|
headers: apiWriteHeaders(),
|
|
@@ -15570,7 +15820,12 @@ document.addEventListener('keydown', function(e) {
|
|
|
15570
15820
|
});
|
|
15571
15821
|
document.addEventListener('mouseup', function(e) {
|
|
15572
15822
|
if (e && e.button !== 0) return;
|
|
15573
|
-
runAutoCopySelection(
|
|
15823
|
+
runAutoCopySelection({
|
|
15824
|
+
allowFallback: true,
|
|
15825
|
+
allowTextareaFallback: false,
|
|
15826
|
+
notifyCopied: isTerminalSelectionContext(),
|
|
15827
|
+
copiedToastMessage: 'Copied'
|
|
15828
|
+
});
|
|
15574
15829
|
});
|
|
15575
15830
|
document.addEventListener('keyup', function(e) {
|
|
15576
15831
|
if (!e) return;
|
|
@@ -15583,7 +15838,12 @@ document.addEventListener('keyup', function(e) {
|
|
|
15583
15838
|
|| key.indexOf('Arrow') === 0
|
|
15584
15839
|
|| ((e.ctrlKey || e.metaKey) && key.toLowerCase() === 'a');
|
|
15585
15840
|
if (!isSelectionKey) return;
|
|
15586
|
-
runAutoCopySelection(
|
|
15841
|
+
runAutoCopySelection({
|
|
15842
|
+
allowFallback: true,
|
|
15843
|
+
allowTextareaFallback: false,
|
|
15844
|
+
notifyCopied: isTerminalSelectionContext(),
|
|
15845
|
+
copiedToastMessage: 'Copied'
|
|
15846
|
+
});
|
|
15587
15847
|
});
|
|
15588
15848
|
</script>
|
|
15589
15849
|
|
package/dist/lib/ui.js
CHANGED
|
@@ -1195,27 +1195,32 @@ function closeWebTerminalBridgeClients(bridge, code = 4001, reason = 'labgate-br
|
|
|
1195
1195
|
}
|
|
1196
1196
|
async function ensureWebTerminalBridge(record) {
|
|
1197
1197
|
const existing = webTerminalBridges.get(record.id);
|
|
1198
|
-
if (existing && existing.pty)
|
|
1199
|
-
return existing;
|
|
1200
|
-
const ptyModule = await loadNodePtyModule();
|
|
1201
|
-
if (!ptyModule) {
|
|
1202
|
-
return null;
|
|
1203
|
-
}
|
|
1204
1198
|
let tmuxBin = 'tmux';
|
|
1205
1199
|
try {
|
|
1206
1200
|
tmuxBin = await (0, web_terminal_js_1.getTmuxBinary)();
|
|
1207
1201
|
}
|
|
1208
1202
|
catch (err) {
|
|
1203
|
+
if (existing && existing.pty) {
|
|
1204
|
+
log.warn(`Could not resolve tmux binary for web terminal bridge ${record.id}: ${err?.message ?? String(err)}`);
|
|
1205
|
+
return existing;
|
|
1206
|
+
}
|
|
1209
1207
|
log.warn(`Could not resolve tmux binary for web terminal bridge ${record.id}: ${err?.message ?? String(err)}`);
|
|
1210
1208
|
return null;
|
|
1211
1209
|
}
|
|
1212
1210
|
try {
|
|
1213
|
-
// Keep
|
|
1214
|
-
|
|
1211
|
+
// Keep web terminal copy/selection behavior reliable by letting xterm own
|
|
1212
|
+
// mouse selection instead of tmux copy-mode selection.
|
|
1213
|
+
await execFileAsync(tmuxBin, ['set-option', '-t', record.tmuxSession, 'mouse', 'off'], { timeout: 10_000 });
|
|
1215
1214
|
}
|
|
1216
1215
|
catch {
|
|
1217
1216
|
// Best effort only; attach should still proceed.
|
|
1218
1217
|
}
|
|
1218
|
+
if (existing && existing.pty)
|
|
1219
|
+
return existing;
|
|
1220
|
+
const ptyModule = await loadNodePtyModule();
|
|
1221
|
+
if (!ptyModule) {
|
|
1222
|
+
return null;
|
|
1223
|
+
}
|
|
1219
1224
|
const env = {};
|
|
1220
1225
|
for (const [k, v] of Object.entries(process.env)) {
|
|
1221
1226
|
if (v !== undefined)
|
|
@@ -1376,7 +1381,6 @@ async function startClaudeHeadlessWsRun(ws, record, prompt, resumeSessionId) {
|
|
|
1376
1381
|
return () => { };
|
|
1377
1382
|
}
|
|
1378
1383
|
const args = buildClaudeHeadlessApptainerArgs(config, record.workdir, trimmedPrompt, resumeSessionId);
|
|
1379
|
-
send({ type: 'status', stage: 'run', message: 'Running Claude in headless mode...' });
|
|
1380
1384
|
const child = (0, child_process_1.spawn)('apptainer', args, {
|
|
1381
1385
|
cwd: record.workdir,
|
|
1382
1386
|
env: process.env,
|