agentgui 1.0.637 → 1.0.639
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/tool-manager.js +11 -10
- 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 = '';
|
package/lib/tool-manager.js
CHANGED
|
@@ -10,11 +10,11 @@ const TOOLS = [
|
|
|
10
10
|
{ id: 'cli-gemini', name: 'Gemini CLI', pkg: '@google/gemini-cli', category: 'cli' },
|
|
11
11
|
{ id: 'cli-kilo', name: 'Kilo Code', pkg: '@kilocode/cli', category: 'cli' },
|
|
12
12
|
{ id: 'cli-codex', name: 'Codex CLI', pkg: '@openai/codex', category: 'cli' },
|
|
13
|
-
{ id: 'gm-cc', name: 'GM Claude', pkg: 'gm-cc', pluginId: 'gm-cc', category: 'plugin', frameWork: 'claude' },
|
|
14
|
-
{ id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', pluginId: 'gm', category: 'plugin', frameWork: 'opencode' },
|
|
15
|
-
{ id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', pluginId: 'gm', category: 'plugin', frameWork: 'gemini' },
|
|
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' },
|
|
13
|
+
{ id: 'gm-cc', name: 'GM Claude', pkg: 'gm-cc', installPkg: 'gm-cc@latest', pluginId: 'gm-cc', category: 'plugin', frameWork: 'claude' },
|
|
14
|
+
{ id: 'gm-oc', name: 'GM OpenCode', pkg: 'gm-oc', installPkg: 'gm-oc@latest', pluginId: 'gm', category: 'plugin', frameWork: 'opencode' },
|
|
15
|
+
{ id: 'gm-gc', name: 'GM Gemini', pkg: 'gm-gc', installPkg: 'gm-gc@latest', pluginId: 'gm', category: 'plugin', frameWork: 'gemini' },
|
|
16
|
+
{ id: 'gm-kilo', name: 'GM Kilo', pkg: 'gm-kilo', installPkg: 'gm-kilo@latest', pluginId: 'gm', category: 'plugin', frameWork: 'kilo' },
|
|
17
|
+
{ id: 'gm-codex', name: 'GM Codex', pkg: 'gm-codex', installPkg: 'gm-codex@latest', pluginId: 'gm', category: 'plugin', frameWork: 'codex' },
|
|
18
18
|
];
|
|
19
19
|
|
|
20
20
|
const statusCache = new Map();
|
|
@@ -158,7 +158,7 @@ const checkCliInstalled = (pkg) => {
|
|
|
158
158
|
const binMap = { '@anthropic-ai/claude-code': 'claude', 'opencode-ai': 'opencode', '@google/gemini-cli': 'gemini', '@kilocode/cli': 'kilo', '@openai/codex': 'codex' };
|
|
159
159
|
const bin = binMap[pkg];
|
|
160
160
|
if (bin) {
|
|
161
|
-
execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000 });
|
|
161
|
+
execSync(`${cmd} ${bin}`, { stdio: 'pipe', timeout: 3000, windowsHide: true });
|
|
162
162
|
return true;
|
|
163
163
|
}
|
|
164
164
|
} catch (_) {}
|
|
@@ -172,7 +172,7 @@ const getCliVersion = (pkg) => {
|
|
|
172
172
|
if (bin) {
|
|
173
173
|
try {
|
|
174
174
|
// Use short timeout - we already know the binary exists from checkCliInstalled
|
|
175
|
-
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 });
|
|
176
176
|
const match = out.match(/(\d+\.\d+\.\d+)/);
|
|
177
177
|
if (match) {
|
|
178
178
|
console.log(`[tool-manager] CLI ${pkg} (${bin}) version: ${match[1]}`);
|
|
@@ -315,7 +315,7 @@ const spawnNpmInstall = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
315
315
|
let completed = false, stderr = '', stdout = '';
|
|
316
316
|
let proc;
|
|
317
317
|
try {
|
|
318
|
-
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 });
|
|
319
319
|
} catch (err) {
|
|
320
320
|
return resolve({ success: false, error: `Failed to spawn npm install: ${err.message}` });
|
|
321
321
|
}
|
|
@@ -340,7 +340,7 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
340
340
|
let proc;
|
|
341
341
|
|
|
342
342
|
try {
|
|
343
|
-
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 });
|
|
344
344
|
} catch (err) {
|
|
345
345
|
return resolve({ success: false, error: `Failed to spawn bun x: ${err.message}` });
|
|
346
346
|
}
|
|
@@ -406,7 +406,8 @@ const spawnBunxProc = (pkg, onProgress) => new Promise((resolve) => {
|
|
|
406
406
|
});
|
|
407
407
|
|
|
408
408
|
const spawnForTool = (tool, onProgress) => {
|
|
409
|
-
|
|
409
|
+
const pkg = tool.installPkg || tool.pkg;
|
|
410
|
+
return tool.category === 'cli' ? spawnNpmInstall(pkg, onProgress) : spawnBunxProc(pkg, onProgress);
|
|
410
411
|
};
|
|
411
412
|
|
|
412
413
|
export async function install(toolId, onProgress) {
|
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,
|