@siftd/connect-agent 0.2.52 → 0.2.54
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/orchestrator.js +38 -6
- 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/orchestrator.js
CHANGED
|
@@ -734,8 +734,8 @@ export class MasterOrchestrator {
|
|
|
734
734
|
}
|
|
735
735
|
hasCalendarMutation(message) {
|
|
736
736
|
const lower = this.stripTodoSnapshot(message).toLowerCase();
|
|
737
|
-
const target = /(^|\s)\/cal\b|\/calendar\b|\bcalendar\b|\bcal\b/.test(lower);
|
|
738
|
-
const action = /\b(add|create|schedule|book|move|reschedule|update|change|cancel|delete|remove)\b/.test(lower);
|
|
737
|
+
const target = /(^|\s)\/cal\b|\/calendar\b|\bcalendar\b|\bcal\b|\bschedule\b|\bagenda\b/.test(lower);
|
|
738
|
+
const action = /\b(add|create|schedule|book|move|reschedule|update|change|cancel|delete|remove|set|put|place|remind)\b/.test(lower);
|
|
739
739
|
const query = /\b(what|show|list|open|view|see)\b/.test(lower);
|
|
740
740
|
return target && action && !query;
|
|
741
741
|
}
|
|
@@ -1422,9 +1422,9 @@ ${hubContextStr}
|
|
|
1422
1422
|
const toolsBase = this.getToolDefinitions();
|
|
1423
1423
|
const last = messages[messages.length - 1];
|
|
1424
1424
|
const lastContent = last && last.role === 'user' && typeof last.content === 'string' ? last.content : '';
|
|
1425
|
-
const
|
|
1426
|
-
|
|
1427
|
-
|
|
1425
|
+
const requiredTodo = lastContent ? this.hasTodoMutation(lastContent) : false;
|
|
1426
|
+
const requiredCal = lastContent ? this.hasCalendarMutation(lastContent) : false;
|
|
1427
|
+
const restrictWorkers = requiredTodo || requiredCal;
|
|
1428
1428
|
const wantsTodoOrCal = restrictWorkers;
|
|
1429
1429
|
const todoCalTools = new Set(['calendar_upsert_events', 'todo_upsert_items']);
|
|
1430
1430
|
const blockedTools = new Set([
|
|
@@ -1444,6 +1444,7 @@ ${hubContextStr}
|
|
|
1444
1444
|
const requestTimeoutMs = 60000;
|
|
1445
1445
|
const forcedToolChoice = this.getToolChoice(currentMessages);
|
|
1446
1446
|
let retriedForcedTool = false;
|
|
1447
|
+
let retriedTodoCal = false;
|
|
1447
1448
|
while (iterations < maxIterations) {
|
|
1448
1449
|
iterations++;
|
|
1449
1450
|
const toolChoice = forcedToolChoice ?? this.getToolChoice(currentMessages);
|
|
@@ -1492,13 +1493,44 @@ ${hubContextStr}
|
|
|
1492
1493
|
];
|
|
1493
1494
|
continue;
|
|
1494
1495
|
}
|
|
1496
|
+
if (wantsTodoOrCal && !retriedTodoCal && (requiredTodo || requiredCal)) {
|
|
1497
|
+
retriedTodoCal = true;
|
|
1498
|
+
const required = [
|
|
1499
|
+
requiredTodo ? 'todo_upsert_items' : null,
|
|
1500
|
+
requiredCal ? 'calendar_upsert_events' : null
|
|
1501
|
+
].filter(Boolean).join(' and ');
|
|
1502
|
+
const followup = `You must call ${required} now. Do not spawn workers or call any other tools.`;
|
|
1503
|
+
currentMessages = [
|
|
1504
|
+
...currentMessages,
|
|
1505
|
+
{ role: 'assistant', content: response.content },
|
|
1506
|
+
{ role: 'user', content: followup }
|
|
1507
|
+
];
|
|
1508
|
+
continue;
|
|
1509
|
+
}
|
|
1495
1510
|
return this.extractText(response.content);
|
|
1496
1511
|
}
|
|
1497
1512
|
const toolUseBlocks = response.content.filter((block) => block.type === 'tool_use');
|
|
1498
1513
|
const toolNames = toolUseBlocks.map((block) => block.name);
|
|
1499
1514
|
// Process tool calls
|
|
1500
1515
|
const toolResults = await this.processToolCalls(response.content, sendMessage);
|
|
1501
|
-
|
|
1516
|
+
const missingTodo = requiredTodo && !toolNames.includes('todo_upsert_items');
|
|
1517
|
+
const missingCal = requiredCal && !toolNames.includes('calendar_upsert_events');
|
|
1518
|
+
if (wantsTodoOrCal && (missingTodo || missingCal) && !retriedTodoCal) {
|
|
1519
|
+
retriedTodoCal = true;
|
|
1520
|
+
const required = [
|
|
1521
|
+
missingTodo ? 'todo_upsert_items' : null,
|
|
1522
|
+
missingCal ? 'calendar_upsert_events' : null
|
|
1523
|
+
].filter(Boolean).join(' and ');
|
|
1524
|
+
const followup = `You must call ${required} now. Do not repeat tools already called. Do not spawn workers.`;
|
|
1525
|
+
currentMessages = [
|
|
1526
|
+
...currentMessages,
|
|
1527
|
+
{ role: 'assistant', content: response.content },
|
|
1528
|
+
{ role: 'user', content: toolResults },
|
|
1529
|
+
{ role: 'user', content: followup }
|
|
1530
|
+
];
|
|
1531
|
+
continue;
|
|
1532
|
+
}
|
|
1533
|
+
if (wantsTodoOrCal && !missingTodo && !missingCal) {
|
|
1502
1534
|
const updates = [];
|
|
1503
1535
|
if (toolNames.includes('todo_upsert_items'))
|
|
1504
1536
|
updates.push('Updated /todo.');
|
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
|
}
|