agentgui 1.0.652 → 1.0.653

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentgui",
3
- "version": "1.0.652",
3
+ "version": "1.0.653",
4
4
  "description": "Multi-agent ACP client with real-time communication",
5
5
  "type": "module",
6
6
  "main": "server.js",
package/server.js CHANGED
@@ -302,7 +302,6 @@ const rateLimitState = new Map();
302
302
  const activeProcessesByRunId = new Map();
303
303
  const activeProcessesByConvId = new Map(); // Store process handles by conversationId for steering
304
304
  const steeringTimeouts = new Map(); // Track timeout handles for process cleanup
305
- const acpQueries = queries;
306
305
  const checkpointManager = new CheckpointManager(queries);
307
306
  const STUCK_AGENT_THRESHOLD_MS = 600000;
308
307
  const NO_PID_GRACE_PERIOD_MS = 60000;
@@ -1592,115 +1591,6 @@ const server = http.createServer(async (req, res) => {
1592
1591
  return;
1593
1592
  }
1594
1593
 
1595
- const oldRunByIdMatch = pathOnly.match(/^\/api\/runs\/([^/]+)$/);
1596
- if (oldRunByIdMatch) {
1597
- const runId = oldRunByIdMatch[1];
1598
- const session = queries.getSession(runId);
1599
-
1600
- if (!session) {
1601
- sendJSON(req, res, 404, { error: 'Run not found' });
1602
- return;
1603
- }
1604
-
1605
- if (req.method === 'GET') {
1606
- sendJSON(req, res, 200, {
1607
- id: session.id,
1608
- status: session.status,
1609
- started_at: session.started_at,
1610
- completed_at: session.completed_at,
1611
- agentId: session.agentId,
1612
- input: null,
1613
- output: null
1614
- });
1615
- return;
1616
- }
1617
-
1618
- if (req.method === 'DELETE') {
1619
- queries.deleteSession(runId);
1620
- sendJSON(req, res, 204, {});
1621
- return;
1622
- }
1623
-
1624
- if (req.method === 'POST') {
1625
- if (session.status !== 'interrupted') {
1626
- sendJSON(req, res, 409, { error: 'Can only resume interrupted runs' });
1627
- return;
1628
- }
1629
-
1630
- let body = '';
1631
- for await (const chunk of req) { body += chunk; }
1632
- let parsed = {};
1633
- try { parsed = body ? JSON.parse(body) : {}; } catch {}
1634
-
1635
- const { input } = parsed;
1636
- if (!input) {
1637
- sendJSON(req, res, 400, { error: 'Missing input in request body' });
1638
- return;
1639
- }
1640
-
1641
- const conv = queries.getConversation(session.conversationId);
1642
- const resolvedAgentId = session.agentId || conv?.agentId || 'claude-code';
1643
- const resolvedModel = conv?.model || null;
1644
- const cwd = conv?.workingDirectory || STARTUP_CWD;
1645
-
1646
- queries.updateSession(runId, { status: 'pending' });
1647
-
1648
- const message = queries.createMessage(session.conversationId, 'user', typeof input === 'string' ? input : JSON.stringify(input));
1649
-
1650
- processMessageWithStreaming(session.conversationId, message.id, runId, typeof input === 'string' ? input : JSON.stringify(input), resolvedAgentId, resolvedModel);
1651
-
1652
- sendJSON(req, res, 200, {
1653
- id: session.id,
1654
- status: 'pending',
1655
- started_at: session.started_at,
1656
- agentId: resolvedAgentId
1657
- });
1658
- return;
1659
- }
1660
- }
1661
-
1662
- const oldRunCancelMatch = pathOnly.match(/^\/api\/runs\/([^/]+)\/cancel$/);
1663
- if (oldRunCancelMatch && req.method === 'POST') {
1664
- const runId = oldRunCancelMatch[1];
1665
- const session = queries.getSession(runId);
1666
-
1667
- if (!session) {
1668
- sendJSON(req, res, 404, { error: 'Run not found' });
1669
- return;
1670
- }
1671
-
1672
- const conversationId = session.conversationId;
1673
- const entry = activeExecutions.get(conversationId);
1674
-
1675
- if (entry && entry.sessionId === runId) {
1676
- const { pid } = entry;
1677
- if (pid) {
1678
- try {
1679
- process.kill(-pid, 'SIGKILL');
1680
- } catch {
1681
- try {
1682
- process.kill(pid, 'SIGKILL');
1683
- } catch (e) {}
1684
- }
1685
- }
1686
- }
1687
-
1688
- queries.updateSession(runId, { status: 'interrupted', completed_at: Date.now() });
1689
- queries.setIsStreaming(conversationId, false);
1690
- activeExecutions.delete(conversationId);
1691
-
1692
- broadcastSync({
1693
- type: 'streaming_complete',
1694
- sessionId: runId,
1695
- conversationId,
1696
- interrupted: true,
1697
- timestamp: Date.now()
1698
- });
1699
-
1700
- sendJSON(req, res, 204, {});
1701
- return;
1702
- }
1703
-
1704
1594
  const scriptsMatch = pathOnly.match(/^\/api\/conversations\/([^/]+)\/scripts$/);
1705
1595
  if (scriptsMatch && req.method === 'GET') {
1706
1596
  const conv = queries.getConversation(scriptsMatch[1]);
@@ -3597,6 +3487,21 @@ function createChunkBatcher() {
3597
3487
  return { add, drain };
3598
3488
  }
3599
3489
 
3490
+ function parseRateLimitResetTime(text) {
3491
+ const match = text.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
3492
+ if (!match) return 300;
3493
+ let hours = parseInt(match[1], 10);
3494
+ const minutes = match[2] ? parseInt(match[2], 10) : 0;
3495
+ const period = match[3]?.toLowerCase();
3496
+ if (period === 'pm' && hours !== 12) hours += 12;
3497
+ if (period === 'am' && hours === 12) hours = 0;
3498
+ const now = new Date();
3499
+ const resetTime = new Date(now);
3500
+ resetTime.setUTCHours(hours, minutes, 0, 0);
3501
+ if (resetTime <= now) resetTime.setUTCDate(resetTime.getUTCDate() + 1);
3502
+ return Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
3503
+ }
3504
+
3600
3505
  async function processMessageWithStreaming(conversationId, messageId, sessionId, content, agentId, model, subAgent) {
3601
3506
  const startTime = Date.now();
3602
3507
  touchACP(agentId);
@@ -3700,28 +3605,8 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
3700
3605
  if (rateLimitTextMatch) {
3701
3606
  debugLog(`[rate-limit] Detected rate limit message in stream for conv ${conversationId}`);
3702
3607
 
3703
- // Extract reset time from message
3704
- let retryAfterSec = 300; // default 5 minutes
3705
- const resetTimeMatch = block.text.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
3706
- if (resetTimeMatch) {
3707
- let hours = parseInt(resetTimeMatch[1], 10);
3708
- const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
3709
- const period = resetTimeMatch[3]?.toLowerCase();
3710
-
3711
- if (period === 'pm' && hours !== 12) hours += 12;
3712
- if (period === 'am' && hours === 12) hours = 0;
3713
-
3714
- const now = new Date();
3715
- const resetTime = new Date(now);
3716
- resetTime.setUTCHours(hours, minutes, 0, 0);
3717
-
3718
- if (resetTime <= now) {
3719
- resetTime.setUTCDate(resetTime.getUTCDate() + 1);
3720
- }
3721
-
3722
- retryAfterSec = Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
3723
- debugLog(`[rate-limit] Parsed reset time: ${resetTime.toISOString()}, retry in ${retryAfterSec}s`);
3724
- }
3608
+ const retryAfterSec = parseRateLimitResetTime(block.text);
3609
+ debugLog(`[rate-limit] Parsed reset time, retry in ${retryAfterSec}s`);
3725
3610
 
3726
3611
  // Kill the running process
3727
3612
  const entry = activeExecutions.get(conversationId);
@@ -3833,26 +3718,7 @@ async function processMessageWithStreaming(conversationId, messageId, sessionId,
3833
3718
  if (rateLimitResultMatch) {
3834
3719
  debugLog(`[rate-limit] Detected rate limit in result for conv ${conversationId}`);
3835
3720
 
3836
- let retryAfterSec = 300;
3837
- const resetTimeMatch = resultText.match(/resets?\s+(?:at\s+)?(\d{1,2})(?::(\d{2}))?\s*(am|pm)?\s*\(?(UTC|[A-Z]{2,4})\)?/i);
3838
- if (resetTimeMatch) {
3839
- let hours = parseInt(resetTimeMatch[1], 10);
3840
- const minutes = resetTimeMatch[2] ? parseInt(resetTimeMatch[2], 10) : 0;
3841
- const period = resetTimeMatch[3]?.toLowerCase();
3842
-
3843
- if (period === 'pm' && hours !== 12) hours += 12;
3844
- if (period === 'am' && hours === 12) hours = 0;
3845
-
3846
- const now = new Date();
3847
- const resetTime = new Date(now);
3848
- resetTime.setUTCHours(hours, minutes, 0, 0);
3849
-
3850
- if (resetTime <= now) {
3851
- resetTime.setUTCDate(resetTime.getUTCDate() + 1);
3852
- }
3853
-
3854
- retryAfterSec = Math.max(60, Math.ceil((resetTime.getTime() - now.getTime()) / 1000));
3855
- }
3721
+ const retryAfterSec = parseRateLimitResetTime(resultText);
3856
3722
 
3857
3723
  const entry = activeExecutions.get(conversationId);
3858
3724
  if (entry && entry.pid) {
package/static/index.html CHANGED
@@ -3245,6 +3245,7 @@
3245
3245
  var _escHtmlRe = /[&<>"']/g;
3246
3246
  window._escHtml = function(t) { return t.replace(_escHtmlRe, function(c) { return _escHtmlMap[c]; }); };
3247
3247
  </script>
3248
+ <script defer src="/gm/js/conversations.js"></script>
3248
3249
  <script defer src="/gm/js/event-processor.js"></script>
3249
3250
  <script defer src="/gm/js/streaming-renderer.js"></script>
3250
3251
  <script defer src="/gm/js/image-loader.js"></script>
@@ -3252,12 +3253,10 @@
3252
3253
  <script defer src="/gm/js/event-consolidator.js"></script>
3253
3254
  <script defer src="/gm/js/websocket-manager.js"></script>
3254
3255
  <script defer src="/gm/js/ws-client.js"></script>
3255
- <script defer src="/gm/js/event-filter.js"></script>
3256
- <script defer src="/gm/js/syntax-highlighter.js"></script>
3256
+ <script defer src="/gm/js/syntax-highlighter.js"></script>
3257
3257
  <script defer src="/gm/js/dialogs.js"></script>
3258
3258
  <script defer src="/gm/js/ui-components.js"></script>
3259
3259
  <script defer src="/gm/js/state-barrier.js"></script>
3260
- <script defer src="/gm/js/conversations.js"></script>
3261
3260
  <script defer src="/gm/js/terminal.js"></script>
3262
3261
  <script defer src="/gm/js/script-runner.js"></script>
3263
3262
  <script defer src="/gm/js/tools-manager.js"></script>
@@ -4,15 +4,6 @@
4
4
  * for Claude Code streaming execution display
5
5
  */
6
6
 
7
- function pathSplit(p) {
8
- return p.split(/[\/\\]/).filter(Boolean);
9
- }
10
-
11
- function pathBasename(p) {
12
- const parts = pathSplit(p);
13
- return parts.length ? parts.pop() : '';
14
- }
15
-
16
7
  class StreamingRenderer {
17
8
  constructor(config = {}) {
18
9
  // Configuration
package/lib/compressor.js DELETED
@@ -1,125 +0,0 @@
1
- /**
2
- * Compressor: tokenize text fields → msgpackr → optional gzip
3
- *
4
- * Transport: event → msgpackr.pack() → gzip if >512B → binary WS frame
5
- * Storage: text → token array (Uint32) → msgpackr.pack() → BLOB
6
- */
7
- import zlib from 'zlib';
8
- import { pack, unpack } from 'msgpackr';
9
- import { encode as encodeTokens, decode as decodeTokens } from 'gpt-tokenizer';
10
-
11
- // Magic prefix stored at start of compressed BLOBs so we can detect them
12
- const MAGIC = Buffer.from([0xC0, 0xDE]); // "CODE"
13
- const GZIP_MAGIC = Buffer.from([0x1f, 0x8b]);
14
-
15
- // ── Token helpers ─────────────────────────────────────────────────────────────
16
-
17
- export function tokenize(text) {
18
- if (typeof text !== 'string' || text.length === 0) return null;
19
- try {
20
- return new Uint32Array(encodeTokens(text));
21
- } catch {
22
- return null;
23
- }
24
- }
25
-
26
- export function detokenize(tokens) {
27
- try {
28
- const arr = tokens instanceof Uint32Array ? Array.from(tokens) : tokens;
29
- return decodeTokens(arr);
30
- } catch {
31
- return null;
32
- }
33
- }
34
-
35
- // ── Storage compression (text → tokens → msgpack BLOB) ────────────────────────
36
-
37
- /**
38
- * Compress a string for database storage.
39
- * Returns a Buffer starting with MAGIC prefix.
40
- */
41
- export function compressForStorage(text) {
42
- if (typeof text !== 'string') return null;
43
- const tokens = tokenize(text);
44
- if (!tokens) return null;
45
- const packed = pack({ t: Array.from(tokens) });
46
- return Buffer.concat([MAGIC, packed]);
47
- }
48
-
49
- /**
50
- * Decompress a storage BLOB back to a string.
51
- * Returns null if not a compressed BLOB (caller should use raw value).
52
- */
53
- export function decompressFromStorage(buf) {
54
- if (!Buffer.isBuffer(buf) && !(buf instanceof Uint8Array)) return null;
55
- const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
56
- if (b.length < MAGIC.length || b[0] !== MAGIC[0] || b[1] !== MAGIC[1]) return null;
57
- try {
58
- const { t } = unpack(b.slice(MAGIC.length));
59
- return detokenize(t);
60
- } catch {
61
- return null;
62
- }
63
- }
64
-
65
- // ── Transport compression (event → msgpack → gzip → binary frame) ─────────────
66
-
67
- const GZ_THRESHOLD = 512; // bytes — compress if msgpack payload exceeds this
68
-
69
- /**
70
- * Pack one event (or array of events) into a binary buffer for WebSocket transport.
71
- * Format: [ 0x01 ] + gzip(msgpack(data)) when compressed
72
- * [ 0x00 ] + msgpack(data) when not compressed
73
- */
74
- export function packForTransport(data) {
75
- const packed = pack(data);
76
- if (packed.length > GZ_THRESHOLD) {
77
- try {
78
- const compressed = zlib.gzipSync(packed, { level: 6 });
79
- if (compressed.length < packed.length * 0.9) {
80
- const out = Buffer.allocUnsafe(1 + compressed.length);
81
- out[0] = 0x01; // flag: gzipped
82
- compressed.copy(out, 1);
83
- return out;
84
- }
85
- } catch (_) {}
86
- }
87
- const out = Buffer.allocUnsafe(1 + packed.length);
88
- out[0] = 0x00; // flag: plain msgpack
89
- packed.copy(out, 1);
90
- return out;
91
- }
92
-
93
- /**
94
- * Unpack a binary buffer received from WebSocket transport.
95
- */
96
- export function unpackFromTransport(buf) {
97
- const b = Buffer.isBuffer(buf) ? buf : Buffer.from(buf);
98
- if (b.length < 2) return null;
99
- const flag = b[0];
100
- const payload = b.slice(1);
101
- if (flag === 0x01) {
102
- return unpack(zlib.gunzipSync(payload));
103
- }
104
- return unpack(payload);
105
- }
106
-
107
- // ── Tokenize specific fields in an event object (for storage) ─────────────────
108
-
109
- const TEXT_FIELDS = ['content', 'text', 'data', 'output', 'input', 'message', 'prompt', 'response'];
110
-
111
- /**
112
- * Walk an event object and compress any large text fields in-place for storage.
113
- * Returns the (possibly mutated) object — safe to pass to JSON.stringify or pack().
114
- */
115
- export function compressEventFields(obj) {
116
- if (!obj || typeof obj !== 'object') return obj;
117
- for (const key of TEXT_FIELDS) {
118
- const val = obj[key];
119
- if (typeof val === 'string' && val.length > 64) {
120
- const buf = compressForStorage(val);
121
- if (buf) obj[key] = buf;
122
- }
123
- }
124
- return obj;
125
- }
@@ -1,138 +0,0 @@
1
- /**
2
- * Request Manager - Phase 2: Request Lifetime Management
3
- * Tracks in-flight requests with unique IDs, enables cancellation on navigation
4
- * Prevents race conditions where older requests complete after newer ones
5
- */
6
-
7
- class RequestManager {
8
- constructor() {
9
- this._requestId = 0;
10
- this._inflightRequests = new Map(); // requestId -> { conversationId, abortController, timestamp, priority }
11
- this._activeLoadId = null; // Track which request is currently being rendered
12
- }
13
-
14
- /**
15
- * Start a new load request for a conversation
16
- * Returns a request token that must be verified before rendering
17
- */
18
- startLoadRequest(conversationId, priority = 'normal') {
19
- const requestId = ++this._requestId;
20
- const abortController = new AbortController();
21
-
22
- this._inflightRequests.set(requestId, {
23
- conversationId,
24
- abortController,
25
- timestamp: Date.now(),
26
- priority,
27
- status: 'pending'
28
- });
29
-
30
- return {
31
- requestId,
32
- abortSignal: abortController.signal,
33
- cancel: () => this._cancelRequest(requestId),
34
- verify: () => this._verifyRequest(requestId, conversationId)
35
- };
36
- }
37
-
38
- /**
39
- * Mark request as completed (allows rendering)
40
- */
41
- completeRequest(requestId) {
42
- const req = this._inflightRequests.get(requestId);
43
- if (req) {
44
- req.status = 'completed';
45
- this._activeLoadId = requestId;
46
- }
47
- }
48
-
49
- /**
50
- * Verify request is still valid before rendering
51
- * Returns true only if this is the most recent request for this conversation
52
- */
53
- _verifyRequest(requestId, conversationId) {
54
- const req = this._inflightRequests.get(requestId);
55
-
56
- // Request not found or cancelled
57
- if (!req) return false;
58
-
59
- // Request is for different conversation
60
- if (req.conversationId !== conversationId) return false;
61
-
62
- // Find all requests for this conversation
63
- const allForConv = Array.from(this._inflightRequests.entries())
64
- .filter(([_, r]) => r.conversationId === conversationId && r.status === 'completed')
65
- .sort((a, b) => b[0] - a[0]); // Sort by requestId descending (newest first)
66
-
67
- // This request is the newest completed one for this conversation
68
- return allForConv.length > 0 && allForConv[0][0] === requestId;
69
- }
70
-
71
- /**
72
- * Cancel a request (aborts any pending network operations)
73
- */
74
- _cancelRequest(requestId) {
75
- const req = this._inflightRequests.get(requestId);
76
- if (req) {
77
- req.status = 'cancelled';
78
- req.abortController.abort();
79
- }
80
- }
81
-
82
- /**
83
- * Cancel all pending requests for a conversation
84
- */
85
- cancelConversationRequests(conversationId) {
86
- for (const [id, req] of this._inflightRequests.entries()) {
87
- if (req.conversationId === conversationId && req.status !== 'completed') {
88
- this._cancelRequest(id);
89
- }
90
- }
91
- }
92
-
93
- /**
94
- * Cancel all in-flight requests
95
- */
96
- cancelAllRequests() {
97
- for (const [id, req] of this._inflightRequests.entries()) {
98
- if (req.status !== 'completed') {
99
- this._cancelRequest(id);
100
- }
101
- }
102
- }
103
-
104
- /**
105
- * Clean up old requests to prevent memory leak
106
- */
107
- cleanup() {
108
- const now = Date.now();
109
- const maxAge = 60000; // Keep requests for 60 seconds
110
-
111
- for (const [id, req] of this._inflightRequests.entries()) {
112
- if (now - req.timestamp > maxAge) {
113
- this._inflightRequests.delete(id);
114
- }
115
- }
116
- }
117
-
118
- /**
119
- * Get debug info about in-flight requests
120
- */
121
- getDebugInfo() {
122
- return {
123
- activeLoadId: this._activeLoadId,
124
- inflightRequests: Array.from(this._inflightRequests.entries()).map(([id, req]) => ({
125
- requestId: id,
126
- conversationId: req.conversationId,
127
- timestamp: req.timestamp,
128
- status: req.status,
129
- priority: req.priority,
130
- age: Date.now() - req.timestamp
131
- }))
132
- };
133
- }
134
- }
135
-
136
- if (typeof window !== 'undefined') {
137
- window.RequestManager = new RequestManager();
138
- }