agentgui 1.0.636 → 1.0.638
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/lib/claude-runner.js +1 -1
- package/lib/gm-agent-configs.js +3 -2
- package/lib/plugins/tools-plugin.js +17 -11
- package/lib/tool-manager.js +13 -8
- package/package.json +1 -1
- package/static/js/client.js +83 -1
- package/static/js/terminal.js +11 -0
package/lib/claude-runner.js
CHANGED
|
@@ -3,7 +3,7 @@ import { spawn, spawnSync } from 'child_process';
|
|
|
3
3
|
const isWindows = process.platform === 'win32';
|
|
4
4
|
|
|
5
5
|
function getSpawnOptions(cwd, additionalOptions = {}) {
|
|
6
|
-
const options = { cwd, ...additionalOptions };
|
|
6
|
+
const options = { cwd, windowsHide: true, ...additionalOptions };
|
|
7
7
|
if (isWindows) {
|
|
8
8
|
options.shell = true;
|
|
9
9
|
}
|
package/lib/gm-agent-configs.js
CHANGED
|
@@ -15,11 +15,12 @@ function log(msg) { console.log('[GM-CONFIG] ' + msg); }
|
|
|
15
15
|
|
|
16
16
|
function runInstaller(pkg) {
|
|
17
17
|
return new Promise((resolve) => {
|
|
18
|
-
const
|
|
19
|
-
const proc = spawn(
|
|
18
|
+
const bunCmd = isWindows ? 'bun.cmd' : 'bun';
|
|
19
|
+
const proc = spawn(bunCmd, ['x', pkg], {
|
|
20
20
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
21
21
|
timeout: 60000,
|
|
22
22
|
shell: isWindows,
|
|
23
|
+
windowsHide: true,
|
|
23
24
|
});
|
|
24
25
|
|
|
25
26
|
let stdout = '';
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
// Tools plugin - tool detection, versioning, install/update handlers
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
import os from 'os';
|
|
5
|
+
|
|
6
|
+
const isWindows = os.platform() === 'win32';
|
|
7
|
+
const bunCmd = isWindows ? 'bun.cmd' : 'bun';
|
|
4
8
|
|
|
5
9
|
export default {
|
|
6
10
|
name: 'tools',
|
|
@@ -15,19 +19,21 @@ export default {
|
|
|
15
19
|
|
|
16
20
|
const detectTools = async () => {
|
|
17
21
|
const tools = [
|
|
18
|
-
{ id: 'gm-cc', pkg: '
|
|
19
|
-
{ id: 'gm-oc', pkg: '
|
|
20
|
-
{ id: 'gm-gc', pkg: '
|
|
21
|
-
{ id: 'gm-kilo', pkg: '
|
|
22
|
+
{ id: 'gm-cc', pkg: 'gm-cc', name: 'GM Claude' },
|
|
23
|
+
{ id: 'gm-oc', pkg: 'gm-oc', name: 'GM OpenCode' },
|
|
24
|
+
{ id: 'gm-gc', pkg: 'gm-gc', name: 'GM Gemini' },
|
|
25
|
+
{ id: 'gm-kilo', pkg: 'gm-kilo', name: 'GM Kilo' },
|
|
26
|
+
{ id: 'gm-codex', pkg: 'gm-codex', name: 'GM Codex' },
|
|
27
|
+
{ id: 'cli-claude', pkg: '@anthropic-ai/claude-code', name: 'Claude Code' },
|
|
28
|
+
{ id: 'cli-opencode', pkg: 'opencode-ai', name: 'OpenCode' },
|
|
29
|
+
{ id: 'cli-gemini', pkg: '@google/gemini-cli', name: 'Gemini CLI' },
|
|
30
|
+
{ id: 'cli-kilo', pkg: '@kilocode/cli', name: 'Kilo Code' },
|
|
31
|
+
{ id: 'cli-codex', pkg: '@openai/codex', name: 'Codex CLI' },
|
|
22
32
|
];
|
|
23
33
|
|
|
24
34
|
for (const tool of tools) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
tool.installed = true;
|
|
28
|
-
} catch {
|
|
29
|
-
tool.installed = false;
|
|
30
|
-
}
|
|
35
|
+
const result = spawnSync(bunCmd, ['x', tool.pkg, '--version'], { stdio: 'ignore', timeout: 10000 });
|
|
36
|
+
tool.installed = result.status === 0;
|
|
31
37
|
toolCache.set(tool.id, tool);
|
|
32
38
|
}
|
|
33
39
|
return tools;
|
package/lib/tool-manager.js
CHANGED
|
@@ -14,6 +14,7 @@ const TOOLS = [
|
|
|
14
14
|
{ id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', pluginId: 'gm', category: 'plugin', frameWork: 'opencode' },
|
|
15
15
|
{ id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', pluginId: 'gm', category: 'plugin', frameWork: 'gemini' },
|
|
16
16
|
{ id: 'gm-kilo', name: 'GM Kilo', pkg: 'gm-kilo', pluginId: 'gm', category: 'plugin', frameWork: 'kilo' },
|
|
17
|
+
{ id: 'gm-codex', name: 'GM Codex', pkg: 'gm-codex', pluginId: 'gm', category: 'plugin', frameWork: 'codex' },
|
|
17
18
|
];
|
|
18
19
|
|
|
19
20
|
const statusCache = new Map();
|
|
@@ -123,14 +124,15 @@ const getInstalledVersion = (pkg, pluginId = null, frameWork = null) => {
|
|
|
123
124
|
|
|
124
125
|
// Check Codex CLI (stored at ~/.codex)
|
|
125
126
|
if (!frameWork || frameWork === 'codex') {
|
|
126
|
-
const
|
|
127
|
-
if (fs.existsSync(
|
|
127
|
+
const codexPluginPath = path.join(homeDir, '.codex', 'plugins', actualPluginId, 'plugin.json');
|
|
128
|
+
if (fs.existsSync(codexPluginPath)) {
|
|
128
129
|
try {
|
|
129
|
-
const pluginJson = JSON.parse(fs.readFileSync(
|
|
130
|
+
const pluginJson = JSON.parse(fs.readFileSync(codexPluginPath, 'utf-8'));
|
|
130
131
|
if (pluginJson.version) return pluginJson.version;
|
|
131
132
|
} catch (e) {
|
|
132
|
-
console.warn(`[tool-manager] Failed to parse ${
|
|
133
|
+
console.warn(`[tool-manager] Failed to parse ${codexPluginPath}:`, e.message);
|
|
133
134
|
}
|
|
135
|
+
return 'installed';
|
|
134
136
|
}
|
|
135
137
|
}
|
|
136
138
|
} catch (_) {}
|
|
@@ -156,7 +158,7 @@ const checkCliInstalled = (pkg) => {
|
|
|
156
158
|
const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
|
|
157
159
|
const bin = binMap[pkg];
|
|
158
160
|
if (bin) {
|
|
159
|
-
execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000 });
|
|
161
|
+
execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000, windowsHide: true });
|
|
160
162
|
return true;
|
|
161
163
|
}
|
|
162
164
|
} catch (_) {}
|
|
@@ -170,7 +172,7 @@ const getCliVersion = (pkg) => {
|
|
|
170
172
|
if (bin) {
|
|
171
173
|
try {
|
|
172
174
|
// Use short timeout - we already know the binary exists from checkCliInstalled
|
|
173
|
-
const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 1000, encoding: 'utf8' });
|
|
175
|
+
const out = execSync(`${bin} --version`, { stdio: 'pipe', timeout: 1000, encoding: 'utf8', windowsHide: true });
|
|
174
176
|
const match = out.match(/(\d+\.\d+\.\d+)/);
|
|
175
177
|
if (match) {
|
|
176
178
|
console.log(`[tool-manager] CLI ${pkg} (${bin}) version: ${match[1]}`);
|
|
@@ -204,6 +206,9 @@ const checkToolInstalled = (pluginId, frameWork = null) => {
|
|
|
204
206
|
if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId + '.md'))) return true;
|
|
205
207
|
if (fs.existsSync(path.join(homeDir, '.config', 'kilo', 'agents', pluginId))) return true;
|
|
206
208
|
}
|
|
209
|
+
if (!frameWork || frameWork === 'codex') {
|
|
210
|
+
if (fs.existsSync(path.join(homeDir, '.codex', 'plugins', pluginId))) return true;
|
|
211
|
+
}
|
|
207
212
|
} catch (_) {}
|
|
208
213
|
return false;
|
|
209
214
|
};
|
|
@@ -310,7 +315,7 @@ const spawnNpmInstall = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
310
315
|
let completed = false, stderr = '', stdout = '';
|
|
311
316
|
let proc;
|
|
312
317
|
try {
|
|
313
|
-
proc = spawn(cmd, ['install', '-g', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
|
|
318
|
+
proc = spawn(cmd, ['install', '-g', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true });
|
|
314
319
|
} catch (err) {
|
|
315
320
|
return resolve({ success: false, error: `Failed to spawn npm install: ${err.message}` });
|
|
316
321
|
}
|
|
@@ -335,7 +340,7 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
335
340
|
let proc;
|
|
336
341
|
|
|
337
342
|
try {
|
|
338
|
-
proc = spawn(cmd, ['x', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows });
|
|
343
|
+
proc = spawn(cmd, ['x', pkg], { stdio: ['pipe', 'pipe', 'pipe'], timeout: 300000, shell: isWindows, windowsHide: true });
|
|
339
344
|
} catch (err) {
|
|
340
345
|
return resolve({ success: false, error: `Failed to spawn bun x: ${err.message}` });
|
|
341
346
|
}
|
package/package.json
CHANGED
package/static/js/client.js
CHANGED
|
@@ -66,6 +66,12 @@ class AgentGUIClient {
|
|
|
66
66
|
this._inflightRequests = new Map();
|
|
67
67
|
this._previousConvAbort = null;
|
|
68
68
|
|
|
69
|
+
// Background conversation cache: keeps last 50 conversations' streaming blocks in memory
|
|
70
|
+
// Blocks are stored as packed msgpackr Uint8Arrays for memory efficiency
|
|
71
|
+
// Map<conversationId, { packed: Uint8Array[], seqSet: Set<number>, sessionId: string }>
|
|
72
|
+
this._bgCache = new Map();
|
|
73
|
+
this.BG_CACHE_MAX = 50;
|
|
74
|
+
|
|
69
75
|
// PHASE 2: Request Lifetime Tracking
|
|
70
76
|
this._loadInProgress = {}; // { [conversationId]: { requestId, abortController, timestamp, prevConversationId } }
|
|
71
77
|
this._currentRequestId = 0; // Auto-incrementing request counter
|
|
@@ -955,6 +961,29 @@ class AgentGUIClient {
|
|
|
955
961
|
if (!this.state.streamingBlocks) this.state.streamingBlocks = [];
|
|
956
962
|
this.state.streamingBlocks.push(block);
|
|
957
963
|
|
|
964
|
+
// Cache block for background conversations (all 50 cached convs, not just active)
|
|
965
|
+
const convId = data.conversationId;
|
|
966
|
+
if (convId) {
|
|
967
|
+
let entry = this._bgCache.get(convId);
|
|
968
|
+
if (!entry) {
|
|
969
|
+
// Evict oldest if at capacity
|
|
970
|
+
if (this._bgCache.size >= this.BG_CACHE_MAX) {
|
|
971
|
+
const oldestKey = this._bgCache.keys().next().value;
|
|
972
|
+
this._bgCache.delete(oldestKey);
|
|
973
|
+
}
|
|
974
|
+
entry = { packed: [], seqSet: new Set(), sessionId: data.sessionId };
|
|
975
|
+
this._bgCache.set(convId, entry);
|
|
976
|
+
}
|
|
977
|
+
if (data.seq === undefined || !entry.seqSet.has(data.seq)) {
|
|
978
|
+
if (data.seq !== undefined) entry.seqSet.add(data.seq);
|
|
979
|
+
entry.sessionId = data.sessionId;
|
|
980
|
+
// Pack with msgpackr if available, else store raw
|
|
981
|
+
try {
|
|
982
|
+
entry.packed.push(typeof msgpackr !== 'undefined' ? msgpackr.pack(block) : block);
|
|
983
|
+
} catch (_) { entry.packed.push(block); }
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
958
987
|
// Only render for the currently-visible session
|
|
959
988
|
if (this.state.currentSession?.id !== data.sessionId) return;
|
|
960
989
|
|
|
@@ -1671,6 +1700,30 @@ class AgentGUIClient {
|
|
|
1671
1700
|
}
|
|
1672
1701
|
}
|
|
1673
1702
|
|
|
1703
|
+
// Flush background-cached blocks into the active streaming container
|
|
1704
|
+
_flushBgCache(conversationId, sessionId) {
|
|
1705
|
+
const entry = this._bgCache.get(conversationId);
|
|
1706
|
+
if (!entry || entry.packed.length === 0) return;
|
|
1707
|
+
if (entry.sessionId !== sessionId) { this._bgCache.delete(conversationId); return; }
|
|
1708
|
+
|
|
1709
|
+
const streamingEl = document.getElementById(`streaming-${sessionId}`);
|
|
1710
|
+
if (!streamingEl) return;
|
|
1711
|
+
const blocksEl = streamingEl.querySelector('.streaming-blocks');
|
|
1712
|
+
if (!blocksEl) return;
|
|
1713
|
+
|
|
1714
|
+
const seenSeqs = (this._renderedSeqs || {})[sessionId] || new Set();
|
|
1715
|
+
for (const packed of entry.packed) {
|
|
1716
|
+
try {
|
|
1717
|
+
const block = (typeof msgpackr !== 'undefined' && packed instanceof Uint8Array)
|
|
1718
|
+
? msgpackr.unpack(packed) : packed;
|
|
1719
|
+
const el = this.renderer.renderBlock(block, { sessionId }, blocksEl);
|
|
1720
|
+
if (el) blocksEl.appendChild(el);
|
|
1721
|
+
} catch (_) {}
|
|
1722
|
+
}
|
|
1723
|
+
this._bgCache.delete(conversationId);
|
|
1724
|
+
this.scrollToBottom();
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1674
1727
|
async _recoverMissedChunks() {
|
|
1675
1728
|
if (!this.state.currentSession?.id) return;
|
|
1676
1729
|
if (!this.state.streamingConversations.has(this.state.currentConversation?.id)) return;
|
|
@@ -1813,12 +1866,25 @@ class AgentGUIClient {
|
|
|
1813
1866
|
_showWelcomeScreen() {
|
|
1814
1867
|
const outputEl = document.getElementById('output');
|
|
1815
1868
|
if (!outputEl) return;
|
|
1869
|
+
// Build agent options from loaded agents list
|
|
1870
|
+
const agents = this.state.agents || [];
|
|
1871
|
+
const agentOptions = agents.map(a =>
|
|
1872
|
+
`<option value="${this.escapeHtml(a.id)}">${this.escapeHtml(a.name.split(/[\s\-]+/)[0])}</option>`
|
|
1873
|
+
).join('');
|
|
1816
1874
|
outputEl.innerHTML = `
|
|
1817
1875
|
<div style="display:flex;align-items:center;justify-content:center;height:100%;flex-direction:column;gap:2rem;padding:2rem;">
|
|
1818
1876
|
<div style="text-align:center;">
|
|
1819
1877
|
<h1 style="margin:0;font-size:2.5rem;color:var(--color-text-primary);">Welcome to AgentGUI</h1>
|
|
1820
1878
|
<p style="margin:1rem 0 0 0;font-size:1.1rem;color:var(--color-text-secondary);">Start a new conversation or select one from the sidebar</p>
|
|
1821
1879
|
</div>
|
|
1880
|
+
${agents.length > 0 ? `
|
|
1881
|
+
<div style="display:flex;flex-direction:column;align-items:center;gap:0.75rem;">
|
|
1882
|
+
<label style="font-size:0.85rem;color:var(--color-text-secondary);font-weight:500;">Select Agent</label>
|
|
1883
|
+
<select id="welcomeAgentSelect" style="padding:0.5rem 1rem;border-radius:0.375rem;border:1px solid var(--color-border);background:var(--color-bg-secondary);color:var(--color-text-primary);font-size:1rem;cursor:pointer;">
|
|
1884
|
+
${agentOptions}
|
|
1885
|
+
</select>
|
|
1886
|
+
</div>
|
|
1887
|
+
` : ''}
|
|
1822
1888
|
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(250px,1fr));gap:1rem;max-width:600px;">
|
|
1823
1889
|
<div style="padding:1.5rem;border-radius:0.5rem;background:var(--color-bg-secondary);border:1px solid var(--color-border);">
|
|
1824
1890
|
<h3 style="margin:0 0 0.5rem 0;color:var(--color-primary);">New Conversation</h3>
|
|
@@ -1826,11 +1892,22 @@ class AgentGUIClient {
|
|
|
1826
1892
|
</div>
|
|
1827
1893
|
<div style="padding:1.5rem;border-radius:0.5rem;background:var(--color-bg-secondary);border:1px solid var(--color-border);">
|
|
1828
1894
|
<h3 style="margin:0 0 0.5rem 0;color:var(--color-primary);">Available Agents</h3>
|
|
1829
|
-
<p style="margin:0;font-size:0.9rem;color:var(--color-text-secondary);">Claude Code, Gemini, OpenCode, and more</p>
|
|
1895
|
+
<p style="margin:0;font-size:0.9rem;color:var(--color-text-secondary);">${agents.length > 0 ? agents.map(a => a.name.split(/[\s\-]+/)[0]).join(', ') : 'Claude Code, Gemini, OpenCode, and more'}</p>
|
|
1830
1896
|
</div>
|
|
1831
1897
|
</div>
|
|
1832
1898
|
</div>
|
|
1833
1899
|
`;
|
|
1900
|
+
// Sync welcome agent select with the bottom bar cli selector
|
|
1901
|
+
const welcomeSel = document.getElementById('welcomeAgentSelect');
|
|
1902
|
+
if (welcomeSel) {
|
|
1903
|
+
if (this.ui.cliSelector) welcomeSel.value = this.ui.cliSelector.value;
|
|
1904
|
+
welcomeSel.addEventListener('change', () => {
|
|
1905
|
+
if (this.ui.cliSelector) {
|
|
1906
|
+
this.ui.cliSelector.value = welcomeSel.value;
|
|
1907
|
+
this.ui.cliSelector.dispatchEvent(new Event('change'));
|
|
1908
|
+
}
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1834
1911
|
}
|
|
1835
1912
|
|
|
1836
1913
|
_showSkeletonLoading(conversationId) {
|
|
@@ -2463,6 +2540,7 @@ class AgentGUIClient {
|
|
|
2463
2540
|
}
|
|
2464
2541
|
window.ConversationState?.selectConversation(conversationId, 'dom_cache_load', 1);
|
|
2465
2542
|
this.state.currentConversation = cached.conversation;
|
|
2543
|
+
window.dispatchEvent(new CustomEvent('conversation-changed', { detail: { conversationId, conversation: cached.conversation } }));
|
|
2466
2544
|
const cachedHasActivity = cached.conversation.messageCount > 0 || this.state.streamingConversations.has(conversationId);
|
|
2467
2545
|
this.applyAgentAndModelSelection(cached.conversation, cachedHasActivity);
|
|
2468
2546
|
this.conversationCache.delete(conversationId);
|
|
@@ -2510,6 +2588,7 @@ class AgentGUIClient {
|
|
|
2510
2588
|
|
|
2511
2589
|
window.ConversationState?.selectConversation(conversationId, 'server_load', 1);
|
|
2512
2590
|
this.state.currentConversation = conversation;
|
|
2591
|
+
window.dispatchEvent(new CustomEvent('conversation-changed', { detail: { conversationId, conversation } }));
|
|
2513
2592
|
const hasActivity = (allMessages && allMessages.length > 0) || isActivelyStreaming || latestSession || this.state.streamingConversations.has(conversationId);
|
|
2514
2593
|
this.applyAgentAndModelSelection(conversation, hasActivity);
|
|
2515
2594
|
|
|
@@ -2703,6 +2782,9 @@ class AgentGUIClient {
|
|
|
2703
2782
|
|
|
2704
2783
|
this.updateUrlForConversation(conversationId, latestSession.id);
|
|
2705
2784
|
|
|
2785
|
+
// Flush any blocks accumulated in the background cache while this conv wasn't active
|
|
2786
|
+
this._flushBgCache(conversationId, latestSession.id);
|
|
2787
|
+
|
|
2706
2788
|
// IMMUTABLE: Prompt remains enabled - syncPromptState will set correct state
|
|
2707
2789
|
this.syncPromptState(conversationId);
|
|
2708
2790
|
} else {
|
package/static/js/terminal.js
CHANGED
|
@@ -180,6 +180,17 @@
|
|
|
180
180
|
}
|
|
181
181
|
});
|
|
182
182
|
|
|
183
|
+
// Restart terminal in the new conversation's cwd when conversation switches while terminal is active
|
|
184
|
+
window.addEventListener('conversation-changed', function(e) {
|
|
185
|
+
if (!termActive) return;
|
|
186
|
+
var cwd = getCwd();
|
|
187
|
+
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
188
|
+
var dims = term ? { cols: term.cols, rows: term.rows } : { cols: 80, rows: 24 };
|
|
189
|
+
ws.send(JSON.stringify({ type: 'terminal_start', cwd: cwd, cols: dims.cols, rows: dims.rows }));
|
|
190
|
+
if (term) term.write('\r\n\x1b[33m[Switched to: ' + (cwd || '/') + ']\x1b[0m\r\n');
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
|
|
183
194
|
window.terminalModule = {
|
|
184
195
|
start: startTerminal,
|
|
185
196
|
stop: stopTerminal,
|