tabminal 3.0.15 → 3.0.17
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 +427 -182
- package/src/acp-manager.mjs +717 -163
- package/src/acp-test-agent.mjs +128 -0
- package/src/server.mjs +26 -10
package/public/app.js
CHANGED
|
@@ -4264,23 +4264,38 @@ class EditorManager {
|
|
|
4264
4264
|
if (!(body instanceof HTMLElement) || !message?.text) {
|
|
4265
4265
|
return;
|
|
4266
4266
|
}
|
|
4267
|
+
if (agentTab?.busy) {
|
|
4268
|
+
return;
|
|
4269
|
+
}
|
|
4270
|
+
if (isAgentMessageStreaming(agentTab, message)) {
|
|
4271
|
+
return;
|
|
4272
|
+
}
|
|
4267
4273
|
const session = this.currentSession;
|
|
4268
4274
|
if (!session) {
|
|
4269
4275
|
return;
|
|
4270
4276
|
}
|
|
4271
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
|
+
|
|
4272
4287
|
const renderToken = `${Date.now()}:${Math.random()}`;
|
|
4273
4288
|
body.dataset.markdownRenderToken = renderToken;
|
|
4274
4289
|
|
|
4275
4290
|
try {
|
|
4276
4291
|
const { renderer } = await loadMarkdownPreviewBundle();
|
|
4277
4292
|
if (
|
|
4278
|
-
|
|
4279
|
-
||
|
|
4293
|
+
String(message.text || '') !== sourceText
|
|
4294
|
+
|| isAgentMessageStreaming(agentTab, message)
|
|
4280
4295
|
) {
|
|
4281
4296
|
return;
|
|
4282
4297
|
}
|
|
4283
|
-
const rendered = renderer.render(
|
|
4298
|
+
const rendered = renderer.render(sourceText);
|
|
4284
4299
|
const sanitized = DOMPurify.sanitize(rendered, {
|
|
4285
4300
|
USE_PROFILES: {
|
|
4286
4301
|
html: true,
|
|
@@ -4299,6 +4314,17 @@ class EditorManager {
|
|
|
4299
4314
|
basePath,
|
|
4300
4315
|
session
|
|
4301
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');
|
|
4302
4328
|
body.replaceChildren(template.content);
|
|
4303
4329
|
} catch {
|
|
4304
4330
|
// Keep the lightweight fallback rendering.
|
|
@@ -5599,7 +5625,6 @@ class EditorManager {
|
|
|
5599
5625
|
}
|
|
5600
5626
|
|
|
5601
5627
|
renderAgentPanel(agentTab, options = {}) {
|
|
5602
|
-
this.disposeAgentEmbeddedEditors();
|
|
5603
5628
|
const previousLayout = this.captureAgentTranscriptLayout();
|
|
5604
5629
|
const previousScrollTop = previousLayout?.scrollTop || 0;
|
|
5605
5630
|
const wasNearBottom = this.isAgentTranscriptLayoutNearBottom(
|
|
@@ -5663,7 +5688,6 @@ class EditorManager {
|
|
|
5663
5688
|
|
|
5664
5689
|
this.renderAgentComposerAttachments(agentTab);
|
|
5665
5690
|
|
|
5666
|
-
this.agentTranscript.innerHTML = '';
|
|
5667
5691
|
const timeline = getAgentTimelineItems(agentTab);
|
|
5668
5692
|
const shouldPinToBottom = wasNearBottom || (
|
|
5669
5693
|
agentTab.scrollToBottomOnNextRender
|
|
@@ -5682,45 +5706,66 @@ class EditorManager {
|
|
|
5682
5706
|
transcriptWindow.end
|
|
5683
5707
|
);
|
|
5684
5708
|
if (timeline.length === 0) {
|
|
5685
|
-
this.agentTranscript.
|
|
5686
|
-
|
|
5687
|
-
);
|
|
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
|
+
}
|
|
5688
5715
|
} else {
|
|
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);
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
const nextNodes = [];
|
|
5689
5725
|
for (const [index, entry] of visibleTimeline.entries()) {
|
|
5690
5726
|
const timelineIndex = transcriptWindow.start + index;
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
entry.value
|
|
5705
|
-
);
|
|
5706
|
-
}
|
|
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;
|
|
5707
5740
|
if (node) {
|
|
5708
|
-
|
|
5709
|
-
entry,
|
|
5710
|
-
timelineIndex
|
|
5711
|
-
);
|
|
5741
|
+
existingByKey.delete(timelineKey);
|
|
5712
5742
|
if (
|
|
5713
|
-
|
|
5714
|
-
&& entry.type === 'message'
|
|
5715
|
-
&& String(entry.value?.role || '').toLowerCase()
|
|
5716
|
-
=== 'user'
|
|
5743
|
+
node.dataset.renderSignature !== renderSignature
|
|
5717
5744
|
) {
|
|
5718
|
-
|
|
5745
|
+
this.disposeAgentTimelineNode(node);
|
|
5746
|
+
node = null;
|
|
5719
5747
|
}
|
|
5720
|
-
|
|
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);
|
|
5721
5761
|
}
|
|
5722
5762
|
}
|
|
5763
|
+
for (const node of existingByKey.values()) {
|
|
5764
|
+
this.disposeAgentTimelineNode(node);
|
|
5765
|
+
}
|
|
5766
|
+
this.applyAgentTranscriptNodeList(nextNodes);
|
|
5723
5767
|
}
|
|
5768
|
+
this.rebuildAgentEmbeddedTerminalRegistry();
|
|
5724
5769
|
if (options.preserveTranscriptAnchor) {
|
|
5725
5770
|
const restored = this.restoreAgentTranscriptAnchor(
|
|
5726
5771
|
options.preserveTranscriptAnchor
|
|
@@ -5751,6 +5796,60 @@ class EditorManager {
|
|
|
5751
5796
|
this.scheduleAgentTranscriptViewportUpdate(shouldPinToBottom);
|
|
5752
5797
|
}
|
|
5753
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
|
+
|
|
5754
5853
|
async loadOlderAgentTimeline(agentTab) {
|
|
5755
5854
|
if (!agentTab || agentTab.historyWindowLoading) {
|
|
5756
5855
|
return;
|
|
@@ -6141,9 +6240,23 @@ class EditorManager {
|
|
|
6141
6240
|
message.role === 'assistant'
|
|
6142
6241
|
&& message.kind === 'message'
|
|
6143
6242
|
) {
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
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
|
+
}
|
|
6147
6260
|
} else {
|
|
6148
6261
|
body.classList.add('plain');
|
|
6149
6262
|
body.textContent = message.text || '';
|
|
@@ -6424,16 +6537,72 @@ class EditorManager {
|
|
|
6424
6537
|
|
|
6425
6538
|
disposeAgentEmbeddedEditors() {
|
|
6426
6539
|
this.agentEmbeddedTerminals.clear();
|
|
6427
|
-
|
|
6428
|
-
|
|
6429
|
-
|
|
6430
|
-
|
|
6431
|
-
|
|
6432
|
-
|
|
6540
|
+
if (!this.agentTranscript) {
|
|
6541
|
+
this.agentEmbeddedEditors = [];
|
|
6542
|
+
return;
|
|
6543
|
+
}
|
|
6544
|
+
for (const node of Array.from(this.agentTranscript.children)) {
|
|
6545
|
+
this.disposeAgentTimelineNode(node);
|
|
6433
6546
|
}
|
|
6434
6547
|
this.agentEmbeddedEditors = [];
|
|
6435
6548
|
}
|
|
6436
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
|
+
|
|
6437
6606
|
buildAgentSectionBody(details, section) {
|
|
6438
6607
|
if (
|
|
6439
6608
|
section?.kind === 'diff'
|
|
@@ -6497,7 +6666,8 @@ class EditorManager {
|
|
|
6497
6666
|
+ '"JetBrains Mono", Menlo, Consolas, monospace'
|
|
6498
6667
|
}
|
|
6499
6668
|
);
|
|
6500
|
-
this.
|
|
6669
|
+
this.trackAgentTimelineDisposable(host, editor);
|
|
6670
|
+
this.trackAgentTimelineDisposable(host, model);
|
|
6501
6671
|
details.addEventListener('toggle', () => {
|
|
6502
6672
|
if (details.open) {
|
|
6503
6673
|
requestAnimationFrame(() => {
|
|
@@ -6584,20 +6754,19 @@ class EditorManager {
|
|
|
6584
6754
|
}
|
|
6585
6755
|
});
|
|
6586
6756
|
if (terminalId) {
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
terminal: embeddedTerm,
|
|
6596
|
-
fitAddon,
|
|
6757
|
+
host.__agentTerminalBinding = {
|
|
6758
|
+
terminalId,
|
|
6759
|
+
meta,
|
|
6760
|
+
header,
|
|
6761
|
+
openButton,
|
|
6762
|
+
terminalNode,
|
|
6763
|
+
terminal: embeddedTerm,
|
|
6764
|
+
fitAddon,
|
|
6597
6765
|
layout: layoutTerminal
|
|
6598
|
-
}
|
|
6766
|
+
};
|
|
6599
6767
|
}
|
|
6600
|
-
this.
|
|
6768
|
+
this.trackAgentTimelineDisposable(host, embeddedTerm);
|
|
6769
|
+
this.trackAgentTimelineDisposable(host, fitAddon);
|
|
6601
6770
|
|
|
6602
6771
|
return host;
|
|
6603
6772
|
}
|
|
@@ -6711,11 +6880,9 @@ class EditorManager {
|
|
|
6711
6880
|
original: originalModel,
|
|
6712
6881
|
modified: modifiedModel
|
|
6713
6882
|
});
|
|
6714
|
-
this.
|
|
6715
|
-
|
|
6716
|
-
|
|
6717
|
-
modifiedModel
|
|
6718
|
-
);
|
|
6883
|
+
this.trackAgentTimelineDisposable(host, diffEditor);
|
|
6884
|
+
this.trackAgentTimelineDisposable(host, originalModel);
|
|
6885
|
+
this.trackAgentTimelineDisposable(host, modifiedModel);
|
|
6719
6886
|
details.addEventListener('toggle', () => {
|
|
6720
6887
|
if (details.open) {
|
|
6721
6888
|
requestAnimationFrame(() => {
|
|
@@ -9088,6 +9255,7 @@ class AgentTab {
|
|
|
9088
9255
|
this.historyWindowStart = -1;
|
|
9089
9256
|
this.historyWindowEnd = -1;
|
|
9090
9257
|
this.historyWindowLoading = false;
|
|
9258
|
+
this.streamingAssistantStreamKey = '';
|
|
9091
9259
|
this.resumeSessions = [];
|
|
9092
9260
|
this.resumeSessionsLoadedAt = 0;
|
|
9093
9261
|
this.resumeSessionsPromise = null;
|
|
@@ -9295,9 +9463,23 @@ class AgentTab {
|
|
|
9295
9463
|
break;
|
|
9296
9464
|
case 'message_open':
|
|
9297
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
|
+
}
|
|
9298
9473
|
break;
|
|
9299
9474
|
case 'message_chunk':
|
|
9300
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
|
+
}
|
|
9301
9483
|
break;
|
|
9302
9484
|
case 'session_update':
|
|
9303
9485
|
this.#applySessionUpdate(message.update || {});
|
|
@@ -9393,6 +9575,9 @@ class AgentTab {
|
|
|
9393
9575
|
default:
|
|
9394
9576
|
break;
|
|
9395
9577
|
}
|
|
9578
|
+
if (!this.busy) {
|
|
9579
|
+
this.streamingAssistantStreamKey = '';
|
|
9580
|
+
}
|
|
9396
9581
|
const shouldAutostartQueuedPrompt = (
|
|
9397
9582
|
wasBusy
|
|
9398
9583
|
&& !this.busy
|
|
@@ -9646,7 +9831,7 @@ class AgentTab {
|
|
|
9646
9831
|
const byId = this.messages.findIndex(
|
|
9647
9832
|
(message) => message.id === candidate.id
|
|
9648
9833
|
);
|
|
9649
|
-
|
|
9834
|
+
return byId;
|
|
9650
9835
|
}
|
|
9651
9836
|
if (!candidate.streamKey) return -1;
|
|
9652
9837
|
for (let index = this.messages.length - 1; index >= 0; index -= 1) {
|
|
@@ -9666,39 +9851,51 @@ class AgentTab {
|
|
|
9666
9851
|
if (!message) return;
|
|
9667
9852
|
const index = this.#findMessageIndex(message);
|
|
9668
9853
|
if (index === -1) {
|
|
9669
|
-
this
|
|
9854
|
+
const nextMessage = this.#normalizeMessage(message);
|
|
9855
|
+
clearAgentMessageMarkdownCache(nextMessage);
|
|
9856
|
+
this.messages.push(nextMessage);
|
|
9670
9857
|
return;
|
|
9671
9858
|
}
|
|
9672
9859
|
|
|
9673
9860
|
const previous = this.messages[index];
|
|
9674
9861
|
const nextMessage = this.#normalizeMessage(message, previous.order);
|
|
9862
|
+
const mergedText = selectAgentMessageText(previous.text, nextMessage.text);
|
|
9675
9863
|
this.messages[index] = {
|
|
9676
9864
|
...previous,
|
|
9677
9865
|
...nextMessage,
|
|
9678
9866
|
createdAt: nextMessage.createdAt || previous.createdAt || '',
|
|
9679
|
-
text:
|
|
9867
|
+
text: mergedText
|
|
9680
9868
|
};
|
|
9869
|
+
if (mergedText !== (previous.text || '')) {
|
|
9870
|
+
clearAgentMessageMarkdownCache(this.messages[index]);
|
|
9871
|
+
}
|
|
9681
9872
|
}
|
|
9682
9873
|
|
|
9683
9874
|
#appendChunk(message) {
|
|
9684
9875
|
const index = this.#findMessageIndex(message);
|
|
9685
9876
|
if (index !== -1) {
|
|
9686
9877
|
const existing = this.messages[index];
|
|
9687
|
-
|
|
9878
|
+
const nextText = mergeAgentMessageText(
|
|
9688
9879
|
existing.text || '',
|
|
9689
9880
|
message.text || ''
|
|
9690
9881
|
);
|
|
9882
|
+
if (nextText !== existing.text) {
|
|
9883
|
+
existing.text = nextText;
|
|
9884
|
+
clearAgentMessageMarkdownCache(existing);
|
|
9885
|
+
}
|
|
9691
9886
|
return;
|
|
9692
9887
|
}
|
|
9693
9888
|
|
|
9694
|
-
this
|
|
9889
|
+
const nextMessage = this.#normalizeMessage({
|
|
9695
9890
|
id: crypto.randomUUID(),
|
|
9696
9891
|
streamKey: message.streamKey,
|
|
9697
9892
|
role: message.role || 'assistant',
|
|
9698
9893
|
kind: message.kind || 'message',
|
|
9699
9894
|
text: message.text || '',
|
|
9700
9895
|
createdAt: new Date().toISOString()
|
|
9701
|
-
})
|
|
9896
|
+
});
|
|
9897
|
+
clearAgentMessageMarkdownCache(nextMessage);
|
|
9898
|
+
this.messages.push(nextMessage);
|
|
9702
9899
|
}
|
|
9703
9900
|
|
|
9704
9901
|
#applySessionUpdate(update) {
|
|
@@ -9856,9 +10053,21 @@ class AgentTab {
|
|
|
9856
10053
|
this.busy = typeof data.busy === 'boolean' ? data.busy : this.busy;
|
|
9857
10054
|
this.errorMessage = data.errorMessage || this.errorMessage || '';
|
|
9858
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;
|
|
9859
10065
|
this.sessionCapabilities = normalizeAgentSessionCapabilities(
|
|
9860
10066
|
data.sessionCapabilities || this.sessionCapabilities
|
|
9861
10067
|
);
|
|
10068
|
+
if (data.usage) {
|
|
10069
|
+
this.usage = this.#normalizeUsageState(data.usage);
|
|
10070
|
+
}
|
|
9862
10071
|
const nextSession = this.getLinkedSession();
|
|
9863
10072
|
const nextSnapshot = JSON.stringify({
|
|
9864
10073
|
runtimeId: this.runtimeId || '',
|
|
@@ -10825,6 +11034,19 @@ function mergeAgentMessageText(previousText, chunkText) {
|
|
|
10825
11034
|
const chunk = String(chunkText || '');
|
|
10826
11035
|
if (!previous) return chunk;
|
|
10827
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
|
+
}
|
|
10828
11050
|
if (/\s$/.test(previous) || /^\s/.test(chunk)) {
|
|
10829
11051
|
return `${previous}${chunk}`;
|
|
10830
11052
|
}
|
|
@@ -11229,6 +11451,94 @@ function getAgentTimelineItemKey(entry, absoluteIndex = 0) {
|
|
|
11229
11451
|
return `${entry.type}:${order}:${absoluteIndex}`;
|
|
11230
11452
|
}
|
|
11231
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
|
+
|
|
11232
11542
|
function formatWorkspaceTabTitle(
|
|
11233
11543
|
value,
|
|
11234
11544
|
maxLength = WORKSPACE_TAB_TITLE_MAX_LENGTH
|
|
@@ -12141,119 +12451,6 @@ function escapeHtml(text) {
|
|
|
12141
12451
|
.replaceAll("'", ''');
|
|
12142
12452
|
}
|
|
12143
12453
|
|
|
12144
|
-
function renderAgentInlineMarkdown(text) {
|
|
12145
|
-
const source = String(text || '');
|
|
12146
|
-
const pattern = /(`[^`]+`)|(\[([^\]]+)\]\(([^)]+)\))/g;
|
|
12147
|
-
let result = '';
|
|
12148
|
-
let lastIndex = 0;
|
|
12149
|
-
for (const match of source.matchAll(pattern)) {
|
|
12150
|
-
const [token, codeToken, , linkLabel, linkHref] = match;
|
|
12151
|
-
result += escapeHtml(source.slice(lastIndex, match.index));
|
|
12152
|
-
if (codeToken) {
|
|
12153
|
-
result += `<code>${escapeHtml(codeToken.slice(1, -1))}</code>`;
|
|
12154
|
-
} else if (linkLabel && linkHref) {
|
|
12155
|
-
result += `<a href="${escapeHtml(linkHref)}">${escapeHtml(linkLabel)}</a>`;
|
|
12156
|
-
} else {
|
|
12157
|
-
result += escapeHtml(token);
|
|
12158
|
-
}
|
|
12159
|
-
lastIndex = (match.index || 0) + token.length;
|
|
12160
|
-
}
|
|
12161
|
-
result += escapeHtml(source.slice(lastIndex));
|
|
12162
|
-
return result;
|
|
12163
|
-
}
|
|
12164
|
-
|
|
12165
|
-
function normalizeAgentMessageText(text) {
|
|
12166
|
-
return String(text || '')
|
|
12167
|
-
.replace(/\r\n/g, '\n')
|
|
12168
|
-
.replace(/([.!?`'")])([A-Z[`"])/g, '$1\n\n$2');
|
|
12169
|
-
}
|
|
12170
|
-
|
|
12171
|
-
function renderAgentMessageMarkdown(text) {
|
|
12172
|
-
const source = normalizeAgentMessageText(text);
|
|
12173
|
-
if (!source) return '';
|
|
12174
|
-
|
|
12175
|
-
const lines = source.split('\n');
|
|
12176
|
-
const blocks = [];
|
|
12177
|
-
let paragraph = [];
|
|
12178
|
-
let list = [];
|
|
12179
|
-
let codeFence = null;
|
|
12180
|
-
let codeLines = [];
|
|
12181
|
-
|
|
12182
|
-
const flushParagraph = () => {
|
|
12183
|
-
if (paragraph.length === 0) return;
|
|
12184
|
-
blocks.push(
|
|
12185
|
-
`<p>${paragraph.map(renderAgentInlineMarkdown).join('<br>')}</p>`
|
|
12186
|
-
);
|
|
12187
|
-
paragraph = [];
|
|
12188
|
-
};
|
|
12189
|
-
|
|
12190
|
-
const flushList = () => {
|
|
12191
|
-
if (list.length === 0) return;
|
|
12192
|
-
blocks.push(
|
|
12193
|
-
`<ul>${list.map((item) => (
|
|
12194
|
-
`<li>${renderAgentInlineMarkdown(item)}</li>`
|
|
12195
|
-
)).join('')}</ul>`
|
|
12196
|
-
);
|
|
12197
|
-
list = [];
|
|
12198
|
-
};
|
|
12199
|
-
|
|
12200
|
-
const flushCode = () => {
|
|
12201
|
-
if (codeFence === null) return;
|
|
12202
|
-
const languageClass = codeFence
|
|
12203
|
-
? ` class="language-${escapeHtml(codeFence)}"`
|
|
12204
|
-
: '';
|
|
12205
|
-
blocks.push(
|
|
12206
|
-
`<pre><code${languageClass}>${escapeHtml(
|
|
12207
|
-
codeLines.join('\n')
|
|
12208
|
-
)}</code></pre>`
|
|
12209
|
-
);
|
|
12210
|
-
codeFence = null;
|
|
12211
|
-
codeLines = [];
|
|
12212
|
-
};
|
|
12213
|
-
|
|
12214
|
-
for (const line of lines) {
|
|
12215
|
-
const fenceMatch = line.match(/^```(.*)$/);
|
|
12216
|
-
if (fenceMatch) {
|
|
12217
|
-
flushParagraph();
|
|
12218
|
-
flushList();
|
|
12219
|
-
if (codeFence === null) {
|
|
12220
|
-
codeFence = fenceMatch[1].trim();
|
|
12221
|
-
} else {
|
|
12222
|
-
flushCode();
|
|
12223
|
-
}
|
|
12224
|
-
continue;
|
|
12225
|
-
}
|
|
12226
|
-
|
|
12227
|
-
if (codeFence !== null) {
|
|
12228
|
-
codeLines.push(line);
|
|
12229
|
-
continue;
|
|
12230
|
-
}
|
|
12231
|
-
|
|
12232
|
-
if (!line.trim()) {
|
|
12233
|
-
flushParagraph();
|
|
12234
|
-
flushList();
|
|
12235
|
-
continue;
|
|
12236
|
-
}
|
|
12237
|
-
|
|
12238
|
-
const listMatch = line.match(/^[-*]\s+(.*)$/);
|
|
12239
|
-
if (listMatch) {
|
|
12240
|
-
flushParagraph();
|
|
12241
|
-
list.push(listMatch[1]);
|
|
12242
|
-
continue;
|
|
12243
|
-
}
|
|
12244
|
-
|
|
12245
|
-
flushList();
|
|
12246
|
-
paragraph.push(line);
|
|
12247
|
-
}
|
|
12248
|
-
|
|
12249
|
-
flushParagraph();
|
|
12250
|
-
flushList();
|
|
12251
|
-
flushCode();
|
|
12252
|
-
|
|
12253
|
-
const html = blocks.join('');
|
|
12254
|
-
return DOMPurify.sanitize(html);
|
|
12255
|
-
}
|
|
12256
|
-
|
|
12257
12454
|
function truncateAgentDetail(text, limit = AGENT_MESSAGE_MAX_RENDER_BYTES) {
|
|
12258
12455
|
const value = String(text || '');
|
|
12259
12456
|
if (value.length <= limit) return value;
|
|
@@ -13370,9 +13567,20 @@ function upsertAgentTab(server, data) {
|
|
|
13370
13567
|
const key = makeAgentTabKey(server.id, data.id);
|
|
13371
13568
|
const existing = state.agentTabs.get(key);
|
|
13372
13569
|
if (existing) {
|
|
13373
|
-
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
|
+
}
|
|
13374
13580
|
existing.connect();
|
|
13375
|
-
|
|
13581
|
+
if (shouldNotify) {
|
|
13582
|
+
existing.notifyUi();
|
|
13583
|
+
}
|
|
13376
13584
|
return existing;
|
|
13377
13585
|
}
|
|
13378
13586
|
const agentTab = new AgentTab(data, server);
|
|
@@ -13380,6 +13588,43 @@ function upsertAgentTab(server, data) {
|
|
|
13380
13588
|
return agentTab;
|
|
13381
13589
|
}
|
|
13382
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
|
+
|
|
13383
13628
|
function upsertAgentInventoryTab(server, data) {
|
|
13384
13629
|
const key = makeAgentTabKey(server.id, data.id);
|
|
13385
13630
|
const existing = state.agentTabs.get(key);
|