agentgui 1.0.722 → 1.0.723

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.
@@ -16,6 +16,7 @@ export class JsonlWatcher {
16
16
  this._timers = new Map();
17
17
  this._seqs = new Map();
18
18
  this._streaming = new Set();
19
+ this._sessions = new Map();
19
20
  this._watcher = null;
20
21
  }
21
22
 
@@ -42,6 +43,7 @@ export class JsonlWatcher {
42
43
  this._convMap.delete(sid);
43
44
  this._seqs.delete(sid);
44
45
  this._streaming.delete(sid);
46
+ this._sessions.delete(sid);
45
47
  for (const key of [...this._emitted.keys()]) if (key.startsWith(`${sid}:`)) this._emitted.delete(key);
46
48
  for (const [fp, s] of this._tails.entries()) {
47
49
  if (!fp.includes(sid)) continue;
@@ -58,6 +60,7 @@ export class JsonlWatcher {
58
60
  for (const t of this._timers.values()) clearTimeout(t);
59
61
  this._tails.clear(); this._convMap.clear(); this._emitted.clear();
60
62
  this._timers.clear(); this._seqs.clear(); this._streaming.clear();
63
+ this._sessions.clear();
61
64
  }
62
65
 
63
66
  _scanDir(dir, depth) {
@@ -129,8 +132,19 @@ export class JsonlWatcher {
129
132
 
130
133
  _seq(sid) { const n = (this._seqs.get(sid) || 0) + 1; this._seqs.set(sid, n); return n; }
131
134
 
135
+ _dbSession(cid, sid) {
136
+ if (this._sessions.has(sid)) return this._sessions.get(sid);
137
+ const sess = this._q.createSession(cid);
138
+ this._q.updateSession(sess.id, { status: 'active' });
139
+ this._sessions.set(sid, sess.id);
140
+ return sess.id;
141
+ }
142
+
132
143
  _emit(cid, sid, block, role, extra) {
133
- this._bc({ type: 'streaming_progress', sessionId: sid, conversationId: cid, block, blockRole: role, seq: this._seq(sid), timestamp: Date.now(), ...extra });
144
+ const dbSid = this._dbSession(cid, sid);
145
+ const seq = this._seq(sid);
146
+ try { this._q.createChunk(dbSid, cid, seq, block.type || 'unknown', block); } catch (_) {}
147
+ this._bc({ type: 'streaming_progress', sessionId: dbSid, conversationId: cid, block, blockRole: role, seq, timestamp: Date.now(), ...extra });
134
148
  }
135
149
 
136
150
  _emitBlocks(cid, sid, blocks, role) {
@@ -143,20 +157,28 @@ export class JsonlWatcher {
143
157
  _startStreaming(cid, sid) {
144
158
  if (this._streaming.has(sid)) return;
145
159
  this._streaming.add(sid);
146
- this._bc({ type: 'streaming_start', sessionId: sid, conversationId: cid, agentId: 'cli-claude', timestamp: Date.now() });
160
+ const dbSid = this._dbSession(cid, sid);
161
+ this._q.setIsStreaming(cid, true);
162
+ this._bc({ type: 'streaming_start', sessionId: dbSid, conversationId: cid, agentId: 'cli-claude', timestamp: Date.now() });
147
163
  }
148
164
 
149
165
  _endStreaming(cid, sid) {
150
166
  if (!this._streaming.has(sid)) return;
151
167
  this._streaming.delete(sid);
152
- this._bc({ type: 'streaming_complete', sessionId: sid, conversationId: cid, agentId: 'cli-claude', eventCount: 0, seq: this._seq(sid), timestamp: Date.now() });
168
+ const dbSid = this._sessions.get(sid);
169
+ if (dbSid) {
170
+ try { this._q.updateSession(dbSid, { status: 'completed', completed_at: Date.now() }); } catch (_) {}
171
+ }
172
+ this._q.setIsStreaming(cid, false);
173
+ this._bc({ type: 'streaming_complete', sessionId: dbSid || sid, conversationId: cid, agentId: 'cli-claude', eventCount: 0, seq: this._seq(sid), timestamp: Date.now() });
174
+ this._sessions.delete(sid);
153
175
  }
154
176
 
155
177
  _route(cid, sid, e) {
156
178
  if (e.type === 'queue-operation' || e.type === 'last-prompt' || (e.type === 'user' && e.isMeta)) return;
157
179
 
158
180
  if (e.isApiErrorMessage && e.error === 'rate_limit') {
159
- this._bc({ type: 'streaming_error', sessionId: sid, conversationId: cid, error: 'Rate limit hit', recoverable: true, timestamp: Date.now() });
181
+ this._bc({ type: 'streaming_error', sessionId: this._sessions.get(sid) || sid, conversationId: cid, error: 'Rate limit hit', recoverable: true, timestamp: Date.now() });
160
182
  return;
161
183
  }
162
184
 
@@ -185,7 +207,14 @@ export class JsonlWatcher {
185
207
  if (e.type === 'user' && e.message?.content) {
186
208
  if (e.isCompactSummary) { this._emit(cid, sid, { type: 'compact_summary', content: e.message.content }, 'system'); return; }
187
209
  this._startStreaming(cid, sid);
188
- const blocks = Array.isArray(e.message.content) ? e.message.content : [];
210
+ const content = e.message.content;
211
+ const textParts = Array.isArray(content)
212
+ ? content.filter(b => b.type === 'text' && b.text && !b.text.startsWith('<task-notification>')).map(b => b.text).join('\n')
213
+ : (typeof content === 'string' && !content.startsWith('<task-notification>') ? content : '');
214
+ if (textParts) {
215
+ try { this._q.createMessage(cid, 'user', textParts); } catch (_) {}
216
+ }
217
+ const blocks = Array.isArray(content) ? content : [];
189
218
  for (const b of blocks) if (b.type === 'tool_result') this._emit(cid, sid, b, 'tool_result');
190
219
  return;
191
220
  }
@@ -1,162 +1,188 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { EventEmitter } from 'events';
4
-
5
- class PluginLoader extends EventEmitter {
6
- constructor(pluginDir) {
7
- super();
8
- this.pluginDir = pluginDir;
9
- this.registry = new Map();
10
- this.instances = new Map();
11
- this.states = new Map();
12
- this.watchers = new Map();
13
- this.errorCounts = new Map();
14
- this.fileMap = new Map();
15
- }
16
-
17
- async loadPlugin(fileName) {
18
- const filePath = path.join(this.pluginDir, `${fileName}.js`);
19
- if (!fs.existsSync(filePath)) {
20
- throw new Error(`Plugin file not found: ${filePath}`);
21
- }
22
- const fileUrl = `file://${filePath}?v=${Date.now()}`;
23
- try {
24
- const plugin = await import(fileUrl);
25
- const mod = plugin.default || plugin;
26
- const regName = mod.name || fileName;
27
- this.registry.set(regName, mod);
28
- this.fileMap.set(regName, fileName);
29
- return mod;
30
- } catch (error) {
31
- console.error(`Failed to load plugin ${fileName}:`, error.message);
32
- throw error;
33
- }
34
- }
35
-
36
- async initializePlugin(name, config) {
37
- const plugin = this.registry.get(name);
38
- if (!plugin) {
39
- throw new Error(`Plugin ${name} not found in registry`);
40
- }
41
- if (this.instances.has(name)) {
42
- return this.instances.get(name);
43
- }
44
- for (const depName of (plugin.dependencies || [])) {
45
- if (!this.instances.has(depName)) {
46
- await this.initializePlugin(depName, config);
47
- }
48
- }
49
- try {
50
- const result = await plugin.init(config, this.instances);
51
- this.instances.set(name, result);
52
- return result;
53
- } catch (error) {
54
- console.error(`[PluginLoader] Error initializing ${name}:`, error.message);
55
- throw error;
56
- }
57
- }
58
-
59
- get(name) {
60
- return this.instances.get(name);
61
- }
62
-
63
- async reloadPlugin(name) {
64
- const plugin = this.registry.get(name);
65
- if (!plugin) {
66
- console.warn(`[PluginLoader] Cannot reload ${name}: not found`);
67
- return;
68
- }
69
- const state = this.instances.get(name);
70
- if (!state) {
71
- console.warn(`[PluginLoader] Cannot reload ${name}: not initialized`);
72
- return;
73
- }
74
- try {
75
- if (state.stop) await state.stop();
76
- const fileName = this.fileMap.get(name) || name;
77
- await this.loadPlugin(fileName);
78
- const reloadedPlugin = this.registry.get(name);
79
- const newState = await reloadedPlugin.reload(state);
80
- this.instances.set(name, newState);
81
- this.emit('reload', { name, success: true });
82
- console.log(`[PluginLoader] Reloaded plugin: ${name}`);
83
- } catch (error) {
84
- console.error(`[PluginLoader] Error reloading ${name}:`, error.message);
85
- this.emit('reload', { name, success: false, error: error.message });
86
- }
87
- }
88
-
89
- watchPlugin(name, callback) {
90
- const fileName = this.fileMap.get(name) || name;
91
- const filePath = path.join(this.pluginDir, `${fileName}.js`);
92
- if (this.watchers.has(name)) return;
93
- const watcher = fs.watch(filePath, async (eventType) => {
94
- if (eventType === 'change') {
95
- setTimeout(() => callback(name), 100);
96
- }
97
- });
98
- this.watchers.set(name, watcher);
99
- }
100
-
101
- unwatchPlugin(name) {
102
- const watcher = this.watchers.get(name);
103
- if (watcher) {
104
- watcher.close();
105
- this.watchers.delete(name);
106
- }
107
- }
108
-
109
- async loadAllPlugins(config) {
110
- if (!fs.existsSync(this.pluginDir)) {
111
- fs.mkdirSync(this.pluginDir, { recursive: true });
112
- return;
113
- }
114
- const files = fs.readdirSync(this.pluginDir).filter(f => f.endsWith('.js'));
115
- for (const file of files) {
116
- const fileName = file.replace('.js', '');
117
- try {
118
- await this.loadPlugin(fileName);
119
- } catch (error) {
120
- console.error(`[PluginLoader] Failed to load ${fileName}:`, error.message);
121
- }
122
- }
123
- const sorted = this.topologicalSort();
124
- for (const name of sorted) {
125
- try {
126
- await this.initializePlugin(name, config);
127
- } catch (error) {
128
- console.error(`[PluginLoader] Failed to initialize ${name}:`, error.message);
129
- }
130
- }
131
- }
132
-
133
- topologicalSort() {
134
- const visited = new Set(), result = [];
135
- const visit = (name) => {
136
- if (visited.has(name)) return;
137
- visited.add(name);
138
- for (const dep of (this.registry.get(name)?.dependencies || [])) { if (this.registry.has(dep)) visit(dep); }
139
- result.push(name);
140
- };
141
- for (const name of this.registry.keys()) visit(name);
142
- return result;
143
- }
144
-
145
- async shutdown() {
146
- const sorted = this.topologicalSort().reverse();
147
- for (const name of sorted) {
148
- const state = this.instances.get(name);
149
- if (state && state.stop) {
150
- try {
151
- await state.stop();
152
- } catch (error) {
153
- console.error(`[PluginLoader] Error stopping ${name}:`, error.message);
154
- }
155
- }
156
- this.unwatchPlugin(name);
157
- }
158
- this.instances.clear();
159
- this.registry.clear();
160
- }
161
- }
162
- export default PluginLoader;
1
+ // Plugin loader - manages registry, dependencies, hot reload, error isolation
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { EventEmitter } from 'events';
6
+
7
+ class PluginLoader extends EventEmitter {
8
+ constructor(pluginDir) {
9
+ super();
10
+ this.pluginDir = pluginDir;
11
+ this.registry = new Map(); // name => plugin module
12
+ this.instances = new Map(); // name => initialized plugin state
13
+ this.states = new Map(); // name => { routes, wsHandlers, api, ... }
14
+ this.watchers = new Map(); // name => file watcher
15
+ this.errorCounts = new Map(); // name => { count, firstTime }
16
+ }
17
+
18
+ // Load plugin module from disk
19
+ async loadPlugin(name) {
20
+ const filePath = path.join(this.pluginDir, `${name}.js`);
21
+ if (!fs.existsSync(filePath)) {
22
+ throw new Error(`Plugin file not found: ${filePath}`);
23
+ }
24
+ // Clear module cache for hot reload (ES modules use import cache differently)
25
+ const fileUrl = `file://${filePath}?v=${Date.now()}`;
26
+ try {
27
+ const plugin = await import(fileUrl);
28
+ this.registry.set(name, plugin.default || plugin);
29
+ return plugin.default || plugin;
30
+ } catch (error) {
31
+ console.error(`Failed to load plugin ${name}:`, error.message);
32
+ throw error;
33
+ }
34
+ }
35
+
36
+ // Initialize plugin and all dependencies
37
+ async initializePlugin(name, config) {
38
+ const plugin = this.registry.get(name);
39
+ if (!plugin) {
40
+ throw new Error(`Plugin ${name} not found in registry`);
41
+ }
42
+
43
+ // Check if already initialized
44
+ if (this.instances.has(name)) {
45
+ return this.instances.get(name);
46
+ }
47
+
48
+ // Initialize dependencies first
49
+ for (const depName of (plugin.dependencies || [])) {
50
+ if (!this.instances.has(depName)) {
51
+ await this.initializePlugin(depName, config);
52
+ }
53
+ }
54
+
55
+ // Initialize this plugin
56
+ try {
57
+ const result = await plugin.init(config, this.instances);
58
+ this.instances.set(name, result);
59
+ return result;
60
+ } catch (error) {
61
+ console.error(`[PluginLoader] Error initializing ${name}:`, error.message);
62
+ throw error;
63
+ }
64
+ }
65
+
66
+ // Get initialized plugin result
67
+ get(name) {
68
+ return this.instances.get(name);
69
+ }
70
+
71
+ // Hot reload a plugin
72
+ async reloadPlugin(name) {
73
+ const plugin = this.registry.get(name);
74
+ if (!plugin) {
75
+ console.warn(`[PluginLoader] Cannot reload ${name}: not found`);
76
+ return;
77
+ }
78
+
79
+ const state = this.instances.get(name);
80
+ if (!state) {
81
+ console.warn(`[PluginLoader] Cannot reload ${name}: not initialized`);
82
+ return;
83
+ }
84
+
85
+ try {
86
+ // Stop old instance
87
+ if (state.stop) await state.stop();
88
+
89
+ // Reload plugin module
90
+ this.loadPlugin(name);
91
+ const reloadedPlugin = this.registry.get(name);
92
+
93
+ // Reinitialize with preserved state
94
+ const newState = await reloadedPlugin.reload(state);
95
+ this.instances.set(name, newState);
96
+ this.emit('reload', { name, success: true });
97
+ console.log(`[PluginLoader] Reloaded plugin: ${name}`);
98
+ } catch (error) {
99
+ console.error(`[PluginLoader] Error reloading ${name}:`, error.message);
100
+ this.emit('reload', { name, success: false, error: error.message });
101
+ }
102
+ }
103
+
104
+ // Watch a plugin file for changes
105
+ watchPlugin(name, callback) {
106
+ const filePath = path.join(this.pluginDir, `${name}.js`);
107
+ if (this.watchers.has(name)) {
108
+ return; // Already watching
109
+ }
110
+
111
+ const watcher = fs.watch(filePath, async (eventType) => {
112
+ if (eventType === 'change') {
113
+ setTimeout(() => callback(name), 100); // Debounce
114
+ }
115
+ });
116
+
117
+ this.watchers.set(name, watcher);
118
+ }
119
+
120
+ // Stop watching a plugin
121
+ unwatchPlugin(name) {
122
+ const watcher = this.watchers.get(name);
123
+ if (watcher) {
124
+ watcher.close();
125
+ this.watchers.delete(name);
126
+ }
127
+ }
128
+
129
+ // Load all plugins from directory
130
+ async loadAllPlugins(config) {
131
+ if (!fs.existsSync(this.pluginDir)) {
132
+ fs.mkdirSync(this.pluginDir, { recursive: true });
133
+ return;
134
+ }
135
+
136
+ const files = fs.readdirSync(this.pluginDir).filter(f => f.endsWith('.js'));
137
+ for (const file of files) {
138
+ const name = file.replace('.js', '');
139
+ try {
140
+ await this.loadPlugin(name);
141
+ } catch (error) {
142
+ console.error(`[PluginLoader] Failed to load ${name}:`, error.message);
143
+ }
144
+ }
145
+
146
+ // Initialize in dependency order
147
+ const sorted = this.topologicalSort();
148
+ for (const name of sorted) {
149
+ try {
150
+ await this.initializePlugin(name, config);
151
+ } catch (error) {
152
+ console.error(`[PluginLoader] Failed to initialize ${name}:`, error.message);
153
+ }
154
+ }
155
+ }
156
+
157
+ // Topological sort by dependencies
158
+ topologicalSort() {
159
+ const visited = new Set(), result = [];
160
+ const visit = (name) => {
161
+ if (visited.has(name)) return;
162
+ visited.add(name);
163
+ for (const dep of (this.registry.get(name)?.dependencies || [])) { if (this.registry.has(dep)) visit(dep); }
164
+ result.push(name);
165
+ };
166
+ for (const name of this.registry.keys()) visit(name);
167
+ return result;
168
+ }
169
+
170
+ // Graceful shutdown
171
+ async shutdown() {
172
+ const sorted = this.topologicalSort().reverse();
173
+ for (const name of sorted) {
174
+ const state = this.instances.get(name);
175
+ if (state && state.stop) {
176
+ try {
177
+ await state.stop();
178
+ } catch (error) {
179
+ console.error(`[PluginLoader] Error stopping ${name}:`, error.message);
180
+ }
181
+ }
182
+ this.unwatchPlugin(name);
183
+ }
184
+ this.instances.clear();
185
+ this.registry.clear();
186
+ }
187
+ }
188
+ export default PluginLoader;
@@ -49,9 +49,10 @@ export default {
49
49
  if (!agent) return res.status(404).json({ error: 'Agent not found' });
50
50
 
51
51
  const session = stream.api.createSession(id);
52
+ // Use runClaudeWithStreaming instead
52
53
  activeExecutions.set(id, { sessionId: session.id });
53
54
 
54
- res.json({ sessionId: session.id });
55
+ res.json({ sessionId: session.id, pid: proc.pid });
55
56
  } catch (e) {
56
57
  res.status(500).json({ error: e.message });
57
58
  }
@@ -1,6 +1,6 @@
1
1
  // Database plugin - SQLite init, schema, checkpoint recovery
2
2
 
3
- import * as dbModule from '../../database.js';
3
+ import * as dbModule from '../database.js';
4
4
 
5
5
  export default {
6
6
  name: 'database',
@@ -8,8 +8,7 @@ export default {
8
8
  dependencies: ['database'],
9
9
 
10
10
  async init(config, plugins) {
11
- const dbPlugin = plugins.get('database');
12
- const db = dbPlugin?.api || {};
11
+ const db = plugins.get('database');
13
12
  const activeSessions = new Map();
14
13
  const pendingMessages = new Map();
15
14
  const rateLimitState = new Map();
@@ -38,7 +38,7 @@ export async function autoProvision(TOOLS, statusCache, install, broadcast) {
38
38
  broadcast({ type: 'tool_update_failed', toolId: tool.id, data: result });
39
39
  }
40
40
  } else {
41
- log(`${tool.id} v${status.installedVersion || 'unknown'} up-to-date`);
41
+ log(`${tool.id} v${status.installedVersion} up-to-date`);
42
42
  broadcast({ type: 'tool_status_update', toolId: tool.id, data: { installed: true, isUpToDate: true, installedVersion: status.installedVersion, status: 'installed' } });
43
43
  }
44
44
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.722",
3
+ "version": "1.0.723",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -4479,7 +4479,7 @@ const BROADCAST_TYPES = new Set([
4479
4479
  'streaming_start', 'streaming_progress', 'streaming_complete', 'streaming_error',
4480
4480
  'tool_install_started', 'tool_install_progress', 'tool_install_complete', 'tool_install_failed',
4481
4481
  'tool_update_progress', 'tool_update_complete', 'tool_update_failed',
4482
- 'tool_status_update', 'tool_update_available',
4482
+ 'tool_status_update',
4483
4483
  'tools_update_started', 'tools_update_complete', 'tools_refresh_complete',
4484
4484
  'pm2_monit_update', 'pm2_monitoring_started', 'pm2_monitoring_stopped',
4485
4485
  'pm2_list_response', 'pm2_start_response', 'pm2_stop_response',
@@ -5257,7 +5257,7 @@ import PluginLoader from './lib/plugin-loader.js';
5257
5257
  const pluginLoader = new PluginLoader(path.join(__dirname, 'lib', 'plugins'));
5258
5258
  async function loadPluginExtensions() {
5259
5259
  try {
5260
- await pluginLoader.loadAllPlugins({ router: expressApp, baseUrl: BASE_URL, logger: console, env: process.env });
5260
+ await pluginLoader.loadAllPlugins({ router: app, baseUrl: BASE_URL, logger: console, env: process.env });
5261
5261
  const names = Array.from(pluginLoader.registry.keys());
5262
5262
  if (names.length > 0) {
5263
5263
  for (const name of names) {
@@ -5266,7 +5266,7 @@ async function loadPluginExtensions() {
5266
5266
  for (const route of state.routes) {
5267
5267
  const fullPath = BASE_URL + route.path;
5268
5268
  const method = (route.method || 'GET').toLowerCase();
5269
- if (expressApp[method]) expressApp[method](fullPath, route.handler);
5269
+ if (app[method]) app[method](fullPath, route.handler);
5270
5270
  }
5271
5271
  }
5272
5272
  console.log(`[PLUGINS] Loaded extensions: ${names.join(', ')}`);