agentgui 1.0.593 → 1.0.595
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/static/index.html +1 -0
- package/static/js/client.js +22 -10
- package/static/js/conversations.js +8 -0
- package/static/js/state-barrier.js +109 -0
package/package.json
CHANGED
package/static/index.html
CHANGED
|
@@ -3240,6 +3240,7 @@
|
|
|
3240
3240
|
<script defer src="/gm/js/syntax-highlighter.js"></script>
|
|
3241
3241
|
<script defer src="/gm/js/dialogs.js"></script>
|
|
3242
3242
|
<script defer src="/gm/js/ui-components.js"></script>
|
|
3243
|
+
<script defer src="/gm/js/state-barrier.js"></script>
|
|
3243
3244
|
<script defer src="/gm/js/conversations.js"></script>
|
|
3244
3245
|
<script defer src="/gm/js/terminal.js"></script>
|
|
3245
3246
|
<script defer src="/gm/js/script-runner.js"></script>
|
package/static/js/client.js
CHANGED
|
@@ -491,17 +491,21 @@ class AgentGUIClient {
|
|
|
491
491
|
this.showError('Please enter a message to steer');
|
|
492
492
|
return;
|
|
493
493
|
}
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
}
|
|
501
|
-
} catch (err) {
|
|
502
|
-
console.error('Failed to steer:', err);
|
|
503
|
-
this.showError('Failed to steer: ' + err.message);
|
|
494
|
+
|
|
495
|
+
// Capture message and clear UI immediately (no await)
|
|
496
|
+
const steerMsg = message;
|
|
497
|
+
if (this.ui.messageInput) {
|
|
498
|
+
this.ui.messageInput.value = '';
|
|
499
|
+
this.ui.messageInput.style.height = 'auto';
|
|
504
500
|
}
|
|
501
|
+
|
|
502
|
+
// Fire RPC in background, don't await
|
|
503
|
+
window.wsClient.rpc('conv.steer', { id: this.state.currentConversation.id, content: steerMsg })
|
|
504
|
+
.then(data => console.log('Steer response:', data))
|
|
505
|
+
.catch(err => {
|
|
506
|
+
console.error('Failed to steer:', err);
|
|
507
|
+
this.showError('Failed to steer: ' + err.message);
|
|
508
|
+
});
|
|
505
509
|
} else {
|
|
506
510
|
const instructions = await window.UIDialog.prompt('Enter instructions to inject into the running agent:', '', 'Inject Instructions');
|
|
507
511
|
if (!instructions) return;
|
|
@@ -584,6 +588,7 @@ class AgentGUIClient {
|
|
|
584
588
|
|
|
585
589
|
// Listen for active conversation deletion
|
|
586
590
|
window.addEventListener('conversation-deselected', () => {
|
|
591
|
+
window.ConversationState?.clear('deselected');
|
|
587
592
|
this.state.currentConversation = null;
|
|
588
593
|
this.state.currentSession = null;
|
|
589
594
|
this.updateUrlForConversation(null);
|
|
@@ -1318,6 +1323,7 @@ class AgentGUIClient {
|
|
|
1318
1323
|
}
|
|
1319
1324
|
|
|
1320
1325
|
async handleAllConversationsDeleted(data) {
|
|
1326
|
+
window.ConversationState?.clear('all_deleted');
|
|
1321
1327
|
this.state.currentConversation = null;
|
|
1322
1328
|
this.state.conversations = [];
|
|
1323
1329
|
this.state.sessionEvents = [];
|
|
@@ -1556,6 +1562,7 @@ class AgentGUIClient {
|
|
|
1556
1562
|
if (model) body.model = model;
|
|
1557
1563
|
if (subAgent) body.subAgent = subAgent;
|
|
1558
1564
|
const { conversation } = await window.wsClient.rpc('conv.new', body);
|
|
1565
|
+
window.ConversationState?.selectConversation(conversation.id, 'conversation_created', 1);
|
|
1559
1566
|
this.state.currentConversation = conversation;
|
|
1560
1567
|
this.lockAgentAndModel(agentId, model);
|
|
1561
1568
|
|
|
@@ -1834,6 +1841,7 @@ class AgentGUIClient {
|
|
|
1834
1841
|
if (model) createBody.model = model;
|
|
1835
1842
|
if (subAgent) createBody.subAgent = subAgent;
|
|
1836
1843
|
const { conversation: newConv } = await window.wsClient.rpc('conv.new', createBody);
|
|
1844
|
+
window.ConversationState?.selectConversation(newConv.id, 'stream_recreate', 1);
|
|
1837
1845
|
this.state.currentConversation = newConv;
|
|
1838
1846
|
if (window.conversationManager) {
|
|
1839
1847
|
window.conversationManager.loadConversations();
|
|
@@ -2556,6 +2564,7 @@ class AgentGUIClient {
|
|
|
2556
2564
|
|
|
2557
2565
|
const cachedConv = this.state.conversations.find(c => c.id === conversationId);
|
|
2558
2566
|
if (cachedConv && this.state.currentConversation?.id !== conversationId) {
|
|
2567
|
+
window.ConversationState?.selectConversation(conversationId, 'cache_load', 1);
|
|
2559
2568
|
this.state.currentConversation = cachedConv;
|
|
2560
2569
|
}
|
|
2561
2570
|
|
|
@@ -2572,6 +2581,7 @@ class AgentGUIClient {
|
|
|
2572
2581
|
while (cached.dom.firstChild) {
|
|
2573
2582
|
outputEl.appendChild(cached.dom.firstChild);
|
|
2574
2583
|
}
|
|
2584
|
+
window.ConversationState?.selectConversation(conversationId, 'dom_cache_load', 1);
|
|
2575
2585
|
this.state.currentConversation = cached.conversation;
|
|
2576
2586
|
const cachedHasActivity = cached.conversation.messageCount > 0 || this.state.streamingConversations.has(conversationId);
|
|
2577
2587
|
this.applyAgentAndModelSelection(cached.conversation, cachedHasActivity);
|
|
@@ -2594,6 +2604,7 @@ class AgentGUIClient {
|
|
|
2594
2604
|
} catch (e) {
|
|
2595
2605
|
if (e.code === 404) {
|
|
2596
2606
|
console.warn('Conversation no longer exists:', conversationId);
|
|
2607
|
+
window.ConversationState?.clear('conversation_not_found');
|
|
2597
2608
|
this.state.currentConversation = null;
|
|
2598
2609
|
if (window.conversationManager) window.conversationManager.loadConversations();
|
|
2599
2610
|
// Resume from last successful conversation if available, or fall back to any available conversation
|
|
@@ -2613,6 +2624,7 @@ class AgentGUIClient {
|
|
|
2613
2624
|
}
|
|
2614
2625
|
const { conversation, isActivelyStreaming, latestSession, chunks: rawChunks, totalChunks, messages: allMessages } = fullData;
|
|
2615
2626
|
|
|
2627
|
+
window.ConversationState?.selectConversation(conversationId, 'server_load', 1);
|
|
2616
2628
|
this.state.currentConversation = conversation;
|
|
2617
2629
|
const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this.state.streamingConversations.has(conversationId);
|
|
2618
2630
|
this.applyAgentAndModelSelection(conversation, hasActivity);
|
|
@@ -301,6 +301,7 @@ class ConversationManager {
|
|
|
301
301
|
await window.wsClient.rpc('conv.del.all', {});
|
|
302
302
|
console.log('[ConversationManager] Deleted all conversations');
|
|
303
303
|
this._updateConversations([], 'clear_all');
|
|
304
|
+
window.ConversationState?.clear('delete_all');
|
|
304
305
|
this.activeId = null;
|
|
305
306
|
window.dispatchEvent(new CustomEvent('conversation-deselected'));
|
|
306
307
|
this.render();
|
|
@@ -535,6 +536,11 @@ class ConversationManager {
|
|
|
535
536
|
}
|
|
536
537
|
|
|
537
538
|
select(convId) {
|
|
539
|
+
const result = window.ConversationState?.selectConversation(convId, 'user_click', 1) || { success: false };
|
|
540
|
+
if (!result.success && result.reason !== 'already_selected') {
|
|
541
|
+
console.error('[ConvMgr] activeId mutation rejected:', result.reason);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
538
544
|
this.activeId = convId;
|
|
539
545
|
|
|
540
546
|
document.querySelectorAll('.conversation-item').forEach(item => {
|
|
@@ -589,6 +595,7 @@ class ConversationManager {
|
|
|
589
595
|
const newConvs = this.conversations.filter(c => c.id !== convId);
|
|
590
596
|
this._updateConversations(newConvs, 'delete', { convId });
|
|
591
597
|
if (wasActive) {
|
|
598
|
+
window.ConversationState?.deleteConversation(convId, 1);
|
|
592
599
|
this.activeId = null;
|
|
593
600
|
window.dispatchEvent(new CustomEvent('conversation-deselected'));
|
|
594
601
|
}
|
|
@@ -607,6 +614,7 @@ class ConversationManager {
|
|
|
607
614
|
this.deleteConversation(msg.conversationId);
|
|
608
615
|
} else if (msg.type === 'all_conversations_deleted') {
|
|
609
616
|
this._updateConversations([], 'ws_clear_all');
|
|
617
|
+
window.ConversationState?.clear('all_deleted');
|
|
610
618
|
this.activeId = null;
|
|
611
619
|
this.streamingConversations.clear();
|
|
612
620
|
this.showEmpty('No conversations yet');
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State Barrier - Atomic state machine for conversation management
|
|
3
|
+
* Eliminates race conditions through single source of truth and version tracking
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
class ConversationState {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.current = {
|
|
9
|
+
id: null,
|
|
10
|
+
version: 0,
|
|
11
|
+
data: null,
|
|
12
|
+
timestamp: 0,
|
|
13
|
+
reason: null
|
|
14
|
+
};
|
|
15
|
+
this.history = [];
|
|
16
|
+
this.MAX_HISTORY = 50;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
selectConversation(id, reason, serverVersion) {
|
|
20
|
+
if (id === this.current.id && serverVersion === this.current.version) {
|
|
21
|
+
return { success: false, reason: 'already_selected', prevState: this.current, newState: this.current };
|
|
22
|
+
}
|
|
23
|
+
const prevState = { ...this.current };
|
|
24
|
+
this.current.id = id;
|
|
25
|
+
this.current.version = serverVersion || (this.current.version + 1);
|
|
26
|
+
this.current.timestamp = Date.now();
|
|
27
|
+
this.current.reason = reason;
|
|
28
|
+
this.current.data = null;
|
|
29
|
+
this._recordHistory('selectConversation', prevState, this.current, reason);
|
|
30
|
+
return { success: true, reason: 'selected', prevState, newState: { ...this.current } };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
updateConversation(id, data, serverVersion) {
|
|
34
|
+
if (id !== this.current.id) {
|
|
35
|
+
return { success: false, reason: 'version_mismatch', prevState: this.current, newState: this.current };
|
|
36
|
+
}
|
|
37
|
+
if (serverVersion && serverVersion < this.current.version) {
|
|
38
|
+
return { success: false, reason: 'stale_version', prevState: this.current, newState: this.current };
|
|
39
|
+
}
|
|
40
|
+
const prevState = { ...this.current };
|
|
41
|
+
this.current.data = { ...this.current.data, ...data };
|
|
42
|
+
this.current.version = serverVersion || (this.current.version + 1);
|
|
43
|
+
this.current.timestamp = Date.now();
|
|
44
|
+
this._recordHistory('updateConversation', prevState, this.current, 'update');
|
|
45
|
+
return { success: true, reason: 'updated', prevState, newState: { ...this.current } };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
deleteConversation(id, serverVersion) {
|
|
49
|
+
if (id !== this.current.id) {
|
|
50
|
+
return { success: false, reason: 'not_current', prevState: this.current, newState: this.current };
|
|
51
|
+
}
|
|
52
|
+
const prevState = { ...this.current };
|
|
53
|
+
this.current.id = null;
|
|
54
|
+
this.current.version = 0;
|
|
55
|
+
this.current.data = null;
|
|
56
|
+
this.current.timestamp = Date.now();
|
|
57
|
+
this.current.reason = 'deleted';
|
|
58
|
+
this._recordHistory('deleteConversation', prevState, this.current, 'delete');
|
|
59
|
+
return { success: true, reason: 'deleted', prevState, newState: { ...this.current } };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
clear(reason) {
|
|
63
|
+
const prevState = { ...this.current };
|
|
64
|
+
this.current.id = null;
|
|
65
|
+
this.current.version = 0;
|
|
66
|
+
this.current.data = null;
|
|
67
|
+
this.current.timestamp = Date.now();
|
|
68
|
+
this.current.reason = reason;
|
|
69
|
+
this._recordHistory('clear', prevState, this.current, reason);
|
|
70
|
+
return { success: true, reason: 'cleared', prevState, newState: { ...this.current } };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
getCurrent() {
|
|
74
|
+
return { ...this.current };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
getVersion() {
|
|
78
|
+
return this.current.version;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
_recordHistory(operation, prevState, newState, detail) {
|
|
82
|
+
this.history.push({
|
|
83
|
+
operation,
|
|
84
|
+
prevState,
|
|
85
|
+
newState,
|
|
86
|
+
detail,
|
|
87
|
+
timestamp: Date.now()
|
|
88
|
+
});
|
|
89
|
+
if (this.history.length > this.MAX_HISTORY) {
|
|
90
|
+
this.history.shift();
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getHistory() {
|
|
95
|
+
return [...this.history];
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
debugDump() {
|
|
99
|
+
return {
|
|
100
|
+
current: { ...this.current },
|
|
101
|
+
history: this.getHistory(),
|
|
102
|
+
timestamp: Date.now()
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (typeof window !== 'undefined') {
|
|
108
|
+
window.ConversationState = new ConversationState();
|
|
109
|
+
}
|