tabminal 3.0.14 → 3.0.16
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/public/app.js +874 -188
- package/public/styles.css +73 -0
- package/src/acp-manager.mjs +707 -163
- package/src/acp-test-agent.mjs +128 -0
- package/src/server.mjs +26 -10
package/public/app.js
CHANGED
|
@@ -105,6 +105,10 @@ const HEARTBEAT_INTERVAL_MS = 1000;
|
|
|
105
105
|
const RECONNECT_RETRY_MS = 5000;
|
|
106
106
|
const FILE_TREE_REFRESH_INTERVAL_MS = 3000;
|
|
107
107
|
const FILE_VERSION_CHECK_INTERVAL_MS = 3000;
|
|
108
|
+
const AGENT_TRANSCRIPT_INITIAL_VISIBLE_BLOCKS = 100;
|
|
109
|
+
const AGENT_TRANSCRIPT_WINDOW_STEP = 50;
|
|
110
|
+
const AGENT_TRANSCRIPT_FOLLOW_LATEST_TOLERANCE = 5;
|
|
111
|
+
const WORKSPACE_TAB_TITLE_MAX_LENGTH = 20;
|
|
108
112
|
const MAIN_SERVER_ID = 'main';
|
|
109
113
|
const RUNTIME_BOOT_ID_STORAGE_KEY = 'tabminal_runtime_boot_id';
|
|
110
114
|
const WORKSPACE_DEVICE_ID_STORAGE_KEY = 'tabminal_workspace_device_id';
|
|
@@ -417,6 +421,20 @@ function resolveMarkdownLocalTarget(baseFilePath, href) {
|
|
|
417
421
|
}
|
|
418
422
|
}
|
|
419
423
|
|
|
424
|
+
function buildMarkdownContextBasePath(filePath = '', baseDirectory = '') {
|
|
425
|
+
const nextFilePath = String(filePath || '').trim();
|
|
426
|
+
if (nextFilePath) {
|
|
427
|
+
return nextFilePath;
|
|
428
|
+
}
|
|
429
|
+
const nextBaseDirectory = String(baseDirectory || '')
|
|
430
|
+
.trim()
|
|
431
|
+
.replace(/\/+$/, '');
|
|
432
|
+
if (!nextBaseDirectory) {
|
|
433
|
+
return '';
|
|
434
|
+
}
|
|
435
|
+
return `${nextBaseDirectory}/__tabminal__.md`;
|
|
436
|
+
}
|
|
437
|
+
|
|
420
438
|
function slugifyMarkdownHeading(text) {
|
|
421
439
|
return String(text || '')
|
|
422
440
|
.trim()
|
|
@@ -1535,6 +1553,24 @@ class EditorManager {
|
|
|
1535
1553
|
this.agentTranscript = document.createElement('div');
|
|
1536
1554
|
this.agentTranscript.className = 'agent-panel-transcript';
|
|
1537
1555
|
this.agentTranscript.addEventListener('click', (event) => {
|
|
1556
|
+
const markdownLink = event.target.closest(
|
|
1557
|
+
'a[data-markdown-local-path]'
|
|
1558
|
+
);
|
|
1559
|
+
if (markdownLink) {
|
|
1560
|
+
const filePath = String(
|
|
1561
|
+
markdownLink.dataset.markdownLocalPath || ''
|
|
1562
|
+
).trim();
|
|
1563
|
+
if (!filePath) {
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
event.preventDefault();
|
|
1567
|
+
event.stopPropagation();
|
|
1568
|
+
void this.openLocalMarkdownLink(
|
|
1569
|
+
filePath,
|
|
1570
|
+
String(markdownLink.dataset.markdownLocalHash || '')
|
|
1571
|
+
);
|
|
1572
|
+
return;
|
|
1573
|
+
}
|
|
1538
1574
|
const anchor = event.target.closest('a');
|
|
1539
1575
|
if (!anchor) return;
|
|
1540
1576
|
const href = anchor.getAttribute('href') || '';
|
|
@@ -1545,6 +1581,20 @@ class EditorManager {
|
|
|
1545
1581
|
void this.openFile(href);
|
|
1546
1582
|
});
|
|
1547
1583
|
this.agentTranscript.addEventListener('scroll', () => {
|
|
1584
|
+
const activeAgentTab = getActiveAgentTab();
|
|
1585
|
+
if (
|
|
1586
|
+
activeAgentTab
|
|
1587
|
+
&& this.agentTranscript.scrollTop <= 24
|
|
1588
|
+
) {
|
|
1589
|
+
activeAgentTab.scrollToBottomOnNextRender = false;
|
|
1590
|
+
void this.loadOlderAgentTimeline(activeAgentTab);
|
|
1591
|
+
} else if (
|
|
1592
|
+
activeAgentTab
|
|
1593
|
+
&& this.isAgentTranscriptNearBottom(24)
|
|
1594
|
+
) {
|
|
1595
|
+
activeAgentTab.scrollToBottomOnNextRender = false;
|
|
1596
|
+
void this.loadNewerAgentTimeline(activeAgentTab);
|
|
1597
|
+
}
|
|
1548
1598
|
this.updateAgentScrollBottomButton();
|
|
1549
1599
|
this.rememberAgentTranscriptLayout();
|
|
1550
1600
|
});
|
|
@@ -4197,6 +4247,90 @@ class EditorManager {
|
|
|
4197
4247
|
}
|
|
4198
4248
|
}
|
|
4199
4249
|
|
|
4250
|
+
getAgentMarkdownBaseDirectory(agentTab, message) {
|
|
4251
|
+
const messageCwd = String(message?.cwd || '').trim();
|
|
4252
|
+
if (messageCwd) {
|
|
4253
|
+
return messageCwd;
|
|
4254
|
+
}
|
|
4255
|
+
const tabCwd = String(agentTab?.cwd || '').trim();
|
|
4256
|
+
if (tabCwd) {
|
|
4257
|
+
return tabCwd;
|
|
4258
|
+
}
|
|
4259
|
+
const session = this.currentSession;
|
|
4260
|
+
return String(session?.cwd || session?.initialCwd || '').trim();
|
|
4261
|
+
}
|
|
4262
|
+
|
|
4263
|
+
async enhanceAgentMarkdownBody(agentTab, message, body) {
|
|
4264
|
+
if (!(body instanceof HTMLElement) || !message?.text) {
|
|
4265
|
+
return;
|
|
4266
|
+
}
|
|
4267
|
+
if (agentTab?.busy) {
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
if (isAgentMessageStreaming(agentTab, message)) {
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4273
|
+
const session = this.currentSession;
|
|
4274
|
+
if (!session) {
|
|
4275
|
+
return;
|
|
4276
|
+
}
|
|
4277
|
+
|
|
4278
|
+
const sourceText = String(message.text || '');
|
|
4279
|
+
const cachedMarkdown = getAgentMessageMarkdownCache(message);
|
|
4280
|
+
if (cachedMarkdown) {
|
|
4281
|
+
body.classList.remove('plain');
|
|
4282
|
+
body.classList.add('markdown');
|
|
4283
|
+
body.innerHTML = cachedMarkdown;
|
|
4284
|
+
return;
|
|
4285
|
+
}
|
|
4286
|
+
|
|
4287
|
+
const renderToken = `${Date.now()}:${Math.random()}`;
|
|
4288
|
+
body.dataset.markdownRenderToken = renderToken;
|
|
4289
|
+
|
|
4290
|
+
try {
|
|
4291
|
+
const { renderer } = await loadMarkdownPreviewBundle();
|
|
4292
|
+
if (
|
|
4293
|
+
String(message.text || '') !== sourceText
|
|
4294
|
+
|| isAgentMessageStreaming(agentTab, message)
|
|
4295
|
+
) {
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
const rendered = renderer.render(sourceText);
|
|
4299
|
+
const sanitized = DOMPurify.sanitize(rendered, {
|
|
4300
|
+
USE_PROFILES: {
|
|
4301
|
+
html: true,
|
|
4302
|
+
mathMl: true,
|
|
4303
|
+
svg: true
|
|
4304
|
+
}
|
|
4305
|
+
});
|
|
4306
|
+
const template = document.createElement('template');
|
|
4307
|
+
template.innerHTML = sanitized;
|
|
4308
|
+
const basePath = buildMarkdownContextBasePath(
|
|
4309
|
+
'',
|
|
4310
|
+
this.getAgentMarkdownBaseDirectory(agentTab, message)
|
|
4311
|
+
);
|
|
4312
|
+
this.decorateMarkdownPreviewContent(
|
|
4313
|
+
template.content,
|
|
4314
|
+
basePath,
|
|
4315
|
+
session
|
|
4316
|
+
);
|
|
4317
|
+
const nextMarkdownHtml = template.innerHTML;
|
|
4318
|
+
message.markdownRenderSource = sourceText;
|
|
4319
|
+
message.markdownRenderHtml = nextMarkdownHtml;
|
|
4320
|
+
if (
|
|
4321
|
+
!body.isConnected
|
|
4322
|
+
|| body.dataset.markdownRenderToken !== renderToken
|
|
4323
|
+
) {
|
|
4324
|
+
return;
|
|
4325
|
+
}
|
|
4326
|
+
body.classList.remove('plain');
|
|
4327
|
+
body.classList.add('markdown');
|
|
4328
|
+
body.replaceChildren(template.content);
|
|
4329
|
+
} catch {
|
|
4330
|
+
// Keep the lightweight fallback rendering.
|
|
4331
|
+
}
|
|
4332
|
+
}
|
|
4333
|
+
|
|
4200
4334
|
scrollMarkdownPreviewHash(hash) {
|
|
4201
4335
|
const nextHash = String(hash || '').trim();
|
|
4202
4336
|
if (!nextHash || !this.markdownPreviewScroll) {
|
|
@@ -5171,7 +5305,10 @@ class EditorManager {
|
|
|
5171
5305
|
);
|
|
5172
5306
|
|
|
5173
5307
|
const label = document.createElement('span');
|
|
5174
|
-
|
|
5308
|
+
tab.title = String(getAgentDisplayLabel(agentTab) || '').trim();
|
|
5309
|
+
label.textContent = formatWorkspaceTabTitle(
|
|
5310
|
+
getAgentDisplayLabel(agentTab)
|
|
5311
|
+
);
|
|
5175
5312
|
|
|
5176
5313
|
const closeBtn = document.createElement('span');
|
|
5177
5314
|
closeBtn.className = 'close-btn';
|
|
@@ -5475,7 +5612,9 @@ class EditorManager {
|
|
|
5475
5612
|
this.hideMarkdownPreview();
|
|
5476
5613
|
this.emptyState.style.display = 'none';
|
|
5477
5614
|
this.agentContainer.style.display = 'flex';
|
|
5478
|
-
this.renderAgentPanel(agentTab
|
|
5615
|
+
this.renderAgentPanel(agentTab, {
|
|
5616
|
+
reason: isRestore ? 'activate-restore' : 'activate'
|
|
5617
|
+
});
|
|
5479
5618
|
}
|
|
5480
5619
|
|
|
5481
5620
|
async closeAgentTab(agentTabKey) {
|
|
@@ -5485,8 +5624,7 @@ class EditorManager {
|
|
|
5485
5624
|
removeAgentTab(agentTabKey);
|
|
5486
5625
|
}
|
|
5487
5626
|
|
|
5488
|
-
renderAgentPanel(agentTab) {
|
|
5489
|
-
this.disposeAgentEmbeddedEditors();
|
|
5627
|
+
renderAgentPanel(agentTab, options = {}) {
|
|
5490
5628
|
const previousLayout = this.captureAgentTranscriptLayout();
|
|
5491
5629
|
const previousScrollTop = previousLayout?.scrollTop || 0;
|
|
5492
5630
|
const wasNearBottom = this.isAgentTranscriptLayoutNearBottom(
|
|
@@ -5550,50 +5688,97 @@ class EditorManager {
|
|
|
5550
5688
|
|
|
5551
5689
|
this.renderAgentComposerAttachments(agentTab);
|
|
5552
5690
|
|
|
5553
|
-
this.agentTranscript.innerHTML = '';
|
|
5554
5691
|
const timeline = getAgentTimelineItems(agentTab);
|
|
5692
|
+
const shouldPinToBottom = wasNearBottom || (
|
|
5693
|
+
agentTab.scrollToBottomOnNextRender
|
|
5694
|
+
&& isAgentTranscriptWindowNearLatest(
|
|
5695
|
+
agentTab,
|
|
5696
|
+
timeline.length
|
|
5697
|
+
)
|
|
5698
|
+
);
|
|
5699
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5700
|
+
agentTab,
|
|
5701
|
+
timeline.length,
|
|
5702
|
+
{ pinToBottom: shouldPinToBottom }
|
|
5703
|
+
);
|
|
5704
|
+
const visibleTimeline = timeline.slice(
|
|
5705
|
+
transcriptWindow.start,
|
|
5706
|
+
transcriptWindow.end
|
|
5707
|
+
);
|
|
5555
5708
|
if (timeline.length === 0) {
|
|
5556
|
-
this.agentTranscript.
|
|
5557
|
-
|
|
5558
|
-
);
|
|
5709
|
+
const existingChildren = Array.from(this.agentTranscript.children);
|
|
5710
|
+
const emptyState = this.buildAgentEmptyState(agentTab);
|
|
5711
|
+
this.agentTranscript.replaceChildren(emptyState);
|
|
5712
|
+
for (const node of existingChildren) {
|
|
5713
|
+
this.disposeAgentTimelineNode(node);
|
|
5714
|
+
}
|
|
5559
5715
|
} else {
|
|
5560
|
-
|
|
5561
|
-
|
|
5562
|
-
|
|
5563
|
-
|
|
5564
|
-
|
|
5565
|
-
|
|
5566
|
-
} else if (entry.type === 'permission') {
|
|
5567
|
-
node = this.buildAgentPermissionNode(
|
|
5568
|
-
agentTab,
|
|
5569
|
-
entry.value
|
|
5570
|
-
);
|
|
5571
|
-
} else if (entry.type === 'plan') {
|
|
5572
|
-
node = this.buildAgentPlanHistoryNode(
|
|
5573
|
-
agentTab,
|
|
5574
|
-
entry.value
|
|
5575
|
-
);
|
|
5716
|
+
const existingChildren = Array.from(this.agentTranscript.children);
|
|
5717
|
+
const existingByKey = new Map();
|
|
5718
|
+
for (const node of existingChildren) {
|
|
5719
|
+
const key = node?.dataset?.timelineKey || '';
|
|
5720
|
+
if (key) {
|
|
5721
|
+
existingByKey.set(key, node);
|
|
5576
5722
|
}
|
|
5723
|
+
}
|
|
5724
|
+
const nextNodes = [];
|
|
5725
|
+
for (const [index, entry] of visibleTimeline.entries()) {
|
|
5726
|
+
const timelineIndex = transcriptWindow.start + index;
|
|
5727
|
+
const timelineKey = getAgentTimelineItemKey(
|
|
5728
|
+
entry,
|
|
5729
|
+
timelineIndex
|
|
5730
|
+
);
|
|
5731
|
+
const renderSignature = getAgentTimelineRenderSignature(
|
|
5732
|
+
agentTab,
|
|
5733
|
+
entry
|
|
5734
|
+
);
|
|
5735
|
+
const turnStart = timelineIndex > 0
|
|
5736
|
+
&& entry.type === 'message'
|
|
5737
|
+
&& String(entry.value?.role || '').toLowerCase()
|
|
5738
|
+
=== 'user';
|
|
5739
|
+
let node = existingByKey.get(timelineKey) || null;
|
|
5577
5740
|
if (node) {
|
|
5741
|
+
existingByKey.delete(timelineKey);
|
|
5578
5742
|
if (
|
|
5579
|
-
|
|
5580
|
-
&& entry.type === 'message'
|
|
5581
|
-
&& String(entry.value?.role || '').toLowerCase()
|
|
5582
|
-
=== 'user'
|
|
5743
|
+
node.dataset.renderSignature !== renderSignature
|
|
5583
5744
|
) {
|
|
5584
|
-
|
|
5745
|
+
this.disposeAgentTimelineNode(node);
|
|
5746
|
+
node = null;
|
|
5585
5747
|
}
|
|
5586
|
-
this.agentTranscript.appendChild(node);
|
|
5587
5748
|
}
|
|
5749
|
+
if (!node) {
|
|
5750
|
+
node = this.buildAgentTimelineNode(
|
|
5751
|
+
agentTab,
|
|
5752
|
+
entry,
|
|
5753
|
+
timelineIndex
|
|
5754
|
+
);
|
|
5755
|
+
}
|
|
5756
|
+
if (node) {
|
|
5757
|
+
node.dataset.timelineKey = timelineKey;
|
|
5758
|
+
node.dataset.renderSignature = renderSignature;
|
|
5759
|
+
node.classList.toggle('agent-turn-start', turnStart);
|
|
5760
|
+
nextNodes.push(node);
|
|
5761
|
+
}
|
|
5762
|
+
}
|
|
5763
|
+
for (const node of existingByKey.values()) {
|
|
5764
|
+
this.disposeAgentTimelineNode(node);
|
|
5588
5765
|
}
|
|
5766
|
+
this.applyAgentTranscriptNodeList(nextNodes);
|
|
5589
5767
|
}
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5768
|
+
this.rebuildAgentEmbeddedTerminalRegistry();
|
|
5769
|
+
if (options.preserveTranscriptAnchor) {
|
|
5770
|
+
const restored = this.restoreAgentTranscriptAnchor(
|
|
5771
|
+
options.preserveTranscriptAnchor
|
|
5772
|
+
);
|
|
5773
|
+
if (!restored) {
|
|
5774
|
+
this.agentTranscript.scrollTop = previousScrollTop;
|
|
5775
|
+
}
|
|
5776
|
+
} else if (shouldPinToBottom) {
|
|
5593
5777
|
this.agentTranscript.scrollTop = this.agentTranscript.scrollHeight;
|
|
5594
5778
|
agentTab.scrollToBottomOnNextRender = false;
|
|
5595
5779
|
} else {
|
|
5596
5780
|
this.agentTranscript.scrollTop = previousScrollTop;
|
|
5781
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
5597
5782
|
}
|
|
5598
5783
|
this.updateAgentScrollBottomButton();
|
|
5599
5784
|
this.rememberAgentTranscriptLayout();
|
|
@@ -5611,6 +5796,149 @@ class EditorManager {
|
|
|
5611
5796
|
this.scheduleAgentTranscriptViewportUpdate(shouldPinToBottom);
|
|
5612
5797
|
}
|
|
5613
5798
|
|
|
5799
|
+
buildAgentTimelineNode(agentTab, entry, timelineIndex) {
|
|
5800
|
+
if (!entry) {
|
|
5801
|
+
return null;
|
|
5802
|
+
}
|
|
5803
|
+
let node = null;
|
|
5804
|
+
if (entry.type === 'message') {
|
|
5805
|
+
node = this.buildAgentMessageNode(agentTab, entry.value);
|
|
5806
|
+
} else if (entry.type === 'tool') {
|
|
5807
|
+
node = this.buildAgentToolNode(agentTab, entry.value);
|
|
5808
|
+
} else if (entry.type === 'permission') {
|
|
5809
|
+
node = this.buildAgentPermissionNode(
|
|
5810
|
+
agentTab,
|
|
5811
|
+
entry.value
|
|
5812
|
+
);
|
|
5813
|
+
} else if (entry.type === 'plan') {
|
|
5814
|
+
node = this.buildAgentPlanHistoryNode(
|
|
5815
|
+
agentTab,
|
|
5816
|
+
entry.value
|
|
5817
|
+
);
|
|
5818
|
+
}
|
|
5819
|
+
if (!node) {
|
|
5820
|
+
return null;
|
|
5821
|
+
}
|
|
5822
|
+
node.dataset.timelineKey = getAgentTimelineItemKey(
|
|
5823
|
+
entry,
|
|
5824
|
+
timelineIndex
|
|
5825
|
+
);
|
|
5826
|
+
node.dataset.renderSignature = getAgentTimelineRenderSignature(
|
|
5827
|
+
agentTab,
|
|
5828
|
+
entry
|
|
5829
|
+
);
|
|
5830
|
+
return node;
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
applyAgentTranscriptNodeList(nextNodes) {
|
|
5834
|
+
if (!this.agentTranscript) {
|
|
5835
|
+
return;
|
|
5836
|
+
}
|
|
5837
|
+
const keepNodes = new Set(nextNodes);
|
|
5838
|
+
let cursor = this.agentTranscript.firstChild;
|
|
5839
|
+
for (const node of nextNodes) {
|
|
5840
|
+
if (node === cursor) {
|
|
5841
|
+
cursor = cursor?.nextSibling || null;
|
|
5842
|
+
continue;
|
|
5843
|
+
}
|
|
5844
|
+
this.agentTranscript.insertBefore(node, cursor);
|
|
5845
|
+
}
|
|
5846
|
+
for (const node of Array.from(this.agentTranscript.children)) {
|
|
5847
|
+
if (!keepNodes.has(node)) {
|
|
5848
|
+
node.remove();
|
|
5849
|
+
}
|
|
5850
|
+
}
|
|
5851
|
+
}
|
|
5852
|
+
|
|
5853
|
+
async loadOlderAgentTimeline(agentTab) {
|
|
5854
|
+
if (!agentTab || agentTab.historyWindowLoading) {
|
|
5855
|
+
return;
|
|
5856
|
+
}
|
|
5857
|
+
const timeline = getAgentTimelineItems(agentTab);
|
|
5858
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5859
|
+
agentTab,
|
|
5860
|
+
timeline.length
|
|
5861
|
+
);
|
|
5862
|
+
if (transcriptWindow.start <= 0) {
|
|
5863
|
+
return;
|
|
5864
|
+
}
|
|
5865
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
5866
|
+
const currentWindowSize = transcriptWindow.end
|
|
5867
|
+
- transcriptWindow.start;
|
|
5868
|
+
const step = Math.min(
|
|
5869
|
+
AGENT_TRANSCRIPT_WINDOW_STEP,
|
|
5870
|
+
transcriptWindow.start
|
|
5871
|
+
);
|
|
5872
|
+
const nextStart = Math.max(0, transcriptWindow.start - step);
|
|
5873
|
+
const anchor = this.captureAgentTranscriptAnchor(
|
|
5874
|
+
getAgentTimelineItemKey(
|
|
5875
|
+
timeline[transcriptWindow.start],
|
|
5876
|
+
transcriptWindow.start
|
|
5877
|
+
)
|
|
5878
|
+
);
|
|
5879
|
+
agentTab.historyWindowLoading = true;
|
|
5880
|
+
agentTab.historyWindowStart = nextStart;
|
|
5881
|
+
agentTab.historyWindowEnd = Math.min(
|
|
5882
|
+
timeline.length,
|
|
5883
|
+
nextStart + currentWindowSize
|
|
5884
|
+
);
|
|
5885
|
+
try {
|
|
5886
|
+
this.renderAgentPanel(agentTab, {
|
|
5887
|
+
reason: 'history-older',
|
|
5888
|
+
preserveTranscriptAnchor: anchor
|
|
5889
|
+
});
|
|
5890
|
+
} finally {
|
|
5891
|
+
agentTab.historyWindowLoading = false;
|
|
5892
|
+
}
|
|
5893
|
+
}
|
|
5894
|
+
|
|
5895
|
+
async loadNewerAgentTimeline(agentTab) {
|
|
5896
|
+
if (!agentTab || agentTab.historyWindowLoading) {
|
|
5897
|
+
return;
|
|
5898
|
+
}
|
|
5899
|
+
const timeline = getAgentTimelineItems(agentTab);
|
|
5900
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
5901
|
+
agentTab,
|
|
5902
|
+
timeline.length
|
|
5903
|
+
);
|
|
5904
|
+
if (transcriptWindow.end >= timeline.length) {
|
|
5905
|
+
return;
|
|
5906
|
+
}
|
|
5907
|
+
agentTab.scrollToBottomOnNextRender = false;
|
|
5908
|
+
const currentWindowSize = transcriptWindow.end
|
|
5909
|
+
- transcriptWindow.start;
|
|
5910
|
+
const step = Math.min(
|
|
5911
|
+
AGENT_TRANSCRIPT_WINDOW_STEP,
|
|
5912
|
+
timeline.length - transcriptWindow.end
|
|
5913
|
+
);
|
|
5914
|
+
const nextEnd = Math.min(
|
|
5915
|
+
timeline.length,
|
|
5916
|
+
transcriptWindow.end + step
|
|
5917
|
+
);
|
|
5918
|
+
const nextStart = Math.max(0, nextEnd - currentWindowSize);
|
|
5919
|
+
const anchorIndex = Math.max(
|
|
5920
|
+
transcriptWindow.start,
|
|
5921
|
+
transcriptWindow.end - 1
|
|
5922
|
+
);
|
|
5923
|
+
const anchor = this.captureAgentTranscriptAnchor(
|
|
5924
|
+
getAgentTimelineItemKey(
|
|
5925
|
+
timeline[anchorIndex],
|
|
5926
|
+
anchorIndex
|
|
5927
|
+
)
|
|
5928
|
+
);
|
|
5929
|
+
agentTab.historyWindowLoading = true;
|
|
5930
|
+
agentTab.historyWindowStart = nextStart;
|
|
5931
|
+
agentTab.historyWindowEnd = nextEnd;
|
|
5932
|
+
try {
|
|
5933
|
+
this.renderAgentPanel(agentTab, {
|
|
5934
|
+
reason: 'history-newer',
|
|
5935
|
+
preserveTranscriptAnchor: anchor
|
|
5936
|
+
});
|
|
5937
|
+
} finally {
|
|
5938
|
+
agentTab.historyWindowLoading = false;
|
|
5939
|
+
}
|
|
5940
|
+
}
|
|
5941
|
+
|
|
5614
5942
|
refreshAgentTimelineTimestamps() {
|
|
5615
5943
|
if (!this.agentContainer || this.agentContainer.style.display === 'none') {
|
|
5616
5944
|
return;
|
|
@@ -5912,8 +6240,23 @@ class EditorManager {
|
|
|
5912
6240
|
message.role === 'assistant'
|
|
5913
6241
|
&& message.kind === 'message'
|
|
5914
6242
|
) {
|
|
5915
|
-
|
|
5916
|
-
|
|
6243
|
+
const cachedMarkdown = getAgentMessageMarkdownCache(message);
|
|
6244
|
+
if (
|
|
6245
|
+
!agentTab?.busy
|
|
6246
|
+
&& !isAgentMessageStreaming(agentTab, message)
|
|
6247
|
+
&& cachedMarkdown
|
|
6248
|
+
) {
|
|
6249
|
+
body.classList.add('markdown');
|
|
6250
|
+
body.innerHTML = cachedMarkdown;
|
|
6251
|
+
} else {
|
|
6252
|
+
body.classList.add('plain');
|
|
6253
|
+
body.textContent = message.text || '';
|
|
6254
|
+
void this.enhanceAgentMarkdownBody(
|
|
6255
|
+
agentTab,
|
|
6256
|
+
message,
|
|
6257
|
+
body
|
|
6258
|
+
);
|
|
6259
|
+
}
|
|
5917
6260
|
} else {
|
|
5918
6261
|
body.classList.add('plain');
|
|
5919
6262
|
body.textContent = message.text || '';
|
|
@@ -5999,9 +6342,27 @@ class EditorManager {
|
|
|
5999
6342
|
toolStatusClass
|
|
6000
6343
|
);
|
|
6001
6344
|
details.appendChild(summary);
|
|
6002
|
-
|
|
6003
|
-
|
|
6004
|
-
)
|
|
6345
|
+
const bodyHost = document.createElement('div');
|
|
6346
|
+
bodyHost.className = 'agent-tool-call-section-content';
|
|
6347
|
+
const mountBody = () => {
|
|
6348
|
+
if (bodyHost.dataset.mounted === 'true') {
|
|
6349
|
+
return;
|
|
6350
|
+
}
|
|
6351
|
+
bodyHost.dataset.mounted = 'true';
|
|
6352
|
+
bodyHost.appendChild(
|
|
6353
|
+
this.buildAgentSectionBody(details, section)
|
|
6354
|
+
);
|
|
6355
|
+
};
|
|
6356
|
+
details.appendChild(bodyHost);
|
|
6357
|
+
if (details.open) {
|
|
6358
|
+
queueMicrotask(mountBody);
|
|
6359
|
+
} else {
|
|
6360
|
+
details.addEventListener('toggle', () => {
|
|
6361
|
+
if (details.open) {
|
|
6362
|
+
mountBody();
|
|
6363
|
+
}
|
|
6364
|
+
}, { once: true });
|
|
6365
|
+
}
|
|
6005
6366
|
sectionContainer.appendChild(details);
|
|
6006
6367
|
}
|
|
6007
6368
|
node.appendChild(sectionContainer);
|
|
@@ -6093,9 +6454,27 @@ class EditorManager {
|
|
|
6093
6454
|
permission.status || 'pending'
|
|
6094
6455
|
);
|
|
6095
6456
|
details.appendChild(summary);
|
|
6096
|
-
|
|
6097
|
-
|
|
6098
|
-
)
|
|
6457
|
+
const bodyHost = document.createElement('div');
|
|
6458
|
+
bodyHost.className = 'agent-tool-call-section-content';
|
|
6459
|
+
const mountBody = () => {
|
|
6460
|
+
if (bodyHost.dataset.mounted === 'true') {
|
|
6461
|
+
return;
|
|
6462
|
+
}
|
|
6463
|
+
bodyHost.dataset.mounted = 'true';
|
|
6464
|
+
bodyHost.appendChild(
|
|
6465
|
+
this.buildAgentSectionBody(details, section)
|
|
6466
|
+
);
|
|
6467
|
+
};
|
|
6468
|
+
details.appendChild(bodyHost);
|
|
6469
|
+
if (details.open) {
|
|
6470
|
+
queueMicrotask(mountBody);
|
|
6471
|
+
} else {
|
|
6472
|
+
details.addEventListener('toggle', () => {
|
|
6473
|
+
if (details.open) {
|
|
6474
|
+
mountBody();
|
|
6475
|
+
}
|
|
6476
|
+
}, { once: true });
|
|
6477
|
+
}
|
|
6099
6478
|
sectionContainer.appendChild(details);
|
|
6100
6479
|
}
|
|
6101
6480
|
card.appendChild(sectionContainer);
|
|
@@ -6158,16 +6537,72 @@ class EditorManager {
|
|
|
6158
6537
|
|
|
6159
6538
|
disposeAgentEmbeddedEditors() {
|
|
6160
6539
|
this.agentEmbeddedTerminals.clear();
|
|
6161
|
-
|
|
6162
|
-
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6540
|
+
if (!this.agentTranscript) {
|
|
6541
|
+
this.agentEmbeddedEditors = [];
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6544
|
+
for (const node of Array.from(this.agentTranscript.children)) {
|
|
6545
|
+
this.disposeAgentTimelineNode(node);
|
|
6167
6546
|
}
|
|
6168
6547
|
this.agentEmbeddedEditors = [];
|
|
6169
6548
|
}
|
|
6170
6549
|
|
|
6550
|
+
trackAgentTimelineDisposable(node, disposable) {
|
|
6551
|
+
if (!node || !disposable) {
|
|
6552
|
+
return;
|
|
6553
|
+
}
|
|
6554
|
+
if (!Array.isArray(node.__agentDisposables)) {
|
|
6555
|
+
node.__agentDisposables = [];
|
|
6556
|
+
}
|
|
6557
|
+
node.__agentDisposables.push(disposable);
|
|
6558
|
+
}
|
|
6559
|
+
|
|
6560
|
+
disposeAgentTimelineNode(node) {
|
|
6561
|
+
if (!node || typeof node.querySelectorAll !== 'function') {
|
|
6562
|
+
return;
|
|
6563
|
+
}
|
|
6564
|
+
const ownedNodes = [
|
|
6565
|
+
node,
|
|
6566
|
+
...Array.from(node.querySelectorAll('*'))
|
|
6567
|
+
];
|
|
6568
|
+
for (const ownedNode of ownedNodes) {
|
|
6569
|
+
const disposables = Array.isArray(ownedNode.__agentDisposables)
|
|
6570
|
+
? ownedNode.__agentDisposables
|
|
6571
|
+
: [];
|
|
6572
|
+
for (const disposable of disposables) {
|
|
6573
|
+
try {
|
|
6574
|
+
disposable.dispose();
|
|
6575
|
+
} catch {
|
|
6576
|
+
// Ignore embedded editor disposal failures.
|
|
6577
|
+
}
|
|
6578
|
+
}
|
|
6579
|
+
ownedNode.__agentDisposables = [];
|
|
6580
|
+
if (ownedNode.__agentTerminalBinding) {
|
|
6581
|
+
ownedNode.__agentTerminalBinding = null;
|
|
6582
|
+
}
|
|
6583
|
+
}
|
|
6584
|
+
}
|
|
6585
|
+
|
|
6586
|
+
rebuildAgentEmbeddedTerminalRegistry() {
|
|
6587
|
+
this.agentEmbeddedTerminals.clear();
|
|
6588
|
+
if (!this.agentTranscript) {
|
|
6589
|
+
return;
|
|
6590
|
+
}
|
|
6591
|
+
const terminalHosts = this.agentTranscript.querySelectorAll(
|
|
6592
|
+
'.agent-tool-call-terminal-host'
|
|
6593
|
+
);
|
|
6594
|
+
for (const host of terminalHosts) {
|
|
6595
|
+
const binding = host.__agentTerminalBinding;
|
|
6596
|
+
if (!binding?.terminalId) {
|
|
6597
|
+
continue;
|
|
6598
|
+
}
|
|
6599
|
+
if (!this.agentEmbeddedTerminals.has(binding.terminalId)) {
|
|
6600
|
+
this.agentEmbeddedTerminals.set(binding.terminalId, []);
|
|
6601
|
+
}
|
|
6602
|
+
this.agentEmbeddedTerminals.get(binding.terminalId).push(binding);
|
|
6603
|
+
}
|
|
6604
|
+
}
|
|
6605
|
+
|
|
6171
6606
|
buildAgentSectionBody(details, section) {
|
|
6172
6607
|
if (
|
|
6173
6608
|
section?.kind === 'diff'
|
|
@@ -6231,7 +6666,8 @@ class EditorManager {
|
|
|
6231
6666
|
+ '"JetBrains Mono", Menlo, Consolas, monospace'
|
|
6232
6667
|
}
|
|
6233
6668
|
);
|
|
6234
|
-
this.
|
|
6669
|
+
this.trackAgentTimelineDisposable(host, editor);
|
|
6670
|
+
this.trackAgentTimelineDisposable(host, model);
|
|
6235
6671
|
details.addEventListener('toggle', () => {
|
|
6236
6672
|
if (details.open) {
|
|
6237
6673
|
requestAnimationFrame(() => {
|
|
@@ -6318,20 +6754,19 @@ class EditorManager {
|
|
|
6318
6754
|
}
|
|
6319
6755
|
});
|
|
6320
6756
|
if (terminalId) {
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
terminal: embeddedTerm,
|
|
6330
|
-
fitAddon,
|
|
6757
|
+
host.__agentTerminalBinding = {
|
|
6758
|
+
terminalId,
|
|
6759
|
+
meta,
|
|
6760
|
+
header,
|
|
6761
|
+
openButton,
|
|
6762
|
+
terminalNode,
|
|
6763
|
+
terminal: embeddedTerm,
|
|
6764
|
+
fitAddon,
|
|
6331
6765
|
layout: layoutTerminal
|
|
6332
|
-
}
|
|
6766
|
+
};
|
|
6333
6767
|
}
|
|
6334
|
-
this.
|
|
6768
|
+
this.trackAgentTimelineDisposable(host, embeddedTerm);
|
|
6769
|
+
this.trackAgentTimelineDisposable(host, fitAddon);
|
|
6335
6770
|
|
|
6336
6771
|
return host;
|
|
6337
6772
|
}
|
|
@@ -6445,11 +6880,9 @@ class EditorManager {
|
|
|
6445
6880
|
original: originalModel,
|
|
6446
6881
|
modified: modifiedModel
|
|
6447
6882
|
});
|
|
6448
|
-
this.
|
|
6449
|
-
|
|
6450
|
-
|
|
6451
|
-
modifiedModel
|
|
6452
|
-
);
|
|
6883
|
+
this.trackAgentTimelineDisposable(host, diffEditor);
|
|
6884
|
+
this.trackAgentTimelineDisposable(host, originalModel);
|
|
6885
|
+
this.trackAgentTimelineDisposable(host, modifiedModel);
|
|
6453
6886
|
details.addEventListener('toggle', () => {
|
|
6454
6887
|
if (details.open) {
|
|
6455
6888
|
requestAnimationFrame(() => {
|
|
@@ -6790,6 +7223,51 @@ class EditorManager {
|
|
|
6790
7223
|
};
|
|
6791
7224
|
}
|
|
6792
7225
|
|
|
7226
|
+
findAgentTranscriptNodeByKey(timelineKey = '') {
|
|
7227
|
+
if (!this.agentTranscript || !timelineKey) {
|
|
7228
|
+
return null;
|
|
7229
|
+
}
|
|
7230
|
+
for (const node of this.agentTranscript.children) {
|
|
7231
|
+
if (node?.dataset?.timelineKey === timelineKey) {
|
|
7232
|
+
return node;
|
|
7233
|
+
}
|
|
7234
|
+
}
|
|
7235
|
+
return null;
|
|
7236
|
+
}
|
|
7237
|
+
|
|
7238
|
+
captureAgentTranscriptAnchor(timelineKey = '') {
|
|
7239
|
+
const node = this.findAgentTranscriptNodeByKey(timelineKey);
|
|
7240
|
+
if (!node || !this.agentTranscript) {
|
|
7241
|
+
return null;
|
|
7242
|
+
}
|
|
7243
|
+
return {
|
|
7244
|
+
timelineKey,
|
|
7245
|
+
scrollTop: this.agentTranscript.scrollTop,
|
|
7246
|
+
offsetTop: node.offsetTop
|
|
7247
|
+
};
|
|
7248
|
+
}
|
|
7249
|
+
|
|
7250
|
+
restoreAgentTranscriptAnchor(anchor = null) {
|
|
7251
|
+
if (!anchor || !this.agentTranscript) {
|
|
7252
|
+
return false;
|
|
7253
|
+
}
|
|
7254
|
+
const node = this.findAgentTranscriptNodeByKey(
|
|
7255
|
+
anchor.timelineKey || ''
|
|
7256
|
+
);
|
|
7257
|
+
if (!node) {
|
|
7258
|
+
return false;
|
|
7259
|
+
}
|
|
7260
|
+
const previousOffsetTop = Number.isFinite(anchor.offsetTop)
|
|
7261
|
+
? anchor.offsetTop
|
|
7262
|
+
: 0;
|
|
7263
|
+
const previousScrollTop = Number.isFinite(anchor.scrollTop)
|
|
7264
|
+
? anchor.scrollTop
|
|
7265
|
+
: 0;
|
|
7266
|
+
this.agentTranscript.scrollTop = previousScrollTop
|
|
7267
|
+
+ (node.offsetTop - previousOffsetTop);
|
|
7268
|
+
return true;
|
|
7269
|
+
}
|
|
7270
|
+
|
|
6793
7271
|
rememberAgentTranscriptLayout() {
|
|
6794
7272
|
this.agentTranscriptLayout = this.captureAgentTranscriptLayout();
|
|
6795
7273
|
}
|
|
@@ -6810,6 +7288,32 @@ class EditorManager {
|
|
|
6810
7288
|
}
|
|
6811
7289
|
|
|
6812
7290
|
scrollAgentTranscriptToBottom() {
|
|
7291
|
+
const activeTab = getActiveAgentTab();
|
|
7292
|
+
if (activeTab) {
|
|
7293
|
+
const total = getAgentTimelineItems(activeTab).length;
|
|
7294
|
+
const transcriptWindow = getAgentTranscriptWindow(
|
|
7295
|
+
activeTab,
|
|
7296
|
+
total,
|
|
7297
|
+
{ pinToBottom: false }
|
|
7298
|
+
);
|
|
7299
|
+
const latestWindow = getAgentTranscriptWindow(
|
|
7300
|
+
null,
|
|
7301
|
+
total,
|
|
7302
|
+
{ pinToBottom: true }
|
|
7303
|
+
);
|
|
7304
|
+
const alreadyLatest = transcriptWindow.start === latestWindow.start
|
|
7305
|
+
&& transcriptWindow.end === latestWindow.end;
|
|
7306
|
+
if (!alreadyLatest) {
|
|
7307
|
+
activeTab.historyWindowStart = latestWindow.start;
|
|
7308
|
+
activeTab.historyWindowEnd = latestWindow.end;
|
|
7309
|
+
activeTab.scrollToBottomOnNextRender = true;
|
|
7310
|
+
this.renderAgentPanel(activeTab, {
|
|
7311
|
+
reason: 'scroll-latest'
|
|
7312
|
+
});
|
|
7313
|
+
return;
|
|
7314
|
+
}
|
|
7315
|
+
activeTab.scrollToBottomOnNextRender = false;
|
|
7316
|
+
}
|
|
6813
7317
|
if (!this.agentTranscript) return;
|
|
6814
7318
|
this.agentTranscript.scrollTop = this.agentTranscript.scrollHeight;
|
|
6815
7319
|
this.updateAgentScrollBottomButton();
|
|
@@ -8315,10 +8819,11 @@ class Session {
|
|
|
8315
8819
|
|
|
8316
8820
|
const titleEl = tab.querySelector('.title');
|
|
8317
8821
|
const titleTextEl = tab.querySelector('.tab-title-text');
|
|
8822
|
+
const displayTitle = formatWorkspaceTabTitle(this.title);
|
|
8318
8823
|
if (titleTextEl) {
|
|
8319
|
-
titleTextEl.textContent =
|
|
8824
|
+
titleTextEl.textContent = displayTitle;
|
|
8320
8825
|
} else if (titleEl) {
|
|
8321
|
-
titleEl.textContent =
|
|
8826
|
+
titleEl.textContent = displayTitle;
|
|
8322
8827
|
}
|
|
8323
8828
|
|
|
8324
8829
|
const titleIconEl = tab.querySelector('.tab-status-icon');
|
|
@@ -8747,6 +9252,10 @@ class AgentTab {
|
|
|
8747
9252
|
this.scrollToBottomOnNextRender = true;
|
|
8748
9253
|
this.busySyncTimer = null;
|
|
8749
9254
|
this.planHistory = [];
|
|
9255
|
+
this.historyWindowStart = -1;
|
|
9256
|
+
this.historyWindowEnd = -1;
|
|
9257
|
+
this.historyWindowLoading = false;
|
|
9258
|
+
this.streamingAssistantStreamKey = '';
|
|
8750
9259
|
this.resumeSessions = [];
|
|
8751
9260
|
this.resumeSessionsLoadedAt = 0;
|
|
8752
9261
|
this.resumeSessionsPromise = null;
|
|
@@ -8954,9 +9463,23 @@ class AgentTab {
|
|
|
8954
9463
|
break;
|
|
8955
9464
|
case 'message_open':
|
|
8956
9465
|
this.#upsertMessage(message.message);
|
|
9466
|
+
if (
|
|
9467
|
+
message.message?.role === 'assistant'
|
|
9468
|
+
&& message.message?.kind === 'message'
|
|
9469
|
+
&& typeof message.message?.streamKey === 'string'
|
|
9470
|
+
) {
|
|
9471
|
+
this.streamingAssistantStreamKey = message.message.streamKey;
|
|
9472
|
+
}
|
|
8957
9473
|
break;
|
|
8958
9474
|
case 'message_chunk':
|
|
8959
9475
|
this.#appendChunk(message);
|
|
9476
|
+
if (
|
|
9477
|
+
message.role === 'assistant'
|
|
9478
|
+
&& message.kind === 'message'
|
|
9479
|
+
&& typeof message.streamKey === 'string'
|
|
9480
|
+
) {
|
|
9481
|
+
this.streamingAssistantStreamKey = message.streamKey;
|
|
9482
|
+
}
|
|
8960
9483
|
break;
|
|
8961
9484
|
case 'session_update':
|
|
8962
9485
|
this.#applySessionUpdate(message.update || {});
|
|
@@ -9052,6 +9575,9 @@ class AgentTab {
|
|
|
9052
9575
|
default:
|
|
9053
9576
|
break;
|
|
9054
9577
|
}
|
|
9578
|
+
if (!this.busy) {
|
|
9579
|
+
this.streamingAssistantStreamKey = '';
|
|
9580
|
+
}
|
|
9055
9581
|
const shouldAutostartQueuedPrompt = (
|
|
9056
9582
|
wasBusy
|
|
9057
9583
|
&& !this.busy
|
|
@@ -9305,7 +9831,7 @@ class AgentTab {
|
|
|
9305
9831
|
const byId = this.messages.findIndex(
|
|
9306
9832
|
(message) => message.id === candidate.id
|
|
9307
9833
|
);
|
|
9308
|
-
|
|
9834
|
+
return byId;
|
|
9309
9835
|
}
|
|
9310
9836
|
if (!candidate.streamKey) return -1;
|
|
9311
9837
|
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -9325,39 +9851,51 @@ class AgentTab {
|
|
|
9325
9851
|
if (!message) return;
|
|
9326
9852
|
const index = this.#findMessageIndex(message);
|
|
9327
9853
|
if (index === -1) {
|
|
9328
|
-
this
|
|
9854
|
+
const nextMessage = this.#normalizeMessage(message);
|
|
9855
|
+
clearAgentMessageMarkdownCache(nextMessage);
|
|
9856
|
+
this.messages.push(nextMessage);
|
|
9329
9857
|
return;
|
|
9330
9858
|
}
|
|
9331
9859
|
|
|
9332
9860
|
const previous = this.messages[index];
|
|
9333
9861
|
const nextMessage = this.#normalizeMessage(message, previous.order);
|
|
9862
|
+
const mergedText = selectAgentMessageText(previous.text, nextMessage.text);
|
|
9334
9863
|
this.messages[index] = {
|
|
9335
9864
|
...previous,
|
|
9336
9865
|
...nextMessage,
|
|
9337
9866
|
createdAt: nextMessage.createdAt || previous.createdAt || '',
|
|
9338
|
-
text:
|
|
9867
|
+
text: mergedText
|
|
9339
9868
|
};
|
|
9869
|
+
if (mergedText !== (previous.text || '')) {
|
|
9870
|
+
clearAgentMessageMarkdownCache(this.messages[index]);
|
|
9871
|
+
}
|
|
9340
9872
|
}
|
|
9341
9873
|
|
|
9342
9874
|
#appendChunk(message) {
|
|
9343
9875
|
const index = this.#findMessageIndex(message);
|
|
9344
9876
|
if (index !== -1) {
|
|
9345
9877
|
const existing = this.messages[index];
|
|
9346
|
-
|
|
9878
|
+
const nextText = mergeAgentMessageText(
|
|
9347
9879
|
existing.text || '',
|
|
9348
9880
|
message.text || ''
|
|
9349
9881
|
);
|
|
9882
|
+
if (nextText !== existing.text) {
|
|
9883
|
+
existing.text = nextText;
|
|
9884
|
+
clearAgentMessageMarkdownCache(existing);
|
|
9885
|
+
}
|
|
9350
9886
|
return;
|
|
9351
9887
|
}
|
|
9352
9888
|
|
|
9353
|
-
this
|
|
9889
|
+
const nextMessage = this.#normalizeMessage({
|
|
9354
9890
|
id: crypto.randomUUID(),
|
|
9355
9891
|
streamKey: message.streamKey,
|
|
9356
9892
|
role: message.role || 'assistant',
|
|
9357
9893
|
kind: message.kind || 'message',
|
|
9358
9894
|
text: message.text || '',
|
|
9359
9895
|
createdAt: new Date().toISOString()
|
|
9360
|
-
})
|
|
9896
|
+
});
|
|
9897
|
+
clearAgentMessageMarkdownCache(nextMessage);
|
|
9898
|
+
this.messages.push(nextMessage);
|
|
9361
9899
|
}
|
|
9362
9900
|
|
|
9363
9901
|
#applySessionUpdate(update) {
|
|
@@ -9515,9 +10053,21 @@ class AgentTab {
|
|
|
9515
10053
|
this.busy = typeof data.busy === 'boolean' ? data.busy : this.busy;
|
|
9516
10054
|
this.errorMessage = data.errorMessage || this.errorMessage || '';
|
|
9517
10055
|
this.currentModeId = data.currentModeId || this.currentModeId || '';
|
|
10056
|
+
this.availableModes = Array.isArray(data.availableModes)
|
|
10057
|
+
? data.availableModes
|
|
10058
|
+
: this.availableModes;
|
|
10059
|
+
this.availableCommands = Array.isArray(data.availableCommands)
|
|
10060
|
+
? data.availableCommands
|
|
10061
|
+
: this.availableCommands;
|
|
10062
|
+
this.configOptions = Array.isArray(data.configOptions)
|
|
10063
|
+
? data.configOptions
|
|
10064
|
+
: this.configOptions;
|
|
9518
10065
|
this.sessionCapabilities = normalizeAgentSessionCapabilities(
|
|
9519
10066
|
data.sessionCapabilities || this.sessionCapabilities
|
|
9520
10067
|
);
|
|
10068
|
+
if (data.usage) {
|
|
10069
|
+
this.usage = this.#normalizeUsageState(data.usage);
|
|
10070
|
+
}
|
|
9521
10071
|
const nextSession = this.getLinkedSession();
|
|
9522
10072
|
const nextSnapshot = JSON.stringify({
|
|
9523
10073
|
runtimeId: this.runtimeId || '',
|
|
@@ -10484,6 +11034,19 @@ function mergeAgentMessageText(previousText, chunkText) {
|
|
|
10484
11034
|
const chunk = String(chunkText || '');
|
|
10485
11035
|
if (!previous) return chunk;
|
|
10486
11036
|
if (!chunk) return previous;
|
|
11037
|
+
if (previous === chunk) return previous;
|
|
11038
|
+
if (chunk.startsWith(previous)) {
|
|
11039
|
+
return chunk;
|
|
11040
|
+
}
|
|
11041
|
+
if (previous.startsWith(chunk)) {
|
|
11042
|
+
return previous;
|
|
11043
|
+
}
|
|
11044
|
+
const maxOverlap = Math.min(previous.length, chunk.length, 2048);
|
|
11045
|
+
for (let overlap = maxOverlap; overlap >= 2; overlap -= 1) {
|
|
11046
|
+
if (previous.slice(-overlap) === chunk.slice(0, overlap)) {
|
|
11047
|
+
return `${previous}${chunk.slice(overlap)}`;
|
|
11048
|
+
}
|
|
11049
|
+
}
|
|
10487
11050
|
if (/\s$/.test(previous) || /^\s/.test(chunk)) {
|
|
10488
11051
|
return `${previous}${chunk}`;
|
|
10489
11052
|
}
|
|
@@ -10880,6 +11443,194 @@ function getAgentTimelineItems(agentTab) {
|
|
|
10880
11443
|
return items;
|
|
10881
11444
|
}
|
|
10882
11445
|
|
|
11446
|
+
function getAgentTimelineItemKey(entry, absoluteIndex = 0) {
|
|
11447
|
+
if (!entry) {
|
|
11448
|
+
return `unknown:${absoluteIndex}`;
|
|
11449
|
+
}
|
|
11450
|
+
const order = Number.isFinite(entry.order) ? entry.order : -1;
|
|
11451
|
+
return `${entry.type}:${order}:${absoluteIndex}`;
|
|
11452
|
+
}
|
|
11453
|
+
|
|
11454
|
+
function clearAgentMessageMarkdownCache(message) {
|
|
11455
|
+
if (!message || typeof message !== 'object') {
|
|
11456
|
+
return;
|
|
11457
|
+
}
|
|
11458
|
+
delete message.markdownRenderSource;
|
|
11459
|
+
delete message.markdownRenderHtml;
|
|
11460
|
+
}
|
|
11461
|
+
|
|
11462
|
+
function getAgentMessageMarkdownCache(message) {
|
|
11463
|
+
if (!message || typeof message !== 'object') {
|
|
11464
|
+
return '';
|
|
11465
|
+
}
|
|
11466
|
+
const source = typeof message.text === 'string' ? message.text : '';
|
|
11467
|
+
if (
|
|
11468
|
+
typeof message.markdownRenderSource !== 'string'
|
|
11469
|
+
|| message.markdownRenderSource !== source
|
|
11470
|
+
) {
|
|
11471
|
+
return '';
|
|
11472
|
+
}
|
|
11473
|
+
return typeof message.markdownRenderHtml === 'string'
|
|
11474
|
+
? message.markdownRenderHtml
|
|
11475
|
+
: '';
|
|
11476
|
+
}
|
|
11477
|
+
|
|
11478
|
+
function isAgentMessageStreaming(agentTab, message) {
|
|
11479
|
+
if (!agentTab || !message) {
|
|
11480
|
+
return false;
|
|
11481
|
+
}
|
|
11482
|
+
return !!(
|
|
11483
|
+
agentTab.busy
|
|
11484
|
+
&& message.role === 'assistant'
|
|
11485
|
+
&& message.kind === 'message'
|
|
11486
|
+
&& message.streamKey
|
|
11487
|
+
&& message.streamKey === agentTab.streamingAssistantStreamKey
|
|
11488
|
+
);
|
|
11489
|
+
}
|
|
11490
|
+
|
|
11491
|
+
function hashUiText(value) {
|
|
11492
|
+
const text = String(value || '');
|
|
11493
|
+
let hash = 2166136261;
|
|
11494
|
+
for (let index = 0; index < text.length; index += 1) {
|
|
11495
|
+
hash ^= text.charCodeAt(index);
|
|
11496
|
+
hash = Math.imul(hash, 16777619);
|
|
11497
|
+
}
|
|
11498
|
+
return (hash >>> 0).toString(16);
|
|
11499
|
+
}
|
|
11500
|
+
|
|
11501
|
+
function getAgentTimelineEntrySignature(entry) {
|
|
11502
|
+
if (!entry || typeof entry !== 'object') {
|
|
11503
|
+
return 'unknown';
|
|
11504
|
+
}
|
|
11505
|
+
const type = String(entry.type || '');
|
|
11506
|
+
const value = entry.value;
|
|
11507
|
+
if (type === 'message') {
|
|
11508
|
+
const attachments = Array.isArray(value?.attachments)
|
|
11509
|
+
? value.attachments.map((attachment) => [
|
|
11510
|
+
attachment?.kind || '',
|
|
11511
|
+
attachment?.name || '',
|
|
11512
|
+
attachment?.path || '',
|
|
11513
|
+
attachment?.url || '',
|
|
11514
|
+
attachment?.size || 0,
|
|
11515
|
+
attachment?.lastModified || 0
|
|
11516
|
+
])
|
|
11517
|
+
: [];
|
|
11518
|
+
return [
|
|
11519
|
+
type,
|
|
11520
|
+
value?.role || '',
|
|
11521
|
+
value?.kind || '',
|
|
11522
|
+
value?.createdAt || '',
|
|
11523
|
+
hashUiText(value?.text || ''),
|
|
11524
|
+
hashUiText(JSON.stringify(attachments))
|
|
11525
|
+
].join(':');
|
|
11526
|
+
}
|
|
11527
|
+
return `${type}:${hashUiText(JSON.stringify(value || null))}`;
|
|
11528
|
+
}
|
|
11529
|
+
|
|
11530
|
+
function getAgentTimelineRenderSignature(agentTab, entry) {
|
|
11531
|
+
const base = getAgentTimelineEntrySignature(entry);
|
|
11532
|
+
if (entry?.type !== 'message') {
|
|
11533
|
+
return base;
|
|
11534
|
+
}
|
|
11535
|
+
return `${base}:${
|
|
11536
|
+
isAgentMessageStreaming(agentTab, entry.value)
|
|
11537
|
+
? 'streaming'
|
|
11538
|
+
: 'settled'
|
|
11539
|
+
}`;
|
|
11540
|
+
}
|
|
11541
|
+
|
|
11542
|
+
function formatWorkspaceTabTitle(
|
|
11543
|
+
value,
|
|
11544
|
+
maxLength = WORKSPACE_TAB_TITLE_MAX_LENGTH
|
|
11545
|
+
) {
|
|
11546
|
+
const text = String(value || '');
|
|
11547
|
+
const characters = Array.from(text);
|
|
11548
|
+
if (characters.length <= maxLength) {
|
|
11549
|
+
return text;
|
|
11550
|
+
}
|
|
11551
|
+
if (maxLength <= 3) {
|
|
11552
|
+
return '.'.repeat(Math.max(0, maxLength));
|
|
11553
|
+
}
|
|
11554
|
+
return `${characters.slice(0, maxLength - 3).join('')}...`;
|
|
11555
|
+
}
|
|
11556
|
+
|
|
11557
|
+
function getAgentTranscriptWindow(
|
|
11558
|
+
agentTab,
|
|
11559
|
+
totalCount = 0,
|
|
11560
|
+
options = {}
|
|
11561
|
+
) {
|
|
11562
|
+
const total = Number.isFinite(totalCount)
|
|
11563
|
+
? Math.max(0, totalCount)
|
|
11564
|
+
: 0;
|
|
11565
|
+
const windowSize = Math.min(total, AGENT_TRANSCRIPT_INITIAL_VISIBLE_BLOCKS);
|
|
11566
|
+
const latestStart = Math.max(0, total - windowSize);
|
|
11567
|
+
const latestWindow = {
|
|
11568
|
+
start: latestStart,
|
|
11569
|
+
end: total
|
|
11570
|
+
};
|
|
11571
|
+
if (!agentTab) {
|
|
11572
|
+
return latestWindow;
|
|
11573
|
+
}
|
|
11574
|
+
if (windowSize === 0) {
|
|
11575
|
+
agentTab.historyWindowStart = 0;
|
|
11576
|
+
agentTab.historyWindowEnd = 0;
|
|
11577
|
+
return { start: 0, end: 0 };
|
|
11578
|
+
}
|
|
11579
|
+
if (options.pinToBottom) {
|
|
11580
|
+
agentTab.historyWindowStart = latestWindow.start;
|
|
11581
|
+
agentTab.historyWindowEnd = latestWindow.end;
|
|
11582
|
+
return latestWindow;
|
|
11583
|
+
}
|
|
11584
|
+
let start = Number.isFinite(agentTab.historyWindowStart)
|
|
11585
|
+
? Math.max(0, Math.floor(agentTab.historyWindowStart))
|
|
11586
|
+
: latestWindow.start;
|
|
11587
|
+
let end = Number.isFinite(agentTab.historyWindowEnd)
|
|
11588
|
+
? Math.max(start, Math.floor(agentTab.historyWindowEnd))
|
|
11589
|
+
: latestWindow.end;
|
|
11590
|
+
if (end > total) {
|
|
11591
|
+
end = total;
|
|
11592
|
+
}
|
|
11593
|
+
if (end - start !== windowSize) {
|
|
11594
|
+
if (total <= windowSize) {
|
|
11595
|
+
start = 0;
|
|
11596
|
+
end = total;
|
|
11597
|
+
} else if (end >= total) {
|
|
11598
|
+
end = total;
|
|
11599
|
+
start = latestWindow.start;
|
|
11600
|
+
} else if (start <= 0) {
|
|
11601
|
+
start = 0;
|
|
11602
|
+
end = windowSize;
|
|
11603
|
+
} else {
|
|
11604
|
+
end = Math.min(total, start + windowSize);
|
|
11605
|
+
start = Math.max(0, end - windowSize);
|
|
11606
|
+
}
|
|
11607
|
+
}
|
|
11608
|
+
agentTab.historyWindowStart = start;
|
|
11609
|
+
agentTab.historyWindowEnd = end;
|
|
11610
|
+
return { start, end };
|
|
11611
|
+
}
|
|
11612
|
+
|
|
11613
|
+
function isAgentTranscriptWindowNearLatest(agentTab, totalCount = 0) {
|
|
11614
|
+
const total = Number.isFinite(totalCount)
|
|
11615
|
+
? Math.max(0, totalCount)
|
|
11616
|
+
: 0;
|
|
11617
|
+
if (!agentTab) {
|
|
11618
|
+
return true;
|
|
11619
|
+
}
|
|
11620
|
+
if (
|
|
11621
|
+
!Number.isFinite(agentTab.historyWindowStart)
|
|
11622
|
+
|| !Number.isFinite(agentTab.historyWindowEnd)
|
|
11623
|
+
|| agentTab.historyWindowStart < 0
|
|
11624
|
+
|| agentTab.historyWindowEnd < 0
|
|
11625
|
+
) {
|
|
11626
|
+
return true;
|
|
11627
|
+
}
|
|
11628
|
+
return agentTab.historyWindowEnd >= Math.max(
|
|
11629
|
+
0,
|
|
11630
|
+
total - AGENT_TRANSCRIPT_FOLLOW_LATEST_TOLERANCE
|
|
11631
|
+
);
|
|
11632
|
+
}
|
|
11633
|
+
|
|
10883
11634
|
function normalizePlanStatusClass(status = '') {
|
|
10884
11635
|
const value = String(status || '').toLowerCase();
|
|
10885
11636
|
if (value === 'completed') return 'completed';
|
|
@@ -11700,119 +12451,6 @@ function escapeHtml(text) {
|
|
|
11700
12451
|
.replaceAll("'", ''');
|
|
11701
12452
|
}
|
|
11702
12453
|
|
|
11703
|
-
function renderAgentInlineMarkdown(text) {
|
|
11704
|
-
const source = String(text || '');
|
|
11705
|
-
const pattern = /(`[^`]+`)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
11706
|
-
let result = '';
|
|
11707
|
-
let lastIndex = 0;
|
|
11708
|
-
for (const match of source.matchAll(pattern)) {
|
|
11709
|
-
const [token, codeToken, , linkLabel, linkHref] = match;
|
|
11710
|
-
result += escapeHtml(source.slice(lastIndex, match.index));
|
|
11711
|
-
if (codeToken) {
|
|
11712
|
-
result += `<code>${escapeHtml(codeToken.slice(1, -1))}</code>`;
|
|
11713
|
-
} else if (linkLabel && linkHref) {
|
|
11714
|
-
result += `<a href="${escapeHtml(linkHref)}">${escapeHtml(linkLabel)}</a>`;
|
|
11715
|
-
} else {
|
|
11716
|
-
result += escapeHtml(token);
|
|
11717
|
-
}
|
|
11718
|
-
lastIndex = (match.index || 0) + token.length;
|
|
11719
|
-
}
|
|
11720
|
-
result += escapeHtml(source.slice(lastIndex));
|
|
11721
|
-
return result;
|
|
11722
|
-
}
|
|
11723
|
-
|
|
11724
|
-
function normalizeAgentMessageText(text) {
|
|
11725
|
-
return String(text || '')
|
|
11726
|
-
.replace(/\r\n/g, '\n')
|
|
11727
|
-
.replace(/([.!?`'")])([A-Z[`"])/g, '$1\n\n$2');
|
|
11728
|
-
}
|
|
11729
|
-
|
|
11730
|
-
function renderAgentMessageMarkdown(text) {
|
|
11731
|
-
const source = normalizeAgentMessageText(text);
|
|
11732
|
-
if (!source) return '';
|
|
11733
|
-
|
|
11734
|
-
const lines = source.split('\n');
|
|
11735
|
-
const blocks = [];
|
|
11736
|
-
let paragraph = [];
|
|
11737
|
-
let list = [];
|
|
11738
|
-
let codeFence = null;
|
|
11739
|
-
let codeLines = [];
|
|
11740
|
-
|
|
11741
|
-
const flushParagraph = () => {
|
|
11742
|
-
if (paragraph.length === 0) return;
|
|
11743
|
-
blocks.push(
|
|
11744
|
-
`<p>${paragraph.map(renderAgentInlineMarkdown).join('<br>')}</p>`
|
|
11745
|
-
);
|
|
11746
|
-
paragraph = [];
|
|
11747
|
-
};
|
|
11748
|
-
|
|
11749
|
-
const flushList = () => {
|
|
11750
|
-
if (list.length === 0) return;
|
|
11751
|
-
blocks.push(
|
|
11752
|
-
`<ul>${list.map((item) => (
|
|
11753
|
-
`<li>${renderAgentInlineMarkdown(item)}</li>`
|
|
11754
|
-
)).join('')}</ul>`
|
|
11755
|
-
);
|
|
11756
|
-
list = [];
|
|
11757
|
-
};
|
|
11758
|
-
|
|
11759
|
-
const flushCode = () => {
|
|
11760
|
-
if (codeFence === null) return;
|
|
11761
|
-
const languageClass = codeFence
|
|
11762
|
-
? ` class="language-${escapeHtml(codeFence)}"`
|
|
11763
|
-
: '';
|
|
11764
|
-
blocks.push(
|
|
11765
|
-
`<pre><code${languageClass}>${escapeHtml(
|
|
11766
|
-
codeLines.join('\n')
|
|
11767
|
-
)}</code></pre>`
|
|
11768
|
-
);
|
|
11769
|
-
codeFence = null;
|
|
11770
|
-
codeLines = [];
|
|
11771
|
-
};
|
|
11772
|
-
|
|
11773
|
-
for (const line of lines) {
|
|
11774
|
-
const fenceMatch = line.match(/^```(.*)$/);
|
|
11775
|
-
if (fenceMatch) {
|
|
11776
|
-
flushParagraph();
|
|
11777
|
-
flushList();
|
|
11778
|
-
if (codeFence === null) {
|
|
11779
|
-
codeFence = fenceMatch[1].trim();
|
|
11780
|
-
} else {
|
|
11781
|
-
flushCode();
|
|
11782
|
-
}
|
|
11783
|
-
continue;
|
|
11784
|
-
}
|
|
11785
|
-
|
|
11786
|
-
if (codeFence !== null) {
|
|
11787
|
-
codeLines.push(line);
|
|
11788
|
-
continue;
|
|
11789
|
-
}
|
|
11790
|
-
|
|
11791
|
-
if (!line.trim()) {
|
|
11792
|
-
flushParagraph();
|
|
11793
|
-
flushList();
|
|
11794
|
-
continue;
|
|
11795
|
-
}
|
|
11796
|
-
|
|
11797
|
-
const listMatch = line.match(/^[-*]\s+(.*)$/);
|
|
11798
|
-
if (listMatch) {
|
|
11799
|
-
flushParagraph();
|
|
11800
|
-
list.push(listMatch[1]);
|
|
11801
|
-
continue;
|
|
11802
|
-
}
|
|
11803
|
-
|
|
11804
|
-
flushList();
|
|
11805
|
-
paragraph.push(line);
|
|
11806
|
-
}
|
|
11807
|
-
|
|
11808
|
-
flushParagraph();
|
|
11809
|
-
flushList();
|
|
11810
|
-
flushCode();
|
|
11811
|
-
|
|
11812
|
-
const html = blocks.join('');
|
|
11813
|
-
return DOMPurify.sanitize(html);
|
|
11814
|
-
}
|
|
11815
|
-
|
|
11816
12454
|
function truncateAgentDetail(text, limit = AGENT_MESSAGE_MAX_RENDER_BYTES) {
|
|
11817
12455
|
const value = String(text || '');
|
|
11818
12456
|
if (value.length <= limit) return value;
|
|
@@ -12929,9 +13567,20 @@ function upsertAgentTab(server, data) {
|
|
|
12929
13567
|
const key = makeAgentTabKey(server.id, data.id);
|
|
12930
13568
|
const existing = state.agentTabs.get(key);
|
|
12931
13569
|
if (existing) {
|
|
12932
|
-
existing.
|
|
13570
|
+
const hasLiveSocket = existing.socket?.readyState === WebSocket.OPEN;
|
|
13571
|
+
let shouldNotify = true;
|
|
13572
|
+
if (
|
|
13573
|
+
hasLiveSocket
|
|
13574
|
+
&& !shouldApplyAuthoritativeAgentSnapshot(existing, data)
|
|
13575
|
+
) {
|
|
13576
|
+
shouldNotify = existing.applyInventory(data);
|
|
13577
|
+
} else {
|
|
13578
|
+
existing.update(data);
|
|
13579
|
+
}
|
|
12933
13580
|
existing.connect();
|
|
12934
|
-
|
|
13581
|
+
if (shouldNotify) {
|
|
13582
|
+
existing.notifyUi();
|
|
13583
|
+
}
|
|
12935
13584
|
return existing;
|
|
12936
13585
|
}
|
|
12937
13586
|
const agentTab = new AgentTab(data, server);
|
|
@@ -12939,6 +13588,43 @@ function upsertAgentTab(server, data) {
|
|
|
12939
13588
|
return agentTab;
|
|
12940
13589
|
}
|
|
12941
13590
|
|
|
13591
|
+
function getAgentMessageComparableSignature(message) {
|
|
13592
|
+
if (!message || typeof message !== 'object') {
|
|
13593
|
+
return '';
|
|
13594
|
+
}
|
|
13595
|
+
return JSON.stringify([
|
|
13596
|
+
message.id || '',
|
|
13597
|
+
message.order || 0,
|
|
13598
|
+
message.role || '',
|
|
13599
|
+
message.kind || '',
|
|
13600
|
+
message.streamKey || '',
|
|
13601
|
+
hashUiText(message.text || '')
|
|
13602
|
+
]);
|
|
13603
|
+
}
|
|
13604
|
+
|
|
13605
|
+
function shouldApplyAuthoritativeAgentSnapshot(existing, data) {
|
|
13606
|
+
if (!existing || !data || typeof data !== 'object') {
|
|
13607
|
+
return false;
|
|
13608
|
+
}
|
|
13609
|
+
const incomingBusy = data.busy === true;
|
|
13610
|
+
if (incomingBusy) {
|
|
13611
|
+
return false;
|
|
13612
|
+
}
|
|
13613
|
+
if (!Array.isArray(data.messages)) {
|
|
13614
|
+
return true;
|
|
13615
|
+
}
|
|
13616
|
+
const currentMessages = Array.isArray(existing.messages)
|
|
13617
|
+
? existing.messages
|
|
13618
|
+
: [];
|
|
13619
|
+
if (data.messages.length !== currentMessages.length) {
|
|
13620
|
+
return true;
|
|
13621
|
+
}
|
|
13622
|
+
const incomingLast = data.messages[data.messages.length - 1] || null;
|
|
13623
|
+
const currentLast = currentMessages[currentMessages.length - 1] || null;
|
|
13624
|
+
return getAgentMessageComparableSignature(incomingLast)
|
|
13625
|
+
!== getAgentMessageComparableSignature(currentLast);
|
|
13626
|
+
}
|
|
13627
|
+
|
|
12942
13628
|
function upsertAgentInventoryTab(server, data) {
|
|
12943
13629
|
const key = makeAgentTabKey(server.id, data.id);
|
|
12944
13630
|
const existing = state.agentTabs.get(key);
|