@siftd/connect-agent 0.2.52 → 0.2.53
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/README.md +16 -0
- package/dist/agent.js +63 -35
- package/dist/api.js +3 -1
- package/dist/websocket.d.ts +2 -0
- package/dist/websocket.js +37 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -133,6 +133,22 @@ export function buildWorkerPrompt(task, options) { ... }
|
|
|
133
133
|
- `VOYAGE_API_KEY` - (Optional) For better semantic search embeddings
|
|
134
134
|
- `DATABASE_URL` - (Optional) PostgreSQL for cloud memory persistence
|
|
135
135
|
|
|
136
|
+
### Model + Cost Controls (Optional)
|
|
137
|
+
|
|
138
|
+
- `LIA_FORCE_OPUS` - Defaults to `1`; set to `0` to allow model overrides
|
|
139
|
+
- `LIA_MODEL` - Explicit model override (defaults to Opus 4.5 when forced)
|
|
140
|
+
- `LIA_MAX_BUDGET_USD` - Per-worker budget cap (USD) for Claude Code CLI
|
|
141
|
+
- `LIA_TOOL_MAX_TOKENS` - Max tokens for `/todo` + `/cal` tool calls
|
|
142
|
+
- `LIA_WORKER_VERBOSE` - Set to `1` for verbose worker logging
|
|
143
|
+
|
|
144
|
+
### Usage Logging
|
|
145
|
+
|
|
146
|
+
Anthropic usage is logged per channel in:
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
~/Lia-Hub/shared/outputs/.lia/usage.jsonl
|
|
150
|
+
```
|
|
151
|
+
|
|
136
152
|
## Requirements
|
|
137
153
|
|
|
138
154
|
- Node.js 18+
|
package/dist/agent.js
CHANGED
|
@@ -423,46 +423,63 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
423
423
|
});
|
|
424
424
|
if (wsConnected) {
|
|
425
425
|
console.log('[AGENT] Using WebSocket for real-time communication\n');
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
console.log('[AGENT] WebSocket unavailable, using HTTP polling\n');
|
|
429
|
+
}
|
|
430
|
+
// Handle messages via WebSocket
|
|
431
|
+
wsClient.onMessage(async (wsMsg) => {
|
|
432
|
+
if (wsMsg.type === 'message' && wsMsg.content && wsMsg.id) {
|
|
433
|
+
const message = {
|
|
434
|
+
id: wsMsg.id,
|
|
435
|
+
content: wsMsg.content,
|
|
436
|
+
timestamp: wsMsg.timestamp || Date.now()
|
|
437
|
+
};
|
|
438
|
+
const response = await processMessage(message);
|
|
439
|
+
if (wsClient?.connected()) {
|
|
435
440
|
// Send response via WebSocket
|
|
436
441
|
wsClient.sendResponse(wsMsg.id, response);
|
|
437
442
|
console.log(`[AGENT] Response sent via WebSocket (${response.length} chars)`);
|
|
438
443
|
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
// Poll only if WebSocket is not connected (WS input is preferred for latency).
|
|
443
|
-
while (true) {
|
|
444
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
445
|
-
if (!wsClient?.connected()) {
|
|
446
|
-
try {
|
|
447
|
-
const { messages } = await pollMessages();
|
|
448
|
-
for (const msg of messages) {
|
|
449
|
-
const response = await processMessage(msg);
|
|
450
|
-
await sendResponse(msg.id, response);
|
|
451
|
-
console.log(`[AGENT] Response sent (${response.length} chars)`);
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
catch (error) {
|
|
455
|
-
console.error('[AGENT] Poll error:', error.message);
|
|
456
|
-
}
|
|
444
|
+
else {
|
|
445
|
+
await sendResponse(wsMsg.id, response);
|
|
446
|
+
console.log(`[AGENT] Response sent via HTTP (${response.length} chars)`);
|
|
457
447
|
}
|
|
458
448
|
}
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
449
|
+
});
|
|
450
|
+
console.log('[AGENT] Listening for WebSocket messages...\n');
|
|
451
|
+
const sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
|
|
452
|
+
const basePollInterval = pollInterval;
|
|
453
|
+
const maxPollBackoff = 30000;
|
|
454
|
+
const wsReconnectInterval = 30000;
|
|
455
|
+
let pollErrorCount = 0;
|
|
456
|
+
let lastPollErrorLog = 0;
|
|
457
|
+
let lastWsReconnectAttempt = Date.now();
|
|
458
|
+
let nextPollDelay = basePollInterval;
|
|
459
|
+
const logPollError = (message) => {
|
|
460
|
+
const now = Date.now();
|
|
461
|
+
if (now - lastPollErrorLog > 10000) {
|
|
462
|
+
lastPollErrorLog = now;
|
|
463
|
+
console.error('[AGENT] Poll error:', message);
|
|
464
|
+
}
|
|
465
|
+
};
|
|
466
|
+
const computeBackoff = (count) => {
|
|
467
|
+
const exponent = Math.min(count, 5);
|
|
468
|
+
const delay = Math.min(maxPollBackoff, basePollInterval * Math.pow(2, exponent));
|
|
469
|
+
const jitter = delay * 0.1 * (Math.random() * 2 - 1);
|
|
470
|
+
return Math.max(basePollInterval, Math.round(delay + jitter));
|
|
471
|
+
};
|
|
472
|
+
while (true) {
|
|
473
|
+
const now = Date.now();
|
|
474
|
+
if (!wsClient?.connected() && now - lastWsReconnectAttempt > wsReconnectInterval) {
|
|
475
|
+
lastWsReconnectAttempt = now;
|
|
476
|
+
void wsClient.connect();
|
|
477
|
+
}
|
|
478
|
+
if (!wsClient?.connected()) {
|
|
464
479
|
try {
|
|
465
480
|
const { messages } = await pollMessages();
|
|
481
|
+
pollErrorCount = 0;
|
|
482
|
+
nextPollDelay = basePollInterval;
|
|
466
483
|
for (const msg of messages) {
|
|
467
484
|
const response = await processMessage(msg);
|
|
468
485
|
await sendResponse(msg.id, response);
|
|
@@ -470,11 +487,22 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
470
487
|
}
|
|
471
488
|
}
|
|
472
489
|
catch (error) {
|
|
473
|
-
|
|
474
|
-
|
|
490
|
+
pollErrorCount += 1;
|
|
491
|
+
const status = error.status;
|
|
492
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
493
|
+
logPollError(message);
|
|
494
|
+
if (status === 401 || status === 403) {
|
|
495
|
+
nextPollDelay = maxPollBackoff;
|
|
496
|
+
}
|
|
497
|
+
else {
|
|
498
|
+
nextPollDelay = computeBackoff(pollErrorCount);
|
|
475
499
|
}
|
|
476
500
|
}
|
|
477
|
-
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
478
501
|
}
|
|
502
|
+
else {
|
|
503
|
+
pollErrorCount = 0;
|
|
504
|
+
nextPollDelay = basePollInterval;
|
|
505
|
+
}
|
|
506
|
+
await sleep(nextPollDelay);
|
|
479
507
|
}
|
|
480
508
|
}
|
package/dist/api.js
CHANGED
|
@@ -26,7 +26,9 @@ export async function connectWithPairingCode(code) {
|
|
|
26
26
|
export async function pollMessages() {
|
|
27
27
|
const res = await fetchWithAuth('/api/agent/messages');
|
|
28
28
|
if (!res.ok) {
|
|
29
|
-
|
|
29
|
+
const error = new Error(`Failed to poll messages: ${res.status}`);
|
|
30
|
+
error.status = res.status;
|
|
31
|
+
throw error;
|
|
30
32
|
}
|
|
31
33
|
return res.json();
|
|
32
34
|
}
|
package/dist/websocket.d.ts
CHANGED
package/dist/websocket.js
CHANGED
|
@@ -19,6 +19,8 @@ export class AgentWebSocket {
|
|
|
19
19
|
reconnectDelay = 1000;
|
|
20
20
|
pingInterval = null;
|
|
21
21
|
isConnected = false;
|
|
22
|
+
connecting = false;
|
|
23
|
+
connectPromise = null;
|
|
22
24
|
pendingResponses = new Map();
|
|
23
25
|
cloudflareBlocked = false; // Track if Cloudflare is blocking WebSockets
|
|
24
26
|
pendingMessages = [];
|
|
@@ -40,7 +42,32 @@ export class AgentWebSocket {
|
|
|
40
42
|
* Connect to the WebSocket server
|
|
41
43
|
*/
|
|
42
44
|
async connect() {
|
|
43
|
-
|
|
45
|
+
if (this.connected()) {
|
|
46
|
+
return Promise.resolve(true);
|
|
47
|
+
}
|
|
48
|
+
if (this.connecting && this.connectPromise) {
|
|
49
|
+
return this.connectPromise;
|
|
50
|
+
}
|
|
51
|
+
if (this.ws && this.ws.readyState !== WebSocket.OPEN) {
|
|
52
|
+
try {
|
|
53
|
+
this.ws.terminate();
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
// Ignore terminate errors
|
|
57
|
+
}
|
|
58
|
+
this.ws = null;
|
|
59
|
+
}
|
|
60
|
+
this.connecting = true;
|
|
61
|
+
this.connectPromise = new Promise((resolve) => {
|
|
62
|
+
let settled = false;
|
|
63
|
+
const finalize = (value) => {
|
|
64
|
+
if (settled)
|
|
65
|
+
return;
|
|
66
|
+
settled = true;
|
|
67
|
+
this.connecting = false;
|
|
68
|
+
this.connectPromise = null;
|
|
69
|
+
resolve(value);
|
|
70
|
+
};
|
|
44
71
|
try {
|
|
45
72
|
console.log('[WS] Connecting to', this.serverUrl);
|
|
46
73
|
this.ws = new WebSocket(this.serverUrl, {
|
|
@@ -59,7 +86,7 @@ export class AgentWebSocket {
|
|
|
59
86
|
else {
|
|
60
87
|
this.readyPending = true;
|
|
61
88
|
}
|
|
62
|
-
|
|
89
|
+
finalize(true);
|
|
63
90
|
});
|
|
64
91
|
this.ws.on('message', (data) => {
|
|
65
92
|
this.handleMessage(data.toString());
|
|
@@ -72,6 +99,7 @@ export class AgentWebSocket {
|
|
|
72
99
|
this.isConnected = false;
|
|
73
100
|
this.stopPingInterval();
|
|
74
101
|
this.attemptReconnect();
|
|
102
|
+
finalize(false);
|
|
75
103
|
});
|
|
76
104
|
this.ws.on('error', (error) => {
|
|
77
105
|
// Check for Cloudflare 524 timeout - don't spam logs
|
|
@@ -85,22 +113,23 @@ export class AgentWebSocket {
|
|
|
85
113
|
console.error('[WS] Error:', error.message);
|
|
86
114
|
}
|
|
87
115
|
if (!this.isConnected) {
|
|
88
|
-
|
|
116
|
+
finalize(false);
|
|
89
117
|
}
|
|
90
118
|
});
|
|
91
119
|
// Timeout for initial connection
|
|
92
120
|
setTimeout(() => {
|
|
93
121
|
if (!this.isConnected) {
|
|
94
122
|
console.log('[WS] Connection timeout');
|
|
95
|
-
|
|
123
|
+
finalize(false);
|
|
96
124
|
}
|
|
97
125
|
}, 10000);
|
|
98
126
|
}
|
|
99
127
|
catch (error) {
|
|
100
128
|
console.error('[WS] Connection failed:', error);
|
|
101
|
-
|
|
129
|
+
finalize(false);
|
|
102
130
|
}
|
|
103
131
|
});
|
|
132
|
+
return this.connectPromise;
|
|
104
133
|
}
|
|
105
134
|
/**
|
|
106
135
|
* Set handler for incoming messages
|
|
@@ -339,7 +368,9 @@ export class AgentWebSocket {
|
|
|
339
368
|
this.reconnectAttempts++;
|
|
340
369
|
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
|
|
341
370
|
setTimeout(() => {
|
|
342
|
-
this.
|
|
371
|
+
if (!this.connecting) {
|
|
372
|
+
this.connect();
|
|
373
|
+
}
|
|
343
374
|
}, delay);
|
|
344
375
|
}
|
|
345
376
|
}
|