agentgui 1.0.728 → 1.0.730
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/CLAUDE.md +19 -8
- package/lib/tool-install-machine.js +157 -0
- package/lib/tool-manager.js +1 -2
- package/lib/tool-provisioner.js +6 -1
- package/lib/tool-spawner.js +24 -11
- package/lib/ws-handlers-util.js +5 -1
- package/package.json +1 -1
- package/server.js +24 -5
- package/static/index.html +5 -0
- package/static/js/client.js +5 -30
- package/static/js/conv-list-machine.js +137 -0
- package/static/js/conversations.js +39 -74
- package/static/js/prompt-machine.js +108 -0
- package/static/js/tool-install-machine.js +155 -0
- package/static/js/tools-manager-ui.js +119 -0
- package/static/js/tools-manager.js +164 -435
- package/static/js/voice-machine.js +145 -0
- package/static/js/voice.js +132 -119
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
const { createMachine, createActor, assign } = XState;
|
|
3
|
+
|
|
4
|
+
const convListMachine = createMachine({
|
|
5
|
+
id: 'conv-list',
|
|
6
|
+
initial: 'unloaded',
|
|
7
|
+
context: {
|
|
8
|
+
conversations: [],
|
|
9
|
+
activeId: null,
|
|
10
|
+
streamingIds: [],
|
|
11
|
+
version: 0,
|
|
12
|
+
lastPollAt: null,
|
|
13
|
+
},
|
|
14
|
+
states: {
|
|
15
|
+
unloaded: {
|
|
16
|
+
on: {
|
|
17
|
+
LOAD_START: 'loading',
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
loading: {
|
|
21
|
+
on: {
|
|
22
|
+
LOAD_DONE: {
|
|
23
|
+
target: 'loaded',
|
|
24
|
+
actions: assign(({ context, event }) => {
|
|
25
|
+
const incoming = event.conversations || [];
|
|
26
|
+
let conversations;
|
|
27
|
+
if (incoming.length === 0 && context.conversations.length > 0) {
|
|
28
|
+
conversations = context.conversations;
|
|
29
|
+
} else if (incoming.length > 0 && incoming.length < context.conversations.length) {
|
|
30
|
+
const polledIds = new Set(incoming.map(c => c.id));
|
|
31
|
+
const kept = context.conversations.filter(c => !polledIds.has(c.id));
|
|
32
|
+
conversations = incoming.map(pc => {
|
|
33
|
+
const cached = context.conversations.find(c => c.id === pc.id);
|
|
34
|
+
return cached ? Object.assign({}, cached, pc) : pc;
|
|
35
|
+
}).concat(kept);
|
|
36
|
+
} else {
|
|
37
|
+
conversations = incoming;
|
|
38
|
+
}
|
|
39
|
+
return { conversations, version: context.version + 1, lastPollAt: Date.now() };
|
|
40
|
+
}),
|
|
41
|
+
},
|
|
42
|
+
LOAD_ERROR: 'error',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
loaded: {
|
|
46
|
+
on: {
|
|
47
|
+
LOAD_START: 'loading',
|
|
48
|
+
ADD: {
|
|
49
|
+
actions: assign(({ context, event }) => {
|
|
50
|
+
if (context.conversations.some(c => c.id === event.conversation.id)) return {};
|
|
51
|
+
return { conversations: [event.conversation, ...context.conversations], version: context.version + 1 };
|
|
52
|
+
}),
|
|
53
|
+
},
|
|
54
|
+
UPDATE: {
|
|
55
|
+
actions: assign(({ context, event }) => {
|
|
56
|
+
const idx = context.conversations.findIndex(c => c.id === event.conversation.id);
|
|
57
|
+
if (idx < 0) return {};
|
|
58
|
+
const updated = [...context.conversations];
|
|
59
|
+
updated[idx] = Object.assign({}, updated[idx], event.conversation);
|
|
60
|
+
return { conversations: updated, version: context.version + 1 };
|
|
61
|
+
}),
|
|
62
|
+
},
|
|
63
|
+
DELETE: {
|
|
64
|
+
actions: assign(({ context, event }) => ({
|
|
65
|
+
conversations: context.conversations.filter(c => c.id !== event.id),
|
|
66
|
+
activeId: context.activeId === event.id ? null : context.activeId,
|
|
67
|
+
streamingIds: context.streamingIds.filter(id => id !== event.id),
|
|
68
|
+
version: context.version + 1,
|
|
69
|
+
})),
|
|
70
|
+
},
|
|
71
|
+
CLEAR_ALL: {
|
|
72
|
+
actions: assign({ conversations: [], activeId: null, streamingIds: [], version: 0 }),
|
|
73
|
+
},
|
|
74
|
+
SET_STREAMING: {
|
|
75
|
+
actions: assign(({ context, event }) => ({
|
|
76
|
+
streamingIds: context.streamingIds.includes(event.id)
|
|
77
|
+
? context.streamingIds
|
|
78
|
+
: [...context.streamingIds, event.id],
|
|
79
|
+
})),
|
|
80
|
+
},
|
|
81
|
+
CLEAR_STREAMING: {
|
|
82
|
+
actions: assign(({ context, event }) => ({
|
|
83
|
+
streamingIds: context.streamingIds.filter(id => id !== event.id),
|
|
84
|
+
})),
|
|
85
|
+
},
|
|
86
|
+
SELECT: {
|
|
87
|
+
actions: assign(({ event }) => ({ activeId: event.id })),
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
error: {
|
|
92
|
+
on: {
|
|
93
|
+
LOAD_START: 'loading',
|
|
94
|
+
},
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
const actor = createActor(convListMachine);
|
|
100
|
+
actor.start();
|
|
101
|
+
|
|
102
|
+
function sendEvent(event) {
|
|
103
|
+
actor.send(event);
|
|
104
|
+
return actor.getSnapshot();
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function getState() {
|
|
108
|
+
return actor.getSnapshot().value;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function getContext() {
|
|
112
|
+
return actor.getSnapshot().context;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getConversations() {
|
|
116
|
+
return actor.getSnapshot().context.conversations;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function getStreamingIds() {
|
|
120
|
+
return actor.getSnapshot().context.streamingIds;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function isStreaming(id) {
|
|
124
|
+
return actor.getSnapshot().context.streamingIds.includes(id);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getActiveId() {
|
|
128
|
+
return actor.getSnapshot().context.activeId;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function subscribe(fn) {
|
|
132
|
+
return actor.subscribe(fn);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
window.__convListMachine = actor;
|
|
136
|
+
window.convListMachineAPI = { send: sendEvent, getState, getContext, getConversations, getStreamingIds, isStreaming, getActiveId, subscribe };
|
|
137
|
+
})();
|
|
@@ -15,19 +15,12 @@ function pathBasename(p) {
|
|
|
15
15
|
|
|
16
16
|
class ConversationManager {
|
|
17
17
|
constructor() {
|
|
18
|
-
this.conversations = [];
|
|
19
|
-
this.activeId = null;
|
|
20
18
|
this.listEl = document.querySelector('[data-conversation-list]');
|
|
21
19
|
this.emptyEl = document.querySelector('[data-conversation-empty]');
|
|
22
20
|
this.newBtn = document.querySelector('[data-new-conversation]');
|
|
23
21
|
this.sidebarEl = document.querySelector('[data-sidebar]');
|
|
24
|
-
this.streamingConversations = new Set();
|
|
25
22
|
this.agents = new Map();
|
|
26
23
|
|
|
27
|
-
this._conversationVersion = 0;
|
|
28
|
-
this._lastMutationSource = null;
|
|
29
|
-
this._lastMutationTime = 0;
|
|
30
|
-
|
|
31
24
|
this.folderBrowser = {
|
|
32
25
|
modal: null,
|
|
33
26
|
listEl: null,
|
|
@@ -43,6 +36,23 @@ class ConversationManager {
|
|
|
43
36
|
this.init();
|
|
44
37
|
}
|
|
45
38
|
|
|
39
|
+
get conversations() {
|
|
40
|
+
return window.convListMachineAPI ? window.convListMachineAPI.getConversations() : [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get activeId() {
|
|
44
|
+
return window.convListMachineAPI ? window.convListMachineAPI.getActiveId() : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
set activeId(id) {
|
|
48
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'SELECT', id });
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
get streamingConversations() {
|
|
52
|
+
const ids = window.convListMachineAPI ? window.convListMachineAPI.getStreamingIds() : [];
|
|
53
|
+
return { has: (id) => ids.includes(id), add: (id) => window.convListMachineAPI?.send({ type: 'SET_STREAMING', id }), delete: (id) => window.convListMachineAPI?.send({ type: 'CLEAR_STREAMING', id }), clear: () => ids.forEach(id => window.convListMachineAPI?.send({ type: 'CLEAR_STREAMING', id })) };
|
|
54
|
+
}
|
|
55
|
+
|
|
46
56
|
async init() {
|
|
47
57
|
this.newBtn?.addEventListener('click', () => this.openFolderBrowser());
|
|
48
58
|
this.setupDelegatedListeners();
|
|
@@ -80,27 +90,18 @@ class ConversationManager {
|
|
|
80
90
|
}
|
|
81
91
|
}
|
|
82
92
|
|
|
83
|
-
_updateConversations(newArray, source
|
|
93
|
+
_updateConversations(newArray, source) {
|
|
94
|
+
if (!window.convListMachineAPI) return { version: 0, timestamp: Date.now(), oldLen: 0, newLen: 0 };
|
|
84
95
|
const oldLen = this.conversations.length;
|
|
85
96
|
const newLen = Array.isArray(newArray) ? newArray.length : 0;
|
|
86
|
-
|
|
87
|
-
const
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
this._lastMutationSource = source;
|
|
91
|
-
this._lastMutationTime = timestamp;
|
|
92
|
-
|
|
93
|
-
window._conversationCacheVersion = mutationId;
|
|
94
|
-
|
|
95
|
-
if (context.verbose) {
|
|
96
|
-
console.log(`[ConvMgr] mutation #${mutationId} (${source}): ${oldLen} → ${newLen} items, ts=${timestamp}`);
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return { version: mutationId, timestamp, oldLen, newLen };
|
|
97
|
+
window.convListMachineAPI.send({ type: 'LOAD_DONE', conversations: Array.isArray(newArray) ? newArray : [] });
|
|
98
|
+
const version = window.convListMachineAPI.getContext().version;
|
|
99
|
+
window._conversationCacheVersion = version;
|
|
100
|
+
return { version, timestamp: Date.now(), oldLen, newLen };
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
getConversationCacheVersion() {
|
|
103
|
-
return
|
|
104
|
+
return window.convListMachineAPI ? window.convListMachineAPI.getContext().version : 0;
|
|
104
105
|
}
|
|
105
106
|
|
|
106
107
|
getAgentDisplayName(agentId) {
|
|
@@ -315,9 +316,8 @@ class ConversationManager {
|
|
|
315
316
|
this.deleteAllBtn.disabled = true;
|
|
316
317
|
await window.wsClient.rpc('conv.del.all', {});
|
|
317
318
|
console.log('[ConversationManager] Deleted all conversations');
|
|
318
|
-
|
|
319
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'CLEAR_ALL' });
|
|
319
320
|
window.ConversationState?.clear('delete_all');
|
|
320
|
-
this.activeId = null;
|
|
321
321
|
window.dispatchEvent(new CustomEvent('conversation-deselected'));
|
|
322
322
|
this.render();
|
|
323
323
|
} catch (err) {
|
|
@@ -424,6 +424,7 @@ class ConversationManager {
|
|
|
424
424
|
}
|
|
425
425
|
|
|
426
426
|
async loadConversations() {
|
|
427
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'LOAD_START' });
|
|
427
428
|
try {
|
|
428
429
|
const base = window.__BASE_URL || '/gm';
|
|
429
430
|
const res = await fetch(base + '/api/conversations');
|
|
@@ -431,44 +432,25 @@ class ConversationManager {
|
|
|
431
432
|
const data = await res.json();
|
|
432
433
|
const convList = data.conversations || [];
|
|
433
434
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
if (convList.length > 0) {
|
|
437
|
-
// If poll returns fewer conversations than cached, merge to avoid dropping items
|
|
438
|
-
// due to transient server errors or partial responses
|
|
439
|
-
if (convList.length < this.conversations.length) {
|
|
440
|
-
const polledIds = new Set(convList.map(c => c.id));
|
|
441
|
-
const kept = this.conversations.filter(c => !polledIds.has(c.id));
|
|
442
|
-
// Update polled items in place, append any cached items not in poll result
|
|
443
|
-
const merged = convList.map(pc => {
|
|
444
|
-
const cached = this.conversations.find(c => c.id === pc.id);
|
|
445
|
-
return cached ? Object.assign({}, cached, pc) : pc;
|
|
446
|
-
}).concat(kept);
|
|
447
|
-
this._updateConversations(merged, 'poll_merge');
|
|
448
|
-
} else {
|
|
449
|
-
this._updateConversations(convList, 'poll');
|
|
450
|
-
}
|
|
451
|
-
} else if (this.conversations.length === 0) {
|
|
452
|
-
// First load and empty - show empty state, but don't clear on subsequent polls
|
|
453
|
-
this._updateConversations(convList, 'poll');
|
|
435
|
+
if (window.convListMachineAPI) {
|
|
436
|
+
window.convListMachineAPI.send({ type: 'LOAD_DONE', conversations: convList });
|
|
454
437
|
}
|
|
455
|
-
// If convList is empty but this.conversations has items, do nothing - keep existing
|
|
456
438
|
|
|
457
439
|
const clientStreamingMap = window.agentGuiClient?.state?.streamingConversations;
|
|
458
440
|
for (const conv of this.conversations) {
|
|
459
441
|
const serverStreaming = conv.isStreaming === 1 || conv.isStreaming === true;
|
|
460
442
|
const clientStreaming = clientStreamingMap ? clientStreamingMap.has(conv.id) : false;
|
|
461
443
|
if (serverStreaming || clientStreaming) {
|
|
462
|
-
|
|
444
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'SET_STREAMING', id: conv.id });
|
|
463
445
|
} else {
|
|
464
|
-
|
|
446
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'CLEAR_STREAMING', id: conv.id });
|
|
465
447
|
}
|
|
466
448
|
}
|
|
467
449
|
|
|
468
450
|
this.render();
|
|
469
451
|
} catch (err) {
|
|
470
452
|
console.error('Failed to load conversations:', err);
|
|
471
|
-
|
|
453
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'LOAD_ERROR' });
|
|
472
454
|
if (this.conversations.length === 0) {
|
|
473
455
|
this.showEmpty('Failed to load conversations');
|
|
474
456
|
}
|
|
@@ -595,7 +577,7 @@ class ConversationManager {
|
|
|
595
577
|
console.error('[ConvMgr] activeId mutation rejected:', result.reason);
|
|
596
578
|
return;
|
|
597
579
|
}
|
|
598
|
-
|
|
580
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'SELECT', id: convId });
|
|
599
581
|
|
|
600
582
|
document.querySelectorAll('.conversation-item').forEach(item => {
|
|
601
583
|
item.classList.remove('active');
|
|
@@ -622,35 +604,20 @@ class ConversationManager {
|
|
|
622
604
|
}
|
|
623
605
|
|
|
624
606
|
addConversation(conv) {
|
|
625
|
-
if (
|
|
626
|
-
return;
|
|
627
|
-
}
|
|
628
|
-
const newConvs = [conv, ...this.conversations];
|
|
629
|
-
this._updateConversations(newConvs, 'add', { convId: conv.id });
|
|
607
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'ADD', conversation: conv });
|
|
630
608
|
this.render();
|
|
631
609
|
}
|
|
632
610
|
|
|
633
611
|
updateConversation(convId, updates) {
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
const updated = Object.assign({}, this.conversations[idx], updates);
|
|
637
|
-
const newConvs = [
|
|
638
|
-
...this.conversations.slice(0, idx),
|
|
639
|
-
updated,
|
|
640
|
-
...this.conversations.slice(idx + 1)
|
|
641
|
-
];
|
|
642
|
-
this._updateConversations(newConvs, 'update', { convId });
|
|
643
|
-
this.render();
|
|
644
|
-
}
|
|
612
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'UPDATE', conversation: Object.assign({ id: convId }, updates) });
|
|
613
|
+
this.render();
|
|
645
614
|
}
|
|
646
615
|
|
|
647
616
|
deleteConversation(convId) {
|
|
648
617
|
const wasActive = this.activeId === convId;
|
|
649
|
-
|
|
650
|
-
this._updateConversations(newConvs, 'delete', { convId });
|
|
618
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'DELETE', id: convId });
|
|
651
619
|
if (wasActive) {
|
|
652
620
|
window.ConversationState?.deleteConversation(convId, 1);
|
|
653
|
-
this.activeId = null;
|
|
654
621
|
window.dispatchEvent(new CustomEvent('conversation-deselected'));
|
|
655
622
|
}
|
|
656
623
|
this.render();
|
|
@@ -667,16 +634,14 @@ class ConversationManager {
|
|
|
667
634
|
} else if (msg.type === 'conversation_deleted') {
|
|
668
635
|
this.deleteConversation(msg.conversationId);
|
|
669
636
|
} else if (msg.type === 'all_conversations_deleted') {
|
|
670
|
-
|
|
637
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'CLEAR_ALL' });
|
|
671
638
|
window.ConversationState?.clear('all_deleted');
|
|
672
|
-
this.activeId = null;
|
|
673
|
-
this.streamingConversations.clear();
|
|
674
639
|
this.showEmpty('No conversations yet');
|
|
675
640
|
} else if (msg.type === 'streaming_start' && msg.conversationId) {
|
|
676
|
-
|
|
641
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'SET_STREAMING', id: msg.conversationId });
|
|
677
642
|
this.render();
|
|
678
643
|
} else if ((msg.type === 'streaming_complete' || msg.type === 'streaming_error') && msg.conversationId) {
|
|
679
|
-
|
|
644
|
+
if (window.convListMachineAPI) window.convListMachineAPI.send({ type: 'CLEAR_STREAMING', id: msg.conversationId });
|
|
680
645
|
this.render();
|
|
681
646
|
}
|
|
682
647
|
});
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
const { createMachine, createActor, assign } = XState;
|
|
3
|
+
|
|
4
|
+
const promptMachine = createMachine({
|
|
5
|
+
id: 'prompt-area',
|
|
6
|
+
initial: 'ready',
|
|
7
|
+
context: {
|
|
8
|
+
conversationId: null,
|
|
9
|
+
queueLength: 0,
|
|
10
|
+
},
|
|
11
|
+
states: {
|
|
12
|
+
ready: {
|
|
13
|
+
on: {
|
|
14
|
+
LOADING: 'loading',
|
|
15
|
+
STREAMING: {
|
|
16
|
+
target: 'streaming',
|
|
17
|
+
actions: assign(({ event }) => ({ conversationId: event.conversationId || null })),
|
|
18
|
+
},
|
|
19
|
+
DISABLED: 'disabled',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
loading: {
|
|
23
|
+
on: {
|
|
24
|
+
READY: 'ready',
|
|
25
|
+
STREAMING: {
|
|
26
|
+
target: 'streaming',
|
|
27
|
+
actions: assign(({ event }) => ({ conversationId: event.conversationId || null })),
|
|
28
|
+
},
|
|
29
|
+
DISABLED: 'disabled',
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
streaming: {
|
|
33
|
+
on: {
|
|
34
|
+
QUEUED: {
|
|
35
|
+
target: 'queued',
|
|
36
|
+
actions: assign(({ event }) => ({ queueLength: event.queueLength || 1 })),
|
|
37
|
+
},
|
|
38
|
+
READY: {
|
|
39
|
+
target: 'ready',
|
|
40
|
+
actions: assign({ conversationId: null, queueLength: 0 }),
|
|
41
|
+
},
|
|
42
|
+
STREAMING: {
|
|
43
|
+
actions: assign(({ event }) => ({ conversationId: event.conversationId || null })),
|
|
44
|
+
},
|
|
45
|
+
QUEUE_UPDATE: {
|
|
46
|
+
actions: assign(({ event }) => ({ queueLength: event.queueLength || 0 })),
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
queued: {
|
|
51
|
+
on: {
|
|
52
|
+
STREAMING: {
|
|
53
|
+
target: 'streaming',
|
|
54
|
+
actions: assign(({ event }) => ({ conversationId: event.conversationId || null, queueLength: 0 })),
|
|
55
|
+
},
|
|
56
|
+
READY: {
|
|
57
|
+
target: 'ready',
|
|
58
|
+
actions: assign({ conversationId: null, queueLength: 0 }),
|
|
59
|
+
},
|
|
60
|
+
QUEUE_UPDATE: {
|
|
61
|
+
actions: assign(({ event }) => ({ queueLength: event.queueLength || 0 })),
|
|
62
|
+
guard: ({ event }) => (event.queueLength || 0) > 0,
|
|
63
|
+
},
|
|
64
|
+
QUEUE_EMPTY: {
|
|
65
|
+
target: 'ready',
|
|
66
|
+
actions: assign({ conversationId: null, queueLength: 0 }),
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
disabled: {
|
|
71
|
+
on: {
|
|
72
|
+
READY: 'ready',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const actor = createActor(promptMachine);
|
|
79
|
+
actor.start();
|
|
80
|
+
|
|
81
|
+
function sendEvent(event) {
|
|
82
|
+
actor.send(event);
|
|
83
|
+
return actor.getSnapshot();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getState() {
|
|
87
|
+
return actor.getSnapshot().value;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function isReady() {
|
|
91
|
+
return getState() === 'ready';
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isStreaming() {
|
|
95
|
+
return getState() === 'streaming';
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function isDisabled() {
|
|
99
|
+
return getState() === 'disabled';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function subscribe(fn) {
|
|
103
|
+
return actor.subscribe(fn);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
window.__promptMachine = actor;
|
|
107
|
+
window.promptMachineAPI = { send: sendEvent, getState, isReady, isStreaming, isDisabled, subscribe };
|
|
108
|
+
})();
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
const { createMachine, createActor, assign } = XState;
|
|
3
|
+
|
|
4
|
+
const toolInstallMachine = createMachine({
|
|
5
|
+
id: 'tool-install-ui',
|
|
6
|
+
initial: 'idle',
|
|
7
|
+
context: {
|
|
8
|
+
version: null,
|
|
9
|
+
error: null,
|
|
10
|
+
progress: 0,
|
|
11
|
+
installedVersion: null,
|
|
12
|
+
publishedVersion: null,
|
|
13
|
+
},
|
|
14
|
+
states: {
|
|
15
|
+
idle: {
|
|
16
|
+
entry: assign({ error: null, progress: 0 }),
|
|
17
|
+
on: {
|
|
18
|
+
INSTALL: 'installing',
|
|
19
|
+
UPDATE: 'updating',
|
|
20
|
+
SET_INSTALLED: {
|
|
21
|
+
target: 'installed',
|
|
22
|
+
actions: assign(({ event }) => ({
|
|
23
|
+
installedVersion: event.installedVersion || null,
|
|
24
|
+
publishedVersion: event.publishedVersion || null,
|
|
25
|
+
version: event.version || event.installedVersion || null,
|
|
26
|
+
})),
|
|
27
|
+
},
|
|
28
|
+
SET_NEEDS_UPDATE: {
|
|
29
|
+
target: 'needs_update',
|
|
30
|
+
actions: assign(({ event }) => ({
|
|
31
|
+
installedVersion: event.installedVersion || null,
|
|
32
|
+
publishedVersion: event.publishedVersion || null,
|
|
33
|
+
})),
|
|
34
|
+
},
|
|
35
|
+
SET_FAILED: {
|
|
36
|
+
target: 'failed',
|
|
37
|
+
actions: assign(({ event }) => ({ error: event.error || null })),
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
installing: {
|
|
42
|
+
entry: assign({ error: null, progress: 0 }),
|
|
43
|
+
on: {
|
|
44
|
+
PROGRESS: {
|
|
45
|
+
actions: assign(({ context, event }) => ({
|
|
46
|
+
progress: Math.min(event.progress || context.progress + 5, 90),
|
|
47
|
+
})),
|
|
48
|
+
},
|
|
49
|
+
COMPLETE: {
|
|
50
|
+
target: 'installed',
|
|
51
|
+
actions: assign(({ event }) => ({
|
|
52
|
+
version: event.version || null,
|
|
53
|
+
installedVersion: event.installedVersion || null,
|
|
54
|
+
publishedVersion: event.publishedVersion || null,
|
|
55
|
+
progress: 100,
|
|
56
|
+
error: null,
|
|
57
|
+
})),
|
|
58
|
+
},
|
|
59
|
+
FAILED: {
|
|
60
|
+
target: 'failed',
|
|
61
|
+
actions: assign(({ event }) => ({ error: event.error || null, progress: 0 })),
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
installed: {
|
|
66
|
+
on: {
|
|
67
|
+
UPDATE: 'updating',
|
|
68
|
+
SET_NEEDS_UPDATE: {
|
|
69
|
+
target: 'needs_update',
|
|
70
|
+
actions: assign(({ event }) => ({
|
|
71
|
+
installedVersion: event.installedVersion || null,
|
|
72
|
+
publishedVersion: event.publishedVersion || null,
|
|
73
|
+
})),
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
updating: {
|
|
78
|
+
entry: assign({ error: null, progress: 0 }),
|
|
79
|
+
on: {
|
|
80
|
+
PROGRESS: {
|
|
81
|
+
actions: assign(({ context, event }) => ({
|
|
82
|
+
progress: Math.min(event.progress || context.progress + 5, 90),
|
|
83
|
+
})),
|
|
84
|
+
},
|
|
85
|
+
COMPLETE: {
|
|
86
|
+
target: 'installed',
|
|
87
|
+
actions: assign(({ event }) => ({
|
|
88
|
+
version: event.version || null,
|
|
89
|
+
installedVersion: event.installedVersion || null,
|
|
90
|
+
publishedVersion: event.publishedVersion || null,
|
|
91
|
+
progress: 100,
|
|
92
|
+
error: null,
|
|
93
|
+
})),
|
|
94
|
+
},
|
|
95
|
+
FAILED: {
|
|
96
|
+
target: 'failed',
|
|
97
|
+
actions: assign(({ event }) => ({ error: event.error || null, progress: 0 })),
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
needs_update: {
|
|
102
|
+
on: {
|
|
103
|
+
UPDATE: 'updating',
|
|
104
|
+
SET_INSTALLED: {
|
|
105
|
+
target: 'installed',
|
|
106
|
+
actions: assign(({ event }) => ({
|
|
107
|
+
installedVersion: event.installedVersion || null,
|
|
108
|
+
publishedVersion: event.publishedVersion || null,
|
|
109
|
+
})),
|
|
110
|
+
},
|
|
111
|
+
},
|
|
112
|
+
},
|
|
113
|
+
failed: {
|
|
114
|
+
on: {
|
|
115
|
+
INSTALL: 'installing',
|
|
116
|
+
UPDATE: 'updating',
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
const toolInstallMachines = new Map();
|
|
123
|
+
|
|
124
|
+
function getOrCreate(toolId) {
|
|
125
|
+
if (toolInstallMachines.has(toolId)) return toolInstallMachines.get(toolId);
|
|
126
|
+
const actor = createActor(toolInstallMachine);
|
|
127
|
+
actor.start();
|
|
128
|
+
toolInstallMachines.set(toolId, actor);
|
|
129
|
+
return actor;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function sendEvent(toolId, event) {
|
|
133
|
+
const actor = getOrCreate(toolId);
|
|
134
|
+
actor.send(event);
|
|
135
|
+
return actor.getSnapshot();
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getState(toolId) {
|
|
139
|
+
const actor = toolInstallMachines.get(toolId);
|
|
140
|
+
return actor ? actor.getSnapshot().value : 'idle';
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isLocked(toolId) {
|
|
144
|
+
const s = getState(toolId);
|
|
145
|
+
return s === 'installing' || s === 'updating';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function remove(toolId) {
|
|
149
|
+
const actor = toolInstallMachines.get(toolId);
|
|
150
|
+
if (actor) { actor.stop(); toolInstallMachines.delete(toolId); }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
window.__toolInstallMachines = toolInstallMachines;
|
|
154
|
+
window.toolInstallMachineAPI = { getOrCreate, send: sendEvent, getState, isLocked, remove };
|
|
155
|
+
})();
|