@siftd/connect-agent 0.2.39 → 0.2.40
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 +3 -7
- package/dist/agent.js +69 -127
- package/dist/api.d.ts +0 -1
- package/dist/cli.js +1 -1
- package/dist/core/task-queue.d.ts +0 -1
- package/dist/orchestrator.d.ts +20 -4
- package/dist/orchestrator.js +140 -13
- package/dist/websocket.d.ts +5 -2
- package/dist/websocket.js +10 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Connect Agent runs on your machine and bridges the Connect web app to Claude Cod
|
|
|
18
18
|
```bash
|
|
19
19
|
# 1. Go to https://connect.siftd.app and get a pairing code
|
|
20
20
|
|
|
21
|
-
# 2. Pair your machine (with API key
|
|
21
|
+
# 2. Pair your machine (with API key)
|
|
22
22
|
npx @siftd/connect-agent pair <CODE> --api-key <YOUR_ANTHROPIC_API_KEY>
|
|
23
23
|
|
|
24
24
|
# 3. Start the agent
|
|
@@ -33,17 +33,13 @@ On first start, the agent creates `~/Lia-Hub/` with:
|
|
|
33
33
|
|
|
34
34
|
## Modes
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
When you provide an Anthropic API key, the agent runs as a **master orchestrator**:
|
|
36
|
+
The agent runs as a **master orchestrator** (requires an Anthropic API key):
|
|
38
37
|
- Uses Claude API directly for the orchestration layer
|
|
39
38
|
- Maintains persistent memory about you and your projects
|
|
40
39
|
- Delegates file/code work to Claude Code CLI workers
|
|
41
40
|
- Schedules future tasks
|
|
42
41
|
- Reads hub files for context on every startup
|
|
43
42
|
|
|
44
|
-
### Simple Relay Mode
|
|
45
|
-
Without an API key, the agent acts as a simple relay to `claude -p --continue`.
|
|
46
|
-
|
|
47
43
|
## Commands
|
|
48
44
|
|
|
49
45
|
```bash
|
|
@@ -66,7 +62,7 @@ connect-agent logout
|
|
|
66
62
|
## Special Commands (via web chat)
|
|
67
63
|
|
|
68
64
|
- `/reset` or `/clear` - Clear conversation history
|
|
69
|
-
- `/mode` -
|
|
65
|
+
- `/mode` - Show runtime details
|
|
70
66
|
- `/memory` - Show memory statistics
|
|
71
67
|
- `/verbose` - Toggle verbose tool output
|
|
72
68
|
|
package/dist/agent.js
CHANGED
|
@@ -12,10 +12,6 @@ import { startHeartbeat, stopHeartbeat, getHeartbeatState } from './heartbeat.js
|
|
|
12
12
|
import { loadHubContext, readScratchpad } from './core/hub.js';
|
|
13
13
|
import { PRODUCT_FULL_NAME } from './branding.js';
|
|
14
14
|
import { startPreviewWorker, stopPreviewWorker } from './core/preview-worker.js';
|
|
15
|
-
// Strip ANSI escape codes for clean output
|
|
16
|
-
function stripAnsi(str) {
|
|
17
|
-
return str.replace(/\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])/g, '');
|
|
18
|
-
}
|
|
19
15
|
function parseAttachments(content) {
|
|
20
16
|
const marker = '📎 Attached files:';
|
|
21
17
|
const index = content.indexOf(marker);
|
|
@@ -162,13 +158,10 @@ function initOrchestrator() {
|
|
|
162
158
|
const instanceMode = getInstanceMode();
|
|
163
159
|
const userOrgIds = getUserOrgIds();
|
|
164
160
|
if (!apiKey) {
|
|
165
|
-
|
|
166
|
-
console.log('[AGENT] To enable orchestrator: pair with --api-key flag');
|
|
167
|
-
return null;
|
|
161
|
+
throw new Error('Missing Anthropic API key. Set ANTHROPIC_API_KEY or run `lia-agent set-key <key>`.');
|
|
168
162
|
}
|
|
169
163
|
if (!userId) {
|
|
170
|
-
|
|
171
|
-
return null;
|
|
164
|
+
throw new Error('Missing userId configuration. Re-pair the agent.');
|
|
172
165
|
}
|
|
173
166
|
console.log(`[AGENT] Initializing Master Orchestrator (mode: ${instanceMode})...`);
|
|
174
167
|
if (instanceMode === 'personal' && userOrgIds.length > 0) {
|
|
@@ -187,59 +180,11 @@ function initOrchestrator() {
|
|
|
187
180
|
userOrgIds: instanceMode === 'personal' ? userOrgIds : undefined,
|
|
188
181
|
});
|
|
189
182
|
}
|
|
190
|
-
/**
|
|
191
|
-
* Simple relay mode - just pipes to Claude Code CLI
|
|
192
|
-
*/
|
|
193
|
-
async function sendToClaudeSimple(input) {
|
|
194
|
-
return new Promise((resolve) => {
|
|
195
|
-
console.log(`\n[CLAUDE] Sending: ${input.substring(0, 80)}...`);
|
|
196
|
-
const claude = spawn('claude', ['-p', '--continue'], {
|
|
197
|
-
cwd: process.env.HOME,
|
|
198
|
-
env: process.env,
|
|
199
|
-
shell: true,
|
|
200
|
-
});
|
|
201
|
-
let output = '';
|
|
202
|
-
let errorOutput = '';
|
|
203
|
-
claude.stdout?.on('data', (data) => {
|
|
204
|
-
const text = data.toString();
|
|
205
|
-
output += text;
|
|
206
|
-
process.stdout.write(text);
|
|
207
|
-
});
|
|
208
|
-
claude.stderr?.on('data', (data) => {
|
|
209
|
-
const text = data.toString();
|
|
210
|
-
errorOutput += text;
|
|
211
|
-
if (!text.includes('Checking') && !text.includes('Connected')) {
|
|
212
|
-
process.stderr.write(text);
|
|
213
|
-
}
|
|
214
|
-
});
|
|
215
|
-
claude.on('close', (code) => {
|
|
216
|
-
if (code === 0) {
|
|
217
|
-
const response = stripAnsi(output).trim();
|
|
218
|
-
console.log(`\n[CLAUDE] Response: ${response.substring(0, 80)}...`);
|
|
219
|
-
resolve(response || 'No response from Claude');
|
|
220
|
-
}
|
|
221
|
-
else {
|
|
222
|
-
const error = errorOutput || `Claude exited with code ${code}`;
|
|
223
|
-
console.error(`\n[CLAUDE] Error: ${error}`);
|
|
224
|
-
resolve(`Error: ${stripAnsi(error).trim()}`);
|
|
225
|
-
}
|
|
226
|
-
});
|
|
227
|
-
claude.on('error', (err) => {
|
|
228
|
-
console.error(`\n[CLAUDE] Failed to start: ${err.message}`);
|
|
229
|
-
resolve(`Error: Failed to start Claude - ${err.message}`);
|
|
230
|
-
});
|
|
231
|
-
if (claude.stdin) {
|
|
232
|
-
claude.stdin.write(input);
|
|
233
|
-
claude.stdin.end();
|
|
234
|
-
}
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
183
|
/**
|
|
238
184
|
* Orchestrator mode - uses the master orchestrator with memory
|
|
239
185
|
* Supports WebSocket streaming for real-time progress updates
|
|
240
|
-
* @param apiKey Optional per-request API key from user
|
|
241
186
|
*/
|
|
242
|
-
async function sendToOrchestrator(input, orch, messageId
|
|
187
|
+
async function sendToOrchestrator(input, orch, messageId) {
|
|
243
188
|
console.log(`\n[ORCHESTRATOR] Processing: ${input.substring(0, 80)}...`);
|
|
244
189
|
// Send typing indicator via WebSocket
|
|
245
190
|
if (wsClient?.connected()) {
|
|
@@ -263,8 +208,7 @@ async function sendToOrchestrator(input, orch, messageId, apiKey) {
|
|
|
263
208
|
if (wsClient?.connected() && messageId) {
|
|
264
209
|
wsClient.sendProgress(messageId, msg);
|
|
265
210
|
}
|
|
266
|
-
}
|
|
267
|
-
);
|
|
211
|
+
});
|
|
268
212
|
// Update conversation history (only if response is non-empty)
|
|
269
213
|
if (response && response.trim()) {
|
|
270
214
|
conversationHistory.push({ role: 'user', content: input });
|
|
@@ -307,14 +251,11 @@ export async function processMessage(message) {
|
|
|
307
251
|
const wsStatus = wsClient?.connected() ? 'WebSocket' : 'Polling';
|
|
308
252
|
const hbState = getHeartbeatState();
|
|
309
253
|
const runnerInfo = hbState.runnerId ? ` [${hbState.runnerId}]` : '';
|
|
310
|
-
return
|
|
311
|
-
? `Running in ORCHESTRATOR mode (${wsStatus})${runnerInfo} with memory and delegation`
|
|
312
|
-
: `Running in SIMPLE mode (${wsStatus})${runnerInfo} - direct Claude Code relay`;
|
|
254
|
+
return `Running in ORCHESTRATOR mode (${wsStatus})${runnerInfo} with memory and delegation`;
|
|
313
255
|
}
|
|
314
256
|
if (content === '/memory' && orchestrator) {
|
|
315
257
|
// Trigger memory stats
|
|
316
|
-
const response = await orchestrator.processMessage('Show me my memory stats', [], undefined
|
|
317
|
-
);
|
|
258
|
+
const response = await orchestrator.processMessage('Show me my memory stats', [], undefined);
|
|
318
259
|
return response;
|
|
319
260
|
}
|
|
320
261
|
// System command: force update (sent by webapp banner)
|
|
@@ -359,12 +300,7 @@ export async function processMessage(message) {
|
|
|
359
300
|
};
|
|
360
301
|
}
|
|
361
302
|
try {
|
|
362
|
-
|
|
363
|
-
return await sendToOrchestrator(message.content, orchestrator, message.id, message.apiKey);
|
|
364
|
-
}
|
|
365
|
-
else {
|
|
366
|
-
return await sendToClaudeSimple(message.content);
|
|
367
|
-
}
|
|
303
|
+
return await sendToOrchestrator(message.content, orchestrator, message.id);
|
|
368
304
|
}
|
|
369
305
|
catch (error) {
|
|
370
306
|
console.error('Error:', error);
|
|
@@ -372,9 +308,15 @@ export async function processMessage(message) {
|
|
|
372
308
|
}
|
|
373
309
|
}
|
|
374
310
|
export async function runAgent(pollInterval = 2000) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
311
|
+
try {
|
|
312
|
+
orchestrator = initOrchestrator();
|
|
313
|
+
}
|
|
314
|
+
catch (error) {
|
|
315
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
316
|
+
console.error(`[AGENT] ${msg}`);
|
|
317
|
+
process.exit(1);
|
|
318
|
+
}
|
|
319
|
+
const mode = 'ORCHESTRATOR';
|
|
378
320
|
const deployment = getDeploymentInfo();
|
|
379
321
|
console.log('╔══════════════════════════════════════════════════╗');
|
|
380
322
|
console.log(`║ ${PRODUCT_FULL_NAME.padEnd(47)}║`);
|
|
@@ -385,29 +327,27 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
385
327
|
console.log(`[AGENT] Running in CLOUD mode`);
|
|
386
328
|
console.log(`[AGENT] Server: ${deployment.server}`);
|
|
387
329
|
}
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
console.log(`[AGENT] Scratchpad: ${scratchpad.length} chars of pending notes`);
|
|
407
|
-
}
|
|
330
|
+
console.log('[AGENT] Features: Memory, Scheduling, Worker Delegation');
|
|
331
|
+
// Initialize orchestrator (indexes filesystem into memory)
|
|
332
|
+
await orchestrator.initialize();
|
|
333
|
+
// Load hub context on startup
|
|
334
|
+
const hubContext = loadHubContext();
|
|
335
|
+
if (hubContext.agentIdentity) {
|
|
336
|
+
console.log('[AGENT] Hub: AGENTS.md loaded');
|
|
337
|
+
}
|
|
338
|
+
if (hubContext.claudeMd) {
|
|
339
|
+
console.log('[AGENT] Hub: CLAUDE.md loaded');
|
|
340
|
+
}
|
|
341
|
+
if (hubContext.landmarks) {
|
|
342
|
+
console.log('[AGENT] Hub: LANDMARKS.md loaded');
|
|
343
|
+
}
|
|
344
|
+
// Check scratchpad for pending plans
|
|
345
|
+
const scratchpad = readScratchpad();
|
|
346
|
+
if (scratchpad && scratchpad.length > 100) {
|
|
347
|
+
console.log(`[AGENT] Scratchpad: ${scratchpad.length} chars of pending notes`);
|
|
408
348
|
}
|
|
409
349
|
// Start preview worker for fast-path asset previews (no LLM)
|
|
410
|
-
|
|
350
|
+
startPreviewWorker({
|
|
411
351
|
verbose: true,
|
|
412
352
|
onAsset: (manifest) => {
|
|
413
353
|
console.log(`[PREVIEW] New asset: ${manifest.name} (${manifest.id})`);
|
|
@@ -425,7 +365,7 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
425
365
|
const runnerType = isCloudMode() ? 'vm' : 'local';
|
|
426
366
|
startHeartbeat({
|
|
427
367
|
runnerType,
|
|
428
|
-
capabilities:
|
|
368
|
+
capabilities: ['orchestrator', 'memory'],
|
|
429
369
|
onError: (error) => {
|
|
430
370
|
// Log only significant errors
|
|
431
371
|
if (error.message.includes('401') || error.message.includes('403')) {
|
|
@@ -449,35 +389,38 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
449
389
|
// Try WebSocket first
|
|
450
390
|
wsClient = new AgentWebSocket();
|
|
451
391
|
const wsConnected = await wsClient.connect();
|
|
452
|
-
//
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
392
|
+
// Progress bars
|
|
393
|
+
orchestrator.setWorkerStatusCallback((workers) => {
|
|
394
|
+
if (wsClient?.connected()) {
|
|
395
|
+
wsClient.sendWorkersUpdate(workers);
|
|
396
|
+
}
|
|
397
|
+
const running = workers.filter(w => w.status === 'running');
|
|
398
|
+
if (running.length > 0) {
|
|
399
|
+
console.log(`[WORKERS] ${running.length} running`);
|
|
400
|
+
}
|
|
401
|
+
});
|
|
402
|
+
// Todo updates - send to user for inline display in chat
|
|
403
|
+
orchestrator.setTodoUpdateCallback((todos) => {
|
|
404
|
+
if (wsClient?.connected()) {
|
|
405
|
+
wsClient.sendTodoUpdate(todos);
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
// Gallery updates - send worker assets for UI gallery view
|
|
409
|
+
orchestrator.setGalleryCallback((galleryWorkers) => {
|
|
410
|
+
if (wsClient?.connected()) {
|
|
411
|
+
wsClient.sendGalleryWorkers(galleryWorkers);
|
|
412
|
+
const totalAssets = galleryWorkers.reduce((sum, w) => sum + w.assets.length, 0);
|
|
413
|
+
console.log(`[GALLERY] ${galleryWorkers.length} workers, ${totalAssets} assets`);
|
|
414
|
+
}
|
|
415
|
+
});
|
|
416
|
+
// Worker results - send to user when workers complete
|
|
417
|
+
orchestrator.setWorkerResultCallback((workerId, result) => {
|
|
418
|
+
console.log(`[WORKER DONE] ${workerId}: ${result.slice(0, 100)}...`);
|
|
419
|
+
if (wsClient?.connected()) {
|
|
420
|
+
// Send as response with worker ID as message ID
|
|
421
|
+
wsClient.sendResponse(workerId, `**Worker completed:**\n\n${result}`);
|
|
422
|
+
}
|
|
423
|
+
});
|
|
481
424
|
if (wsConnected) {
|
|
482
425
|
console.log('[AGENT] Using WebSocket for real-time communication\n');
|
|
483
426
|
// Handle messages via WebSocket
|
|
@@ -486,8 +429,7 @@ export async function runAgent(pollInterval = 2000) {
|
|
|
486
429
|
const message = {
|
|
487
430
|
id: wsMsg.id,
|
|
488
431
|
content: wsMsg.content,
|
|
489
|
-
timestamp: wsMsg.timestamp || Date.now()
|
|
490
|
-
apiKey: wsMsg.apiKey // Pass through per-request API key
|
|
432
|
+
timestamp: wsMsg.timestamp || Date.now()
|
|
491
433
|
};
|
|
492
434
|
const response = await processMessage(message);
|
|
493
435
|
// Send response via WebSocket
|
package/dist/api.d.ts
CHANGED
package/dist/cli.js
CHANGED
|
@@ -84,7 +84,7 @@ program
|
|
|
84
84
|
console.log(`Config file: ${getConfigPath()}`);
|
|
85
85
|
console.log(`Server URL: ${getServerUrl()}`);
|
|
86
86
|
console.log(`Configured: ${isConfigured() ? 'Yes' : 'No'}`);
|
|
87
|
-
console.log(`
|
|
87
|
+
console.log(`Anthropic API key: ${getAnthropicApiKey() ? 'Configured' : 'Missing'}`);
|
|
88
88
|
if (isConfigured()) {
|
|
89
89
|
const spinner = ora('Checking connection...').start();
|
|
90
90
|
const connected = await checkConnection();
|
package/dist/orchestrator.d.ts
CHANGED
|
@@ -17,6 +17,15 @@ export interface WorkerStatus {
|
|
|
17
17
|
}
|
|
18
18
|
export type WorkerStatusCallback = (workers: WorkerStatus[]) => void;
|
|
19
19
|
export type WorkerLogCallback = (workerId: string, line: string, stream: 'stdout' | 'stderr') => void;
|
|
20
|
+
export interface LiaTodoItem {
|
|
21
|
+
id: string;
|
|
22
|
+
content: string;
|
|
23
|
+
activeForm: string;
|
|
24
|
+
status: 'pending' | 'in_progress' | 'completed';
|
|
25
|
+
workerId?: string;
|
|
26
|
+
progress?: number;
|
|
27
|
+
}
|
|
28
|
+
export type TodoUpdateCallback = (todos: LiaTodoItem[]) => void;
|
|
20
29
|
export interface WorkerAsset {
|
|
21
30
|
path: string;
|
|
22
31
|
name: string;
|
|
@@ -71,6 +80,8 @@ export declare class MasterOrchestrator {
|
|
|
71
80
|
private taskQueue;
|
|
72
81
|
private taskUpdateCallback?;
|
|
73
82
|
private planUpdateCallback?;
|
|
83
|
+
private currentTodos;
|
|
84
|
+
private todoUpdateCallback?;
|
|
74
85
|
constructor(options: {
|
|
75
86
|
apiKey: string;
|
|
76
87
|
userId: string;
|
|
@@ -107,13 +118,16 @@ export declare class MasterOrchestrator {
|
|
|
107
118
|
* Set callback for plan updates (for real-time UI updates)
|
|
108
119
|
*/
|
|
109
120
|
setPlanUpdateCallback(callback: ((plan: LiaPlan) => void) | null): void;
|
|
121
|
+
/**
|
|
122
|
+
* Set callback for visible todo list updates (displayed inline in chat)
|
|
123
|
+
*/
|
|
124
|
+
setTodoUpdateCallback(callback: TodoUpdateCallback | null): void;
|
|
110
125
|
/**
|
|
111
126
|
* Queue a message for async processing (non-blocking)
|
|
112
127
|
* Returns immediately with task ID - use callbacks or getTaskQueueStatus for results
|
|
113
128
|
*/
|
|
114
129
|
queueMessage(message: string, options?: {
|
|
115
130
|
priority?: 'urgent' | 'high' | 'normal' | 'low';
|
|
116
|
-
apiKey?: string;
|
|
117
131
|
messageId?: string;
|
|
118
132
|
}): LiaTask;
|
|
119
133
|
/**
|
|
@@ -181,9 +195,8 @@ export declare class MasterOrchestrator {
|
|
|
181
195
|
isVerbose(): boolean;
|
|
182
196
|
/**
|
|
183
197
|
* Process a user message
|
|
184
|
-
* @param apiKey Optional per-request API key (overrides default)
|
|
185
198
|
*/
|
|
186
|
-
processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender
|
|
199
|
+
processMessage(message: string, conversationHistory?: MessageParam[], sendMessage?: MessageSender): Promise<string>;
|
|
187
200
|
private tryHandleCalendarTodo;
|
|
188
201
|
private extractTodoItems;
|
|
189
202
|
private extractCalendarEvents;
|
|
@@ -211,7 +224,6 @@ export declare class MasterOrchestrator {
|
|
|
211
224
|
private isIgnoredEntity;
|
|
212
225
|
/**
|
|
213
226
|
* Run the agentic loop
|
|
214
|
-
* @param apiKey Optional per-request API key (creates temporary client)
|
|
215
227
|
*/
|
|
216
228
|
private runAgentLoop;
|
|
217
229
|
/**
|
|
@@ -300,6 +312,10 @@ export declare class MasterOrchestrator {
|
|
|
300
312
|
* Get Lia's task queue status
|
|
301
313
|
*/
|
|
302
314
|
private executeLiaGetQueue;
|
|
315
|
+
/**
|
|
316
|
+
* Update visible todo list (displayed inline in chat)
|
|
317
|
+
*/
|
|
318
|
+
private executeLiaTodoWrite;
|
|
303
319
|
/**
|
|
304
320
|
* Format tool preview for user
|
|
305
321
|
*/
|
package/dist/orchestrator.js
CHANGED
|
@@ -75,6 +75,13 @@ YOUR IDENTITY:
|
|
|
75
75
|
HOW YOU WORK:
|
|
76
76
|
You are the orchestrator, not the executor. You coordinate and remember while workers do the hands-on work.
|
|
77
77
|
|
|
78
|
+
CRITICAL - DATE AWARENESS:
|
|
79
|
+
ALWAYS run \`date\` via bash FIRST before referencing ANY temporal context from memory.
|
|
80
|
+
- Before mentioning meetings, deadlines, or scheduled items: check today's date
|
|
81
|
+
- Cross-reference memory timestamps with current date
|
|
82
|
+
- Only surface RELEVANT, CURRENT information - never reference past events as if they're upcoming
|
|
83
|
+
- A personal assistant that gives outdated information is worse than useless - it's actively misleading
|
|
84
|
+
|
|
78
85
|
TASK QUEUE (Always-Listening):
|
|
79
86
|
You have an internal task queue. Users can keep sending messages while you work - you're interruptible.
|
|
80
87
|
- Use lia_plan to break down complex goals into steps
|
|
@@ -150,6 +157,23 @@ CALENDAR + TODO (Lia-managed data):
|
|
|
150
157
|
- calendar.json: { "version": 1, "calendars": [...], "events": [...] }
|
|
151
158
|
- todos.json: { "version": 1, "items": [...] }
|
|
152
159
|
|
|
160
|
+
COMPLEX TODO/CALENDAR OPERATIONS:
|
|
161
|
+
When users ask to "break down", "split", or "parse" a todo item:
|
|
162
|
+
1. READ THE NOTES FIELD - the TODO SNAPSHOT includes notes with full content
|
|
163
|
+
2. Parse the notes into distinct actionable items (grants, meetings, tasks, etc.)
|
|
164
|
+
3. Call todo_upsert_items with MULTIPLE new items extracted from the notes
|
|
165
|
+
4. Mark the original item as done: true OR delete by not including it in upsert
|
|
166
|
+
5. Each new item should have: id (unique), title, priority, notes (if details needed), due (if deadline mentioned)
|
|
167
|
+
|
|
168
|
+
Example: If a todo has notes listing "NIH R01 deadline Jan 25, AASLD pilot Feb, ADA Mar":
|
|
169
|
+
→ Create 3 separate todos: one for NIH R01 (due: 2026-01-25), one for AASLD, one for ADA
|
|
170
|
+
→ Extract deadlines, descriptions, requirements from the original notes
|
|
171
|
+
|
|
172
|
+
When users reference a todo by number (e.g., "#3", "item 3", "the third one"):
|
|
173
|
+
- Match it to the items in the TODO SNAPSHOT (which is ordered)
|
|
174
|
+
- READ its notes field carefully - that's where the real content lives
|
|
175
|
+
- Don't guess what it contains - the snapshot shows you exactly what's there
|
|
176
|
+
|
|
153
177
|
WORKFLOW:
|
|
154
178
|
Before complex work: Check CLAUDE.md → Read LANDMARKS.md → Search memory
|
|
155
179
|
After completing work: Update LANDMARKS.md → Remember learnings
|
|
@@ -192,9 +216,12 @@ export class MasterOrchestrator {
|
|
|
192
216
|
taskQueue;
|
|
193
217
|
taskUpdateCallback;
|
|
194
218
|
planUpdateCallback;
|
|
219
|
+
// Lia's visible todo list (displayed inline in chat)
|
|
220
|
+
currentTodos = [];
|
|
221
|
+
todoUpdateCallback;
|
|
195
222
|
constructor(options) {
|
|
196
223
|
this.client = new Anthropic({ apiKey: options.apiKey });
|
|
197
|
-
this.model = options.model || 'claude-sonnet-4-20250514';
|
|
224
|
+
this.model = options.model || process.env.ANTHROPIC_MODEL || 'claude-sonnet-4-20250514';
|
|
198
225
|
this.maxTokens = options.maxTokens || 4096;
|
|
199
226
|
this.userId = options.userId;
|
|
200
227
|
this.orgId = options.orgId;
|
|
@@ -349,6 +376,12 @@ export class MasterOrchestrator {
|
|
|
349
376
|
setPlanUpdateCallback(callback) {
|
|
350
377
|
this.planUpdateCallback = callback || undefined;
|
|
351
378
|
}
|
|
379
|
+
/**
|
|
380
|
+
* Set callback for visible todo list updates (displayed inline in chat)
|
|
381
|
+
*/
|
|
382
|
+
setTodoUpdateCallback(callback) {
|
|
383
|
+
this.todoUpdateCallback = callback || undefined;
|
|
384
|
+
}
|
|
352
385
|
/**
|
|
353
386
|
* Queue a message for async processing (non-blocking)
|
|
354
387
|
* Returns immediately with task ID - use callbacks or getTaskQueueStatus for results
|
|
@@ -361,7 +394,6 @@ export class MasterOrchestrator {
|
|
|
361
394
|
metadata: {
|
|
362
395
|
userId: this.userId,
|
|
363
396
|
messageId: options?.messageId,
|
|
364
|
-
apiKey: options?.apiKey,
|
|
365
397
|
context: this.instanceMode,
|
|
366
398
|
orgId: this.orgId,
|
|
367
399
|
},
|
|
@@ -373,8 +405,8 @@ export class MasterOrchestrator {
|
|
|
373
405
|
async processQueuedTask(task) {
|
|
374
406
|
// This is where the actual work happens
|
|
375
407
|
return this.processMessage(task.content, [], // Fresh conversation for each task
|
|
376
|
-
undefined
|
|
377
|
-
|
|
408
|
+
undefined // No sendMessage callback for queued tasks
|
|
409
|
+
);
|
|
378
410
|
}
|
|
379
411
|
/**
|
|
380
412
|
* Get task queue status (for UI)
|
|
@@ -517,6 +549,22 @@ export class MasterOrchestrator {
|
|
|
517
549
|
if (hasRunning || workers.length > 0) {
|
|
518
550
|
this.workerStatusCallback(workers);
|
|
519
551
|
}
|
|
552
|
+
// Update linked todos with worker progress
|
|
553
|
+
if (this.currentTodos.length > 0 && this.todoUpdateCallback) {
|
|
554
|
+
let todoUpdated = false;
|
|
555
|
+
for (const todo of this.currentTodos) {
|
|
556
|
+
if (todo.workerId && todo.status === 'in_progress') {
|
|
557
|
+
const worker = workers.find(w => w.id === todo.workerId);
|
|
558
|
+
if (worker && todo.progress !== worker.progress) {
|
|
559
|
+
todo.progress = worker.progress;
|
|
560
|
+
todoUpdated = true;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
if (todoUpdated) {
|
|
565
|
+
this.todoUpdateCallback(this.currentTodos);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
520
568
|
// Clean up completed jobs after 3 seconds
|
|
521
569
|
const now = Date.now();
|
|
522
570
|
for (const [id, job] of this.jobs) {
|
|
@@ -636,9 +684,8 @@ export class MasterOrchestrator {
|
|
|
636
684
|
}
|
|
637
685
|
/**
|
|
638
686
|
* Process a user message
|
|
639
|
-
* @param apiKey Optional per-request API key (overrides default)
|
|
640
687
|
*/
|
|
641
|
-
async processMessage(message, conversationHistory = [], sendMessage
|
|
688
|
+
async processMessage(message, conversationHistory = [], sendMessage) {
|
|
642
689
|
// Handle slash commands first
|
|
643
690
|
const slashResponse = this.handleSlashCommand(message);
|
|
644
691
|
if (slashResponse) {
|
|
@@ -686,7 +733,7 @@ ${hubContextStr}
|
|
|
686
733
|
{ role: 'user', content: message }
|
|
687
734
|
];
|
|
688
735
|
try {
|
|
689
|
-
const response = await this.runAgentLoop(messages, systemWithContext, sendMessage
|
|
736
|
+
const response = await this.runAgentLoop(messages, systemWithContext, sendMessage);
|
|
690
737
|
// Auto-remember important things from the conversation
|
|
691
738
|
await this.autoRemember(message, response);
|
|
692
739
|
void this.autoCaptureContext(message);
|
|
@@ -1087,22 +1134,19 @@ ${hubContextStr}
|
|
|
1087
1134
|
}
|
|
1088
1135
|
/**
|
|
1089
1136
|
* Run the agentic loop
|
|
1090
|
-
* @param apiKey Optional per-request API key (creates temporary client)
|
|
1091
1137
|
*/
|
|
1092
|
-
async runAgentLoop(messages, system, sendMessage
|
|
1138
|
+
async runAgentLoop(messages, system, sendMessage) {
|
|
1093
1139
|
const tools = this.getToolDefinitions();
|
|
1094
1140
|
let currentMessages = [...messages];
|
|
1095
1141
|
let iterations = 0;
|
|
1096
1142
|
const maxIterations = 30; // Increased for complex multi-tool tasks
|
|
1097
1143
|
const requestTimeoutMs = 60000;
|
|
1098
|
-
// Use per-request client if apiKey provided, otherwise use default
|
|
1099
|
-
const client = apiKey ? new Anthropic({ apiKey }) : this.client;
|
|
1100
1144
|
while (iterations < maxIterations) {
|
|
1101
1145
|
iterations++;
|
|
1102
1146
|
const requestStart = Date.now();
|
|
1103
1147
|
console.log(`[ORCHESTRATOR] Anthropic request ${iterations}/${maxIterations} (model: ${this.model})`);
|
|
1104
1148
|
const response = await Promise.race([
|
|
1105
|
-
client.messages.create({
|
|
1149
|
+
this.client.messages.create({
|
|
1106
1150
|
model: this.model,
|
|
1107
1151
|
max_tokens: this.maxTokens,
|
|
1108
1152
|
system,
|
|
@@ -1205,7 +1249,18 @@ Writes ONLY to ~/Lia-Hub/shared/outputs/.lia/calendar.json (creates the director
|
|
|
1205
1249
|
name: 'todo_upsert_items',
|
|
1206
1250
|
description: `Create or update todo items for /todo.
|
|
1207
1251
|
|
|
1208
|
-
Writes ONLY to ~/Lia-Hub/shared/outputs/.lia/todos.json (creates the directory/file if missing)
|
|
1252
|
+
Writes ONLY to ~/Lia-Hub/shared/outputs/.lia/todos.json (creates the directory/file if missing).
|
|
1253
|
+
|
|
1254
|
+
UPSERT BEHAVIOR:
|
|
1255
|
+
- Items with matching 'id' are REPLACED (use this to update or mark done)
|
|
1256
|
+
- Items with new 'id' are ADDED
|
|
1257
|
+
- To "delete" an item, set done: true or exclude it when replacing
|
|
1258
|
+
|
|
1259
|
+
BREAKING DOWN A TODO:
|
|
1260
|
+
When asked to split/parse a todo into multiple items:
|
|
1261
|
+
1. Include the original item with done: true (marks it complete)
|
|
1262
|
+
2. Add the new parsed items with unique ids
|
|
1263
|
+
3. Extract deadlines, priorities, and notes from the original content`,
|
|
1209
1264
|
input_schema: {
|
|
1210
1265
|
type: 'object',
|
|
1211
1266
|
properties: {
|
|
@@ -1676,6 +1731,37 @@ Be specific about what you want done.`,
|
|
|
1676
1731
|
properties: {},
|
|
1677
1732
|
required: []
|
|
1678
1733
|
}
|
|
1734
|
+
},
|
|
1735
|
+
{
|
|
1736
|
+
name: 'lia_todo_write',
|
|
1737
|
+
description: `Update your visible todo list displayed inline in the chat. Use this to show the user what you're working on.
|
|
1738
|
+
|
|
1739
|
+
Unlike lia_plan (internal only), this creates a VISIBLE todo list that appears in the chat conversation.
|
|
1740
|
+
- Use for multi-step tasks to show real-time progress
|
|
1741
|
+
- Update status as you complete each item
|
|
1742
|
+
- Only have ONE item as in_progress at a time
|
|
1743
|
+
- Link workerId to show actual worker progress percentage`,
|
|
1744
|
+
input_schema: {
|
|
1745
|
+
type: 'object',
|
|
1746
|
+
properties: {
|
|
1747
|
+
todos: {
|
|
1748
|
+
type: 'array',
|
|
1749
|
+
items: {
|
|
1750
|
+
type: 'object',
|
|
1751
|
+
properties: {
|
|
1752
|
+
id: { type: 'string', description: 'Unique identifier for the todo (optional, auto-generated if not provided)' },
|
|
1753
|
+
content: { type: 'string', description: 'Imperative form: "Run tests", "Build component"' },
|
|
1754
|
+
activeForm: { type: 'string', description: 'Present continuous: "Running tests", "Building component"' },
|
|
1755
|
+
status: { type: 'string', enum: ['pending', 'in_progress', 'completed'] },
|
|
1756
|
+
workerId: { type: 'string', description: 'Optional: link to a spawned worker ID for real progress tracking' }
|
|
1757
|
+
},
|
|
1758
|
+
required: ['content', 'activeForm', 'status']
|
|
1759
|
+
},
|
|
1760
|
+
description: 'The full todo list (replaces existing list)'
|
|
1761
|
+
}
|
|
1762
|
+
},
|
|
1763
|
+
required: ['todos']
|
|
1764
|
+
}
|
|
1679
1765
|
}
|
|
1680
1766
|
];
|
|
1681
1767
|
}
|
|
@@ -1829,6 +1915,9 @@ Be specific about what you want done.`,
|
|
|
1829
1915
|
case 'lia_get_queue':
|
|
1830
1916
|
result = this.executeLiaGetQueue();
|
|
1831
1917
|
break;
|
|
1918
|
+
case 'lia_todo_write':
|
|
1919
|
+
result = this.executeLiaTodoWrite(input.todos);
|
|
1920
|
+
break;
|
|
1832
1921
|
default:
|
|
1833
1922
|
result = { success: false, output: `Unknown tool: ${toolUse.name}` };
|
|
1834
1923
|
}
|
|
@@ -2392,6 +2481,42 @@ Be specific about what you want done.`,
|
|
|
2392
2481
|
return { success: false, output: `Failed to get queue: ${error}` };
|
|
2393
2482
|
}
|
|
2394
2483
|
}
|
|
2484
|
+
/**
|
|
2485
|
+
* Update visible todo list (displayed inline in chat)
|
|
2486
|
+
*/
|
|
2487
|
+
executeLiaTodoWrite(todos) {
|
|
2488
|
+
try {
|
|
2489
|
+
// Assign IDs if missing
|
|
2490
|
+
this.currentTodos = todos.map((todo, i) => ({
|
|
2491
|
+
...todo,
|
|
2492
|
+
id: todo.id || `todo_${Date.now()}_${i}`,
|
|
2493
|
+
}));
|
|
2494
|
+
// Update progress from linked workers
|
|
2495
|
+
const allWorkers = this.getWorkerStatus();
|
|
2496
|
+
for (const todo of this.currentTodos) {
|
|
2497
|
+
if (todo.workerId && todo.status === 'in_progress') {
|
|
2498
|
+
const workerStatus = allWorkers.find(w => w.id === todo.workerId);
|
|
2499
|
+
if (workerStatus) {
|
|
2500
|
+
todo.progress = workerStatus.progress;
|
|
2501
|
+
}
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
// Broadcast to UI
|
|
2505
|
+
this.todoUpdateCallback?.(this.currentTodos);
|
|
2506
|
+
const counts = {
|
|
2507
|
+
pending: this.currentTodos.filter(t => t.status === 'pending').length,
|
|
2508
|
+
inProgress: this.currentTodos.filter(t => t.status === 'in_progress').length,
|
|
2509
|
+
completed: this.currentTodos.filter(t => t.status === 'completed').length,
|
|
2510
|
+
};
|
|
2511
|
+
return {
|
|
2512
|
+
success: true,
|
|
2513
|
+
output: `Todo list updated: ${counts.completed}/${this.currentTodos.length} completed, ${counts.inProgress} in progress`
|
|
2514
|
+
};
|
|
2515
|
+
}
|
|
2516
|
+
catch (error) {
|
|
2517
|
+
return { success: false, output: `Failed to update todo list: ${error}` };
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2395
2520
|
/**
|
|
2396
2521
|
* Format tool preview for user
|
|
2397
2522
|
*/
|
|
@@ -2447,6 +2572,8 @@ Be specific about what you want done.`,
|
|
|
2447
2572
|
return `Queuing task: ${input.task.slice(0, 50)}...`;
|
|
2448
2573
|
case 'lia_get_queue':
|
|
2449
2574
|
return 'Checking my task queue...';
|
|
2575
|
+
case 'lia_todo_write':
|
|
2576
|
+
return null; // Don't show preview - the todo list itself is the UI
|
|
2450
2577
|
default:
|
|
2451
2578
|
return null;
|
|
2452
2579
|
}
|
package/dist/websocket.d.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* - Streams responses as they're generated
|
|
7
7
|
* - Supports interruption and progress updates
|
|
8
8
|
*/
|
|
9
|
-
import type { WorkerStatus } from './orchestrator.js';
|
|
9
|
+
import type { WorkerStatus, LiaTodoItem } from './orchestrator.js';
|
|
10
10
|
import { AssetResponse } from './core/asset-api.js';
|
|
11
11
|
export type MessageHandler = (message: WebSocketMessage) => Promise<void>;
|
|
12
12
|
export type StreamHandler = (chunk: string) => void;
|
|
@@ -15,7 +15,6 @@ export interface WebSocketMessage {
|
|
|
15
15
|
id?: string;
|
|
16
16
|
content?: string;
|
|
17
17
|
timestamp?: number;
|
|
18
|
-
apiKey?: string;
|
|
19
18
|
requestId?: string;
|
|
20
19
|
assetId?: string;
|
|
21
20
|
groupId?: string;
|
|
@@ -67,6 +66,10 @@ export declare class AgentWebSocket {
|
|
|
67
66
|
* Send workers status update for progress bars
|
|
68
67
|
*/
|
|
69
68
|
sendWorkersUpdate(workers: WorkerStatus[]): void;
|
|
69
|
+
/**
|
|
70
|
+
* Send todo list update for inline display in chat
|
|
71
|
+
*/
|
|
72
|
+
sendTodoUpdate(todos: LiaTodoItem[]): void;
|
|
70
73
|
/**
|
|
71
74
|
* Send gallery command (AI controls the gallery)
|
|
72
75
|
*/
|
package/dist/websocket.js
CHANGED
|
@@ -169,6 +169,16 @@ export class AgentWebSocket {
|
|
|
169
169
|
workers
|
|
170
170
|
});
|
|
171
171
|
}
|
|
172
|
+
/**
|
|
173
|
+
* Send todo list update for inline display in chat
|
|
174
|
+
*/
|
|
175
|
+
sendTodoUpdate(todos) {
|
|
176
|
+
this.sendToServer({
|
|
177
|
+
type: 'lia_todo_update',
|
|
178
|
+
todos,
|
|
179
|
+
timestamp: Date.now()
|
|
180
|
+
});
|
|
181
|
+
}
|
|
172
182
|
/**
|
|
173
183
|
* Send gallery command (AI controls the gallery)
|
|
174
184
|
*/
|