@siftd/connect-agent 0.1.0 → 0.2.0

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.
@@ -0,0 +1,532 @@
1
+ /**
2
+ * Master Orchestrator Agent
3
+ *
4
+ * This is the BRAIN - it orchestrates, remembers, and delegates.
5
+ * It does NOT do the work itself. Claude Code CLI workers do the work.
6
+ */
7
+ import Anthropic from '@anthropic-ai/sdk';
8
+ import { spawn } from 'child_process';
9
+ import { AdvancedMemoryStore } from './core/memory-advanced.js';
10
+ import { TaskScheduler } from './core/scheduler.js';
11
+ const SYSTEM_PROMPT = `You are the MASTER ORCHESTRATOR - the user's intelligent assistant accessed via web browser.
12
+
13
+ CRITICAL IDENTITY:
14
+ You are NOT a coding assistant. You are NOT a worker. You are the ORCHESTRATOR.
15
+ Your job is to UNDERSTAND, REMEMBER, DELEGATE, and SYNTHESIZE.
16
+
17
+ You have a powerful worker at your disposal: Claude Code CLI. It can:
18
+ - Read, write, edit any file on the user's machine
19
+ - Run any shell command
20
+ - Explore codebases
21
+ - Make multi-file changes
22
+ - Run builds, tests, deployments
23
+ - Do complex development work
24
+
25
+ YOUR ROLE vs WORKER ROLE:
26
+ ┌─────────────────────────────────────────────────────────────────┐
27
+ │ YOU (Master Orchestrator) │ WORKER (Claude Code CLI) │
28
+ ├────────────────────────────────────┼────────────────────────────┤
29
+ │ Understand user intent │ Execute file operations │
30
+ │ Remember user preferences │ Run shell commands │
31
+ │ Track ongoing projects │ Make code changes │
32
+ │ Decide what needs to be done │ Explore codebases │
33
+ │ Delegate complex tasks │ Build/test/deploy │
34
+ │ Synthesize results for user │ Handle multi-step tasks │
35
+ │ Schedule future work │ Do the actual work │
36
+ │ Maintain conversation context │ Work autonomously │
37
+ └────────────────────────────────────┴────────────────────────────┘
38
+
39
+ DELEGATION STRATEGY:
40
+ - ALWAYS delegate: file operations, code changes, builds, tests, deployments
41
+ - ALWAYS delegate: multi-step tasks, codebase exploration
42
+ - ALWAYS delegate: anything requiring filesystem or shell access
43
+ - Handle yourself: simple questions, scheduling, memory operations, conversation
44
+
45
+ NEVER TRY TO DO THE WORK YOURSELF. You don't have filesystem access. You don't have shell access.
46
+ Your only tools are: delegate_to_worker, remember, search_memory, schedule_task.
47
+
48
+ WHEN YOU DELEGATE:
49
+ 1. Be specific about what you want done
50
+ 2. Include context about user preferences from memory
51
+ 3. Tell the user you're delegating ("Let me have my worker handle that...")
52
+ 4. Wait for results and synthesize them into a clear response
53
+
54
+ MEMORY SYSTEM:
55
+ You have persistent memory. Use it to:
56
+ - Remember user preferences ("I prefer TypeScript", "I use pnpm")
57
+ - Track ongoing projects and their status
58
+ - Note user's communication style
59
+ - Store important facts about the user's system/environment
60
+ - Remember past conversations and decisions
61
+
62
+ COMMUNICATION STYLE:
63
+ - Be concise - this is a chat interface, not documentation
64
+ - Be direct - say what you're doing
65
+ - Be helpful - anticipate needs based on memory
66
+ - Acknowledge the user as someone you know and work with
67
+
68
+ You are the user's intelligent agent - you remember them, understand their projects,
69
+ and orchestrate work on their behalf. You are the persistent layer that makes
70
+ working with AI feel like having a knowledgeable assistant who knows you.`;
71
+ export class MasterOrchestrator {
72
+ client;
73
+ model;
74
+ maxTokens;
75
+ memory;
76
+ scheduler;
77
+ jobs = new Map();
78
+ jobCounter = 0;
79
+ userId;
80
+ workspaceDir;
81
+ constructor(options) {
82
+ this.client = new Anthropic({ apiKey: options.apiKey });
83
+ this.model = options.model || 'claude-sonnet-4-20250514';
84
+ this.maxTokens = options.maxTokens || 4096;
85
+ this.userId = options.userId;
86
+ this.workspaceDir = options.workspaceDir || process.env.HOME || '/tmp';
87
+ // Initialize memory with user-specific paths
88
+ this.memory = new AdvancedMemoryStore({
89
+ dbPath: `memories_${options.userId}.db`,
90
+ vectorPath: `./memory_vectors_${options.userId}`,
91
+ voyageApiKey: options.voyageApiKey
92
+ });
93
+ this.scheduler = new TaskScheduler(`scheduled_${options.userId}.json`);
94
+ }
95
+ /**
96
+ * Process a user message
97
+ */
98
+ async processMessage(message, conversationHistory = [], sendMessage) {
99
+ // Build context from memory
100
+ const memoryContext = await this.getMemoryContext(message);
101
+ // Build system prompt with memory context
102
+ const systemWithMemory = memoryContext
103
+ ? `${SYSTEM_PROMPT}\n\nRELEVANT MEMORIES:\n${memoryContext}`
104
+ : SYSTEM_PROMPT;
105
+ // Add user message
106
+ const messages = [
107
+ ...conversationHistory,
108
+ { role: 'user', content: message }
109
+ ];
110
+ try {
111
+ const response = await this.runAgentLoop(messages, systemWithMemory, sendMessage);
112
+ // Auto-remember important things from the conversation
113
+ await this.autoRemember(message, response);
114
+ return response;
115
+ }
116
+ catch (error) {
117
+ const errorMessage = error instanceof Error ? error.message : String(error);
118
+ return `Error: ${errorMessage}`;
119
+ }
120
+ }
121
+ /**
122
+ * Get relevant memory context for a message
123
+ */
124
+ async getMemoryContext(message) {
125
+ try {
126
+ const memories = await this.memory.search(message, { limit: 5 });
127
+ if (memories.length === 0)
128
+ return null;
129
+ return memories.map(m => `[${m.type}] ${m.content}`).join('\n');
130
+ }
131
+ catch {
132
+ return null;
133
+ }
134
+ }
135
+ /**
136
+ * Auto-remember important information from conversations
137
+ */
138
+ async autoRemember(userMessage, response) {
139
+ // Look for preference indicators
140
+ const prefIndicators = ['i prefer', 'i like', 'i always', 'i never', 'i use', 'my favorite'];
141
+ const lower = userMessage.toLowerCase();
142
+ for (const indicator of prefIndicators) {
143
+ if (lower.includes(indicator)) {
144
+ await this.memory.remember(userMessage, {
145
+ type: 'semantic',
146
+ source: 'auto-extract',
147
+ importance: 0.7,
148
+ tags: ['preference', 'user']
149
+ });
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ /**
155
+ * Run the agentic loop
156
+ */
157
+ async runAgentLoop(messages, system, sendMessage) {
158
+ const tools = this.getToolDefinitions();
159
+ let currentMessages = [...messages];
160
+ let iterations = 0;
161
+ const maxIterations = 15;
162
+ while (iterations < maxIterations) {
163
+ iterations++;
164
+ const response = await this.client.messages.create({
165
+ model: this.model,
166
+ max_tokens: this.maxTokens,
167
+ system,
168
+ tools,
169
+ messages: currentMessages
170
+ });
171
+ // Check if done
172
+ if (response.stop_reason === 'end_turn' || !this.hasToolUse(response.content)) {
173
+ return this.extractText(response.content);
174
+ }
175
+ // Process tool calls
176
+ const toolResults = await this.processToolCalls(response.content, sendMessage);
177
+ // Continue conversation
178
+ currentMessages = [
179
+ ...currentMessages,
180
+ { role: 'assistant', content: response.content },
181
+ { role: 'user', content: toolResults }
182
+ ];
183
+ }
184
+ return 'Maximum iterations reached.';
185
+ }
186
+ /**
187
+ * Get tool definitions for the orchestrator
188
+ */
189
+ getToolDefinitions() {
190
+ return [
191
+ {
192
+ name: 'delegate_to_worker',
193
+ description: `Delegate a task to Claude Code CLI worker. This is your PRIMARY tool.
194
+
195
+ Use this for ANY task that involves:
196
+ - Reading, writing, or editing files
197
+ - Running shell commands
198
+ - Exploring codebases
199
+ - Making code changes
200
+ - Building, testing, deploying
201
+ - ANY filesystem or system operation
202
+
203
+ The worker will execute the task autonomously and return results.
204
+ Be specific about what you want done.`,
205
+ input_schema: {
206
+ type: 'object',
207
+ properties: {
208
+ task: {
209
+ type: 'string',
210
+ description: 'Detailed description of what the worker should do'
211
+ },
212
+ context: {
213
+ type: 'string',
214
+ description: 'Additional context (user preferences, project info, etc)'
215
+ },
216
+ working_directory: {
217
+ type: 'string',
218
+ description: 'Directory to work in (defaults to user home)'
219
+ }
220
+ },
221
+ required: ['task']
222
+ }
223
+ },
224
+ {
225
+ name: 'remember',
226
+ description: 'Store something in persistent memory. Use for user preferences, project info, important facts.',
227
+ input_schema: {
228
+ type: 'object',
229
+ properties: {
230
+ content: {
231
+ type: 'string',
232
+ description: 'What to remember'
233
+ },
234
+ type: {
235
+ type: 'string',
236
+ enum: ['episodic', 'semantic', 'procedural'],
237
+ description: 'Memory type: episodic (events), semantic (facts), procedural (how-to)'
238
+ },
239
+ importance: {
240
+ type: 'number',
241
+ description: 'Importance 0-1 (higher = remembered longer)'
242
+ }
243
+ },
244
+ required: ['content']
245
+ }
246
+ },
247
+ {
248
+ name: 'search_memory',
249
+ description: 'Search your memory for relevant information. Use before delegating to include user context.',
250
+ input_schema: {
251
+ type: 'object',
252
+ properties: {
253
+ query: {
254
+ type: 'string',
255
+ description: 'What to search for'
256
+ },
257
+ type: {
258
+ type: 'string',
259
+ enum: ['episodic', 'semantic', 'procedural'],
260
+ description: 'Filter by memory type'
261
+ }
262
+ },
263
+ required: ['query']
264
+ }
265
+ },
266
+ {
267
+ name: 'schedule_task',
268
+ description: 'Schedule a task for future execution.',
269
+ input_schema: {
270
+ type: 'object',
271
+ properties: {
272
+ task: {
273
+ type: 'string',
274
+ description: 'The task to execute later'
275
+ },
276
+ when: {
277
+ type: 'string',
278
+ description: 'When to run (e.g., "in 30 minutes", "daily at 9:00", "tomorrow at 14:00")'
279
+ }
280
+ },
281
+ required: ['task', 'when']
282
+ }
283
+ },
284
+ {
285
+ name: 'list_scheduled',
286
+ description: 'List all scheduled tasks.',
287
+ input_schema: {
288
+ type: 'object',
289
+ properties: {},
290
+ required: []
291
+ }
292
+ },
293
+ {
294
+ name: 'memory_stats',
295
+ description: 'Get statistics about your memory system.',
296
+ input_schema: {
297
+ type: 'object',
298
+ properties: {},
299
+ required: []
300
+ }
301
+ }
302
+ ];
303
+ }
304
+ /**
305
+ * Process tool calls
306
+ */
307
+ async processToolCalls(content, sendMessage) {
308
+ const toolUseBlocks = content.filter((block) => block.type === 'tool_use');
309
+ const results = [];
310
+ for (const toolUse of toolUseBlocks) {
311
+ const input = toolUse.input;
312
+ let result;
313
+ // Notify user of action
314
+ if (sendMessage) {
315
+ const preview = this.formatToolPreview(toolUse.name, input);
316
+ if (preview)
317
+ await sendMessage(preview);
318
+ }
319
+ switch (toolUse.name) {
320
+ case 'delegate_to_worker':
321
+ result = await this.delegateToWorker(input.task, input.context, input.working_directory);
322
+ break;
323
+ case 'remember':
324
+ result = await this.executeRemember(input.content, input.type, input.importance);
325
+ break;
326
+ case 'search_memory':
327
+ result = await this.executeSearchMemory(input.query, input.type);
328
+ break;
329
+ case 'schedule_task':
330
+ result = await this.executeScheduleTask(input.task, input.when);
331
+ break;
332
+ case 'list_scheduled':
333
+ result = await this.executeListScheduled();
334
+ break;
335
+ case 'memory_stats':
336
+ result = await this.executeMemoryStats();
337
+ break;
338
+ default:
339
+ result = { success: false, output: `Unknown tool: ${toolUse.name}` };
340
+ }
341
+ results.push({
342
+ type: 'tool_result',
343
+ tool_use_id: toolUse.id,
344
+ content: result.output,
345
+ is_error: !result.success
346
+ });
347
+ }
348
+ return results;
349
+ }
350
+ /**
351
+ * Delegate task to Claude Code CLI worker
352
+ */
353
+ async delegateToWorker(task, context, workingDir) {
354
+ const id = `worker_${Date.now()}_${++this.jobCounter}`;
355
+ const cwd = workingDir || this.workspaceDir;
356
+ // Build prompt for worker
357
+ let prompt = task;
358
+ if (context) {
359
+ prompt = `Context: ${context}\n\nTask: ${task}`;
360
+ }
361
+ prompt += '\n\nKeep your response concise and focused on results.';
362
+ console.log(`[ORCHESTRATOR] Delegating to worker ${id}: ${task.slice(0, 80)}...`);
363
+ return new Promise((resolve) => {
364
+ const job = {
365
+ id,
366
+ task: task.slice(0, 200),
367
+ status: 'running',
368
+ startTime: Date.now(),
369
+ output: ''
370
+ };
371
+ // Spawn Claude Code CLI - use -p flag and pipe prompt to stdin
372
+ const child = spawn('claude', ['-p'], {
373
+ cwd,
374
+ env: { ...process.env },
375
+ shell: false
376
+ });
377
+ job.process = child;
378
+ this.jobs.set(id, job);
379
+ // Send prompt to stdin
380
+ if (child.stdin) {
381
+ child.stdin.write(prompt);
382
+ child.stdin.end();
383
+ }
384
+ // Timeout after 5 minutes
385
+ const timeout = setTimeout(() => {
386
+ if (job.status === 'running') {
387
+ job.status = 'timeout';
388
+ job.endTime = Date.now();
389
+ child.kill('SIGTERM');
390
+ resolve({
391
+ success: false,
392
+ output: `Worker timed out after 5 minutes.\nPartial output:\n${job.output.slice(-1000)}`
393
+ });
394
+ }
395
+ }, 5 * 60 * 1000);
396
+ child.stdout?.on('data', (data) => {
397
+ job.output += data.toString();
398
+ });
399
+ child.stderr?.on('data', (data) => {
400
+ // Ignore status messages
401
+ const text = data.toString();
402
+ if (!text.includes('Checking') && !text.includes('Connected')) {
403
+ job.output += text;
404
+ }
405
+ });
406
+ child.on('close', (code) => {
407
+ clearTimeout(timeout);
408
+ job.status = code === 0 ? 'completed' : 'failed';
409
+ job.endTime = Date.now();
410
+ const duration = ((job.endTime - job.startTime) / 1000).toFixed(1);
411
+ console.log(`[ORCHESTRATOR] Worker ${id} finished in ${duration}s`);
412
+ resolve({
413
+ success: code === 0,
414
+ output: job.output.trim() || '(No output)'
415
+ });
416
+ });
417
+ child.on('error', (err) => {
418
+ clearTimeout(timeout);
419
+ job.status = 'failed';
420
+ job.endTime = Date.now();
421
+ resolve({
422
+ success: false,
423
+ output: `Worker error: ${err.message}`
424
+ });
425
+ });
426
+ });
427
+ }
428
+ /**
429
+ * Execute remember tool
430
+ */
431
+ async executeRemember(content, type, importance) {
432
+ try {
433
+ const id = await this.memory.remember(content, {
434
+ type: type || undefined,
435
+ importance
436
+ });
437
+ return { success: true, output: `Remembered (${id})` };
438
+ }
439
+ catch (error) {
440
+ return { success: false, output: `Failed to remember: ${error}` };
441
+ }
442
+ }
443
+ /**
444
+ * Execute search memory tool
445
+ */
446
+ async executeSearchMemory(query, type) {
447
+ try {
448
+ const memories = await this.memory.search(query, {
449
+ type: type || undefined,
450
+ limit: 10
451
+ });
452
+ if (memories.length === 0) {
453
+ return { success: true, output: 'No relevant memories found.' };
454
+ }
455
+ const output = memories.map(m => `[${m.type}] (${(m.importance * 100).toFixed(0)}%) ${m.content}`).join('\n\n');
456
+ return { success: true, output };
457
+ }
458
+ catch (error) {
459
+ return { success: false, output: `Memory search failed: ${error}` };
460
+ }
461
+ }
462
+ /**
463
+ * Execute schedule task tool
464
+ */
465
+ async executeScheduleTask(task, when) {
466
+ try {
467
+ const taskId = this.scheduler.schedule(task, when, this.userId);
468
+ const scheduled = this.scheduler.get(taskId);
469
+ return {
470
+ success: true,
471
+ output: `Scheduled: ${taskId}\nWhen: ${scheduled ? this.scheduler.formatSchedule(scheduled.schedule) : when}`
472
+ };
473
+ }
474
+ catch (error) {
475
+ return { success: false, output: `Scheduling failed: ${error}` };
476
+ }
477
+ }
478
+ /**
479
+ * Execute list scheduled tool
480
+ */
481
+ async executeListScheduled() {
482
+ const tasks = this.scheduler.list();
483
+ if (tasks.length === 0) {
484
+ return { success: true, output: 'No scheduled tasks.' };
485
+ }
486
+ const output = tasks.map(t => `${t.enabled ? '✓' : '⏸'} ${t.id}: ${t.task.slice(0, 50)}... (${this.scheduler.formatSchedule(t.schedule)})`).join('\n');
487
+ return { success: true, output };
488
+ }
489
+ /**
490
+ * Execute memory stats tool
491
+ */
492
+ async executeMemoryStats() {
493
+ const stats = this.memory.stats();
494
+ return {
495
+ success: true,
496
+ output: `Memories: ${stats.total} (episodic: ${stats.byType.episodic}, semantic: ${stats.byType.semantic}, procedural: ${stats.byType.procedural})\nAvg importance: ${(stats.avgImportance * 100).toFixed(0)}%\nAssociations: ${stats.totalAssociations}`
497
+ };
498
+ }
499
+ /**
500
+ * Format tool preview for user
501
+ */
502
+ formatToolPreview(name, input) {
503
+ switch (name) {
504
+ case 'delegate_to_worker':
505
+ return `Delegating to worker: ${input.task.slice(0, 80)}...`;
506
+ case 'remember':
507
+ return `Remembering: ${input.content.slice(0, 50)}...`;
508
+ case 'search_memory':
509
+ return `Searching memory: ${input.query}`;
510
+ case 'schedule_task':
511
+ return `Scheduling: ${input.task} for ${input.when}`;
512
+ default:
513
+ return null;
514
+ }
515
+ }
516
+ hasToolUse(content) {
517
+ return content.some(block => block.type === 'tool_use');
518
+ }
519
+ extractText(content) {
520
+ return content
521
+ .filter(block => block.type === 'text')
522
+ .map(block => block.text)
523
+ .join('\n');
524
+ }
525
+ /**
526
+ * Shutdown cleanly
527
+ */
528
+ shutdown() {
529
+ this.scheduler.shutdown();
530
+ this.memory.close();
531
+ }
532
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@siftd/connect-agent",
3
- "version": "0.1.0",
4
- "description": "Local agent that connects to Connect web app - control Claude Code remotely",
3
+ "version": "0.2.0",
4
+ "description": "Master orchestrator agent - control Claude Code remotely via web",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "bin": {
@@ -18,11 +18,13 @@
18
18
  },
19
19
  "keywords": [
20
20
  "agent",
21
+ "orchestrator",
21
22
  "cli",
22
23
  "connect",
23
24
  "claude",
24
25
  "remote",
25
- "terminal"
26
+ "terminal",
27
+ "memory"
26
28
  ],
27
29
  "repository": {
28
30
  "type": "git",
@@ -35,12 +37,18 @@
35
37
  "access": "public"
36
38
  },
37
39
  "dependencies": {
40
+ "@anthropic-ai/sdk": "^0.39.0",
41
+ "better-sqlite3": "^11.7.0",
38
42
  "commander": "^12.1.0",
39
43
  "conf": "^13.0.1",
40
- "ora": "^8.1.1"
44
+ "node-cron": "^3.0.3",
45
+ "ora": "^8.1.1",
46
+ "vectra": "^0.9.0"
41
47
  },
42
48
  "devDependencies": {
49
+ "@types/better-sqlite3": "^7.6.12",
43
50
  "@types/node": "^22.10.2",
51
+ "@types/node-cron": "^3.0.11",
44
52
  "typescript": "^5.7.2"
45
53
  }
46
54
  }