@siftd/connect-agent 0.2.38 → 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 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 for full orchestrator mode)
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
- ### Orchestrator Mode (Recommended)
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` - Check current mode (orchestrator vs simple)
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
- console.log('[AGENT] No API key - using simple relay mode');
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
- console.log('[AGENT] No userId configured - using simple relay mode');
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, apiKey) {
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
- }, apiKey // Pass per-request API key
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 orchestrator
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, message.apiKey // Use per-message API key if provided
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
- if (orchestrator) {
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
- // Initialize orchestrator
376
- orchestrator = initOrchestrator();
377
- const mode = orchestrator ? 'ORCHESTRATOR' : 'SIMPLE RELAY';
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
- if (orchestrator) {
389
- console.log('[AGENT] Features: Memory, Scheduling, Worker Delegation');
390
- // Initialize orchestrator (indexes filesystem into memory)
391
- await orchestrator.initialize();
392
- // Load hub context on startup
393
- const hubContext = loadHubContext();
394
- if (hubContext.agentIdentity) {
395
- console.log('[AGENT] Hub: AGENTS.md loaded');
396
- }
397
- if (hubContext.claudeMd) {
398
- console.log('[AGENT] Hub: CLAUDE.md loaded');
399
- }
400
- if (hubContext.landmarks) {
401
- console.log('[AGENT] Hub: LANDMARKS.md loaded');
402
- }
403
- // Check scratchpad for pending plans
404
- const scratchpad = readScratchpad();
405
- if (scratchpad && scratchpad.length > 100) {
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
- const previewWorker = startPreviewWorker({
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: orchestrator ? ['orchestrator', 'memory'] : [],
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
- // Set up worker callbacks
453
- if (orchestrator) {
454
- // Progress bars
455
- orchestrator.setWorkerStatusCallback((workers) => {
456
- if (wsClient?.connected()) {
457
- wsClient.sendWorkersUpdate(workers);
458
- }
459
- const running = workers.filter(w => w.status === 'running');
460
- if (running.length > 0) {
461
- console.log(`[WORKERS] ${running.length} running`);
462
- }
463
- });
464
- // Gallery updates - send worker assets for UI gallery view
465
- orchestrator.setGalleryCallback((galleryWorkers) => {
466
- if (wsClient?.connected()) {
467
- wsClient.sendGalleryWorkers(galleryWorkers);
468
- const totalAssets = galleryWorkers.reduce((sum, w) => sum + w.assets.length, 0);
469
- console.log(`[GALLERY] ${galleryWorkers.length} workers, ${totalAssets} assets`);
470
- }
471
- });
472
- // Worker results - send to user when workers complete
473
- orchestrator.setWorkerResultCallback((workerId, result) => {
474
- console.log(`[WORKER DONE] ${workerId}: ${result.slice(0, 100)}...`);
475
- if (wsClient?.connected()) {
476
- // Send as response with worker ID as message ID
477
- wsClient.sendResponse(workerId, `**Worker completed:**\n\n${result}`);
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
@@ -2,7 +2,6 @@ export interface Message {
2
2
  id: string;
3
3
  content: string;
4
4
  timestamp: number;
5
- apiKey?: string;
6
5
  }
7
6
  export interface ConnectResponse {
8
7
  success: boolean;
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(`Orchestrator mode: ${getAnthropicApiKey() ? 'Yes (API key saved)' : 'No (simple relay)'}`);
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();
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Lia's Internal Task Queue
3
+ *
4
+ * This manages Lia's own todo list - separate from user /todos.
5
+ * Allows async message processing where users can keep sending
6
+ * while Lia works through her queue.
7
+ */
8
+ export type TaskPriority = 'urgent' | 'high' | 'normal' | 'low';
9
+ export type TaskStatus = 'pending' | 'in_progress' | 'completed' | 'failed' | 'cancelled';
10
+ export interface LiaTask {
11
+ id: string;
12
+ type: 'user_message' | 'follow_up' | 'scheduled' | 'background';
13
+ content: string;
14
+ priority: TaskPriority;
15
+ status: TaskStatus;
16
+ createdAt: Date;
17
+ startedAt?: Date;
18
+ completedAt?: Date;
19
+ result?: string;
20
+ error?: string;
21
+ metadata?: {
22
+ userId?: string;
23
+ messageId?: string;
24
+ parentTaskId?: string;
25
+ context?: 'personal' | 'team';
26
+ orgId?: string;
27
+ };
28
+ }
29
+ export interface LiaPlan {
30
+ id: string;
31
+ goal: string;
32
+ steps: LiaPlanStep[];
33
+ status: 'planning' | 'executing' | 'completed' | 'failed';
34
+ createdAt: Date;
35
+ completedAt?: Date;
36
+ }
37
+ export interface LiaPlanStep {
38
+ id: string;
39
+ description: string;
40
+ status: TaskStatus;
41
+ taskId?: string;
42
+ notes?: string;
43
+ }
44
+ export type TaskQueueCallback = (task: LiaTask) => void;
45
+ export type PlanCallback = (plan: LiaPlan) => void;
46
+ /**
47
+ * Lia's task queue - manages her internal todo list
48
+ */
49
+ export declare class LiaTaskQueue {
50
+ private tasks;
51
+ private plans;
52
+ private queue;
53
+ private currentTask;
54
+ private isProcessing;
55
+ private onTaskUpdate?;
56
+ private onPlanUpdate?;
57
+ private processTask?;
58
+ constructor(options?: {
59
+ onTaskUpdate?: TaskQueueCallback;
60
+ onPlanUpdate?: PlanCallback;
61
+ processTask?: (task: LiaTask) => Promise<string>;
62
+ });
63
+ /**
64
+ * Add a new task to the queue
65
+ */
66
+ addTask(task: Omit<LiaTask, 'id' | 'status' | 'createdAt'>): LiaTask;
67
+ /**
68
+ * Insert task ID into queue based on priority
69
+ */
70
+ private insertByPriority;
71
+ /**
72
+ * Start processing the queue
73
+ */
74
+ startProcessing(): Promise<void>;
75
+ /**
76
+ * Get the current task being processed
77
+ */
78
+ getCurrentTask(): LiaTask | null;
79
+ /**
80
+ * Get all pending tasks
81
+ */
82
+ getPendingTasks(): LiaTask[];
83
+ /**
84
+ * Get recent tasks (last N)
85
+ */
86
+ getRecentTasks(limit?: number): LiaTask[];
87
+ /**
88
+ * Get task by ID
89
+ */
90
+ getTask(taskId: string): LiaTask | null;
91
+ /**
92
+ * Cancel a pending task
93
+ */
94
+ cancelTask(taskId: string): boolean;
95
+ /**
96
+ * Create a plan (breaks down a goal into steps)
97
+ */
98
+ createPlan(goal: string, steps: string[]): LiaPlan;
99
+ /**
100
+ * Update a plan step
101
+ */
102
+ updatePlanStep(planId: string, stepId: string, update: Partial<LiaPlanStep>): boolean;
103
+ /**
104
+ * Get current plan
105
+ */
106
+ getCurrentPlan(): LiaPlan | null;
107
+ /**
108
+ * Get all plans
109
+ */
110
+ getPlans(): LiaPlan[];
111
+ /**
112
+ * Get queue status for display
113
+ */
114
+ getStatus(): {
115
+ isProcessing: boolean;
116
+ currentTask: LiaTask | null;
117
+ pendingCount: number;
118
+ queuePreview: string[];
119
+ };
120
+ /**
121
+ * Format tasks as readable todo list (for LLM context)
122
+ */
123
+ formatAsTodoList(): string;
124
+ }